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:
2026-10-23 22:03:42 +00:00
parent f94e3dc8ac
commit 64fb8df262
29 changed files with 12507 additions and 189 deletions

View File

@@ -1,16 +1,17 @@
import { boolean, index, integer, pgEnum, pgTable, text, unique } from 'drizzle-orm/pg-core';
import { boolean, index, integer, text, unique } from 'drizzle-orm/pg-core';
import { gamesSchema, refSchema } from './schemas';
import { availabilityValues } from '../../../availability';
import { user } from './auth';
import { id, timestamps } from './helpers';
export const availability = pgEnum('availability', availabilityValues);
export const availability = refSchema.enum('availability', availabilityValues);
// Where a catalog row came from. Seeding replaces `pokeapi` and `derived` rows
// but never touches `manual` ones, which are admin corrections.
export const catalogSources = ['pokeapi', 'derived', 'manual'] as const;
const source = () => text('source', { enum: catalogSources }).notNull().default('pokeapi');
export const pokemon = pgTable('pokemon', {
export const pokemon = refSchema.table('pokemon', {
id: id(),
number: integer('number').notNull().unique(),
generation: integer('generation').notNull().default(1),
@@ -32,7 +33,7 @@ export const pokemon = pgTable('pokemon', {
...timestamps
});
export const games = pgTable('games', {
export const games = refSchema.table('pokemon_games', {
id: id(),
sourceId: integer('source_id').unique(),
slug: text('slug').unique(),
@@ -43,7 +44,7 @@ export const games = pgTable('games', {
...timestamps
});
export const pokemonGameEntries = pgTable(
export const pokemonGameEntries = refSchema.table(
'pokemon_game_entries',
{
id: id(),
@@ -62,7 +63,7 @@ export const pokemonGameEntries = pgTable(
(table) => [unique().on(table.pokemonId, table.gameId), index().on(table.gameId)]
);
export const pokemonLocations = pgTable('pokemon_locations', {
export const pokemonLocations = refSchema.table('pokemon_locations', {
id: id(),
sourceId: integer('source_id').unique(),
slug: text('slug').notNull(),
@@ -76,7 +77,7 @@ export const pokemonLocations = pgTable('pokemon_locations', {
...timestamps
});
export const pokemonEncounters = pgTable(
export const pokemonEncounters = refSchema.table(
'pokemon_encounters',
{
id: id(),
@@ -106,7 +107,7 @@ export const pokemonEncounters = pgTable(
// The method that applies in each game, e.g. Magneton → Magnezone needs Mt.
// Coronet in Platinum but a Thunder Stone in Sword.
export const pokemonEvolutions = pgTable(
export const pokemonEvolutions = refSchema.table(
'pokemon_evolutions',
{
id: id(),
@@ -129,8 +130,8 @@ export const pokemonEvolutions = pgTable(
]
);
export const userEntries = pgTable(
'user_entries',
export const userEntries = gamesSchema.table(
'catch_em_all_entries',
{
id: id(),
pokemonGameEntryId: integer('pokemon_game_entry_id')
@@ -145,8 +146,8 @@ export const userEntries = pgTable(
);
// which games a user has chosen to track ("rules")
export const userGames = pgTable(
'user_games',
export const userGames = gamesSchema.table(
'catch_em_all_games',
{
id: id(),
userId: text('user_id')
@@ -161,7 +162,7 @@ export const userGames = pgTable(
);
// A row distinguishes an explicit empty game selection from a new account.
export const catchEmAllSettings = pgTable('catch_em_all_settings', {
export const catchEmAllSettings = gamesSchema.table('catch_em_all_settings', {
id: id(),
userId: text('user_id')
.notNull()