Add registration, login, and logout flows with session management Integrate Resend for email verification tokens Create frontend auth views and update topbar state
189 lines
6.1 KiB
SQL
189 lines
6.1 KiB
SQL
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,
|
|
subcategory text,
|
|
UNIQUE (name, subcategory)
|
|
);
|
|
|
|
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
|
|
);
|
|
|
|
ALTER TABLE items ALTER COLUMN usage_id DROP NOT NULL;
|
|
|
|
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 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)
|
|
);
|