Files
ledgrab/plans/game-integration/phase-2-storage-api.md
T
alexei.dolgolyov 492bdb95e3 feat: game integration system
Receive real-time events from games (CS2, Dota 2, LoL, etc.) and drive
LED effects through the existing color strip and value source pipelines.

Core:
- GameEventBus (thread-safe pub/sub) with standardized 23-type event vocabulary
- GameAdapter ABC + AdapterRegistry + MappingAdapter (YAML-driven)
- Built-in adapters: CS2 GSI, Dota 2 GSI, LoL Live Client, Generic Webhook
- Community YAML adapters: Minecraft, Valorant, Rocket League
- GameEventColorStripStream with 5 effects (flash/pulse/sweep/color_shift/breathing)
- GameEventValueSource with EMA smoothing and timeout
- 4 built-in effect presets (FPS Combat, MOBA Health, Racing, Generic Alert)
- Auto-setup for Valve GSI games (Steam path detection, cfg file writing)
- Demo capture engine exposed to non-demo mode

Frontend:
- Game tab in Streams tree navigation with integration cards
- Game integration editor modal with adapter picker, config fields, event mappings
- game_event source type in CSS and ValueSource editors
- Setup instructions overlay (markdown rendered)
- Live event monitor and connection test

API:
- Full CRUD for game integrations
- Event ingestion endpoint (adapter-level auth)
- Adapter metadata, presets, auto-setup, status/diagnostics endpoints
2026-03-31 13:17:52 +03:00

103 lines
7.4 KiB
Markdown

# Phase 2: Storage & API — Game Integration Configs
**Status:** ✅ Complete
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** backend
## Objective
Create the persistence layer and REST API for managing game integration configurations. Users create/edit/delete game integration configs, and games POST events to an ingestion endpoint.
## Tasks
- [x] Task 1: Create `GameIntegrationConfig` dataclass (`storage/game_integration.py`)
- Fields: id (gi_<8hex>), name, adapter_type, enabled, adapter_config (dict), event_mappings (list of EventMapping dicts), created_at, updated_at, description, tags
- `EventMapping` dataclass: event_type, effect, color ([R,G,B]), duration_ms, intensity, priority
- Standard to_dict/from_dict/create_from_kwargs/apply_update methods
- [x] Task 2: Create `GameIntegrationStore` (`storage/game_integration_store.py`)
- Extend BaseSqliteStore[GameIntegrationConfig]
- CRUD methods: create_integration, update_integration, delete (inherited)
- Name uniqueness validation
- get_references() for cascade prevention
- [x] Task 3: Create Pydantic schemas (`api/schemas/game_integration.py`)
- GameIntegrationCreate, GameIntegrationUpdate, GameIntegrationResponse, GameIntegrationListResponse
- EventMappingSchema
- GameEventPayload (for ingestion endpoint)
- AdapterInfoResponse, AdapterListResponse (for adapter metadata endpoint)
- GameIntegrationStatusResponse (for diagnostics)
- [x] Task 4: Create API routes (`api/routes/game_integration.py`)
- GET /api/v1/game-integrations — list all configs
- POST /api/v1/game-integrations — create config
- GET /api/v1/game-integrations/{id} — get config
- PUT /api/v1/game-integrations/{id} — update config
- DELETE /api/v1/game-integrations/{id} — delete config
- POST /api/v1/game-integrations/{id}/event — receive game event payload (ingestion)
- GET /api/v1/game-integrations/{id}/status — connection status, last event, event count
- GET /api/v1/game-integrations/{id}/events — recent events for debugging
- GET /api/v1/game-adapters — list available adapter types + supported events + config schema
- [x] Task 5: Wire into main.py — create store, register in dependencies, include router
- [x] Task 6: Add to STORE_MAP in api/routes/system.py for backup/restore
- N/A — backups are SQLite-level (full database snapshot). Added `game_integrations` to `_ENTITY_TABLES` in `database.py` which automatically includes it in backup/restore.
- [x] Task 7: Add game_integrations_file to StorageConfig in config.py (if JSON-based) or ensure DB table
- Added `"game_integrations"` to `_ENTITY_TABLES` in `database.py` — table auto-created on startup.
- [x] Task 8: Event ingestion logic — parse payload through adapter, publish to EventBus, track per-integration state (for diff detection)
- [x] Task 9: Write tests for store (CRUD, validation, uniqueness)
- [x] Task 10: Write tests for API routes (create, list, update, delete, event ingestion)
## Files to Modify/Create
- `server/src/wled_controller/storage/game_integration.py` — dataclass models
- `server/src/wled_controller/storage/game_integration_store.py` — SQLite store
- `server/src/wled_controller/api/schemas/game_integration.py` — Pydantic schemas
- `server/src/wled_controller/api/routes/game_integration.py` — REST endpoints
- `server/src/wled_controller/main.py` — wire store + router
- `server/src/wled_controller/api/dependencies.py` — add get_game_integration_store
- `server/src/wled_controller/api/routes/system.py` — add to STORE_MAP
- `server/src/wled_controller/config.py` — add storage config (if needed)
- `server/tests/storage/test_game_integration_store.py` — store tests
- `server/tests/api/test_game_integration_routes.py` — route tests
## Acceptance Criteria
- Full CRUD for game integration configs via REST API
- Event ingestion endpoint parses payload through the correct adapter and publishes to EventBus
- Adapter metadata endpoint returns available adapters with supported events and config schemas
- Status endpoint shows last event time and event counts
- Store validates name uniqueness
- Included in backup/restore via STORE_MAP
- All tests pass
## Notes
- The ingestion endpoint must be low-latency — games send at tick rate (16-64 Hz)
- Store per-integration prev_state dict in memory (not persisted) for diff-based trigger detection
- Auth for ingestion endpoint: adapter-level auth (e.g. CS2 token) checked before standard API auth
## Review Checklist
- [x] All tasks completed
- [x] Code follows project conventions
- [x] No unintended side effects
- [x] Tests pass
## Handoff to Next Phase
**Completed:** All 10 tasks implemented and tested. 48 tests pass, 0 failures. Ruff clean.
### What was built
- `storage/game_integration.py``EventMapping` and `GameIntegrationConfig` dataclasses with full `to_dict()`/`from_dict()`/`create_from_kwargs()`/`apply_update()` methods. `apply_update()` returns a new instance (immutable pattern).
- `storage/game_integration_store.py``GameIntegrationStore` extending `BaseSqliteStore[GameIntegrationConfig]`. CRUD with name uniqueness, write-through caching. Aliases: `get_all_integrations`, `get_integration`, `delete_integration`.
- `api/schemas/game_integration.py` — Full Pydantic schema suite: `EventMappingSchema`, `GameIntegrationCreate/Update/Response/ListResponse`, `GameEventPayload`, `AdapterInfoResponse/AdapterListResponse`, `GameIntegrationStatusResponse`, `GameEventResponse/RecentEventsResponse`.
- `api/routes/game_integration.py` — 9 endpoints: full CRUD + event ingestion + status + recent events + adapter listing. Ingestion endpoint skips `AuthRequired` (uses adapter-level auth via `validate_auth()`). Per-integration runtime state tracked in-memory (`_prev_states`, `_integration_stats`) with thread-safe access.
- `database.py` — Added `"game_integrations"` to `_ENTITY_TABLES` so the table is auto-created and included in database backups.
- `api/dependencies.py` — Added `get_game_integration_store()` and `get_game_event_bus()` getters + `init_dependencies()` params.
- `api/__init__.py` — Registered `game_integration_router`.
- `main.py` — Creates `GameIntegrationStore(db)` and `GameEventBus()`, passes both to `init_dependencies()`.
### Key design decisions
- **Ingestion auth**: The `/event` endpoint does NOT use `AuthRequired`. Instead, it calls `adapter_cls.validate_auth()` with request headers and adapter config. This allows game clients (CS2 GSI, etc.) to authenticate with their own token scheme without needing the app's API key.
- **Runtime state**: Per-integration `prev_state` (for diff detection) and event stats are stored in module-level dicts with a threading lock. Not persisted — resets on server restart.
- **Connected heuristic**: An integration is "connected" if it received an event within the last 30 seconds (based on monotonic time).
- **Immutable updates**: `GameIntegrationConfig.apply_update()` returns a new instance; the store replaces the cache entry.
### What Phase 3+ needs
- `GameEventBus` singleton is now available via `get_game_event_bus()` dependency — Phase 4/5 streams can subscribe to it.
- `GameIntegrationStore` is available via `get_game_integration_store()` — Phase 6 frontend can call the CRUD API.
- Built-in adapters (Phase 3) should call `AdapterRegistry.register(MyAdapter)` at import time to appear in `GET /api/v1/game-adapters`.
- The `adapter_config` field on each integration stores adapter-specific secrets (e.g. CS2 auth token). Phase 3 adapters define their config schema via `get_config_schema()`.