From 66a33a5ad654b2734b69b3b688f310ff64675282 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hartmut=20N=C3=B6renberg?= Date: Tue, 31 Mar 2026 18:22:20 +0200 Subject: [PATCH] refactor(api): centralize timeline read detail schemas --- .../__tests__/timeline-read-shared.test.ts | 30 +++++++++++++++++++ .../api/src/router/timeline-entry-read.ts | 16 ++-------- .../api/src/router/timeline-project-read.ts | 12 +++----- .../api/src/router/timeline-read-shared.ts | 19 ++++++++++++ 4 files changed, 55 insertions(+), 22 deletions(-) diff --git a/packages/api/src/__tests__/timeline-read-shared.test.ts b/packages/api/src/__tests__/timeline-read-shared.test.ts index 112fd6e..771b112 100644 --- a/packages/api/src/__tests__/timeline-read-shared.test.ts +++ b/packages/api/src/__tests__/timeline-read-shared.test.ts @@ -1,6 +1,8 @@ import { describe, expect, it } from "vitest"; import { buildTimelineEntriesDetailInput, + TimelineDetailFiltersSchema, + TimelineProjectContextDetailSchema, } from "../router/timeline-read-shared.js"; describe("timeline read shared", () => { @@ -39,4 +41,32 @@ describe("timeline read shared", () => { }); }); + it("shares detail filter schemas across timeline read endpoints", () => { + expect(TimelineDetailFiltersSchema.parse({ + startDate: "2026-04-01", + durationDays: 14, + resourceIds: ["resource_1"], + countryCodes: ["DE"], + })).toEqual({ + startDate: "2026-04-01", + endDate: undefined, + durationDays: 14, + resourceIds: ["resource_1"], + projectIds: undefined, + clientIds: undefined, + chapters: undefined, + eids: undefined, + countryCodes: ["DE"], + }); + + expect(TimelineProjectContextDetailSchema.parse({ + projectId: "project_1", + endDate: "2026-04-20", + })).toEqual({ + projectId: "project_1", + startDate: undefined, + endDate: "2026-04-20", + durationDays: undefined, + }); + }); }); diff --git a/packages/api/src/router/timeline-entry-read.ts b/packages/api/src/router/timeline-entry-read.ts index 6a13ccd..3367037 100644 --- a/packages/api/src/router/timeline-entry-read.ts +++ b/packages/api/src/router/timeline-entry-read.ts @@ -1,6 +1,6 @@ -import { z } from "zod"; import { controllerProcedure, protectedProcedure } from "../trpc.js"; import { + TimelineDetailFiltersSchema, TimelineWindowFiltersSchema, } from "./timeline-read-shared.js"; import { @@ -24,18 +24,6 @@ export const timelineEntryReadProcedures = { .query(({ ctx, input }) => readMyTimelineEntriesView(ctx, input)), getEntriesDetail: controllerProcedure - .input( - z.object({ - startDate: z.string().optional(), - endDate: z.string().optional(), - durationDays: z.number().int().min(1).max(366).optional(), - resourceIds: z.array(z.string()).optional(), - projectIds: z.array(z.string()).optional(), - clientIds: z.array(z.string()).optional(), - chapters: z.array(z.string()).optional(), - eids: z.array(z.string()).optional(), - countryCodes: z.array(z.string()).optional(), - }), - ) + .input(TimelineDetailFiltersSchema) .query(({ ctx, input }) => readTimelineEntriesDetail(ctx.db, input)), }; diff --git a/packages/api/src/router/timeline-project-read.ts b/packages/api/src/router/timeline-project-read.ts index 239807b..c574104 100644 --- a/packages/api/src/router/timeline-project-read.ts +++ b/packages/api/src/router/timeline-project-read.ts @@ -2,6 +2,9 @@ import { ShiftProjectSchema } from "@capakraken/shared"; import { z } from "zod"; import { controllerProcedure } from "../trpc.js"; import { previewTimelineProjectShift } from "./timeline-project-load-support.js"; +import { + TimelineProjectContextDetailSchema, +} from "./timeline-read-shared.js"; import { readTimelineProjectBudgetStatusResponse, readTimelineProjectContextDetailResponse, @@ -15,14 +18,7 @@ export const timelineProjectReadProcedures = { .query(async ({ ctx, input }) => readTimelineProjectContextResponse(ctx.db, input.projectId)), getProjectContextDetail: controllerProcedure - .input( - z.object({ - projectId: z.string(), - startDate: z.string().optional(), - endDate: z.string().optional(), - durationDays: z.number().int().min(1).max(366).optional(), - }), - ) + .input(TimelineProjectContextDetailSchema) .query(async ({ ctx, input }) => readTimelineProjectContextDetailResponse(ctx.db, input)), previewShift: controllerProcedure diff --git a/packages/api/src/router/timeline-read-shared.ts b/packages/api/src/router/timeline-read-shared.ts index 2f330f3..5c58b41 100644 --- a/packages/api/src/router/timeline-read-shared.ts +++ b/packages/api/src/router/timeline-read-shared.ts @@ -41,6 +41,25 @@ export const TimelineWindowFiltersSchema = z.object({ countryCodes: z.array(z.string()).optional(), }); +export const TimelineDetailFiltersSchema = z.object({ + startDate: z.string().optional(), + endDate: z.string().optional(), + durationDays: z.number().int().min(1).max(366).optional(), + resourceIds: z.array(z.string()).optional(), + projectIds: z.array(z.string()).optional(), + clientIds: z.array(z.string()).optional(), + chapters: z.array(z.string()).optional(), + eids: z.array(z.string()).optional(), + countryCodes: z.array(z.string()).optional(), +}); + +export const TimelineProjectContextDetailSchema = z.object({ + projectId: z.string(), + startDate: z.string().optional(), + endDate: z.string().optional(), + durationDays: z.number().int().min(1).max(366).optional(), +}); + type TimelineWindowFiltersInput = z.infer; export function getAssignmentResourceIds(