Files
atelier/sidecar/db/collections/schema.ts
T
pastilhas c892f2e700 officer_db with atelier_ prefixes, like every other plugin
the extraction kept atelier's own postgres database, because the brief said to
when the tables already had rows. the owner reversed it, and the reason is not
tidiness: a plugin that behaves differently from every other plugin is a
plugin nobody can reason about.

the separate database meant a second connection string to set before install,
a second thing to back up, a second thing to get wrong on a new machine, and
failure modes no other plugin shares — none of it bought anything except
avoiding a migration.

four tables now: atelier_collections, atelier_collection_items,
atelier_providers, atelier_generations. index and check constraint names carry
the prefix too — those are global in postgres and are the one thing that would
otherwise collide with another plugin.

db/schema.ts re-exports the definitions rather than duplicating them: they stay
beside the code that queries them in sidecar/db/, and that file exists to be
found by gen-plugin-schemas.

ATELIER_POSTGRES_URL is gone. db.ts reads OFFICERDEV_POSTGRES_URL ?? POSTGRES_URL,
which the platform already sets.

the standalone service's rows are NOT here and nothing depends on them being
here — the old service is still running against the old database, untouched,
and migrating is a separate watched step after this is proven on alpha.
2026-08-20 17:30:53 +00:00

46 lines
2.2 KiB
TypeScript

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('atelier_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('atelier_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('atelier_collection_items_generation_idx').on(table.generationId),
],
);