refactor(api): extract role router support
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
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` });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user