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
- 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
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
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)
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
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
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
- 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)
- 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
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.
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)
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.
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.
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).
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 🚀