-- Game tracker: owned Pokémon locations/encounters/evolutions and checklist games. -- Additive and repeatable. Apply before seeding (`npm run db:seed`, `npm run db:seed:cod`). ALTER TYPE availability ADD VALUE IF NOT EXISTS 'evolve'; ALTER TYPE availability ADD VALUE IF NOT EXISTS 'breed'; ALTER TABLE pokemon ADD COLUMN IF NOT EXISTS evolves_from integer; ALTER TABLE pokemon ADD COLUMN IF NOT EXISTS evolution_chain integer; ALTER TABLE pokemon ADD COLUMN IF NOT EXISTS is_baby boolean NOT NULL DEFAULT false; ALTER TABLE pokemon ADD COLUMN IF NOT EXISTS is_legendary boolean NOT NULL DEFAULT false; ALTER TABLE pokemon ADD COLUMN IF NOT EXISTS is_mythical boolean NOT NULL DEFAULT false; ALTER TABLE games ADD COLUMN IF NOT EXISTS slug text; ALTER TABLE games ADD COLUMN IF NOT EXISTS sort_order integer NOT NULL DEFAULT 0; ALTER TABLE games ADD COLUMN IF NOT EXISTS breeding boolean NOT NULL DEFAULT true; DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'games_slug_key') THEN ALTER TABLE games ADD CONSTRAINT games_slug_key UNIQUE (slug); END IF; END $$; -- Seeds used to mark their own rows with a "PokéAPI:" note prefix; anything else -- was hand-maintained and must survive reseeding. DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'pokemon_game_entries' AND column_name = 'source') THEN ALTER TABLE pokemon_game_entries ADD COLUMN source text NOT NULL DEFAULT 'pokeapi'; UPDATE pokemon_game_entries SET source = 'manual' WHERE note IS NULL OR note NOT LIKE 'PokéAPI:%'; END IF; END $$; CREATE TABLE IF NOT EXISTS pokemon_locations ( id serial PRIMARY KEY, source_id integer UNIQUE, slug text NOT NULL, name text NOT NULL, region text, description text, image_key text, image_credit text, source text NOT NULL DEFAULT 'pokeapi', created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS pokemon_encounters ( id serial PRIMARY KEY, pokemon_id integer NOT NULL, game_id integer NOT NULL, location_id integer NOT NULL, area text, method text NOT NULL, min_level integer, max_level integer, rarity integer, form text, conditions text[] NOT NULL DEFAULT '{}'::text[], note text, source text NOT NULL DEFAULT 'pokeapi', created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT pokemon_encounters_pokemon_id_pokemon_id_fkey FOREIGN KEY (pokemon_id) REFERENCES pokemon(id) ON DELETE CASCADE, CONSTRAINT pokemon_encounters_game_id_games_id_fkey FOREIGN KEY (game_id) REFERENCES games(id) ON DELETE CASCADE, CONSTRAINT pokemon_encounters_location_id_pokemon_locations_id_fkey FOREIGN KEY (location_id) REFERENCES pokemon_locations(id) ON DELETE CASCADE ); CREATE INDEX IF NOT EXISTS pokemon_encounters_pokemon_id_game_id_index ON pokemon_encounters (pokemon_id, game_id); CREATE INDEX IF NOT EXISTS pokemon_encounters_location_id_index ON pokemon_encounters (location_id); CREATE TABLE IF NOT EXISTS pokemon_evolutions ( id serial PRIMARY KEY, from_pokemon_id integer NOT NULL, to_pokemon_id integer NOT NULL, game_id integer NOT NULL, method text NOT NULL, source text NOT NULL DEFAULT 'pokeapi', created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT pokemon_evolutions_from_pokemon_id_to_pokemon_id_game_id_method_unique UNIQUE (from_pokemon_id, to_pokemon_id, game_id, method), CONSTRAINT pokemon_evolutions_from_pokemon_id_pokemon_id_fkey FOREIGN KEY (from_pokemon_id) REFERENCES pokemon(id) ON DELETE CASCADE, CONSTRAINT pokemon_evolutions_to_pokemon_id_pokemon_id_fkey FOREIGN KEY (to_pokemon_id) REFERENCES pokemon(id) ON DELETE CASCADE, CONSTRAINT pokemon_evolutions_game_id_games_id_fkey FOREIGN KEY (game_id) REFERENCES games(id) ON DELETE CASCADE ); CREATE INDEX IF NOT EXISTS pokemon_evolutions_game_id_index ON pokemon_evolutions (game_id); CREATE TABLE IF NOT EXISTS tracker_games ( id serial PRIMARY KEY, slug text NOT NULL UNIQUE, franchise text NOT NULL, name text NOT NULL, short_name text NOT NULL, release_year integer, sort_order integer NOT NULL DEFAULT 0, rules jsonb NOT NULL, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS tracker_items ( id serial PRIMARY KEY, game_id integer NOT NULL, key text NOT NULL, kind text NOT NULL, category text NOT NULL, name text NOT NULL, description text, icon text, post_launch boolean NOT NULL DEFAULT false, sort_order integer NOT NULL DEFAULT 0, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT tracker_items_game_id_key_unique UNIQUE (game_id, key), CONSTRAINT tracker_items_game_id_tracker_games_id_fkey FOREIGN KEY (game_id) REFERENCES tracker_games(id) ON DELETE CASCADE ); CREATE INDEX IF NOT EXISTS tracker_items_game_id_index ON tracker_items (game_id); CREATE TABLE IF NOT EXISTS tracker_progress ( id serial PRIMARY KEY, user_id text NOT NULL, item_id integer NOT NULL, milestone text NOT NULL DEFAULT 'done', created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT tracker_progress_user_id_item_id_milestone_unique UNIQUE (user_id, item_id, milestone), CONSTRAINT tracker_progress_user_id_user_id_fkey FOREIGN KEY (user_id) REFERENCES "user"(id) ON DELETE CASCADE, CONSTRAINT tracker_progress_item_id_tracker_items_id_fkey FOREIGN KEY (item_id) REFERENCES tracker_items(id) ON DELETE CASCADE ); CREATE INDEX IF NOT EXISTS tracker_progress_user_id_index ON tracker_progress (user_id);