import { boolean, check, date, numeric, index, integer, jsonb, 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 = refSchema.table('pokemon_research', { pokemonId: integer('pokemon_id') .primaryKey() .references(() => pokemon.id, { onDelete: 'cascade' }), facts: jsonb('facts') .$type<{ height: number; weight: number; baseExperience: number | null; captureRate: number; baseHappiness: number | null; growthRate: string; genderRate: number; hatchCounter: number | null; eggGroups: string[]; abilities: { name: string; hidden: boolean }[]; evYield: number[]; }>() .notNull() }); export const pokemonDexNumbers = refSchema.table( 'pokemon_dex_numbers', { id: id(), pokemonId: integer('pokemon_id') .notNull() .references(() => pokemon.id, { onDelete: 'cascade' }), gameId: integer('game_id') .notNull() .references(() => games.id, { onDelete: 'cascade' }), dex: text('dex').notNull(), dexName: text('dex_name').notNull(), number: integer('number').notNull() }, (t) => [unique().on(t.pokemonId, t.gameId, t.dex), index().on(t.gameId)] ); export const pokemonMoves = refSchema.table('pokemon_moves', { id: integer('id').primaryKey(), name: text('name').notNull(), type: text('type').notNull(), category: text('category').notNull(), power: integer('power'), accuracy: integer('accuracy'), pp: integer('pp'), priority: integer('priority').notNull(), target: text('target').notNull() }); export const pokemonLearnsets = refSchema.table( 'pokemon_learnsets', { id: id(), pokemonId: integer('pokemon_id') .notNull() .references(() => pokemon.id, { onDelete: 'cascade' }), versionGroup: integer('version_group').notNull(), moves: jsonb('moves').$type<[moveId: number, method: string, level: number][]>().notNull() }, (t) => [unique().on(t.pokemonId, t.versionGroup)] ); export const pokemonGameResearch = refSchema.table('pokemon_game_research', { gameId: integer('game_id') .primaryKey() .references(() => games.id, { onDelete: 'cascade' }), versionGroup: integer('version_group').notNull(), regions: text('regions').array().notNull() }); export const pokemonJourneys = gamesSchema.table( 'pokemon_journeys', { id: id(), userId: text('user_id') .notNull() .references(() => user.id, { onDelete: 'cascade' }), gameId: integer('game_id') .notNull() .references(() => games.id, { onDelete: 'cascade' }), stage: text('stage', { enum: ['story', 'postgame', 'collection'] }) .notNull() .default('story'), location: text('location'), completed: boolean('completed').notNull().default(false), ...timestamps }, (t) => [ unique().on(t.userId, t.gameId), check('pokemon_journeys_stage_check', sql`${t.stage} IN ('story','postgame','collection')`) ] ); export const pokemonSeen = gamesSchema.table( 'pokemon_seen', { id: id(), userId: text('user_id') .notNull() .references(() => user.id, { onDelete: 'cascade' }), gameId: integer('game_id') .notNull() .references(() => games.id, { onDelete: 'cascade' }), pokemonId: integer('pokemon_id') .notNull() .references(() => pokemon.id, { onDelete: 'cascade' }), ...timestamps }, (t) => [unique().on(t.userId, t.gameId, t.pokemonId), index().on(t.userId)] ); export const pokemonCatches = gamesSchema.table( 'pokemon_catches', { id: id(), userId: text('user_id') .notNull() .references(() => user.id, { onDelete: 'cascade' }), gameId: integer('game_id') .notNull() .references(() => games.id, { onDelete: 'cascade' }), pokemonId: integer('pokemon_id') .notNull() .references(() => pokemon.id, { onDelete: 'cascade' }), nickname: text('nickname').notNull().default(''), level: integer('level').notNull().default(1), moves: text('moves').array().notNull().default([]), partySlot: integer('party_slot'), shiny: boolean('shiny').notNull().default(false), nature: text('nature'), ability: text('ability'), ball: text('ball'), location: text('location'), obtained: text('obtained').notNull().default('caught'), stage: text('stage', { enum: ['story', 'postgame', 'collection'] }) .notNull() .default('story'), notes: text('notes').notNull().default(''), caughtOn: date('caught_on'), ...timestamps }, (t) => [ index().on(t.userId, t.gameId, t.pokemonId), unique().on(t.userId, t.gameId, t.partySlot), 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 = gamesSchema.table( 'pokemon_milestones', { id: id(), userId: text('user_id') .notNull() .references(() => user.id, { onDelete: 'cascade' }), gameId: integer('game_id') .notNull() .references(() => games.id, { onDelete: 'cascade' }), key: text('key').notNull(), title: text('title').notNull(), category: text('category').notNull(), stage: text('stage').notNull().default('story'), done: boolean('done').notNull().default(false), notes: text('notes').notNull().default(''), ...timestamps }, (t) => [unique().on(t.userId, t.gameId, t.key)] ); export const trackerRanks = gamesSchema.table( 'tracker_ranks', { id: id(), userId: text('user_id') .notNull() .references(() => user.id, { onDelete: 'cascade' }), gameId: integer('game_id') .notNull() .references(() => trackerGames.id, { onDelete: 'cascade' }), level: integer('level').notNull().default(1), prestige: integer('prestige').notNull().default(0), ...timestamps }, (t) => [ unique().on(t.userId, t.gameId), 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 = gamesSchema.table( 'tracker_weapon_ranks', { id: id(), userId: text('user_id') .notNull() .references(() => user.id, { onDelete: 'cascade' }), itemId: integer('item_id') .notNull() .references(() => trackerItems.id, { onDelete: 'cascade' }), level: integer('level').notNull().default(1), prestige: integer('prestige').notNull().default(0), ...timestamps }, (t) => [ unique().on(t.userId, t.itemId), 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 = refSchema.table( 'tracker_challenges', { id: id(), gameId: integer('game_id') .notNull() .references(() => trackerGames.id, { onDelete: 'cascade' }), key: text('key').notNull(), name: text('name').notNull(), category: text('category').notNull(), requirement: text('requirement').notNull(), reward: text('reward'), icon: text('icon'), target: integer('target'), sourceUrl: text('source_url').notNull() }, (t) => [unique().on(t.gameId, t.key)] ); export const trackerChallengeProgress = gamesSchema.table( 'tracker_challenge_progress', { id: id(), userId: text('user_id') .notNull() .references(() => user.id, { onDelete: 'cascade' }), challengeId: integer('challenge_id') .notNull() .references(() => trackerChallenges.id, { onDelete: 'cascade' }), current: integer('current').notNull().default(0), done: boolean('done').notNull().default(false), ...timestamps }, (t) => [ unique().on(t.userId, t.challengeId), check('tracker_challenge_progress_current_check', sql`${t.current} >= 0`) ] ); export const osuEntries = gamesSchema.table( 'osu_entries', { id: id(), userId: text('user_id') .notNull() .references(() => user.id, { onDelete: 'cascade' }), kind: text('kind', { enum: ['score', 'goal', 'training'] }).notNull(), title: text('title').notNull(), mode: text('mode').notNull().default('osu'), beatmapUrl: text('beatmap_url'), scoreUrl: text('score_url'), goal: text('goal').notNull().default('pass'), skill: text('skill').notNull().default('stamina'), bpm: integer('bpm'), duration: integer('duration'), accuracy: numeric('accuracy', { mode: 'number' }), combo: integer('combo'), misses: integer('misses'), pp: numeric('pp', { mode: 'number' }), mods: text('mods').notNull().default('NM'), 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), check('osu_entries_kind_check', sql`${t.kind} IN ('score','goal','training')`) ] ); export type PokemonCatch = typeof pokemonCatches.$inferSelect;