db: Drizzle schemas on domain Postgres schemas, baseline migration, reorg SQL
- pgSchema tables for auth, money, house, ref, games, cs, animaldex, site, meta (lists excluded via schemaFilter; runtime list tables are managed by meta.*) - drizzle/ baseline matches the reorged DB exactly (catalog diff = 0 on rehearsal) - db/reorg: one-time SQL that moves the flat public schema (rehearsed on a prod copy) - scripts/db-migrate.ts: migrator with --init to record the baseline and a guard against running the baseline on a non-empty, unrecorded database
This commit is contained in:
@@ -1,47 +1,92 @@
|
||||
import { boolean, index, integer, pgTable, text, unique } from 'drizzle-orm/pg-core';
|
||||
import { id, timestamps } from './helpers';
|
||||
import { user } from './auth';
|
||||
import {
|
||||
boolean,
|
||||
check,
|
||||
index,
|
||||
integer,
|
||||
jsonb,
|
||||
text,
|
||||
timestamp,
|
||||
unique
|
||||
} from 'drizzle-orm/pg-core';
|
||||
import { sql } from 'drizzle-orm';
|
||||
import { metaSchema } from './schemas';
|
||||
|
||||
export const lists = pgTable('lists', {
|
||||
id: id(),
|
||||
name: text('name').notNull(),
|
||||
slug: text('slug').notNull().unique(),
|
||||
pictureUrl: text('picture_url'),
|
||||
isProgress: boolean('is_progress').notNull().default(false),
|
||||
...timestamps
|
||||
});
|
||||
// Registry of GUI-visible tables. Runtime lists live in schema `lists` (one real
|
||||
// table per list, created through meta.create_list()); that schema is excluded
|
||||
// from drizzle-kit via schemaFilter. Structure truth is pg_catalog; these rows are
|
||||
// presentation. See src/lib/server/lists.ts for the list engine.
|
||||
const ts = (name: string) => timestamp(name, { withTimezone: true });
|
||||
|
||||
export const listItems = pgTable(
|
||||
'list_items',
|
||||
export const metaTables = metaSchema.table(
|
||||
'tables',
|
||||
{
|
||||
id: id(),
|
||||
listId: integer('list_id')
|
||||
.notNull()
|
||||
.references(() => lists.id, { onDelete: 'cascade' }),
|
||||
name: text('name').notNull(),
|
||||
pictureUrl: text('picture_url'),
|
||||
id: integer('id').primaryKey().generatedByDefaultAsIdentity(),
|
||||
schemaName: text('schema_name').notNull(),
|
||||
tableName: text('table_name').notNull(),
|
||||
slug: text('slug').notNull().unique('tables_slug_key'),
|
||||
displayName: text('display_name').notNull(),
|
||||
description: text('description'),
|
||||
...timestamps
|
||||
icon: text('icon'),
|
||||
pictureUrl: text('picture_url'),
|
||||
kind: text('kind', { enum: ['list', 'core', 'ref'] }).notNull(),
|
||||
editable: boolean('editable').notNull().default(true),
|
||||
hasDone: boolean('has_done').notNull().default(false),
|
||||
defaultSort: text('default_sort'),
|
||||
sortOrder: integer('sort_order').notNull().default(0),
|
||||
archivedAt: ts('archived_at'),
|
||||
createdAt: ts('created_at').notNull().defaultNow(),
|
||||
updatedAt: ts('updated_at').notNull().defaultNow()
|
||||
},
|
||||
(table) => [index().on(table.listId)]
|
||||
(t) => [
|
||||
unique('tables_schema_name_table_name_key').on(t.schemaName, t.tableName),
|
||||
check('tables_slug_check', sql`slug ~ '^[a-z0-9][a-z0-9-]{0,62}$'`),
|
||||
check('tables_kind_check', sql`kind IN ('list','core','ref')`)
|
||||
]
|
||||
);
|
||||
|
||||
export const listItemCompletions = pgTable(
|
||||
'list_item_completions',
|
||||
export const uiTypes = [
|
||||
'text',
|
||||
'longtext',
|
||||
'url',
|
||||
'image',
|
||||
'number',
|
||||
'cents',
|
||||
'date',
|
||||
'datetime',
|
||||
'bool',
|
||||
'select',
|
||||
'multiselect',
|
||||
'fk',
|
||||
'json'
|
||||
] as const;
|
||||
export type UiType = (typeof uiTypes)[number];
|
||||
|
||||
export const metaColumns = metaSchema.table(
|
||||
'columns',
|
||||
{
|
||||
id: id(),
|
||||
itemId: integer('item_id')
|
||||
id: integer('id').primaryKey().generatedByDefaultAsIdentity(),
|
||||
tableId: integer('table_id')
|
||||
.notNull()
|
||||
.references(() => listItems.id, { onDelete: 'cascade' }),
|
||||
userId: text('user_id')
|
||||
.notNull()
|
||||
.references(() => user.id, { onDelete: 'cascade' }),
|
||||
...timestamps
|
||||
.references(() => metaTables.id, { onDelete: 'cascade' }),
|
||||
columnName: text('column_name').notNull(),
|
||||
label: text('label'),
|
||||
uiType: text('ui_type', { enum: uiTypes }).notNull(),
|
||||
options: jsonb('options').$type<{ values?: string[] }>().notNull().default({}),
|
||||
position: integer('position').notNull().default(0),
|
||||
hidden: boolean('hidden').notNull().default(false),
|
||||
helpText: text('help_text'),
|
||||
createdAt: ts('created_at').notNull().defaultNow(),
|
||||
updatedAt: ts('updated_at').notNull().defaultNow()
|
||||
},
|
||||
(table) => [unique().on(table.itemId, table.userId), index().on(table.userId)]
|
||||
(t) => [
|
||||
unique('columns_table_id_column_name_key').on(t.tableId, t.columnName),
|
||||
index('columns_table_id_index').on(t.tableId),
|
||||
check(
|
||||
'columns_ui_type_check',
|
||||
sql`ui_type IN ('text','longtext','url','image','number','cents','date','datetime','bool','select','multiselect','fk','json')`
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
export type List = typeof lists.$inferSelect;
|
||||
export type NewList = typeof lists.$inferInsert;
|
||||
export type ListItem = typeof listItems.$inferSelect;
|
||||
export type NewListItem = typeof listItems.$inferInsert;
|
||||
export type MetaTable = typeof metaTables.$inferSelect;
|
||||
export type MetaColumn = typeof metaColumns.$inferSelect;
|
||||
|
||||
Reference in New Issue
Block a user