refactor(backend): localize validation errors and consolidate schema
Replace hardcoded validation error messages with i18n keys. Merge ALTER TABLE statements into initial CREATE TABLE definitions. Clean up obsolete data migration scripts from schema file.
This commit is contained in:
@@ -1,9 +1,3 @@
|
||||
CREATE TABLE IF NOT EXISTS environments (
|
||||
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)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS languages (
|
||||
code text PRIMARY KEY,
|
||||
name text NOT NULL,
|
||||
@@ -52,27 +46,6 @@ CREATE TABLE IF NOT EXISTS entity_translations (
|
||||
CREATE INDEX IF NOT EXISTS entity_translations_lookup_idx
|
||||
ON entity_translations (entity_type, entity_id, field_name, locale);
|
||||
|
||||
ALTER TABLE entity_translations DROP CONSTRAINT IF EXISTS entity_translations_entity_type_check;
|
||||
ALTER TABLE entity_translations ADD CONSTRAINT entity_translations_entity_type_check CHECK (
|
||||
entity_type IN (
|
||||
'pokemon',
|
||||
'pokemon-types',
|
||||
'skills',
|
||||
'environments',
|
||||
'favorite-things',
|
||||
'item-categories',
|
||||
'item-usages',
|
||||
'acquisition-methods',
|
||||
'items',
|
||||
'maps',
|
||||
'habitats',
|
||||
'daily-checklist-items',
|
||||
'life-tags'
|
||||
)
|
||||
);
|
||||
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'));
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
email text NOT NULL UNIQUE,
|
||||
@@ -88,11 +61,6 @@ CREATE TABLE IF NOT EXISTS users (
|
||||
CHECK (referral_code IS NULL OR referral_code ~ '^[A-Z0-9]{8,16}$')
|
||||
);
|
||||
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS referral_code text;
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS referred_by_user_id integer REFERENCES users(id) ON DELETE SET NULL;
|
||||
ALTER TABLE users DROP CONSTRAINT IF EXISTS users_referral_code_check;
|
||||
ALTER TABLE users ADD CONSTRAINT users_referral_code_check CHECK (referral_code IS NULL OR referral_code ~ '^[A-Z0-9]{8,16}$');
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS users_referral_code_idx
|
||||
ON users(referral_code)
|
||||
WHERE referral_code IS NOT NULL;
|
||||
@@ -100,6 +68,16 @@ CREATE UNIQUE INDEX IF NOT EXISTS users_referral_code_idx
|
||||
CREATE INDEX IF NOT EXISTS users_referred_by_user_id_idx
|
||||
ON users(referred_by_user_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS environments (
|
||||
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 roles (
|
||||
id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
key text NOT NULL UNIQUE,
|
||||
@@ -114,11 +92,6 @@ CREATE TABLE IF NOT EXISTS roles (
|
||||
CHECK (length(name) BETWEEN 1 AND 80)
|
||||
);
|
||||
|
||||
ALTER TABLE roles ADD COLUMN IF NOT EXISTS description text NOT NULL DEFAULT '';
|
||||
ALTER TABLE roles ADD COLUMN IF NOT EXISTS level integer NOT NULL DEFAULT 0 CHECK (level >= 0);
|
||||
ALTER TABLE roles ADD COLUMN IF NOT EXISTS enabled boolean NOT NULL DEFAULT true;
|
||||
ALTER TABLE roles ADD COLUMN IF NOT EXISTS system_role boolean NOT NULL DEFAULT false;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS roles_level_idx
|
||||
ON roles(level DESC, id);
|
||||
|
||||
@@ -137,11 +110,6 @@ CREATE TABLE IF NOT EXISTS permissions (
|
||||
CHECK (length(category) BETWEEN 1 AND 80)
|
||||
);
|
||||
|
||||
ALTER TABLE permissions ADD COLUMN IF NOT EXISTS description text NOT NULL DEFAULT '';
|
||||
ALTER TABLE permissions ADD COLUMN IF NOT EXISTS category text NOT NULL DEFAULT 'General';
|
||||
ALTER TABLE permissions ADD COLUMN IF NOT EXISTS enabled boolean NOT NULL DEFAULT true;
|
||||
ALTER TABLE permissions ADD COLUMN IF NOT EXISTS system_permission boolean NOT NULL DEFAULT false;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS permissions_category_idx
|
||||
ON permissions(category, key);
|
||||
|
||||
@@ -227,9 +195,7 @@ VALUES
|
||||
('discussions.comments.create', 'Create discussion comments', 'Create entity discussion comments and replies.', 'Discussions', true),
|
||||
('discussions.comments.delete', 'Delete own discussion comments', 'Delete own entity discussion comments.', 'Discussions', true),
|
||||
('discussions.comments.delete-any', 'Delete any discussion comment', 'Delete any entity discussion comment.', 'Discussions', true)
|
||||
ON CONFLICT (key) DO UPDATE
|
||||
SET system_permission = true
|
||||
WHERE permissions.system_permission = false;
|
||||
ON CONFLICT (key) DO NOTHING;
|
||||
|
||||
INSERT INTO roles (key, name, description, level, enabled, system_role)
|
||||
VALUES
|
||||
@@ -238,9 +204,7 @@ VALUES
|
||||
('editor', 'Editor', 'Wiki editor with content creation, update, sorting and community permissions.', 500, true, true),
|
||||
('member', 'Member', 'Community member with Life and discussion permissions.', 100, true, true),
|
||||
('viewer', 'Viewer', 'Read-only role for explicit access grouping.', 0, true, true)
|
||||
ON CONFLICT (key) DO UPDATE
|
||||
SET system_role = true
|
||||
WHERE roles.system_role = false;
|
||||
ON CONFLICT (key) DO NOTHING;
|
||||
|
||||
INSERT INTO role_permissions (role_id, permission_id)
|
||||
SELECT r.id, p.id
|
||||
@@ -506,11 +470,6 @@ CREATE TABLE IF NOT EXISTS life_posts (
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE life_posts DROP COLUMN IF EXISTS link_url;
|
||||
ALTER TABLE life_posts DROP COLUMN IF EXISTS link_title;
|
||||
ALTER TABLE life_posts ADD COLUMN IF NOT EXISTS deleted_by_user_id integer REFERENCES users(id) ON DELETE SET NULL;
|
||||
ALTER TABLE life_posts ADD COLUMN IF NOT EXISTS deleted_at timestamptz;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS life_posts_created_at_idx
|
||||
ON life_posts(created_at DESC, id DESC);
|
||||
|
||||
@@ -561,23 +520,31 @@ 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,
|
||||
sort_order integer NOT NULL DEFAULT 0 CHECK (sort_order >= 0)
|
||||
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 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,
|
||||
sort_order integer NOT NULL DEFAULT 0 CHECK (sort_order >= 0)
|
||||
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 pokemon_types (
|
||||
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)
|
||||
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 pokemon (
|
||||
@@ -601,7 +568,11 @@ CREATE TABLE IF NOT EXISTS pokemon (
|
||||
image_version text NOT NULL DEFAULT '',
|
||||
image_variant text NOT NULL DEFAULT '',
|
||||
image_description text NOT NULL DEFAULT '',
|
||||
sort_order integer NOT NULL DEFAULT 0 CHECK (sort_order >= 0)
|
||||
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 pokemon_pokemon_types (
|
||||
@@ -627,19 +598,31 @@ CREATE TABLE IF NOT EXISTS pokemon_favorite_things (
|
||||
CREATE TABLE IF NOT EXISTS item_categories (
|
||||
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)
|
||||
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 item_usages (
|
||||
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)
|
||||
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 acquisition_methods (
|
||||
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)
|
||||
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 items (
|
||||
@@ -653,62 +636,29 @@ CREATE TABLE IF NOT EXISTS items (
|
||||
no_recipe boolean NOT NULL DEFAULT false,
|
||||
is_event_item boolean NOT NULL DEFAULT false,
|
||||
image_path text NOT NULL DEFAULT '',
|
||||
sort_order integer NOT NULL DEFAULT 0 CHECK (sort_order >= 0)
|
||||
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 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 ADD COLUMN IF NOT EXISTS is_event_item boolean NOT NULL DEFAULT false;
|
||||
ALTER TABLE items ADD COLUMN IF NOT EXISTS image_path text NOT NULL DEFAULT '';
|
||||
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),
|
||||
sort_order integer NOT NULL DEFAULT 0 CHECK (sort_order >= 0)
|
||||
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 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),
|
||||
@@ -739,7 +689,11 @@ CREATE TABLE IF NOT EXISTS recipe_materials (
|
||||
CREATE TABLE IF NOT EXISTS maps (
|
||||
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)
|
||||
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 habitats (
|
||||
@@ -747,7 +701,11 @@ CREATE TABLE IF NOT EXISTS habitats (
|
||||
name text NOT NULL UNIQUE,
|
||||
is_event_item boolean NOT NULL DEFAULT false,
|
||||
image_path text NOT NULL DEFAULT '',
|
||||
sort_order integer NOT NULL DEFAULT 0 CHECK (sort_order >= 0)
|
||||
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 habitat_recipe_items (
|
||||
@@ -767,236 +725,6 @@ CREATE TABLE IF NOT EXISTS habitat_pokemon (
|
||||
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 environments ADD COLUMN IF NOT EXISTS sort_order integer NOT NULL DEFAULT 0 CHECK (sort_order >= 0);
|
||||
|
||||
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 skills ADD COLUMN IF NOT EXISTS sort_order integer NOT NULL DEFAULT 0 CHECK (sort_order >= 0);
|
||||
|
||||
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 favorite_things ADD COLUMN IF NOT EXISTS sort_order integer NOT NULL DEFAULT 0 CHECK (sort_order >= 0);
|
||||
|
||||
ALTER TABLE pokemon_types ADD COLUMN IF NOT EXISTS created_by_user_id integer REFERENCES users(id) ON DELETE SET NULL;
|
||||
ALTER TABLE pokemon_types ADD COLUMN IF NOT EXISTS updated_by_user_id integer REFERENCES users(id) ON DELETE SET NULL;
|
||||
ALTER TABLE pokemon_types ADD COLUMN IF NOT EXISTS created_at timestamptz NOT NULL DEFAULT now();
|
||||
ALTER TABLE pokemon_types ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT now();
|
||||
ALTER TABLE pokemon_types ADD COLUMN IF NOT EXISTS sort_order integer NOT NULL DEFAULT 0 CHECK (sort_order >= 0);
|
||||
|
||||
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 pokemon ADD COLUMN IF NOT EXISTS sort_order integer NOT NULL DEFAULT 0 CHECK (sort_order >= 0);
|
||||
ALTER TABLE pokemon ADD COLUMN IF NOT EXISTS display_id integer;
|
||||
UPDATE pokemon SET display_id = id WHERE display_id IS NULL;
|
||||
ALTER TABLE pokemon ALTER COLUMN display_id SET NOT NULL;
|
||||
ALTER TABLE pokemon ADD COLUMN IF NOT EXISTS is_event_item boolean NOT NULL DEFAULT false;
|
||||
ALTER TABLE pokemon ADD COLUMN IF NOT EXISTS genus text NOT NULL DEFAULT '';
|
||||
ALTER TABLE pokemon ADD COLUMN IF NOT EXISTS details text NOT NULL DEFAULT '';
|
||||
ALTER TABLE pokemon ADD COLUMN IF NOT EXISTS height_inches double precision NOT NULL DEFAULT 0 CHECK (height_inches >= 0);
|
||||
ALTER TABLE pokemon ADD COLUMN IF NOT EXISTS weight_pounds double precision NOT NULL DEFAULT 0 CHECK (weight_pounds >= 0);
|
||||
ALTER TABLE pokemon ADD COLUMN IF NOT EXISTS hp integer NOT NULL DEFAULT 0 CHECK (hp >= 0);
|
||||
ALTER TABLE pokemon ADD COLUMN IF NOT EXISTS attack integer NOT NULL DEFAULT 0 CHECK (attack >= 0);
|
||||
ALTER TABLE pokemon ADD COLUMN IF NOT EXISTS defense integer NOT NULL DEFAULT 0 CHECK (defense >= 0);
|
||||
ALTER TABLE pokemon ADD COLUMN IF NOT EXISTS special_attack integer NOT NULL DEFAULT 0 CHECK (special_attack >= 0);
|
||||
ALTER TABLE pokemon ADD COLUMN IF NOT EXISTS special_defense integer NOT NULL DEFAULT 0 CHECK (special_defense >= 0);
|
||||
ALTER TABLE pokemon ADD COLUMN IF NOT EXISTS speed integer NOT NULL DEFAULT 0 CHECK (speed >= 0);
|
||||
ALTER TABLE pokemon ADD COLUMN IF NOT EXISTS image_path text NOT NULL DEFAULT '';
|
||||
ALTER TABLE pokemon ADD COLUMN IF NOT EXISTS image_style text NOT NULL DEFAULT '';
|
||||
ALTER TABLE pokemon ADD COLUMN IF NOT EXISTS image_version text NOT NULL DEFAULT '';
|
||||
ALTER TABLE pokemon ADD COLUMN IF NOT EXISTS image_variant text NOT NULL DEFAULT '';
|
||||
ALTER TABLE pokemon ADD COLUMN IF NOT EXISTS image_description text NOT NULL DEFAULT '';
|
||||
|
||||
ALTER TABLE life_tags ADD COLUMN IF NOT EXISTS created_by_user_id integer REFERENCES users(id) ON DELETE SET NULL;
|
||||
ALTER TABLE life_tags ADD COLUMN IF NOT EXISTS updated_by_user_id integer REFERENCES users(id) ON DELETE SET NULL;
|
||||
ALTER TABLE life_tags ADD COLUMN IF NOT EXISTS created_at timestamptz NOT NULL DEFAULT now();
|
||||
ALTER TABLE life_tags ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT now();
|
||||
ALTER TABLE life_tags ADD COLUMN IF NOT EXISTS sort_order integer NOT NULL DEFAULT 0 CHECK (sort_order >= 0);
|
||||
|
||||
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_categories ADD COLUMN IF NOT EXISTS sort_order integer NOT NULL DEFAULT 0 CHECK (sort_order >= 0);
|
||||
|
||||
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 item_usages ADD COLUMN IF NOT EXISTS sort_order integer NOT NULL DEFAULT 0 CHECK (sort_order >= 0);
|
||||
|
||||
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 acquisition_methods ADD COLUMN IF NOT EXISTS sort_order integer NOT NULL DEFAULT 0 CHECK (sort_order >= 0);
|
||||
|
||||
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 items ADD COLUMN IF NOT EXISTS sort_order integer NOT NULL DEFAULT 0 CHECK (sort_order >= 0);
|
||||
ALTER TABLE items ADD COLUMN IF NOT EXISTS is_event_item boolean NOT NULL DEFAULT false;
|
||||
|
||||
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 recipes ADD COLUMN IF NOT EXISTS sort_order integer NOT NULL DEFAULT 0 CHECK (sort_order >= 0);
|
||||
|
||||
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 maps ADD COLUMN IF NOT EXISTS sort_order integer NOT NULL DEFAULT 0 CHECK (sort_order >= 0);
|
||||
|
||||
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();
|
||||
ALTER TABLE habitats ADD COLUMN IF NOT EXISTS sort_order integer NOT NULL DEFAULT 0 CHECK (sort_order >= 0);
|
||||
ALTER TABLE habitats ADD COLUMN IF NOT EXISTS is_event_item boolean NOT NULL DEFAULT false;
|
||||
ALTER TABLE habitats ADD COLUMN IF NOT EXISTS image_path text NOT NULL DEFAULT '';
|
||||
|
||||
WITH ordered AS (
|
||||
SELECT id, (row_number() OVER (ORDER BY created_at, id) * 10)::integer AS next_sort_order
|
||||
FROM environments
|
||||
WHERE sort_order = 0
|
||||
)
|
||||
UPDATE environments target
|
||||
SET sort_order = ordered.next_sort_order
|
||||
FROM ordered
|
||||
WHERE target.id = ordered.id;
|
||||
|
||||
WITH ordered AS (
|
||||
SELECT id, (row_number() OVER (ORDER BY created_at, id) * 10)::integer AS next_sort_order
|
||||
FROM skills
|
||||
WHERE sort_order = 0
|
||||
)
|
||||
UPDATE skills target
|
||||
SET sort_order = ordered.next_sort_order
|
||||
FROM ordered
|
||||
WHERE target.id = ordered.id;
|
||||
|
||||
WITH ordered AS (
|
||||
SELECT id, (row_number() OVER (ORDER BY created_at, id) * 10)::integer AS next_sort_order
|
||||
FROM favorite_things
|
||||
WHERE sort_order = 0
|
||||
)
|
||||
UPDATE favorite_things target
|
||||
SET sort_order = ordered.next_sort_order
|
||||
FROM ordered
|
||||
WHERE target.id = ordered.id;
|
||||
|
||||
WITH ordered AS (
|
||||
SELECT id, (row_number() OVER (ORDER BY created_at, id) * 10)::integer AS next_sort_order
|
||||
FROM pokemon_types
|
||||
WHERE sort_order = 0
|
||||
)
|
||||
UPDATE pokemon_types target
|
||||
SET sort_order = ordered.next_sort_order
|
||||
FROM ordered
|
||||
WHERE target.id = ordered.id;
|
||||
|
||||
WITH ordered AS (
|
||||
SELECT id, (row_number() OVER (ORDER BY created_at, id) * 10)::integer AS next_sort_order
|
||||
FROM pokemon
|
||||
WHERE sort_order = 0
|
||||
)
|
||||
UPDATE pokemon target
|
||||
SET sort_order = ordered.next_sort_order
|
||||
FROM ordered
|
||||
WHERE target.id = ordered.id;
|
||||
|
||||
WITH ordered AS (
|
||||
SELECT id, (row_number() OVER (ORDER BY created_at, id) * 10)::integer AS next_sort_order
|
||||
FROM life_tags
|
||||
WHERE sort_order = 0
|
||||
)
|
||||
UPDATE life_tags target
|
||||
SET sort_order = ordered.next_sort_order
|
||||
FROM ordered
|
||||
WHERE target.id = ordered.id;
|
||||
|
||||
WITH ordered AS (
|
||||
SELECT id, (row_number() OVER (ORDER BY created_at, id) * 10)::integer AS next_sort_order
|
||||
FROM item_categories
|
||||
WHERE sort_order = 0
|
||||
)
|
||||
UPDATE item_categories target
|
||||
SET sort_order = ordered.next_sort_order
|
||||
FROM ordered
|
||||
WHERE target.id = ordered.id;
|
||||
|
||||
WITH ordered AS (
|
||||
SELECT id, (row_number() OVER (ORDER BY created_at, id) * 10)::integer AS next_sort_order
|
||||
FROM item_usages
|
||||
WHERE sort_order = 0
|
||||
)
|
||||
UPDATE item_usages target
|
||||
SET sort_order = ordered.next_sort_order
|
||||
FROM ordered
|
||||
WHERE target.id = ordered.id;
|
||||
|
||||
WITH ordered AS (
|
||||
SELECT id, (row_number() OVER (ORDER BY created_at, id) * 10)::integer AS next_sort_order
|
||||
FROM acquisition_methods
|
||||
WHERE sort_order = 0
|
||||
)
|
||||
UPDATE acquisition_methods target
|
||||
SET sort_order = ordered.next_sort_order
|
||||
FROM ordered
|
||||
WHERE target.id = ordered.id;
|
||||
|
||||
WITH ordered AS (
|
||||
SELECT id, (row_number() OVER (ORDER BY created_at, id) * 10)::integer AS next_sort_order
|
||||
FROM items
|
||||
WHERE sort_order = 0
|
||||
)
|
||||
UPDATE items target
|
||||
SET sort_order = ordered.next_sort_order
|
||||
FROM ordered
|
||||
WHERE target.id = ordered.id;
|
||||
|
||||
WITH ordered AS (
|
||||
SELECT id, (row_number() OVER (ORDER BY created_at, id) * 10)::integer AS next_sort_order
|
||||
FROM recipes
|
||||
WHERE sort_order = 0
|
||||
)
|
||||
UPDATE recipes target
|
||||
SET sort_order = ordered.next_sort_order
|
||||
FROM ordered
|
||||
WHERE target.id = ordered.id;
|
||||
|
||||
WITH ordered AS (
|
||||
SELECT id, (row_number() OVER (ORDER BY created_at, id) * 10)::integer AS next_sort_order
|
||||
FROM maps
|
||||
WHERE sort_order = 0
|
||||
)
|
||||
UPDATE maps target
|
||||
SET sort_order = ordered.next_sort_order
|
||||
FROM ordered
|
||||
WHERE target.id = ordered.id;
|
||||
|
||||
WITH ordered AS (
|
||||
SELECT id, (row_number() OVER (ORDER BY created_at, id) * 10)::integer AS next_sort_order
|
||||
FROM habitats
|
||||
WHERE sort_order = 0
|
||||
)
|
||||
UPDATE habitats target
|
||||
SET sort_order = ordered.next_sort_order
|
||||
FROM ordered
|
||||
WHERE target.id = ordered.id;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS environments_sort_order_idx ON environments(sort_order, id);
|
||||
CREATE INDEX IF NOT EXISTS skills_sort_order_idx ON skills(sort_order, id);
|
||||
CREATE INDEX IF NOT EXISTS favorite_things_sort_order_idx ON favorite_things(sort_order, id);
|
||||
@@ -1022,8 +750,6 @@ CREATE TABLE IF NOT EXISTS wiki_edit_logs (
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE wiki_edit_logs ADD COLUMN IF NOT EXISTS changes jsonb NOT NULL DEFAULT '[]'::jsonb;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS wiki_edit_logs_entity_idx
|
||||
ON wiki_edit_logs(entity_type, entity_id, created_at DESC);
|
||||
|
||||
@@ -1045,16 +771,6 @@ CREATE TABLE IF NOT EXISTS entity_image_uploads (
|
||||
CHECK (path !~ '(^/|\\.\\.)')
|
||||
);
|
||||
|
||||
ALTER TABLE entity_image_uploads DROP CONSTRAINT IF EXISTS entity_image_uploads_entity_type_check;
|
||||
ALTER TABLE entity_image_uploads ADD CONSTRAINT entity_image_uploads_entity_type_check CHECK (
|
||||
entity_type IN ('pokemon', 'items', 'habitats')
|
||||
);
|
||||
|
||||
ALTER TABLE entity_image_uploads DROP CONSTRAINT IF EXISTS entity_image_uploads_mime_type_check;
|
||||
ALTER TABLE entity_image_uploads ADD CONSTRAINT entity_image_uploads_mime_type_check CHECK (
|
||||
mime_type IN ('image/png', 'image/jpeg', 'image/webp', 'image/gif')
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS entity_image_uploads_entity_idx
|
||||
ON entity_image_uploads(entity_type, entity_id, created_at DESC, id DESC);
|
||||
|
||||
@@ -1074,15 +790,6 @@ CREATE TABLE IF NOT EXISTS entity_discussion_comments (
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE entity_discussion_comments DROP CONSTRAINT IF EXISTS entity_discussion_comments_entity_type_check;
|
||||
ALTER TABLE entity_discussion_comments ADD CONSTRAINT entity_discussion_comments_entity_type_check CHECK (
|
||||
entity_type IN ('pokemon', 'items', 'recipes', 'habitats')
|
||||
);
|
||||
|
||||
ALTER TABLE entity_discussion_comments ADD COLUMN IF NOT EXISTS deleted_by_user_id integer REFERENCES users(id) ON DELETE SET NULL;
|
||||
ALTER TABLE entity_discussion_comments ADD COLUMN IF NOT EXISTS deleted_at timestamptz;
|
||||
ALTER TABLE entity_discussion_comments ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT now();
|
||||
|
||||
CREATE INDEX IF NOT EXISTS entity_discussion_comments_entity_idx
|
||||
ON entity_discussion_comments(entity_type, entity_id, created_at, id);
|
||||
|
||||
|
||||
@@ -617,7 +617,7 @@ function requirePositiveInteger(value: unknown, message: string): number {
|
||||
return numberValue;
|
||||
}
|
||||
|
||||
function cleanName(value: unknown, message = 'Name is required'): string {
|
||||
function cleanName(value: unknown, message = 'server.validation.nameRequired'): string {
|
||||
if (typeof value !== 'string' || value.trim() === '') {
|
||||
throw validationError(message);
|
||||
}
|
||||
@@ -656,7 +656,7 @@ function cleanPokemonStats(value: unknown): PokemonStats {
|
||||
return pokemonStatLabels.reduce((stats, stat) => {
|
||||
const numberValue = Number(row[stat.key] ?? 0);
|
||||
if (!Number.isInteger(numberValue) || numberValue < 0) {
|
||||
throw validationError(`${stat.label} must be a non-negative integer`);
|
||||
throw validationError('server.validation.statNonNegative');
|
||||
}
|
||||
|
||||
return { ...stats, [stat.key]: numberValue };
|
||||
@@ -756,7 +756,7 @@ async function reorderTableRows(
|
||||
);
|
||||
|
||||
if (existing.rowCount !== ids.length) {
|
||||
throw validationError('Record does not exist');
|
||||
throw validationError('server.validation.recordMissing');
|
||||
}
|
||||
|
||||
for (const [index, id] of ids.entries()) {
|
||||
@@ -792,14 +792,14 @@ async function recordEditLog(
|
||||
function cleanLanguagePayload(payload: Record<string, unknown>, requireCode: boolean): LanguagePayload {
|
||||
const code = typeof payload.code === 'string' ? payload.code.trim() : '';
|
||||
if (requireCode && !localePattern.test(code)) {
|
||||
throw validationError('Language code is invalid');
|
||||
throw validationError('server.validation.languageCodeInvalid');
|
||||
}
|
||||
|
||||
const sortOrder = Number(payload.sortOrder ?? 0);
|
||||
|
||||
return {
|
||||
code,
|
||||
name: cleanName(payload.name, 'Language name is required'),
|
||||
name: cleanName(payload.name, 'server.validation.languageNameRequired'),
|
||||
enabled: payload.enabled !== false,
|
||||
isDefault: Boolean(payload.isDefault),
|
||||
sortOrder: Number.isInteger(sortOrder) && sortOrder >= 0 ? sortOrder : 0
|
||||
@@ -809,7 +809,7 @@ function cleanLanguagePayload(payload: Record<string, unknown>, requireCode: boo
|
||||
function requireLanguageCode(value: unknown): string {
|
||||
const code = typeof value === 'string' ? value.trim() : '';
|
||||
if (!localePattern.test(code)) {
|
||||
throw validationError('Language code is invalid');
|
||||
throw validationError('server.validation.languageCodeInvalid');
|
||||
}
|
||||
return code;
|
||||
}
|
||||
@@ -828,10 +828,10 @@ export async function listLanguages(includeDisabled = false) {
|
||||
export async function createLanguage(payload: Record<string, unknown>) {
|
||||
const cleanPayload = cleanLanguagePayload(payload, true);
|
||||
if (cleanPayload.isDefault && cleanPayload.code !== defaultLocale) {
|
||||
throw validationError('Default language must be English');
|
||||
throw validationError('server.validation.defaultLanguageMustBeEnglish');
|
||||
}
|
||||
if (!cleanPayload.enabled && cleanPayload.isDefault) {
|
||||
throw validationError('Default language must be enabled');
|
||||
throw validationError('server.validation.defaultLanguageMustBeEnabled');
|
||||
}
|
||||
|
||||
await withTransaction(async (client) => {
|
||||
@@ -855,10 +855,10 @@ export async function updateLanguage(code: string, payload: Record<string, unkno
|
||||
const locale = requireLanguageCode(code);
|
||||
const cleanPayload = cleanLanguagePayload({ ...payload, code: locale }, false);
|
||||
if (cleanPayload.isDefault && locale !== defaultLocale) {
|
||||
throw validationError('Default language must be English');
|
||||
throw validationError('server.validation.defaultLanguageMustBeEnglish');
|
||||
}
|
||||
if (!cleanPayload.enabled && cleanPayload.isDefault) {
|
||||
throw validationError('Default language must be enabled');
|
||||
throw validationError('server.validation.defaultLanguageMustBeEnabled');
|
||||
}
|
||||
|
||||
await withTransaction(async (client) => {
|
||||
@@ -868,15 +868,15 @@ export async function updateLanguage(code: string, payload: Record<string, unkno
|
||||
);
|
||||
|
||||
if (current.rowCount === 0) {
|
||||
throw validationError('Language not found');
|
||||
throw validationError('server.validation.languageNotFound');
|
||||
}
|
||||
|
||||
if (!cleanPayload.enabled && current.rows[0].isDefault) {
|
||||
throw validationError('Default language must be enabled');
|
||||
throw validationError('server.validation.defaultLanguageMustBeEnabled');
|
||||
}
|
||||
|
||||
if (current.rows[0].isDefault && !cleanPayload.isDefault) {
|
||||
throw validationError('A default language is required');
|
||||
throw validationError('server.validation.defaultLanguageRequired');
|
||||
}
|
||||
|
||||
if (cleanPayload.isDefault) {
|
||||
@@ -902,7 +902,7 @@ export async function updateLanguage(code: string, payload: Record<string, unkno
|
||||
export async function deleteLanguage(code: string) {
|
||||
const locale = requireLanguageCode(code);
|
||||
if (locale === defaultLocale) {
|
||||
throw validationError('Default language cannot be deleted');
|
||||
throw validationError('server.validation.defaultLanguageCannotBeDeleted');
|
||||
}
|
||||
|
||||
return withTransaction(async (client) => {
|
||||
@@ -917,7 +917,7 @@ export async function deleteLanguage(code: string) {
|
||||
export async function reorderLanguages(payload: Record<string, unknown>) {
|
||||
const codes = Array.isArray(payload.codes) ? payload.codes.map(requireLanguageCode) : [];
|
||||
if (codes.length === 0) {
|
||||
throw validationError('Please select a language');
|
||||
throw validationError('server.validation.selectLanguage');
|
||||
}
|
||||
|
||||
await withTransaction(async (client) => {
|
||||
@@ -927,7 +927,7 @@ export async function reorderLanguages(payload: Record<string, unknown>) {
|
||||
);
|
||||
|
||||
if (existing.rowCount !== codes.length) {
|
||||
throw validationError('Language does not exist');
|
||||
throw validationError('server.validation.languageDoesNotExist');
|
||||
}
|
||||
|
||||
for (const [index, code] of codes.entries()) {
|
||||
@@ -992,7 +992,7 @@ function parseCsv(content: string, fileName: string): CsvRow[] {
|
||||
|
||||
const headers = rows[0]?.map((header) => header.replace(/^\uFEFF/, ''));
|
||||
if (!headers?.length) {
|
||||
throw validationError(`${fileName} is empty`);
|
||||
throw validationError('server.validation.pokemonDataFileEmpty');
|
||||
}
|
||||
|
||||
return rows.slice(1).map((values) =>
|
||||
@@ -1024,7 +1024,7 @@ async function readPokemonDataFile(fileName: string): Promise<string> {
|
||||
}
|
||||
}
|
||||
|
||||
throw validationError(`Pokemon data file ${fileName} is unavailable`);
|
||||
throw validationError('server.validation.pokemonDataFileUnavailable');
|
||||
}
|
||||
|
||||
function csvInteger(row: CsvRow, fieldName: string): number {
|
||||
@@ -1092,7 +1092,7 @@ async function loadPokemonCsvData(): Promise<PokemonCsvData> {
|
||||
function pokemonDataLookupKey(value: unknown): string {
|
||||
const rawValue = typeof value === 'number' ? String(value) : typeof value === 'string' ? value.trim() : '';
|
||||
if (rawValue === '') {
|
||||
throw validationError('Pokemon identifier is required');
|
||||
throw validationError('server.validation.pokemonIdentifierRequired');
|
||||
}
|
||||
|
||||
const numericValue = Number(rawValue);
|
||||
@@ -1362,7 +1362,7 @@ function cleanPokemonImage(value: unknown, pokemonId: number): PokemonImage | nu
|
||||
|
||||
const image = pokemonImageCandidateForPath(pokemonId, path);
|
||||
if (!image) {
|
||||
throw validationError('Pokemon image path is invalid');
|
||||
throw validationError('server.validation.pokemonImagePathInvalid');
|
||||
}
|
||||
return image;
|
||||
}
|
||||
@@ -1427,7 +1427,7 @@ function fetchedPokemonTypeIds(row: CsvRow, data: PokemonCsvData): number[] {
|
||||
const typeIds = [csvInteger(row, 'type_1_id'), csvInteger(row, 'type_2_id')].filter((typeId) => typeId > 0);
|
||||
|
||||
if (typeIds.length === 0 || typeIds.some((typeId) => !data.typesById.has(typeId) || !pokemonTypeIconIds.has(typeId))) {
|
||||
throw validationError('Pokemon type data is unavailable');
|
||||
throw validationError('server.validation.pokemonTypeDataUnavailable');
|
||||
}
|
||||
|
||||
return typeIds;
|
||||
@@ -1496,7 +1496,7 @@ export async function fetchPokemonData(payload: Record<string, unknown>, userId:
|
||||
const pokemonRow = data.pokemonByLookup.get(lookupKey);
|
||||
|
||||
if (!pokemonRow) {
|
||||
throw validationError('Pokemon data was not found');
|
||||
throw validationError('server.validation.pokemonDataNotFound');
|
||||
}
|
||||
|
||||
const id = csvInteger(pokemonRow, 'id');
|
||||
@@ -1532,7 +1532,7 @@ export async function fetchPokemonImageOptions(payload: Record<string, unknown>)
|
||||
const pokemonRow = data.pokemonByLookup.get(lookupKey);
|
||||
|
||||
if (!pokemonRow) {
|
||||
throw validationError('Pokemon data was not found');
|
||||
throw validationError('server.validation.pokemonDataNotFound');
|
||||
}
|
||||
|
||||
const id = csvInteger(pokemonRow, 'id');
|
||||
@@ -1954,7 +1954,7 @@ export async function getOptions(locale = defaultLocale) {
|
||||
|
||||
function cleanDailyChecklistPayload(payload: Record<string, unknown>): DailyChecklistPayload {
|
||||
return {
|
||||
title: cleanName(payload.title, 'Please enter a task'),
|
||||
title: cleanName(payload.title, 'server.validation.taskRequired'),
|
||||
translations: cleanTranslations(payload.translations, ['title'])
|
||||
};
|
||||
}
|
||||
@@ -2042,7 +2042,7 @@ export async function updateDailyChecklistItem(
|
||||
export async function reorderDailyChecklistItems(payload: Record<string, unknown>, userId: number, locale = defaultLocale) {
|
||||
const ids = cleanIds(payload.ids);
|
||||
if (ids.length === 0) {
|
||||
throw validationError('Please select a task');
|
||||
throw validationError('server.validation.selectTask');
|
||||
}
|
||||
|
||||
await withTransaction(async (client) => {
|
||||
@@ -2052,7 +2052,7 @@ export async function reorderDailyChecklistItems(payload: Record<string, unknown
|
||||
);
|
||||
|
||||
if (existing.rowCount !== ids.length) {
|
||||
throw validationError('Task does not exist');
|
||||
throw validationError('server.validation.taskDoesNotExist');
|
||||
}
|
||||
|
||||
for (const [index, id] of ids.entries()) {
|
||||
@@ -2085,9 +2085,9 @@ export async function deleteDailyChecklistItem(id: number, userId: number) {
|
||||
}
|
||||
|
||||
function cleanLifePostPayload(payload: Record<string, unknown>): LifePostPayload {
|
||||
const body = cleanName(payload.body, 'Please enter a post');
|
||||
const body = cleanName(payload.body, 'server.validation.postRequired');
|
||||
if (body.length > 2000) {
|
||||
throw validationError('Post is too long');
|
||||
throw validationError('server.validation.postTooLong');
|
||||
}
|
||||
const tagIds = cleanIds(payload.tagIds);
|
||||
if (tagIds.length === 0) {
|
||||
@@ -2101,9 +2101,9 @@ function cleanLifePostPayload(payload: Record<string, unknown>): LifePostPayload
|
||||
}
|
||||
|
||||
function cleanLifeCommentPayload(payload: Record<string, unknown>): LifeCommentPayload {
|
||||
const body = cleanName(payload.body, 'Please enter a comment');
|
||||
const body = cleanName(payload.body, 'server.validation.commentRequired');
|
||||
if (body.length > 1000) {
|
||||
throw validationError('Comment is too long');
|
||||
throw validationError('server.validation.commentTooLong');
|
||||
}
|
||||
|
||||
return { body };
|
||||
@@ -2124,7 +2124,7 @@ function isLifeReactionType(value: unknown): value is LifeReactionType {
|
||||
|
||||
function cleanLifeReactionType(value: unknown): LifeReactionType {
|
||||
if (!isLifeReactionType(value)) {
|
||||
throw validationError('Reaction is invalid');
|
||||
throw validationError('server.validation.reactionInvalid');
|
||||
}
|
||||
|
||||
return value;
|
||||
@@ -2182,7 +2182,7 @@ function decodeLifePostCursor(value: QueryValue): LifePostCursor | null {
|
||||
const id = Number(cursor.id);
|
||||
|
||||
if (!createdAt || Number.isNaN(new Date(createdAt).getTime()) || !Number.isInteger(id) || id <= 0) {
|
||||
throw validationError('Cursor is invalid');
|
||||
throw validationError('server.validation.cursorInvalid');
|
||||
}
|
||||
|
||||
return { createdAt, id };
|
||||
@@ -2190,7 +2190,7 @@ function decodeLifePostCursor(value: QueryValue): LifePostCursor | null {
|
||||
if (error instanceof Error && 'statusCode' in error) {
|
||||
throw error;
|
||||
}
|
||||
throw validationError('Cursor is invalid');
|
||||
throw validationError('server.validation.cursorInvalid');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2372,7 +2372,7 @@ export async function listLifePosts(
|
||||
}
|
||||
|
||||
if (tagIdValue) {
|
||||
const tagId = requirePositiveInteger(tagIdValue, 'Tag is invalid');
|
||||
const tagId = requirePositiveInteger(tagIdValue, 'server.validation.tagInvalid');
|
||||
params.push(tagId);
|
||||
conditions.push(`EXISTS (
|
||||
SELECT 1
|
||||
@@ -2633,16 +2633,16 @@ export async function deleteLifeComment(id: number, userId: number, allowAny = f
|
||||
|
||||
function cleanDiscussionEntityType(value: unknown): DiscussionEntityType {
|
||||
if (typeof value !== 'string' || !Object.hasOwn(discussionEntityDefinitions, value)) {
|
||||
throw validationError('Entity type is invalid');
|
||||
throw validationError('server.validation.entityTypeInvalid');
|
||||
}
|
||||
|
||||
return value as DiscussionEntityType;
|
||||
}
|
||||
|
||||
function cleanEntityDiscussionCommentPayload(payload: Record<string, unknown>): EntityDiscussionCommentPayload {
|
||||
const body = cleanName(payload.body, 'Please enter a comment');
|
||||
const body = cleanName(payload.body, 'server.validation.commentRequired');
|
||||
if (body.length > 1000) {
|
||||
throw validationError('Comment is too long');
|
||||
throw validationError('server.validation.commentTooLong');
|
||||
}
|
||||
|
||||
return { body };
|
||||
@@ -2724,7 +2724,7 @@ export async function listEntityDiscussionComments(
|
||||
entityIdValue: number
|
||||
): Promise<EntityDiscussionComment[] | null> {
|
||||
const entityType = cleanDiscussionEntityType(entityTypeValue);
|
||||
const entityId = requirePositiveInteger(entityIdValue, 'Record is invalid');
|
||||
const entityId = requirePositiveInteger(entityIdValue, 'server.validation.recordInvalid');
|
||||
|
||||
if (!(await entityDiscussionExists(pool, entityType, entityId))) {
|
||||
return null;
|
||||
@@ -2748,7 +2748,7 @@ export async function createEntityDiscussionComment(
|
||||
userId: number
|
||||
): Promise<EntityDiscussionComment | null> {
|
||||
const entityType = cleanDiscussionEntityType(entityTypeValue);
|
||||
const entityId = requirePositiveInteger(entityIdValue, 'Record is invalid');
|
||||
const entityId = requirePositiveInteger(entityIdValue, 'server.validation.recordInvalid');
|
||||
const cleanPayload = cleanEntityDiscussionCommentPayload(payload);
|
||||
|
||||
const id = await withTransaction(async (client) => {
|
||||
@@ -2779,8 +2779,8 @@ export async function createEntityDiscussionReply(
|
||||
userId: number
|
||||
): Promise<EntityDiscussionComment | null> {
|
||||
const entityType = cleanDiscussionEntityType(entityTypeValue);
|
||||
const entityId = requirePositiveInteger(entityIdValue, 'Record is invalid');
|
||||
const commentId = requirePositiveInteger(commentIdValue, 'Comment is invalid');
|
||||
const entityId = requirePositiveInteger(entityIdValue, 'server.validation.recordInvalid');
|
||||
const commentId = requirePositiveInteger(commentIdValue, 'server.validation.commentInvalid');
|
||||
const cleanPayload = cleanEntityDiscussionCommentPayload(payload);
|
||||
|
||||
const id = await withTransaction(async (client) => {
|
||||
@@ -2816,7 +2816,7 @@ export async function createEntityDiscussionReply(
|
||||
}
|
||||
|
||||
export async function deleteEntityDiscussionComment(id: number, userId: number, allowAny = false): Promise<boolean> {
|
||||
const commentId = requirePositiveInteger(id, 'Comment is invalid');
|
||||
const commentId = requirePositiveInteger(id, 'server.validation.commentInvalid');
|
||||
const result = await queryOne<{ id: number }>(
|
||||
`
|
||||
UPDATE entity_discussion_comments
|
||||
@@ -2917,7 +2917,7 @@ export async function reorderConfig(type: ConfigType, payload: Record<string, un
|
||||
const definition = configDefinitions[type];
|
||||
const ids = cleanIds(payload.ids);
|
||||
if (ids.length === 0) {
|
||||
throw validationError('Please select a record');
|
||||
throw validationError('server.validation.selectRecord');
|
||||
}
|
||||
|
||||
await withTransaction(async (client) => {
|
||||
@@ -2992,7 +2992,7 @@ async function reorderContent(type: SortableContentType, payload: Record<string,
|
||||
const definition = sortableContentDefinitions[type];
|
||||
const ids = cleanIds(payload.ids);
|
||||
if (ids.length === 0) {
|
||||
throw validationError('Please select a record');
|
||||
throw validationError('server.validation.selectRecord');
|
||||
}
|
||||
|
||||
await withTransaction(async (client) => {
|
||||
@@ -3234,16 +3234,16 @@ function cleanPokemonPayload(payload: Record<string, unknown>): PokemonPayload {
|
||||
const skillItemDrops = new Map<string, SkillItemDrop>();
|
||||
|
||||
if (typeIds.length === 0) {
|
||||
throw validationError('Choose at least 1 type');
|
||||
throw validationError('server.validation.typeMin');
|
||||
}
|
||||
if (cleanTypeIds.length > 2) {
|
||||
throw validationError('Choose at most 2 types');
|
||||
throw validationError('server.validation.typeMax');
|
||||
}
|
||||
if (skillIds.length > 2) {
|
||||
throw validationError('Choose at most 2 specialities');
|
||||
throw validationError('server.validation.skillMax');
|
||||
}
|
||||
if (favoriteThingIds.length > 6) {
|
||||
throw validationError('Choose at most 6 favourites');
|
||||
throw validationError('server.validation.favoriteMax');
|
||||
}
|
||||
|
||||
if (Array.isArray(payload.skillItemDrops)) {
|
||||
@@ -3257,27 +3257,27 @@ function cleanPokemonPayload(payload: Record<string, unknown>): PokemonPayload {
|
||||
}
|
||||
|
||||
if (!Number.isInteger(skillId) || skillId <= 0 || !selectedSkillIds.has(skillId)) {
|
||||
throw validationError('Drop items must be linked to selected specialities');
|
||||
throw validationError('server.validation.dropItemSelectedSkill');
|
||||
}
|
||||
|
||||
skillItemDrops.set(String(skillId), { skillId, itemId });
|
||||
}
|
||||
}
|
||||
|
||||
const displayId = requirePositiveInteger(payload.displayId ?? payload.id, 'Pokemon ID is required');
|
||||
const displayId = requirePositiveInteger(payload.displayId, 'server.validation.pokemonIdRequired');
|
||||
|
||||
return {
|
||||
displayId,
|
||||
isEventItem: Boolean(payload.isEventItem),
|
||||
name: cleanName(payload.name, 'Pokemon name is required'),
|
||||
name: cleanName(payload.name, 'server.validation.pokemonNameRequired'),
|
||||
genus: cleanOptionalText(payload.genus),
|
||||
details: cleanOptionalText(payload.details),
|
||||
heightInches: cleanNonNegativeNumber(payload.heightInches, 'Height must be a non-negative number'),
|
||||
weightPounds: cleanNonNegativeNumber(payload.weightPounds, 'Weight must be a non-negative number'),
|
||||
heightInches: cleanNonNegativeNumber(payload.heightInches, 'server.validation.heightNonNegative'),
|
||||
weightPounds: cleanNonNegativeNumber(payload.weightPounds, 'server.validation.weightNonNegative'),
|
||||
translations: cleanTranslations(payload.translations, ['name', 'details', 'genus']),
|
||||
typeIds,
|
||||
stats: cleanPokemonStats(payload.stats),
|
||||
environmentId: requirePositiveInteger(payload.environmentId, 'Ideal Habitat is required'),
|
||||
environmentId: requirePositiveInteger(payload.environmentId, 'server.validation.environmentRequired'),
|
||||
skillIds,
|
||||
favoriteThingIds,
|
||||
skillItemDrops: [...skillItemDrops.values()],
|
||||
@@ -3318,7 +3318,7 @@ async function replacePokemonRelations(client: DbClient, pokemonId: number, payl
|
||||
const allowedDropSkillIds = new Set(allowedDrops.rows.map((row) => row.id));
|
||||
|
||||
if (payload.skillItemDrops.some((drop) => !allowedDropSkillIds.has(drop.skillId))) {
|
||||
throw validationError('This speciality cannot have a drop item');
|
||||
throw validationError('server.validation.skillNoDrop');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3601,9 +3601,9 @@ function cleanHabitatPayload(payload: Record<string, unknown>): HabitatPayload {
|
||||
for (const item of appearances) {
|
||||
const row = item as Record<string, unknown>;
|
||||
const pokemonId = Number(row.pokemonId);
|
||||
const mapIds = cleanIdValues(row.mapIds ?? row.mapId);
|
||||
const selectedTimeOfDays = cleanOptions(row.timeOfDays ?? row.timeOfDay, timeOfDays);
|
||||
const selectedWeathers = cleanOptions(row.weathers ?? row.weather, weathers);
|
||||
const mapIds = cleanIdValues(row.mapIds);
|
||||
const selectedTimeOfDays = cleanOptions(row.timeOfDays, timeOfDays);
|
||||
const selectedWeathers = cleanOptions(row.weathers, weathers);
|
||||
const rarity = Number(row.rarity);
|
||||
|
||||
if (!Number.isInteger(pokemonId) || pokemonId <= 0 || !Number.isInteger(rarity) || rarity < 1 || rarity > 3) {
|
||||
@@ -3626,7 +3626,7 @@ function cleanHabitatPayload(payload: Record<string, unknown>): HabitatPayload {
|
||||
}
|
||||
|
||||
return {
|
||||
name: cleanName(payload.name, 'Habitat name is required'),
|
||||
name: cleanName(payload.name, 'server.validation.habitatNameRequired'),
|
||||
translations: cleanTranslations(payload.translations, ['name']),
|
||||
isEventItem: Boolean(payload.isEventItem),
|
||||
imagePath: cleanUploadImagePath(payload.imagePath, 'habitats'),
|
||||
@@ -3973,12 +3973,12 @@ export async function getItem(id: number, locale = defaultLocale) {
|
||||
function cleanItemPayload(payload: Record<string, unknown>): ItemPayload {
|
||||
const usageId = payload.usageId === null || payload.usageId === '' || payload.usageId === undefined
|
||||
? null
|
||||
: requirePositiveInteger(payload.usageId, 'Usage is required');
|
||||
: requirePositiveInteger(payload.usageId, 'server.validation.usageRequired');
|
||||
|
||||
return {
|
||||
name: cleanName(payload.name, 'Item name is required'),
|
||||
name: cleanName(payload.name, 'server.validation.itemNameRequired'),
|
||||
translations: cleanTranslations(payload.translations, ['name']),
|
||||
categoryId: requirePositiveInteger(payload.categoryId, 'Category is required'),
|
||||
categoryId: requirePositiveInteger(payload.categoryId, 'server.validation.categoryRequired'),
|
||||
usageId,
|
||||
dyeable: Boolean(payload.dyeable),
|
||||
dualDyeable: Boolean(payload.dualDyeable),
|
||||
@@ -3998,7 +3998,7 @@ async function ensureItemCanDisableRecipe(client: DbClient, itemId: number, noRe
|
||||
|
||||
const result = await client.query('SELECT 1 FROM recipes WHERE item_id = $1', [itemId]);
|
||||
if (result.rowCount && result.rowCount > 0) {
|
||||
throw validationError('An item with a recipe cannot be marked as recipe-free');
|
||||
throw validationError('server.validation.recipeFreeWithRecipe');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4227,7 +4227,7 @@ export async function getRecipe(id: number, locale = defaultLocale) {
|
||||
|
||||
function cleanRecipePayload(payload: Record<string, unknown>): RecipePayload {
|
||||
return {
|
||||
itemId: requirePositiveInteger(payload.itemId, 'Item is required'),
|
||||
itemId: requirePositiveInteger(payload.itemId, 'server.validation.itemRequired'),
|
||||
acquisitionMethodIds: cleanIds(payload.acquisitionMethodIds),
|
||||
materials: cleanQuantities(payload.materials)
|
||||
};
|
||||
@@ -4256,11 +4256,11 @@ async function replaceRecipeRelations(client: DbClient, recipeId: number, payloa
|
||||
async function ensureItemCanHaveRecipe(client: DbClient, itemId: number): Promise<void> {
|
||||
const result = await client.query<{ no_recipe: boolean }>('SELECT no_recipe FROM items WHERE id = $1', [itemId]);
|
||||
if (result.rowCount === 0) {
|
||||
throw validationError('Item is required');
|
||||
throw validationError('server.validation.itemRequired');
|
||||
}
|
||||
|
||||
if (result.rows[0].no_recipe) {
|
||||
throw validationError('This item is marked as recipe-free');
|
||||
throw validationError('server.validation.recipeFreeItem');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,56 +21,6 @@ const wordingKeyPattern = /^[A-Za-z][A-Za-z0-9]*(\.[A-Za-z][A-Za-z0-9]*)+$/;
|
||||
const placeholderPattern = /\{([A-Za-z0-9_]+)\}/g;
|
||||
const surfaces = new Set<SystemWordingSurface>(['frontend', 'backend', 'email']);
|
||||
|
||||
const legacyMessageKeys = new Map<string, string>([
|
||||
['Record does not exist', 'server.validation.recordMissing'],
|
||||
['Language code is invalid', 'server.validation.languageCodeInvalid'],
|
||||
['Language name is required', 'server.validation.languageNameRequired'],
|
||||
['Default language must be English', 'server.validation.defaultLanguageMustBeEnglish'],
|
||||
['Default language must be enabled', 'server.validation.defaultLanguageMustBeEnabled'],
|
||||
['Language not found', 'server.validation.languageNotFound'],
|
||||
['A default language is required', 'server.validation.defaultLanguageRequired'],
|
||||
['Default language cannot be deleted', 'server.validation.defaultLanguageCannotBeDeleted'],
|
||||
['Please select a language', 'server.validation.selectLanguage'],
|
||||
['Language does not exist', 'server.validation.languageDoesNotExist'],
|
||||
['Pokemon identifier is required', 'server.validation.pokemonIdentifierRequired'],
|
||||
['Pokemon type data is unavailable', 'server.validation.pokemonTypeDataUnavailable'],
|
||||
['Pokemon data was not found', 'server.validation.pokemonDataNotFound'],
|
||||
['Pokemon image path is invalid', 'server.validation.pokemonImagePathInvalid'],
|
||||
['Please enter a task', 'server.validation.taskRequired'],
|
||||
['Please select a task', 'server.validation.selectTask'],
|
||||
['Task does not exist', 'server.validation.taskDoesNotExist'],
|
||||
['Please enter a post', 'server.validation.postRequired'],
|
||||
['Post is too long', 'server.validation.postTooLong'],
|
||||
['Please enter a comment', 'server.validation.commentRequired'],
|
||||
['Comment is too long', 'server.validation.commentTooLong'],
|
||||
['Reaction is invalid', 'server.validation.reactionInvalid'],
|
||||
['Cursor is invalid', 'server.validation.cursorInvalid'],
|
||||
['Tag is invalid', 'server.validation.tagInvalid'],
|
||||
['Entity type is invalid', 'server.validation.entityTypeInvalid'],
|
||||
['Record is invalid', 'server.validation.recordInvalid'],
|
||||
['Comment is invalid', 'server.validation.commentInvalid'],
|
||||
['Please select a record', 'server.validation.selectRecord'],
|
||||
['Choose at least 1 type', 'server.validation.typeMin'],
|
||||
['Choose at most 2 types', 'server.validation.typeMax'],
|
||||
['Choose at most 2 specialities', 'server.validation.skillMax'],
|
||||
['Choose at most 6 favourites', 'server.validation.favoriteMax'],
|
||||
['Drop items must be linked to selected specialities', 'server.validation.dropItemSelectedSkill'],
|
||||
['Pokemon ID is required', 'server.validation.pokemonIdRequired'],
|
||||
['Pokemon name is required', 'server.validation.pokemonNameRequired'],
|
||||
['Height must be a non-negative number', 'server.validation.heightNonNegative'],
|
||||
['Weight must be a non-negative number', 'server.validation.weightNonNegative'],
|
||||
['Ideal Habitat is required', 'server.validation.environmentRequired'],
|
||||
['This speciality cannot have a drop item', 'server.validation.skillNoDrop'],
|
||||
['Habitat name is required', 'server.validation.habitatNameRequired'],
|
||||
['Usage is required', 'server.validation.usageRequired'],
|
||||
['Item name is required', 'server.validation.itemNameRequired'],
|
||||
['Category is required', 'server.validation.categoryRequired'],
|
||||
['An item with a recipe cannot be marked as recipe-free', 'server.validation.recipeFreeWithRecipe'],
|
||||
['Item is required', 'server.validation.itemRequired'],
|
||||
['This item is marked as recipe-free', 'server.validation.recipeFreeItem'],
|
||||
['Name is required', 'server.validation.nameRequired']
|
||||
]);
|
||||
|
||||
function validationError(message: string): ValidationError {
|
||||
const error = new Error(message) as ValidationError;
|
||||
error.statusCode = 400;
|
||||
@@ -145,22 +95,6 @@ function normalizePlaceholders(value: unknown): string[] {
|
||||
return Array.isArray(value) ? value.map((item) => String(item)).sort() : [];
|
||||
}
|
||||
|
||||
function legacyMessageKey(message: string): string | null {
|
||||
if (message.startsWith('server.') || message.startsWith('email.')) {
|
||||
return message;
|
||||
}
|
||||
if (message.endsWith(' must be a non-negative integer')) {
|
||||
return 'server.validation.statNonNegative';
|
||||
}
|
||||
if (message.endsWith(' is empty')) {
|
||||
return 'server.validation.pokemonDataFileEmpty';
|
||||
}
|
||||
if (message.startsWith('Pokemon data file ') && message.endsWith(' is unavailable')) {
|
||||
return 'server.validation.pokemonDataFileUnavailable';
|
||||
}
|
||||
return legacyMessageKeys.get(message) ?? null;
|
||||
}
|
||||
|
||||
export async function syncSystemWordingCatalog(): Promise<void> {
|
||||
const entries = systemWordingCatalogEntries();
|
||||
const client = await pool.connect();
|
||||
@@ -232,8 +166,7 @@ export async function systemMessage(
|
||||
}
|
||||
|
||||
export async function localizedStatusMessage(locale: string, message: string): Promise<string> {
|
||||
const key = legacyMessageKey(message);
|
||||
return key ? systemMessage(locale, key) : message;
|
||||
return message.startsWith('server.') || message.startsWith('email.') ? systemMessage(locale, message) : message;
|
||||
}
|
||||
|
||||
export async function getSystemWordings(locale: string) {
|
||||
|
||||
Reference in New Issue
Block a user