Commit Graph

82 Commits

Author SHA1 Message Date
Radosław Gierwiało
dd3176196e fix(chat): real-time active users list updates
Fixed issue where active users list in event chat did not update in
real-time when new users joined. Users had to refresh the page to see
newly joined participants.

Root Cause:
- getAllDisplayUsers() used checkedInUsers (loaded once from API) as
  base list, with activeUsers (Socket.IO real-time) only for isOnline flag
- When new user joined chat, they appeared in activeUsers but not in
  checkedInUsers, so they were not displayed

Solution:
- Rewrote getAllDisplayUsers() to prioritize activeUsers (real-time data)
- Merges activeUsers (online) with checkedInUsers (offline checked-in users)
- Uses Socket.IO data as source of truth for online users
- Enriches with database data when available (firstName, lastName, etc)
- Sorts online users first, offline second

Changes:
- EventChatPage.jsx: Rewrote getAllDisplayUsers() to merge activeUsers
  with checkedInUsers, prioritizing real-time Socket.IO data
- useEventChat.js: Added debug logging for active_users events
- socket/index.js: Added debug logging for active_users emissions

Testing:
- User A in chat sees User B appear immediately when B joins
- No page refresh required
- Online/offline status updates in real-time
2025-12-02 23:38:46 +01:00
Radosław Gierwiało
d641e3f059 feat(activity-log): add Socket.IO real-time streaming (Phase 5)
Added socket handlers for admin activity log streaming:

Socket Events:
- join_admin_activity_logs: Admin joins streaming room
  - Verifies isAdmin flag from database
  - Rejects non-admin users with warning log
  - Emits confirmation: admin_activity_logs_joined

- leave_admin_activity_logs: Admin leaves streaming room
  - Clean disconnect from room

Real-time Flow:
1. Admin connects and emits join_admin_activity_logs
2. Server verifies admin status (fresh DB check)
3. Admin joins 'admin_activity_logs' Socket.IO room
4. activityLog.log() emits activity_log_entry to room
5. Admin receives real-time log entries as they happen

The activityLog service (Phase 2) already emits events,
so streaming is fully functional with these handlers.
2025-12-02 20:10:47 +01:00
Radosław Gierwiało
4dd6603018 feat(activity-log): add admin API endpoints (Phase 4)
Added 3 admin-only endpoints for activity log management:

1. GET /api/admin/activity-logs
   - Query logs with comprehensive filtering
   - Supports: date range, action, category, username, userId, success
   - Pagination with limit/offset
   - Logs ADMIN_VIEW_LOGS action
   - Returns: logs array, total count, pagination info

2. GET /api/admin/activity-logs/actions
   - Get list of unique action types
   - Returns: action names and categories
   - Useful for filter dropdowns in UI

3. GET /api/admin/activity-logs/stats
   - Dashboard statistics
   - Returns: total logs, unique users, failed actions,
     success rate, logs by category, recent activity (24h)

All endpoints protected with authenticate + requireAdmin middleware.
2025-12-02 20:09:49 +01:00
Radosław Gierwiało
d83e4163e5 feat(activity-log): integrate logging across all endpoints (Phase 3)
Added comprehensive activity logging to 14 integration points:

Auth Controller (4 actions):
- AUTH_REGISTER: User registration
- AUTH_LOGIN: User login
- AUTH_VERIFY_EMAIL: Email verification (token & code)
- AUTH_PASSWORD_RESET: Password reset

Events Routes (2 actions):
- EVENT_CHECKIN: User checks into event
- EVENT_LEAVE: User leaves event

Socket Handlers (3 actions):
- EVENT_JOIN_CHAT: User joins event chat room
- EVENT_LEAVE_CHAT: User leaves event chat room
- CHAT_JOIN_ROOM: User joins private match chat

Matches Routes (3 actions):
- MATCH_CREATE: Match request created
- MATCH_ACCEPT: Match request accepted
- MATCH_REJECT: Match request rejected/cancelled

Admin Routes (1 action + security):
- ADMIN_MATCHING_RUN: Admin runs matching algorithm
- Added requireAdmin middleware to all admin routes

All logs include:
- User ID and username (denormalized)
- IP address (X-Forwarded-For aware)
- Action type and resource ID
- HTTP method and path (or SOCKET for WebSocket)
- Contextual metadata (event slugs, match IDs, etc.)

Fire-and-forget pattern ensures logging never blocks requests.
2025-12-02 20:07:10 +01:00
Radosław Gierwiało
c9beee9a4e feat(admin): add Activity Log backend services (Phase 2)
Core services for activity logging system:

1. ActivityLog Service (backend/src/services/activityLog.js)
   - Centralized logging with fire-and-forget pattern
   - 18 action constants (auth, event, match, admin, chat)
   - Query interface with filtering (date, action, user, category)
   - Socket.IO emission for real-time streaming
   - Statistics and action types endpoints
   - Never throws - logging cannot crash app

2. Request Utility (backend/src/utils/request.js)
   - getClientIP() - Extract client IP from headers/socket
   - Handles X-Forwarded-For and X-Real-IP proxy headers
   - IPv6-mapped IPv4 conversion

3. Admin Middleware (backend/src/middleware/admin.js)
   - requireAdmin() - Protect admin routes
   - Fresh DB check of isAdmin flag
   - Returns 403 for non-admin users
   - Use after authenticate middleware

Next phases: logging integration points, API endpoints, frontend UI
2025-12-02 19:47:47 +01:00
Radosław Gierwiało
b77ccab9d4 refactor(emails): restructure email system and add recording notifications
- Move email templates to separate files in src/emails/templates/
- Create new email service architecture (service.js, index.js)
- Add recording suggestions email template for matching notifications
- Integrate email notifications with matching system (sends when suggestions created)
- Update controllers (auth.js, user.js) to use new email module
- Update tests to use new email module path
- Remove deprecated src/utils/email.js

Features:
- Template-based email system for easy editing
- Automatic email notifications when recording assignments are made
- Clean separation between template logic and sending logic
- Graceful error handling for AWS SES failures
2025-12-02 19:19:22 +01:00
Radosław Gierwiało
ec659d83e8 feat(matches): implement spam protection and socket notifications
S15.1-15.2: Rate Limiting & Spam Protection
- Add max 20 pending outgoing match requests limit
- Implement rate limiter: 10 match requests per minute per user
- Return 429 status with clear error messages

S16.1: Socket Notifications for New Suggestions
- Emit 'recording_suggestions_created' event when matching creates suggestions
- Notify only assigned recorders (not NOT_FOUND status)
- Group suggestions by recorder for efficiency
- Include event details and suggestion count

Implementation:
- backend/src/routes/matches.js: Rate limiter + pending limit check
- backend/src/services/matching.js: Socket notifications in saveMatchingResults
- backend/src/__tests__/spam-protection-notifications.test.js: 8 test cases

Test coverage:
- TC1-TC3: Max pending requests (spam protection)
- TC4-TC5: Rate limiting (10/min)
- TC6-TC8: Socket notifications for new suggestions
2025-12-01 00:03:46 +01:00
Radosław Gierwiało
bd7212a599 feat(matching): implement origin_run_id tracking and audit tests
- Updated run-matching endpoint to create MatchingRun records
- Added trigger tracking (manual vs scheduler)
- Fixed saveMatchingResults to accept runId parameter
- Added comprehensive audit tests (matching-runs-audit.test.js):
  * TC1: origin_run_id assigned correctly
  * TC2: Sequential runs create separate run IDs
  * TC3: Accepted suggestions preserve origin_run_id
  * TC4: Filter parameters (onlyAssigned, includeNotFound)
  * TC5: Manual vs scheduler trigger differentiation
  * TC6: Failed runs recorded in audit trail
- All 6 audit tests passing
2025-11-30 20:01:10 +01:00
Radosław Gierwiało
f13853c300 fix(tests): correct socket test to use nested user.username field
- Updated event_message test to expect message.user.username
- Matches current socket.io implementation which nests user data
- All 342 tests now passing (100%)
2025-11-30 19:44:32 +01:00
Radosław Gierwiało
72275835f1 test(matching): add comprehensive integration tests for matching algorithm
- Implement all 19 test scenarios from matching-scenarios.md
- Phase 1: Fundamentals (TC1-3) - basic flow and NOT_FOUND cases
- Phase 2: Collision Detection (TC4-9) - heat buffers and slot mapping
- Phase 3: Limits & Workload (TC10-11) - MAX_RECORDINGS and collision bugs
- Phase 4: Fairness & Tiers (TC12-16) - debt calculation and tier penalties
- Phase 5: Edge Cases (TC17-19) - multiple heats and incremental matching
- All 19 tests passing with 76.92% coverage on matching.js
2025-11-30 19:37:36 +01:00
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
Radosław Gierwiało
25236222de feat(matching): prevent auto suggestions when manual match exists + comprehensive test scenarios
Matching Service:
- Fetch manual matches at start of runMatching() (suggestionId: null)
- Build manualBlockedPairs set with both directions (A:B and B:A)
- Skip recorder candidates if manual match exists between dancer and recorder
- Ensures no duplicate matches on /matches page
- Manual match = user-controlled, algorithm respects user decisions

Documentation (docs/TODO.md):
- Add comprehensive matching system test scenarios (S1-S16)
- Document 4 critical gaps (P0): ratings/stats, admin middleware, participant validation
- Document 3 high priority items (P1): cleanup conflicts, rate limiting, notifications
- Document 6 medium priority items (P2): audit endpoints, zombie cleanup, reminders
- List 11 edge cases for team discussion (E1-E11)
- Clear priority ranking and questions for team

Critical Findings:
- recordingsDone/recordingsReceived fields exist but NEVER updated (fairness broken!)
- Admin endpoints lack security middleware
- Inconsistent event participant validation across endpoints

Test Coverage:
- S1-S7: Implemented (basic flow, collisions, limits, manual vs auto)
- S10: NOT IMPLEMENTED - ratings/stats system (CRITICAL!)
- S11: Partially implemented - audit trail exists, API endpoints missing
- S14: Partially implemented - recorder-only auth works, admin middleware missing
- S15-S16: NOT IMPLEMENTED - security, notifications
2025-11-30 15:53:00 +01:00
Radosław Gierwiało
f45cadae7d feat(matches): show both manual match requests and auto recording suggestions
Backend:
- Extend GET /api/matches to include RecordingSuggestions alongside Match objects
- Add 'type' field: 'manual' for user-created matches, 'auto' for algorithm suggestions
- Fetch suggestions where user is dancer (to be recorded) or recorder (recording others)
- Transform suggestions to match format with partner info
- Support status filtering for both types

Frontend:
- Display 'Auto' (purple) or 'Manual' (gray) badge on match cards
- For pending auto suggestions: show 'Go to Records' button instead of Accept/Reject
- For accepted auto suggestions without slug: show 'Chat not available yet'
- Only allow Accept/Reject actions on manual match requests
2025-11-30 15:30:49 +01:00
Radosław Gierwiało
d8799d03af feat(dashboard): add Recording Assignments section
- Extend dashboard API to include recordingSuggestions for each event
- Add toBeRecorded and toRecord arrays with heat and user details
- Export RecordingSummaryCard component
- Add Recording Assignments section to DashboardPage
- Filter and display events with recording suggestions
- Show up to 2 suggestions per event with View Details link
2025-11-30 15:14:06 +01:00
Radosław Gierwiało
2e49fa5c62 feat(recordings): only recorder can accept/reject suggestions in MVP
Backend changes:
- Restrict suggestion status updates to recorder only
- Dancers can now only view who is assigned to record them
- Return 403 error if non-recorder tries to update status

Frontend changes:
- Remove Accept/Reject buttons from dancer view (TO_BE_RECORDED)
- Add "Pending" status badge with clock icon for pending suggestions
- Keep Accept/Reject buttons for recorder view (TO_RECORD)
- Dancers see only status badge and optional chat button

UX flow:
- Dancer sees: "Recording you: @username [Pending]"
- Recorder sees: "You record: @username [Accept] [Reject]"
- Only recorder's action creates the Match
2025-11-30 14:54:09 +01:00
Radosław Gierwiało
560ff1edc1 fix(scheduler): implement deadline-based matching with 5-run limit and fix security issues
Security fixes:
- Replace $queryRawUnsafe with parameterized $queryRaw in admin.js to prevent SQL injection
- Use PostgreSQL ANY() operator for safe array parameter handling

Scheduler improvements:
- Add registrationDeadline support - scheduler now waits until deadline before running
- Implement 5-run limit after deadline (runs exactly 5 times with 5-minute intervals)
- Add countScheduledRunsAfterDeadline() to track post-deadline runs
- Add environment variable validation with sensible min/max ranges
- Fix Prisma query syntax (remove invalid endDate null check for non-nullable field)

UI improvements:
- Fix colspan mismatch in MatchingRunsSection (6 → 8 columns)
- Remove duplicate "Uruchom Matching" button, keep only "Run now" with audit tracking
- Simplify MatchingConfigSection to focus on deadline configuration

Logging enhancements:
- Add detailed scheduler logs showing run progress (e.g., "Running post-deadline matching (3/5)")
- Log wait times before deadline and between runs
- Show completion status after 5 runs
2025-11-30 14:42:08 +01:00
Radosław Gierwiało
752d65035a fix(scheduler): use equals: null filter for endDate to satisfy Prisma where syntax and stop log spam 2025-11-30 13:50:48 +01:00
Radosław Gierwiało
621511fccf feat(matching-runs): add per-run aggregate stats and UI display
- Admin list endpoint returns totalSuggestions, assignedCount, aggregatedNotFoundCount per run
- UI: show Total/Matched/Not found columns using fresh aggregates
- Add anchor link Run #ID and wording 'Pairs created in this run'
2025-11-30 13:43:05 +01:00
Radosław Gierwiało
a9ad25eb38 feat(matching-runs): attach origin_run_id to new suggestions and expose pairs-per-run API
- Extend saveMatchingResults(eventId, suggestions, runId) and set originRunId
- Scheduler/Admin run-now: always pass runId
- Admin API: GET /api/admin/events/:slug/matching-runs/:runId/suggestions
- Prisma: add compound index on (origin_run_id, status)
- Frontend: add getRunSuggestions, expand row in MatchingRunsSection with 'Pairs created in this run' wording
2025-11-30 13:37:32 +01:00
Radosław Gierwiało
537dd112ff feat(scheduler): in-process matching scheduler with audit + admin endpoints
- Add in-process scheduler service triggered by ENABLE_SCHEDULER
- Record runs in new matching_runs table; throttle per-event and log stats
- Add admin endpoints: POST /api/admin/events/:slug/run-now and GET /api/admin/events/:slug/matching-runs
- Wire scheduler start/stop in server and add ENV flags + compose defaults
- Prisma schema: add MatchingRun model and relation
- Update env examples for scheduler configuration
2025-11-30 13:14:02 +01:00
Radosław Gierwiało
a110ddb6a6 feat: implement incremental matching to preserve accepted suggestions
Phase 1 implementation of intelligent rebalancing that preserves
accepted/completed suggestions when rerunning matching algorithm.

**saveMatchingResults changes:**
- Delete only non-committed suggestions (status notIn ['accepted', 'completed'])
- Future-proof: any new statuses (expired, cancelled) auto-cleaned
- Filter out heats that already have accepted/completed suggestions
- Only create new suggestions for unmatched heats

**runMatching changes:**
- Build heatById map for efficient lookup
- Fetch existing accepted/completed suggestions before matching
- Initialize recorderAssignmentCount with accepted assignments
  * Prevents exceeding MAX_RECORDINGS_PER_PERSON
  * Treats accepted suggestions as if created in current run
- Initialize recorderBusySlots with accepted heat slots
  * Prevents slot collisions (two dancers in same time slot)
  * Respects existing recorder commitments
- Skip heats that already have accepted recorders
  * Avoids duplicate suggestions for matched heats

**Integration tests:**
- Phase 1: Preserve accepted suggestions on rerun (3 tests)
  * Verify initial suggestions created
  * Accept suggestion and verify match created
  * Rerun matching and verify accepted preserved, others regenerated
- Phase 2 & 3: Skipped (TODO for future)

**Results:**
- 307/308 tests passing (up from 304)
- No regressions
- Fixes critical bugs:
  * Orphaned matches when rerunning
  * Exceeding recorder limits
  * Slot double-booking
2025-11-30 11:26:43 +01:00
Radosław Gierwiało
8c753a7148 feat: add match data to suggestions and chat link for accepted recordings
Backend changes:
- Modified getUserSuggestions to include match data (id, slug, status)
- Returns match info for both toBeRecorded and toRecord suggestions

Frontend changes:
- Added useNavigate hook to RecordingTab
- Capture match data from updateSuggestionStatus response
- Added MessageCircle icon and chat button to SuggestionCard
- Show "Open Chat" button for accepted suggestions with active matches
- Navigate to /matches/{matchSlug}/chat when clicked

This completes the recording stats flow by allowing users to easily
access the match chat after accepting a recording suggestion.
2025-11-30 11:03:29 +01:00
Radosław Gierwiało
3371b53fc7 refactor: add atomic operations and documentation for recording stats edge cases
Fix race conditions and edge cases in recording stats update mechanism:

1. Race condition prevention:
   - Use atomic updateMany with statsApplied=false condition in rating endpoint
   - Prevents duplicate stats increments when both users rate concurrently
   - Only one request wins the race and applies stats (matches.js:834-843)

2. Multiple heats handling:
   - Check for existing Match by (user1Id, user2Id, eventId) instead of suggestionId
   - Ensures one Match per dancer-recorder pair regardless of number of heats
   - Reuses existing Match and chat room (events.js:1275-1291)

3. Documentation improvements:
   - Add comprehensive JSDoc explaining manual vs auto-match design decision
   - Clarify fairness metrics measure algorithmic assignments, not voluntary collaborations
   - Document user role convention (user1=dancer, user2=recorder)

Edge cases are verified through atomic operations and code review rather than
complex integration tests to maintain test clarity and reliability.

Test Results: 304/305 tests passing (99.7%)
Coverage: 74.63% (+0.1%)
2025-11-30 10:49:56 +01:00
Radosław Gierwiało
145c9f7ce6 feat: implement recording stats update mechanism for auto-matching
Add automatic tracking of recording statistics (recordingsDone/recordingsReceived)
for users participating in auto-matched collaborations. Stats are updated when
both users complete mutual ratings after a recording session.

Changes:
- Add suggestionId, source, and statsApplied fields to Match model
- Implement applyRecordingStatsForMatch() helper with user role convention
  (user1 = dancer, user2 = recorder)
- Update suggestion status endpoint to create Match on acceptance
- Update ratings endpoint to apply stats when match is completed
- Add comprehensive unit tests (5) and integration tests (5)

Convention: Stats only updated for auto-matches (source='auto') to ensure
fairness metrics reflect actual algorithmic assignments, not manual matches.

Test Results: 304/305 tests passing (99.7%)
Coverage: 74.53% (+1.48%)
2025-11-30 10:40:43 +01:00
Radosław Gierwiało
6965a2f7cd docs(tests): add comprehensive test plan for matching integration tests 2025-11-29 23:59:29 +01:00
Radosław Gierwiało
ce10d20cbb test(matching): add comprehensive tests for buffer functions and edge cases
- Export getPostDanceBufferSlots and HEAT_BUFFER_AFTER for testing
- Add direct tests for getPreDanceBufferSlots (4 tests)
- Add direct tests for getPostDanceBufferSlots (2 tests)
- Add test for HEAT_BUFFER_AFTER constant
- Add edge case for getCoverableHeats with multiple recorder heats
- Add edge case for hasCollision with multi-heat scenarios
- Total: 39/39 tests passing (9 new tests added)
2025-11-29 23:52:12 +01:00
Radosław Gierwiało
aef1a35ee2 feat(matching): implement 3-tier account system with fairness-based recording assignment
Add account tier system (BASIC/SUPPORTER/COMFORT) to reduce recording burden
for premium users while maintaining fairness through karma-based assignment.

Database Changes:
- Add AccountTier enum (BASIC, SUPPORTER, COMFORT)
- Add User.accountTier with BASIC default
- Add User.recordingsDone and User.recordingsReceived for karma tracking
- Add EventParticipant.accountTierOverride for event-specific tier upgrades
- Migration: 20251129220604_add_account_tiers_and_recording_stats

Matching Algorithm Updates:
- Implement fairness debt calculation: receivedCount - doneCount
- Apply tier penalties: SUPPORTER (-10), COMFORT (-50)
- New sorting priority: Location > Fairness > Load balancing
- Add getEffectiveTier() helper for tier resolution with override support
- Add getRecordingStatsForUsers() for fetching karma statistics

Tier Behavior:
- BASIC: Normal recording frequency (baseline, no penalty)
- SUPPORTER: Moderately reduced frequency (fairness penalty -10)
- COMFORT: Significantly reduced frequency (fairness penalty -50)
- All tiers can still be assigned when no better candidates available

Constants:
- ACCOUNT_TIER enum in src/constants/tiers.js
- FAIRNESS_SUPPORTER_PENALTY = 10
- FAIRNESS_COMFORT_PENALTY = 50

Tests:
- Update tests for dual buffer system semantics
- All 30 tests passing
- Fix imports: HEAT_BUFFER → HEAT_BUFFER_BEFORE
2025-11-29 23:19:41 +01:00
Radosław Gierwiało
029b25c9b2 fix(matching): improve collision detection and load balancing
- Add dual buffer system: BEFORE (prep) and AFTER (rest) dancing
- Track recording assignments to prevent double-booking recorders
- Fix sorting priority: location score takes precedence over load balancing
- Simplify opt-out logic with complete exclusion from matching pool
- Buffers apply only to dancing heats, not recording assignments
- Improve documentation clarity for algorithm constraints
2025-11-29 21:42:22 +01:00
Radosław Gierwiało
4e9557bd29 feat(chat): add country flags and competitor numbers with normalized data architecture
Implemented display of country flags and competitor numbers in event chat messages:
- Country flags displayed as emoji (🇸🇪, 🇵🇱, etc.) with proper emoji font support
- Competitor numbers shown in #123 format next to usernames
- Normalized data architecture with user and participant caches on frontend
- User data (username, avatar, country) and participant data (competitorNumber) cached separately
- Messages store only core data (id, content, userId, createdAt)
- Prevents data inconsistency when users update profile information
- Fixed duplicate message keys React warning with deduplication logic
- Backend sends nested user/participant objects for cache population
- Auto-updates across all messages when user changes avatar or country

Backend changes:
- Socket.IO event_message and message_history include nested user/participant data
- API /events/:slug/messages endpoint restructured with same nested format
- Batch lookup of competitor numbers for efficiency

Frontend changes:
- useEventChat hook maintains userCache and participantCache
- ChatMessage component accepts separate user/participant props
- ChatMessageList performs cache lookups during render
- Emoji font family support for cross-platform flag rendering
2025-11-29 19:49:06 +01:00
Radosław Gierwiało
6f7465ee5a fix(tests): add wsdcId cleanup to prevent unique constraint violations
Both users.test.js and auth-phase1.5.test.js were failing due to
unique constraint violations on wsdc_id field when running after
other test suites. Added wsdcId to cleanup queries and proper
deletion of related data (messages, matches, eventParticipants).
2025-11-23 22:59:06 +01:00
Radosław Gierwiało
0ca79b6c7d refactor(backend): add status constants and update code to use them
- Create constants/statuses.js with MATCH_STATUS, SUGGESTION_STATUS
- Update routes/dashboard.js to use MATCH_STATUS
- Update routes/matches.js to use MATCH_STATUS
- Update routes/events.js to use SUGGESTION_STATUS
- Update services/matching.js to use SUGGESTION_STATUS
- Update tests to use constants
2025-11-23 22:40:54 +01:00
Radosław Gierwiało
4467c570b0 feat(matching): add schedule config for division collision groups
Allow event organizers to configure which divisions run in parallel
(same time slot) for accurate collision detection in the auto-matching
algorithm. Divisions in the same slot will collide with each other.

- Add scheduleConfig JSON field to Event model
- Add PUT /events/:slug/schedule-config API endpoint
- Update matching algorithm to use slot-based collision detection
- Add UI in EventDetailsPage for managing division slots
- Add unit tests for schedule-based collision detection
2025-11-23 19:05:25 +01:00
Radosław Gierwiało
a5a1296a4e feat(frontend): add recording matching UI
Add frontend components for auto-matching recording partners:

- RecordingTab component with suggestions list and opt-out toggle
- Tab navigation in EventChatPage (Chat, Uczestnicy, Nagrywanie)
- Matching configuration in EventDetailsPage (deadline, run matching)
- matchingAPI functions in api.js
- Return registrationDeadline and matchingRunAt in GET /events/:slug/details

UI allows users to:
- View who will record their heats
- View heats they need to record
- Accept/reject suggestions
- Opt-out from being a recorder
- Set registration deadline (admin)
- Manually trigger matching (admin)
2025-11-23 18:50:35 +01:00
Radosław Gierwiało
c18416ad6f feat(matching): add auto-matching system for recording partners
Implement algorithm to match dancers with recorders based on:
- Heat collision avoidance (division + competitionType + heatNumber)
- Buffer time (1 heat after dancing before can record)
- Location preference (same city > same country > anyone)
- Max 3 recordings per person
- Opt-out support (falls to bottom of queue)

New API endpoints:
- PUT /events/:slug/registration-deadline
- PUT /events/:slug/recorder-opt-out
- POST /events/:slug/run-matching
- GET /events/:slug/match-suggestions
- PUT /events/:slug/match-suggestions/:id/status

Database changes:
- Event: registrationDeadline, matchingRunAt
- EventParticipant: recorderOptOut
- RecordingSuggestion: new model for match suggestions
2025-11-23 18:32:14 +01:00
Radosław Gierwiało
edf68f2489 feat(events): add competitor number (bib) support
Allow participants to set their bib/competitor number per event.
Display as badge next to username in participant lists.

- Add competitorNumber field to EventParticipant model
- Add PUT /events/:slug/competitor-number endpoint
- Include competitorNumber in heats/me and heats/all responses
- Add input field in HeatsBanner component
- Display badge in UserListItem component
- Add unit tests for competitor number feature
2025-11-23 17:55:25 +01:00
Radosław Gierwiało
8a369c1fc4 fix(socket): improve connection stability with heartbeat and auto-reconnect
- Backend: Add pingInterval (25s) and pingTimeout (60s) for better keep-alive
- Frontend: Increase reconnection attempts to Infinity (keep trying forever)
- Frontend: Add reconnect event handlers to rejoin rooms after reconnection
- Frontend: Check initial connection state when reusing socket instance
2025-11-21 21:53:51 +01:00
Radosław Gierwiało
78280ca8d8 feat(dashboard): add unread count for match chats
Track unread messages in match chats and display count badge:
- Schema: Add user1LastReadAt/user2LastReadAt to Match model
- Backend: Calculate unreadCount in dashboard API
- Socket: Update lastReadAt when user joins match room
- Frontend: Display red badge with unread count on match avatar
2025-11-21 21:46:00 +01:00
Radosław Gierwiało
2c0620db6a feat(dashboard): add online count for events
Show real-time count of users currently in each event chat room.
- Backend: Export getEventsOnlineCounts from socket module
- Dashboard API: Include onlineCount for each active event
- Frontend: Display online count with animated green dot indicator
2025-11-21 21:41:16 +01:00
Radosław Gierwiało
901b046a34 feat(backend): implement dashboard API endpoint
- Add GET /api/dashboard endpoint for authenticated users
- Returns active events with user heats
- Returns accepted matches with partner info
- Detects video exchange status from message parsing
- Tracks rating completion status (rated by me/partner)
- Returns incoming/outgoing pending match requests
- Add comprehensive test suite (12 tests, 93% coverage)
- Add DASHBOARD_PLAN.md with full design documentation
2025-11-21 21:00:50 +01:00
Radosław Gierwiało
198c216b44 fix(backend): auto-create event ChatRoom on first check-in
Problem:
- User got "Chat room not found" error when trying to send messages
- Event ChatRooms were only created by seed script, not for manually
  created events
- Event "Another Dance Event" (ID: 420) was missing its ChatRoom

Root Cause:
- Seed script (seed.js:179-188) correctly creates ChatRooms for events
- But events created outside of seed (CLI, manual DB insert) didn't
  create ChatRooms
- Socket handler requires ChatRoom to exist before accepting messages

Solution:
1. Added defensive check in check-in handler (POST /api/events/checkin/:token)
2. Automatically creates ChatRoom if missing when first user checks in
3. Logs creation for debugging: "Created missing chat room for event: {slug}"

Impact:
- Existing events without ChatRooms will get them on next check-in
- Future manually-created events will work correctly
- No breaking changes - all 223 tests pass

Changes:
- backend/src/routes/events.js: Added ChatRoom existence check and
  auto-creation logic (lines 256-272)

Note: Manually created ChatRoom for event ID 420 to fix immediate issue
2025-11-21 17:34:17 +01:00
Radosław Gierwiało
688f71343d test: fix test isolation by using unique test data per suite
- Add unique prefixes to test usernames (users_, matches_, events_)
- Add unique prefixes to test emails to prevent conflicts
- Add unique prefixes to event slugs and worldsdc_id values
- This prevents race conditions when Jest runs tests in parallel

Results:
- All 223 tests now passing (was 145/223)
- 14/14 test suites passing (was 11/14)
- Code coverage improved to 71.31% (from ~45%)

Fixes:
- users.test.js: Changed john_dancer → users_john_dancer
- matches.test.js: Changed to matches_ prefix
- events.test.js: Changed to events_ prefix + unique worldsdc_id
2025-11-20 22:12:09 +01:00
Radosław Gierwiało
fd0dcdf77f test: improve test cleanup with selective deletion
- Replace deleteMany({}) with selective deletion by username/email/slug in:
  - events.test.js (target specific test users/events only)
  - matches.test.js (target specific test users/events only)
  - csrf.test.js (target csrftest user only)
- Replace delete() with deleteMany() for resilient cleanup:
  - matches.test.js (2 inline cleanups)
  - socket-webrtc.test.js (1 inline cleanup)
- Update TODO.md with test status and future UX/UI improvements

Test improvement: 189/223 passing (84.8%), up from 145/223 (65%)
2025-11-20 00:05:24 +01:00
Radosław Gierwiało
d6f3eafeb2 fix(tests): improve socket.test.js cleanup to avoid test interaction issues
- Replace delete() with deleteMany() in all afterAll hooks (more resilient)
- Add checks for testUser existence before creating EventParticipant
- Add .catch() handlers to ignore duplicate or foreign key errors
- Add conditional checks with ?. before cleanup operations

Changes improve test isolation and prevent foreign key constraint
violations when tests run together. All socket tests now pass
individually (12/12 socket.test.js, 7/7 socket-webrtc.test.js).
2025-11-19 22:22:49 +01:00
Radosław Gierwiało
93c0943bfa fix(tests): fix backend test failures and improve test isolation
- Fixed CORS test in app.test.js to use allowed origin
- Updated auth-phase1.5.test.js to match actual error messages
- Fixed socket.test.js to use slug parameter instead of eventId
- Added EventParticipant records for socket event room tests
- Updated security config to allow both frontend origins in dev

All socket tests now passing (12/12). Test changes ensure proper
cleanup and prevent database conflicts by using selective deletion
instead of wiping entire tables.
2025-11-19 22:10:36 +01:00
Radosław Gierwiało
85a47f4e8e test: fix backend test cleanup to preserve production data
Replace deleteMany({}) with selective cleanup targeting only test data:

- events.test.js: Delete only test users (john_dancer, sarah_swings, mike_blues)
  and test events (test-dance-festival-2025) before creating new ones
- matches.test.js: Clean up john_dancer, sarah_swings, mike_moves and
  test-dance-festival slug specifically
- users.test.js: Remove only john_dancer and sarah_swings test users
  in both beforeAll and afterAll hooks
- auth.test.js: Target specific test usernames/emails (testuser, newuser,
  lockouttest, etc.) instead of all users
- auth-phase1.5.test.js: Clean up 12 specific test users by username/email
- socket.test.js: Add beforeAll cleanup for sockettest user to prevent
  conflicts from previous test runs
- socket-webrtc.test.js: Clean up webrtc_user1 and webrtc_user2 before
  creating them

Fix CORS configuration for tests:
- security.js: Add http://localhost:3000 to allowed origins in development
  mode to fix app.test.js CORS test (was failing with 500 error)

Results: Improved from 125/223 passing to 137/223 passing (12 more tests fixed)
All test data cleanup now uses WHERE clauses with specific usernames/emails/slugs
instead of wiping entire tables with deleteMany({})
2025-11-19 21:46:04 +01:00
Radosław Gierwiało
bfbfd0e729 test: fix auth test expectations and add test commands to Makefile
- Update auth.test.js to match current API error messages
  - Registration success message includes email verification notice
  - Duplicate credentials use generic message to prevent user enumeration

- Add test commands to Makefile
  - make test: run all backend tests
  - make test-watch: run tests in watch mode
  - make test-coverage: run tests with coverage report

All auth tests now pass (19/19 ✓)
2025-11-19 20:23:25 +01:00
Radosław Gierwiało
44df50362a feat(security): implement comprehensive security hardening
- Add CSRF protection with cookie-based tokens
  - Add cookie-parser and csurf middleware
  - Create GET /api/csrf-token endpoint
  - Frontend automatically includes CSRF token in POST/PUT/DELETE requests
  - Add retry logic for expired CSRF tokens

- Implement account lockout mechanism
  - Add database fields: failedLoginAttempts, lockedUntil
  - Track failed login attempts and lock accounts after max attempts (configurable)
  - Auto-unlock after lockout duration expires
  - Return helpful error messages with remaining time

- Add comprehensive security environment variables
  - Rate limiting configuration (API, auth, email endpoints)
  - CSRF protection toggle
  - Password policy requirements
  - Account lockout settings
  - Logging levels

- Add comprehensive test coverage
  - 6 new tests for account lockout functionality
  - 11 new tests for CSRF protection
  - All tests handle enabled/disabled states gracefully

- Update documentation
  - Add Phase 3 security hardening to SESSION_CONTEXT.md
  - Document new database fields and migration
  - Update progress to 85%

Files changed:
- Backend: app.js, auth controller, security config, new migration
- Frontend: api.js with CSRF token handling
- Tests: auth.test.js (extended), csrf.test.js (new)
- Config: .env examples with security variables
- Docs: SESSION_CONTEXT.md updated
2025-11-19 20:16:05 +01:00
Radosław Gierwiało
b9d6f42ff5 feat(import): add WSDC list parser with location + update-missing-location option\n\n- Parse Event List for name/date/location/country/sourceUrl\n- Support --source list|calendar|auto and --update-missing-location\n- Keep calendar import for title/start/end/url fallback\n- Update CLI summary and docs (ADMIN_CLI.md, README.md) 2025-11-15 21:56:05 +01:00
Radosław Gierwiało
457de6c1c4 fix(cli): keep REPL alive on errors and consolidate help\n\n- Replace process.exit(1) with thrown errors in handlers\n- REPL catches and prints CLI errors without exiting\n- Consolidated help to include all commands and examples\n- Add events:import:wsdc command mapping and alias 2025-11-15 21:41:01 +01:00
Radosław Gierwiało
78f96e2849 feat(cli): add admin REPL + commands and docs
- Add CLI entry in backend with default REPL, persistent history, aliases
- Add commands: users:list/create/verify, events:list/details/participants/checkin,
  matches:list, logs:app, logs:messages
- Support running subcommands inside REPL via .cli and run()
- Add Makefile targets: dev-cli, prod-cli, dev/prod up/down (+rebuild)
- Update README and add docs/ADMIN_CLI.md
- Add CLI tests with mocked Prisma
2025-11-15 20:51:24 +01:00