Files
spotlightcam/docs/TODO.md
Radosław Gierwiało 065e77fd4e test(ratings): add comprehensive E2E test for ratings & stats flow
Add end-to-end test verifying the complete ratings and stats update flow:
- Auto match creation from suggestion acceptance
- Both users rating each other
- Stats updated exactly once (recordingsDone/recordingsReceived)
- Manual matches do NOT update stats
- Double-rating prevention (idempotency)

Test coverage (9 scenarios):
- STEP 1-3: Event creation, user enrollment, heat declaration
- STEP 4: Matching algorithm execution + saveMatchingResults fix
- STEP 5: Suggestion acceptance creates auto match (source='auto')
- STEP 6a: First rating (no stats update yet)
- STEP 6b: Second rating triggers stats update + match completion
- STEP 7: Verify duplicate rating prevention
- STEP 8: Verify manual matches don't affect fairness stats

Infrastructure:
- Add jest.setup.js to load .env.development for all tests
- Update package.json to use setupFilesAfterEnv

Documentation:
- Mark S10 (Ratings & Stats) as  IMPLEMENTED in TODO.md
- Remove from Critical Gaps section
- Add detailed implementation references

All tests passing 
2025-11-30 19:18:09 +01:00

20 KiB

TODO - spotlight.cam

Active tasks and roadmap


Current Status

Phase: MVP Complete - Ready for Production Deployment Tests: 285/286 passing - 99.7% (73% coverage) Status: Awaiting infrastructure setup

High Priority Tasks

🟡 HIGH: Matching Algorithm Integration Tests

  • Issue: Only unit tests for helper functions exist, no end-to-end tests for runMatching()
  • Test Plan: backend/src/__tests__/matching-scenarios.md (18 scenarios defined)
  • Priority Phases:
    1. Phase 1: Fundamentals (TC1-3) - Basic happy path, NOT_FOUND scenarios
    2. Phase 2: Collision Detection (TC4-9) - Buffers, slot mapping
    3. Phase 3: Limits & Workload (TC10-11) - CRITICAL bug verification (recording-recording collision)
    4. Phase 4: Fairness & Tiers (TC12-16) - Tier penalties, sorting priority
    5. Phase 5: Edge Cases (TC17-18) - Sanity checks
  • Impact: Ensures matching algorithm works correctly end-to-end, verifies critical bug fixes
  • Status: Test plan documented, implementation pending
  • Extended Scenarios: See comprehensive test scenarios below

Recently Completed (2025-11-30)

  • Ratings & Stats System - Auto matches update recordingsDone/recordingsReceived stats, manual matches don't
    • E2E test: backend/src/__tests__/ratings-stats-flow.test.js (9 test scenarios)
    • Atomic stats application with statsApplied flag to prevent double-counting
    • Frontend UI already exists in RatePartnerPage.jsx

Previously Completed (2025-11-29)

  • 3-Tier Account System (BASIC/SUPPORTER/COMFORT) with fairness algorithm
  • Dual Buffer System (prep before + rest after dancing)
  • Clickable Usernames with @ prefix in profiles
  • Country Flags in Event Chat
  • Mobile-first Design Improvements (page titles on mobile)
  • Recording Matching System Improvements (collision detection, schedule config)
  • Test Bot for Automated Testing

Full history: See docs/archive/COMPLETED.md


Matching System - Comprehensive Test Scenarios

Last Updated: 2025-11-30 Status: Documented for team discussion

Implementation Status

Implemented Scenarios

  • S1-S3: Basic flow, collision detection, limits (covered by existing tests)
  • S7.1-7.2: Manual match blocks auto suggestions (implemented 2025-11-30)
  • S10: Ratings & Stats System (implemented 2025-11-30, E2E tested)
  • S12: Multi-heat collision detection (existing logic)
  • S14.1: Only recorder can accept/reject (implemented in MVP)

🔴 Critical Gaps (P0 - Before Production)

  1. S14.2: Admin Middleware - SECURITY

    • Admin endpoints not protected: /admin/events/:slug/run-now, /admin/matching-runs
    • Need: requireAdmin middleware
  2. S14.3: Event Participant Validation - SECURITY

    • Inconsistent checks across endpoints
    • Need: Audit all suggestion/match endpoints for participant validation

⚠️ High Priority (P1 - First Month)

  1. E9/S13.2: Manual match created AFTER auto suggestion

    • Current: Manual blocks only NEW auto suggestions, old pending remain
    • Need: Cleanup conflicting pending auto suggestions when manual match created
  2. S15.1-15.2: Rate Limiting & Spam Protection

    • Max pending outgoing requests (20)
    • Rate limit manual match requests (10/minute)
  3. S16.1: Socket Notifications

    • Real-time notification when new suggestion created

📋 Medium Priority (P2 - Q1 2025)

  1. S11.3-11.4: Matching Run Details API

    • Endpoint: GET /matching-runs/:id/suggestions
    • Filters: onlyAssigned, includeNotFound
  2. S15.3: Zombie Matches Cleanup

    • Auto-cancel pending matches older than 30 days
  3. S16.3: Email Reminders

    • Reminder before event for accepted recording assignments

Test Scenarios by Category

S1: BASIC FLOW Implemented

S1.1: Happy path - basic assignment

  • Given: Event with deadline, user A has heat H1, user B no conflict
  • When: Matching runs after deadline
  • Then: Creates suggestion B records A in H1 (pending)

S1.2: Recorder accepts suggestion

  • Given: Pending suggestion: B records A
  • When: B clicks Accept
  • Then: Status→accepted, creates Match (type: auto), both see on /matches

S1.3: Recorder rejects suggestion

  • Given: Pending suggestion: B records A
  • When: B clicks Reject
  • Then: Status→rejected, A sees read-only, next run may assign different recorder
S2: TIME COLLISION DETECTION Implemented

S2.1: Can't record while dancing

  • Given: A has heat H1, B has heat H1 (same slot)
  • Then: B NOT candidate to record A in H1

S2.2: Buffer before - preparation time

  • Given: A has heat H5, B has heat H6 (next)
  • When: HEAT_BUFFER_BEFORE = 1
  • Then: B NOT candidate (needs H5 for prep)

S2.3: Buffer after - rest time

  • Given: A has heat H5, B has heat H4 (previous)
  • When: HEAT_BUFFER_AFTER = 1
  • Then: B NOT candidate (needs H5 for rest)

S2.4: Schedule config - divisions in same slot

  • Given: Schedule config: slot 1 = [Open, Newcomer]; A has Open H3, B has Newcomer H3
  • Then: B can't record A (same slot despite different divisions)
S3: LIMITS & CONSTRAINTS Implemented

S3.1: Max 3 recordings per person

  • Given: B already assigned to record 3 others
  • Then: B NOT candidate for dancer A

S3.2: Opt-out from recording

  • Given: B checked "I don't want to record others"
  • Then: B completely excluded as potential recorder

S3.3: No self-recording

  • Given: A has heat H1
  • Then: A can't be recorder for themselves
S4: FAIRNESS & TIER SYSTEM Implemented (but stats not updating!)

S4.1: BASIC user - normal frequency

  • Given: C (BASIC): done=5, received=0; A,B (BASIC): done=0, received=0
  • Then: C has priority (fairness debt = -5)

S4.2: SUPPORTER user - reduced frequency (-10)

  • Given: A (SUPPORTER) vs B (BASIC), both 0/0
  • Then: B has priority (A has -10 penalty)

S4.3: COMFORT user - significantly reduced (-50)

  • Given: A (COMFORT), B (SUPPORTER), C (BASIC), all 0/0
  • Then: Priority: C > B > A

S4.4: Location preference

  • Given: Dancer A in LA,US; B (LA,US), C (NYC,US), D (London,UK)
  • Then: Priority: B > C > D
S5: INCREMENTAL MATCHING Implemented

S5.1: Accepted suggestions preserved

  • Given: Run 1 created suggestion B→A (pending), B accepted
  • When: Run 2
  • Then: A-B suggestion NOT deleted or overwritten

S5.2: Pending suggestions regenerated

  • Given: Run 1 created B→A (pending), B didn't accept
  • When: Run 2 (new users joined)
  • Then: Pending A-B deleted, new suggestion may assign C→A

S5.3: Accepted suggestions block slots

  • Given: B accepted recording A in heat H5
  • When: Run 2 for dancer D (also has H5)
  • Then: B NOT candidate (busy in H5)
S6: SCHEDULER Implemented

S6.1: Waits until deadline

  • Given: Event has registrationDeadline in 2 hours
  • Then: Scheduler does NOT run matching (waits)

S6.2: Runs after deadline

  • Given: registrationDeadline passed 1 minute ago
  • Then: Runs matching (run 1/5)

S6.3: 5 runs at 5-minute intervals

  • Then: Run 1 (immediately), Run 2 (+5min), Run 3 (+10min), Run 4 (+15min), Run 5 (+20min), STOP

S6.4: Audit trail

  • Given: Matching ran
  • Then: Admin sees table of runs with timestamps and stats (total/pending/accepted/rejected/not_found)
S7: MANUAL vs AUTO MATCHES S7.1-7.4 Implemented

S7.1: Manual match blocks auto suggestion

  • Given: A sent manual match request to B (pending)
  • When: Matching algorithm runs
  • Then: Does NOT create auto suggestion A↔B or B↔A

S7.2: All manual statuses block

  • Given: Manual match A↔B in status: pending/accepted/completed
  • Then: All cases block auto suggestion

S7.3: /matches shows both types with badges

  • Given: A has manual match with B (pending), auto suggestion with C (pending - C records A)
  • Then: A sees 2 items: B (badge "Manual"), C (badge "Auto")

S7.4: Auto pending - redirect to Records

  • Given: A has auto suggestion with C (pending, C records A)
  • Then: Button "Go to Records" → /events/X/chat?tab=records (no Accept/Reject for dancer)
S10: RATINGS & STATS IMPLEMENTED (2025-11-30)

S10.1: Auto match completed → stats updated exactly once

  • Given: Auto Match A↔B (source='auto', statsApplied=false), both rated
  • When: Second rating submitted
  • Then:
    • recordingsDone++ for recorder (user2)
    • recordingsReceived++ for dancer (user1)
    • match.status = 'completed'
    • match.statsApplied = true
  • Implementation: backend/src/routes/matches.js:961-995 (atomic check-and-set)
  • Tests: backend/src/__tests__/ratings-stats-flow.test.js (STEP 6b)

S10.2: Only one rated → no stats

  • Given: Auto Match A↔B, only A rated
  • Then: statsApplied stays false, stats don't change
  • Tests: backend/src/__tests__/ratings-stats-flow.test.js (STEP 6a)

S10.3: Manual match completion → no stats update

  • Given: Match A↔B (source='manual'), both rated
  • Then: Stats don't change (manual matches don't affect fairness)
  • Implementation: backend/src/services/matching.js:682 (early return if source !== 'auto')
  • Tests: backend/src/__tests__/ratings-stats-flow.test.js (STEP 8)

S10.4: Rating edit → no double counting

  • Given: User tries to rate same match twice
  • Then: 400 error "already rated", stats unchanged
  • Implementation: Unique constraint: (matchId, raterId, ratedId)
  • Tests: backend/src/__tests__/ratings-stats-flow.test.js (STEP 7)

Implementation:

// Match model (Prisma schema)
source: String // 'auto' | 'manual'
statsApplied: Boolean @default(false)
suggestionId: Int? // null for manual matches

// Stats application (backend/src/services/matching.js:679-701)
async function applyRecordingStatsForMatch(match) {
  if (match.source !== 'auto') return; // Manual matches ignored

  await prisma.$transaction([
    prisma.user.update({
      where: { id: match.user2Id }, // recorder
      data: { recordingsDone: { increment: 1 } }
    }),
    prisma.user.update({
      where: { id: match.user1Id }, // dancer
      data: { recordingsReceived: { increment: 1 } }
    })
  ]);
}
S11: AUDIT & origin_run_id Partially Implemented

S11.1: New suggestions get correct origin_run_id

  • Given: Run #5 runs matching
  • Then: Each new recording_suggestion has origin_run_id = 5

S11.2: Existing origin_run_id preserved

  • Given: Suggestion S1 from run #3 (accepted/completed)
  • When: Run #4
  • Then: S1 keeps origin_run_id=3 (not deleted)

S11.3: Get suggestions by run 🔴 NOT IMPLEMENTED

  • Need: GET /matching-runs/:id/suggestions
  • Then: Returns only suggestions with origin_run_id=:id

S11.4: Filters work correctly 🔴 NOT IMPLEMENTED

  • Need: Query params onlyAssigned, includeNotFound
  • Then: Filters recorder presence and NOT_FOUND status
S12: MULTIPLE HEATS & COMPLEX COLLISIONS Implemented

S12.1: Same dancer in multiple heats

  • Given: Dancer A in H10 and H12 (Novice J&J), B no conflict
  • Then: B can record A in both heats (if under limit 3)

S12.2: Recorder records multiple dancers in different slots

  • Given: Recorder B has zero own heats, A has H5 (slot 2), C has H8 (slot 3)
  • Then: B can be assigned to both A and C (different slots)

S12.3: Same recorder can't record two people in same slot

  • Given: B accepted recording A in Novice H10 (slot 2), D has Intermediate H10 (also slot 2)
  • When: Next matching run for D
  • Then: B excluded (busySlot recording A)
S13: MANUAL vs AUTO - FURTHER INTERACTIONS ⚠️ Edge Case

S13.1: Manual before matching DONE

  • See S7.1

S13.2: Manual created AFTER auto suggestion ⚠️ EDGE CASE

  • Given: Run #1 created auto suggestion A↔B (pending), then user A creates manual match with B
  • When: Run #2
  • Then:
    • Current behavior: Manual blocks NEW auto suggestions, old pending remains
    • Recommended: Cleanup conflicting pending auto suggestions when manual created

Recommended implementation:

// routes/matches.js - POST /
// After creating manual match:
await prisma.recordingSuggestion.deleteMany({
  where: {
    eventId,
    OR: [
      { heatId: { in: user1HeatIds }, recorderId: user2Id },
      { heatId: { in: user2HeatIds }, recorderId: user1Id },
    ],
    status: 'pending', // only pending, don't touch accepted
  }
});
S14: PERMISSIONS & VISIBILITY ⚠️ Partially Implemented

S14.1: Only recorder can update status

  • Given: Suggestion B records A (pending), A tries PUT /suggestions/:id/status
  • Then: 403 Forbidden

S14.2: Admin-only endpoints 🔴 NOT SECURE

  • Given: Regular user tries POST /admin/events/:slug/run-now or GET /admin/matching-runs
  • Then: Should get 403, but middleware missing!

Need to implement:

// middleware/auth.js
const requireAdmin = (req, res, next) => {
  if (req.user.role !== 'admin') {
    return res.status(403).json({ error: 'Admin access required' });
  }
  next();
};

S14.3: Event participant visibility ⚠️ INCONSISTENT

  • Need to audit: Do all suggestion/match endpoints verify user is event participant?
  • Locations to check:
    • GET /api/matches?eventSlug=X
    • GET /api/events/:slug/matching/suggestions
    • Frontend: /events/:slug/chat?tab=records
S15: SECURITY & ABUSE PREVENTION 🔴 NOT IMPLEMENTED

S15.1: Rate limiting on manual match requests

  • Given: User A sends 100 manual requests in 1 minute
  • Then: 429 Too Many Requests after 10 requests/minute

S15.2: Max pending requests limit

  • Given: User A has 20 pending outgoing requests
  • Then: Can't send more (limit 20)

S15.3: Zombie matches cleanup

  • Given: Pending match older than 30 days
  • Then: Auto-cancelled by cron job
S16: NOTIFICATIONS & REAL-TIME 🔴 NOT IMPLEMENTED

S16.1: Socket event for new suggestion

  • Given: Matching created suggestion B records A, B is online
  • Then: B receives socket event → toast notification

S16.2: Push notification for offline users

  • Given: B offline when suggestion created
  • Then: Email/push: "You have a new recording assignment"

S16.3: Reminder before event

  • Given: B accepted recording A, event in 2 days
  • Then: Email: "Don't forget to record @userA on Saturday"

Edge Cases for Discussion

E1: User has 10 heats, no one can record

  • Question: Show all as NOT_FOUND? Prioritize which heats are most important?

E2: Recorder wants to un-accept

  • Current: No "un-accept" option
  • Question: Add cancel feature for accepted suggestions?

E3: Manual match rejected, then matching runs

  • Current: Rejected manual still blocks auto suggestions
  • Question: Should rejected manual stop blocking?

E4: User joins AFTER all 5 scheduler runs

  • Current: Won't get auto suggestions (scheduler stopped)
  • Question: Allow admin to manually trigger additional run?

E5: Timezone for deadline

  • Current: Server timezone
  • Question: Use event/organizer timezone?

E6: Load balancing - one recorder has best scores

  • Current: Can get max 3 assignments
  • Question: Distribute more evenly even if scores worse?

E7: Dancer rejects auto suggestion

  • Current: Dancer CAN'T reject (only recorder in MVP)
  • Question: Allow dancers to reject in future?

E8: Event without schedule config

  • Current: Fallback to division-based slots
  • Question: Show admin warning that matching may be less efficient?

E9: Manual match created AFTER auto suggestion exists

  • See S13.2 above

E10: User joins BETWEEN scheduler runs

  • Current: Works - each run regenerates pending suggestions
  • Status: No issue

E11: Recorder accepted but didn't actually record

  • Question: Timeout (7 days after event → expired)? "Report issue" button? Admin override?

Priority Ranking

P0 - CRITICAL (Before Production)

  1. Manual blocks auto suggestions (DONE 2025-11-30)
  2. 🔴 Implement ratings and stats system (S10 - fairness broken without this!)
  3. 🔴 Admin middleware (S14.2 - security)
  4. 🔴 Event participant validation (S14.3 - security)

P1 - HIGH (First Month of Production)

  1. ⚠️ Manual match cleanup on conflict (S13.2 / E9)
  2. ⚠️ Rate limiting and spam protection (S15.1, S15.2)
  3. ⚠️ Socket notifications for new suggestions (S16.1)

P2 - MEDIUM (Q1 2025)

  1. 📋 Endpoint /matching-runs/:id/suggestions with filters (S11.3, S11.4)
  2. 📋 Zombie matches cleanup (S15.3)
  3. 📋 Email reminders (S16.3)

P3 - LOW (Nice to Have)

  1. 📋 Report issue for dancers (E11)
  2. 📋 Admin manual override for suggestions
  3. 📋 Analytics dashboard (success rate, acceptance rate)

Questions for Team

  1. Is ratings system in roadmap? If yes - THIS IS THE MOST IMPORTANT (fairness is broken without it)
  2. Do we have existing admin middleware? If not - must add ASAP for security
  3. Which edge case (E1-E11) is most likely in production?
  4. Should we implement S13.2 cleanup before launch? (manual match after auto suggestion)

Security Audit Findings

Critical Issues (Must Fix Before Production)

Issue Severity File Action
AWS Credentials in Git 10/10 .env.production Rotate & remove from history
Weak JWT Secret 9/10 .env.production Generate 64+ byte secret
Default DB Password 8/10 docker-compose.yml Remove default fallback

High Priority Issues

Issue Severity Action
Missing HTTPS/TLS 8/10 Configure SSL certificate
Missing nginx security headers 6/10 Add X-Frame-Options, CSP, etc.
Dependency vulnerabilities HIGH Run npm audit fix
Excessive body size (500M) 4/10 Reduce to 10M

Positive Security Findings

  • Strong authentication (JWT, bcrypt, email verification)
  • Input validation (express-validator)
  • Security headers (Helmet.js)
  • Rate limiting implemented
  • CORS configured
  • SQL injection prevention (Prisma)
  • Account lockout implemented
  • WebRTC P2P (videos don't touch server)

Pre-Deployment Checklist

  • Rotate AWS credentials, remove from Git history
  • Generate strong JWT_SECRET (64+ bytes)
  • Set strong PostgreSQL password
  • Configure HTTPS/TLS
  • Add nginx security headers
  • Run npm audit fix
  • Reduce nginx body size limit

Future Improvements (Optional)

UX Enhancements

  • Activity Feed (timeline of user activities)
  • Smart sort order (unread first, pending ratings)
  • User statistics (total matches, average rating)
  • Sidebar filters (by nationality, division)

Security Hardening

  • Increase bcrypt rounds (10 → 12)
  • Implement refresh token pattern
  • Add Socket.IO rate limiting
  • Sanitize chat messages

Infrastructure

  • CI/CD pipeline (GitHub Actions)
  • E2E tests (Playwright)
  • Security logging

Progress Summary

Phase Status Time
Phase 0: Frontend Mockup Done ~8h
Phase 1: Backend Foundation Done ~14h
Phase 1.5: Email & WSDC & Profiles Done ~12h
Phase 1.6: Competition Heats Done ~8h
Phase 2: Matches & Ratings Done ~10h
Phase 2.5: WebRTC P2P Done ~10h
Phase 3: MVP Finalization Done ~20h
Total MVP Complete ~82h

Quick Commands

# Development
docker compose up --build

# Tests
docker compose exec backend npm test

# Access
http://localhost:8080

Last Updated: 2025-11-29 Full Details: See docs/archive/COMPLETED.md for implementation details