import { DefaultAdminRoles } from "~/application/dtos/shared/DefaultAdminRoles"; import { DefaultAppRoles } from "~/application/dtos/shared/DefaultAppRoles"; import { DefaultPermission } from "~/application/dtos/shared/DefaultPermissions"; import { defaultAdminRoleSeeds, defaultAppRoleSeeds, defaultPermissionSeedDescriptors, syncDefaultRolesAndPermissions, type DefaultPermissionSeed, type DefaultRoleSeed, } from "@toolctrl/db/permissions-seed.server"; import { getAllEntities } from "~/utils/db/entities/entities.db.server"; import { getAllRoles } from "~/utils/db/permissions/roles.db.server"; import { createUserRoleAssignments, type UserRoleAssignment } from "~/utils/db/permissions/userRoles.db.server"; import { adminGetAllTenants } from "~/utils/db/tenants.db.server"; import { adminGetAllUsers } from "~/utils/db/users.db.server"; import { db } from "~/utils/db.server"; import { clearCacheKey } from "~/utils/cache.server"; import { getEntityPermissions } from "~/utils/helpers/PermissionsHelper"; export type CreateRoleDto = DefaultRoleSeed; export const defaultAdminRoles: CreateRoleDto[] = [...defaultAdminRoleSeeds]; export const defaultAppRoles: CreateRoleDto[] = [...defaultAppRoleSeeds]; export type CreatePermissionDto = Omit & { name: DefaultPermission; type: string; }; export const defaultPermissions: CreatePermissionDto[] = defaultPermissionSeedDescriptors.map((permission) => ({ ...permission, name: permission.name as DefaultPermission, })); export type RolesAndPermissionsSeedDiagnosticPhase = "start" | "success" | "failure"; export type RolesAndPermissionsSeedDiagnosticEvent = { phase: RolesAndPermissionsSeedDiagnosticPhase; adminRoleCount?: number; appRoleCount?: number; defaultPermissionCount?: number; dynamicEntityPermissionCount?: number; adminUserAssignmentCount?: number; tenantUserAssignmentCount?: number; error?: unknown; }; export type RolesAndPermissionsSeedDiagnostics = (event: RolesAndPermissionsSeedDiagnosticEvent) => void | Promise; export type SeedRolesAndPermissionsOptions = { adminEmail?: string; diagnostics?: RolesAndPermissionsSeedDiagnostics; }; function toSeedRolesAndPermissionsOptions(adminEmailOrOptions?: string | SeedRolesAndPermissionsOptions): SeedRolesAndPermissionsOptions { if (typeof adminEmailOrOptions === "string") { return { adminEmail: adminEmailOrOptions }; } return adminEmailOrOptions ?? {}; } async function emitRolesAndPermissionsSeedDiagnostic( diagnostics: RolesAndPermissionsSeedDiagnostics | undefined, event: RolesAndPermissionsSeedDiagnosticEvent ): Promise { await diagnostics?.(event); } async function createDynamicEntityPermissions(permissions: { inRoles: string[]; name: string; description: string; type: string; order: number }[]): Promise { if (permissions.length === 0) { return; } const permissionNames = [...new Set(permissions.map((permission) => permission.name))]; await db.permission.createMany({ data: permissions.map((permission) => ({ order: permission.order, name: permission.name, description: permission.description, type: permission.type, isDefault: true, entityId: null, })), skipDuplicates: true, }); permissionNames.forEach((permissionName) => { clearCacheKey(`permission:${permissionName}`); }); const createdOrExistingPermissions = await db.permission.findMany({ where: { name: { in: permissionNames, }, }, select: { id: true, name: true, }, }); const permissionByName = new Map(createdOrExistingPermissions.map((permission) => [permission.name, permission])); const roleNames = [...new Set(permissions.flatMap((permission) => permission.inRoles))]; const roles = await db.role.findMany({ where: { name: { in: roleNames, }, tenantId: null, }, select: { id: true, name: true, }, }); const roleByName = new Map(roles.map((role) => [role.name, role])); const rolePermissionData = permissions.flatMap((permission) => { const createdOrExistingPermission = permissionByName.get(permission.name); if (!createdOrExistingPermission) { return []; } return permission.inRoles.map((roleName) => { const role = roleByName.get(roleName); if (!role) { throw new Error("Role required: " + roleName); } return { permissionId: createdOrExistingPermission.id, roleId: role.id, }; }); }); if (rolePermissionData.length > 0) { await db.rolePermission.createMany({ data: rolePermissionData, skipDuplicates: true, }); } } export async function seedRolesAndPermissions(adminEmailOrOptions?: string | SeedRolesAndPermissionsOptions): Promise { const { adminEmail, diagnostics } = toSeedRolesAndPermissionsOptions(adminEmailOrOptions); const counts = { adminRoleCount: defaultAdminRoles.length, appRoleCount: defaultAppRoles.length, defaultPermissionCount: defaultPermissions.length, dynamicEntityPermissionCount: 0, adminUserAssignmentCount: 0, tenantUserAssignmentCount: 0, }; await emitRolesAndPermissionsSeedDiagnostic(diagnostics, { phase: "start", ...counts }); try { await syncDefaultRolesAndPermissions({ client: db }); const adminRoles = await getAllRoles("admin"); counts.adminRoleCount = adminRoles.length; const appRoles = await getAllRoles("app"); counts.appRoleCount = appRoles.length; const entities = await getAllEntities({ tenantId: null, active: true }); const entityPermissionGroups = await Promise.all( entities.map(async (entity) => (await getEntityPermissions(entity)).map((permission) => ({ inRoles: [DefaultAdminRoles.SuperAdmin, DefaultAppRoles.SuperUser, DefaultAppRoles.Admin, DefaultAppRoles.User].map((f) => f.toString()), name: permission.name as DefaultPermission, description: permission.description, type: entity.type, })) ) ); const appPermissionCount = defaultPermissions.filter((f) => f.type === "app").length; const dynamicEntityPermissions = entityPermissionGroups.flat().map((permission, index) => ({ ...permission, order: appPermissionCount + index + 3, })); counts.dynamicEntityPermissionCount = dynamicEntityPermissions.length; await createDynamicEntityPermissions(dynamicEntityPermissions); const users = (await adminGetAllUsers()).items; const userRoleAssignments: UserRoleAssignment[] = []; users .filter((f) => f.admin && adminEmail && f.email === adminEmail) .forEach((user) => { adminRoles.forEach((adminRole) => { counts.adminUserAssignmentCount += 1; userRoleAssignments.push({ userId: user.id, roleId: adminRole.id, tenantId: null }); }); }); const tenants = await adminGetAllTenants(); tenants.forEach((tenant) => { tenant.users.forEach((tenantUser) => { appRoles.forEach((appRole) => { counts.tenantUserAssignmentCount += 1; userRoleAssignments.push({ userId: tenantUser.userId, roleId: appRole.id, tenantId: tenant.id }); }); }); }); await createUserRoleAssignments(userRoleAssignments); await emitRolesAndPermissionsSeedDiagnostic(diagnostics, { phase: "success", ...counts }); } catch (error) { await emitRolesAndPermissionsSeedDiagnostic(diagnostics, { phase: "failure", ...counts, error }); throw error; } }