Files
Nexus/packages/api/src/router/role-support.ts
T

153 lines
4.0 KiB
TypeScript

import { countPlanningEntries } from "@capakraken/application";
import type { Prisma, PrismaClient } from "@capakraken/db";
import { CreateRoleSchema, UpdateRoleSchema } from "@capakraken/shared";
import { TRPCError } from "@trpc/server";
import { z } from "zod";
type RolePlanningCountsDb = Pick<PrismaClient, "demandRequirement" | "assignment">;
type RoleIdentifierDb = Pick<PrismaClient, "role">;
type RoleCountRecord = {
id: string;
_count: { resourceRoles: number };
};
type RoleListInput = {
isActive?: boolean | undefined;
search?: string | undefined;
};
type CreateRoleInput = z.infer<typeof CreateRoleSchema>;
type UpdateRoleInput = z.infer<typeof UpdateRoleSchema>;
export function buildRoleListWhere(input: RoleListInput): Prisma.RoleWhereInput {
return {
...(input.isActive !== undefined ? { isActive: input.isActive } : {}),
...(input.search
? { name: { contains: input.search, mode: "insensitive" } }
: {}),
};
}
export async function loadRolePlanningEntryCounts(
db: RolePlanningCountsDb,
roleIds: string[],
) {
const { countsByRoleId } = await countPlanningEntries(db, {
roleIds,
});
return countsByRoleId;
}
export async function attachRolePlanningEntryCounts<
TRole extends RoleCountRecord,
>(
db: RolePlanningCountsDb,
roles: TRole[],
): Promise<Array<TRole & { _count: { resourceRoles: number; allocations: number } }>> {
const countsByRoleId = await loadRolePlanningEntryCounts(
db,
roles.map((role) => role.id),
);
return roles.map((role) => ({
...role,
_count: {
...role._count,
allocations: countsByRoleId.get(role.id) ?? 0,
},
}));
}
export async function attachSingleRolePlanningEntryCount<
TRole extends RoleCountRecord,
>(
db: RolePlanningCountsDb,
role: TRole,
): Promise<TRole & { _count: { resourceRoles: number; allocations: number } }> {
return (await attachRolePlanningEntryCounts(db, [role]))[0]!;
}
export async function findRoleByIdentifier<TRole>(
db: RoleIdentifierDb,
identifier: string,
select: Record<string, unknown>,
): Promise<TRole> {
const normalizedIdentifier = identifier.trim();
let role = await db.role.findUnique({
where: { id: normalizedIdentifier },
select,
}) as TRole | null;
if (!role) {
role = await db.role.findUnique({
where: { name: normalizedIdentifier },
select,
}) as TRole | null;
}
if (!role) {
role = await db.role.findFirst({
where: { name: { equals: normalizedIdentifier, mode: "insensitive" } },
select,
}) as TRole | null;
}
if (!role) {
role = await db.role.findFirst({
where: { name: { contains: normalizedIdentifier, mode: "insensitive" } },
select,
}) as TRole | null;
}
if (!role) {
throw new TRPCError({ code: "NOT_FOUND", message: "Role not found" });
}
return role;
}
export function buildRoleCreateData(
input: CreateRoleInput,
): Prisma.RoleCreateInput {
return {
name: input.name,
description: input.description ?? null,
color: input.color ?? null,
};
}
export function buildRoleUpdateData(
input: UpdateRoleInput,
): Prisma.RoleUpdateInput {
return {
...(input.name !== undefined ? { name: input.name } : {}),
...(input.description !== undefined ? { description: input.description } : {}),
...(input.color !== undefined ? { color: input.color } : {}),
...(input.isActive !== undefined ? { isActive: input.isActive } : {}),
};
}
export function appendZeroAllocationCount<
TRole extends RoleCountRecord,
>(
role: TRole,
): TRole & { _count: { resourceRoles: number; allocations: number } } {
return {
...role,
_count: {
...role._count,
allocations: 0,
},
};
}
export async function assertRoleNameAvailable(
db: RoleIdentifierDb,
name: string,
ignoreId?: string,
): Promise<void> {
const existing = await db.role.findUnique({ where: { name } });
if (existing && existing.id !== ignoreId) {
throw new TRPCError({ code: "CONFLICT", message: `Role "${name}" already exists` });
}
}