fix: mode
This commit is contained in:
parent
e96d04c317
commit
a0821f1ae7
2 changed files with 64 additions and 36 deletions
|
|
@ -56,20 +56,26 @@ type ChurchLike = {
|
|||
recurringSchedule?: unknown[] | null
|
||||
}
|
||||
|
||||
type RegenerateMode = 'replace' | 'append'
|
||||
|
||||
/**
|
||||
* Materializes a single Church's `recurringSchedule` into real `Worship`
|
||||
* documents for the rolling [last Monday, horizon] window.
|
||||
*
|
||||
* Wipe-and-regenerate: every future auto-generated (`generated: true`)
|
||||
* Worship doc for this church is deleted and rebuilt from the current
|
||||
* schedule, so a changed/cleared schedule never leaves stale or double
|
||||
* masses behind. Hand-created Worship docs are never touched. Manual
|
||||
* cancellations on generated docs are snapshotted by date and re-applied
|
||||
* so they survive the regen (mirrors Events/eventOccurrence).
|
||||
* Two modes:
|
||||
* - `replace` (default): wipe every future auto-generated (`generated: true`)
|
||||
* Worship doc for this church and rebuild from the current schedule. Used
|
||||
* by the Church `afterChange` hook, where the schedule may have *changed* —
|
||||
* wiping is what stops a changed/cleared schedule from leaving stale or
|
||||
* double masses behind. Manual cancellations on generated docs are
|
||||
* snapshotted by date and re-applied so they survive the regen.
|
||||
* - `append`: never delete; only create occurrences whose slot doesn't yet
|
||||
* exist. Used by the periodic backfill job, where the schedule hasn't
|
||||
* changed and we only want to extend the rolling window forward — no point
|
||||
* churning (and re-id'ing) docs that are already correct.
|
||||
*
|
||||
* Exposed both as a direct function (called from the Church `afterChange`
|
||||
* hook so edits reflect immediately) and via the Jobs Queue task below
|
||||
* (weekly cron backfill across all churches).
|
||||
* Hand-created Worship docs (generated !== true) are never deleted in either
|
||||
* mode, and never duplicated in `append` mode.
|
||||
*/
|
||||
export const regenerateMassesForChurch = async ({
|
||||
church,
|
||||
|
|
@ -77,12 +83,14 @@ export const regenerateMassesForChurch = async ({
|
|||
req,
|
||||
weeksAhead = DEFAULT_WEEKS_AHEAD,
|
||||
now = new Date(),
|
||||
mode = 'replace',
|
||||
}: {
|
||||
church: ChurchLike
|
||||
payload: Payload
|
||||
req?: PayloadRequest
|
||||
weeksAhead?: number
|
||||
now?: Date
|
||||
mode?: RegenerateMode
|
||||
}): Promise<GenerateRecurringMassesOutput> => {
|
||||
const horizon = new Date(now.getTime() + weeksAhead * MS_PER_WEEK)
|
||||
|
||||
|
|
@ -92,16 +100,16 @@ export const regenerateMassesForChurch = async ({
|
|||
|
||||
let created = 0
|
||||
let skipped = 0
|
||||
let deleted = 0
|
||||
|
||||
// Snapshot manual cancellations on generated docs so they survive the
|
||||
// wipe/regen. Matched by ISO timestamp because regen rebuilds the same
|
||||
// (church, date) slots from the schedule.
|
||||
// Existing Worship docs in the window. We use these to (replace) snapshot
|
||||
// cancellations so they survive the wipe, and (append) dedup so we don't
|
||||
// recreate slots that already exist.
|
||||
const existing = await payload.find({
|
||||
collection: 'worship',
|
||||
where: {
|
||||
and: [
|
||||
{ location: { equals: church.id } },
|
||||
{ generated: { equals: true } },
|
||||
{ date: { greater_than_equal: windowStart.toISOString() } },
|
||||
],
|
||||
},
|
||||
|
|
@ -110,16 +118,20 @@ export const regenerateMassesForChurch = async ({
|
|||
pagination: false,
|
||||
req,
|
||||
})
|
||||
const existingSlots = new Set(
|
||||
existing.docs
|
||||
.filter((doc) => typeof doc.date === 'string')
|
||||
.map((doc) => new Date(doc.date as string).toISOString()),
|
||||
)
|
||||
const cancelledDates = new Set(
|
||||
existing.docs
|
||||
.filter((doc) => doc.cancelled === true && typeof doc.date === 'string')
|
||||
.map((doc) => new Date(doc.date as string).toISOString()),
|
||||
)
|
||||
|
||||
if (mode === 'replace') {
|
||||
// Wipe future auto-generated docs, then regenerate from the current
|
||||
// schedule. This is what keeps a changed schedule from leaving stale
|
||||
// (double) masses behind. Hand-created Worship docs (generated !== true)
|
||||
// are never touched.
|
||||
// schedule. Hand-created docs (generated !== true) are untouched.
|
||||
const wipe = await payload.delete({
|
||||
collection: 'worship',
|
||||
where: {
|
||||
|
|
@ -131,7 +143,8 @@ export const regenerateMassesForChurch = async ({
|
|||
},
|
||||
req,
|
||||
})
|
||||
const deleted = wipe.docs.length
|
||||
deleted = wipe.docs.length
|
||||
}
|
||||
|
||||
const schedule = church.recurringSchedule
|
||||
if (!Array.isArray(schedule) || schedule.length === 0) {
|
||||
|
|
@ -177,16 +190,25 @@ export const regenerateMassesForChurch = async ({
|
|||
continue
|
||||
}
|
||||
|
||||
// `generated: true` marks this as auto-created so a later run (or the
|
||||
// wipe above) can target it without touching manual rows. Re-apply any
|
||||
// cancellation the editor had set on this slot before the wipe.
|
||||
const iso = date.toISOString()
|
||||
|
||||
// In append mode the future docs were never wiped, so leave any slot
|
||||
// that already has a Worship doc (generated or hand-created) alone.
|
||||
if (mode === 'append' && existingSlots.has(iso)) {
|
||||
skipped += 1
|
||||
continue
|
||||
}
|
||||
|
||||
// `generated: true` marks this as auto-created so a later run (or a
|
||||
// replace wipe) can target it without touching manual rows. Re-apply
|
||||
// any cancellation the editor had set on this slot before the wipe.
|
||||
await payload.create({
|
||||
collection: 'worship',
|
||||
data: {
|
||||
date: date.toISOString(),
|
||||
date: iso,
|
||||
location: church.id,
|
||||
type: entry.type,
|
||||
cancelled: cancelledDates.has(date.toISOString()),
|
||||
cancelled: cancelledDates.has(iso),
|
||||
title: entry.defaultTitle || undefined,
|
||||
celebrant: entry.defaultCelebrant || undefined,
|
||||
description: entry.defaultDescription || undefined,
|
||||
|
|
@ -195,6 +217,8 @@ export const regenerateMassesForChurch = async ({
|
|||
req,
|
||||
})
|
||||
|
||||
// Track within-run so two schedule entries can't double-book a slot.
|
||||
existingSlots.add(iso)
|
||||
created += 1
|
||||
}
|
||||
}
|
||||
|
|
@ -265,6 +289,10 @@ export const generateRecurringMassesTask: TaskConfig<{
|
|||
req,
|
||||
weeksAhead,
|
||||
now,
|
||||
// Periodic backfill: only extend the window forward. The schedule
|
||||
// hasn't changed here, so don't wipe/recreate docs that are already
|
||||
// correct (that only happens via the Church hook on an actual edit).
|
||||
mode: 'append',
|
||||
})
|
||||
created += result.created
|
||||
deleted += result.deleted
|
||||
|
|
|
|||
|
|
@ -113,8 +113,8 @@ export default buildConfig({
|
|||
tasks: [generateRecurringMassesTask, generateEventOccurrencesTask],
|
||||
autoRun: [
|
||||
{
|
||||
// every 15 minutes (6-field cron, seconds first)
|
||||
cron: '0 */15 * * * *',
|
||||
// once a day at 03:00 (6-field cron, seconds first)
|
||||
cron: '0 0 3 * * *',
|
||||
queue: 'default',
|
||||
limit: 10,
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue