// @ts-nocheck /* eslint-disable @typescript-eslint/no-explicit-any */ import { prisma } from './prisma'; import { getCalendarEvents, CalendarEvent, CalendarConnection } from './calendar-events'; const STALE_THRESHOLD_MS = 15 * 60 * 1000; // 15 minutes — avoid iCloud rate limiting /** * Get the Monday (start of ISO week) for a given date. */ function getWeekStart(date: Date): Date { const d = new Date(date); d.setUTCHours(0, 0, 0, 0); const day = d.getUTCDay(); // 0=Sun const diff = day === 0 ? -6 : 1 - day; d.setUTCDate(d.getUTCDate() + diff); return d; } /** * Check if the cache for a given connection + time window is stale. */ export async function isCacheStale( connectionId: string, timeMin: Date, ): Promise { const weekStart = getWeekStart(timeMin); const newest = await prisma.cachedCalendarEvent.findFirst({ where: { connectionId, weekStart }, orderBy: { syncedAt: 'desc' }, select: { syncedAt: true }, }); if (!newest) return true; return Date.now() - newest.syncedAt.getTime() > STALE_THRESHOLD_MS; } /** * Read cached events for a user within a date range. * Returns them in the flat shape the frontend expects. */ export async function readCachedEvents( userId: string, timeMin: Date, timeMax: Date, ) { const minDateStr = timeMin.toISOString().slice(0, 10); const maxDateStr = timeMax.toISOString().slice(0, 10); const rows = await prisma.cachedCalendarEvent.findMany({ where: { userId, OR: [ // Timed events within range { startDateTime: { gte: timeMin, lte: timeMax }, }, // All-day events within range { startDate: { gte: minDateStr, lte: maxDateStr }, }, ], }, orderBy: { startDateTime: 'asc' }, }); return rows.map(row => ({ id: row.externalId, title: row.title, description: row.description, location: row.location, url: row.url, recurringEventId: row.recurringEventId, isRecurring: row.isRecurring, startTime: row.startDateTime?.toISOString() ?? row.startDate ?? '', endTime: row.endDateTime?.toISOString() ?? row.endDate ?? '', source: row.provider as 'google' | 'apple' | 'outlook' | 'synology', calendarId: row.calendarId, calendarTitle: row.calendarTitle, calendarColor: row.calendarColor, })); } export interface RefreshableConnection extends CalendarConnection { dbId: string; userId: string; } /** * Fetch live events from provider, then atomic swap into cache. */ export async function refreshConnectionCache( connection: RefreshableConnection, timeMin: Date, timeMax: Date, ): Promise { const weekStart = getWeekStart(timeMin); let liveEvents: CalendarEvent[] = []; try { liveEvents = await getCalendarEvents( [connection], timeMin.toISOString(), timeMax.toISOString(), ); } catch (err) { console.error(`[CACHE] Live fetch failed for connection ${connection.dbId}:`, err); return 0; } const now = new Date(); const rows = liveEvents.map(ev => ({ userId: connection.userId, externalId: ev.id, connectionId: connection.dbId, provider: connection.provider, calendarId: ev.calendarId, calendarTitle: ev.calendarTitle, calendarColor: ev.backgroundColor ?? null, title: ev.title, description: ev.description ?? null, location: ev.location ?? null, url: ev.url ?? null, recurringEventId: ev.recurringEventId ?? null, isRecurring: ev.isRecurring ?? false, startDateTime: ev.start.dateTime ? new Date(ev.start.dateTime) : null, startDate: ev.start.date ?? null, endDateTime: ev.end.dateTime ? new Date(ev.end.dateTime) : null, endDate: ev.end.date ?? null, reminders: ev.reminders ? JSON.parse(JSON.stringify(ev.reminders)) : null, weekStart, syncedAt: now, })); // Atomic swap: delete old events for this connection+week, insert fresh ones await prisma.$transaction([ prisma.cachedCalendarEvent.deleteMany({ where: { connectionId: connection.dbId, weekStart }, }), prisma.cachedCalendarEvent.createMany({ data: rows, skipDuplicates: true, }), ]); console.log(`[CACHE] Refreshed ${rows.length} events for ${connection.provider} connection, week ${weekStart.toISOString().slice(0, 10)}`); return rows.length; } /** * Upsert a single event into cache (after create/update mutations). */ export async function upsertCachedEvent( userId: string, connectionId: string, provider: string, event: CalendarEvent, ): Promise { const weekStart = getWeekStart( new Date(event.start.dateTime ?? event.start.date ?? Date.now()), ); await prisma.cachedCalendarEvent.upsert({ where: { userId_externalId_provider: { userId, externalId: event.id, provider }, }, create: { userId, externalId: event.id, connectionId, provider, calendarId: event.calendarId, calendarTitle: event.calendarTitle, calendarColor: event.backgroundColor ?? null, title: event.title, description: event.description ?? null, location: event.location ?? null, url: event.url ?? null, recurringEventId: event.recurringEventId ?? null, isRecurring: event.isRecurring ?? false, startDateTime: event.start.dateTime ? new Date(event.start.dateTime) : null, startDate: event.start.date ?? null, endDateTime: event.end.dateTime ? new Date(event.end.dateTime) : null, endDate: event.end.date ?? null, reminders: event.reminders ? JSON.parse(JSON.stringify(event.reminders)) : null, weekStart, syncedAt: new Date(), }, update: { calendarTitle: event.calendarTitle, calendarColor: event.backgroundColor ?? null, title: event.title, description: event.description ?? null, location: event.location ?? null, url: event.url ?? null, recurringEventId: event.recurringEventId ?? null, isRecurring: event.isRecurring ?? false, startDateTime: event.start.dateTime ? new Date(event.start.dateTime) : null, startDate: event.start.date ?? null, endDateTime: event.end.dateTime ? new Date(event.end.dateTime) : null, endDate: event.end.date ?? null, reminders: event.reminders ? JSON.parse(JSON.stringify(event.reminders)) : null, weekStart, syncedAt: new Date(), }, }); } /** * Delete a single cached event (after delete mutations). */ export async function deleteCachedEvent( userId: string, externalId: string, provider: string, ): Promise { await prisma.cachedCalendarEvent.deleteMany({ where: { userId, externalId, provider }, }); }