Files
frambe/.claude-context/sessions/2026-09-23-immich-v3-migration.md
T

99 lines
6.0 KiB
Markdown

# Immich v3 API Migration & Deploy Debugging
**Date:** 2026-09-23
**Project:** frambe
**Status:** ongoing
**Tags:** immich-v3, api-migration, docker-compose, env-vars, deployment, search-metadata
## Context
Immich released v3 (1 July 2026) with a large set of breaking API changes. Frambe
called several endpoints that were removed, renamed, or reshaped, so albums/people/
random/favorites all needed updating. After shipping the code fixes (v1.6.0 on `dev`),
the "Start slideshow" button flashed "no photos found" despite albums showing a
nonzero count — which turned out to be a deploy/env problem, not a code problem.
## Key Decisions
- Bumped **1.5.2 → 1.6.0** (minor). Rationale: substantial multi-endpoint
compatibility fix pack for a breaking upstream change, but no new user-facing
features. Not a major (no feature pack), not a patch (more than a small fix).
- Album assets now fetched via **`POST /search/metadata` with `albumIds`**, because
v3 removed `AlbumResponseDto.assets`. Kept a v2 fallback (`al.assets` if present)
so the same code works against both server versions during transition.
- Added a **`searchMetadata()` pagination helper** (pages via `assets.nextPage`,
integer `page`/`size`) rather than one-shot single-page fetches — album/person
sets can exceed one page of 250.
- Person assets moved to `POST /search/metadata` with `personIds`, keeping the
legacy `GET /people/:id/assets` as a try/catch fallback. Considered leaving the
old endpoint alone (not listed as removed in the migration doc) but chose the
canonical v3 path with fallback for durability.
- **`visibility` handling is still open** — see Open Questions. Initially forced
`visibility:'timeline'` everywhere to keep archived/hidden assets off frames;
this is the leading suspect for empty album results and may need to be dropped
for album/person queries.
- Deploy stays **scp + ssh**, not the Gitea API: `server.js` is ~27KB, over the
Gitea `update_file` ~16KB silent-truncation limit. Uploader BAT verifies md5 +
`node --check` on the server before it will commit/push.
## Technical Details
- Immich creds are set **inline in `docker-compose.yml`** under `environment:`
(`IMMICH_URL`, `IMMICH_API_KEY`), NOT in `.env`. Compose ALSO references an
`env_file:` (`.env`) that is gitignored and currently MISSING from `~/frambe`
on hal-blackpearl — this makes every `docker compose` command abort before it
does anything (rebuild, exec, up all fail with "env file ... not found").
- App reads `process.env.IMMICH_API_KEY` (server.js line 15) and
`process.env.IMMICH_URL` (line 14). These match the compose `environment:` block.
A debug test that printed `$API_KEY` showed empty — that was the WRONG var name
in the test shell, a red herring, not an app bug.
- Proven working in-container (with a valid key): `POST $IMMICH_URL/api/search/metadata`
body `{"albumIds":["<uuid>"],"size":5}` returns `assets.total:5` — so the v3
`albumIds` search logic is correct. The empty result in the UI is a runtime
deploy/env issue, not the query.
- Server on hal-blackpearl uses Alpine node image: has `wget`, not `curl`. Use
`wget -qO-` for in-container HTTP tests.
- Frambe app endpoint: `http://localhost:3030/api/albums/:id` (host port 3030).
- v3 endpoint/shape changes applied:
- `GET /albums` list: `?shared=true` → `?isShared=true`; added `?isOwned=true`.
- `GET /albums/:id`: `AlbumResponseDto.assets` removed → search/metadata.
- `GET /assets/random` REMOVED → `POST /search/random` (body `{size, visibility}`,
returns a bare array; code tolerates array or `{assets:{items}}`).
- `POST /search/metadata`: `page`/`size` now integers; omitted `visibility` now
defaults to "any" (was "timeline").
- `AssetResponseDto.duration`: now a number in **milliseconds** (was
"H:MM:SS.mmm" string) → `normDuration()` normalizes both to ms.
- Error bodies moved to Zod shape + `X-Correlation-ID` header — NOT handled
because Frambe only reads HTTP status codes, not error bodies. No change needed.
## Code Changes
- `server.js` — VERSION → 1.6.0; added `normDuration()`, `searchMetadata()`;
`mapAsset` uses `normDuration`; `/api/albums` uses isShared/isOwned;
`/api/albums/:id` uses search/metadata (v2 fallback); `/api/people/:id` uses
personIds search (legacy fallback); `/api/assets/random` uses search/random;
`/api/assets/favorites` routed through `searchMetadata`. Added `logErr` to more
catch blocks.
- `package.json` — version 1.4.1 → 1.6.0 (was stale).
- `public/js/app.js` — no changes (consumes the server's normalized shape).
## Open Questions / Next Steps
- **Immediate blocker:** `.env` missing in `~/frambe` on hal-blackpearl, so Compose
aborts. Fix by either (a) `cp .env.example .env` and fill real values
(`TZ=Australia/Melbourne`, admin password, Frambe API token), or (b) drop the
`env_file:` line from `docker-compose.yml` since the Immich creds already live in
the `environment:` block. Recommendation leaning toward (b) — removes dependence
on a gitignored file that keeps disappearing.
- Once Compose runs again: `docker compose up -d --build --force-recreate`, then
re-test `curl http://localhost:3030/api/albums/<uuid>` — expected to return
photos now that v1.6.0 code + valid key are both live.
- **Decide on `visibility`:** if albums still come back empty after the container is
confirmed current, drop `visibility:'timeline'` from the album/person searches
(keep it only where wanted). Album assets aren't guaranteed timeline-visible, so
forcing timeline can zero out results. Possibly make it a config toggle.
- `docker-compose.yml` still has the obsolete top-level `version:` attribute →
harmless warning; remove when convenient.
- Not yet done: CHANGELOG entry / README version bump — deferred to the `main` merge.
- Merge `dev` → `main` via Gitea UI once frames are confirmed working.
## Artifacts
- `dev` HEAD: `55538e5` "v1.6 Updates for Immich V3 API Changes"
- Immich v3 migration doc: https://immich.app (Migrating to v3, 1 July 2026)
- Repo: https://gitea.hideawaygaming.com.au/jessikitty/frambe (branch: dev)