Preskoči na sadržaj

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

See E5: Waiting Queue Tasks

Doc References


Last Updated: January 2026