18 KiB
TODO - spotlight.cam
Active tasks and roadmap - optimized for quick reference
🎯 CURRENT STATUS
Phase: 2.5 (WebRTC Implementation) - ⏳ NEXT Previous Phase: 2 (Matches & Ratings API) - ✅ COMPLETED Also Completed: 1.6 (Competition Heats System - Backend & Frontend) - ✅ COMPLETED Progress: ~72% complete
✅ Completed
- Phase 0: Frontend mockup with all views
- Phase 1: Backend Foundation
- Node.js + Express API
- PostgreSQL database with Prisma ORM
- JWT authentication (register, login)
- Socket.IO real-time chat (event & match rooms)
- Comprehensive test coverage (81%+)
- Phase 1.5: Email & WSDC & Profiles & Security
- Email verification (AWS SES with link + PIN)
- Password reset workflow
- WSDC API integration (auto-fill registration)
- User profiles (social media links, location)
- Public profiles (/{username})
- Event participation tracking (auto-save joined events)
- Event security (unique slugs, prevent ID enumeration)
- QR code event check-in system (physical presence required at venue)
- Phase 1.6: Competition Heats System
- Database schema (divisions, competition_types, event_user_heats)
- Backend API (CRUD operations, validation)
- Frontend components (HeatsBanner, forms, badges)
- Phase 2: Matches & Ratings API
- Match requests with CUID slugs
- Real-time match notifications
- Ratings system (1-5 stars, comments, preferences)
- Public profile ratings display
- Profile links from chat/matches
- Message history persistence
- Duplicate rating prevention
⏳ Next Priority
WebRTC Implementation - P2P signaling and file transfer
See: docs/COMPLETED.md for full list of completed tasks
📌 Phase 1: Backend Foundation - ✅ COMPLETED
Completed: 2025-11-12 Time Spent: ~14 hours
✅ Step 1: Backend Setup (COMPLETED)
- Add
backendservice to docker-compose.yml - Initialize Node.js + Express project
- Create folder structure (routes, controllers, middleware, etc.)
- Add healthcheck endpoint:
GET /api/health - Update nginx config to proxy
/api/*to backend - Unit tests (7 tests passing)
✅ Step 2: PostgreSQL Setup (COMPLETED)
- Add
dbservice (PostgreSQL 15) to docker-compose.yml - Configure volumes for data persistence
- Prisma ORM implementation
- Create database schema (6 tables with relations)
- Add indexes for performance
- Create seed data (3 events, 2 users, chat rooms)
- Fix OpenSSL compatibility issue for Prisma
✅ Step 3: Authentication API (COMPLETED)
- Install dependencies: bcrypt, jsonwebtoken, express-validator
- Implement password hashing with bcrypt (10 salt rounds)
- Implement JWT token generation and verification
- Create endpoints: register, login, /users/me
- Create auth middleware for protected routes
- Update frontend AuthContext to use real API
- Unit tests (30 tests, 78%+ coverage)
✅ Step 4: WebSocket Chat (COMPLETED)
- Install Socket.IO 4.8.1 on backend
- Setup Socket.IO server with Express integration
- JWT authentication for socket connections
- Event rooms (join/leave/messages/active users)
- Match rooms (private 1:1 chat)
- Install socket.io-client on frontend
- Update EventChatPage with real-time messaging
- Update MatchChatPage with real-time chat
- Unit tests (12 tests, 89% coverage for Socket.IO module)
📌 Phase 1.6: Competition Heats System - ⏳ IN PROGRESS
Estimated Time: 6-8 hours Priority: HIGH (blocking for proper matchmaking) Status: Design phase completed, ready for implementation
Business Logic Summary
- Users must declare their competition heats before matchmaking
- One user can compete in multiple divisions/heats (e.g., J&J Novice + Strictly Advanced)
- Constraint: Cannot compete in same role in same division+competition type (e.g., cannot have "J&J Novice Leader" twice)
- Role is optional (can be NULL = undeclared)
- Heat numbers: 1-9
- Format example: "J&J NOV 1 L" (Jack & Jill, Novice, Heat 1, Leader)
Step 1: Database Schema (1-2h) ⏳
- Create migration for 3 new tables:
divisions- Pre-defined competition divisions- Columns: id, name (varchar), abbreviation (varchar 3), display_order (int)
- Seed data: Newcomer (NEW), Novice (NOV), Intermediate (INT), Advanced (ADV), All-Star (ALL), Champion (CHA)
competition_types- Pre-defined competition types- Columns: id, name (varchar), abbreviation (varchar 3)
- Seed data: Jack & Jill (J&J), Strictly (STR)
event_user_heats- User's declared heats for event- Columns: id, user_id, event_id, division_id, competition_type_id, heat_number (1-9), role (enum: Leader/Follower/NULL), created_at, updated_at
- UNIQUE constraint: (user_id, event_id, division_id, competition_type_id, role)
- Foreign keys: user_id → users.id, event_id → events.id, division_id → divisions.id, competition_type_id → competition_types.id
- Indexes: (user_id, event_id), (event_id)
- Update Prisma schema
- Run migration
- Verify seed data
Step 2: Backend API (2-3h) ⏳
- Create routes and controllers:
GET /api/divisions- List all divisions (public)GET /api/competition-types- List all competition types (public)POST /api/events/:slug/heats- Add/update user's heats (authenticated)- Input: array of { divisionId, competitionTypeId, heatNumber, role? }
- Validation: unique constraint, heat number 1-9
- Replace all existing heats (upsert pattern)
GET /api/events/:slug/heats/me- Get current user's heats (authenticated)GET /api/events/:slug/heats/all- Get all users' heats for sidebar (authenticated)- Returns: userId, username, avatar, heats[]
DELETE /api/events/:slug/heats/:id- Delete specific heat (authenticated)
- Validation middleware:
- Heat number 1-9
- Role enum (Leader/Follower/NULL)
- Unique constraint enforcement
- User must be event participant
- Unit tests (CRUD operations, validation, constraints)
Step 3: Socket.IO Events (0.5h) ⏳
- Add event:
heats_updated- Broadcast when user updates heats- Payload: { userId, username, heats[] }
- Send to all users in event room
- Update active_users event to include heats data
Step 4: Frontend Components (2-3h) ⏳
- Create HeatsBanner component (sticky between header and chat) - ✅ DONE
- Show only if user has no heats declared
- Form with dynamic heat entries (add/remove)
- Fields per entry: Competition Type (select), Division (select), Heat Number (1-9), Role (optional: Leader/Follower)
- "Save Heats" button → POST /api/events/:slug/heats
- On save success: hide banner, show success message
- Add "Edit Heats" button in EventChatPage header (next to "Leave Event") - ⏳ TODO
- Opens modal with same form as banner
- Pre-fill with existing heats
- "Update Heats" button
- Update EventChatPage sidebar (Active Users) - ⏳ TODO
- Display heat badges under username
- Format: "J&J NOV 1 L", "STR ADV 3" (no role if NULL)
- Max 3 visible badges, "+" indicator if more
- Add checkbox: "Hide users from my heats"
- Logic: Hide users with ANY matching (division + competition_type + heat_number)
- Disable UserPlus icon if user has no heats declared
- Create frontend API methods in services/api.js - ✅ DONE
- divisionsAPI.getAll()
- competitionTypesAPI.getAll()
- heatsAPI.saveHeats(slug, heats[])
- heatsAPI.getMyHeats(slug)
- heatsAPI.getAllHeats(slug)
- heatsAPI.deleteHeat(slug, heatId)
- Socket.IO integration - ⏳ TODO
- Listen to
heats_updatedevent - Update active users list in real-time
- Listen to
Step 4.1: EventChatPage Integration - ⏳ IN PROGRESS (Remaining work)
What needs to be done:
-
Add state management for heats:
const [myHeats, setMyHeats] = useState([]); const [userHeats, setUserHeats] = useState(new Map()); // userId → heats[] const [showHeatsBanner, setShowHeatsBanner] = useState(false); const [hideMyHeats, setHideMyHeats] = useState(false); const [showHeatsModal, setShowHeatsModal] = useState(false); -
Load heats on component mount:
useEffect(() => { const loadHeats = async () => { const [myHeatsData, allHeatsData] = await Promise.all([ heatsAPI.getMyHeats(slug), heatsAPI.getAllHeats(slug), ]); setMyHeats(myHeatsData); setShowHeatsBanner(myHeatsData.length === 0); // Map userHeats const heatsMap = new Map(); allHeatsData.forEach(userHeat => { heatsMap.set(userHeat.userId, userHeat.heats); }); setUserHeats(heatsMap); }; loadHeats(); }, [slug]); -
Add HeatsBanner before chat:
{showHeatsBanner && ( <HeatsBanner slug={slug} onSave={() => { setShowHeatsBanner(false); // Reload heats }} /> )} -
Add "Edit Heats" button in header (next to "Leave Event"):
<button onClick={() => setShowHeatsModal(true)} className="flex items-center gap-2 px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50" > <Edit size={16} /> Edit Heats </button> -
Create modal for editing heats (reuse HeatsBanner logic)
-
Add heat badges to sidebar under username:
{activeUsers.map(activeUser => { const userHeatsForThisUser = userHeats.get(activeUser.userId) || []; const hasHeats = userHeatsForThisUser.length > 0; return ( <div key={activeUser.userId}> <div className="flex items-center"> <img src={activeUser.avatar} /> <span>{activeUser.username}</span> </div> {/* Heat badges */} <div className="flex flex-wrap gap-1 mt-1"> {userHeatsForThisUser.slice(0, 3).map(heat => ( <span key={heat.id} className="text-xs px-2 py-0.5 bg-blue-100 text-blue-700 rounded"> {heat.competitionType.abbreviation} {heat.division.abbreviation} {heat.heatNumber} {heat.role && ` ${heat.role[0]}`} </span> ))} {userHeatsForThisUser.length > 3 && ( <span className="text-xs px-2 py-0.5 bg-gray-100 text-gray-600 rounded"> +{userHeatsForThisUser.length - 3} </span> )} </div> {/* UserPlus button - disabled if no heats */} <button onClick={() => handleMatchWith(activeUser.userId)} disabled={!hasHeats} className="p-1 text-primary-600 hover:bg-primary-50 rounded disabled:opacity-30 disabled:cursor-not-allowed" > <UserPlus className="w-4 h-4" /> </button> </div> ); })} -
Add filter checkbox above active users:
<label className="flex items-center gap-2 text-sm text-gray-700 mb-2"> <input type="checkbox" checked={hideMyHeats} onChange={(e) => setHideMyHeats(e.target.checked)} className="rounded border-gray-300" /> Hide users from my heats </label> -
Filter logic:
const filteredUsers = hideMyHeats ? activeUsers.filter(activeUser => { const theirHeats = userHeats.get(activeUser.userId) || []; return !theirHeats.some(theirHeat => myHeats.some(myHeat => myHeat.divisionId === theirHeat.divisionId && myHeat.competitionTypeId === theirHeat.competitionTypeId && myHeat.heatNumber === theirHeat.heatNumber ) ); }) : activeUsers; -
Socket.IO heats_updated listener:
useEffect(() => { const socket = getSocket(); if (!socket) return; socket.on('heats_updated', ({ userId, username, heats }) => { setUserHeats(prev => { const newMap = new Map(prev); newMap.set(userId, heats); return newMap; }); // If it's current user, update myHeats if (userId === user.id) { setMyHeats(heats); setShowHeatsBanner(heats.length === 0); } }); return () => { socket.off('heats_updated'); }; }, [user.id]);
Step 5: Styling & UX (0.5-1h) ⏳
- Heat badges design (color-coded by division?)
- Banner responsive design (mobile + desktop)
- Modal for editing heats
- Loading states for heat operations
- Error handling & validation messages
- Empty states ("No heats declared yet")
Step 6: Testing & Edge Cases (0.5-1h) ⏳
- Test unique constraint violation (frontend + backend)
- Test filter "Hide users from my heats"
- Test real-time updates when someone changes heats
- Test UserPlus button disabled for users without heats
- Test banner dismissal and re-opening via "Edit Heats"
- Test multiple heats display in sidebar
- Test role optional (NULL) handling
Technical Notes
- Abbreviations:
- Divisions: NEW, NOV, INT, ADV, ALL, CHA
- Competition Types: J&J, STR
- Roles: L (Leader), F (Follower), empty (NULL)
- Display format: "{CompType} {Div} {Heat} {Role?}" → "J&J NOV 1 L"
- Future enhancement: When Matches API is implemented, editing heats with active match requires partner confirmation
📌 NEXT STEPS - Phase 2.5: WebRTC Implementation
Estimated Time: 8-10 hours Priority: HIGH
Step 1: WebRTC Signaling (3-4h) ⏳
- Add Socket.IO signaling events:
webrtc_offer- Send SDP offerwebrtc_answer- Send SDP answerwebrtc_ice_candidate- Exchange ICE candidates
- Frontend WebRTC setup:
- RTCPeerConnection initialization
- STUN server configuration
- Signaling flow implementation
- Connection state monitoring
- Unit tests (signaling message exchange)
- Test TURN servers - Verify NAT traversal for symmetric NAT scenarios
- Test connection from different network configurations
- Verify fallback to TURN when direct/STUN fails
- Monitor ICE candidate gathering and connection state
- Test with restricted/symmetric NAT environments
Step 2: WebRTC File Transfer (4-5h) ⏳
- RTCDataChannel setup (ordered, reliable)
- File metadata exchange (name, size, type)
- File chunking implementation (16KB chunks)
- Progress monitoring (sender & receiver)
- Error handling & reconnection logic
- Complete P2P video transfer flow:
- Select video file
- Establish P2P connection
- Transfer file via DataChannel
- Save file on receiver side
- Test with various file sizes
- Fallback: Link sharing (already implemented in UI)
🎯 Future Phases (Reference)
Phase 3: MVP Finalization (2-3 weeks)
- Security hardening:
- Rate limiting (express-rate-limit)
- Input validation & sanitization
- CORS configuration
- SQL injection prevention
- XSS protection
- Testing:
- Integration tests (API endpoints)
- E2E tests (Playwright/Cypress)
- WebRTC connection tests
- PWA features:
- Web app manifest
- Service worker (offline support)
- App icons & splash screens
- Install prompt
- Deployment:
- Production Docker images
- Environment configuration
- Database backups
- Monitoring & logging
- CI/CD pipeline
Phase 5: Optional Extensions
- User badges & trust system
- Block users
- Public profiles
- Push notifications
- Video compression
- Multi-file transfer
📝 Active Development Tasks
Documentation
- ✅ README.md
- ✅ QUICKSTART.md
- ✅ CONTEXT.md
- ✅ TODO.md
- ✅ SESSION_CONTEXT.md
- ✅ ARCHITECTURE.md
- ✅ COMPLETED.md
- ✅ RESOURCES.md
- ⏳ API documentation (Swagger/OpenAPI) - after backend
- ⏳ Architecture diagrams - after backend
- ⏳ WebRTC flow diagram - after WebRTC implementation
Infrastructure
- ✅ Docker Compose (nginx, frontend)
- ⏳ Docker Compose (backend, db)
- ⏳ Production Dockerfile optimization (multi-stage builds)
- ⏳ CI/CD pipeline (GitHub Actions)
- ⏳ HTTPS setup (Let's Encrypt)
Testing
- ⏳ Backend tests (Jest + Supertest)
- ⏳ Frontend tests (Vitest + React Testing Library)
- ⏳ E2E tests (Playwright / Cypress)
- ⏳ WebRTC manual testing (different devices)
🚀 Quick Commands
Start development:
docker compose up --build
Rebuild after changes:
docker compose down && docker compose up --build
Access:
- Frontend: http://localhost:8080
- Backend (future): http://localhost:8080/api
Git workflow:
git status
git add .
git commit -m "feat: description"
📊 Progress Tracking
| Phase | Status | Progress | Estimated Time |
|---|---|---|---|
| Phase 0: Frontend Mockup | ✅ Done | 100% | ~8h (completed) |
| Phase 1: Backend Foundation | ✅ Done | 100% | ~14h (completed) |
| Phase 1.5: Email & WSDC & Profiles | ✅ Done | 100% | ~12h (completed) |
| Phase 1.6: Competition Heats | ✅ Done | 100% | ~8h (completed) |
| Phase 2: Matches & Ratings API | ✅ Done | 100% | ~10h (completed) |
| Phase 2.5: WebRTC Implementation | ⏳ Next | 0% | ~8-10h |
| Phase 3: MVP Finalization | ⏳ Pending | 0% | ~15-20h |
| Phase 4: Extensions | ⏳ Pending | 0% | TBD |
Overall Progress: ~72% (Phases 0, 1, 1.5, 1.6, 2 completed)
📝 Notes
- Frontend mockup is presentation-ready
- All views work with mock data - easy to connect real API
- WebRTC P2P mockup in MatchChatPage - needs real implementation
- Focus on Phase 1 next (backend foundation)
- Update task status: ⏳ → 🔄 → ✅
For detailed task history: See docs/COMPLETED.md
For learning resources: See docs/RESOURCES.md
For quick session context: See docs/SESSION_CONTEXT.md
For technical details: See docs/ARCHITECTURE.md
Last Updated: 2025-11-14