8.2 KiB
8.2 KiB
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)
NODE_ENV=development
PORT=4000
DATABASE_PATH=./backend/awards.db
ADMIN_USERNAME=admin
ADMIN_PASSWORD_HASH=<bcrypt_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)
VITE_API_URL=http://localhost:4000
Production Environment
The Kubernetes deployment uses secrets for sensitive values:
ADMIN_USERNAMEandADMIN_PASSWORD_HASHare stored inawards-secret- Other environment variables are set directly in k8s.yaml
Development
Setup
# Install backend dependencies
cd backend
npm install
# Install frontend dependencies
cd ../frontend
npm install
Running Locally
# 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
# 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
awardsnamespace created in Kubernetes- Traefik ingress controller installed
- TLS secret
awards-tlsconfigured for awards.scottyah.com
Deployment Process
# 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:
# 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 awardsGET /awards/top- Get top 5 awards by emoji reactionsPOST /awards- Submit new award nominationPATCH /awards/:id/emojis- Add emoji reactionPATCH /awards/:id/emojis/remove- Remove emoji reactionGET /health- Health check endpoint
Admin Endpoints (require Basic Auth)
GET /awards/pending- Get pending (unapproved) awardsPATCH /awards/:id/approve- Approve an awardDELETE /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
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.logand/app/combined.log - Development: Logs output to console
Kubernetes Logs
# 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 /healthevery 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
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
kubectl get pvc -n awards kubectl describe pvc awards-pvc -n awards
Performance Considerations
- Geocoding: Server geocodes addresses once on submission; frontend uses cached lat/lng
- Emoji Tally: Stored as JSON string; consider dedicated table if tallies grow large
- Map Rendering: Memoized to prevent unnecessary re-renders
- 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