Files
server/core/src/stores/userStore.ts
2026-02-10 18:46:11 -05:00

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,
};
});