Files
nlcc-itinerary/server/plugins/sermon-cleanup.ts
Joshua Ryder 287284c2fe perf: Comprehensive efficiency optimizations
Implemented all 5 critical efficiency improvements to optimize
performance, reduce resource usage, and improve scalability.

## 1. Database Indexes
- Added indexes on sermon_notes foreign keys (user_id, sermon_id)
- Added composite index on sermons (archived, date DESC)
- Added indexes on frequently queried columns across all tables
- Impact: Faster queries as data grows, better JOIN performance

## 2. Eliminated N+1 Query Pattern
- Reduced 2 API calls to 1 on home page load
- Changed from separate active/archived fetches to single call
- Filter archived sermons client-side using computed properties
- Impact: 50% reduction in HTTP requests per page load

## 3. Scheduled Database Cleanup
- Extended existing plugin to clean expired sessions hourly
- Added cleanup for expired rate limits every hour
- Added cleanup for expired password reset codes every hour
- Sermon cleanup continues to run daily based on retention policy
- Impact: Prevents database table growth, better performance

## 4. Multi-stage Docker Build
- Implemented 3-stage build: deps -> builder -> runtime
- Separated build-time and runtime dependencies
- Added non-root user (nuxt:nodejs) for security
- Integrated dumb-init for proper signal handling
- Added health check endpoint at /api/health
- Impact: Smaller image size, faster deployments, better security

## 5. HTTP Caching
- Static assets: 1 year cache (immutable)
- Logos/images: 1 year cache (immutable)
- API routes: No cache (always fresh)
- HTML pages: 10 minute cache with revalidation
- Impact: Reduced bandwidth, faster page loads, less server load

All optimizations follow best practices and maintain backward
compatibility with existing functionality.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-06 08:01:45 -05:00

110 lines
3.5 KiB
TypeScript

import { getSetting, deleteOldSermons, deleteExpiredSessions, getDatabase } 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 runSermonCleanup() {
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 sermon cleanup:', error)
}
}
async function runSessionCleanup() {
try {
const result = deleteExpiredSessions()
if (result.changes > 0) {
console.log(`[Session Cleanup] Deleted ${result.changes} expired session(s)`)
}
} catch (error) {
console.error('[Session Cleanup] Error cleaning expired sessions:', error)
}
}
async function runRateLimitCleanup() {
try {
const db = getDatabase()
const result = db.prepare("DELETE FROM rate_limits WHERE reset_at <= datetime('now')").run()
if (result.changes > 0) {
console.log(`[Rate Limit Cleanup] Deleted ${result.changes} expired rate limit(s)`)
}
} catch (error) {
console.error('[Rate Limit Cleanup] Error cleaning expired rate limits:', error)
}
}
async function runPasswordResetCleanup() {
try {
const db = getDatabase()
const result = db.prepare("DELETE FROM password_reset_codes WHERE expires_at <= datetime('now')").run()
if (result.changes > 0) {
console.log(`[Password Reset Cleanup] Deleted ${result.changes} expired reset code(s)`)
}
} catch (error) {
console.error('[Password Reset Cleanup] Error cleaning expired password reset codes:', error)
}
}
async function runAllCleanupTasks() {
console.log('[Cleanup] Starting scheduled cleanup tasks')
await runSermonCleanup()
await runSessionCleanup()
await runRateLimitCleanup()
await runPasswordResetCleanup()
console.log('[Cleanup] All cleanup tasks completed')
}
export default defineNitroPlugin((nitroApp) => {
console.log('[Cleanup Plugin] Initializing scheduled cleanup tasks')
// Run all cleanup tasks at startup (after a short delay)
setTimeout(async () => {
console.log('[Cleanup] Running initial cleanup')
await runAllCleanupTasks()
}, 10000) // 10 seconds after startup
// Schedule cleanup to run daily (every 24 hours) for sermons
const dailyInterval = 24 * 60 * 60 * 1000 // 24 hours in milliseconds
setInterval(async () => {
await runAllCleanupTasks()
}, dailyInterval)
// Run session/rate-limit cleanup more frequently (every hour)
// This prevents these tables from growing too large
const hourlyInterval = 60 * 60 * 1000 // 1 hour in milliseconds
setInterval(async () => {
console.log('[Cleanup] Running hourly cleanup')
await runSessionCleanup()
await runRateLimitCleanup()
await runPasswordResetCleanup()
}, hourlyInterval)
})