E5-F1: Queue Join and Position Assignment¶
Epic: E5: Waiting Queue System
Size: M (Medium)
Problem / Outcome¶
Users join queue and receive position.
Scope¶
In-Scope:
- Queue join endpoint via Queue Service
- Position assignment using Redis sorted set
- Queue size tracking
- FIFO ordering
- WebSocket connection for real-time updates
Out-of-Scope:
- Priority queue
Queue Service API Contract¶
Extracted Service
This feature is implemented in the Queue Service (Node.js or Go), not the main Symfony monolith. See Microservices Strategy.
REST Endpoints¶
Join Queue¶
POST /api/v1/queue/{match_id}/join
Authorization: Bearer {jwt_token}
Content-Type: application/json
Response (200 OK):
{
"queue_token": "qt_abc123",
"position": 4521,
"total_in_queue": 45000,
"estimated_wait_minutes": 45,
"websocket_url": "wss://queue.hns.hr/ws/{queue_token}"
}
Get Current Position¶
GET /api/v1/queue/{match_id}/position
Authorization: Bearer {jwt_token}
Response (200 OK):
{
"position": 4200,
"total_in_queue": 44500,
"estimated_wait_minutes": 42,
"status": "waiting"
}
Leave Queue¶
DELETE /api/v1/queue/{match_id}
Authorization: Bearer {jwt_token}
WebSocket Protocol¶
Connect to wss://queue.hns.hr/ws/{queue_token} after joining.
Server → Client Messages:
// Position update (every 30 seconds or on significant change)
{
"type": "position_update",
"position": 3500,
"estimated_wait_minutes": 35
}
// Your turn (purchase window granted)
{
"type": "turn_granted",
"purchase_window_expires_at": "2026-09-15T14:35:00Z",
"checkout_url": "https://hns.hr/checkout/{session_token}"
}
// Queue closed (sold out)
{
"type": "queue_closed",
"reason": "sold_out"
}
Client → Server Messages:
// Heartbeat (every 60 seconds to maintain position)
{
"type": "heartbeat"
}
Internal Events (Redis Pub/Sub)¶
Events published to the monolith:
// queue.turn_granted
{
"event": "queue.turn_granted",
"user_id": "uuid",
"match_id": "uuid",
"session_token": "st_xyz789",
"expires_at": "2026-09-15T14:35:00Z"
}
// queue.expired
{
"event": "queue.expired",
"user_id": "uuid",
"match_id": "uuid",
"reason": "window_timeout"
}
Acceptance Criteria¶
- AC1: Given user joins queue, when processed, then position assigned (FIFO)
- AC2: Queue join returns position, estimated wait time, total queue size
- AC3: Same user joining from another device gets same position (by user_id)
Data Model Impact¶
QueueEntry (Redis sorted set):
- key: queue:{match_id}
- member: user_id
- score: timestamp (for FIFO ordering)
QueueMetadata (Redis hash):
- key: queue_meta:{match_id}
- total_size (INTEGER)
- processing_rate (INTEGER per minute)
- created_at (TIMESTAMP)
QueueEntry table (PostgreSQL for persistence):
- id (UUID, PK)
- user_id (UUID, FK)
- match_id (UUID, FK)
- position (INTEGER)
- joined_at (TIMESTAMP)
- status (ENUM: waiting, active, expired, completed)
Permissions/Roles¶
- Authenticated user
How to Verify¶
npm test -- --grep "queue join"
Expected: Position assigned, multi-device sync works.
Dependencies¶
- None (foundational for queue)
Implementation Tasks¶
Doc References¶
Last Updated: January 2026