Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/utils/services/rolesAndPermissionsService.ts
T

208 lines
7.4 KiB
TypeScript

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<DefaultPermissionSeed, "name" | "type"> & {
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<void>;
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<void> {
await diagnostics?.(event);
}
async function createDynamicEntityPermissions(permissions: { inRoles: string[]; name: string; description: string; type: string; order: number }[]): Promise<void> {
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<void> {
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;
}
}