feat(dish): add dish management and public view

Add database schema, permissions, and API endpoints for dishes
Implement frontend views and admin management for dish data
This commit is contained in:
2026-05-04 21:00:23 +08:00
parent 2ff2519647
commit 2220d5d595
12 changed files with 2147 additions and 25 deletions

View File

@@ -35,12 +35,15 @@ CREATE TABLE IF NOT EXISTS entity_translations (
'habitats',
'daily-checklist-items',
'life-tags',
'game-versions'
'game-versions',
'dish-categories',
'dish-flavors',
'dishes'
)
),
entity_id integer NOT NULL,
locale text NOT NULL REFERENCES languages(code) ON DELETE CASCADE,
field_name text NOT NULL CHECK (field_name IN ('name', 'title', 'details', 'genus')),
field_name text NOT NULL CHECK (field_name IN ('name', 'title', 'details', 'genus', 'effect', 'mosslaxEffect')),
value text NOT NULL,
PRIMARY KEY (entity_type, entity_id, locale, field_name)
);
@@ -68,10 +71,21 @@ ALTER TABLE entity_translations
'habitats',
'daily-checklist-items',
'life-tags',
'game-versions'
'game-versions',
'dish-categories',
'dish-flavors',
'dishes'
)
);
ALTER TABLE entity_translations
DROP CONSTRAINT IF EXISTS entity_translations_field_name_check;
ALTER TABLE entity_translations
ADD CONSTRAINT entity_translations_field_name_check CHECK (
field_name IN ('name', 'title', 'details', 'genus', 'effect', 'mosslaxEffect')
);
CREATE TABLE IF NOT EXISTS users (
id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
email text NOT NULL UNIQUE,
@@ -291,6 +305,10 @@ VALUES
('recipes.update', 'Update recipes', 'Edit recipe records.', 'Recipes', true),
('recipes.delete', 'Delete recipes', 'Delete recipe records.', 'Recipes', true),
('recipes.order', 'Order recipes', 'Reorder recipe records.', 'Recipes', true),
('dish.create', 'Create Dish records', 'Create Dish categories and dish records.', 'Dish', true),
('dish.update', 'Update Dish records', 'Edit Dish categories and dish records.', 'Dish', true),
('dish.delete', 'Delete Dish records', 'Delete Dish categories and dish records.', 'Dish', true),
('dish.order', 'Order Dish records', 'Reorder Dish categories and dish records.', 'Dish', true),
('life.posts.create', 'Create Life posts', 'Create Life posts.', 'Life', true),
('life.posts.update', 'Update own Life posts', 'Edit own Life posts.', 'Life', true),
('life.posts.delete', 'Delete own Life posts', 'Delete own Life posts.', 'Life', true),
@@ -385,6 +403,10 @@ JOIN permissions p ON p.key = ANY (ARRAY[
'recipes.update',
'recipes.delete',
'recipes.order',
'dish.create',
'dish.update',
'dish.delete',
'dish.order',
'life.posts.create',
'life.posts.update',
'life.posts.delete',
@@ -459,6 +481,9 @@ JOIN permissions p ON p.key = ANY (ARRAY[
'recipes.create',
'recipes.update',
'recipes.order',
'dish.create',
'dish.update',
'dish.order',
'life.posts.create',
'life.posts.update',
'life.posts.delete',
@@ -505,6 +530,29 @@ JOIN permissions p ON p.key = ANY (ARRAY[
WHERE r.key = 'editor'
ON CONFLICT DO NOTHING;
INSERT INTO role_permissions (role_id, permission_id)
SELECT r.id, p.id
FROM roles r
JOIN permissions p ON p.key = ANY (ARRAY[
'dish.create',
'dish.update',
'dish.delete',
'dish.order'
])
WHERE r.key = 'admin'
ON CONFLICT DO NOTHING;
INSERT INTO role_permissions (role_id, permission_id)
SELECT r.id, p.id
FROM roles r
JOIN permissions p ON p.key = ANY (ARRAY[
'dish.create',
'dish.update',
'dish.order'
])
WHERE r.key = 'editor'
ON CONFLICT DO NOTHING;
INSERT INTO role_permissions (role_id, permission_id)
SELECT r.id, p.id
FROM roles r
@@ -1024,6 +1072,112 @@ CREATE TABLE IF NOT EXISTS recipe_materials (
PRIMARY KEY (recipe_id, item_id)
);
CREATE TABLE IF NOT EXISTS dish_categories (
id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name text NOT NULL UNIQUE,
cookware_item_id integer NOT NULL REFERENCES items(id),
main_material_item_id integer NOT NULL REFERENCES items(id),
total_material_quantity integer NOT NULL DEFAULT 2 CHECK (total_material_quantity >= 2),
effect text NOT NULL DEFAULT '',
sort_order integer NOT NULL DEFAULT 0 CHECK (sort_order >= 0),
created_by_user_id integer REFERENCES users(id) ON DELETE SET NULL,
updated_by_user_id integer REFERENCES users(id) ON DELETE SET NULL,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
ALTER TABLE dish_categories
ADD COLUMN IF NOT EXISTS main_material_item_id integer REFERENCES items(id);
ALTER TABLE dish_categories
ADD COLUMN IF NOT EXISTS total_material_quantity integer NOT NULL DEFAULT 2;
DO $$
BEGIN
IF to_regclass('public.dishes') IS NOT NULL
AND EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'dishes'
AND column_name = 'main_material_item_id'
)
THEN
EXECUTE '
UPDATE dish_categories dc
SET main_material_item_id = source.main_material_item_id
FROM (
SELECT DISTINCT ON (category_id) category_id, main_material_item_id
FROM dishes
WHERE main_material_item_id IS NOT NULL
ORDER BY category_id, sort_order, id
) AS source
WHERE dc.id = source.category_id
AND dc.main_material_item_id IS NULL
';
END IF;
END $$;
UPDATE dish_categories
SET main_material_item_id = cookware_item_id
WHERE main_material_item_id IS NULL;
ALTER TABLE dish_categories
ALTER COLUMN main_material_item_id SET NOT NULL;
ALTER TABLE dish_categories
ALTER COLUMN total_material_quantity SET DEFAULT 2;
UPDATE dish_categories
SET total_material_quantity = 2
WHERE total_material_quantity < 2;
ALTER TABLE dish_categories
DROP CONSTRAINT IF EXISTS dish_categories_total_material_quantity_check;
ALTER TABLE dish_categories
ADD CONSTRAINT dish_categories_total_material_quantity_check CHECK (total_material_quantity >= 2);
CREATE TABLE IF NOT EXISTS dish_flavors (
id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name text NOT NULL UNIQUE,
sort_order integer NOT NULL DEFAULT 0 CHECK (sort_order >= 0),
created_by_user_id integer REFERENCES users(id) ON DELETE SET NULL,
updated_by_user_id integer REFERENCES users(id) ON DELETE SET NULL,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS dishes (
id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
category_id integer NOT NULL REFERENCES dish_categories(id) ON DELETE CASCADE,
item_id integer NOT NULL UNIQUE REFERENCES items(id),
flavor_id integer NOT NULL REFERENCES dish_flavors(id),
secondary_material_1_item_id integer REFERENCES items(id),
secondary_material_2_item_id integer REFERENCES items(id),
pokemon_skill_id integer REFERENCES skills(id),
mosslax_effect text NOT NULL DEFAULT '',
sort_order integer NOT NULL DEFAULT 0 CHECK (sort_order >= 0),
created_by_user_id integer REFERENCES users(id) ON DELETE SET NULL,
updated_by_user_id integer REFERENCES users(id) ON DELETE SET NULL,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
CHECK (
secondary_material_1_item_id IS NULL
OR secondary_material_2_item_id IS NULL
OR secondary_material_1_item_id <> secondary_material_2_item_id
)
);
ALTER TABLE dishes
ADD COLUMN IF NOT EXISTS flavor_id integer REFERENCES dish_flavors(id);
ALTER TABLE dishes
ALTER COLUMN secondary_material_1_item_id DROP NOT NULL;
ALTER TABLE dishes
DROP COLUMN IF EXISTS main_material_item_id;
CREATE TABLE IF NOT EXISTS maps (
id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name text NOT NULL UNIQUE,
@@ -1193,6 +1347,10 @@ CREATE INDEX IF NOT EXISTS items_display_order_idx ON items(is_event_item, displ
CREATE INDEX IF NOT EXISTS ancient_artifacts_sort_order_idx ON ancient_artifacts(sort_order, id);
CREATE INDEX IF NOT EXISTS ancient_artifacts_display_order_idx ON ancient_artifacts(display_id, sort_order, id);
CREATE INDEX IF NOT EXISTS recipes_sort_order_idx ON recipes(sort_order, id);
CREATE INDEX IF NOT EXISTS dish_categories_sort_order_idx ON dish_categories(sort_order, id);
CREATE INDEX IF NOT EXISTS dish_flavors_sort_order_idx ON dish_flavors(sort_order, id);
CREATE INDEX IF NOT EXISTS dishes_category_sort_order_idx ON dishes(category_id, sort_order, id);
CREATE INDEX IF NOT EXISTS dishes_sort_order_idx ON dishes(sort_order, id);
CREATE INDEX IF NOT EXISTS maps_sort_order_idx ON maps(sort_order, id);
CREATE INDEX IF NOT EXISTS habitats_sort_order_idx ON habitats(sort_order, id);