refactor(api): extract holiday calendar write support

This commit is contained in:
2026-03-31 13:25:27 +02:00
parent 8e542fd6ba
commit 0a10a440ee
3 changed files with 308 additions and 135 deletions
@@ -0,0 +1,119 @@
import { describe, expect, it, vi } from "vitest";
import {
assertHolidayCalendarEntryDateAvailable,
assertHolidayCalendarScopeConsistency,
clampHolidayCalendarDate,
normalizeHolidayCalendarScopeInput,
normalizeHolidayCalendarStateCode,
resolveHolidayCalendarUpdateScope,
} from "../router/holiday-calendar-write-support.js";
describe("holiday calendar write support", () => {
it("normalizes state codes and optional scope fields", () => {
expect(normalizeHolidayCalendarStateCode(" by ")).toBe("BY");
expect(normalizeHolidayCalendarStateCode(undefined)).toBeNull();
expect(normalizeHolidayCalendarScopeInput({
stateCode: " ny ",
metroCityId: undefined,
})).toEqual({
stateCode: "NY",
metroCityId: null,
});
});
it("reuses existing scope values for partial updates", () => {
expect(resolveHolidayCalendarUpdateScope({
existing: {
stateCode: "BY",
metroCityId: "city_1",
},
data: {
stateCode: undefined,
metroCityId: null,
},
})).toEqual({
stateCode: "BY",
metroCityId: null,
});
});
it("clamps dates to UTC midnight", () => {
expect(clampHolidayCalendarDate(new Date("2026-12-24T18:45:00.000Z")).toISOString())
.toBe("2026-12-24T00:00:00.000Z");
});
it("rejects invalid state and city scope combinations before hitting uniqueness checks", async () => {
const db = {
metroCity: { findUnique: vi.fn() },
holidayCalendar: { findFirst: vi.fn() },
} as never;
await expect(assertHolidayCalendarScopeConsistency(db, {
scopeType: "STATE",
countryId: "country_de",
stateCode: null,
metroCityId: null,
})).rejects.toMatchObject({
code: "BAD_REQUEST",
message: "State calendars require a state code",
});
await expect(assertHolidayCalendarScopeConsistency(db, {
scopeType: "CITY",
countryId: "country_de",
stateCode: null,
metroCityId: null,
})).rejects.toMatchObject({
code: "BAD_REQUEST",
message: "City calendars require a metro city",
});
expect(db.holidayCalendar.findFirst).not.toHaveBeenCalled();
});
it("rejects city scopes whose metro city belongs to a different country", async () => {
const db = {
metroCity: {
findUnique: vi.fn().mockResolvedValue({
id: "city_augsburg",
countryId: "country_us",
}),
},
holidayCalendar: { findFirst: vi.fn() },
} as never;
await expect(assertHolidayCalendarScopeConsistency(db, {
scopeType: "CITY",
countryId: "country_de",
stateCode: null,
metroCityId: "city_augsburg",
})).rejects.toMatchObject({
code: "BAD_REQUEST",
message: "Metro city must belong to the selected country",
});
});
it("rejects duplicate holiday entry dates using the clamped date", async () => {
const db = {
holidayCalendarEntry: {
findFirst: vi.fn().mockResolvedValue({ id: "entry_existing" }),
},
} as never;
await expect(assertHolidayCalendarEntryDateAvailable(db, {
holidayCalendarId: "cal_1",
date: new Date("2026-12-24T18:45:00.000Z"),
})).rejects.toMatchObject({
code: "CONFLICT",
message: "A holiday entry for this calendar and date already exists",
});
expect(db.holidayCalendarEntry.findFirst).toHaveBeenCalledWith({
where: {
holidayCalendarId: "cal_1",
date: new Date("2026-12-24T00:00:00.000Z"),
},
select: { id: true },
});
});
});
@@ -0,0 +1,158 @@
import { TRPCError } from "@trpc/server";
import { findUniqueOrThrow } from "../db/helpers.js";
import {
type HolidayCalendarDb,
} from "./holiday-calendar-shared.js";
import type { HolidayCalendarScopeInput } from "@capakraken/shared";
type HolidayCalendarScope = HolidayCalendarScopeInput;
const HOLIDAY_SCOPE = {
COUNTRY: "COUNTRY",
STATE: "STATE",
CITY: "CITY",
} as const satisfies Record<HolidayCalendarScope, HolidayCalendarScope>;
export function clampHolidayCalendarDate(date: Date): Date {
const value = new Date(date);
value.setUTCHours(0, 0, 0, 0);
return value;
}
export function normalizeHolidayCalendarStateCode(
stateCode: string | null | undefined,
): string | null {
return stateCode?.trim().toUpperCase() ?? null;
}
export function normalizeHolidayCalendarScopeInput(input: {
stateCode?: string | null | undefined;
metroCityId?: string | null | undefined;
}) {
return {
stateCode: normalizeHolidayCalendarStateCode(input.stateCode),
metroCityId: input.metroCityId ?? null,
};
}
export function resolveHolidayCalendarUpdateScope(input: {
existing: {
stateCode: string | null;
metroCityId: string | null;
};
data: {
stateCode?: string | null | undefined;
metroCityId?: string | null | undefined;
};
}) {
return {
stateCode: input.data.stateCode === undefined
? input.existing.stateCode
: normalizeHolidayCalendarStateCode(input.data.stateCode),
metroCityId: input.data.metroCityId === undefined
? input.existing.metroCityId
: input.data.metroCityId ?? null,
};
}
export async function assertHolidayCalendarEntryDateAvailable(
db: HolidayCalendarDb,
input: {
holidayCalendarId: string;
date: Date;
},
ignoreId?: string,
) {
const existing = await db.holidayCalendarEntry.findFirst({
where: {
holidayCalendarId: input.holidayCalendarId,
date: clampHolidayCalendarDate(input.date),
...(ignoreId ? { id: { not: ignoreId } } : {}),
},
select: { id: true },
});
if (existing) {
throw new TRPCError({
code: "CONFLICT",
message: "A holiday entry for this calendar and date already exists",
});
}
}
export async function assertHolidayCalendarScopeConsistency(
db: HolidayCalendarDb,
input: {
scopeType: HolidayCalendarScope;
countryId: string;
stateCode?: string | null;
metroCityId?: string | null;
},
ignoreId?: string,
) {
if (input.scopeType === HOLIDAY_SCOPE.COUNTRY) {
if (input.stateCode || input.metroCityId) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Country calendars may not define a state or metro city",
});
}
}
if (input.scopeType === HOLIDAY_SCOPE.STATE) {
if (!input.stateCode) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "State calendars require a state code",
});
}
if (input.metroCityId) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "State calendars may not define a metro city",
});
}
}
if (input.scopeType === HOLIDAY_SCOPE.CITY) {
if (!input.metroCityId) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "City calendars require a metro city",
});
}
const metroCity = await findUniqueOrThrow(
db.metroCity.findUnique({
where: { id: input.metroCityId },
select: { id: true, countryId: true },
}),
"Metro city",
);
if (metroCity.countryId !== input.countryId) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Metro city must belong to the selected country",
});
}
}
const existing = await db.holidayCalendar.findFirst({
where: {
countryId: input.countryId,
scopeType: input.scopeType,
...(input.scopeType === HOLIDAY_SCOPE.STATE ? { stateCode: input.stateCode ?? null } : {}),
...(input.scopeType === HOLIDAY_SCOPE.CITY ? { metroCityId: input.metroCityId ?? null } : {}),
...(ignoreId ? { id: { not: ignoreId } } : {}),
},
select: { id: true },
});
if (existing) {
throw new TRPCError({
code: "CONFLICT",
message: "A holiday calendar for this exact scope already exists",
});
}
}
+31 -135
View File
@@ -1,134 +1,23 @@
import {
CreateHolidayCalendarEntrySchema,
CreateHolidayCalendarSchema,
type HolidayCalendarScopeInput,
UpdateHolidayCalendarEntrySchema,
UpdateHolidayCalendarSchema,
} from "@capakraken/shared";
import { TRPCError } from "@trpc/server";
import { z } from "zod";
import { findUniqueOrThrow } from "../db/helpers.js";
import { createAuditEntry } from "../lib/audit.js";
import { createTRPCRouter, adminProcedure } from "../trpc.js";
import { holidayCalendarCatalogReadProcedures } from "./holiday-calendar-catalog-read.js";
import { holidayCalendarResolutionReadProcedures } from "./holiday-calendar-resolution-read.js";
import { asHolidayCalendarDb, type HolidayCalendarDb } from "./holiday-calendar-shared.js";
type HolidayCalendarScope = HolidayCalendarScopeInput;
const HOLIDAY_SCOPE = {
COUNTRY: "COUNTRY",
STATE: "STATE",
CITY: "CITY",
} as const satisfies Record<HolidayCalendarScope, HolidayCalendarScope>;
function clampDate(date: Date): Date {
const value = new Date(date);
value.setUTCHours(0, 0, 0, 0);
return value;
}
async function assertEntryDateAvailable(
db: HolidayCalendarDb,
input: {
holidayCalendarId: string;
date: Date;
},
ignoreId?: string,
) {
const existing = await db.holidayCalendarEntry.findFirst({
where: {
holidayCalendarId: input.holidayCalendarId,
date: clampDate(input.date),
...(ignoreId ? { id: { not: ignoreId } } : {}),
},
select: { id: true },
});
if (existing) {
throw new TRPCError({
code: "CONFLICT",
message: "A holiday entry for this calendar and date already exists",
});
}
}
async function assertScopeConsistency(
db: HolidayCalendarDb,
input: {
scopeType: HolidayCalendarScope;
countryId: string;
stateCode?: string | null;
metroCityId?: string | null;
},
ignoreId?: string,
) {
if (input.scopeType === HOLIDAY_SCOPE.COUNTRY) {
if (input.stateCode || input.metroCityId) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Country calendars may not define a state or metro city",
});
}
}
if (input.scopeType === HOLIDAY_SCOPE.STATE) {
if (!input.stateCode) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "State calendars require a state code",
});
}
if (input.metroCityId) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "State calendars may not define a metro city",
});
}
}
if (input.scopeType === HOLIDAY_SCOPE.CITY) {
if (!input.metroCityId) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "City calendars require a metro city",
});
}
const metroCity = await findUniqueOrThrow(
db.metroCity.findUnique({
where: { id: input.metroCityId },
select: { id: true, countryId: true },
}),
"Metro city",
);
if (metroCity.countryId !== input.countryId) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Metro city must belong to the selected country",
});
}
}
const existing = await db.holidayCalendar.findFirst({
where: {
countryId: input.countryId,
scopeType: input.scopeType,
...(input.scopeType === HOLIDAY_SCOPE.STATE ? { stateCode: input.stateCode ?? null } : {}),
...(input.scopeType === HOLIDAY_SCOPE.CITY ? { metroCityId: input.metroCityId ?? null } : {}),
...(ignoreId ? { id: { not: ignoreId } } : {}),
},
select: { id: true },
});
if (existing) {
throw new TRPCError({
code: "CONFLICT",
message: "A holiday calendar for this exact scope already exists",
});
}
}
import { asHolidayCalendarDb } from "./holiday-calendar-shared.js";
import {
assertHolidayCalendarEntryDateAvailable,
assertHolidayCalendarScopeConsistency,
clampHolidayCalendarDate,
normalizeHolidayCalendarScopeInput,
resolveHolidayCalendarUpdateScope,
} from "./holiday-calendar-write-support.js";
export const holidayCalendarRouter = createTRPCRouter({
...holidayCalendarCatalogReadProcedures,
@@ -147,11 +36,18 @@ export const holidayCalendarRouter = createTRPCRouter({
"Country",
);
await assertScopeConsistency(db, {
await assertHolidayCalendarScopeConsistency(db, {
scopeType: input.scopeType,
countryId: input.countryId,
stateCode: input.stateCode?.trim().toUpperCase() ?? null,
metroCityId: input.metroCityId ?? null,
...normalizeHolidayCalendarScopeInput({
stateCode: input.stateCode,
metroCityId: input.metroCityId,
}),
});
const normalizedScope = normalizeHolidayCalendarScopeInput({
stateCode: input.stateCode,
metroCityId: input.metroCityId,
});
const created = await db.holidayCalendar.create({
@@ -159,8 +55,8 @@ export const holidayCalendarRouter = createTRPCRouter({
name: input.name,
scopeType: input.scopeType,
countryId: input.countryId,
...(input.stateCode ? { stateCode: input.stateCode.trim().toUpperCase() } : {}),
...(input.metroCityId ? { metroCityId: input.metroCityId } : {}),
...(normalizedScope.stateCode ? { stateCode: normalizedScope.stateCode } : {}),
...(normalizedScope.metroCityId ? { metroCityId: normalizedScope.metroCityId } : {}),
isActive: input.isActive ?? true,
priority: input.priority ?? 0,
},
@@ -194,14 +90,12 @@ export const holidayCalendarRouter = createTRPCRouter({
"Holiday calendar",
);
const stateCode = input.data.stateCode === undefined
? existing.stateCode
: input.data.stateCode?.trim().toUpperCase() ?? null;
const metroCityId = input.data.metroCityId === undefined
? existing.metroCityId
: input.data.metroCityId ?? null;
const { stateCode, metroCityId } = resolveHolidayCalendarUpdateScope({
existing,
data: input.data,
});
await assertScopeConsistency(db, {
await assertHolidayCalendarScopeConsistency(db, {
scopeType: existing.scopeType,
countryId: existing.countryId,
stateCode,
@@ -280,7 +174,7 @@ export const holidayCalendarRouter = createTRPCRouter({
"Holiday calendar",
);
await assertEntryDateAvailable(db, {
await assertHolidayCalendarEntryDateAvailable(db, {
holidayCalendarId: input.holidayCalendarId,
date: input.date,
});
@@ -288,7 +182,7 @@ export const holidayCalendarRouter = createTRPCRouter({
const created = await db.holidayCalendarEntry.create({
data: {
holidayCalendarId: input.holidayCalendarId,
date: clampDate(input.date),
date: clampHolidayCalendarDate(input.date),
name: input.name,
isRecurringAnnual: input.isRecurringAnnual ?? false,
...(input.source ? { source: input.source } : {}),
@@ -317,9 +211,11 @@ export const holidayCalendarRouter = createTRPCRouter({
db.holidayCalendarEntry.findUnique({ where: { id: input.id } }),
"Holiday calendar entry",
);
const nextDate = input.data.date !== undefined ? clampDate(input.data.date) : existing.date;
const nextDate = input.data.date !== undefined
? clampHolidayCalendarDate(input.data.date)
: existing.date;
await assertEntryDateAvailable(db, {
await assertHolidayCalendarEntryDateAvailable(db, {
holidayCalendarId: existing.holidayCalendarId,
date: nextDate,
}, existing.id);