From bff425c3bd73e7bbc02374c180e03e1f3f92e8ca Mon Sep 17 00:00:00 2001 From: Benno Tielen Date: Wed, 24 Jun 2026 14:24:51 +0200 Subject: [PATCH] feature: cache events --- src/compositions/Blocks/EventsBlock.tsx | 2 +- src/fetch/eventOccurrences.ts | 165 ++++++++++++++++-------- 2 files changed, 112 insertions(+), 55 deletions(-) diff --git a/src/compositions/Blocks/EventsBlock.tsx b/src/compositions/Blocks/EventsBlock.tsx index 7b2f2e7..eff867e 100644 --- a/src/compositions/Blocks/EventsBlock.tsx +++ b/src/compositions/Blocks/EventsBlock.tsx @@ -32,7 +32,7 @@ export async function EventsBlock({ <Events events={transformOccurrences(occurrenceDocs)} - n={itemsPerPage || 6} + n={6} schema={'contrast'} /> </Section> diff --git a/src/fetch/eventOccurrences.ts b/src/fetch/eventOccurrences.ts index 8d31ac4..c536189 100644 --- a/src/fetch/eventOccurrences.ts +++ b/src/fetch/eventOccurrences.ts @@ -1,7 +1,10 @@ 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 @@ -12,10 +15,66 @@ type ListArgs = { 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: [ + { date: { 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>> { @@ -29,41 +88,59 @@ export async function fetchUpcomingOccurrences( toDate, } = args || {} - const query: any = { - and: [ - { date: { greater_than_equal: fromDate.toISOString() } }, - { 'event._status': { equals: 'published' } }, - ], - } - - if (toDate) { - query.and.push({ date: { less_than: toDate.toISOString() } }) - } - 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, + return getUpcomingOccurrences({ + parishId, + groupId, + eventId, limit, page, - }) as Promise<PaginatedDocs<EventOccurrence>> + 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>> { @@ -77,35 +154,15 @@ export async function fetchPastOccurrences( toDate = new Date(), } = args || {} - const query: any = { - and: [ - { date: { less_than: toDate.toISOString() } }, - { 'event._status': { equals: 'published' } }, - ], - } - - if (fromDate) { - query.and.push({ date: { greater_than_equal: fromDate.toISOString() } }) - } - 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, + return getPastOccurrences({ + parishId, + groupId, + eventId, limit, page, - }) as Promise<PaginatedDocs<EventOccurrence>> + fromDate: fromDate?.toISOString(), + toDate: roundDownToTtl(toDate).toISOString(), + }) } /**