From efa162f2ad97c478743fa36033068f00ef36885f Mon Sep 17 00:00:00 2001 From: 3dtours Date: Sat, 10 Oct 2026 17:10:30 +0700 Subject: [PATCH] chore(immich): drop the IMMICH_URL hook A deployment no longer suggests an address for the first server: every address is each account's own and is typed in the app, so the field opens blank with a placeholder. The env var, its compose passthrough, the example line and the `defaultUrl` field that carried it are all gone; the backend's config route now answers with the saved list alone. --- docker/.env.example | 7 ------- docker/backend/src/immich.ts | 11 ++++------- docker/backend/test/immich.mjs | 3 +-- docker/docker-compose.yml | 5 ----- docker/frontend/scripts/immich-check.mjs | 8 +++++--- docker/frontend/src/Library.tsx | 8 -------- docker/frontend/src/engine/immich.ts | 16 ++++++++-------- docker/frontend/src/ui/ImmichDialog.tsx | 4 +--- 8 files changed, 19 insertions(+), 43 deletions(-) diff --git a/docker/.env.example b/docker/.env.example index ecac8d1..d40b736 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -23,10 +23,3 @@ SMTP_PASS= # What the mail says it is from. Defaults to SMTP_USER, then a noreply address. SMTP_FROM= SMTP_SECURE= - -# Immich, as a second photo source beside the folders on disk in LIBRARY. This is -# only the address the "add an Immich server" dialog starts with — the key is -# each account's own, typed in the app and kept in the database (never in the -# browser, never in this file). Leave it empty and the field starts blank; the -# "add Immich" button is there either way. -IMMICH_URL=https://photos.labz.io.vn diff --git a/docker/backend/src/immich.ts b/docker/backend/src/immich.ts index 5bac647..7d84a9b 100644 --- a/docker/backend/src/immich.ts +++ b/docker/backend/src/immich.ts @@ -280,16 +280,13 @@ export function immichRoutes( return server; }; - // The saved list, plus the address this deployment suggests for the first one. - // No upstream call at all: the tree has to draw on a phone with no signal. + // The saved list. No upstream call at all: the tree has to draw on a phone with + // no signal. Nothing here suggests an address — the address and the key are each + // account's own, typed in the app, so a fresh server is a blank field. app.get('/api/immich/config', async (req, reply) => { const user = member(req, reply); if (!user) return; - return reply.send({ - ok: true, - defaultUrl: process.env.IMMICH_URL ?? '', - servers: immichServers(user.id).map(view), - }); + return reply.send({ ok: true, servers: immichServers(user.id).map(view) }); }); // The dialog's "check this key": nothing is stored, so a typo never becomes a diff --git a/docker/backend/test/immich.mjs b/docker/backend/test/immich.mjs index a73e19e..809b704 100644 --- a/docker/backend/test/immich.mjs +++ b/docker/backend/test/immich.mjs @@ -191,7 +191,6 @@ const server = spawn(process.execPath, entry, { PORT: String(PORT), DATA_DIR, NODE_ENV: 'test', - IMMICH_URL: `http://127.0.0.1:${fake.port}`, }, stdio: ['ignore', 'pipe', 'pipe'], }); @@ -253,7 +252,7 @@ try { check('the key never comes back in clear', !JSON.stringify(saved.body).includes(KEY), JSON.stringify(saved.body)); const config = await user.req('/immich/config'); - check('the saved list carries the deployment default address', config.body?.defaultUrl === url, JSON.stringify(config.body?.defaultUrl)); + check('the saved list suggests no address of its own', config.body?.defaultUrl === undefined, JSON.stringify(config.body?.defaultUrl)); check('the saved server is listed with its version', config.body?.servers?.[0]?.version === '3.1.0', JSON.stringify(config.body?.servers?.[0])); // A server saved without a name falls back to its host, which is what the tree diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 47a4fd5..d704c81 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -23,11 +23,6 @@ services: SMTP_PASS: ${SMTP_PASS:-} SMTP_FROM: ${SMTP_FROM:-} SMTP_SECURE: ${SMTP_SECURE:-} - # Immich, offered as a second photo source in LIBRARY. This is only the - # address the "add an Immich server" dialog starts with: the URL and the - # key belong to each account, are typed in the app, and are stored in the - # users table. Empty simply means the field starts blank. - IMMICH_URL: ${IMMICH_URL:-} volumes: # SQLite (WAL) lives on the host so a rebuild never loses accounts. - ./data:/data diff --git a/docker/frontend/scripts/immich-check.mjs b/docker/frontend/scripts/immich-check.mjs index 8de0460..187ca01 100644 --- a/docker/frontend/scripts/immich-check.mjs +++ b/docker/frontend/scripts/immich-check.mjs @@ -133,7 +133,7 @@ await context.route('**/api/**', (route) => { if (path === '/api/immich/config') { if (req.method() === 'GET') { - return send(200, { ok: true, defaultUrl: 'https://photos.example.com', servers: saved.map(view) }); + return send(200, { ok: true, servers: saved.map(view) }); } if (req.method() === 'DELETE') { const at = saved.findIndex((s) => s.id === url.searchParams.get('id')); @@ -297,10 +297,12 @@ await page.waitForSelector('[data-key="lib-add-immich"]', { timeout: 60_000 }); await page.click('[data-key="lib-add-immich"]'); await page.waitForSelector('[data-key="immich-url"]'); step( - 'the dialog opens with the address the deployment suggests already in it', - (await page.inputValue('[data-key="immich-url"]')) === 'https://photos.example.com', + 'the dialog opens blank: the address is each reader’s to type, with a nudge in the field', + (await page.inputValue('[data-key="immich-url"]')) === '' && + (await page.getAttribute('[data-key="immich-url"]', 'placeholder')) === 'https://photos.example.com', await page.inputValue('[data-key="immich-url"]') ); +await page.fill('[data-key="immich-url"]', 'https://photos.example.com'); await page.fill('[data-key="immich-key"]', 'good-key'); await page.click('[data-key="immich-check"]'); await page.waitForSelector('[data-key="immich-status"]'); diff --git a/docker/frontend/src/Library.tsx b/docker/frontend/src/Library.tsx index 080828a..ab73a5b 100644 --- a/docker/frontend/src/Library.tsx +++ b/docker/frontend/src/Library.tsx @@ -336,10 +336,6 @@ function Catalogue({ admin }: { admin: boolean }) { // A server is not a folder on the disk — see `engine/immich.ts` — so it is the // column's second source and the page holds no key of its own. const [servers, setServers] = useState([]); - // The address this deployment suggests for a first server — `IMMICH_URL`, which - // the backend answers with. Offered as a filled-in field, not as a saved row: - // it is a suggestion until a key is typed against it. - const [immichUrl, setImmichUrl] = useState(''); const [addImmich, setAddImmich] = useState(false); const [editAlbums, setEditAlbums] = useState(null); // The strip starts on the catalogue the last screen read: coming back from the @@ -800,7 +796,6 @@ function Catalogue({ admin }: { admin: boolean }) { const config = await immichConfig(); if (!alive) return; setServers(config.servers); - setImmichUrl(config.defaultUrl); // 5. Await photos reload if still running await reloadPromise; @@ -1106,7 +1101,6 @@ function Catalogue({ admin }: { admin: boolean }) { // holds the key, and what it says is what the dialog will show next time. const config = await immichConfig(true); setServers(config.servers); - setImmichUrl(config.defaultUrl); if (!isNew && edited) { const next: LibraryFolder = { ...edited, albums: saved.albums }; await putFolder(next); @@ -3123,7 +3117,6 @@ function Catalogue({ admin }: { admin: boolean }) { it goes straight up to the backend, which probes it before it stores. */} {addImmich ? ( void serverSaved(saved, isNew)} onClose={() => setAddImmich(false)} /> @@ -3131,7 +3124,6 @@ function Catalogue({ admin }: { admin: boolean }) { {editAlbums ? ( s.id === editAlbums.server) ?? null} - defaultUrl="" onSaved={(saved, isNew) => void serverSaved(saved, isNew)} onClose={() => setEditAlbums(null)} /> diff --git a/docker/frontend/src/engine/immich.ts b/docker/frontend/src/engine/immich.ts index 29aa56e..2cb9d4e 100644 --- a/docker/frontend/src/engine/immich.ts +++ b/docker/frontend/src/engine/immich.ts @@ -87,16 +87,16 @@ async function get(path: string): Promise { } } -// The saved servers, plus the address this deployment suggests for the first one. -// One call per session: the tree has to draw on the first paint, and a server -// list that is asked for on every render is a round trip per render. -let configOnce: Promise<{ defaultUrl: string; servers: ImmichServer[] }> | null = null; +// The saved servers. One call per session: the tree has to draw on the first +// paint, and a server list that is asked for on every render is a round trip per +// render. +let configOnce: Promise<{ servers: ImmichServer[] }> | null = null; -export function immichConfig(fresh = false): Promise<{ defaultUrl: string; servers: ImmichServer[] }> { +export function immichConfig(fresh = false): Promise<{ servers: ImmichServer[] }> { if (fresh || !configOnce) { - configOnce = get<{ defaultUrl?: string; servers?: ImmichServer[] }>('/api/immich/config').then((body) => - body ? { defaultUrl: body.defaultUrl ?? '', servers: body.servers ?? [] } : { defaultUrl: '', servers: [] } - ); + configOnce = get<{ servers?: ImmichServer[] }>('/api/immich/config').then((body) => ({ + servers: body?.servers ?? [], + })); } return configOnce; } diff --git a/docker/frontend/src/ui/ImmichDialog.tsx b/docker/frontend/src/ui/ImmichDialog.tsx index 3b85561..d9e308c 100644 --- a/docker/frontend/src/ui/ImmichDialog.tsx +++ b/docker/frontend/src/ui/ImmichDialog.tsx @@ -32,19 +32,17 @@ const UUID = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i; export function ImmichDialog({ server, - defaultUrl, onSaved, onClose, }: { server?: ImmichServer | null; - defaultUrl: string; onSaved: (saved: ImmichServer, isNew: boolean) => void; onClose: () => void; }) { const { t } = useI18n(); const editing = Boolean(server); const [name, setName] = useState(server?.name ?? ''); - const [url, setUrl] = useState(server?.url ?? defaultUrl); + const [url, setUrl] = useState(server?.url ?? ''); const [key, setKey] = useState(''); // The key a node is being given in place of the one it has. Blank means the // question was not asked — the node keeps the key it already answered with — so