CREATE TABLE IF NOT EXISTS environments ( id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, name text NOT NULL UNIQUE ); CREATE TABLE IF NOT EXISTS users ( id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, email text NOT NULL UNIQUE, display_name text NOT NULL, password_hash text NOT NULL, email_verified_at timestamptz, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), CHECK (email = lower(email)), CHECK (length(display_name) BETWEEN 1 AND 40) ); CREATE TABLE IF NOT EXISTS email_verification_tokens ( id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, user_id integer NOT NULL REFERENCES users(id) ON DELETE CASCADE, token_hash text NOT NULL UNIQUE, expires_at timestamptz NOT NULL, used_at timestamptz, created_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS email_verification_tokens_user_id_idx ON email_verification_tokens(user_id); CREATE TABLE IF NOT EXISTS user_sessions ( id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, user_id integer NOT NULL REFERENCES users(id) ON DELETE CASCADE, token_hash text NOT NULL UNIQUE, expires_at timestamptz NOT NULL, created_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS user_sessions_user_id_idx ON user_sessions(user_id); CREATE TABLE IF NOT EXISTS skills ( id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, name text NOT NULL UNIQUE, has_item_drop boolean NOT NULL DEFAULT false ); ALTER TABLE skills DROP COLUMN IF EXISTS subcategory; ALTER TABLE skills ADD COLUMN IF NOT EXISTS has_item_drop boolean NOT NULL DEFAULT false; CREATE UNIQUE INDEX IF NOT EXISTS skills_name_key ON skills(name); CREATE TABLE IF NOT EXISTS favorite_things ( id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, name text NOT NULL UNIQUE ); CREATE TABLE IF NOT EXISTS pokemon ( id integer PRIMARY KEY, name text NOT NULL UNIQUE, environment_id integer NOT NULL REFERENCES environments(id) ); CREATE TABLE IF NOT EXISTS pokemon_skills ( pokemon_id integer NOT NULL REFERENCES pokemon(id) ON DELETE CASCADE, skill_id integer NOT NULL REFERENCES skills(id), PRIMARY KEY (pokemon_id, skill_id) ); CREATE TABLE IF NOT EXISTS pokemon_favorite_things ( pokemon_id integer NOT NULL REFERENCES pokemon(id) ON DELETE CASCADE, favorite_thing_id integer NOT NULL REFERENCES favorite_things(id), PRIMARY KEY (pokemon_id, favorite_thing_id) ); CREATE TABLE IF NOT EXISTS item_categories ( id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, name text NOT NULL UNIQUE ); CREATE TABLE IF NOT EXISTS item_usages ( id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, name text NOT NULL UNIQUE ); CREATE TABLE IF NOT EXISTS acquisition_methods ( id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, name text NOT NULL UNIQUE ); CREATE TABLE IF NOT EXISTS items ( id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, name text NOT NULL UNIQUE, category_id integer NOT NULL REFERENCES item_categories(id), usage_id integer REFERENCES item_usages(id), dyeable boolean NOT NULL DEFAULT false, dual_dyeable boolean NOT NULL DEFAULT false, pattern_editable boolean NOT NULL DEFAULT false, no_recipe boolean NOT NULL DEFAULT false ); ALTER TABLE items ALTER COLUMN usage_id DROP NOT NULL; ALTER TABLE items ADD COLUMN IF NOT EXISTS no_recipe boolean NOT NULL DEFAULT false; ALTER TABLE items DROP COLUMN IF EXISTS no_habitat; CREATE TABLE IF NOT EXISTS recipes ( id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, item_id integer NOT NULL UNIQUE REFERENCES items(id) ); ALTER TABLE recipes ADD COLUMN IF NOT EXISTS item_id integer REFERENCES items(id); DO $$ BEGIN IF EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_name = 'items' AND column_name = 'recipe_id' ) THEN EXECUTE ' UPDATE recipes r SET item_id = linked.item_id FROM ( SELECT DISTINCT ON (recipe_id) recipe_id, id AS item_id FROM items WHERE recipe_id IS NOT NULL ORDER BY recipe_id, id ) linked WHERE r.id = linked.recipe_id AND r.item_id IS NULL '; END IF; END $$; DELETE FROM recipes WHERE item_id IS NULL; ALTER TABLE recipes ALTER COLUMN item_id SET NOT NULL; CREATE UNIQUE INDEX IF NOT EXISTS recipes_item_id_key ON recipes(item_id); ALTER TABLE recipes DROP COLUMN IF EXISTS name; ALTER TABLE items DROP COLUMN IF EXISTS recipe_id; CREATE TABLE IF NOT EXISTS recipe_acquisition_methods ( recipe_id integer NOT NULL REFERENCES recipes(id) ON DELETE CASCADE, acquisition_method_id integer NOT NULL REFERENCES acquisition_methods(id), PRIMARY KEY (recipe_id, acquisition_method_id) ); DROP TABLE IF EXISTS item_item_tags; DROP TABLE IF EXISTS item_tags; CREATE TABLE IF NOT EXISTS item_acquisition_methods ( item_id integer NOT NULL REFERENCES items(id) ON DELETE CASCADE, acquisition_method_id integer NOT NULL REFERENCES acquisition_methods(id), PRIMARY KEY (item_id, acquisition_method_id) ); CREATE TABLE IF NOT EXISTS item_favorite_things ( item_id integer NOT NULL REFERENCES items(id) ON DELETE CASCADE, favorite_thing_id integer NOT NULL REFERENCES favorite_things(id), PRIMARY KEY (item_id, favorite_thing_id) ); CREATE TABLE IF NOT EXISTS pokemon_skill_item_drops ( pokemon_id integer NOT NULL, skill_id integer NOT NULL, item_id integer NOT NULL REFERENCES items(id), PRIMARY KEY (pokemon_id, skill_id), FOREIGN KEY (pokemon_id, skill_id) REFERENCES pokemon_skills(pokemon_id, skill_id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS recipe_materials ( recipe_id integer NOT NULL REFERENCES recipes(id) ON DELETE CASCADE, item_id integer NOT NULL REFERENCES items(id), quantity integer NOT NULL CHECK (quantity > 0), PRIMARY KEY (recipe_id, item_id) ); CREATE TABLE IF NOT EXISTS maps ( id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, name text NOT NULL UNIQUE ); CREATE TABLE IF NOT EXISTS habitats ( id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, name text NOT NULL UNIQUE ); CREATE TABLE IF NOT EXISTS habitat_recipe_items ( habitat_id integer NOT NULL REFERENCES habitats(id) ON DELETE CASCADE, item_id integer NOT NULL REFERENCES items(id), quantity integer NOT NULL CHECK (quantity > 0), PRIMARY KEY (habitat_id, item_id) ); CREATE TABLE IF NOT EXISTS habitat_pokemon ( habitat_id integer NOT NULL REFERENCES habitats(id) ON DELETE CASCADE, pokemon_id integer NOT NULL REFERENCES pokemon(id) ON DELETE CASCADE, map_id integer NOT NULL REFERENCES maps(id), time_of_day text NOT NULL CHECK (time_of_day IN ('早晨', '中午', '傍晚', '晚上')), weather text NOT NULL CHECK (weather IN ('晴天', '阴天', '雨天')), rarity integer NOT NULL CHECK (rarity BETWEEN 1 AND 3), PRIMARY KEY (habitat_id, pokemon_id, map_id, time_of_day, weather) ); ALTER TABLE environments ADD COLUMN IF NOT EXISTS created_by_user_id integer REFERENCES users(id) ON DELETE SET NULL; ALTER TABLE environments ADD COLUMN IF NOT EXISTS updated_by_user_id integer REFERENCES users(id) ON DELETE SET NULL; ALTER TABLE environments ADD COLUMN IF NOT EXISTS created_at timestamptz NOT NULL DEFAULT now(); ALTER TABLE environments ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT now(); ALTER TABLE skills ADD COLUMN IF NOT EXISTS created_by_user_id integer REFERENCES users(id) ON DELETE SET NULL; ALTER TABLE skills ADD COLUMN IF NOT EXISTS updated_by_user_id integer REFERENCES users(id) ON DELETE SET NULL; ALTER TABLE skills ADD COLUMN IF NOT EXISTS created_at timestamptz NOT NULL DEFAULT now(); ALTER TABLE skills ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT now(); ALTER TABLE favorite_things ADD COLUMN IF NOT EXISTS created_by_user_id integer REFERENCES users(id) ON DELETE SET NULL; ALTER TABLE favorite_things ADD COLUMN IF NOT EXISTS updated_by_user_id integer REFERENCES users(id) ON DELETE SET NULL; ALTER TABLE favorite_things ADD COLUMN IF NOT EXISTS created_at timestamptz NOT NULL DEFAULT now(); ALTER TABLE favorite_things ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT now(); ALTER TABLE pokemon ADD COLUMN IF NOT EXISTS created_by_user_id integer REFERENCES users(id) ON DELETE SET NULL; ALTER TABLE pokemon ADD COLUMN IF NOT EXISTS updated_by_user_id integer REFERENCES users(id) ON DELETE SET NULL; ALTER TABLE pokemon ADD COLUMN IF NOT EXISTS created_at timestamptz NOT NULL DEFAULT now(); ALTER TABLE pokemon ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT now(); ALTER TABLE item_categories ADD COLUMN IF NOT EXISTS created_by_user_id integer REFERENCES users(id) ON DELETE SET NULL; ALTER TABLE item_categories ADD COLUMN IF NOT EXISTS updated_by_user_id integer REFERENCES users(id) ON DELETE SET NULL; ALTER TABLE item_categories ADD COLUMN IF NOT EXISTS created_at timestamptz NOT NULL DEFAULT now(); ALTER TABLE item_categories ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT now(); ALTER TABLE item_usages ADD COLUMN IF NOT EXISTS created_by_user_id integer REFERENCES users(id) ON DELETE SET NULL; ALTER TABLE item_usages ADD COLUMN IF NOT EXISTS updated_by_user_id integer REFERENCES users(id) ON DELETE SET NULL; ALTER TABLE item_usages ADD COLUMN IF NOT EXISTS created_at timestamptz NOT NULL DEFAULT now(); ALTER TABLE item_usages ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT now(); ALTER TABLE acquisition_methods ADD COLUMN IF NOT EXISTS created_by_user_id integer REFERENCES users(id) ON DELETE SET NULL; ALTER TABLE acquisition_methods ADD COLUMN IF NOT EXISTS updated_by_user_id integer REFERENCES users(id) ON DELETE SET NULL; ALTER TABLE acquisition_methods ADD COLUMN IF NOT EXISTS created_at timestamptz NOT NULL DEFAULT now(); ALTER TABLE acquisition_methods ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT now(); ALTER TABLE items ADD COLUMN IF NOT EXISTS created_by_user_id integer REFERENCES users(id) ON DELETE SET NULL; ALTER TABLE items ADD COLUMN IF NOT EXISTS updated_by_user_id integer REFERENCES users(id) ON DELETE SET NULL; ALTER TABLE items ADD COLUMN IF NOT EXISTS created_at timestamptz NOT NULL DEFAULT now(); ALTER TABLE items ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT now(); ALTER TABLE recipes ADD COLUMN IF NOT EXISTS created_by_user_id integer REFERENCES users(id) ON DELETE SET NULL; ALTER TABLE recipes ADD COLUMN IF NOT EXISTS updated_by_user_id integer REFERENCES users(id) ON DELETE SET NULL; ALTER TABLE recipes ADD COLUMN IF NOT EXISTS created_at timestamptz NOT NULL DEFAULT now(); ALTER TABLE recipes ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT now(); ALTER TABLE maps ADD COLUMN IF NOT EXISTS created_by_user_id integer REFERENCES users(id) ON DELETE SET NULL; ALTER TABLE maps ADD COLUMN IF NOT EXISTS updated_by_user_id integer REFERENCES users(id) ON DELETE SET NULL; ALTER TABLE maps ADD COLUMN IF NOT EXISTS created_at timestamptz NOT NULL DEFAULT now(); ALTER TABLE maps ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT now(); ALTER TABLE habitats ADD COLUMN IF NOT EXISTS created_by_user_id integer REFERENCES users(id) ON DELETE SET NULL; ALTER TABLE habitats ADD COLUMN IF NOT EXISTS updated_by_user_id integer REFERENCES users(id) ON DELETE SET NULL; ALTER TABLE habitats ADD COLUMN IF NOT EXISTS created_at timestamptz NOT NULL DEFAULT now(); ALTER TABLE habitats ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT now(); CREATE TABLE IF NOT EXISTS wiki_edit_logs ( id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, entity_type text NOT NULL, entity_id integer NOT NULL, action text NOT NULL CHECK (action IN ('create', 'update', 'delete')), user_id integer REFERENCES users(id) ON DELETE SET NULL, created_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS wiki_edit_logs_entity_idx ON wiki_edit_logs(entity_type, entity_id, created_at DESC); CREATE INDEX IF NOT EXISTS wiki_edit_logs_user_id_idx ON wiki_edit_logs(user_id);