8cdeadd6dd
PR 1 of 4 for the runtime service registry change. - Add Fernet encryption helper (services/secrets.py) with a required MANAGE_ENCRYPTION_KEY; validate it on startup. - Add closed integrations/ registry with Pydantic config + widget-config definitions for grafana, prometheus, jellyfin, nextcloud, and ssh_tasks. - Add services + service_task_runs tables and SettingsStore CRUD with cascade-delete (defensive until widgets carry service_id). - Add /api/services/types and /api/services/instances CRUD (encrypted secrets, secrets_set flags only; never plaintext). - Declare cryptography as a direct dependency. - Require MANAGE_ENCRYPTION_KEY in compose + .env.example + README. - Add 25 backend tests (registry, encryption, CRUD, cascade, task-run history). Verification: ruff clean; pytest 225 passed; frontend lint/build green.
80 lines
3.7 KiB
Markdown
80 lines
3.7 KiB
Markdown
# Apply Progress: Runtime Service Registry
|
|
|
|
**Change:** `service-registry`
|
|
**Apply run:** PR 1 / Slice 1 — Backend service foundation
|
|
**Date:** 2026-06-19
|
|
|
|
## Completed tasks (Slice 1)
|
|
|
|
- [x] 1.1 Add encryption helper (`services/secrets.py`)
|
|
- [x] 1.2 Add integrations base classes (`integrations/base.py`)
|
|
- [x] 1.3 Add five service definitions + registry
|
|
- [x] 1.4 Add `services` + `service_task_runs` tables + store CRUD with cascade delete
|
|
- [x] 1.5 Add service Pydantic models + `/api/services*` router
|
|
- [x] 1.6 Validate `MANAGE_ENCRYPTION_KEY` on startup
|
|
- [x] 1.7 Add backend tests (`tests/test_services.py`)
|
|
- [x] 1.8 Verify (ruff + pytest green)
|
|
|
|
## Files changed (Slice 1)
|
|
|
|
### New files
|
|
|
|
- `backend/src/media_library_viewer_api/integrations/__init__.py` — package marker.
|
|
- `backend/src/media_library_viewer_api/integrations/base.py` — `ServiceConfigBase`,
|
|
`WidgetConfigBase`, `SecretField`, `WidgetKind`, `ServiceDefinition`, `widget_kind()`,
|
|
`validate_config()`.
|
|
- `backend/src/media_library_viewer_api/integrations/{grafana,prometheus,jellyfin,nextcloud,ssh_tasks}.py`
|
|
— one Pydantic-config + widget-config definition per service.
|
|
- `backend/src/media_library_viewer_api/integrations/registry.py` — closed
|
|
`SERVICE_DEFINITIONS` + helpers.
|
|
- `backend/src/media_library_viewer_api/services/secrets.py` — Fernet encrypt/decrypt
|
|
- key validation.
|
|
- `backend/src/media_library_viewer_api/models/services.py` — request/response models.
|
|
- `backend/src/media_library_viewer_api/routers/services.py` — `/api/services/types`
|
|
- `/api/services/instances` CRUD.
|
|
- `backend/tests/test_services.py` — 25 tests.
|
|
|
|
### Modified files
|
|
|
|
- `backend/src/media_library_viewer_api/services/settings_store.py` — `services` and
|
|
`service_task_runs` tables; service CRUD; cascade delete (defensive against the
|
|
not-yet-present `dashboard_widgets.service_id` column); task-run history helpers.
|
|
- `backend/src/media_library_viewer_api/main.py` — register `services_router`;
|
|
validate encryption key on startup.
|
|
- `backend/pyproject.toml` — declare `cryptography>=42.0` direct dependency.
|
|
- `docker-compose.yml`, `docker-compose.dev.yml`, `.env.example`, `README.md` — require
|
|
and document `MANAGE_ENCRYPTION_KEY`.
|
|
|
|
## Verification (Slice 1)
|
|
|
|
```bash
|
|
cd backend
|
|
.venv/bin/ruff check . # All checks passed
|
|
PYTHONPATH=src .venv/bin/python -m pytest # 225 passed
|
|
cd ../frontend
|
|
npm run lint # 0 errors
|
|
npm run build # success
|
|
```
|
|
|
|
Smoke: encryption round-trip OK; missing `MANAGE_ENCRYPTION_KEY` raises on startup.
|
|
|
|
## Deviations from design
|
|
|
|
- Service-config and widget-config schemas are derived from **Pydantic models**
|
|
(`model_json_schema()`), matching the user's "proper pydantic config definitions"
|
|
request. The design's hand-written JSON schemas were replaced by model-derived ones.
|
|
- Service-table CRUD lives on `SettingsStore` (not a separate `service_store.py`) to
|
|
match how widgets/saved_tasks/ssh_keys are already handled there. This keeps a single
|
|
store owner for all tables.
|
|
- The cascade delete defensively checks for `dashboard_widgets.service_id` (added in
|
|
Slice 2) so Slice 1 stays green without the column.
|
|
|
|
## Remaining work
|
|
|
|
- Slice 2: Backend widget rebind to services (add `service_id`/`widget_kind`, refactor
|
|
adapters to take a `ServiceRecord`, retire old widget registry, SSH run logging).
|
|
- Slice 3: Frontend services runtime (types, API, hooks, frontend registry, service
|
|
pages, route swap).
|
|
- Slice 4: Dashboard picker, settings rework, remove `grafana_url`/`prometheus_url`
|
|
env vars, stop default seeding, docs + changelog.
|