Phase 1b — Jellyfin Connect & Sync #3

Closed
opened 2026-10-08 07:30:16 +00:00 by agent-pi · 2 comments
Collaborator

Xique must learn what each user has watched. Today it has accounts but no media history.

This issue connects a user's Jellyfin server (read-only) and syncs their played items into the local ledger, so recommendations have real data to work from. It covers: the media catalog keyed on provider IDs (never Jellyfin internal item IDs), encrypted per-user Jellyfin credentials obtained via AuthenticateByName, the Jellyfin client, and the delta sync job that appends to the watch ledger and mirrors ratings.

Xique must learn what each user has watched. Today it has accounts but no media history. This issue connects a user's Jellyfin server (read-only) and syncs their played items into the local ledger, so recommendations have real data to work from. It covers: the media catalog keyed on provider IDs (never Jellyfin internal item IDs), encrypted per-user Jellyfin credentials obtained via AuthenticateByName, the Jellyfin client, and the delta sync job that appends to the watch ledger and mirrors ratings.
Author
Collaborator

Plan of attack

Full plan: .superpowers/plans/2026-10-08-xique-02-jellyfin-movie-sync.md (local working artifact; not committed per AGENTS.md).

Scope: movies only. TV/episode tracking stays in Phase 3; the media schema carries media_type for later.

Approach. Per-user Jellyfin base URL + Fernet-encrypted access token in jellyfin_connections; token obtained once via AuthenticateByName, password discarded. A media catalog keyed on provider IDs (never Jellyfin item IDs) plus append-only watch_events and updatable ratings. The web enqueues a jellyfin_sync job; the worker does all network work.

Endpoints (verified against Jellyfin OpenAPI 12.2.0): GET /System/Info/Public, POST /Users/AuthenticateByName, GET /Items?userId=…&recursive=true&includeItemTypes=Movie&filters=IsPlayed&enableUserData=true&fields=ProviderIds,UserData,Genres,Overview&sortBy=DatePlayed.

Key files. src/xique/services/{crypto,jellyfin,sync}.py, src/xique/db/{connections,media,ledger,jobs}.py, src/xique/db/schema.py (migration 2), src/xique/worker.py, src/xique/web/routes/settings.py + template, tests alongside.

Sequence (7 tasks, each TDD with its own commit):

  1. Fernet token encryption
  2. Migration 2 (connections/media/ledger) + transactional migrations
  3. Connections/media/ledger data access
  4. Jellyfin client (validate, authenticate, played movies)
  5. Movie delta sync service
  6. Worker hardening + jellyfin_sync dispatch
  7. Jellyfin connect settings route + UI

Issue #7 hardening folded in here: transactional migrations (task 2); jobs.status index, complete/fail status guards, stale-job recovery, poll-interval Setting, bounded worker cycle (task 6). Deferred to a follow-up small PR in this phase: login throttling, session cookie Secure, logout CSRF shape, remaining auth coverage gaps.

Branch: feat/issue-3-phase-1b-jellyfin-movie-sync

PR: opened via curl once work is complete, targeting main, body Closes #3.

## Plan of attack Full plan: `.superpowers/plans/2026-10-08-xique-02-jellyfin-movie-sync.md` (local working artifact; not committed per AGENTS.md). **Scope: movies only.** TV/episode tracking stays in Phase 3; the `media` schema carries `media_type` for later. **Approach.** Per-user Jellyfin base URL + Fernet-encrypted access token in `jellyfin_connections`; token obtained once via `AuthenticateByName`, password discarded. A `media` catalog keyed on provider IDs (never Jellyfin item IDs) plus append-only `watch_events` and updatable `ratings`. The web enqueues a `jellyfin_sync` job; the worker does all network work. **Endpoints (verified against Jellyfin OpenAPI 12.2.0):** `GET /System/Info/Public`, `POST /Users/AuthenticateByName`, `GET /Items?userId=…&recursive=true&includeItemTypes=Movie&filters=IsPlayed&enableUserData=true&fields=ProviderIds,UserData,Genres,Overview&sortBy=DatePlayed`. **Key files.** `src/xique/services/{crypto,jellyfin,sync}.py`, `src/xique/db/{connections,media,ledger,jobs}.py`, `src/xique/db/schema.py` (migration 2), `src/xique/worker.py`, `src/xique/web/routes/settings.py` + template, tests alongside. **Sequence (7 tasks, each TDD with its own commit):** 1. Fernet token encryption 2. Migration 2 (connections/media/ledger) + transactional migrations 3. Connections/media/ledger data access 4. Jellyfin client (validate, authenticate, played movies) 5. Movie delta sync service 6. Worker hardening + `jellyfin_sync` dispatch 7. Jellyfin connect settings route + UI **Issue #7 hardening folded in here:** transactional migrations (task 2); `jobs.status` index, complete/fail status guards, stale-job recovery, poll-interval Setting, bounded worker cycle (task 6). Deferred to a follow-up small PR in this phase: login throttling, session cookie `Secure`, logout CSRF shape, remaining auth coverage gaps. **Branch:** `feat/issue-3-phase-1b-jellyfin-movie-sync` **PR:** opened via curl once work is complete, targeting `main`, body `Closes #3`.
Author
Collaborator

Merged in PR #8 (merge commit 135d3bd). Phase 1b delivered: Jellyfin connect with Fernet-encrypted tokens, migration 2 (media/ledger/connections), movie delta sync, worker hardening (status guards, stale-job recovery, bounded cycle, poll-interval setting), transactional migrations, and the zero-JS settings UI. CI green.

Merged in PR #8 (merge commit 135d3bd). Phase 1b delivered: Jellyfin connect with Fernet-encrypted tokens, migration 2 (media/ledger/connections), movie delta sync, worker hardening (status guards, stale-job recovery, bounded cycle, poll-interval setting), transactional migrations, and the zero-JS settings UI. CI green.
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
marvin8/xique#3
No description provided.