From 0ff37e89996b57302c90d3226446fb8566e1a3a8 Mon Sep 17 00:00:00 2001 From: Joshua Ryder Date: Fri, 7 Nov 2025 09:53:17 -0500 Subject: [PATCH] feat: Implement automatic sermon archiving based on dates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add intelligent auto-archiving system that automatically moves sermons to the "Previous Sermons" list when they are 1 day past their most recent date. Features: - Auto-archive logic that checks both primary and additional sermon dates - Finds the most recent date across all dates for a sermon - Archives sermon 1 day after the most recent date has passed - Manual trigger via "Run Auto-Archive Now" button on admin page - Automatic daily execution via scheduled cleanup task - Clear admin UI with explanatory text and status messages - Manual archive/unarchive functionality preserved Implementation: - Added getMostRecentSermonDate() helper to find latest date from primary and additional dates - Added autoArchiveOldSermons() function to database utils - Created /api/sermons/auto-archive endpoint for manual triggering - Integrated into daily cleanup plugin schedule - Updated admin UI with auto-archive button and status indicators - Added unarchiveSermon() function for completeness The system runs automatically every 24 hours and can be manually triggered by admins. Sermons are moved to the previous sermons dropdown on the home page exactly 1 day after their final presentation date, ensuring the main page always shows current and upcoming content while preserving access to past sermons. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- pages/admin.vue | 72 ++++++++++++++++++++----- server/api/sermons/auto-archive.post.ts | 41 ++++++++++++++ server/plugins/sermon-cleanup.ts | 17 +++++- server/utils/database.ts | 56 +++++++++++++++++++ 4 files changed, 173 insertions(+), 13 deletions(-) create mode 100644 server/api/sermons/auto-archive.post.ts diff --git a/pages/admin.vue b/pages/admin.vue index a8ae4e9..e152a99 100644 --- a/pages/admin.vue +++ b/pages/admin.vue @@ -378,18 +378,42 @@ {{ retentionPolicySuccess }} -
- -

- Manually trigger the cleanup process to delete sermons according to the retention policy. - This will also run automatically on a daily schedule. -

+
+
+ +

+ Automatically archive sermons that are 1 day past their most recent date. + This moves them to the "Previous Sermons" list on the home page. + This also runs automatically on a daily schedule. +

+
+ +
+ +

+ Manually trigger the cleanup process to delete sermons according to the retention policy. + This will also run automatically on a daily schedule. +

+
+
+ +
+ {{ autoArchiveError }} +
+
+ {{ autoArchiveSuccess }}
@@ -450,6 +474,9 @@ const retentionPolicy = ref('forever') const savingRetentionPolicy = ref(false) const retentionPolicyError = ref('') const retentionPolicySuccess = ref('') +const autoArchiving = ref(false) +const autoArchiveError = ref('') +const autoArchiveSuccess = ref('') const cleaningUp = ref(false) const cleanupError = ref('') const cleanupSuccess = ref('') @@ -756,6 +783,27 @@ async function handleRetentionPolicyChange() { } } +async function handleAutoArchive() { + autoArchiveError.value = '' + autoArchiveSuccess.value = '' + autoArchiving.value = true + + try { + const result = await $fetch('/api/sermons/auto-archive', { + method: 'POST' + }) + + autoArchiveSuccess.value = result.message + + // Refresh sermon list after auto-archive + await refreshSermons() + } catch (e: any) { + autoArchiveError.value = e.data?.message || 'Failed to run auto-archive' + } finally { + autoArchiving.value = false + } +} + async function handleManualCleanup() { if (!confirm('Are you sure you want to run the cleanup process?\n\nThis will permanently delete all sermons older than the retention policy. This action cannot be undone.')) { return diff --git a/server/api/sermons/auto-archive.post.ts b/server/api/sermons/auto-archive.post.ts new file mode 100644 index 0000000..42f1980 --- /dev/null +++ b/server/api/sermons/auto-archive.post.ts @@ -0,0 +1,41 @@ +import { getSessionUsername } from '~/server/utils/auth' +import { autoArchiveOldSermons, getUserByUsername } from '~/server/utils/database' + +export default defineEventHandler(async (event) => { + // Check authentication + const username = await getSessionUsername(event) + + if (!username) { + throw createError({ + statusCode: 401, + message: 'Unauthorized' + }) + } + + // Check admin role + const user = getUserByUsername(username) + + if (!user || user.is_admin !== 1) { + throw createError({ + statusCode: 403, + message: 'Forbidden - Admin access required' + }) + } + + try { + const result = autoArchiveOldSermons() + + return { + success: true, + message: result.archivedCount > 0 + ? `Successfully auto-archived ${result.archivedCount} sermon(s)` + : 'No sermons needed to be archived', + archivedCount: result.archivedCount + } + } catch (error: any) { + throw createError({ + statusCode: 500, + message: error.message || 'Failed to auto-archive sermons' + }) + } +}) diff --git a/server/plugins/sermon-cleanup.ts b/server/plugins/sermon-cleanup.ts index 72aa638..e1ea12e 100644 --- a/server/plugins/sermon-cleanup.ts +++ b/server/plugins/sermon-cleanup.ts @@ -1,4 +1,4 @@ -import { getSetting, deleteOldSermons, deleteExpiredSessions, getDatabase } from '../utils/database' +import { getSetting, deleteOldSermons, deleteExpiredSessions, autoArchiveOldSermons, getDatabase } from '../utils/database' // Map retention policy to days const retentionDaysMap: Record = { @@ -12,6 +12,20 @@ const retentionDaysMap: Record = { '10_years': 3650 } +async function runSermonAutoArchive() { + try { + const result = autoArchiveOldSermons() + + if (result.archivedCount > 0) { + console.log(`[Sermon Auto-Archive] Archived ${result.archivedCount} sermon(s) that passed their most recent date`) + } else { + console.log(`[Sermon Auto-Archive] No sermons needed archiving`) + } + } catch (error) { + console.error('[Sermon Auto-Archive] Error running auto-archive:', error) + } +} + async function runSermonCleanup() { try { // Get the retention policy setting @@ -75,6 +89,7 @@ async function runPasswordResetCleanup() { async function runAllCleanupTasks() { console.log('[Cleanup] Starting scheduled cleanup tasks') + await runSermonAutoArchive() await runSermonCleanup() await runSessionCleanup() await runRateLimitCleanup() diff --git a/server/utils/database.ts b/server/utils/database.ts index 65b0eb6..8f508b0 100644 --- a/server/utils/database.ts +++ b/server/utils/database.ts @@ -372,6 +372,62 @@ export function archiveSermon(id: number) { return db.prepare('UPDATE sermons SET archived = 1 WHERE id = ?').run(id) } +export function unarchiveSermon(id: number) { + const db = getDatabase() + return db.prepare('UPDATE sermons SET archived = 0 WHERE id = ?').run(id) +} + +// Get the most recent date from a sermon (checking both primary date and additional dates) +function getMostRecentSermonDate(sermon: any): Date { + const dates: string[] = [sermon.date] + + // Add additional dates if they exist + if (sermon.dates) { + try { + const additionalDates = JSON.parse(sermon.dates) + dates.push(...additionalDates) + } catch { + // If parsing fails, just use primary date + } + } + + // Convert all dates to Date objects and find the most recent + const dateTimes = dates.map(dateStr => { + const date = new Date(dateStr + 'T00:00:00') + return date.getTime() + }) + + return new Date(Math.max(...dateTimes)) +} + +export function autoArchiveOldSermons(): { archivedCount: number } { + const db = getDatabase() + + // Get all non-archived sermons + const sermons = db.prepare('SELECT * FROM sermons WHERE archived = 0').all() as any[] + + const now = new Date() + now.setHours(0, 0, 0, 0) // Start of today + + let archivedCount = 0 + + for (const sermon of sermons) { + const mostRecentDate = getMostRecentSermonDate(sermon) + + // Calculate days difference + const oneDayAfterSermon = new Date(mostRecentDate) + oneDayAfterSermon.setDate(oneDayAfterSermon.getDate() + 1) + + // If it's been at least 1 day since the most recent sermon date, archive it + if (now >= oneDayAfterSermon) { + archiveSermon(sermon.id!) + archivedCount++ + } + } + + return { archivedCount } +} + export function getSermonBySlug(slug: string) { const db = getDatabase() return db.prepare('SELECT * FROM sermons WHERE slug = ?').get(slug) as Sermon | undefined