Expand website and game trackers
Some checks failed
Test and deploy staging / staging (push) Failing after 5m29s

This commit is contained in:
2026-09-25 22:43:04 +00:00
parent f6b34150d4
commit c2ba8febb4
4076 changed files with 97237 additions and 1453 deletions

View File

@@ -0,0 +1,299 @@
import {
boolean,
check,
date,
index,
integer,
jsonb,
pgTable,
text,
unique
} from 'drizzle-orm/pg-core';
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', {
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 = pgTable(
'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 = pgTable('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 = pgTable(
'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 = pgTable('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 = pgTable(
'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)]
);
export const pokemonSeen = pgTable(
'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 = pgTable(
'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_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`)
]
);
export const pokemonMilestones = pgTable(
'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 = pgTable(
'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_rank_range',
sql`${t.level} between 1 and 10000 and ${t.prestige} between 0 and 1000`
)
]
);
export const trackerWeaponRanks = pgTable(
'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_rank_range',
sql`${t.level} between 1 and 10000 and ${t.prestige} between 0 and 1000`
)
]
);
export const trackerChallenges = pgTable(
'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 = pgTable(
'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)]
);
export const osuEntries = pgTable(
'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: text('accuracy'),
combo: integer('combo'),
misses: integer('misses'),
pp: text('pp'),
mods: text('mods').notNull().default('NM'),
rate: text('rate').notNull().default('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)]
);
export type PokemonCatch = typeof pokemonCatches.$inferSelect;