feat(plan): assertNotPastDate guard for write paths

This commit is contained in:
BOHA
2026-06-05 12:15:39 +02:00
parent 42f9561337
commit 74113035ae
2 changed files with 52 additions and 0 deletions

View File

@@ -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();
});
});

View File

@@ -245,3 +245,29 @@ export async function listPlanUsers(): Promise<PlanUser[]> {
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;
}