import { and, eq, sql } from "drizzle-orm"; import { calendarHref, calendarObjectHref } from "@/worker/caldav/paths"; import type { CalDavDoDb } from "@/worker/db/cal-dav-do/client"; import { calendarDeadProps, calendars, type CalendarRow } from "@/worker/db/cal-dav-do/schema"; import { calendarResource, calendarSyncToken as syncToken, ensureCalendar, ensureDefaultCalendar, homeResource, listCalendars, objectByName, objectResource, objectResources, } from "@/worker/db/cal-dav-do/repository"; import { calDavError as err, type CalDavError, type CalendarResource, type DescribeInput, type EnsureInitializedInput, type ProppatchInput, } from "@/worker/objects/cal-dav/types"; import { calendarWritePrecondition } from "@/worker/objects/cal-dav/preconditions"; import { describeCollectionDomain, type CollectionDescribeSpec } from "@/worker/objects/common/collection-describe"; import { proppatchCollectionDomain, type CollectionProppatchSpec } from "@/worker/objects/common/collection-proppatch"; export function ensureInitialized(db: CalDavDoDb, input: EnsureInitializedInput): { ok: true } { ensureDefaultCalendar(db, input.subjectId, input.nowMs); return { ok: true }; } export function describe( db: CalDavDoDb, input: DescribeInput, ): { ok: true; resources: CalendarResource[]; syncToken: string } | CalDavError { return describeCollectionDomain(db, { ...input, collectionName: input.calendarName }, calendarDescribeSpec); } export function proppatch(db: CalDavDoDb, input: ProppatchInput): { ok: true } | CalDavError { return proppatchCollectionDomain(db, { ...input, collectionName: input.calendarName }, calendarProppatchSpec); } function targetHref(calendar: CalendarRow, kind: "home" | "calendar" | "object", objectName?: string): string { if (kind === "home") return "/calendars/"; if (kind === "calendar") return calendarHref(calendar.name); return calendarObjectHref(calendar.name, objectName ?? ""); } const calendarDescribeSpec = { collectionKind: "calendar", ensureCollection: ensureCalendar, homeResource, listCollections: listCalendars, collectionResource: calendarResource, objectByName, objectResource, objectResources, syncToken, collectionNotFound: () => err(404, "not_found", "Calendar not found"), objectNotFound: () => err(404, "not_found", "Calendar object not found"), } satisfies CollectionDescribeSpec< CalDavDoDb, CalendarRow, NonNullable>, CalendarResource, CalDavError >; const calendarProppatchSpec = { collectionKind: "calendar", ensureDefaultCollection: ensureDefaultCalendar, ensureCollection: ensureCalendar, objectByName, targetHref, writePrecondition: calendarWritePrecondition, transaction: (db, callback) => db.transaction((tx) => callback(tx)), updateCollectionDisplayName: (tx, collectionId, displayName, nowMs) => { // RFC 4791 4.2: DAV:displayname on a calendar collection is a live // property mapped to the calendar's displayName column. tx.update(calendars) .set({ displayName, modifiedAtMs: nowMs, syncSeq: sql`${calendars.syncSeq} + 1` }) .where(eq(calendars.id, collectionId)) .run(); }, upsertDeadProp: (tx, key, xmlValue) => { tx.insert(calendarDeadProps) .values({ ...key, resourceKind: key.resourceKind as "home" | "calendar" | "object", xmlValue }) .onConflictDoUpdate({ target: [ calendarDeadProps.resourceKind, calendarDeadProps.resourceId, calendarDeadProps.nsUri, calendarDeadProps.localName, ], set: { xmlValue }, }) .run(); }, deleteDeadProp: (tx, key) => { tx.delete(calendarDeadProps) .where( and( eq(calendarDeadProps.resourceKind, key.resourceKind as "home" | "calendar" | "object"), eq(calendarDeadProps.resourceId, key.resourceId), eq(calendarDeadProps.nsUri, key.nsUri), eq(calendarDeadProps.localName, key.localName), ), ) .run(); }, collectionNotFound: () => err(404, "not_found", "Calendar not found"), resourceNotFound: () => err(404, "not_found", "Resource not found"), } satisfies CollectionProppatchSpec< CalDavDoDb, CalDavDoDb, CalendarRow, NonNullable>, CalDavError >;