fix(03): correct event-write timezone + per-user calendar identity (Gate 2 Part D)
BUG A — timed events written 4h off: EventForm sent a naive local wall-clock string with no offset; the UTC API container parsed it via new Date() as UTC, so 09:00 America/Toronto serialized to DTSTART:...090000Z. Fix: new apps/pwa/src/lib/eventDateTime.ts serializes timed events to an unambiguous UTC instant in the browser (where the operator's zone is known); all-day stays a DATE string. No backend change. BUG B — created events attached to the wrong user's calendar + duplicate calendar rows per poll: calendars had no unique key on url, and poller/sync matched calendars by url alone — so under the shared single Fastmail account (D-16) one member's collection resolved to the other member's row. Fix: composite unique(user_id, url); scope poller lookup + sync select to (userId, url); hand migration 0001 (dedup + add key), applied to the live DB. Regression tests fail against the buggy url-only predicate. API 98/98, PWA 140/140, tsc clean both packages.
This commit is contained in:
@@ -18,7 +18,7 @@
|
||||
import type { DAVCalendar } from 'tsdav'
|
||||
import type { FastmailClient } from './client.js'
|
||||
import ICAL from 'ical.js'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import { db } from '../db/client.js'
|
||||
import { calendars, calendarEvents } from '../db/schema.js'
|
||||
|
||||
@@ -56,10 +56,17 @@ export async function syncCalendar(
|
||||
})
|
||||
|
||||
// 2. Select the calendar row to get its DB id (insertId is unreliable on ON DUPLICATE KEY UPDATE).
|
||||
const [cal] = await db.select().from(calendars).where(eq(calendars.url, davCal.url)).limit(1)
|
||||
// BUG B: scope by (userId, url) — the same collection URL exists for both members
|
||||
// (shared Fastmail account, D-16). A url-only lookup returned the OTHER member's
|
||||
// row (lowest id), so events were cached under the wrong calendarId.
|
||||
const [cal] = await db
|
||||
.select()
|
||||
.from(calendars)
|
||||
.where(and(eq(calendars.userId, userId), eq(calendars.url, davCal.url)))
|
||||
.limit(1)
|
||||
if (!cal) {
|
||||
// Should never happen — we just upserted it
|
||||
throw new Error(`syncCalendar: could not find calendar row for url=${davCal.url}`)
|
||||
throw new Error(`syncCalendar: could not find calendar row for userId=${userId} url=${davCal.url}`)
|
||||
}
|
||||
|
||||
// 3. Fetch all calendar objects (REPORT calendar-query).
|
||||
|
||||
Reference in New Issue
Block a user