wire the trigger: connecting an admin server provisions everyone
the decision and the execution existed; nothing called them. connecting a server would have given a working media connection and zero accounts. the trigger is registering an ADMIN server, not installing the plugin. at install there is no credential, so there is nothing to provision with — and this is also the right moment for a re-run, since a revoked token or a moved server both come back through the same door. the sidecar now holds the pushed account list in memory. each users:list REPLACES what was held rather than merging: it is the platform's current truth, not a cache. persisting it would create a second copy of the platform's users that could disagree with it, and the recovery from disagreement is re-reading the platform anyway. `hasProvisionableUsers()` distinguishes never-told from told-there-is-nobody. an empty list means a server with only an owner; no list at all means the push has not arrived or the plugin is not entitled. provisioning nothing is right for the first and a silent failure for the second. awaited, so the response says what happened instead of the owner finding it in a log later. and it never throws: registering the server already succeeded by then, and losing it to a provisioning error would leave them with no media access AND no accounts, which is worse than the accounts alone. mapping rows are written one at a time as each result is known, not batched at the end. a create that succeeds upstream but whose row fails to write leaves an orphan account officer will try to create again — the narrower that window, the better. found while wiring: the first edit landed the block in editServer as well as addServer, where `auth` does not exist. caught by tsgo, removed.
This commit is contained in:
+20
-1
@@ -9,6 +9,7 @@ import {
|
||||
updateJellyfinServer,
|
||||
} from '../db';
|
||||
import { UpstreamError, authenticate, invalidateConfig, normalizeBase, probe, publicInfo } from './upstream';
|
||||
import { runProvisioning } from './provision-run';
|
||||
|
||||
// `/_config` — the Jellyfin server registry, driven from the app.
|
||||
//
|
||||
@@ -116,7 +117,25 @@ async function addServer(req: Request, userId: number): Promise<Response> {
|
||||
activate,
|
||||
});
|
||||
invalidateConfig(userId);
|
||||
return Response.json({ server });
|
||||
|
||||
// ── Provision, if this connection can ──
|
||||
//
|
||||
// The trigger is registering an ADMIN server, not installing the plugin: at install time there is no
|
||||
// credential, so there is nothing to provision with. This is also the right moment for a re-run — a
|
||||
// token revoked and re-issued, or the server moving, both come back through here.
|
||||
//
|
||||
// Awaited, so the response says what happened rather than the owner discovering it in a log. It can
|
||||
// take a few seconds against a server with many accounts; that is the honest cost of telling the truth
|
||||
// in the reply.
|
||||
const provisioning = await runProvisioning({
|
||||
userId,
|
||||
serverId: server.id,
|
||||
base: url,
|
||||
token: auth.token,
|
||||
jellyfinUserId: auth.userId,
|
||||
deviceId,
|
||||
});
|
||||
return Response.json({ server, provisioning });
|
||||
} catch (err) {
|
||||
if (duplicateLabel(err)) return bad(`you already have a server called "${label}"`);
|
||||
throw err;
|
||||
|
||||
Reference in New Issue
Block a user