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:
@@ -0,0 +1,31 @@
|
||||
import type { ProvisionableUser } from 'officerdb';
|
||||
|
||||
// The account list the platform pushes, held for as long as this process lives.
|
||||
//
|
||||
// Pushed rather than fetched — see src/servers/plugins/user-provisioning.ts. It arrives once when this
|
||||
// sidecar registers, and again whenever an Officer account is created or removed, so it is the current
|
||||
// truth rather than a cache with an expiry: each message REPLACES what was held, never merges.
|
||||
//
|
||||
// In memory on purpose. Persisting it would create a second copy of the platform's own users that could
|
||||
// disagree with it, and the recovery from disagreement would be re-reading the platform anyway. A restart
|
||||
// re-registers, which re-sends.
|
||||
|
||||
let users: ProvisionableUser[] = [];
|
||||
let receivedAt: Date | null = null;
|
||||
|
||||
export function setProvisionableUsers(next: ProvisionableUser[]): void {
|
||||
users = next;
|
||||
receivedAt = new Date();
|
||||
console.log(`[jellyfin] user list: ${next.length} account(s) the platform can provision`);
|
||||
}
|
||||
|
||||
export const getProvisionableUsers = (): ProvisionableUser[] => users;
|
||||
|
||||
/**
|
||||
* Whether the platform has told us anything yet.
|
||||
*
|
||||
* The distinction matters: an EMPTY list means "this server has only an owner", while never having been
|
||||
* told means the plugin is not entitled or the push has not arrived. Provisioning nothing is right in the
|
||||
* first case and wrong in the second, so the caller has to be able to tell them apart.
|
||||
*/
|
||||
export const hasProvisionableUsers = (): boolean => receivedAt !== null;
|
||||
Reference in New Issue
Block a user