492bdb95e3
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
103 lines
7.4 KiB
Markdown
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()`.
|