Files
bestofpb/CLAUDE.md
2025-12-23 17:41:30 -08:00

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_USERNAME and ADMIN_PASSWORD_HASH are stored in awards-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
  • awards namespace created in Kubernetes
  • Traefik ingress controller installed
  • TLS secret awards-tls configured 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 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

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

# 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
    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

  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