refactor(api): extract role read procedures

This commit is contained in:
2026-03-31 21:22:44 +02:00
parent cba4d44f16
commit 884f1012c9
5 changed files with 345 additions and 84 deletions
@@ -2,17 +2,34 @@ import { CreateRoleSchema, PermissionKey, UpdateRoleSchema } from "@capakraken/s
import { TRPCError } from "@trpc/server";
import { z } from "zod";
import { findUniqueOrThrow } from "../db/helpers.js";
import { RESOURCE_BRIEF_SELECT } from "../db/selects.js";
import { emitRoleCreated, emitRoleDeleted, emitRoleUpdated } from "../sse/event-bus.js";
import type { TRPCContext } from "../trpc.js";
import { requirePermission } from "../trpc.js";
import {
appendZeroAllocationCount,
assertRoleNameAvailable,
attachRolePlanningEntryCounts,
attachSingleRolePlanningEntryCount,
buildRoleCreateData,
buildRoleUpdateData,
buildRoleListWhere,
findRoleByIdentifier,
} from "./role-support.js";
export const RoleListInputSchema = z.object({
isActive: z.boolean().optional(),
search: z.string().optional(),
});
export const RoleIdentifierInputSchema = z.object({
identifier: z.string(),
});
export const ResolveRoleIdentifierInputSchema = z.object({
identifier: z.string().trim().min(1),
});
export const RoleIdInputSchema = z.object({
id: z.string(),
});
@@ -26,6 +43,91 @@ type RoleMutationContext = Pick<TRPCContext, "db" | "dbUser"> & {
permissions: Set<PermissionKey>;
};
type RoleReadContext = Pick<TRPCContext, "db">;
export async function listRoles(
ctx: RoleReadContext,
input: z.infer<typeof RoleListInputSchema>,
) {
const roles = await ctx.db.role.findMany({
where: buildRoleListWhere(input),
include: {
_count: {
select: { resourceRoles: true },
},
},
orderBy: { name: "asc" },
});
return attachRolePlanningEntryCounts(ctx.db, roles);
}
export async function resolveRoleByIdentifier(
ctx: RoleReadContext,
input: z.infer<typeof ResolveRoleIdentifierInputSchema>,
) {
const select = {
id: true,
name: true,
color: true,
isActive: true,
} as const;
return findRoleByIdentifier<{
id: string;
name: string;
color: string | null;
isActive: boolean;
}>(ctx.db, input.identifier, select);
}
export async function getRoleByIdentifier(
ctx: RoleReadContext,
input: z.infer<typeof RoleIdentifierInputSchema>,
) {
const select = {
id: true,
name: true,
description: true,
color: true,
isActive: true,
_count: { select: { resourceRoles: true } },
} as const;
const role = await findRoleByIdentifier<{
id: string;
name: string;
description: string | null;
color: string | null;
isActive: boolean;
_count: { resourceRoles: number };
}>(ctx.db, input.identifier, select);
return attachSingleRolePlanningEntryCount(ctx.db, role);
}
export async function getRoleById(
ctx: RoleReadContext,
input: z.infer<typeof RoleIdInputSchema>,
) {
const role = await findUniqueOrThrow(
ctx.db.role.findUnique({
where: { id: input.id },
include: {
_count: { select: { resourceRoles: true } },
resourceRoles: {
include: {
resource: { select: RESOURCE_BRIEF_SELECT },
},
},
},
}),
"Role",
);
return attachSingleRolePlanningEntryCount(ctx.db, role);
}
export async function createRole(
ctx: RoleMutationContext,
input: z.infer<typeof CreateRoleSchema>,
+15 -84
View File
@@ -1,7 +1,4 @@
import { CreateRoleSchema } from "@capakraken/shared";
import { z } from "zod";
import { findUniqueOrThrow } from "../db/helpers.js";
import { RESOURCE_BRIEF_SELECT } from "../db/selects.js";
import {
createTRPCRouter,
managerProcedure,
@@ -12,100 +9,34 @@ import {
createRole,
deactivateRole,
deleteRole,
getRoleById,
getRoleByIdentifier,
listRoles,
ResolveRoleIdentifierInputSchema,
resolveRoleByIdentifier,
RoleIdentifierInputSchema,
RoleIdInputSchema,
RoleListInputSchema,
UpdateRoleProcedureInputSchema,
updateRole,
} from "./role-procedure-support.js";
import {
attachRolePlanningEntryCounts,
attachSingleRolePlanningEntryCount,
buildRoleListWhere,
findRoleByIdentifier,
} from "./role-support.js";
export const roleRouter = createTRPCRouter({
list: planningReadProcedure
.input(
z.object({
isActive: z.boolean().optional(),
search: z.string().optional(),
}),
)
.query(async ({ ctx, input }) => {
const roles = await ctx.db.role.findMany({
where: buildRoleListWhere(input),
include: {
_count: {
select: { resourceRoles: true },
},
},
orderBy: { name: "asc" },
});
return attachRolePlanningEntryCounts(ctx.db, roles);
}),
.input(RoleListInputSchema)
.query(({ ctx, input }) => listRoles(ctx, input)),
resolveByIdentifier: protectedProcedure
.input(z.object({ identifier: z.string().trim().min(1) }))
.query(async ({ ctx, input }) => {
const select = {
id: true,
name: true,
color: true,
isActive: true,
} as const;
return findRoleByIdentifier<{
id: string;
name: string;
color: string | null;
isActive: boolean;
}>(ctx.db, input.identifier, select);
}),
.input(ResolveRoleIdentifierInputSchema)
.query(({ ctx, input }) => resolveRoleByIdentifier(ctx, input)),
getByIdentifier: planningReadProcedure
.input(z.object({ identifier: z.string() }))
.query(async ({ ctx, input }) => {
const select = {
id: true,
name: true,
description: true,
color: true,
isActive: true,
_count: { select: { resourceRoles: true } },
} as const;
const role = await findRoleByIdentifier<{
id: string;
name: string;
description: string | null;
color: string | null;
isActive: boolean;
_count: { resourceRoles: number };
}>(ctx.db, input.identifier, select);
return attachSingleRolePlanningEntryCount(ctx.db, role);
}),
.input(RoleIdentifierInputSchema)
.query(({ ctx, input }) => getRoleByIdentifier(ctx, input)),
getById: planningReadProcedure
.input(z.object({ id: z.string() }))
.query(async ({ ctx, input }) => {
const role = await findUniqueOrThrow(
ctx.db.role.findUnique({
where: { id: input.id },
include: {
_count: { select: { resourceRoles: true } },
resourceRoles: {
include: {
resource: { select: RESOURCE_BRIEF_SELECT },
},
},
},
}),
"Role",
);
return attachSingleRolePlanningEntryCount(ctx.db, role);
}),
.input(RoleIdInputSchema)
.query(({ ctx, input }) => getRoleById(ctx, input)),
create: managerProcedure
.input(CreateRoleSchema)