276 lines
7.5 KiB
TypeScript
276 lines
7.5 KiB
TypeScript
import { defineStore } from 'pinia';
|
|
import { ref, computed } from 'vue';
|
|
import { router } from '@KTXC/router';
|
|
import { authenticationService } from '@KTXC/services/authenticationService';
|
|
import { userService } from '@KTXC/services/user/userService';
|
|
import type { AuthenticatedUser } from '@KTXC/types/authenticationTypes';
|
|
import type { UserProfileInterface } from '@KTXC/types/user/userProfileTypes';
|
|
import type { UserSettingsInterface } from '@KTXC/types/user/userSettingsTypes';
|
|
import { UserProfile } from '@KTXC/models/userProfile';
|
|
import { UserSettings } from '@KTXC/models/userSettings';
|
|
|
|
const STORAGE_KEY = 'userStore.auth';
|
|
|
|
// Flush pending updates before page unload
|
|
if (typeof window !== 'undefined') {
|
|
window.addEventListener('beforeunload', () => {
|
|
userService.flushAll();
|
|
});
|
|
}
|
|
|
|
export const useUserStore = defineStore('userStore', () => {
|
|
// =========================================================================
|
|
// State
|
|
// =========================================================================
|
|
|
|
const auth = ref<AuthenticatedUser | null>(
|
|
localStorage.getItem(STORAGE_KEY)
|
|
? (JSON.parse(localStorage.getItem(STORAGE_KEY)!) as AuthenticatedUser)
|
|
: null
|
|
);
|
|
|
|
const profile = ref<UserProfile>(new UserProfile());
|
|
const settings = ref<UserSettings>(new UserSettings());
|
|
const returnUrl = ref<string | null>(null);
|
|
|
|
// =========================================================================
|
|
// Authentication Getters
|
|
// =========================================================================
|
|
|
|
const isAuthenticated = computed(() => auth.value !== null);
|
|
const identifier = computed(() => auth.value?.identifier ?? null);
|
|
const identity = computed(() => auth.value?.identity ?? null);
|
|
const label = computed(() => auth.value?.label ?? null);
|
|
const roles = computed(() => auth.value?.roles ?? []);
|
|
const permissions = computed(() => auth.value?.permissions ?? []);
|
|
|
|
// =========================================================================
|
|
// Profile Getters
|
|
// =========================================================================
|
|
|
|
const profileFields = computed(() => profile.value.fields);
|
|
|
|
const editableProfileFields = computed(() => profile.value.editableFields);
|
|
|
|
const managedProfileFields = computed(() => profile.value.managedFields);
|
|
|
|
// =========================================================================
|
|
// Authentication Actions
|
|
// =========================================================================
|
|
|
|
function setAuth(authUser: AuthenticatedUser): void {
|
|
auth.value = authUser;
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(authUser));
|
|
}
|
|
|
|
function clearAuth(): void {
|
|
auth.value = null;
|
|
localStorage.removeItem(STORAGE_KEY);
|
|
}
|
|
|
|
async function logout(): Promise<void> {
|
|
try {
|
|
// Flush any pending profile/settings updates before logout
|
|
await userService.flushAll();
|
|
|
|
await authenticationService.logout();
|
|
} catch (error) {
|
|
console.warn('Logout request failed, clearing local state:', error);
|
|
} finally {
|
|
clearAuth();
|
|
clearProfile();
|
|
clearSettings();
|
|
router.push('/login');
|
|
}
|
|
}
|
|
|
|
async function refreshToken(): Promise<boolean> {
|
|
try {
|
|
await authenticationService.refresh();
|
|
return true;
|
|
} catch (error) {
|
|
await logout();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// =========================================================================
|
|
// Profile Actions
|
|
// =========================================================================
|
|
|
|
function initProfile(profileData: UserProfileInterface): void {
|
|
profile.value = new UserProfile(profileData);
|
|
}
|
|
|
|
function clearProfile(): void {
|
|
profile.value = new UserProfile();
|
|
}
|
|
|
|
function getProfileField(key: string): any {
|
|
return profile.value.get(key);
|
|
}
|
|
|
|
function setProfileField(key: string, value: any): boolean {
|
|
const success = profile.value.set(key, value);
|
|
if (success) {
|
|
// Debounced update to backend
|
|
userService.updateProfile({ [key]: value }).catch(error => {
|
|
console.error('Failed to update profile:', error);
|
|
});
|
|
}
|
|
return success;
|
|
}
|
|
|
|
function isProfileFieldEditable(key: string): boolean {
|
|
return profile.value.isEditable(key);
|
|
}
|
|
|
|
// =========================================================================
|
|
// Settings Actions
|
|
// =========================================================================
|
|
|
|
function initSettings(settingsData: UserSettingsInterface): void {
|
|
settings.value = new UserSettings(settingsData);
|
|
}
|
|
|
|
function clearSettings(): void {
|
|
settings.value = new UserSettings();
|
|
}
|
|
|
|
function getSetting(key: string): any {
|
|
return settings.value.get(key);
|
|
}
|
|
|
|
function setSetting(key: string, value: any): void {
|
|
settings.value.set(key, value);
|
|
// Debounced update to backend
|
|
userService.updateSettings({ [key]: value }).catch(error => {
|
|
console.error('Failed to update setting:', error);
|
|
});
|
|
}
|
|
|
|
// =========================================================================
|
|
// Permission Checking
|
|
// =========================================================================
|
|
|
|
/**
|
|
* Check if user has a specific permission
|
|
* Supports wildcards: user_manager.users.* matches all user actions
|
|
*/
|
|
function hasPermission(permission: string): boolean {
|
|
const userPermissions = permissions.value;
|
|
|
|
// Exact match
|
|
if (userPermissions.includes(permission)) {
|
|
return true;
|
|
}
|
|
|
|
// Wildcard match
|
|
for (const userPerm of userPermissions) {
|
|
if (userPerm.endsWith('.*')) {
|
|
const prefix = userPerm.slice(0, -2);
|
|
if (permission.startsWith(prefix + '.')) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Full wildcard
|
|
if (userPermissions.includes('*')) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check if user has ANY of the permissions (OR logic)
|
|
*/
|
|
function hasAnyPermission(perms: string[]): boolean {
|
|
return perms.some(p => hasPermission(p));
|
|
}
|
|
|
|
/**
|
|
* Check if user has ALL permissions (AND logic)
|
|
*/
|
|
function hasAllPermissions(perms: string[]): boolean {
|
|
return perms.every(p => hasPermission(p));
|
|
}
|
|
|
|
/**
|
|
* Check if user has a specific role
|
|
*/
|
|
function hasRole(role: string): boolean {
|
|
return roles.value.includes(role);
|
|
}
|
|
|
|
// =========================================================================
|
|
// Initialize from /init endpoint
|
|
// =========================================================================
|
|
|
|
function init(userData: {
|
|
auth?: AuthenticatedUser;
|
|
profile?: UserProfileInterface;
|
|
settings?: UserSettingsInterface
|
|
}): void {
|
|
if (userData.auth) {
|
|
setAuth(userData.auth);
|
|
}
|
|
if (userData.profile) {
|
|
initProfile(userData.profile);
|
|
}
|
|
if (userData.settings) {
|
|
initSettings(userData.settings);
|
|
}
|
|
}
|
|
|
|
return {
|
|
// State
|
|
auth,
|
|
profile,
|
|
settings,
|
|
returnUrl,
|
|
|
|
// Auth getters
|
|
isAuthenticated,
|
|
identifier,
|
|
identity,
|
|
label,
|
|
roles,
|
|
permissions,
|
|
|
|
// Profile getters
|
|
profileFields,
|
|
editableProfileFields,
|
|
managedProfileFields,
|
|
|
|
// Auth actions
|
|
setAuth,
|
|
clearAuth,
|
|
logout,
|
|
refreshToken,
|
|
|
|
// Profile actions
|
|
initProfile,
|
|
clearProfile,
|
|
getProfileField,
|
|
setProfileField,
|
|
isProfileFieldEditable,
|
|
|
|
// Settings actions
|
|
initSettings,
|
|
clearSettings,
|
|
getSetting,
|
|
setSetting,
|
|
|
|
// Permission actions
|
|
hasPermission,
|
|
hasAnyPermission,
|
|
hasAllPermissions,
|
|
hasRole,
|
|
|
|
// Init
|
|
init,
|
|
};
|
|
});
|