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

@@ -2,20 +2,21 @@ import {
boolean,
check,
date,
numeric,
index,
integer,
jsonb,
pgTable,
text,
unique
} from 'drizzle-orm/pg-core';
import { gamesSchema, refSchema } from './schemas';
import { sql } from 'drizzle-orm';
import { user } from './auth';
import { games, pokemon } from './catch-em-all';
import { trackerGames, trackerItems } from './game-tracker';
import { id, timestamps } from './helpers';
export const pokemonResearch = pgTable('pokemon_research', {
export const pokemonResearch = refSchema.table('pokemon_research', {
pokemonId: integer('pokemon_id')
.primaryKey()
.references(() => pokemon.id, { onDelete: 'cascade' }),
@@ -36,7 +37,7 @@ export const pokemonResearch = pgTable('pokemon_research', {
.notNull()
});
export const pokemonDexNumbers = pgTable(
export const pokemonDexNumbers = refSchema.table(
'pokemon_dex_numbers',
{
id: id(),
@@ -53,7 +54,7 @@ export const pokemonDexNumbers = pgTable(
(t) => [unique().on(t.pokemonId, t.gameId, t.dex), index().on(t.gameId)]
);
export const pokemonMoves = pgTable('pokemon_moves', {
export const pokemonMoves = refSchema.table('pokemon_moves', {
id: integer('id').primaryKey(),
name: text('name').notNull(),
type: text('type').notNull(),
@@ -65,7 +66,7 @@ export const pokemonMoves = pgTable('pokemon_moves', {
target: text('target').notNull()
});
export const pokemonLearnsets = pgTable(
export const pokemonLearnsets = refSchema.table(
'pokemon_learnsets',
{
id: id(),
@@ -78,7 +79,7 @@ export const pokemonLearnsets = pgTable(
(t) => [unique().on(t.pokemonId, t.versionGroup)]
);
export const pokemonGameResearch = pgTable('pokemon_game_research', {
export const pokemonGameResearch = refSchema.table('pokemon_game_research', {
gameId: integer('game_id')
.primaryKey()
.references(() => games.id, { onDelete: 'cascade' }),
@@ -86,7 +87,7 @@ export const pokemonGameResearch = pgTable('pokemon_game_research', {
regions: text('regions').array().notNull()
});
export const pokemonJourneys = pgTable(
export const pokemonJourneys = gamesSchema.table(
'pokemon_journeys',
{
id: id(),
@@ -103,10 +104,13 @@ export const pokemonJourneys = pgTable(
completed: boolean('completed').notNull().default(false),
...timestamps
},
(t) => [unique().on(t.userId, t.gameId)]
(t) => [
unique().on(t.userId, t.gameId),
check('pokemon_journeys_stage_check', sql`${t.stage} IN ('story','postgame','collection')`)
]
);
export const pokemonSeen = pgTable(
export const pokemonSeen = gamesSchema.table(
'pokemon_seen',
{
id: id(),
@@ -124,7 +128,7 @@ export const pokemonSeen = pgTable(
(t) => [unique().on(t.userId, t.gameId, t.pokemonId), index().on(t.userId)]
);
export const pokemonCatches = pgTable(
export const pokemonCatches = gamesSchema.table(
'pokemon_catches',
{
id: id(),
@@ -157,13 +161,14 @@ export const pokemonCatches = pgTable(
(t) => [
index().on(t.userId, t.gameId, t.pokemonId),
unique().on(t.userId, t.gameId, t.partySlot),
check('pokemon_catch_level', sql`${t.level} between 1 and 100`),
check('pokemon_party_slot', sql`${t.partySlot} between 1 and 6`),
check('pokemon_four_moves', sql`cardinality(${t.moves}) <= 4`)
check('pokemon_catches_level_check', sql`${t.level} between 1 and 100`),
check('pokemon_catches_party_slot_check', sql`${t.partySlot} between 1 and 6`),
check('pokemon_catches_moves_check', sql`cardinality(${t.moves}) <= 4`),
check('pokemon_catches_stage_check', sql`${t.stage} IN ('story','postgame','collection')`)
]
);
export const pokemonMilestones = pgTable(
export const pokemonMilestones = gamesSchema.table(
'pokemon_milestones',
{
id: id(),
@@ -184,7 +189,7 @@ export const pokemonMilestones = pgTable(
(t) => [unique().on(t.userId, t.gameId, t.key)]
);
export const trackerRanks = pgTable(
export const trackerRanks = gamesSchema.table(
'tracker_ranks',
{
id: id(),
@@ -200,14 +205,12 @@ export const trackerRanks = pgTable(
},
(t) => [
unique().on(t.userId, t.gameId),
check(
'tracker_rank_range',
sql`${t.level} between 1 and 10000 and ${t.prestige} between 0 and 1000`
)
check('tracker_ranks_level_check', sql`${t.level} between 1 and 10000`),
check('tracker_ranks_prestige_check', sql`${t.prestige} between 0 and 1000`)
]
);
export const trackerWeaponRanks = pgTable(
export const trackerWeaponRanks = gamesSchema.table(
'tracker_weapon_ranks',
{
id: id(),
@@ -223,14 +226,12 @@ export const trackerWeaponRanks = pgTable(
},
(t) => [
unique().on(t.userId, t.itemId),
check(
'tracker_weapon_rank_range',
sql`${t.level} between 1 and 10000 and ${t.prestige} between 0 and 1000`
)
check('tracker_weapon_ranks_level_check', sql`${t.level} between 1 and 10000`),
check('tracker_weapon_ranks_prestige_check', sql`${t.prestige} between 0 and 1000`)
]
);
export const trackerChallenges = pgTable(
export const trackerChallenges = refSchema.table(
'tracker_challenges',
{
id: id(),
@@ -249,7 +250,7 @@ export const trackerChallenges = pgTable(
(t) => [unique().on(t.gameId, t.key)]
);
export const trackerChallengeProgress = pgTable(
export const trackerChallengeProgress = gamesSchema.table(
'tracker_challenge_progress',
{
id: id(),
@@ -263,10 +264,13 @@ export const trackerChallengeProgress = pgTable(
done: boolean('done').notNull().default(false),
...timestamps
},
(t) => [unique().on(t.userId, t.challengeId)]
(t) => [
unique().on(t.userId, t.challengeId),
check('tracker_challenge_progress_current_check', sql`${t.current} >= 0`)
]
);
export const osuEntries = pgTable(
export const osuEntries = gamesSchema.table(
'osu_entries',
{
id: id(),
@@ -282,18 +286,23 @@ export const osuEntries = pgTable(
skill: text('skill').notNull().default('stamina'),
bpm: integer('bpm'),
duration: integer('duration'),
accuracy: text('accuracy'),
accuracy: numeric('accuracy', { mode: 'number' }),
combo: integer('combo'),
misses: integer('misses'),
pp: text('pp'),
pp: numeric('pp', { mode: 'number' }),
mods: text('mods').notNull().default('NM'),
rate: text('rate').notNull().default('1.00'),
rate: numeric('rate', { precision: 4, scale: 2, mode: 'number' })
.notNull()
.default(sql`1.00`),
achieved: boolean('achieved').notNull().default(false),
playedOn: date('played_on').notNull(),
notes: text('notes').notNull().default(''),
...timestamps
},
(t) => [index().on(t.userId, t.kind)]
(t) => [
index().on(t.userId, t.kind),
check('osu_entries_kind_check', sql`${t.kind} IN ('score','goal','training')`)
]
);
export type PokemonCatch = typeof pokemonCatches.$inferSelect;