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.
46 lines
2.2 KiB
TypeScript
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),
|
|
],
|
|
);
|