Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/apps/web/app/services/admin/platform-leads.server.ts
T

171 lines
6.1 KiB
TypeScript

import { createPrismaPlatformLeadService, type PlatformLeadService } from "@hivegrid/db/platform-leads-admin.server";
import {
createAdminSalesLeadService,
createPrismaAdminSalesLeadRepository,
} from "@hivegrid/db/admin-sales-leads.server";
import type { AdminPermissionName } from "@hivegrid/db/admin-permissions";
import { runWithRlsContext } from "@hivegrid/db/rls-context.server";
import { listAdminPermissionNames } from "@hivegrid/db/admin-rbac.server";
import { extractWebActorIp, extractWebActorUserAgent, extractWebRequestId } from "~/utils/audit/withAuditContext";
import { db } from "~/utils/db.server";
import { getUserInfo } from "~/utils/session.server";
export const PLATFORM_LEAD_PERMISSIONS = Object.freeze({
readOwn: "admin_sales.crm.read",
readAll: "admin_sales.crm.read_all",
write: "admin_sales.crm.write",
writeAll: "admin_sales.crm.write_all",
admin: "admin_sales.crm.assign",
merge: "admin_sales.crm.merge",
} as const);
export type PlatformLeadOperatorAccess = {
readonly actorAdminUserId: string;
readonly actorEmail: string;
readonly canReadOwn: boolean;
readonly canReadAll: boolean;
readonly canWrite: boolean;
readonly canAdmin: boolean;
readonly canMerge: boolean;
readonly audit: (reason: string) => {
actorAdminUserId: string;
actorEmail: string;
actorIp: string | null;
actorUserAgent: string | null;
requestId: string | null;
reason: string;
};
};
export type PlatformLeadOperatorContext = {
readonly access: PlatformLeadOperatorAccess;
readonly service: PlatformLeadService;
readonly advancedService: ReturnType<typeof createAdminSalesLeadService>;
};
export type PlatformLeadAssigneeOption = {
readonly id: string;
readonly label: string;
};
export function platformLeadNotFound(): Response {
return new Response("Not Found", { status: 404 });
}
export function requirePlatformLeadRead(access: PlatformLeadOperatorAccess): void {
if (!access.canReadOwn && !access.canReadAll) throw platformLeadNotFound();
}
export function requirePlatformLeadWrite(access: PlatformLeadOperatorAccess): void {
requirePlatformLeadRead(access);
if (!access.canWrite) throw platformLeadNotFound();
}
export function requirePlatformLeadAdmin(access: PlatformLeadOperatorAccess): void {
requirePlatformLeadRead(access);
if (!access.canAdmin) throw platformLeadNotFound();
}
/**
* Binds the current operator identity for the complete read/write callback.
* This is deliberately callback-based: returning a client after the callback
* would lose the transaction-local RLS identity on staging and production.
*/
export async function runAsPlatformLeadOperator<T>(request: Request, callback: (context: PlatformLeadOperatorContext) => Promise<T>): Promise<T> {
const session = await getUserInfo(request);
if (!session.userId) throw platformLeadNotFound();
return runWithRlsContext({ userId: session.userId }, async () => {
const operator = await db.adminUser.findUnique({
where: { id: session.userId },
select: { id: true, email: true, status: true },
});
if (!operator || operator.status !== "active") throw platformLeadNotFound();
const permissionNames = new Set(await listAdminPermissionNames(operator.id));
const auditBase = {
actorAdminUserId: operator.id,
actorEmail: operator.email,
actorIp: extractWebActorIp(request),
actorUserAgent: extractWebActorUserAgent(request),
requestId: extractWebRequestId(request),
};
const access: PlatformLeadOperatorAccess = {
actorAdminUserId: operator.id,
actorEmail: operator.email,
canReadOwn: permissionNames.has(PLATFORM_LEAD_PERMISSIONS.readOwn),
canReadAll: permissionNames.has(PLATFORM_LEAD_PERMISSIONS.readAll),
canWrite: permissionNames.has(PLATFORM_LEAD_PERMISSIONS.write),
canAdmin: permissionNames.has(PLATFORM_LEAD_PERMISSIONS.admin),
canMerge: permissionNames.has(PLATFORM_LEAD_PERMISSIONS.merge),
audit: (reason) => ({ ...auditBase, reason }),
};
const authorize = (permission: AdminPermissionName) => {
if (!permissionNames.has(permission)) throw platformLeadNotFound();
};
return callback({
access,
service: createPrismaPlatformLeadService(db, {
adminUserId: operator.id,
canReadAll: permissionNames.has(PLATFORM_LEAD_PERMISSIONS.readAll),
canWriteAll: permissionNames.has(PLATFORM_LEAD_PERMISSIONS.writeAll),
canReadAllCommunication: permissionNames.has(
"admin_sales.communication.read_all"
),
}),
advancedService: createAdminSalesLeadService({
repository: createPrismaAdminSalesLeadRepository(db, {
accessScope: {
adminUserId: operator.id,
canReadAll: permissionNames.has(PLATFORM_LEAD_PERMISSIONS.readAll),
canWriteAll: permissionNames.has(
PLATFORM_LEAD_PERMISSIONS.writeAll
),
canReadAllCommunication: permissionNames.has(
"admin_sales.communication.read_all"
),
},
}),
authorize,
}),
});
});
}
export async function listPlatformLeadAssignees(): Promise<PlatformLeadAssigneeOption[]> {
const operators = await db.adminUser.findMany({
where: {
status: "active",
roles: {
some: {
role: {
active: true,
permissions: {
some: {
permission: {
name: PLATFORM_LEAD_PERMISSIONS.readOwn,
},
},
},
},
},
},
},
orderBy: [{ firstName: "asc" }, { lastName: "asc" }, { id: "asc" }],
select: { id: true, firstName: true, lastName: true, email: true },
});
return operators.map((operator) => ({
id: operator.id,
label: [operator.firstName, operator.lastName].filter(Boolean).join(" ").trim() || operator.email,
}));
}
export function assertPlatformLeadVisibleToOperator(lead: { readonly assignedToAdminUserId: string | null }, access: PlatformLeadOperatorAccess): void {
requirePlatformLeadRead(access);
if (!access.canReadAll && lead.assignedToAdminUserId !== access.actorAdminUserId) {
throw platformLeadNotFound();
}
}