Files
recflare/apps/api/migrations/0022_custom_avatar_item_json.sql

70 lines
4.0 KiB
SQL

-- Custom avatar items become a JSON blob per row, the layout every other entity table on this
-- database uses (`invention`, `event`, `room`, `account`, `club`): the client's `CustomAvatarItem`
-- record verbatim in `data`, with the fields the queries filter and sort on exposed as generated
-- (virtual) columns. Generated from src/custom-avatar-items-db.ts (SCHEMA_DDL) — keep in sync.
--
-- Why: the flat table (0015) had one NOT NULL column per field of the player-made shirt, and the
-- FIRST-PARTY items being imported are the same record with those four fields NULL
-- (`BaseAvatarItemId`, `BaseAvatarItemColor`, `DesignFilename`, `ThumbnailImageFilename` — a
-- studio-built item has no base item and no design PNG) plus fields the table had no home for:
-- `CurrentSaves` (one built Unity assetbundle per body type, which is how the client renders
-- them), `Tags`, `CustomBadgeMetadata`, `RankedEntityId`. Storing the record whole means the
-- import is the record as exported, and a field the source adds later is served without a
-- migration. `CurrentSaves` stays embedded rather than in a child table: the client reads a
-- save off its item and picks one by `BodyType`; nothing looks a save up by its bare id.
--
-- The generated columns keep the names the flat table had, so the indexes and every query
-- read the same. `name_lower`/`description_lower` back the store's search, which matches the
-- needle as a case-folded substring of either. Booleans come out of `json_extract` as 1/0
-- (see 0008), so `is_featured = 1` keeps working.
--
-- The existing player-made rows are carried across by rebuilding the DTO the old `toDto`
-- built from their columns — the same record the routes were already serving — with the
-- fields the flat table never had filled in as it served them (empty saves and tags, null
-- badge/ranking, `RankedEntityId` = the item id, as the official record carries it).
CREATE TABLE custom_avatar_item_new (
data TEXT NOT NULL,
custom_avatar_item_id TEXT GENERATED ALWAYS AS (json_extract(data, '$.CustomAvatarItemId')) VIRTUAL,
creator_account_id INTEGER GENERATED ALWAYS AS (json_extract(data, '$.CreatorAccountId')) VIRTUAL,
accessibility INTEGER GENERATED ALWAYS AS (json_extract(data, '$.Accessibility')) VIRTUAL,
outfit_type INTEGER GENERATED ALWAYS AS (json_extract(data, '$.OutfitType')) VIRTUAL,
price INTEGER GENERATED ALWAYS AS (json_extract(data, '$.Price')) VIRTUAL,
is_featured INTEGER GENERATED ALWAYS AS (json_extract(data, '$.IsFeatured')) VIRTUAL,
created_at TEXT GENERATED ALWAYS AS (json_extract(data, '$.CreatedAt')) VIRTUAL,
name_lower TEXT GENERATED ALWAYS AS (lower(coalesce(json_extract(data, '$.Name'), ''))) VIRTUAL,
description_lower TEXT GENERATED ALWAYS AS (lower(coalesce(json_extract(data, '$.Description'), ''))) VIRTUAL
);
INSERT INTO custom_avatar_item_new (data)
SELECT json_object(
'CustomAvatarItemId', custom_avatar_item_id,
'CreatorAccountId', creator_account_id,
'Name', name,
'Description', description,
'Price', price,
'Accessibility', accessibility,
'ForceCannotPublish', json(iif(force_cannot_publish, 'true', 'false')),
'IsFeatured', json(iif(is_featured, 'true', 'false')),
'IsRecRoomApproved', json(iif(is_rec_room_approved, 'true', 'false')),
'BaseAvatarItemId', base_avatar_item_id,
'BaseAvatarItemColor', base_avatar_item_color,
'DesignFilename', design_filename,
'ThumbnailImageFilename', thumbnail_image_filename,
'CreatedAt', created_at,
'ModifiedAt', modified_at,
'PreviewOrientation', preview_orientation,
'OutfitType', outfit_type,
'CurrentSaves', json('[]'),
'Tags', json('[]'),
'CustomBadgeMetadata', NULL,
'RankedEntityId', custom_avatar_item_id,
'RankingContext', NULL
) FROM custom_avatar_item;
DROP TABLE custom_avatar_item;
ALTER TABLE custom_avatar_item_new RENAME TO custom_avatar_item;
CREATE UNIQUE INDEX IF NOT EXISTS idx_custom_avatar_item_id ON custom_avatar_item (custom_avatar_item_id);
CREATE INDEX IF NOT EXISTS idx_custom_avatar_item_creator ON custom_avatar_item (creator_account_id);