# Best of Pacific Beach Awards - Technical Documentation ## Overview A full-stack web application for nominating and voting on the best places in Pacific Beach, San Diego. Features a React frontend with interactive maps, emoji reactions, and an Express backend with SQLite database. **Live URL:** https://awards.scottyah.com ## Architecture ### Technology Stack - **Frontend:** React 19, Vite, React Leaflet (maps), React Router - **Backend:** Express 5, SQLite3, Winston (logging) - **Security:** bcryptjs, express-rate-limit, express-validator - **Deployment:** Docker, Kubernetes, Harbor registry, Traefik ingress ### Key Features - Interactive map showing award locations - Emoji reactions with session tracking - Admin management panel with basic auth - Address validation (Pacific Beach 92109 only) - Rate limiting and input validation - Geocoding via Nominatim API ## Project Structure ``` react-awards-map/ ├── backend/ │ ├── index.js # Main Express server │ ├── migrate.js # Database migration script │ ├── package.json │ └── awards.db # SQLite database (gitignored) ├── frontend/ │ ├── src/ │ │ ├── components/ │ │ │ ├── EmojiPicker.jsx │ │ │ └── EmojiPicker.css │ │ ├── config/ │ │ │ └── api.js # API URL configuration │ │ ├── App.jsx │ │ ├── AwardsPage.jsx │ │ └── ManagementPage.jsx │ └── package.json ├── Dockerfile # Production container ├── k8s.yaml # Kubernetes manifests ├── deploy.sh # Deployment script ├── .env.example # Environment template └── .env.local # Local environment (gitignored) ``` ## Environment Configuration ### Backend (.env.local) ```bash NODE_ENV=development PORT=4000 DATABASE_PATH=./backend/awards.db ADMIN_USERNAME=admin ADMIN_PASSWORD_HASH= CORS_ORIGIN=http://localhost:5173 NOMINATIM_BASE_URL=https://nominatim.openstreetmap.org NOMINATIM_USER_AGENT=BestOfPBAwardsApp/1.0 RATE_LIMIT_WINDOW_MS=900000 RATE_LIMIT_MAX_REQUESTS=100 RATE_LIMIT_NOMINATION_MAX=10 RATE_LIMIT_EMOJI_MAX=50 ``` ### Frontend (frontend/.env.local) ```bash VITE_API_URL=http://localhost:4000 ``` ### Production Environment The Kubernetes deployment uses secrets for sensitive values: - `ADMIN_USERNAME` and `ADMIN_PASSWORD_HASH` are stored in `awards-secret` - Other environment variables are set directly in k8s.yaml ## Development ### Setup ```bash # Install backend dependencies cd backend npm install # Install frontend dependencies cd ../frontend npm install ``` ### Running Locally ```bash # Terminal 1: Start backend cd backend node index.js # Terminal 2: Start frontend cd frontend npm run dev ``` Access the app at http://localhost:5173 ### Database Operations ```bash # Create tables node backend/migrate.js create # Seed with sample data node backend/migrate.js seed # Reset and seed node backend/migrate.js reset:seed # Backup database node backend/migrate.js backup ``` ## Production Deployment ### Prerequisites - Docker installed and logged into harbor.scottyah.com - kubectl configured for your Kubernetes cluster - `awards` namespace created in Kubernetes - Traefik ingress controller installed - TLS secret `awards-tls` configured for awards.scottyah.com ### Deployment Process ```bash # 1. Ensure .env.local contains production secrets # (ADMIN_USERNAME and ADMIN_PASSWORD_HASH) # 2. Run deployment script ./deploy.sh [tag] # The script will: # - Build Docker image with frontend and backend # - Push to harbor.scottyah.com/secure/awards # - Create Kubernetes secrets from .env.local # - Apply k8s.yaml manifests # - Restart the deployment ``` ### Manual Deployment Steps If you need to deploy manually: ```bash # Build and push docker build -t harbor.scottyah.com/secure/awards:latest . docker push harbor.scottyah.com/secure/awards:latest # Create namespace (if not exists) kubectl create namespace awards # Create secrets kubectl create secret generic awards-secret -n awards \ --from-literal=admin-username='admin' \ --from-literal=admin-password-hash='$2b$10$...' # Apply manifests kubectl apply -f k8s.yaml # Restart deployment kubectl rollout restart deployment/awards-dep -n awards ``` ## API Endpoints ### Public Endpoints - `GET /awards` - Get all approved awards - `GET /awards/top` - Get top 5 awards by emoji reactions - `POST /awards` - Submit new award nomination - `PATCH /awards/:id/emojis` - Add emoji reaction - `PATCH /awards/:id/emojis/remove` - Remove emoji reaction - `GET /health` - Health check endpoint ### Admin Endpoints (require Basic Auth) - `GET /awards/pending` - Get pending (unapproved) awards - `PATCH /awards/:id/approve` - Approve an award - `DELETE /awards/:id` - Reject/delete an award ### Rate Limits - General: 100 requests per 15 minutes - Nominations: 10 per 15 minutes - Emoji reactions: 50 per 15 minutes ## Security Features ### Implemented (Priority 1 & 2) - ✅ Environment-based configuration (no hardcoded values) - ✅ CORS restricted to awards.scottyah.com - ✅ Bcrypt password hashing for admin auth - ✅ Rate limiting on all endpoints - ✅ Input validation with express-validator - ✅ Structured logging with Winston - ✅ Graceful shutdown handling - ✅ Health check endpoint - ✅ Production static file serving - ✅ Removed duplicate geocoding (uses server lat/lng) - ✅ Extracted shared EmojiPicker component ### Not Yet Implemented (Priority 3) - ❌ Automated tests - ❌ Geocoding result caching - ❌ Pagination for awards endpoint - ❌ TypeScript migration - ❌ Database migrations system ## Database Schema ### awards table ```sql CREATE TABLE awards ( id INTEGER PRIMARY KEY AUTOINCREMENT, category TEXT NOT NULL, address TEXT NOT NULL, submitted_by VARCHAR(255), emoji_tally TEXT DEFAULT '{}', -- JSON string submitted_date DATETIME DEFAULT CURRENT_TIMESTAMP, approved_date DATETIME, lat REAL, lng REAL ); ``` ## Monitoring & Logs ### Application Logs - **Production:** Logs written to `/app/error.log` and `/app/combined.log` - **Development:** Logs output to console ### Kubernetes Logs ```bash # View pod logs kubectl logs -n awards -l app=awards --tail=100 -f # Check deployment status kubectl get pods -n awards kubectl describe deployment awards-dep -n awards ``` ### Health Checks - Liveness probe: `GET /health` every 30s - Readiness probe: Same as liveness ## Troubleshooting ### Common Issues **Issue:** Geocoding returns "not in Pacific Beach" - **Solution:** Address must contain "92109" zip code or "Pacific Beach" in the result **Issue:** Admin login fails - **Solution:** Check ADMIN_PASSWORD_HASH is correctly set in secrets ```bash kubectl get secret awards-secret -n awards -o yaml ``` **Issue:** Frontend shows "Failed to fetch" - **Solution:** Check CORS_ORIGIN matches the frontend domain - Verify backend is running: `kubectl get pods -n awards` **Issue:** Database resets after pod restart - **Solution:** Ensure PersistentVolumeClaim is properly mounted ```bash kubectl get pvc -n awards kubectl describe pvc awards-pvc -n awards ``` ## Performance Considerations 1. **Geocoding:** Server geocodes addresses once on submission; frontend uses cached lat/lng 2. **Emoji Tally:** Stored as JSON string; consider dedicated table if tallies grow large 3. **Map Rendering:** Memoized to prevent unnecessary re-renders 4. **Static Assets:** Frontend built and served from Express in production ## Future Enhancements - Add caching for geocoding results - Implement pagination for large award lists - Add user accounts and authentication - Create admin dashboard with analytics - Add email notifications for new nominations - Implement search and filtering - Add categories system - Mobile app version ## Admin Credentials **Development:** - Username: `admin` - Password: `AwardsPB2024!Secure` **Production:** - Stored in Kubernetes secret `awards-secret` - Generate new hash: `node -e "import('bcryptjs').then(b => b.default.hash('your-password', 10).then(console.log))"` ## Support & Maintenance For issues or questions, contact the development team or create an issue in the repository. Last updated: December 2024