234 lines
7.9 KiB
TypeScript
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;
|
|
},
|
|
};
|
|
});
|