145 lines
5.9 KiB
TypeScript
145 lines
5.9 KiB
TypeScript
import type { TFunction } from "i18next";
|
|
import { Params } from "react-router";
|
|
import { getTranslations } from "~/locale/i18next.server";
|
|
import { createAdminLog } from "~/utils/db/logs.db.server";
|
|
import { getAllPermissions } from "~/utils/db/permissions/permissions.db.server";
|
|
import { setRolePermissions } from "~/utils/db/permissions/rolePermissions.db.server";
|
|
import { createRole, deleteRole, getNextRolesOrder, getRole, getTenantAppRoleByName, updateRole } from "~/utils/db/permissions/roles.db.server";
|
|
import { verifyUserHasPermission } from "~/utils/helpers/.server/PermissionsService";
|
|
|
|
export type WorkspaceRoleMutationInput = {
|
|
name: string;
|
|
description: string;
|
|
assignToNewUsers?: boolean;
|
|
type?: "admin" | "app";
|
|
permissionNames: string[];
|
|
};
|
|
|
|
export type WorkspaceRoleAccessArgs = {
|
|
request: Request;
|
|
params: Params;
|
|
tenantId: string;
|
|
};
|
|
|
|
const WORKSPACE_ROLES_PERMISSION = "app.settings.roles.set";
|
|
|
|
// Flag-Gate (workspace-roles) ENTFERNT 2026-06-22: Tenant-eigene Rollen sind P2-Kernfunktion fuer ALLE
|
|
// Tenants -> nur noch die Permission `app.settings.roles.set` entscheidet (kein Feature-Flag-Kill-Switch
|
|
// mehr). Hintergrund: der eigentliche Mehrwert (Fach-Rechte zum Zuweisen) waechst erst mit den Modulen ab
|
|
// P6-P9; der Builder selbst soll aber jedem Tenant offenstehen.
|
|
export async function requireWorkspaceRolesBuilderAccess({ request, tenantId }: WorkspaceRoleAccessArgs) {
|
|
await verifyUserHasPermission(request, WORKSPACE_ROLES_PERMISSION, tenantId);
|
|
return { tenantId };
|
|
}
|
|
|
|
export async function createWorkspaceRole({ request, tenantId, data }: { request: Request; tenantId: string; data: WorkspaceRoleMutationInput }) {
|
|
const { t } = await getTranslations(request);
|
|
assertAppRoleInput(data.type, t);
|
|
const name = normalizeRoleName(data.name, t);
|
|
await assertUniqueRoleName({ name, tenantId, t });
|
|
await assertAppPermissionNames(data.permissionNames, t);
|
|
|
|
const role = await createRole({
|
|
order: await getNextRolesOrder("app", tenantId),
|
|
name,
|
|
description: data.description,
|
|
assignToNewUsers: data.assignToNewUsers ?? false,
|
|
type: "app",
|
|
isDefault: false,
|
|
tenantId,
|
|
});
|
|
await setRolePermissions(role.id, data.permissionNames);
|
|
await logWorkspaceRoleMutation(request, "Workspace Role Created", tenantId, role.id, role.name, data.permissionNames.length);
|
|
|
|
return role;
|
|
}
|
|
|
|
export async function updateWorkspaceRole({ request, tenantId, roleId, data }: { request: Request; tenantId: string; roleId: string; data: WorkspaceRoleMutationInput }) {
|
|
const { t } = await getTranslations(request);
|
|
assertAppRoleInput(data.type, t);
|
|
const existing = await getExistingMutableAppRole(roleId, tenantId, t);
|
|
const name = normalizeRoleName(data.name, t);
|
|
await assertUniqueRoleName({ name, tenantId, currentRoleId: roleId, t });
|
|
await assertAppPermissionNames(data.permissionNames, t);
|
|
|
|
const role = await updateRole(roleId, {
|
|
name,
|
|
description: data.description,
|
|
assignToNewUsers: data.assignToNewUsers ?? false,
|
|
type: "app",
|
|
});
|
|
await setRolePermissions(existing.id, data.permissionNames);
|
|
await logWorkspaceRoleMutation(request, "Workspace Role Updated", tenantId, role.id, role.name, data.permissionNames.length);
|
|
|
|
return role;
|
|
}
|
|
|
|
export async function deleteWorkspaceRole({ request, tenantId, roleId }: { request: Request; tenantId: string; roleId: string }) {
|
|
const { t } = await getTranslations(request);
|
|
const existing = await getExistingMutableAppRole(roleId, tenantId, t);
|
|
const role = await deleteRole(existing.id);
|
|
await logWorkspaceRoleMutation(request, "Workspace Role Deleted", tenantId, role.id, role.name, existing.permissions.length);
|
|
|
|
return role;
|
|
}
|
|
|
|
function assertAppRoleInput(type: "admin" | "app" | undefined, t: TFunction) {
|
|
if (type === "admin") {
|
|
throw new Response(t("settings.rolesAndPermissions.errors.appRolesOnly"), { status: 400 });
|
|
}
|
|
}
|
|
|
|
function normalizeRoleName(name: string, t: TFunction) {
|
|
const normalized = name.trim();
|
|
if (!normalized) {
|
|
throw new Response(t("settings.rolesAndPermissions.errors.roleNameRequired"), { status: 400 });
|
|
}
|
|
return normalized;
|
|
}
|
|
|
|
async function assertUniqueRoleName({ name, tenantId, currentRoleId, t }: { name: string; tenantId: string; currentRoleId?: string; t: TFunction }) {
|
|
const existing = await getTenantAppRoleByName(name, tenantId);
|
|
if (existing && existing.id !== currentRoleId) {
|
|
throw new Response(t("settings.rolesAndPermissions.errors.existingRole", { name }), { status: 400 });
|
|
}
|
|
}
|
|
|
|
async function assertAppPermissionNames(permissionNames: string[], t: TFunction) {
|
|
const appPermissions = await getAllPermissions("app");
|
|
const allowedNames = new Set(appPermissions.map((permission) => permission.name));
|
|
const invalid = permissionNames.filter((name) => !allowedNames.has(name));
|
|
if (invalid.length > 0) {
|
|
throw new Response(t("settings.rolesAndPermissions.errors.invalidAppPermissions", { permissions: invalid.join(", ") }), { status: 400 });
|
|
}
|
|
}
|
|
|
|
async function getExistingMutableAppRole(roleId: string, tenantId: string, t: TFunction) {
|
|
const existing = await getRole(roleId);
|
|
if (!existing) {
|
|
throw new Response(t("settings.rolesAndPermissions.errors.roleNotFound"), { status: 404 });
|
|
}
|
|
if (existing.type !== "app") {
|
|
throw new Response(t("settings.rolesAndPermissions.errors.appRolesOnly"), { status: 400 });
|
|
}
|
|
if (existing.isDefault) {
|
|
throw new Response(t("settings.rolesAndPermissions.errors.defaultRoleImmutable"), { status: 400 });
|
|
}
|
|
if (existing.tenantId !== tenantId) {
|
|
throw new Response(t("settings.rolesAndPermissions.errors.roleNotFound"), { status: 404 });
|
|
}
|
|
return existing;
|
|
}
|
|
|
|
async function logWorkspaceRoleMutation(request: Request, action: string, tenantId: string, roleId: string, roleName: string, permissionCount: number) {
|
|
await createAdminLog(
|
|
request,
|
|
action,
|
|
JSON.stringify({
|
|
tenantId,
|
|
roleId,
|
|
roleName,
|
|
permissionCount,
|
|
})
|
|
);
|
|
}
|