diff --git a/src/server/loaders.js b/src/server/loaders.js index 9d6083f..cca9df9 100644 --- a/src/server/loaders.js +++ b/src/server/loaders.js @@ -1039,6 +1039,29 @@ const buildPetSwfAssetLoader = (db, loaders) => ); }); +const buildAltStyleSwfAssetLoader = (db, loaders) => + new DataLoader(async (altStyleIds) => { + const qs = altStyleIds.map((_) => "?").join(","); + const [rows] = await db.execute( + `SELECT sa.*, rel.parent_id FROM swf_assets sa + INNER JOIN parents_swf_assets rel ON + rel.parent_type = "AltStyle" AND + rel.swf_asset_id = sa.id + WHERE rel.parent_id IN (${qs})`, + altStyleIds, + ); + + const entities = rows.map(normalizeRow); + + for (const swfAsset of entities) { + loaders.swfAssetLoader.prime(swfAsset.id, swfAsset); + } + + return altStyleIds.map((altStyleId) => + entities.filter((e) => e.parentId === altStyleId), + ); + }); + const buildNeopetsConnectionLoader = (db) => new DataLoader(async (ids) => { const qs = ids.map((_) => "?").join(", "); @@ -1490,6 +1513,7 @@ function buildLoaders(db) { loaders.swfAssetByRemoteIdLoader = buildSwfAssetByRemoteIdLoader(db); loaders.itemSwfAssetLoader = buildItemSwfAssetLoader(db, loaders); loaders.petSwfAssetLoader = buildPetSwfAssetLoader(db, loaders); + loaders.altStyleSwfAssetLoader = buildAltStyleSwfAssetLoader(db, loaders); loaders.neopetsConnectionLoader = buildNeopetsConnectionLoader(db); loaders.outfitLoader = buildOutfitLoader(db); loaders.itemOutfitRelationshipsLoader = diff --git a/src/server/types/Outfit.js b/src/server/types/Outfit.js index 55f52ba..b032825 100644 --- a/src/server/types/Outfit.js +++ b/src/server/types/Outfit.js @@ -62,9 +62,9 @@ const resolvers = { const outfit = await outfitLoader.load(id); return outfit.name; }, - petAppearance: async ({ id }, _, { outfitLoader }) => { + petAppearance: async ({ id }, _, { outfitLoader, altStyleLoader }) => { const outfit = await outfitLoader.load(id); - return { id: outfit.petStateId }; + return { id: outfit.petStateId, altStyleId: outfit.altStyleId }; }, itemAppearances: async ( { id }, @@ -73,15 +73,26 @@ const resolvers = { outfitLoader, petStateLoader, petTypeLoader, + altStyleLoader, itemOutfitRelationshipsLoader, - } + }, ) => { - const [petType, relationships] = await Promise.all([ - outfitLoader - .load(id) - .then((outfit) => petStateLoader.load(outfit.petStateId)) - .then((petState) => petTypeLoader.load(petState.petTypeId)), - itemOutfitRelationshipsLoader.load(id), + const relationshipsPromise = itemOutfitRelationshipsLoader.load(id); + + const outfit = await outfitLoader.load(id); + const bodyIdPromise = + outfit.altStyleId != null + ? altStyleLoader + .load(outfit.altStyleId) + .then((altStyle) => altStyle.bodyId) + : petStateLoader + .load(outfit.petStateId) + .then((petState) => petTypeLoader.load(petState.petTypeId)) + .then((petType) => petType.bodyId); + + const [bodyId, relationships] = await Promise.all([ + bodyIdPromise, + relationshipsPromise, ]); const wornItemIds = relationships @@ -90,7 +101,7 @@ const resolvers = { return wornItemIds.map((itemId) => ({ item: { id: itemId }, - bodyId: petType.bodyId, + bodyId, })); }, wornItems: async ({ id }, _, { itemOutfitRelationshipsLoader }) => { @@ -157,11 +168,11 @@ const resolvers = { outfitLoader, petTypeBySpeciesAndColorLoader, petStatesForPetTypeLoader, - } + }, ) => { if (!currentUserId) { throw new Error( - "saveOutfit requires login for now. This might change!" + "saveOutfit requires login for now. This might change!", ); } @@ -172,7 +183,7 @@ const resolvers = { } if (outfit.userId !== currentUserId) { throw new Error( - `user ${currentUserId} does not own outfit ${outfit.id}` + `user ${currentUserId} does not own outfit ${outfit.id}`, ); } } @@ -181,7 +192,7 @@ const resolvers = { // suffixes. const baseName = (rawName || "Untitled outfit").replace( /\s*\([0-9]+\)\s*$/, - "" + "", ); const namePlaceholder = baseName.trim().replace(/_%/g, "\\$0") + "%"; @@ -192,10 +203,10 @@ const resolvers = { ` SELECT name FROM outfits WHERE user_id = ? AND name LIKE ? AND id != ?; `, - [currentUserId, namePlaceholder, id || ""] + [currentUserId, namePlaceholder, id || ""], ); const existingOutfitNames = new Set( - outfitRows.map(({ name }) => name.trim()) + outfitRows.map(({ name }) => name.trim()), ); // Then, get the unique name to use for this outfit: try the provided @@ -213,7 +224,7 @@ const resolvers = { }); if (!petType) { throw new Error( - `could not find pet type for species=${speciesId}, color=${colorId}` + `could not find pet type for species=${speciesId}, color=${colorId}`, ); } // TODO: We could query for this more directly, instead of loading all @@ -222,7 +233,7 @@ const resolvers = { const petState = petStates.find((ps) => getPoseFromPetState(ps) === pose); if (!petState) { throw new Error( - `could not find appearance for species=${speciesId}, color=${colorId}, pose=${pose}` + `could not find appearance for species=${speciesId}, color=${colorId}, pose=${pose}`, ); } @@ -242,7 +253,7 @@ const resolvers = { updated_at = CURRENT_TIMESTAMP() WHERE id = ?; `, - [name, petState.id, id] + [name, petState.id, id], ) : await connection.execute( ` @@ -250,7 +261,7 @@ const resolvers = { (name, pet_state_id, user_id, created_at, updated_at) VALUES (?, ?, ?, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP()); `, - [name, petState.id, currentUserId] + [name, petState.id, currentUserId], ); newOutfitId = id || String(result.insertId); @@ -264,14 +275,14 @@ const resolvers = { if (id) { await connection.execute( `DELETE FROM item_outfit_relationships WHERE outfit_id = ?;`, - [id] + [id], ); } if (wornItemIds.length > 0 || closetedItemIds.length > 0) { const itemRowPlaceholders = [ [...wornItemIds, ...closetedItemIds].map( - (_) => `(?, ?, ?, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP())` + (_) => `(?, ?, ?, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP())`, ), ].join(", "); const itemRowValues = [ @@ -286,7 +297,7 @@ const resolvers = { (outfit_id, item_id, is_worn, created_at, updated_at) VALUES ${itemRowPlaceholders}; `, - itemRowValues + itemRowValues, ); } @@ -314,7 +325,7 @@ const resolvers = { } if (outfit.userId !== currentUserId) { throw new Error( - `user ${currentUserId} does not own outfit ${outfit.id}` + `user ${currentUserId} does not own outfit ${outfit.id}`, ); } diff --git a/src/server/types/PetAppearance.js b/src/server/types/PetAppearance.js index 69b2e86..47336e5 100644 --- a/src/server/types/PetAppearance.js +++ b/src/server/types/PetAppearance.js @@ -279,13 +279,29 @@ const resolvers = { const petType = await petTypeLoader.load(petState.petTypeId); return { id: petType.speciesId }; }, - body: async ({ id }, _, { petStateLoader, petTypeLoader }) => { + body: async ( + { id, altStyleId }, + _, + { petStateLoader, petTypeLoader, altStyleLoader }, + ) => { const petState = await petStateLoader.load(id); + if (altStyleId != null) { + const altStyle = await altStyleLoader.load(altStyleId); + return { id: altStyle.bodyId }; + } const petType = await petTypeLoader.load(petState.petTypeId); return { id: petType.bodyId }; }, - bodyId: async ({ id }, _, { petStateLoader, petTypeLoader }) => { + bodyId: async ( + { id, altStyleId }, + _, + { petStateLoader, petTypeLoader, altStyleLoader }, + ) => { const petState = await petStateLoader.load(id); + if (altStyleId != null) { + const altStyle = await altStyleLoader.load(altStyleId); + return altStyle.bodyId; + } const petType = await petTypeLoader.load(petState.petTypeId); return petType.bodyId; }, @@ -293,14 +309,28 @@ const resolvers = { const petState = await petStateLoader.load(id); return getPoseFromPetState(petState); }, - layers: async ({ id }, _, { petSwfAssetLoader }) => { - const swfAssets = await petSwfAssetLoader.load(id); + layers: async ( + { id, altStyleId }, + _, + { petSwfAssetLoader, altStyleSwfAssetLoader }, + ) => { + const swfAssets = + altStyleId != null + ? await altStyleSwfAssetLoader.load(altStyleId) + : await petSwfAssetLoader.load(id); return swfAssets; }, - restrictedZones: async ({ id }, _, { petSwfAssetLoader }) => { + restrictedZones: async ( + { id, altStyleId }, + _, + { petSwfAssetLoader, altStyleSwfAssetLoader }, + ) => { // The restricted zones are defined on the layers. Load them and aggegate // the zones, then uniquify and sort them for ease of use. - const swfAssets = await petSwfAssetLoader.load(id); + const swfAssets = + altStyleId != null + ? await altStyleSwfAssetLoader.load(altStyleId) + : await petSwfAssetLoader.load(id); let restrictedZoneIds = swfAssets .map((sa) => getRestrictedZoneIds(sa.zonesRestrict)) .flat();