krona-front/src/core/session/sessionStore.ts

234 lines
7.9 KiB
TypeScript

import { create } from 'zustand';
import { SystemRoleId, SYSTEM_ROLES } from '../permissions/types';
import { CapabilityId } from '../capabilities/types';
import { BUSINESS_PROFILES } from '../profiles/profiles';
import { BusinessProfile } from '../profiles/types';
import { setActiveGlobalTenantId, getActiveGlobalTenantId } from '../../services/contracts/context';
export interface SessionState {
activeTenantId: string;
activeOrgId: string;
currentUserId: string;
currentRoleId: SystemRoleId;
currentProfileId: string;
activeLocation: string;
capabilities: Record<CapabilityId, boolean>;
// Actions
setActiveTenantId: (tenantId: string) => void;
setActiveOrgId: (orgId: string) => void;
setCurrentUserId: (userId: string) => void;
setCurrentRoleId: (roleId: SystemRoleId) => void;
setCurrentProfileId: (profileId: string) => void;
setActiveLocation: (location: string) => void;
setCapabilities: (capabilities: Record<CapabilityId, boolean>) => void;
toggleCapability: (id: CapabilityId) => void;
resetCapabilitiesToProfile: () => void;
resetSession: () => void;
// RBAC & Permission helpers (Strict deny-by-default)
hasModulePermission: (moduleId: string) => boolean;
getActiveProfile: () => BusinessProfile;
getActiveCapabilities: () => Record<CapabilityId, boolean>;
}
const DEFAULT_PROFILE_ID = 'restaurant_hall_delivery';
const DEFAULT_ROLE_ID: SystemRoleId = 'owner';
const DEFAULT_TENANT_ID = 'tenant_main';
const DEFAULT_ORG_ID = 'org_main';
const DEFAULT_USER_ID = 'user_admin';
function getInitialProfileId(): string {
if (typeof window !== 'undefined') {
const saved = localStorage.getItem('krona_profile_id');
if (saved && BUSINESS_PROFILES[saved]) return saved;
}
return DEFAULT_PROFILE_ID;
}
function getInitialRoleId(): SystemRoleId {
if (typeof window !== 'undefined') {
const saved = localStorage.getItem('krona_role_id') as SystemRoleId;
if (saved && SYSTEM_ROLES[saved]) return saved;
}
return DEFAULT_ROLE_ID;
}
function getInitialTenantId(): string {
if (typeof window !== 'undefined') {
const saved = localStorage.getItem('krona_tenant_id') || localStorage.getItem('krona_active_tenant_id');
if (saved) return saved;
}
return getActiveGlobalTenantId() || DEFAULT_TENANT_ID;
}
function getInitialOrgId(): string {
if (typeof window !== 'undefined') {
const saved = localStorage.getItem('krona_org_id') || localStorage.getItem('krona_active_org_id');
if (saved) return saved;
}
return DEFAULT_ORG_ID;
}
function getInitialUserId(): string {
if (typeof window !== 'undefined') {
const saved = localStorage.getItem('krona_user_id') || localStorage.getItem('krona_current_user_id');
if (saved) return saved;
}
return DEFAULT_USER_ID;
}
function getInitialLocation(profileId: string): string {
if (typeof window !== 'undefined') {
const saved = localStorage.getItem('krona_location') || localStorage.getItem('krona_active_location');
if (saved) return saved;
}
const prof = BUSINESS_PROFILES[profileId] || BUSINESS_PROFILES[DEFAULT_PROFILE_ID];
return prof.locations[0] || 'Горский 1/1';
}
function getInitialCapabilities(profileId: string): Record<CapabilityId, boolean> {
const prof = BUSINESS_PROFILES[profileId] || BUSINESS_PROFILES[DEFAULT_PROFILE_ID];
return { ...prof.capabilities };
}
export const useSessionStore = create<SessionState>((set, get) => {
const initialProfileId = getInitialProfileId();
const initialTenantId = getInitialTenantId();
// Sync global tenant id on startup
setActiveGlobalTenantId(initialTenantId);
return {
activeTenantId: initialTenantId,
activeOrgId: getInitialOrgId(),
currentUserId: getInitialUserId(),
currentRoleId: getInitialRoleId(),
currentProfileId: initialProfileId,
activeLocation: getInitialLocation(initialProfileId),
capabilities: getInitialCapabilities(initialProfileId),
setActiveTenantId: (activeTenantId: string) => {
if (typeof window !== 'undefined') {
localStorage.setItem('krona_tenant_id', activeTenantId);
localStorage.setItem('krona_active_tenant_id', activeTenantId);
}
setActiveGlobalTenantId(activeTenantId);
set({ activeTenantId });
},
setActiveOrgId: (activeOrgId: string) => {
if (typeof window !== 'undefined') {
localStorage.setItem('krona_org_id', activeOrgId);
localStorage.setItem('krona_active_org_id', activeOrgId);
}
set({ activeOrgId });
},
setCurrentUserId: (currentUserId: string) => {
if (typeof window !== 'undefined') {
localStorage.setItem('krona_user_id', currentUserId);
localStorage.setItem('krona_current_user_id', currentUserId);
}
set({ currentUserId });
},
setCurrentRoleId: (currentRoleId: SystemRoleId) => {
if (typeof window !== 'undefined') {
localStorage.setItem('krona_role_id', currentRoleId);
}
set({ currentRoleId });
},
setCurrentProfileId: (currentProfileId: string) => {
if (typeof window !== 'undefined') {
localStorage.setItem('krona_profile_id', currentProfileId);
}
const profile = BUSINESS_PROFILES[currentProfileId] || BUSINESS_PROFILES[DEFAULT_PROFILE_ID];
const newLoc = profile.locations[0] || 'Главная точка';
if (typeof window !== 'undefined') {
localStorage.setItem('krona_location', newLoc);
}
set({
currentProfileId,
capabilities: { ...profile.capabilities },
activeLocation: newLoc,
});
},
setActiveLocation: (activeLocation: string) => {
if (typeof window !== 'undefined') {
localStorage.setItem('krona_location', activeLocation);
localStorage.setItem('krona_active_location', activeLocation);
}
set({ activeLocation });
},
setCapabilities: (capabilities: Record<CapabilityId, boolean>) => {
set({ capabilities });
},
toggleCapability: (id: CapabilityId) => {
set((state) => ({
capabilities: {
...state.capabilities,
[id]: !state.capabilities[id],
},
}));
},
resetCapabilitiesToProfile: () => {
const state = get();
const profile = BUSINESS_PROFILES[state.currentProfileId] || BUSINESS_PROFILES[DEFAULT_PROFILE_ID];
set({ capabilities: { ...profile.capabilities } });
},
resetSession: () => {
const defaultProf = BUSINESS_PROFILES[DEFAULT_PROFILE_ID];
setActiveGlobalTenantId(DEFAULT_TENANT_ID);
if (typeof window !== 'undefined') {
localStorage.removeItem('krona_tenant_id');
localStorage.removeItem('krona_active_tenant_id');
localStorage.removeItem('krona_org_id');
localStorage.removeItem('krona_active_org_id');
localStorage.removeItem('krona_user_id');
localStorage.removeItem('krona_current_user_id');
localStorage.removeItem('krona_role_id');
localStorage.removeItem('krona_profile_id');
localStorage.removeItem('krona_location');
}
set({
activeTenantId: DEFAULT_TENANT_ID,
activeOrgId: DEFAULT_ORG_ID,
currentUserId: DEFAULT_USER_ID,
currentRoleId: DEFAULT_ROLE_ID,
currentProfileId: DEFAULT_PROFILE_ID,
activeLocation: defaultProf.locations[0] || 'Горский 1/1',
capabilities: { ...defaultProf.capabilities },
});
},
// Strict deny-by-default RBAC check
hasModulePermission: (moduleId: string) => {
const state = get();
const role = SYSTEM_ROLES[state.currentRoleId];
if (!role) return false;
// Super roles or full wildcard
if (role.id === 'owner' || role.id === 'administrator' || role.allowedModules.includes('*')) {
return true;
}
return role.allowedModules.includes(moduleId);
},
getActiveProfile: () => {
const state = get();
return BUSINESS_PROFILES[state.currentProfileId] || BUSINESS_PROFILES[DEFAULT_PROFILE_ID];
},
getActiveCapabilities: () => {
return get().capabilities;
},
};
});