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,46 @@
|
||||
import { pgTable, uuid, text, timestamp, index, primaryKey } from 'drizzle-orm/pg-core';
|
||||
import { generations } from '../generations/schema';
|
||||
|
||||
/**
|
||||
* A named set of images — an afternoon's work, an icon suite, a client.
|
||||
*
|
||||
* `name` is unique, and that is load-bearing rather than tidy: the generation API accepts a
|
||||
* collection by name and creates it when it does not exist, so without the constraint a second
|
||||
* request with the same name would quietly start a second collection and split the batch in two.
|
||||
*/
|
||||
export const collections = pgTable('collections', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
name: text('name').notNull().unique(),
|
||||
description: text('description'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Membership, as a join table rather than a column on `generations`.
|
||||
*
|
||||
* An image belongs to as many collections as it earns: the icon that came out of Tuesday's session is
|
||||
* in "tuesday" and also in "shipped", and removing it from one must not touch the other. A
|
||||
* collection_id column would force a choice at generation time and make re-filing a destructive edit.
|
||||
*
|
||||
* Both sides cascade. Deleting a collection removes its memberships and NOT its images — a collection
|
||||
* is a way of looking at generations, not a container that owns them. Deleting a generation removes
|
||||
* its memberships, since a row pointing at nothing is only a way to crash a join later.
|
||||
*/
|
||||
export const collectionItems = pgTable(
|
||||
'collection_items',
|
||||
{
|
||||
collectionId: uuid('collection_id')
|
||||
.notNull()
|
||||
.references(() => collections.id, { onDelete: 'cascade' }),
|
||||
generationId: uuid('generation_id')
|
||||
.notNull()
|
||||
.references(() => generations.id, { onDelete: 'cascade' }),
|
||||
addedAt: timestamp('added_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(table) => [
|
||||
// The composite key is what makes adding an image twice a no-op instead of a duplicate.
|
||||
primaryKey({ columns: [table.collectionId, table.generationId] }),
|
||||
index('collection_items_generation_idx').on(table.generationId),
|
||||
],
|
||||
);
|
||||
Reference in New Issue
Block a user