Files
website/src/lib/server/db/schema/lists.ts
Stan Runge 9d08e6af23
All checks were successful
Deploy production / deploy (push) Successful in 2m33s
Lists: owner-only database browser/editor for all tables (meta.edit_log audit)
2026-10-24 02:52:18 +00:00

118 lines
3.6 KiB
TypeScript

import {
bigint,
boolean,
check,
index,
integer,
jsonb,
text,
timestamp,
unique
} from 'drizzle-orm/pg-core';
import { sql } from 'drizzle-orm';
import { metaSchema } from './schemas';
// 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 metaTables = metaSchema.table(
'tables',
{
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'),
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()
},
(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 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: integer('id').primaryKey().generatedByDefaultAsIdentity(),
tableId: integer('table_id')
.notNull()
.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()
},
(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')`
)
]
);
/** Audit trail of every write made through the database browser (/lists/db). */
export const metaEditLog = metaSchema.table(
'edit_log',
{
id: bigint('id', { mode: 'number' }).primaryKey().generatedAlwaysAsIdentity(),
at: ts('at').notNull().defaultNow(),
userId: text('user_id'),
userEmail: text('user_email'),
schemaName: text('schema_name').notNull(),
tableName: text('table_name').notNull(),
action: text('action').notNull(),
pk: jsonb('pk'),
before: jsonb('before'),
after: jsonb('after')
},
(t) => [
index('edit_log_table_at_index').on(t.schemaName, t.tableName, t.at),
check(
'edit_log_action_check',
sql`action IN ('insert','update','delete','import','add_column')`
)
]
);
export type MetaTable = typeof metaTables.$inferSelect;
export type MetaColumn = typeof metaColumns.$inferSelect;