fix(10): guard shared-calendar designation against non-existent target (CR-01)

PUT /api/admin/calendars/:id/shared cleared the current shared calendar then
set the target in two non-transactional UPDATEs without checking the target
exists — a bad/stale id wiped the family shared lane and still returned ok.
Verify the target inside a transaction; return 404 when absent. Adds a
regression test (RED→GREEN).
This commit is contained in:
Lucas Berger
2026-06-13 15:38:35 -04:00
parent ebde3e1d08
commit 2f347cbd98
2 changed files with 42 additions and 4 deletions
+21 -4
View File
@@ -153,11 +153,28 @@ adminRouter.put('/calendars/:id/shared', async (c) => {
return c.json({ error: 'Invalid calendar id' }, 400);
}
// Step 1: Clear is_shared on any currently-shared calendar
await db.update(calendars).set({ isShared: false }).where(eq(calendars.isShared, true));
// CR-01: verify the target exists and flip the shared lane atomically.
// Without the existence check + transaction, a bad/stale id would clear the
// current shared calendar in step 1 and update 0 rows in step 2 — silently
// leaving the household with NO shared calendar while still returning ok.
const found = await db.transaction(async (tx) => {
const [target] = await tx
.select({ id: calendars.id })
.from(calendars)
.where(eq(calendars.id, targetId))
.limit(1);
if (!target) return false;
// Step 2: Set is_shared on the target calendar
await db.update(calendars).set({ isShared: true }).where(eq(calendars.id, targetId));
// Step 1: Clear is_shared on any currently-shared calendar
await tx.update(calendars).set({ isShared: false }).where(eq(calendars.isShared, true));
// Step 2: Set is_shared on the target calendar (D-06 single-select)
await tx.update(calendars).set({ isShared: true }).where(eq(calendars.id, targetId));
return true;
});
if (!found) {
return c.json({ error: 'Calendar not found' }, 404);
}
return c.json({ ok: true }, 200);
});