Add sermon retention policy feature

Implemented a configurable retention policy system for sermons with automatic cleanup:
- Added settings table to store retention policy configuration
- Created API endpoints for getting/setting retention policy
- Added Database Settings section to admin page with retention options (forever, 1-10 years)
- Implemented manual cleanup endpoint for on-demand deletion
- Added automated daily cleanup task via Nitro plugin
- Sermons are deleted based on their date field according to the retention policy

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-04 14:07:14 -05:00
parent 587cefec41
commit 66172e0baa
6 changed files with 356 additions and 1 deletions

View File

@@ -0,0 +1,57 @@
import { getSessionUsername } from '~/server/utils/auth'
import { getUserByUsername, getSetting, deleteOldSermons } from '~/server/utils/database'
export default defineEventHandler(async (event) => {
// Check if user is authenticated and is admin
const username = await getSessionUsername(event)
if (!username) {
throw createError({
statusCode: 401,
message: 'Unauthorized'
})
}
const user = getUserByUsername(username)
if (!user || user.is_admin !== 1) {
throw createError({
statusCode: 403,
message: 'Forbidden - Admin access required'
})
}
// Get the retention policy setting
const setting = getSetting('sermon_retention_policy')
const retentionPolicy = setting ? setting.value : 'forever'
// Map retention policy to days
const retentionDaysMap: Record<string, number> = {
'forever': 0, // 0 means no deletion
'1_month': 30,
'3_months': 90,
'6_months': 180,
'1_year': 365,
'3_years': 1095,
'5_years': 1825,
'10_years': 3650
}
const retentionDays = retentionDaysMap[retentionPolicy] || 0
if (retentionDays === 0) {
return {
success: true,
message: 'Retention policy is set to forever, no sermons deleted',
deletedCount: 0
}
}
// Delete old sermons
const result = deleteOldSermons(retentionDays)
return {
success: true,
message: `Deleted sermons older than ${retentionDays} days`,
deletedCount: result.changes,
retentionPolicy
}
})

View File

@@ -0,0 +1,29 @@
import { getSessionUsername } from '~/server/utils/auth'
import { getUserByUsername, getSetting } from '~/server/utils/database'
export default defineEventHandler(async (event) => {
// Check if user is authenticated and is admin
const username = await getSessionUsername(event)
if (!username) {
throw createError({
statusCode: 401,
message: 'Unauthorized'
})
}
const user = getUserByUsername(username)
if (!user || user.is_admin !== 1) {
throw createError({
statusCode: 403,
message: 'Forbidden - Admin access required'
})
}
// Get the retention policy setting, default to 'forever' if not set
const setting = getSetting('sermon_retention_policy')
const retentionPolicy = setting ? setting.value : 'forever'
return {
retentionPolicy
}
})

View File

@@ -0,0 +1,42 @@
import { getSessionUsername } from '~/server/utils/auth'
import { getUserByUsername, setSetting } from '~/server/utils/database'
export default defineEventHandler(async (event) => {
// Check if user is authenticated and is admin
const username = await getSessionUsername(event)
if (!username) {
throw createError({
statusCode: 401,
message: 'Unauthorized'
})
}
const user = getUserByUsername(username)
if (!user || user.is_admin !== 1) {
throw createError({
statusCode: 403,
message: 'Forbidden - Admin access required'
})
}
// Get the retention policy from the request body
const body = await readBody(event)
const { retentionPolicy } = body
// Validate the retention policy value
const validPolicies = ['forever', '1_month', '3_months', '6_months', '1_year', '3_years', '5_years', '10_years']
if (!validPolicies.includes(retentionPolicy)) {
throw createError({
statusCode: 400,
message: 'Invalid retention policy'
})
}
// Save the retention policy setting
setSetting('sermon_retention_policy', retentionPolicy)
return {
success: true,
retentionPolicy
}
})

View File

@@ -0,0 +1,56 @@
import { getSetting, deleteOldSermons } from '../utils/database'
// Map retention policy to days
const retentionDaysMap: Record<string, number> = {
'forever': 0,
'1_month': 30,
'3_months': 90,
'6_months': 180,
'1_year': 365,
'3_years': 1095,
'5_years': 1825,
'10_years': 3650
}
async function runCleanup() {
try {
// Get the retention policy setting
const setting = getSetting('sermon_retention_policy')
const retentionPolicy = setting ? setting.value : 'forever'
const retentionDays = retentionDaysMap[retentionPolicy] || 0
if (retentionDays === 0) {
console.log('[Sermon Cleanup] Retention policy is set to forever, skipping cleanup')
return
}
// Delete old sermons
const result = deleteOldSermons(retentionDays)
if (result.changes > 0) {
console.log(`[Sermon Cleanup] Deleted ${result.changes} sermon(s) older than ${retentionDays} days`)
} else {
console.log(`[Sermon Cleanup] No sermons to delete (policy: ${retentionPolicy})`)
}
} catch (error) {
console.error('[Sermon Cleanup] Error running cleanup:', error)
}
}
export default defineNitroPlugin((nitroApp) => {
console.log('[Sermon Cleanup] Scheduling daily cleanup task')
// Run cleanup once at startup (after a short delay)
setTimeout(async () => {
console.log('[Sermon Cleanup] Running initial cleanup')
await runCleanup()
}, 10000) // 10 seconds after startup
// Schedule cleanup to run daily (every 24 hours)
const dailyInterval = 24 * 60 * 60 * 1000 // 24 hours in milliseconds
setInterval(async () => {
console.log('[Sermon Cleanup] Running scheduled cleanup')
await runCleanup()
}, dailyInterval)
})

View File

@@ -61,6 +61,13 @@ export interface RateLimit {
reset_at: string
}
export interface Setting {
id?: number
key: string
value: string
updated_at?: string
}
export function getDatabase() {
if (!db) {
const dbPath = join(process.cwd(), 'data', 'sermons.db')
@@ -147,6 +154,15 @@ export function getDatabase() {
UNIQUE(identifier, endpoint)
)
`)
db.exec(`
CREATE TABLE IF NOT EXISTS settings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key TEXT UNIQUE NOT NULL,
value TEXT NOT NULL,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`)
// Insert default admin user from environment variables with hashed password
const config = useRuntimeConfig()
@@ -354,3 +370,32 @@ export function resetRateLimit(identifier: string, endpoint: string) {
return db.prepare('DELETE FROM rate_limits WHERE identifier = ? AND endpoint = ?')
.run(identifier, endpoint)
}
// Settings management functions
export function getSetting(key: string) {
const db = getDatabase()
return db.prepare('SELECT * FROM settings WHERE key = ?').get(key) as Setting | undefined
}
export function setSetting(key: string, value: string) {
const db = getDatabase()
const existing = getSetting(key)
if (existing) {
return db.prepare('UPDATE settings SET value = ?, updated_at = CURRENT_TIMESTAMP WHERE key = ?').run(value, key)
} else {
return db.prepare('INSERT INTO settings (key, value) VALUES (?, ?)').run(key, value)
}
}
// Sermon retention policy functions
export function deleteOldSermons(retentionDays: number) {
const db = getDatabase()
// Calculate the cutoff date based on retention days
const cutoffDate = new Date()
cutoffDate.setDate(cutoffDate.getDate() - retentionDays)
const cutoffDateStr = cutoffDate.toISOString().split('T')[0] // Format as YYYY-MM-DD
// Delete sermons older than the cutoff date
return db.prepare('DELETE FROM sermons WHERE date < ?').run(cutoffDateStr)
}