refactor(api): extract project router support
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
import { BlueprintTarget, CreateProjectSchema, PermissionKey, UpdateProjectSchema } from "@capakraken/shared";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
import { findUniqueOrThrow } from "../db/helpers.js";
|
||||
import { assertBlueprintDynamicFields } from "./blueprint-validation.js";
|
||||
import { managerProcedure, requirePermission, type TRPCContext } from "../trpc.js";
|
||||
import type { ProjectBackgroundEffects } from "./project-background-effects.js";
|
||||
|
||||
function buildProjectCreateData(
|
||||
input: z.infer<typeof CreateProjectSchema>,
|
||||
): Parameters<TRPCContext["db"]["project"]["create"]>[0]["data"] {
|
||||
return {
|
||||
shortCode: input.shortCode,
|
||||
name: input.name,
|
||||
orderType: input.orderType,
|
||||
allocationType: input.allocationType,
|
||||
winProbability: input.winProbability,
|
||||
budgetCents: input.budgetCents,
|
||||
startDate: input.startDate,
|
||||
endDate: input.endDate,
|
||||
status: input.status,
|
||||
responsiblePerson: input.responsiblePerson,
|
||||
...(input.color !== undefined ? { color: input.color } : {}),
|
||||
staffingReqs: input.staffingReqs as unknown as import("@capakraken/db").Prisma.InputJsonValue,
|
||||
dynamicFields: input.dynamicFields as unknown as import("@capakraken/db").Prisma.InputJsonValue,
|
||||
blueprintId: input.blueprintId,
|
||||
...(input.utilizationCategoryId !== undefined ? { utilizationCategoryId: input.utilizationCategoryId || null } : {}),
|
||||
...(input.clientId !== undefined ? { clientId: input.clientId || null } : {}),
|
||||
} as unknown as Parameters<TRPCContext["db"]["project"]["create"]>[0]["data"];
|
||||
}
|
||||
|
||||
function buildProjectUpdateData(
|
||||
input: z.infer<typeof UpdateProjectSchema>,
|
||||
): Parameters<TRPCContext["db"]["project"]["update"]>[0]["data"] {
|
||||
return {
|
||||
...(input.name !== undefined ? { name: input.name } : {}),
|
||||
...(input.orderType !== undefined ? { orderType: input.orderType } : {}),
|
||||
...(input.allocationType !== undefined ? { allocationType: input.allocationType } : {}),
|
||||
...(input.winProbability !== undefined ? { winProbability: input.winProbability } : {}),
|
||||
...(input.budgetCents !== undefined ? { budgetCents: input.budgetCents } : {}),
|
||||
...(input.startDate !== undefined ? { startDate: input.startDate } : {}),
|
||||
...(input.endDate !== undefined ? { endDate: input.endDate } : {}),
|
||||
...(input.status !== undefined ? { status: input.status } : {}),
|
||||
...(input.responsiblePerson !== undefined ? { responsiblePerson: input.responsiblePerson } : {}),
|
||||
...(input.color !== undefined ? { color: input.color } : {}),
|
||||
...(input.staffingReqs !== undefined ? { staffingReqs: input.staffingReqs as unknown as import("@capakraken/db").Prisma.InputJsonValue } : {}),
|
||||
...(input.dynamicFields !== undefined ? { dynamicFields: input.dynamicFields as unknown as import("@capakraken/db").Prisma.InputJsonValue } : {}),
|
||||
...(input.blueprintId !== undefined ? { blueprintId: input.blueprintId } : {}),
|
||||
...(input.utilizationCategoryId !== undefined ? { utilizationCategoryId: input.utilizationCategoryId || null } : {}),
|
||||
...(input.clientId !== undefined ? { clientId: input.clientId || null } : {}),
|
||||
...(input.shoringThreshold !== undefined ? { shoringThreshold: input.shoringThreshold } : {}),
|
||||
...(input.onshoreCountryCode !== undefined ? { onshoreCountryCode: input.onshoreCountryCode } : {}),
|
||||
} as unknown as Parameters<TRPCContext["db"]["project"]["update"]>[0]["data"];
|
||||
}
|
||||
|
||||
export function createProjectMutationProcedures(
|
||||
backgroundEffects: ProjectBackgroundEffects,
|
||||
) {
|
||||
return {
|
||||
create: managerProcedure
|
||||
.input(CreateProjectSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
requirePermission(ctx, PermissionKey.MANAGE_PROJECTS);
|
||||
|
||||
const existing = await ctx.db.project.findUnique({
|
||||
where: { shortCode: input.shortCode },
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
throw new TRPCError({
|
||||
code: "CONFLICT",
|
||||
message: `Project with short code "${input.shortCode}" already exists`,
|
||||
});
|
||||
}
|
||||
|
||||
await assertBlueprintDynamicFields({
|
||||
db: ctx.db,
|
||||
blueprintId: input.blueprintId,
|
||||
dynamicFields: input.dynamicFields,
|
||||
target: BlueprintTarget.PROJECT,
|
||||
});
|
||||
|
||||
const project = await ctx.db.project.create({
|
||||
data: buildProjectCreateData(input),
|
||||
});
|
||||
|
||||
await ctx.db.auditLog.create({
|
||||
data: {
|
||||
entityType: "Project",
|
||||
entityId: project.id,
|
||||
action: "CREATE",
|
||||
changes: { after: project },
|
||||
},
|
||||
});
|
||||
|
||||
backgroundEffects.invalidateDashboardCacheInBackground();
|
||||
backgroundEffects.dispatchProjectWebhookInBackground(ctx.db, "project.created", {
|
||||
id: project.id,
|
||||
shortCode: project.shortCode,
|
||||
name: project.name,
|
||||
status: project.status,
|
||||
});
|
||||
|
||||
return project;
|
||||
}),
|
||||
|
||||
update: managerProcedure
|
||||
.input(z.object({ id: z.string(), data: UpdateProjectSchema }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
requirePermission(ctx, PermissionKey.MANAGE_PROJECTS);
|
||||
|
||||
const existing = await findUniqueOrThrow(
|
||||
ctx.db.project.findUnique({ where: { id: input.id } }),
|
||||
"Project",
|
||||
);
|
||||
|
||||
const nextBlueprintId = input.data.blueprintId ?? existing.blueprintId ?? undefined;
|
||||
const nextDynamicFields = (input.data.dynamicFields ?? existing.dynamicFields ?? {}) as Record<string, unknown>;
|
||||
|
||||
await assertBlueprintDynamicFields({
|
||||
db: ctx.db,
|
||||
blueprintId: nextBlueprintId,
|
||||
dynamicFields: nextDynamicFields,
|
||||
target: BlueprintTarget.PROJECT,
|
||||
});
|
||||
|
||||
const updated = await ctx.db.project.update({
|
||||
where: { id: input.id },
|
||||
data: buildProjectUpdateData(input.data),
|
||||
});
|
||||
|
||||
await ctx.db.auditLog.create({
|
||||
data: {
|
||||
entityType: "Project",
|
||||
entityId: input.id,
|
||||
action: "UPDATE",
|
||||
changes: { before: existing, after: updated },
|
||||
},
|
||||
});
|
||||
|
||||
backgroundEffects.invalidateDashboardCacheInBackground();
|
||||
return updated;
|
||||
}),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user