manifest, thin proxy router, and the sidecar backend
ported from the standalone officer-atelier service: providers (adapter-shaped, openai the only adapter today), the generation queue, collections, and the database layer — kept on its own Postgres database rather than folded into officer_db, since both collections and provider_settings had rows on the machine this was extracted from. what changed rather than just moved: - images are served from an authenticated GET /files/:filename instead of an unauthenticated PUBLIC_URL-prefixed static route - the OpenAI-key bootstrap fallback reads the secret store (getSecret) before .env, per-adapter - provider_settings (the one-row-per-adapter shape providers replaced) did not come along — it has no callers left anywhere in the server code, only a migration script that also did not move ping/echo (smoke-test routes with no caller) did not move either; the sidecar's own /_health covers liveness. not yet wired into the platform: sidecar/index.ts exists but nothing mounts it, and web/ has not been written, so this is not yet a discoverable plugin.
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
import { drizzle } from 'drizzle-orm/postgres-js';
|
||||
import postgres from 'postgres';
|
||||
import * as schema from './schema';
|
||||
|
||||
// The `atelier` Postgres database — separate from officer_db, on purpose. See PLUGIN.md.
|
||||
//
|
||||
// `ATELIER_POSTGRES_URL` rather than officer's own `OFFICERDEV_POSTGRES_URL`/`POSTGRES_URL`: this
|
||||
// plugin talks to a different database on (most likely) the same Postgres server, and the platform's
|
||||
// own connection string must never be reused here by accident — the standalone service's
|
||||
// `atelier_db/src/env.ts` had a whole enforcement mechanism for exactly this mistake ("a development
|
||||
// process cannot reach the production database"), which no longer applies once this runs as one
|
||||
// process under one PM2 entry, but the principle — don't let this plugin's queries land on the
|
||||
// platform's own tables — still does.
|
||||
const POSTGRES_URL = process.env.ATELIER_POSTGRES_URL;
|
||||
|
||||
if (!POSTGRES_URL) {
|
||||
throw new Error(
|
||||
'ATELIER_POSTGRES_URL environment variable is required — set it in the platform .env to the ' +
|
||||
'`atelier` Postgres database this plugin should use. See plugins/atelier/PLUGIN.md.',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* One client per PROCESS, not one per module evaluation — see the standalone repo's original comment
|
||||
* (kept because the reasoning still holds under PM2's own occasional module reloads): a fresh pool per
|
||||
* reload abandons the previous one's open sockets, and a query in flight when the swap happens never
|
||||
* resolves and never releases its connection.
|
||||
*/
|
||||
const globalForDb = globalThis as unknown as { __atelierPgClient?: ReturnType<typeof postgres> };
|
||||
|
||||
const client = (globalForDb.__atelierPgClient ??= postgres(POSTGRES_URL, {
|
||||
max: 10,
|
||||
idle_timeout: 30,
|
||||
connect_timeout: 10,
|
||||
connection: {
|
||||
statement_timeout: 15_000,
|
||||
},
|
||||
}));
|
||||
export const db = drizzle(client, { schema });
|
||||
|
||||
/**
|
||||
* Block until Postgres answers, or give up after `timeoutMs`.
|
||||
*
|
||||
* `postgres()` is lazy — nothing fails at import when the database is not up yet — so startup work
|
||||
* that queries fires once, fails, and is swallowed by whatever `.catch()` it was written with. The
|
||||
* restart sweep that fails interrupted rows (`failInterrupted`) is exactly that kind of work.
|
||||
*/
|
||||
export async function waitForDatabase(timeoutMs = 30_000): Promise<boolean> {
|
||||
const started = Date.now();
|
||||
let announced = false;
|
||||
|
||||
for (;;) {
|
||||
try {
|
||||
await client`select 1`;
|
||||
if (announced) console.log('[atelier:db] Postgres is up');
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (Date.now() - started >= timeoutMs) {
|
||||
console.error(
|
||||
`[atelier:db] Postgres did not answer within ${Math.round(timeoutMs / 1000)}s:`,
|
||||
error instanceof Error ? error.message : error,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (!announced) {
|
||||
console.log('[atelier:db] waiting for Postgres…');
|
||||
announced = true;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 1_000));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user