diff --git a/package.json b/package.json index 02c8668..480f3f5 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "mysql-dev": "mysql --host=localhost --user=impress_2020_dev --password=impress_2020_dev --database=impress_2020_dev", "mysql-admin": "mysql --host=impress.openneo.net --user=matchu --password --database=openneo_impress", "mysqldump": "mysqldump --host=impress.openneo.net --user=$(dotenv -p IMPRESS_MYSQL_USER) --password=$(dotenv -p IMPRESS_MYSQL_PASSWORD) --column-statistics=0", - "download-mysql-schema": "yarn --silent mysqldump --no-data openneo_impress items item_translations pet_types pet_states swf_assets | sed 's/ AUTO_INCREMENT=[0-9]*//g' > scripts/setup-mysql-dev-schema.sql && yarn --silent mysqldump openneo_impress species species_translations colors color_translations > scripts/setup-mysql-dev-constants.sql", + "download-mysql-schema": "yarn --silent mysqldump --no-data openneo_impress items item_translations parents_swf_assets pet_types pet_states swf_assets | sed 's/ AUTO_INCREMENT=[0-9]*//g' > scripts/setup-mysql-dev-schema.sql && yarn --silent mysqldump openneo_impress species species_translations colors color_translations > scripts/setup-mysql-dev-constants.sql", "setup-mysql": "yarn mysql-admin < scripts/setup-mysql.sql", "setup-mysql-dev": "yarn mysql-dev < scripts/setup-mysql-dev-constants.sql && yarn mysql-dev < scripts/setup-mysql-dev-schema.sql", "build-cached-data": "node -r dotenv/config scripts/build-cached-data.js", diff --git a/scripts/setup-mysql-dev-constants.sql b/scripts/setup-mysql-dev-constants.sql index 943c270..0180c57 100644 --- a/scripts/setup-mysql-dev-constants.sql +++ b/scripts/setup-mysql-dev-constants.sql @@ -133,4 +133,4 @@ UNLOCK TABLES; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2020-09-19 4:00:01 +-- Dump completed on 2020-09-19 4:19:07 diff --git a/scripts/setup-mysql-dev-schema.sql b/scripts/setup-mysql-dev-schema.sql index 63c252b..2623c24 100644 --- a/scripts/setup-mysql-dev-schema.sql +++ b/scripts/setup-mysql-dev-schema.sql @@ -68,6 +68,25 @@ CREATE TABLE `item_translations` ( ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; +-- +-- Table structure for table `parents_swf_assets` +-- + +DROP TABLE IF EXISTS `parents_swf_assets`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `parents_swf_assets` ( + `parent_id` mediumint(9) NOT NULL, + `swf_asset_id` mediumint(9) NOT NULL, + `id` int(11) NOT NULL AUTO_INCREMENT, + `parent_type` varchar(8) COLLATE utf8_unicode_ci NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `unique_parents_swf_assets` (`parent_id`,`swf_asset_id`), + KEY `parents_swf_assets_swf_asset_id` (`swf_asset_id`), + KEY `index_parents_swf_assets_on_parent_id_and_parent_type` (`parent_id`,`parent_type`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + -- -- Table structure for table `pet_types` -- @@ -148,4 +167,4 @@ CREATE TABLE `swf_assets` ( /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2020-09-19 3:59:55 +-- Dump completed on 2020-09-19 4:19:01 diff --git a/src/server/loaders.js b/src/server/loaders.js index 9e5e944..39168be 100644 --- a/src/server/loaders.js +++ b/src/server/loaders.js @@ -401,8 +401,8 @@ const buildSwfAssetByRemoteIdLoader = (db) => const entities = rows.map(normalizeRow); - return swfAssetIds.map((remoteId) => - entities.find((e) => e.remoteId === remoteId) + return typeAndRemoteIdPairs.map(({ type, remoteId }) => + entities.find((e) => e.type === type && e.remoteId === remoteId) ); }, { cacheKeyFn: ({ type, remoteId }) => `${type},${remoteId}` } @@ -583,9 +583,9 @@ const buildPetStateByPetTypeAndAssetsLoader = (db) => const qs = petTypeIdAndAssetIdsPairs .map((_) => "(pet_type_id = ? AND swf_asset_ids = ?)") .join(" OR "); - const values = petTypeIdAndAssetIdsPairs.map( - ({ petTypeId, swfAssetIds }) => [petTypeId, swfAssetIds] - ); + const values = petTypeIdAndAssetIdsPairs + .map(({ petTypeId, swfAssetIds }) => [petTypeId, swfAssetIds]) + .flat(); const [rows, _] = await db.execute( `SELECT * FROM pet_states WHERE ${qs}`, values diff --git a/src/server/query-tests/Pet.test.js b/src/server/query-tests/Pet.test.js index 3ca5544..5f9a51d 100644 --- a/src/server/query-tests/Pet.test.js +++ b/src/server/query-tests/Pet.test.js @@ -90,6 +90,14 @@ describe("Pet", () => { id pose bodyId + + restrictedZones { + id + } + layers { + id + swfUrl + } } items( diff --git a/src/server/query-tests/__snapshots__/Pet.test.js.snap b/src/server/query-tests/__snapshots__/Pet.test.js.snap index b6c1a55..bd6831b 100644 --- a/src/server/query-tests/__snapshots__/Pet.test.js.snap +++ b/src/server/query-tests/__snapshots__/Pet.test.js.snap @@ -491,10 +491,8 @@ Array [ Array [ "SELECT * FROM pet_states WHERE (pet_type_id = ? AND swf_asset_ids = ?)", Array [ - Array [ - "1", - "21057,21060,24008,7941,7942,7946", - ], + "1", + "21057,21060,24008,7941,7942,7946", ], ], Array [ @@ -508,6 +506,54 @@ Array [ false, ], ], + Array [ + "SELECT * FROM pet_states WHERE (pet_type_id = ? AND swf_asset_ids = ?)", + Array [ + "1", + "21057,21060,24008,7941,7942,7946", + ], + ], + Array [ + "SELECT * FROM swf_assets WHERE (type = ? AND remote_id = ?) OR (type = ? AND remote_id = ?) OR (type = ? AND remote_id = ?) OR (type = ? AND remote_id = ?) OR (type = ? AND remote_id = ?) OR (type = ? AND remote_id = ?)", + Array [ + "biology", + "7942", + "biology", + "7941", + "biology", + "24008", + "biology", + "21060", + "biology", + "21057", + "biology", + "7946", + ], + ], + Array [ + "INSERT INTO parents_swf_assets (parent_type, parent_id, swf_asset_id) + VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?), (?, ?, ?), (?, ?, ?), (?, ?, ?);", + Array [ + "PetState", + "1", + "9", + "PetState", + "1", + "10", + "PetState", + "1", + "11", + "PetState", + "1", + "12", + "PetState", + "1", + "13", + "PetState", + "1", + "14", + ], + ], ] `; @@ -590,7 +636,34 @@ Object { "petAppearance": Object { "bodyId": "180", "id": "1", + "layers": Array [ + Object { + "id": "9", + "swfUrl": "http://images.neopets.com/cp/bio/swf/000/000/007/7942_2eab06fd7b.swf", + }, + Object { + "id": "10", + "swfUrl": "http://images.neopets.com/cp/bio/swf/000/000/007/7941_2c4cc4b846.swf", + }, + Object { + "id": "11", + "swfUrl": "http://images.neopets.com/cp/bio/swf/000/000/024/24008_a05fe9876a.swf", + }, + Object { + "id": "12", + "swfUrl": "http://images.neopets.com/cp/bio/swf/000/000/021/21060_d77ba93b7b.swf", + }, + Object { + "id": "13", + "swfUrl": "http://images.neopets.com/cp/bio/swf/000/000/021/21057_4550efbb2f.swf", + }, + Object { + "id": "14", + "swfUrl": "http://images.neopets.com/cp/bio/swf/000/000/007/7946_0348dad587.swf", + }, + ], "pose": "SAD_MASC", + "restrictedZones": Array [], }, } `; @@ -639,6 +712,16 @@ Array [ "1", ], ], + Array [ + "SELECT sa.*, rel.parent_id FROM swf_assets sa + INNER JOIN parents_swf_assets rel ON + rel.parent_type = \\"PetState\\" AND + rel.swf_asset_id = sa.id + WHERE rel.parent_id IN (?)", + Array [ + "1", + ], + ], ] `; @@ -980,10 +1063,8 @@ Array [ Array [ "SELECT * FROM pet_states WHERE (pet_type_id = ? AND swf_asset_ids = ?)", Array [ - Array [ - "1", - "21057,21060,24008,7941,7942,7946", - ], + "1", + "21057,21060,24008,7941,7942,7946", ], ], Array [ @@ -997,6 +1078,54 @@ Array [ false, ], ], + Array [ + "SELECT * FROM pet_states WHERE (pet_type_id = ? AND swf_asset_ids = ?)", + Array [ + "1", + "21057,21060,24008,7941,7942,7946", + ], + ], + Array [ + "SELECT * FROM swf_assets WHERE (type = ? AND remote_id = ?) OR (type = ? AND remote_id = ?) OR (type = ? AND remote_id = ?) OR (type = ? AND remote_id = ?) OR (type = ? AND remote_id = ?) OR (type = ? AND remote_id = ?)", + Array [ + "biology", + "7942", + "biology", + "7941", + "biology", + "24008", + "biology", + "21060", + "biology", + "21057", + "biology", + "7946", + ], + ], + Array [ + "INSERT INTO parents_swf_assets (parent_type, parent_id, swf_asset_id) + VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?), (?, ?, ?), (?, ?, ?), (?, ?, ?);", + Array [ + "PetState", + "1", + "9", + "PetState", + "1", + "10", + "PetState", + "1", + "11", + "PetState", + "1", + "12", + "PetState", + "1", + "13", + "PetState", + "1", + "14", + ], + ], ] `; diff --git a/src/server/types/Outfit.js b/src/server/types/Outfit.js index 88ba1f5..4122c61 100644 --- a/src/server/types/Outfit.js +++ b/src/server/types/Outfit.js @@ -211,21 +211,19 @@ async function saveModelingData( const incomingSwfAssets = [...incomingItemSwfAssets, ...incomingPetSwfAssets]; - const incomingPetTypes = [ - { - colorId: String(customPet.color_id), - speciesId: String(customPet.species_id), - bodyId: String(customPet.body_id), - // NOTE: I skip the image_hash stuff here... on Rails, we set a hash on - // creation, and may or may not bother to update it, I forget? But - // here I don't want to bother with an update. We could maybe do - // a merge function to make it on create only, but eh, I don't - // care enough ^_^` - }, - ]; + const incomingPetType = { + colorId: String(customPet.color_id), + speciesId: String(customPet.species_id), + bodyId: String(customPet.body_id), + // NOTE: I skip the image_hash stuff here... on Rails, we set a hash on + // creation, and may or may not bother to update it, I forget? But + // here I don't want to bother with an update. We could maybe do + // a merge function to make it on create only, but eh, I don't + // care enough ^_^` + }; await Promise.all([ - syncToDb(db, incomingPetTypes, { + syncToDb(db, [incomingPetType], { loader: petTypeBySpeciesAndColorLoader, tableName: "pet_types", buildLoaderKey: (row) => ({ @@ -276,21 +274,19 @@ async function saveModelingData( colorId: String(customPet.color_id), speciesId: String(customPet.species_id), }); - const incomingPetStates = [ - { - petTypeId: petType.id, - swfAssetIds: incomingPetSwfAssets - .map((a) => a.remoteId) - .sort() - .join(","), - female: petMetaData.gender === 2, // sorry for this column name :/ - moodId: petMetaData.mood, - unconverted: incomingPetSwfAssets.length === 1, - labeled: true, - }, - ]; + const incomingPetState = { + petTypeId: petType.id, + swfAssetIds: incomingPetSwfAssets + .map((a) => a.remoteId) + .sort() + .join(","), + female: petMetaData.gender === 2, // sorry for this column name :/ + moodId: petMetaData.mood, + unconverted: incomingPetSwfAssets.length === 1, + labeled: true, + }; - await syncToDb(db, incomingPetStates, { + await syncToDb(db, [incomingPetState], { loader: petStateByPetTypeAndAssetsLoader, tableName: "pet_states", buildLoaderKey: (row) => ({ @@ -304,6 +300,35 @@ async function saveModelingData( ], includeCreatedAt: false, includeUpdatedAt: false, + // For pet states, syncing assets is easy: a new set of assets counts as a + // new state, so, whatever! Just insert the relationships when inserting + // the pet state, and ignore them any other time. + afterInsert: async () => { + // We need to load from the db to get the actual inserted IDs. Not lovely + // for perf, but this is a real new-data model, so that's fine! + let [petState, swfAssets] = await Promise.all([ + petStateByPetTypeAndAssetsLoader.load({ + petTypeId: incomingPetState.petTypeId, + swfAssetIds: incomingPetState.swfAssetIds, + }), + swfAssetByRemoteIdLoader.loadMany( + incomingPetSwfAssets.map((row) => ({ + type: row.type, + remoteId: row.remoteId, + })) + ), + ]); + swfAssets = swfAssets.filter((sa) => sa != null); + const qs = swfAssets.map((_) => `(?, ?, ?)`).join(", "); + const values = swfAssets + .map((sa) => ["PetState", petState.id, sa.id]) + .flat(); + await db.execute( + `INSERT INTO parents_swf_assets (parent_type, parent_id, swf_asset_id) + VALUES ${qs};`, + values + ); + }, }); } @@ -329,6 +354,7 @@ async function syncToDb( buildUpdateCondition, includeCreatedAt = true, includeUpdatedAt = true, + afterInsert = null, } ) { const loaderKeys = incomingRows.map(buildLoaderKey); @@ -396,6 +422,9 @@ async function syncToDb( `INSERT INTO ${tableName} (${columnsStr}) VALUES ${rowQs};`, rowFields.flat() ); + if (afterInsert) { + await afterInsert(); + } } // Do parallel updates of anything that needs updated.