import { computeEffectiveAccess } from "../../entities/access/computeEffectiveAccess"; import type { AccessRequest } from "../../entities/access-request/types"; import type { EffectiveAccessResult, ServiceAccessException, ServiceGrant, ServiceModuleEntitlement } from "../../entities/access/types"; import type { Client, TaskManagerWorkspaceManagedBy } from "../../entities/client/types"; import type { EngineWorkflowAccessRequest } from "../../entities/engine-workflow-access-request/types"; import type { Invite } from "../../entities/invite/types"; import { PUBLIC_POOL_CLIENT, isPublicPoolClientId } from "../../entities/public-pool/constants"; import { getServiceLaunchLink } from "../../entities/service/links"; import type { LauncherServiceView, Service } from "../../entities/service/types"; import type { SyncStatus } from "../../entities/sync/types"; import type { TaskerInviteRequest } from "../../entities/tasker-invite-request/types"; import type { ClientGroup, ClientMembership, ClientMembershipRole, LauncherGlobalRole, LauncherUser, } from "../../entities/user/types"; import { resolveLauncherRole, resolvePermissions, type LauncherPermissions } from "../lib/permissions"; import { mockAuditEvents, mockAccessRequests, mockEngineWorkflowAccessRequests, mockTaskerInviteRequests, mockClients, mockExceptions, mockGrants, mockGroups, mockInvites, mockMemberships, mockServices, mockSyncStatuses, mockUsers, } from "./mockData"; export interface AuthentikClaimsMock { sub: string; email: string; name: string; avatarUrl?: string | null; groups: string[]; activeClientId: string; } export interface MeResponse { user: Pick; launcherRole: LauncherGlobalRole; memberships: Array<{ clientId: string; clientName: string; role: ClientMembershipRole; status: ClientMembership["status"]; coreAssistantRole: ClientMembership["coreAssistantRole"]; }>; activeClientId: string; permissions: LauncherPermissions; mockAuthentikClaims: AuthentikClaimsMock; } export interface LauncherData { clients: Client[]; users: LauncherUser[]; memberships: ClientMembership[]; groups: ClientGroup[]; services: Service[]; grants: ServiceGrant[]; exceptions: ServiceAccessException[]; serviceModuleEntitlements: ServiceModuleEntitlement[]; invites: Invite[]; accessRequests: AccessRequest[]; revokedAccounts: RevokedAccount[]; taskerInviteRequests: TaskerInviteRequest[]; engineWorkflowAccessRequests: EngineWorkflowAccessRequest[]; syncStatuses: SyncStatus[]; auditEvents: typeof mockAuditEvents; taskManagerMemberships: TaskManagerMembershipAssignment[]; taskManagerProjectMemberships: TaskManagerProjectMembershipAssignment[]; settings: LauncherSettings; } export interface RevokedAccount { id: string; email: string; name?: string | null; sourceUserId?: string | null; authentikUserId?: string | null; reason: string; revokedByUserId?: string | null; revokedByUserEmail?: string | null; revokedByUserName?: string | null; revokedAt: string; createdAt: string; updatedAt: string; } export interface TaskManagerMembershipAssignment { id: string; clientId: string; userId: string; workspaceSlug: string; workspaceName?: string | null; role: "guest" | "member" | "admin"; managedBy?: TaskManagerWorkspaceManagedBy; planeUserId?: string | null; planeRole?: number | null; updatedAt: string; } export interface TaskManagerProjectMembershipAssignment { id: string; clientId: string; userId: string; workspaceSlug: string; workspaceName?: string | null; projectId: string; projectIdentifier?: string | null; projectName?: string | null; role: "guest" | "member" | "admin"; managedBy?: TaskManagerWorkspaceManagedBy; planeUserId?: string | null; planeRole?: number | null; updatedAt: string; } export interface LauncherSettings { brand: { logoLinkUrl: string; }; taskManager: { workspaceCreationPolicy: TaskManagerWorkspaceCreationPolicy; }; } export type TaskManagerWorkspaceCreationPolicy = "any_authorized_user" | "task_admins_only" | "disabled"; export interface ProfileOption { userId: string; label: string; description: string; defaultClientId: string; } export interface AccessMatrixCell { userId: string; serviceId: string; effectiveAccess: EffectiveAccessResult; } export interface AccessMatrix { client: Client; users: LauncherUser[]; groups: ClientGroup[]; services: Service[]; cells: AccessMatrixCell[]; } export const defaultLauncherSettings: LauncherSettings = { brand: { logoLinkUrl: "/", }, taskManager: { workspaceCreationPolicy: "any_authorized_user", }, }; export const initialLauncherData: LauncherData = normalizeLauncherData({ clients: mockClients, users: mockUsers, memberships: mockMemberships, groups: mockGroups, services: mockServices, grants: mockGrants, exceptions: mockExceptions, invites: mockInvites, accessRequests: mockAccessRequests, revokedAccounts: [], taskerInviteRequests: mockTaskerInviteRequests, engineWorkflowAccessRequests: mockEngineWorkflowAccessRequests, syncStatuses: mockSyncStatuses, auditEvents: mockAuditEvents, settings: defaultLauncherSettings, }); export function normalizeLauncherSettings(settings?: Partial | null): LauncherSettings { const brand = typeof settings?.brand === "object" && settings.brand !== null ? settings.brand : ({} as Partial); const taskManager = typeof settings?.taskManager === "object" && settings.taskManager !== null ? settings.taskManager : ({} as Partial); const logoLinkUrl = typeof brand.logoLinkUrl === "string" && brand.logoLinkUrl.trim() ? brand.logoLinkUrl.trim() : "/"; const workspaceCreationPolicy = isTaskManagerWorkspaceCreationPolicy(taskManager.workspaceCreationPolicy) ? taskManager.workspaceCreationPolicy : defaultLauncherSettings.taskManager.workspaceCreationPolicy; return { brand: { logoLinkUrl, }, taskManager: { workspaceCreationPolicy, }, }; } function isTaskManagerWorkspaceCreationPolicy(value: unknown): value is TaskManagerWorkspaceCreationPolicy { return value === "any_authorized_user" || value === "task_admins_only" || value === "disabled"; } export function normalizeLauncherData(data: Partial | null | undefined): LauncherData { const payload = data ?? {}; return { clients: Array.isArray(payload.clients) ? payload.clients : mockClients, users: Array.isArray(payload.users) ? payload.users : mockUsers, memberships: Array.isArray(payload.memberships) ? payload.memberships.map(normalizeMembership) : mockMemberships.map(normalizeMembership), groups: Array.isArray(payload.groups) ? payload.groups : mockGroups, services: Array.isArray(payload.services) ? payload.services : mockServices, grants: Array.isArray(payload.grants) ? payload.grants : mockGrants, exceptions: Array.isArray(payload.exceptions) ? payload.exceptions : mockExceptions, serviceModuleEntitlements: Array.isArray(payload.serviceModuleEntitlements) ? payload.serviceModuleEntitlements : [], invites: Array.isArray(payload.invites) ? payload.invites : mockInvites, accessRequests: Array.isArray(payload.accessRequests) ? payload.accessRequests : mockAccessRequests, revokedAccounts: Array.isArray(payload.revokedAccounts) ? payload.revokedAccounts : [], taskerInviteRequests: Array.isArray(payload.taskerInviteRequests) ? payload.taskerInviteRequests : mockTaskerInviteRequests, engineWorkflowAccessRequests: Array.isArray(payload.engineWorkflowAccessRequests) ? payload.engineWorkflowAccessRequests : mockEngineWorkflowAccessRequests, syncStatuses: Array.isArray(payload.syncStatuses) ? payload.syncStatuses : mockSyncStatuses, auditEvents: Array.isArray(payload.auditEvents) ? payload.auditEvents : mockAuditEvents, taskManagerMemberships: Array.isArray(payload.taskManagerMemberships) ? payload.taskManagerMemberships : [], taskManagerProjectMemberships: Array.isArray(payload.taskManagerProjectMemberships) ? payload.taskManagerProjectMemberships : [], settings: normalizeLauncherSettings(payload.settings), }; } export const profileOptions: ProfileOption[] = [ { userId: "user_root", label: "DC Touch", description: "NODE.DC superadmin", defaultClientId: "client_romashka", }, { userId: "user_silver_psih", label: "Silver Psy", description: "DCTOUCH manager", defaultClientId: "client_romashka", }, ]; export function buildMe(data: LauncherData, userId: string, requestedClientId?: string): MeResponse { const user = getUser(data, userId); const isRoot = user.id === "user_root"; const availableMemberships = isRoot ? data.clients.map((client) => ({ clientId: client.id, clientName: client.name, role: "client_owner" as const, status: "active" as const, coreAssistantRole: "admin" as const, })) : data.memberships .filter((membership) => membership.userId === user.id) .map((membership) => ({ clientId: membership.clientId, clientName: getClient(data, membership.clientId).name, role: membership.role, status: membership.status, coreAssistantRole: normalizeCoreAssistantRole(membership.coreAssistantRole), })); const fallbackClientId = profileOptions.find((option) => option.userId === user.id)?.defaultClientId ?? availableMemberships[0]?.clientId ?? PUBLIC_POOL_CLIENT.id; const canUseRequestedClient = availableMemberships.some((membership) => membership.clientId === requestedClientId); const activeClientId = canUseRequestedClient ? requestedClientId! : fallbackClientId; const activeMembership = availableMemberships.find((membership) => membership.clientId === activeClientId); const launcherRole = resolveLauncherRole({ isRoot, membershipRole: activeMembership?.role }); const permissions = resolvePermissions({ launcherRole, membershipStatus: activeMembership?.status, }); return { user: { id: user.id, authentikUserId: user.authentikUserId, name: user.name, email: user.email, avatarUrl: user.avatarUrl, }, launcherRole, memberships: availableMemberships, activeClientId, permissions, mockAuthentikClaims: { sub: user.authentikUserId ?? user.id, email: user.email, name: user.name, groups: buildMockGroups(data, user.id, activeClientId, launcherRole), activeClientId, }, }; } function normalizeMembership(membership: ClientMembership): ClientMembership { return { ...membership, coreAssistantRole: normalizeCoreAssistantRole(membership.coreAssistantRole, membership.userId === "user_root" ? "admin" : "member"), }; } function normalizeCoreAssistantRole( value: ClientMembership["coreAssistantRole"] | null | undefined, fallback: NonNullable = "member" ): NonNullable { if (value === "blocked" || value === "member" || value === "admin") return value; return fallback; } export function buildLauncherServices(data: LauncherData, userId: string, activeClientId: string): LauncherServiceView[] { const me = buildMe(data, userId, activeClientId); const user = getUser(data, userId); const client = getClient(data, activeClientId); const membership = getRuntimeMembership(data, user.id, activeClientId, me.launcherRole === "root_admin"); const userGroups = getUserGroups(data, user.id, activeClientId); const isRoot = me.launcherRole === "root_admin"; return data.services .slice() .sort((a, b) => a.order - b.order) .map((service) => { const effectiveAccess = computeEffectiveAccess({ client, user, membership, userGroups, service, grants: data.grants, exceptions: data.exceptions, }); return { id: service.id, slug: service.slug, title: service.title, subtitle: service.subtitle, description: service.description, fullDescription: service.fullDescription, status: service.status, userAccess: effectiveAccess.allowed ? ("allowed" as const) : ("denied" as const), appRole: effectiveAccess.appRole, openUrl: effectiveAccess.openEnabled ? getServiceLaunchLink(service) || null : null, accentColor: service.accentColor, media: { icon: service.iconUrl, thumbnail: service.coverImageUrl, coverImage: service.coverImageUrl, coverKind: service.coverMediaKind, coverSource: service.coverMediaSource, coverFileName: service.coverMediaFileName, previewVideo: service.previewVideoUrl, ambientVideo: service.ambientVideoUrl, ambientKind: service.ambientMediaKind, ambientSource: service.ambientMediaSource, ambientFileName: service.ambientMediaFileName, fallbackGradient: service.fallbackGradient, }, effectiveAccess, }; }) .filter((service) => isRoot || service.status !== "hidden"); } export function buildAccessMatrix(data: LauncherData, clientId: string, includeAllServices: boolean): AccessMatrix { const client = getClient(data, clientId); const memberships = data.memberships.filter((membership) => membership.clientId === clientId); const users = memberships.map((membership) => getUser(data, membership.userId)); const groups = data.groups.filter((group) => group.clientId === clientId); const clientServiceIds = new Set( data.grants .filter((grant) => grant.targetType === "client" && grant.targetId === clientId && grant.status === "active") .map((grant) => grant.serviceId) ); const services = data.services .filter((service) => includeAllServices || clientServiceIds.has(service.id) || service.status === "active") .sort((a, b) => a.order - b.order); const cells = users.flatMap((user) => { const membership = memberships.find((item) => item.userId === user.id) ?? getRuntimeMembership(data, user.id, clientId); const userGroups = getUserGroups(data, user.id, clientId); return services.map((service) => ({ userId: user.id, serviceId: service.id, effectiveAccess: computeEffectiveAccess({ client, user, membership, userGroups, service, grants: data.grants, exceptions: data.exceptions, }), })); }); return { client, users, groups, services, cells }; } export function getClient(data: LauncherData, clientId: string): Client { if (isPublicPoolClientId(clientId)) { return PUBLIC_POOL_CLIENT; } const client = data.clients.find((item) => item.id === clientId); if (!client) throw new Error(`Unknown client: ${clientId}`); return client; } export function getUser(data: LauncherData, userId: string): LauncherUser { const user = data.users.find((item) => item.id === userId); if (!user) throw new Error(`Unknown user: ${userId}`); return user; } export function getService(data: LauncherData, serviceId: string): Service { const service = data.services.find((item) => item.id === serviceId); if (!service) throw new Error(`Unknown service: ${serviceId}`); return service; } export function getClientUsers(data: LauncherData, clientId: string): LauncherUser[] { const userIds = new Set(data.memberships.filter((membership) => membership.clientId === clientId).map((item) => item.userId)); return data.users.filter((user) => userIds.has(user.id)); } export function getUserGroups(data: LauncherData, userId: string, clientId: string): ClientGroup[] { return data.groups.filter((group) => group.clientId === clientId && group.memberIds.includes(userId)); } export function getRuntimeMembership( data: LauncherData, userId: string, clientId: string, allowVirtualRoot = false ): ClientMembership { const membership = data.memberships.find((item) => item.userId === userId && item.clientId === clientId); if (membership) return membership; if (allowVirtualRoot) { return { id: `virtual_root_${clientId}`, clientId, userId, role: "client_owner", status: "active", createdAt: "2026-05-01T09:00:00Z", updatedAt: "2026-05-01T09:00:00Z", }; } return { id: `missing_${clientId}_${userId}`, clientId, userId, role: "member", status: "disabled", createdAt: "2026-05-01T09:00:00Z", updatedAt: "2026-05-01T09:00:00Z", }; } function buildMockGroups( data: LauncherData, userId: string, activeClientId: string, launcherRole: LauncherGlobalRole ): string[] { const groups = getUserGroups(data, userId, activeClientId).map((group) => `client:${activeClientId}:group:${group.name}`); return [`launcher:${launcherRole}`, `client:${activeClientId}`, ...groups]; }