Files
pastilhasandClaude Opus 5 2dad1907f8 photos, extracted from the platform into its own repository
The fifth plugin out, and the cheapest so far — offscale's shape rather than
music's or vault's. Nothing stayed behind and nothing had to be redesigned,
for three structural reasons recorded in PLUGIN.md:

  • No reverse dependency. Nothing in src/ imported anything of photos'.
  • Configuration was already right. The URL and API key live in photos_config,
    a per-user registry encrypted at rest and driven from the app. Vault had to
    move VAULTWARDEN_URL out of .env and invent a settings panel; here the
    sidecar already read the database, and its own comment already said why
    nothing reads process.env.
  • The frontend was already panels. The screen was a WorkspaceView around
    exactly photos-nav and photos-view, so web/panels.ts and web/layout.ts are
    the screen's contents with the wrapper removed.

The router's prefix is now derived from mountPrefix rather than the literal
'/api/photos', which was correct only by coincidence — a third-party publisher
mounts at /api/p/<publisher>/photos and the literal would forward the wrong
path to the sidecar.

Palette debt paid down as the runbook requires: 6 classes (it says 47; someone
had already done most of it), all of them state colours wearing palette
numbers, now `success` and `warning` from the closed tone set so they follow
the theme.

Immich itself is still provisioned by the app store rather than by this plugin
— the `provisioned` half of its old catalogue entry has no home until Docker
provisioning for plugins exists. Point it at an instance you already run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 19:10:39 +00:00

30 lines
1.7 KiB
TypeScript

import { createSidecarProxy } from '@@/sidecar/create-proxy';
import { mountPrefix } from '@@/plugins/manifest';
import { manifest } from '../manifest';
// /api/photos/* — auth, then forward to officer-photos. No routes of its own and no Immich knowledge:
// this file must never grow app logic. The sidecar owns the Immich contract and holds its API key.
//
// ── The prefix is DERIVED, not written ──
//
// It was the literal '/api/photos', which is right only by coincidence: `mountPrefix` returns `/photos`
// for a first-party publisher, and the proxy strips exactly `prefix.length` characters to build the
// sidecar's path. Published by anyone else this mounts at `/api/p/<publisher>/photos` and a literal would
// forward `/alice/photos/assets` to a sidecar expecting `/assets`. Same reasoning, and the same fix, as
// `plugins/music/api/router.ts`.
const proxy = createSidecarProxy({
name: 'photos',
prefix: `/api${mountPrefix({ appName: 'photos', manifest })}`,
// Originals and `download/archive` zips are large and Immich builds the archive as it streams it, so the
// socket can sit quiet longer than the default 60s idle drop allows.
timeoutSeconds: 600,
// Immich judges an asset from its first few KB and rejects immediately, while we are still writing —
// and the response is lost in the teardown, which is what made every large upload fail with an
// unexplained empty 400. Buffering here removes the overlap. The sidecar does the same on its own hop
// to Immich (sidecar/routes.ts → readBodyCapped), and both are needed: fixing one alone still lost the
// answer roughly two runs in three at 32 MB.
bufferRequestBody: true,
});
export const router = proxy.router;