207 lines
5.1 KiB
TypeScript
207 lines
5.1 KiB
TypeScript
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<PaginatedDocs<EventOccurrence>> => {
|
|
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<PaginatedDocs<EventOccurrence>>
|
|
},
|
|
['fetchUpcomingOccurrences'],
|
|
{ revalidate: CACHE_TTL },
|
|
)
|
|
|
|
export async function fetchUpcomingOccurrences(
|
|
args?: ListArgs,
|
|
): Promise<PaginatedDocs<EventOccurrence>> {
|
|
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<PaginatedDocs<EventOccurrence>> => {
|
|
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<PaginatedDocs<EventOccurrence>>
|
|
},
|
|
['fetchPastOccurrences'],
|
|
{ revalidate: CACHE_TTL },
|
|
)
|
|
|
|
export async function fetchPastOccurrences(
|
|
args?: ListArgs,
|
|
): Promise<PaginatedDocs<EventOccurrence>> {
|
|
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<PaginatedDocs<EventOccurrence>> {
|
|
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<EventOccurrence | undefined> {
|
|
try {
|
|
const payload = await getPayload({ config })
|
|
return await payload.findByID({
|
|
collection: 'eventOccurrence',
|
|
id,
|
|
depth: 2,
|
|
draft,
|
|
})
|
|
} catch {
|
|
return undefined
|
|
}
|
|
}
|