Commit Graph

17 Commits

Author SHA1 Message Date
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
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
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
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
c4240f05bb feat: add Socket.IO heats_updated broadcast event
- Export getIO function from socket module
- Broadcast heats_updated event when user updates their heats
- Event includes userId, username, and updated heats array
- Non-blocking broadcast (won't fail request if socket fails)
2025-11-14 15:35:39 +01:00
Radosław Gierwiało
02d3d7ac42 feat: add competition heats system backend
- Add 3 new database tables: divisions, competition_types, event_user_heats
- Add seed data for 6 divisions (NEW, NOV, INT, ADV, ALL, CHA) and 2 competition types (J&J, STR)
- Add API endpoints for divisions and competition types
- Add heats management endpoints in events route (POST/GET/DELETE)
- Implement unique constraint: cannot have same role in same division+competition type
- Add participant verification before allowing heats management
- Support heat numbers 1-9 with optional Leader/Follower role
2025-11-14 15:32:40 +01:00
Radosław Gierwiało
6823851b63 fix: improve event check-in UX and participant counting
Backend:
- Use _count.participants for accurate real-time participant count
- Remove reliance on stale participantsCount column

Frontend:
- Show check-in requirement message for non-joined events
- Display "Open chat" button only for joined events
- Add dev-only "View details" button for QR code access during testing
- Improve visual feedback with amber-colored check-in notice

This ensures the participant count reflects actual checked-in users
and prevents unauthorized access to QR codes in production while
maintaining developer convenience in development mode.
2025-11-14 14:20:20 +01:00
Radosław Gierwiało
71cba01db3 feat: add QR code event check-in system
Backend:
- Add event_checkin_tokens table with unique tokens per event
- Implement GET /api/events/:slug/details endpoint (on-demand token generation)
- Implement POST /api/events/checkin/:token endpoint (date validation only in production)
- Implement DELETE /api/events/:slug/leave endpoint
- Add comprehensive test suite for check-in endpoints

Frontend:
- Add EventDetailsPage with QR code display, participant list, and stats
- Add EventCheckinPage with success/error screens
- Add "Leave Event" button with confirmation modal to EventChatPage
- Install qrcode.react library for QR code generation
- Update routing and API client with new endpoints

Features:
- QR codes valid from (startDate-1d) to (endDate+1d)
- Development mode bypasses date validation for testing
- Automatic participant count tracking
- Duplicate check-in prevention
- Token reuse for same event (generated once, cached)
2025-11-14 14:11:24 +01:00
Radosław Gierwiało
b2c2527c46 feat: add event slugs to prevent ID enumeration attacks
Replace sequential event IDs in URLs with unique alphanumeric slugs to prevent enumeration attacks. Event URLs now use format /events/{slug}/chat instead of /events/{id}/chat.

Backend changes:
- Add slug field (VARCHAR 50, unique) to Event model
- Create migration with auto-generated 12-char MD5-based slugs for existing events
- Update GET /api/events/:slug endpoint (changed from :id)
- Update GET /api/events/:slug/messages endpoint (changed from :eventId)
- Modify Socket.IO join_event_room to accept slug parameter
- Update send_event_message to use stored event context instead of passing eventId

Frontend changes:
- Update eventsAPI.getBySlug() method (changed from getById)
- Update eventsAPI.getMessages() to use slug parameter
- Change route from /events/:eventId/chat to /events/:slug/chat
- Update EventsPage to navigate using event.slug
- Update EventChatPage to fetch event data via slug and use slug in socket events

Security impact: Prevents attackers from discovering all events by iterating sequential IDs.
2025-11-13 21:43:58 +01:00
Radosław Gierwiało
20f405cab3 feat: track event participation and show joined events first
Backend:
- Add EventParticipant model to track user-event participation
- Create database migration for event_participants table
- Record participation when user joins event chat via Socket.IO
- Update GET /api/events to include isJoined flag for current user
- Sort events: joined events first, then by start date
- Add authenticate middleware to GET /api/events

Frontend:
- Replace mock events with real API data from backend
- Add loading and error states to EventsPage
- Display "Joined" badge on events user has joined
- Highlight joined events with colored border
- Show "Open chat" vs "Join chat" button text
- Auto-refresh events list when navigating back

When users join an event chat, this is now recorded in the database.
Joined events appear at the top of the list with visual indicators.
2025-11-13 21:18:15 +01:00
Radosław Gierwiało
9d8fc9f6d6 feat: add chat message history and infinite scroll
Backend changes:
- Socket.IO: Send last 20 messages on join_event_room
- REST API: Add GET /api/events/:eventId/messages endpoint with pagination
- Support for 'before' cursor-based pagination for loading older messages

Frontend changes:
- Load initial 20 messages when joining event chat
- Implement infinite scroll to load older messages on scroll to top
- Add loading indicator for older messages
- Preserve scroll position when loading older messages
- Add eventsAPI.getMessages() function for pagination

User experience:
- New users see last 20 messages immediately
- Scrolling up automatically loads older messages in batches of 20
- Smooth scrolling experience with position restoration

Note: Messages are encrypted in transit via HTTPS/WSS but stored
as plain text in database (no E2E encryption).
2025-11-13 20:16:58 +01:00
Radosław Gierwiało
0e62b12f5e feat: add PostgreSQL database with Prisma ORM
Phase 1 - Step 2: PostgreSQL Setup

**Infrastructure:**
- Add PostgreSQL 15 Alpine container to docker-compose.yml
- Configure persistent volume for database data
- Update backend Dockerfile with OpenSSL for Prisma compatibility

**Database Schema (Prisma):**
- 6 tables: users, events, chat_rooms, messages, matches, ratings
- Foreign key relationships and cascading deletes
- Performance indexes on frequently queried columns
- Unique constraints for data integrity

**Prisma Setup:**
- Prisma Client for database queries
- Migration system with initial migration
- Seed script with 4 test events and chat rooms
- Database connection utility with singleton pattern

**API Implementation:**
- GET /api/events - List all events (with filtering and sorting)
- GET /api/events/:id - Get single event with relations
- Database connection test on server startup
- Graceful database disconnect on shutdown

**Seed Data:**
- Warsaw Dance Festival 2025
- Swing Camp Barcelona 2025
- Blues Week Herräng 2025
- Krakow Swing Connection 2025

**Testing:**
- Database connection verified 
- API endpoints returning data from PostgreSQL 
- Migrations applied successfully 

All systems operational 🚀
2025-11-12 21:56:11 +01:00