305 lines
8.2 KiB
Markdown
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
|