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

305 lines
8.2 KiB
Markdown

# 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=<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)
```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