Перейти к содержанию

Admin service

Responsibility

  • Admin authentication (OTP + JWT)
  • Read-only access to game logs across all sessions
  • Enriched audit timeline (actions + lifecycle events)

Location

Implemented as FastAPI module services/api/app/modules/admin/ mounted at /api/v1/admin/*.

Auth endpoints live under /api/v1/auth/admin/*.

Data model

game_events (new table)

Append-only events not captured as player actions.

Column Type Description
id UUID PK
game_instance_id UUID FK Nullable for session-level events
session_id UUID FK Session reference
event_type string See event types below
payload JSONB Event-specific data
player_id UUID Nullable; actor for rejected actions
created_at timestamptz Server time

Event types

event_type Trigger payload
game.started start_game plugin_id, initial_state, player_ids, host_id
game.finished win or end_active_game winner_id, ended_by_host, final_state
session.participant_joined join_session participant_id, display_name, channel, role
session.closed close_session {}
action.rejected failed submit_action action_type, payload, error_code, channel

game_actions extension

Column Type Description
channel string web or telegram — channel used for this action

Existing columns unchanged: action_id, action_type, payload, player_id, result_state, created_at.

Admin API

All routes require get_current_admin dependency.

Method Path Description
GET /admin/games List games with filters and pagination
GET /admin/games/{id} Game metadata + participants
GET /admin/games/{id}/log Merged timeline
GET /admin/sessions List sessions with game count
GET /admin/sessions/{id}/games Games in session

Timeline response shape

{
  "game": {
    "id": "uuid",
    "plugin_id": "dice_board",
    "session_id": "uuid",
    "invite_code": "ABC123",
    "status": "finished",
    "started_at": "2026-06-14T12:00:00Z",
    "finished_at": "2026-06-14T12:05:00Z",
    "winner_id": "uuid"
  },
  "participants": [
    {
      "id": "uuid",
      "display_name": "Alice",
      "role": "host",
      "control_channel": "web",
      "guest_id": null,
      "user_id": null
    }
  ],
  "timeline": [
    {
      "seq": 1,
      "kind": "event",
      "event_type": "game.started",
      "at": "2026-06-14T12:00:00Z",
      "payload": {}
    },
    {
      "seq": 2,
      "kind": "action",
      "action_id": "uuid",
      "action_type": "roll_dice",
      "player_id": "uuid",
      "player_name": "Alice",
      "channel": "web",
      "payload": {},
      "result_state": {},
      "at": "2026-06-14T12:00:15Z"
    }
  ]
}

Timeline entries are sorted by created_at, then assigned seq.

Auth module

Method Path Auth Description
POST /auth/admin/otp/request Bot (telegram_id check) Generate OTP
POST /auth/admin/otp/verify Public Verify OTP → admin JWT

Redis keys:

  • admin:otp:{telegram_id} — OTP code
  • admin:otp:attempts:{telegram_id} — verify attempt counter
  • admin:otp:rate:{telegram_id} — request rate limit

RBAC

async def get_current_admin(authorization: str = Header(None)) -> AdminContext:
    # Parse JWT, require type == "admin"

Player JWT and guest tokens are rejected on admin routes.

Audit endpoint change

GET /games/{id}/auditadmin only (403 for unauthenticated/non-admin). Full detail available via /admin/games/{id}/log.

Retention

Aligned with NFR-027: game_actions and game_events retained 90 days (purge job future work).