import { getPayload, PaginatedDocs } from 'payload' import { unstable_cache } from 'next/cache' import config from '@/payload.config' import { EventOccurrence } from '@/payload-types' const CACHE_TTL = 300 // 5 minutes type ListArgs = { parishId?: string groupId?: string eventId?: string limit?: number page?: number fromDate?: Date toDate?: Date } // Resolved args use ISO strings so they serialize into a stable cache key. type ResolvedArgs = { parishId?: string groupId?: string eventId?: string limit: number page: number fromDate?: string toDate?: string } // Round down to the cache window so a default `new Date()` (millisecond // precision) produces a stable key for the duration of the TTL. function roundDownToTtl(date: Date): Date { const ms = CACHE_TTL * 1000 return new Date(Math.floor(date.getTime() / ms) * ms) } /** * Fetch upcoming event occurrences, joined to their parent event. Always * filters to occurrences whose parent event is published. */ const getUpcomingOccurrences = unstable_cache( async (args: ResolvedArgs): Promise> => { const { parishId, groupId, eventId, limit, page, fromDate, toDate } = args const query: any = { and: [ // Overlap semantics: an occurrence stays "upcoming" while it is still // running, not only before it starts. endDateTime is null for // occurrences without an end; those fall back to the start date. { or: [ { date: { greater_than_equal: fromDate } }, { endDateTime: { greater_than_equal: fromDate } }, ], }, { 'event._status': { equals: 'published' } }, ], } if (toDate) { query.and.push({ date: { less_than: toDate } }) } if (eventId) { query.and.push({ event: { equals: eventId } }) } if (parishId) { query.and.push({ 'event.parish': { equals: parishId } }) } if (groupId) { query.and.push({ 'event.group': { equals: groupId } }) } const payload = await getPayload({ config }) return payload.find({ collection: 'eventOccurrence', sort: 'date', where: query, depth: 2, limit, page, }) as Promise> }, ['fetchUpcomingOccurrences'], { revalidate: CACHE_TTL }, ) export async function fetchUpcomingOccurrences( args?: ListArgs, ): Promise> { const { parishId, groupId, eventId, limit = 30, page = 0, fromDate = new Date(), toDate, } = args || {} return getUpcomingOccurrences({ parishId, groupId, eventId, limit, page, fromDate: roundDownToTtl(fromDate).toISOString(), toDate: toDate?.toISOString(), }) } /** * Fetch past event occurrences (most recent first), joined to their parent * event. Always filters to occurrences whose parent event is published. */ const getPastOccurrences = unstable_cache( async (args: ResolvedArgs): Promise> => { const { parishId, groupId, eventId, limit, page, fromDate, toDate } = args const query: any = { and: [ { date: { less_than: toDate } }, { 'event._status': { equals: 'published' } }, ], } if (fromDate) { query.and.push({ date: { greater_than_equal: fromDate } }) } if (eventId) { query.and.push({ event: { equals: eventId } }) } if (parishId) { query.and.push({ 'event.parish': { equals: parishId } }) } if (groupId) { query.and.push({ 'event.group': { equals: groupId } }) } const payload = await getPayload({ config }) return payload.find({ collection: 'eventOccurrence', sort: '-date', where: query, depth: 2, limit, page, }) as Promise> }, ['fetchPastOccurrences'], { revalidate: CACHE_TTL }, ) export async function fetchPastOccurrences( args?: ListArgs, ): Promise> { const { parishId, groupId, eventId, limit = 30, page = 0, fromDate, toDate = new Date(), } = args || {} return getPastOccurrences({ parishId, groupId, eventId, limit, page, fromDate: fromDate?.toISOString(), toDate: roundDownToTtl(toDate).toISOString(), }) } /** * Fetch upcoming occurrences, falling back to past occurrences if there * are no upcoming ones. */ export async function fetchUpcomingOrPastOccurrences( args?: ListArgs, ): Promise> { const upcoming = await fetchUpcomingOccurrences(args) if (upcoming.docs.length > 0) return upcoming return fetchPastOccurrences(args) } /** * Fetch a single occurrence by id with its parent event populated. * Returns undefined if not found or if the id is malformed. */ export async function fetchOccurrenceById( id: string, draft: boolean = false, ): Promise { try { const payload = await getPayload({ config }) return await payload.findByID({ collection: 'eventOccurrence', id, depth: 2, draft, }) } catch { return undefined } }