SetTimes Admin Handbook¶
For System Administrators
Table of Contents¶
- Introduction
- System Architecture
- User Management
- Database Administration
- Security & Access Control
- Monitoring & Logs
- Backup & Recovery
- Performance Optimization
- Troubleshooting
- Maintenance Tasks
Introduction¶
This handbook is for system administrators managing the SetTimes platform. It covers:
- System architecture and components
- User and role management
- Database administration
- Security best practices
- Monitoring and maintenance
Prerequisites:
- Access to Cloudflare dashboard
- Database admin credentials
- Understanding of Cloudflare Workers/Pages
- Basic SQL knowledge
System Architecture¶
Technology Stack¶
Frontend:
- React 19 with Vite 8
- React Router v7
- Tailwind CSS 4
- lucide-react icons
- Cloudflare Pages (hosting)
Backend:
- Cloudflare Workers (API layer)
- Cloudflare Pages Functions
- D1 (SQLite) database
- R2 (Object storage for photos - optional)
Security:
- Session-based authentication
- HTTPOnly cookies
- CSRF protection (double-submit pattern)
- Role-based access control (RBAC)
Architecture Diagram¶
┌─────────────────────────────────────┐
│ Users (Browsers/Mobile) │
└──────────────┬──────────────────────┘
│
│ HTTPS
▼
┌──────────────────────────────────────┐
│ Cloudflare CDN + WAF │
└──────────────┬───────────────────────┘
│
┌────────┴────────┐
│ │
▼ ▼
┌──────────┐ ┌──────────────┐
│ Pages │ │ Functions │
│ (Static) │ │ (API Routes) │
└──────────┘ └──────┬───────┘
│
▼
┌──────────────┐
│ D1 Database │
└──────────────┘
Component Responsibilities¶
Cloudflare Pages (frontend/):
- Serves static React application
- Handles routing (SPA)
- PWA service worker
- Cached assets
Cloudflare Functions (functions/api/):
- REST API endpoints
- Authentication & authorization
- Business logic
- Database queries
D1 Database:
- User accounts & sessions
- Events, venues, bands
- Performance schedules
- Audit logs
User Management¶
Creating User Accounts¶
Via Invite Codes:
- Log in to admin panel
- Go to Users tab
- Click "Generate Invite Code"
- Share code with new user
- User signs up with code
Direct Creation (Admin Only):
// Via admin API
POST /api/admin/users
{
"email": "user@example.com",
"name": "New User",
"role": "editor",
"password": "temporary123"
}
Role Hierarchy¶
| Role | Level | Permissions |
|---|---|---|
| Admin | 3 | Full system access, user management |
| Editor | 2 | Create/edit events, venues, bands |
| Viewer | 1 | Read-only access |
Permission Model:
checkPermission(request, env, requiredRole)- User role level must be ≥ required level
- Example: Admin (3) ≥ Editor (2) = Access granted
Managing User Roles¶
Promoting/Demoting Users:
- Users tab → Select user
- Click "Edit"
- Change role dropdown
- Save changes
Security Note: Only admins can change roles.
Disabling User Accounts¶
Temporarily Disable:
- Users tab → Select user
- Click "Toggle Status"
- User cannot log in (session terminated)
Permanently Delete:
- Users tab → Select user
- Click "Delete" (requires confirmation)
- User data removed (irreversible)
Note: Cannot delete users with active sessions unless force-logout is enabled.
Password Management¶
Reset User Password:
- Users tab → Select user
- Click "Reset Password"
- System generates temporary password
- Send to user via secure channel
- User must change on first login
Password Requirements:
- Minimum 8 characters
- Must include: uppercase, lowercase, number
- Hashed using PBKDF2-SHA256 via the Web Crypto API (not bcrypt — Workers cannot run native bcrypt)
Database Administration¶
D1 Database Structure¶
Tables:
users- User accountslucia_sessions- Active server-side sessionsevents- Event definitionsvenues- Performance venuesband_profiles- Artist/band profilesperformances- Links a band profile to an event, venue, and set timeaudit_logs- Security audit trailinvite_codes- User invite system
Schema Location: numbered migrations in migrations/ (with database/setup-complete.sql as the consolidated reference)
Accessing the Database¶
Via Wrangler CLI:
# List databases
wrangler d1 list
# Execute query
wrangler d1 execute settimes-production-db --command="SELECT * FROM users LIMIT 10"
# Run migration
wrangler d1 execute settimes-production-db --file=./migrations/001_initial_schema.sql
Via Cloudflare Dashboard:
- Workers & Pages → D1
- Select database:
settimes-production-db - Console tab → Run queries
Common Database Queries¶
Check User Sessions:
SELECT u.email, s.expires_at
FROM lucia_sessions s
JOIN users u ON s.user_id = u.id
WHERE s.expires_at > unixepoch()
ORDER BY s.expires_at DESC;
Event Statistics:
SELECT
e.name,
e.date,
COUNT(DISTINCT p.band_profile_id) as band_count,
COUNT(DISTINCT p.venue_id) as venue_count
FROM events e
LEFT JOIN performances p ON e.id = p.event_id
GROUP BY e.id
ORDER BY e.date DESC;
Audit Log Review:
SELECT
al.action,
u.email,
al.details,
al.ip_address,
al.created_at
FROM audit_logs al
JOIN users u ON al.user_id = u.id
WHERE al.created_at > datetime('now', '-7 days')
ORDER BY al.created_at DESC
LIMIT 100;
Database Backups¶
Automated Backups:
- Cloudflare D1 automatically backs up databases
- Point-in-time recovery available (contact Cloudflare support)
Manual Backup:
# Export entire database
wrangler d1 export settimes-production-db --output=backup-$(date +%Y%m%d).sql
Restore from Backup:
# Restore database
wrangler d1 execute settimes-production-db --file=backup-20251119.sql
Backup Schedule:
- Daily automated backups (Cloudflare)
- Weekly manual export (recommended)
- Store backups securely off-platform
Security & Access Control¶
Authentication Flow¶
- User submits email + password to
/api/admin/auth/login - Backend verifies credentials (PBKDF2-SHA256 via Web Crypto)
- Session created in
lucia_sessionstable - Session token returned in HTTPOnly cookie
- All subsequent requests include cookie
- Middleware validates session + checks RBAC
Multi-Factor Authentication (MFA)¶
Admins can enable TOTP-based two-factor authentication on their accounts:
- Setup: In account settings, scan the QR code (or enter the secret) into an authenticator app (Google Authenticator, Authy, 1Password, etc.) and confirm with a 6-digit code. TOTP is HMAC-SHA1 over a 30-second window, computed via the Web Crypto API (
functions/utils/totp.js) — no third-party crypto library. - Backup codes: Enabling MFA reveals one-time backup codes — store them securely. Each works once if the authenticator is unavailable.
- Trusted devices: A device can be marked trusted to skip the MFA prompt there for a limited period; trusted devices can be revoked at any time.
- Enforcement: With MFA enabled, login requires the password and a valid TOTP (or backup) code before a session is issued. On any successful re-authentication, the user's prior sessions are invalidated.
Session Management¶
Session Configuration:
- Storage:
lucia_sessionsrows in D1 (direct D1 session manager;expires_atis INTEGER Unix-epoch seconds) - Admin idle timeout: 15 minutes
- Admin absolute lifetime: 8 hours
- Cookie transport: HttpOnly session cookie (
__Host-session_tokenin production) - CSRF: Double-submit CSRF cookie plus
X-CSRF-Token
Session Cleanup:
-- Remove expired sessions (run daily)
DELETE FROM lucia_sessions WHERE expires_at < strftime('%s', 'now');
Force Logout User:
-- Terminate all sessions for a user
DELETE FROM lucia_sessions WHERE user_id = <user_id>;
CSRF Protection¶
Implementation: Double-submit cookie pattern
How it works:
- Server generates CSRF token
- Token sent as cookie (readable by JS)
- Client includes token in
X-CSRF-Tokenheader - Server verifies cookie matches header
Location: functions/utils/csrf.js
Bypass (for testing only):
// Set in request header
X-CSRF-Token: <token_from_cookie>
Audit Logging¶
What's Logged:
- User logins/logouts
- Event creation/modification/deletion
- Venue/band changes
- Role changes
- Failed login attempts
Audit Log Retention:
- Keep for 90 days minimum
- Archive older logs to R2/S3
Review Logs Regularly:
-- Failed logins (potential attacks)
SELECT * FROM audit_logs
WHERE action LIKE '%login%failed%'
AND created_at > datetime('now', '-1 day');
-- Admin actions
SELECT * FROM audit_logs al
JOIN users u ON al.user_id = u.id
WHERE u.role = 'admin'
ORDER BY al.created_at DESC
LIMIT 50;
Security Best Practices¶
Regular Tasks:
- [ ] Review audit logs weekly
- [ ] Monitor failed login attempts
- [ ] Update dependencies monthly
- [ ] Rotate admin passwords quarterly
- [ ] Review user access permissions
Access Control:
- Principle of least privilege (grant minimum required role)
- Regular access reviews (quarterly)
- Remove inactive users (>90 days)
- Use strong passwords (enforce policy)
Monitoring & Logs¶
Cloudflare Analytics¶
Access:
- Cloudflare Dashboard
- Pages → settimes project
- Analytics tab
Key Metrics:
- Page views
- Unique visitors
- Request rate
- Error rate (4xx, 5xx)
- Bandwidth usage
Application Logs¶
View Logs:
# Real-time logs
wrangler tail
# Filter by status code
wrangler tail --status error
Log Locations:
- API Logs: Cloudflare Workers logs (via
wrangler tail) - Audit Logs: D1
audit_logstable - Error Logs: Console errors (browser dev tools)
Performance Monitoring¶
Core Web Vitals:
- LCP (Largest Contentful Paint): < 2.5s
- FID (First Input Delay): < 100ms
- CLS (Cumulative Layout Shift): < 0.1
Check Performance:
- Use Lighthouse in Chrome DevTools
- Run:
npm run lighthousein frontend/ - Review PageSpeed Insights:
npm run psi
Alerting¶
Set Up Alerts:
- Cloudflare Dashboard → Notifications
- Configure alerts for:
- High error rate (>5%)
- Traffic spikes (unusual patterns)
- DDoS attacks
- SSL certificate expiration
Backup & Recovery¶
Database Backups¶
Backup Strategy:
- Frequency: Daily automated, weekly manual
- Retention: 30 days rolling
- Storage: Off-platform (S3/R2)
Backup Command:
# Create backup
wrangler d1 export settimes-production-db --output=backups/settimes-$(date +%Y%m%d-%H%M%S).sql
# Compress backup
gzip backups/settimes-*.sql
Restore Database:
# Restore from backup
gunzip backups/settimes-20251119.sql.gz
wrangler d1 execute settimes-production-db --file=backups/settimes-20251119.sql
Disaster Recovery Plan¶
Scenario: Database Corruption
- Stop all write operations
- Assess damage (check audit logs)
- Restore from latest backup
- Verify data integrity
- Resume operations
- Document incident
Scenario: Cloudflare Outage
- Check Cloudflare status page
- Communicate to users (status page)
- Wait for resolution (no action needed)
- Verify functionality after recovery
RTO/RPO:
- Recovery Time Objective (RTO): 4 hours
- Recovery Point Objective (RPO): 24 hours
Performance Optimization¶
Frontend Optimization¶
Build Optimization:
- Vite code splitting
- Tree shaking enabled
- Asset compression (gzip/brotli)
- Image lazy loading
- React.memo for pure components
Cache Strategy:
/assets/* → 1 year (immutable)
/*.html → no-cache
/api/* → no-store
Service Worker → no-cache
API Optimization¶
Query Optimization:
- Use indexes on foreign keys
- Avoid N+1 queries
- Limit results (
LIMITclause) - Cache frequently accessed data
Rate Limiting:
- Implement Cloudflare Rate Limiting
- Login endpoint: 5 requests/minute/IP
- API endpoints: 100 requests/minute/IP
Database Optimization¶
Indexes:
-- Ensure indexes exist
CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id);
CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at);
CREATE INDEX IF NOT EXISTS idx_bands_event_id ON bands(event_id);
CREATE INDEX IF NOT EXISTS idx_audit_logs_user_id ON audit_logs(user_id);
Cleanup Old Data:
-- Remove old sessions
DELETE FROM lucia_sessions WHERE expires_at < unixepoch('now', '-7 days');
-- Archive old audit logs
INSERT INTO audit_logs_archive SELECT * FROM audit_logs
WHERE created_at < datetime('now', '-90 days');
DELETE FROM audit_logs WHERE created_at < datetime('now', '-90 days');
Troubleshooting¶
Common Issues¶
Issue: Users can't log in
Diagnosis:
-- Check if user exists
SELECT * FROM users WHERE email = 'user@example.com';
-- Check if account is active
SELECT is_active FROM users WHERE email = 'user@example.com';
-- Check failed login attempts
SELECT * FROM audit_logs
WHERE action LIKE '%login%'
AND details LIKE '%user@example.com%'
ORDER BY created_at DESC LIMIT 10;
Solutions:
- Verify user exists and is active
- Reset password if forgotten
- Check for account lockout (not implemented yet)
Issue: Events not appearing on public timeline
Diagnosis:
-- Check event status
SELECT name, date, status, is_published FROM events WHERE id = <event_id>;
-- Check if bands assigned
SELECT COUNT(*) FROM bands WHERE event_id = <event_id>;
Solutions:
- Ensure
is_published = 1 - Ensure
status != 'archived' - Verify bands are assigned to event
- Clear Cloudflare cache
Issue: Slow API responses
Diagnosis:
# Check API logs
wrangler tail --status slow
# Check query performance
wrangler d1 execute settimes-production-db --command="EXPLAIN QUERY PLAN <your_query>"
Solutions:
- Add missing indexes
- Optimize slow queries
- Implement caching
- Upgrade D1 plan if needed
Debug Mode¶
Enable Debug Logging:
// In functions/api/_middleware.js
console.log("[DEBUG]", { user, action, details });
View Debug Logs:
wrangler tail --format=pretty
Maintenance Tasks¶
Daily¶
- [ ] Review error logs
- [ ] Monitor API response times
- [ ] Check failed login attempts
Weekly¶
- [ ] Review audit logs
- [ ] Export database backup
- [ ] Check disk usage (D1 limits)
- [ ] Review user access
Monthly¶
- [ ] Update npm dependencies
- [ ] Security audit (run
npm audit) - [ ] Review and optimize slow queries
- [ ] Clean up expired sessions
- [ ] Test disaster recovery plan
Quarterly¶
- [ ] Rotate admin passwords
- [ ] Review user roles and permissions
- [ ] Archive old audit logs
- [ ] Performance audit (Lighthouse)
- [ ] Update documentation
Emergency Contacts¶
Cloudflare Support:
- Dashboard: cloudflare.com/support
- Enterprise: 24/7 phone support
- Community: community.cloudflare.com
Development Team:
- GitHub: github.com/BreakableHoodie/settimesdotca
- Issues: Create issue on GitHub
Appendix¶
Useful Commands¶
# Deploy to production
cd frontend && npm run deploy:prod
# Deploy to development
cd frontend && npm run deploy:dev
# Run migrations
wrangler d1 migrations apply settimes-production-db
# Check database size
wrangler d1 info settimes-production-db
# List all users
wrangler d1 execute settimes-production-db --command="SELECT email, role FROM users"
# Force logout all users
wrangler d1 execute settimes-production-db --command="DELETE FROM lucia_sessions"
Environment Variables¶
Required in wrangler.toml:
[env.production.vars]
DATABASE_ID = "settimes-production-db"
ENVIRONMENT = "production"
[env.development.vars]
DATABASE_ID = "settimes-dev-db"
ENVIRONMENT = "development"
Version: 1.0 Last Updated: 2026-07-04 For: SetTimes.ca Platform
Questions? Refer to the other guides or contact the development team.