diff --git a/backend/db/schema.sql b/backend/db/schema.sql index 82d6b3c..bf30e55 100644 --- a/backend/db/schema.sql +++ b/backend/db/schema.sql @@ -26,8 +26,6 @@ CREATE TABLE IF NOT EXISTS entity_translations ( 'skills', 'environments', 'favorite-things', - 'item-categories', - 'item-usages', 'acquisition-methods', 'items', 'ancient-artifacts', @@ -51,41 +49,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', - 'ancient-artifacts', - 'maps', - 'habitats', - 'daily-checklist-items', - 'life-tags', - '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, @@ -201,22 +164,10 @@ CREATE TABLE IF NOT EXISTS ai_moderation_settings ( CHECK (length(model) BETWEEN 1 AND 120) ); -ALTER TABLE ai_moderation_settings - ADD COLUMN IF NOT EXISTS api_format text NOT NULL DEFAULT 'gemini-generate-content' CHECK (api_format IN ('gemini-generate-content', 'openai-chat-completions')), - ADD COLUMN IF NOT EXISTS auth_mode text NOT NULL DEFAULT 'bearer-token' CHECK (auth_mode IN ('query-key', 'bearer-token')); - INSERT INTO ai_moderation_settings (id) VALUES (true) ON CONFLICT (id) DO NOTHING; -UPDATE ai_moderation_settings -SET api_format = 'gemini-generate-content', - auth_mode = 'bearer-token', - updated_at = now() -WHERE api_format = 'openai-chat-completions' - AND auth_mode = 'query-key' - AND endpoint ~* '/v1beta/?$'; - CREATE TABLE IF NOT EXISTS ai_moderation_cache ( content_hash text NOT NULL, model text NOT NULL, @@ -229,9 +180,6 @@ CREATE TABLE IF NOT EXISTS ai_moderation_cache ( CHECK (length(model) BETWEEN 1 AND 120) ); -ALTER TABLE ai_moderation_cache - ADD COLUMN IF NOT EXISTS reason text; - CREATE TABLE IF NOT EXISTS rate_limit_settings ( id boolean PRIMARY KEY DEFAULT true CHECK (id = true), settings jsonb NOT NULL DEFAULT '{}'::jsonb CHECK (jsonb_typeof(settings) = 'object'), @@ -918,10 +866,6 @@ CREATE TABLE IF NOT EXISTS pokemon ( updated_at timestamptz NOT NULL DEFAULT now() ); -ALTER TABLE pokemon - ADD COLUMN IF NOT EXISTS data_id integer CHECK (data_id > 0), - ADD COLUMN IF NOT EXISTS data_identifier text NOT NULL DEFAULT ''; - CREATE TABLE IF NOT EXISTS pokemon_pokemon_types ( pokemon_id integer NOT NULL REFERENCES pokemon(id) ON DELETE CASCADE, type_id integer NOT NULL REFERENCES pokemon_types(id) ON DELETE CASCADE, @@ -942,26 +886,6 @@ CREATE TABLE IF NOT EXISTS pokemon_favorite_things ( 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, - 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), - 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, @@ -980,8 +904,6 @@ CREATE TABLE IF NOT EXISTS items ( ancient_artifact_category_key text, category_key text NOT NULL DEFAULT 'other', usage_key text, - category_id integer 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, @@ -1071,58 +993,6 @@ CREATE TABLE IF NOT EXISTS dish_categories ( 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, @@ -1154,15 +1024,6 @@ CREATE TABLE IF NOT EXISTS dishes ( ) ); -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, @@ -1202,221 +1063,6 @@ CREATE TABLE IF NOT EXISTS habitat_pokemon ( PRIMARY KEY (habitat_id, pokemon_id, map_id, time_of_day, weather) ); -ALTER TABLE life_tags - ADD COLUMN IF NOT EXISTS is_default boolean NOT NULL DEFAULT false; - -ALTER TABLE items - ADD COLUMN IF NOT EXISTS details text NOT NULL DEFAULT '', - ADD COLUMN IF NOT EXISTS base_price integer, - ADD COLUMN IF NOT EXISTS ancient_artifact_category_key text, - ADD COLUMN IF NOT EXISTS category_key text, - ADD COLUMN IF NOT EXISTS usage_key text; - -UPDATE items -SET base_price = NULL -WHERE base_price < 0; - -DO $$ -BEGIN - IF EXISTS ( - SELECT 1 - FROM information_schema.columns - WHERE table_name = 'items' - AND column_name = 'category_id' - AND table_schema = current_schema() - ) THEN - ALTER TABLE items ALTER COLUMN category_id DROP NOT NULL; - END IF; -END $$; - -UPDATE items i -SET category_key = CASE lower(trim(c.name)) - WHEN 'furniture' THEN 'furniture' - WHEN 'misc' THEN 'misc' - WHEN 'outdoor' THEN 'outdoor' - WHEN 'utilities' THEN 'utilities' - WHEN 'buildings' THEN 'buildings' - WHEN 'blocks' THEN 'blocks' - WHEN 'kits' THEN 'kits' - WHEN 'nature' THEN 'nature' - WHEN 'food' THEN 'food' - WHEN 'materials' THEN 'materials' - WHEN 'key items' THEN 'key-items' - WHEN 'key-items' THEN 'key-items' - WHEN 'other' THEN 'other' - ELSE 'other' -END -FROM item_categories c -WHERE i.category_id = c.id - AND (i.category_key IS NULL OR i.category_key = ''); - -UPDATE items i -SET usage_key = CASE lower(trim(u.name)) - WHEN 'decoration' THEN 'decoration' - WHEN 'relaxation' THEN 'relaxation' - WHEN 'toy' THEN 'toy' - WHEN 'road' THEN 'road' - ELSE NULL -END -FROM item_usages u -WHERE i.usage_id = u.id - AND i.usage_key IS NULL; - -UPDATE items -SET category_key = 'other' -WHERE category_key IS NULL - OR category_key NOT IN ( - 'furniture', - 'misc', - 'outdoor', - 'utilities', - 'buildings', - 'blocks', - 'kits', - 'nature', - 'food', - 'materials', - 'key-items', - 'other' - ); - -UPDATE items -SET usage_key = NULL -WHERE usage_key IS NOT NULL - AND usage_key NOT IN ('decoration', 'relaxation', 'toy', 'road'); - -ALTER TABLE items - ALTER COLUMN category_key SET NOT NULL, - ALTER COLUMN category_key SET DEFAULT 'other'; - -ALTER TABLE items - DROP CONSTRAINT IF EXISTS items_display_id_positive, - DROP CONSTRAINT IF EXISTS items_base_price_check, - DROP CONSTRAINT IF EXISTS items_ancient_artifact_category_key_check, - DROP CONSTRAINT IF EXISTS items_category_key_check, - DROP CONSTRAINT IF EXISTS items_usage_key_check; - -ALTER TABLE items - ADD CONSTRAINT items_base_price_check CHECK (base_price IS NULL OR base_price >= 0), - ADD CONSTRAINT items_ancient_artifact_category_key_check CHECK ( - ancient_artifact_category_key IS NULL - OR ancient_artifact_category_key IN ('lost-relics-l', 'lost-relics-s', 'fossils') - ), - ADD CONSTRAINT items_category_key_check CHECK (category_key IN ( - 'furniture', - 'misc', - 'outdoor', - 'utilities', - 'buildings', - 'blocks', - 'kits', - 'nature', - 'food', - 'materials', - 'key-items', - 'other' - )), - ADD CONSTRAINT items_usage_key_check CHECK (usage_key IS NULL OR usage_key IN ('decoration', 'relaxation', 'toy', 'road')); - -DO $$ -BEGIN - IF to_regclass('ancient_artifacts') IS NOT NULL THEN - ALTER TABLE ancient_artifacts - ADD COLUMN IF NOT EXISTS image_path text NOT NULL DEFAULT ''; - - CREATE TEMP TABLE migrated_ancient_artifact_items ( - old_id integer PRIMARY KEY, - item_id integer NOT NULL - ) ON COMMIT DROP; - - INSERT INTO items ( - name, - details, - ancient_artifact_category_key, - category_key, - image_path, - sort_order, - created_by_user_id, - updated_by_user_id, - created_at, - updated_at - ) - SELECT - a.name, - a.details, - a.category_key, - 'other', - a.image_path, - a.sort_order, - a.created_by_user_id, - a.updated_by_user_id, - a.created_at, - a.updated_at - FROM ancient_artifacts a - ON CONFLICT (name) DO UPDATE - SET ancient_artifact_category_key = EXCLUDED.ancient_artifact_category_key, - details = CASE WHEN items.details = '' THEN EXCLUDED.details ELSE items.details END, - image_path = CASE WHEN items.image_path = '' THEN EXCLUDED.image_path ELSE items.image_path END, - updated_by_user_id = COALESCE(items.updated_by_user_id, EXCLUDED.updated_by_user_id), - updated_at = GREATEST(items.updated_at, EXCLUDED.updated_at); - - INSERT INTO migrated_ancient_artifact_items (old_id, item_id) - SELECT a.id, i.id - FROM ancient_artifacts a - JOIN items i ON i.name = a.name - ON CONFLICT (old_id) DO UPDATE SET item_id = EXCLUDED.item_id; - - IF to_regclass('ancient_artifact_favorite_things') IS NOT NULL THEN - INSERT INTO item_favorite_things (item_id, favorite_thing_id) - SELECT m.item_id, aft.favorite_thing_id - FROM ancient_artifact_favorite_things aft - JOIN migrated_ancient_artifact_items m ON m.old_id = aft.ancient_artifact_id - ON CONFLICT DO NOTHING; - END IF; - - INSERT INTO entity_translations (entity_type, entity_id, locale, field_name, value) - SELECT 'items', m.item_id, et.locale, et.field_name, et.value - FROM entity_translations et - JOIN migrated_ancient_artifact_items m ON m.old_id = et.entity_id - WHERE et.entity_type = 'ancient-artifacts' - ON CONFLICT (entity_type, entity_id, locale, field_name) DO UPDATE - SET value = EXCLUDED.value; - - UPDATE wiki_edit_logs wel - SET entity_type = 'items', - entity_id = m.item_id - FROM migrated_ancient_artifact_items m - WHERE wel.entity_type = 'ancient-artifacts' - AND wel.entity_id = m.old_id; - - UPDATE entity_image_uploads eiu - SET entity_type = 'items', - entity_id = m.item_id - FROM migrated_ancient_artifact_items m - WHERE eiu.entity_type = 'ancient-artifacts' - AND eiu.entity_id = m.old_id; - - UPDATE entity_discussion_comments edc - SET entity_id = m.item_id - FROM migrated_ancient_artifact_items m - WHERE edc.entity_type = 'ancient-artifacts' - AND edc.entity_id = m.old_id; - - DELETE FROM entity_translations - WHERE entity_type = 'ancient-artifacts'; - END IF; -END $$; - -DROP INDEX IF EXISTS items_display_event_item_key; -DROP INDEX IF EXISTS items_display_order_idx; -DROP INDEX IF EXISTS ancient_artifacts_display_order_idx; - -ALTER TABLE items - DROP COLUMN IF EXISTS display_id; - -DROP TABLE IF EXISTS ancient_artifact_favorite_things; -DROP TABLE IF EXISTS ancient_artifacts; - 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); @@ -1425,8 +1071,6 @@ CREATE INDEX IF NOT EXISTS pokemon_sort_order_idx ON pokemon(sort_order, id); CREATE UNIQUE INDEX IF NOT EXISTS pokemon_display_event_item_key ON pokemon(display_id, is_event_item); CREATE INDEX IF NOT EXISTS life_tags_sort_order_idx ON life_tags(sort_order, id); CREATE UNIQUE INDEX IF NOT EXISTS life_tags_single_default_idx ON life_tags(is_default) WHERE is_default = true; -CREATE INDEX IF NOT EXISTS item_categories_sort_order_idx ON item_categories(sort_order, id); -CREATE INDEX IF NOT EXISTS item_usages_sort_order_idx ON item_usages(sort_order, id); CREATE INDEX IF NOT EXISTS acquisition_methods_sort_order_idx ON acquisition_methods(sort_order, id); CREATE INDEX IF NOT EXISTS items_sort_order_idx ON items(sort_order, id); CREATE INDEX IF NOT EXISTS recipes_sort_order_idx ON recipes(sort_order, id); @@ -1468,14 +1112,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', 'ancient-artifacts') - ); - CREATE INDEX IF NOT EXISTS entity_image_uploads_entity_idx ON entity_image_uploads(entity_type, entity_id, created_at DESC, id DESC); @@ -1524,14 +1160,6 @@ CREATE INDEX IF NOT EXISTS entity_discussion_comment_likes_comment_idx CREATE INDEX IF NOT EXISTS entity_discussion_comment_likes_user_idx ON entity_discussion_comment_likes(user_id, created_at DESC, comment_id DESC); -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', 'ancient-artifacts') - ); - CREATE TABLE IF NOT EXISTS notifications ( id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, recipient_user_id integer NOT NULL REFERENCES users(id) ON DELETE CASCADE, @@ -1587,9 +1215,6 @@ CREATE UNIQUE INDEX IF NOT EXISTS notifications_life_post_reaction_unique_idx ON notifications(recipient_user_id, actor_user_id, life_post_id) WHERE type = 'life_post_reaction' AND actor_user_id IS NOT NULL AND life_post_id IS NOT NULL; -ALTER TABLE notifications - ADD COLUMN IF NOT EXISTS profile_user_id integer REFERENCES users(id) ON DELETE CASCADE; - CREATE UNIQUE INDEX IF NOT EXISTS notifications_user_follow_unique_idx ON notifications(recipient_user_id, actor_user_id, profile_user_id) WHERE type = 'user_follow' AND actor_user_id IS NOT NULL AND profile_user_id IS NOT NULL; @@ -1606,66 +1231,6 @@ CREATE TABLE IF NOT EXISTS notification_ws_tickets ( CREATE INDEX IF NOT EXISTS notification_ws_tickets_user_idx ON notification_ws_tickets(user_id, expires_at DESC); -ALTER TABLE notifications - ADD COLUMN IF NOT EXISTS moderation_reason text; - -ALTER TABLE notifications - ADD COLUMN IF NOT EXISTS profile_user_id integer REFERENCES users(id) ON DELETE CASCADE; - -ALTER TABLE notifications - DROP CONSTRAINT IF EXISTS notifications_type_check; - -ALTER TABLE notifications - ADD CONSTRAINT notifications_type_check CHECK ( - type IN ( - 'life_post_comment', - 'life_comment_reply', - 'discussion_comment_reply', - 'life_post_reaction', - 'user_follow', - 'moderation_result' - ) - ); - -ALTER TABLE life_tags - ADD COLUMN IF NOT EXISTS is_rateable boolean NOT NULL DEFAULT false; - -CREATE TABLE IF NOT EXISTS game_versions ( - id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - name text NOT NULL UNIQUE, - change_log 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() -); - -CREATE INDEX IF NOT EXISTS game_versions_sort_order_idx - ON game_versions(sort_order, id); - -ALTER TABLE life_posts - ADD COLUMN IF NOT EXISTS ai_moderation_status text NOT NULL DEFAULT 'unreviewed' CHECK (ai_moderation_status IN ('unreviewed', 'reviewing', 'approved', 'rejected', 'failed')), - ADD COLUMN IF NOT EXISTS category_id integer REFERENCES life_tags(id) ON DELETE RESTRICT, - ADD COLUMN IF NOT EXISTS game_version_id integer REFERENCES game_versions(id) ON DELETE SET NULL, - ADD COLUMN IF NOT EXISTS ai_moderation_language_code text REFERENCES languages(code) ON DELETE SET NULL, - ADD COLUMN IF NOT EXISTS ai_moderation_reason text, - ADD COLUMN IF NOT EXISTS ai_moderation_content_hash text, - ADD COLUMN IF NOT EXISTS ai_moderation_checked_at timestamptz, - ADD COLUMN IF NOT EXISTS ai_moderation_retry_count integer NOT NULL DEFAULT 0 CHECK (ai_moderation_retry_count >= 0), - ADD COLUMN IF NOT EXISTS ai_moderation_updated_at timestamptz NOT NULL DEFAULT now(); - -UPDATE life_posts lp -SET category_id = selected.tag_id -FROM ( - SELECT DISTINCT ON (lpt.post_id) lpt.post_id, lpt.tag_id - FROM life_post_tags lpt - JOIN life_tags lt ON lt.id = lpt.tag_id - ORDER BY lpt.post_id, lt.sort_order, lt.id -) selected -WHERE lp.id = selected.post_id - AND lp.category_id IS NULL; - CREATE INDEX IF NOT EXISTS life_posts_category_idx ON life_posts(category_id, created_at DESC, id DESC) WHERE deleted_at IS NULL; @@ -1674,39 +1239,6 @@ CREATE INDEX IF NOT EXISTS life_posts_game_version_idx ON life_posts(game_version_id, created_at DESC, id DESC) WHERE deleted_at IS NULL; -CREATE TABLE IF NOT EXISTS life_post_ratings ( - post_id integer NOT NULL REFERENCES life_posts(id) ON DELETE CASCADE, - user_id integer NOT NULL REFERENCES users(id) ON DELETE CASCADE, - rating integer NOT NULL CHECK (rating BETWEEN 1 AND 5), - created_at timestamptz NOT NULL DEFAULT now(), - updated_at timestamptz NOT NULL DEFAULT now(), - PRIMARY KEY (post_id, user_id) -); - -CREATE INDEX IF NOT EXISTS life_post_ratings_post_idx - ON life_post_ratings(post_id, rating); - -CREATE INDEX IF NOT EXISTS life_post_ratings_user_idx - ON life_post_ratings(user_id, updated_at DESC, post_id DESC); - -ALTER TABLE life_post_comments - ADD COLUMN IF NOT EXISTS ai_moderation_status text NOT NULL DEFAULT 'unreviewed' CHECK (ai_moderation_status IN ('unreviewed', 'reviewing', 'approved', 'rejected', 'failed')), - ADD COLUMN IF NOT EXISTS ai_moderation_language_code text REFERENCES languages(code) ON DELETE SET NULL, - ADD COLUMN IF NOT EXISTS ai_moderation_reason text, - ADD COLUMN IF NOT EXISTS ai_moderation_content_hash text, - ADD COLUMN IF NOT EXISTS ai_moderation_checked_at timestamptz, - ADD COLUMN IF NOT EXISTS ai_moderation_retry_count integer NOT NULL DEFAULT 0 CHECK (ai_moderation_retry_count >= 0), - ADD COLUMN IF NOT EXISTS ai_moderation_updated_at timestamptz NOT NULL DEFAULT now(); - -ALTER TABLE entity_discussion_comments - ADD COLUMN IF NOT EXISTS ai_moderation_status text NOT NULL DEFAULT 'unreviewed' CHECK (ai_moderation_status IN ('unreviewed', 'reviewing', 'approved', 'rejected', 'failed')), - ADD COLUMN IF NOT EXISTS ai_moderation_language_code text REFERENCES languages(code) ON DELETE SET NULL, - ADD COLUMN IF NOT EXISTS ai_moderation_reason text, - ADD COLUMN IF NOT EXISTS ai_moderation_content_hash text, - ADD COLUMN IF NOT EXISTS ai_moderation_checked_at timestamptz, - ADD COLUMN IF NOT EXISTS ai_moderation_retry_count integer NOT NULL DEFAULT 0 CHECK (ai_moderation_retry_count >= 0), - ADD COLUMN IF NOT EXISTS ai_moderation_updated_at timestamptz NOT NULL DEFAULT now(); - CREATE INDEX IF NOT EXISTS life_posts_ai_moderation_status_idx ON life_posts(ai_moderation_status, ai_moderation_updated_at, id) WHERE deleted_at IS NULL; diff --git a/backend/src/queries.ts b/backend/src/queries.ts index 5240a88..34fdcb3 100644 --- a/backend/src/queries.ts +++ b/backend/src/queries.ts @@ -71,8 +71,6 @@ type EntityType = | 'skills' | 'environments' | 'favorite-things' - | 'item-categories' - | 'item-usages' | 'acquisition-methods' | 'items' | 'ancient-artifacts' @@ -7693,7 +7691,6 @@ const dataToolColumns = { ], itemAcquisitionMethods: ['item_id', 'acquisition_method_id'], itemFavoriteThings: ['item_id', 'favorite_thing_id'], - artifacts: [] as string[], recipes: ['id', 'item_id', 'sort_order', 'created_by_user_id', 'updated_by_user_id', 'created_at', 'updated_at'], recipeAcquisitionMethods: ['recipe_id', 'acquisition_method_id'], recipeMaterials: ['recipe_id', 'item_id', 'quantity'], @@ -7800,67 +7797,19 @@ function dataToolDataWithRows(key: string, ...sources: Array source?.[key] !== undefined); } -function dataToolArtifactRows(data: DataToolScopeData | undefined): DataToolRows { - return dataToolTableRows(data, 'artifacts').map((row) => { - if (row.ancient_artifact_category_key !== undefined) { - return row; - } - - return { - ...row, - base_price: null, - ancient_artifact_category_key: row.category_key, - category_key: 'other', - usage_key: null, - dyeable: false, - dual_dyeable: false, - pattern_editable: false, - no_recipe: false, - is_event_item: false - }; - }); -} - -function dataToolArtifactFavoriteThingRows(data: DataToolScopeData | undefined): DataToolRows { - const itemRows = dataToolTableRows(data, 'itemFavoriteThings'); - const artifactRows = dataToolTableRows(data, 'artifactFavoriteThings').map((row) => ({ - item_id: row.ancient_artifact_id, - favorite_thing_id: row.favorite_thing_id - })); - return [...itemRows, ...artifactRows]; -} - async function tableRows(client: DbClient, sql: string, params: unknown[] = []): Promise { const result = await client.query>(sql, params); return result.rows; } -function normalizeImportValue(column: string, value: unknown, row: Record): unknown { - if (value === undefined) { - if (column === 'display_id' && typeof row.id === 'number') { - return row.id; - } - if (column === 'details') { - return ''; - } - if (column === 'image_path') { - return ''; - } - if (column === 'category_key') { - return 'other'; - } - return null; - } - if (column === 'changes' && typeof value !== 'string') { - return JSON.stringify(value ?? []); - } - return value; +function normalizeImportValue(value: unknown): unknown { + return value === undefined ? null : value; } async function insertRows(client: DbClient, tableName: string, columns: readonly string[], rows: DataToolRows): Promise { for (const row of rows) { const placeholders = columns.map((_, index) => `$${index + 1}`).join(', '); - const values = columns.map((column) => normalizeImportValue(column, row[column], row)); + const values = columns.map((column) => normalizeImportValue(row[column])); await client.query(`INSERT INTO ${tableName} (${columns.join(', ')}) VALUES (${placeholders})`, values); } } @@ -7870,7 +7819,7 @@ async function upsertRowsById(client: DbClient, tableName: string, columns: read for (const row of rows) { const placeholders = columns.map((_, index) => `$${index + 1}`).join(', '); const assignments = updateColumns.map((column) => `${column} = EXCLUDED.${column}`).join(', '); - const values = columns.map((column) => normalizeImportValue(column, row[column], row)); + const values = columns.map((column) => normalizeImportValue(row[column])); await client.query( `INSERT INTO ${tableName} (${columns.join(', ')}) VALUES (${placeholders}) ON CONFLICT (id) DO UPDATE SET ${assignments}`, values @@ -7881,7 +7830,7 @@ async function upsertRowsById(client: DbClient, tableName: string, columns: read async function insertRowsIgnoreConflicts(client: DbClient, tableName: string, columns: readonly string[], rows: DataToolRows): Promise { for (const row of rows) { const placeholders = columns.map((_, index) => `$${index + 1}`).join(', '); - const values = columns.map((column) => normalizeImportValue(column, row[column], row)); + const values = columns.map((column) => normalizeImportValue(row[column])); await client.query(`INSERT INTO ${tableName} (${columns.join(', ')}) VALUES (${placeholders}) ON CONFLICT DO NOTHING`, values); } } @@ -8163,7 +8112,7 @@ async function importScopeMainRows(client: DbClient, bundle: DataToolsBundle): P const recipeData = bundle.data.recipes; await insertRows(client, 'items', dataToolColumns.items, dataToolTableRows(itemData, 'items')); - await upsertRowsById(client, 'items', dataToolColumns.items, dataToolArtifactRows(artifactData)); + await upsertRowsById(client, 'items', dataToolColumns.items, dataToolTableRows(artifactData, 'artifacts')); await insertRows(client, 'pokemon', dataToolColumns.pokemon, dataToolTableRows(pokemonData, 'pokemon')); await insertRows(client, 'habitats', dataToolColumns.habitats, dataToolTableRows(habitatData, 'habitats')); await insertRows(client, 'daily_checklist_items', dataToolColumns.checklist, dataToolTableRows(checklistData, 'checklist')); @@ -8182,7 +8131,7 @@ async function importScopeRelationRows(client: DbClient, bundle: DataToolsBundle await insertRows(client, 'item_acquisition_methods', dataToolColumns.itemAcquisitionMethods, dataToolTableRows(itemData, 'itemAcquisitionMethods')); await insertRows(client, 'item_favorite_things', dataToolColumns.itemFavoriteThings, dataToolTableRows(itemData, 'itemFavoriteThings')); - await insertRowsIgnoreConflicts(client, 'item_favorite_things', dataToolColumns.itemFavoriteThings, dataToolArtifactFavoriteThingRows(artifactData)); + await insertRowsIgnoreConflicts(client, 'item_favorite_things', dataToolColumns.itemFavoriteThings, dataToolTableRows(artifactData, 'itemFavoriteThings')); await insertRows(client, 'pokemon_pokemon_types', dataToolColumns.pokemonTypeLinks, dataToolTableRows(pokemonData, 'pokemonTypeLinks')); await insertRows(client, 'pokemon_skills', dataToolColumns.pokemonSkills, dataToolTableRows(pokemonData, 'pokemonSkills')); await insertRows(client, 'pokemon_favorite_things', dataToolColumns.pokemonFavoriteThings, dataToolTableRows(pokemonData, 'pokemonFavoriteThings'));