refactor(api): extract project procedures
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
import { countPlanningEntries } from "@capakraken/application";
|
||||
import { FieldType, ProjectStatus } from "@capakraken/shared";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
import { paginate, PaginationInputSchema } from "../db/pagination.js";
|
||||
import type { TRPCContext } from "../trpc.js";
|
||||
import { buildDynamicFieldWhereClauses } from "./custom-field-filters.js";
|
||||
import { loadProjectPlanningReadModel } from "./project-planning-read-model.js";
|
||||
import { getProjectShoringRatio } from "./project-shoring-ratio.js";
|
||||
|
||||
export const ProjectListInputSchema = PaginationInputSchema.extend({
|
||||
status: z.nativeEnum(ProjectStatus).optional(),
|
||||
search: z.string().optional(),
|
||||
customFieldFilters: z.array(z.object({
|
||||
key: z.string(),
|
||||
value: z.string(),
|
||||
type: z.nativeEnum(FieldType),
|
||||
})).optional(),
|
||||
});
|
||||
|
||||
export const ProjectIdInputSchema = z.object({
|
||||
id: z.string(),
|
||||
});
|
||||
|
||||
export const ProjectShoringRatioInputSchema = z.object({
|
||||
projectId: z.string(),
|
||||
});
|
||||
|
||||
type ProjectProcedureContext = Pick<TRPCContext, "db">;
|
||||
|
||||
export async function listProjects(
|
||||
ctx: ProjectProcedureContext,
|
||||
input: z.infer<typeof ProjectListInputSchema>,
|
||||
) {
|
||||
const { status, search, cursor, customFieldFilters } = input;
|
||||
|
||||
const cfConditions = buildDynamicFieldWhereClauses(customFieldFilters)
|
||||
.map((dynamicFields) => ({ dynamicFields }));
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const where: any = {
|
||||
...(status ? { status } : {}),
|
||||
...(search
|
||||
? {
|
||||
OR: [
|
||||
{ name: { contains: search, mode: "insensitive" as const } },
|
||||
{ shortCode: { contains: search, mode: "insensitive" as const } },
|
||||
],
|
||||
}
|
||||
: {}),
|
||||
...(cfConditions.length > 0 ? { AND: cfConditions } : {}),
|
||||
};
|
||||
|
||||
const whereWithCursor = cursor ? { ...where, id: { gt: cursor } } : where;
|
||||
|
||||
const result = await paginate(
|
||||
({ skip, take }) =>
|
||||
ctx.db.project.findMany({
|
||||
where: whereWithCursor,
|
||||
skip,
|
||||
take,
|
||||
orderBy: [{ startDate: "asc" }, { id: "asc" }],
|
||||
}),
|
||||
() => ctx.db.project.count({ where }),
|
||||
input,
|
||||
);
|
||||
|
||||
const { countsByProjectId } = await countPlanningEntries(ctx.db, {
|
||||
projectIds: result.items.map((project) => project.id),
|
||||
});
|
||||
|
||||
return {
|
||||
projects: result.items.map((project) => ({
|
||||
...project,
|
||||
_count: {
|
||||
allocations: countsByProjectId.get(project.id) ?? 0,
|
||||
},
|
||||
})),
|
||||
total: result.total,
|
||||
page: result.page,
|
||||
limit: result.limit,
|
||||
nextCursor: result.nextCursor,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getProjectById(
|
||||
ctx: ProjectProcedureContext,
|
||||
input: z.infer<typeof ProjectIdInputSchema>,
|
||||
) {
|
||||
const [project, planningRead] = await Promise.all([
|
||||
ctx.db.project.findUnique({
|
||||
where: { id: input.id },
|
||||
include: { blueprint: true },
|
||||
}),
|
||||
loadProjectPlanningReadModel(ctx.db, { projectId: input.id }),
|
||||
]);
|
||||
|
||||
if (!project) {
|
||||
throw new TRPCError({ code: "NOT_FOUND", message: "Project not found" });
|
||||
}
|
||||
|
||||
return {
|
||||
...project,
|
||||
allocations: planningRead.readModel.assignments,
|
||||
demands: planningRead.readModel.demands,
|
||||
assignments: planningRead.readModel.assignments,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getProjectShoringRatioData(
|
||||
ctx: ProjectProcedureContext,
|
||||
input: z.infer<typeof ProjectShoringRatioInputSchema>,
|
||||
) {
|
||||
return getProjectShoringRatio(ctx.db, input.projectId);
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
import { countPlanningEntries } from "@capakraken/application";
|
||||
import { FieldType, ProjectStatus } from "@capakraken/shared";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
import { paginate, PaginationInputSchema } from "../db/pagination.js";
|
||||
import { buildDynamicFieldWhereClauses } from "./custom-field-filters.js";
|
||||
import { projectCostReadProcedures } from "./project-cost-read.js";
|
||||
import { projectCoverProcedures } from "./project-cover.js";
|
||||
import { projectIdentifierReadProcedures } from "./project-identifier-read.js";
|
||||
import { createProjectLifecycleProcedures } from "./project-lifecycle.js";
|
||||
import { createProjectMutationProcedures } from "./project-mutations.js";
|
||||
import { loadProjectPlanningReadModel } from "./project-planning-read-model.js";
|
||||
import { createProjectBackgroundEffects } from "./project-background-effects.js";
|
||||
import { getProjectShoringRatio } from "./project-shoring-ratio.js";
|
||||
import {
|
||||
getProjectById,
|
||||
getProjectShoringRatioData,
|
||||
listProjects,
|
||||
ProjectIdInputSchema,
|
||||
ProjectListInputSchema,
|
||||
ProjectShoringRatioInputSchema,
|
||||
} from "./project-procedure-support.js";
|
||||
import { controllerProcedure, createTRPCRouter } from "../trpc.js";
|
||||
|
||||
const projectBackgroundEffects = createProjectBackgroundEffects();
|
||||
@@ -27,94 +27,15 @@ export const projectRouter = createTRPCRouter({
|
||||
...createProjectMutationProcedures(projectBackgroundEffects),
|
||||
|
||||
list: controllerProcedure
|
||||
.input(
|
||||
PaginationInputSchema.extend({
|
||||
status: z.nativeEnum(ProjectStatus).optional(),
|
||||
search: z.string().optional(),
|
||||
// Custom field JSONB filters
|
||||
customFieldFilters: z.array(z.object({
|
||||
key: z.string(),
|
||||
value: z.string(),
|
||||
type: z.nativeEnum(FieldType),
|
||||
})).optional(),
|
||||
}),
|
||||
)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const { status, search, cursor, customFieldFilters } = input;
|
||||
|
||||
const cfConditions = buildDynamicFieldWhereClauses(customFieldFilters).map((dynamicFields) => ({ dynamicFields }));
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const where: any = {
|
||||
...(status ? { status } : {}),
|
||||
...(search
|
||||
? {
|
||||
OR: [
|
||||
{ name: { contains: search, mode: "insensitive" as const } },
|
||||
{ shortCode: { contains: search, mode: "insensitive" as const } },
|
||||
],
|
||||
}
|
||||
: {}),
|
||||
...(cfConditions.length > 0 ? { AND: cfConditions } : {}),
|
||||
};
|
||||
|
||||
const whereWithCursor = cursor ? { ...where, id: { gt: cursor } } : where;
|
||||
|
||||
const result = await paginate(
|
||||
({ skip, take }) =>
|
||||
ctx.db.project.findMany({
|
||||
where: whereWithCursor,
|
||||
skip,
|
||||
take,
|
||||
orderBy: [{ startDate: "asc" }, { id: "asc" }],
|
||||
}),
|
||||
() => ctx.db.project.count({ where }),
|
||||
input,
|
||||
);
|
||||
|
||||
const { countsByProjectId } = await countPlanningEntries(ctx.db, {
|
||||
projectIds: result.items.map((project) => project.id),
|
||||
});
|
||||
|
||||
return {
|
||||
projects: result.items.map((project) => ({
|
||||
...project,
|
||||
_count: {
|
||||
allocations: countsByProjectId.get(project.id) ?? 0,
|
||||
},
|
||||
})),
|
||||
total: result.total,
|
||||
page: result.page,
|
||||
limit: result.limit,
|
||||
nextCursor: result.nextCursor,
|
||||
};
|
||||
}),
|
||||
.input(ProjectListInputSchema)
|
||||
.query(({ ctx, input }) => listProjects(ctx, input)),
|
||||
|
||||
getById: controllerProcedure
|
||||
.input(z.object({ id: z.string() }))
|
||||
.query(async ({ ctx, input }) => {
|
||||
const [project, planningRead] = await Promise.all([
|
||||
ctx.db.project.findUnique({
|
||||
where: { id: input.id },
|
||||
include: { blueprint: true },
|
||||
}),
|
||||
loadProjectPlanningReadModel(ctx.db, { projectId: input.id }),
|
||||
]);
|
||||
|
||||
if (!project) {
|
||||
throw new TRPCError({ code: "NOT_FOUND", message: "Project not found" });
|
||||
}
|
||||
|
||||
return {
|
||||
...project,
|
||||
allocations: planningRead.readModel.assignments,
|
||||
demands: planningRead.readModel.demands,
|
||||
assignments: planningRead.readModel.assignments,
|
||||
};
|
||||
}),
|
||||
.input(ProjectIdInputSchema)
|
||||
.query(({ ctx, input }) => getProjectById(ctx, input)),
|
||||
|
||||
getShoringRatio: controllerProcedure
|
||||
.input(z.object({ projectId: z.string() }))
|
||||
.query(async ({ ctx, input }) => getProjectShoringRatio(ctx.db, input.projectId)),
|
||||
.input(ProjectShoringRatioInputSchema)
|
||||
.query(({ ctx, input }) => getProjectShoringRatioData(ctx, input)),
|
||||
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user