From 74113035aed4e1a687b2ff936343ed9881e0b9e6 Mon Sep 17 00:00:00 2001 From: BOHA Date: Fri, 5 Jun 2026 12:15:39 +0200 Subject: [PATCH] feat(plan): assertNotPastDate guard for write paths --- src/__tests__/plan.test.ts | 26 ++++++++++++++++++++++++++ src/services/plan.service.ts | 26 ++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/src/__tests__/plan.test.ts b/src/__tests__/plan.test.ts index 0135a83..364e7e8 100644 --- a/src/__tests__/plan.test.ts +++ b/src/__tests__/plan.test.ts @@ -4,6 +4,7 @@ import { resolveCell, resolveGrid, listPlanUsers, + assertNotPastDate, } from "../services/plan.service"; // --------------------------------------------------------------------------- @@ -187,3 +188,28 @@ describe("plan.service.listPlanUsers", () => { expect(found?.has_attendance_record).toBe(true); }); }); + +// --------------------------------------------------------------------------- +// assertNotPastDate +// --------------------------------------------------------------------------- + +describe("plan.service.assertNotPastDate", () => { + it("returns error for a past date", () => { + const result = assertNotPastDate("2000-01-01", false); + expect(result).toEqual({ + error: + "Nelze upravovat plán pro datum v minulosti. Pro nouzovou opravu použijte ?force=1.", + status: 403, + }); + }); + + it("returns null for a future date", () => { + const result = assertNotPastDate("2099-01-01", false); + expect(result).toBeNull(); + }); + + it("returns null for a past date with force=true", () => { + const result = assertNotPastDate("2000-01-01", true); + expect(result).toBeNull(); + }); +}); diff --git a/src/services/plan.service.ts b/src/services/plan.service.ts index 0cf1fad..c2e4642 100644 --- a/src/services/plan.service.ts +++ b/src/services/plan.service.ts @@ -245,3 +245,29 @@ export async function listPlanUsers(): Promise { has_attendance_record: true, })); } + +/** + * Guard helper: returns { error, status } if `dateStr` is in the past + * and `force` is not set. Returns null if the date is editable. + * + * The comparison uses ISO date string equality. For past-date guard purposes + * any date strictly before today (in the local timezone per the project's + * TZ=Europe/Prague setting) is locked. + */ +export function assertNotPastDate( + dateStr: string, + force: boolean, +): { error: string; status: number } | null { + if (force) return null; + + const today = new Date(); + const todayStr = today.toISOString().slice(0, 10); + if (dateStr < todayStr) { + return { + error: + "Nelze upravovat plán pro datum v minulosti. Pro nouzovou opravu použijte ?force=1.", + status: 403, + }; + } + return null; +}