Fix highlighting and all rich text formatting in emails by converting HTML tags to inline styles before sending. Email clients strip <style> blocks, so inline styles are the only reliable method.
Changes:
- Convert <mark> tags to <span> with inline background-color (#fef08a)
- Add inline styles to all rich text elements (strong, em, u, s, headings, lists)
- Process HTML conversion in email API before sending
- Simplified email template by removing unreliable <style> block
- All formatting now uses inline styles directly on elements
This ensures highlights and all other formatting (bold, italic, underline, strikethrough, headings, lists) render correctly across all email clients including Gmail, Outlook, Apple Mail, etc.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
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 <noreply@anthropic.com>
Implement comprehensive rich text editing capabilities using Tiptap editor with full formatting toolbar and functionality.
Features:
- Bold, italic, underline, strikethrough text formatting
- Highlight text with yellow marker
- Bullet and numbered lists
- Heading styles (H2, H3)
- Text alignment (left, center, right)
- Undo/redo support
- Clear formatting option
- Intuitive toolbar with icons and tooltips
- Responsive font size support
Technical changes:
- Added Tiptap dependencies to package.json (@tiptap/vue-3, starter-kit, extensions)
- Created RichTextEditor component with full toolbar and formatting options
- Integrated editor into sermon notes section replacing textarea
- Updated download API to convert HTML to formatted plain text
- Updated email API to handle HTML content properly
- Notes now stored as HTML with rich formatting preserved
The editor provides a professional writing experience with all standard formatting tools while maintaining automatic save functionality and seamless integration with email/download features.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
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>
When an admin manually unlocks an account, both the account-level
lockout and all IP-based rate limits for the login endpoint are now
cleared. This ensures legitimate users can immediately attempt to
login after being unlocked, without being blocked by stale rate
limit cache entries.
Changes:
- Added clearAllRateLimitsForEndpoint() function to database utils
- Modified unlock endpoint to clear login rate limits after unlocking
- Updated success message to reflect rate limit clearing
- Enhanced logging to track rate limit clearing operations
Fixes issue where users would see "Too many login attempts" message
even with correct credentials after admin unlock, due to persistent
IP rate limit cache from previous failed attempts.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed inconsistency between IP-based rate limiting and per-account lockout.
Previously, users would hit the IP rate limit at 5 attempts (15 min lockout)
but the account wouldn't be marked as locked until 10 attempts (30 min).
This caused confusion in the admin UI where locked accounts wouldn't show
the unlock button until 10 attempts were reached.
Changes:
- Reduced account lockout threshold from 10 to 5 failed attempts
- Reduced account lockout duration from 30 to 15 minutes
- Updated error message to reflect 15 minute lockout period
- Added detailed logging when account gets locked
- Updated README documentation to reflect correct limits
Both protection layers now work in harmony:
- IP-based rate limiting: 5 attempts = 15 min lockout
- Per-account lockout: 5 attempts = 15 min lock
This ensures the admin UI accurately shows account lock status and provides
the unlock option as soon as users hit the lockout threshold.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed an issue where SMTP configuration would fall back to defaults despite
environment variables being set in docker-compose.yml. The email utility now
properly accesses runtime configuration by accepting the H3 event context.
Changes:
- Created getEmailConfig() helper with dual-strategy config access
- Pass event context from API handlers to email functions
- Added fallback to direct process.env access for reliability
- Added debug logging to diagnose configuration issues in production
This ensures Office365 and other SMTP providers work correctly when configured
via environment variables.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
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>