fix(02): include all-day recurring masters in events route pre-filter

- Old filter: hasRrule=1 AND dtstartUtc < windowEnd
  All-day recurring masters have dtstartUtc=NULL so the comparison evaluates
  to NULL/false — 11 such rows in live cache were never returned
- New filter: hasRrule=1 AND (dtstartUtc < windowEnd OR dtstartDate < end)
  The OR covers all-day masters whose only date column is dtstartDate (DATE)
- expandOccurrences already does precise per-occurrence window checks, so
  over-selecting a master on the DATE path is safe
- Extend events.test.ts: assert timed recurring master (dtstart 2024) returns
  occurrences in 2026 window; assert all-day recurring master (dtstartDate 2024,
  dtstartUtc NULL) returns its 2026-06-15 occurrence
This commit is contained in:
Lucas Berger
2026-06-05 14:50:27 -04:00
parent f70496871a
commit 1f0b9546a8
2 changed files with 106 additions and 18 deletions
+9 -2
View File
@@ -88,10 +88,17 @@ eventsRouter.get('/', zValidator('query', eventsQuerySchema), async (c) => {
.innerJoin(users, eq(calendars.userId, users.id))
.where(
or(
// Recurring masters: may have occurrences inside the window even if dtstartUtc is old
// Recurring masters: may have occurrences inside the window even if dtstartUtc is old.
// Two sub-cases:
// (a) Timed recurring masters: dtstartUtc < windowEnd
// (b) All-day recurring masters: dtstartUtc is NULL (DATE-only), use dtstartDate < end
// A NULL dtstartUtc causes the timed comparison to be NULL/false, so (b) carries it.
and(
sql`${calendarEvents.hasRrule} = 1`,
sql`${calendarEvents.dtstartUtc} < ${windowEndDate}`,
or(
sql`${calendarEvents.dtstartUtc} < ${windowEndDate}`,
sql`${calendarEvents.dtstartDate} < ${end}`,
),
),
// Non-recurring timed events: dtstartUtc falls in [windowStart, windowEnd)
and(