diff --git a/src/server/loaders.js b/src/server/loaders.js index 8934003..1cae144 100644 --- a/src/server/loaders.js +++ b/src/server/loaders.js @@ -6,7 +6,7 @@ const buildClosetListLoader = (db) => const qs = ids.map((_) => "?").join(","); const [rows] = await db.execute( `SELECT * FROM closet_lists WHERE id IN (${qs})`, - ids + ids, ); const entities = rows.map(normalizeRow); @@ -19,13 +19,13 @@ const buildClosetHangersForListLoader = (db) => const qs = closetListIds.map((_) => "?").join(","); const [rows] = await db.execute( `SELECT * FROM closet_hangers WHERE list_id IN (${qs})`, - closetListIds + closetListIds, ); const entities = rows.map(normalizeRow); return closetListIds.map((closetListId) => - entities.filter((e) => e.listId === closetListId) + entities.filter((e) => e.listId === closetListId), ); }); @@ -42,7 +42,7 @@ const buildClosetHangersForDefaultListLoader = (db) => .flat(); const [rows] = await db.execute( `SELECT * FROM closet_hangers WHERE ${conditions}`, - values + values, ); const entities = rows.map(normalizeRow); @@ -51,8 +51,8 @@ const buildClosetHangersForDefaultListLoader = (db) => entities.filter( (e) => e.userId === userId && - Boolean(e.owned) === (ownsOrWantsItems === "OWNS") - ) + Boolean(e.owned) === (ownsOrWantsItems === "OWNS"), + ), ); }); @@ -61,7 +61,7 @@ const buildColorLoader = (db) => { const qs = colorIds.map((_) => "?").join(","); const [rows] = await db.execute( `SELECT * FROM colors WHERE id IN (${qs}) AND prank = 0`, - colorIds + colorIds, ); const entities = rows.map(normalizeRow); @@ -70,7 +70,7 @@ const buildColorLoader = (db) => { return colorIds.map( (colorId) => entitiesByColorId.get(String(colorId)) || - new Error(`could not find color ${colorId}`) + new Error(`could not find color ${colorId}`), ); }); @@ -94,7 +94,7 @@ const buildColorTranslationLoader = (db) => const [rows] = await db.execute( `SELECT * FROM color_translations WHERE color_id IN (${qs}) AND locale = "en"`, - colorIds + colorIds, ); const entities = rows.map(normalizeRow); @@ -103,7 +103,7 @@ const buildColorTranslationLoader = (db) => return colorIds.map( (colorId) => entitiesByColorId.get(String(colorId)) || - new Error(`could not find translation for color ${colorId}`) + new Error(`could not find translation for color ${colorId}`), ); }); @@ -112,7 +112,7 @@ const buildSpeciesLoader = (db) => { const qs = speciesIds.map((_) => "?").join(","); const [rows] = await db.execute( `SELECT * FROM species WHERE id IN (${qs})`, - speciesIds + speciesIds, ); const entities = rows.map(normalizeRow); @@ -121,7 +121,7 @@ const buildSpeciesLoader = (db) => { return speciesIds.map( (speciesId) => entitiesBySpeciesId.get(String(speciesId)) || - new Error(`could not find color ${speciesId}`) + new Error(`could not find color ${speciesId}`), ); }); @@ -145,7 +145,7 @@ const buildSpeciesTranslationLoader = (db) => const [rows] = await db.execute( `SELECT * FROM species_translations WHERE species_id IN (${qs}) AND locale = "en"`, - speciesIds + speciesIds, ); const entities = rows.map(normalizeRow); @@ -154,7 +154,7 @@ const buildSpeciesTranslationLoader = (db) => return speciesIds.map( (speciesId) => entitiesBySpeciesId.get(String(speciesId)) || - new Error(`could not find translation for species ${speciesId}`) + new Error(`could not find translation for species ${speciesId}`), ); }); @@ -164,7 +164,7 @@ const buildTradeMatchesLoader = (db) => const conditions = userPairs .map( (_) => - `(public_user_hangers.user_id = ? AND current_user_hangers.user_id = ? AND public_user_hangers.owned = ? AND current_user_hangers.owned = ?)` + `(public_user_hangers.user_id = ? AND current_user_hangers.user_id = ? AND public_user_hangers.owned = ? AND current_user_hangers.owned = ?)`, ) .join(" OR "); const conditionValues = userPairs @@ -175,7 +175,7 @@ const buildTradeMatchesLoader = (db) => return [publicUserId, currentUserId, false, true]; } else { throw new Error( - `unexpected user pair direction: ${JSON.stringify(direction)}` + `unexpected user pair direction: ${JSON.stringify(direction)}`, ); } }) @@ -217,7 +217,7 @@ const buildTradeMatchesLoader = (db) => ) GROUP BY public_user_id, current_user_id; `, - conditionValues + conditionValues, ); const entities = rows.map(normalizeRow); @@ -227,7 +227,7 @@ const buildTradeMatchesLoader = (db) => (e) => e.publicUserId === publicUserId && e.currentUserId === currentUserId && - e.direction === direction + e.direction === direction, ); return entity ? entity.itemIds.split(",") : []; }); @@ -235,7 +235,7 @@ const buildTradeMatchesLoader = (db) => { cacheKeyFn: ({ publicUserId, currentUserId, direction }) => `${publicUserId}-${currentUserId}-${direction}`, - } + }, ); const loadAllPetTypes = (db) => async () => { @@ -249,7 +249,7 @@ const buildItemLoader = (db) => const qs = ids.map((_) => "?").join(","); const [rows] = await db.execute( `SELECT * FROM items WHERE id IN (${qs})`, - ids + ids, ); const entities = rows.map(normalizeRow); @@ -258,7 +258,7 @@ const buildItemLoader = (db) => return ids.map( (id) => entitiesById.get(String(id)) || - new Error(`could not find item with ID: ${id}`) + new Error(`could not find item with ID: ${id}`), ); }); @@ -267,7 +267,7 @@ const buildItemTranslationLoader = (db) => const qs = itemIds.map((_) => "?").join(","); const [rows] = await db.execute( `SELECT * FROM item_translations WHERE item_id IN (${qs}) AND locale = "en"`, - itemIds + itemIds, ); const entities = rows.map(normalizeRow); @@ -276,7 +276,7 @@ const buildItemTranslationLoader = (db) => return itemIds.map( (itemId) => entitiesByItemId.get(String(itemId)) || - new Error(`could not find translation for item ${itemId}`) + new Error(`could not find translation for item ${itemId}`), ); }); @@ -293,7 +293,7 @@ const buildItemByNameLoader = (db, loaders) => WHERE name IN (${qs}) AND locale = "en"`, nestTables: true, }, - normalizedNames + normalizedNames, ); const entitiesByName = new Map(); @@ -309,10 +309,10 @@ const buildItemByNameLoader = (db, loaders) => return normalizedNames.map( (name) => - entitiesByName.get(name) || { item: null, itemTranslation: null } + entitiesByName.get(name) || { item: null, itemTranslation: null }, ); }, - { cacheKeyFn: (name) => name.trim().toLowerCase() } + { cacheKeyFn: (name) => name.trim().toLowerCase() }, ); const itemSearchKindConditions = { @@ -409,18 +409,15 @@ const buildItemSearchNumTotalItemsLoader = (db) => currentUserId, zoneIds = [], }) => { - const { - queryJoins, - queryConditions, - queryConditionValues, - } = buildItemSearchConditions({ - query, - bodyId, - itemKind, - currentUserOwnsOrWants, - currentUserId, - zoneIds, - }); + const { queryJoins, queryConditions, queryConditionValues } = + buildItemSearchConditions({ + query, + bodyId, + itemKind, + currentUserOwnsOrWants, + currentUserId, + zoneIds, + }); const [totalRows] = await db.execute( ` @@ -428,12 +425,12 @@ const buildItemSearchNumTotalItemsLoader = (db) => ${queryJoins} WHERE ${queryConditions} `, - queryConditionValues + queryConditionValues, ); const { numTotalItems } = totalRows[0]; return numTotalItems; - } + }, ); const responses = await Promise.all(queryPromises); @@ -459,18 +456,15 @@ const buildItemSearchItemsLoader = (db, loaders) => const actualOffset = offset || 0; const actualLimit = Math.min(limit || 30, 30); - const { - queryJoins, - queryConditions, - queryConditionValues, - } = buildItemSearchConditions({ - query, - bodyId, - itemKind, - currentUserOwnsOrWants, - currentUserId, - zoneIds, - }); + const { queryJoins, queryConditions, queryConditionValues } = + buildItemSearchConditions({ + query, + bodyId, + itemKind, + currentUserOwnsOrWants, + currentUserId, + zoneIds, + }); const [rows] = await db.execute( ` @@ -480,7 +474,7 @@ const buildItemSearchItemsLoader = (db, loaders) => ORDER BY t.name LIMIT ? OFFSET ? `, - [...queryConditionValues, actualLimit, actualOffset] + [...queryConditionValues, actualLimit, actualOffset], ); const entities = rows.map(normalizeRow); @@ -490,7 +484,7 @@ const buildItemSearchItemsLoader = (db, loaders) => } return entities; - } + }, ); const responses = await Promise.all(queryPromises); @@ -504,12 +498,12 @@ const buildNewestItemsLoader = (db, loaders) => // loaders, even though there's only one query to run. if (keys.length !== 1 && keys[0] !== "all-newest") { throw new Error( - `this loader can only be loaded with the key "all-newest"` + `this loader can only be loaded with the key "all-newest"`, ); } const [rows] = await db.execute( - `SELECT * FROM items ORDER BY created_at DESC LIMIT 20;` + `SELECT * FROM items ORDER BY created_at DESC LIMIT 20;`, ); const entities = rows.map(normalizeRow); @@ -616,7 +610,7 @@ async function runItemModelingQuery(db, filterToItemIds) { -- take up a bunch of resources and crash the site? LIMIT 200; `, - [...itemIdsValues] + [...itemIdsValues], ); } @@ -638,10 +632,10 @@ const buildSpeciesThatNeedModelsForItemLoader = (db) => // color built into the query (well, no row when no models needed!). So, // find the right row for each color/item pair, or possibly null! return colorIdAndItemIdPairs.map(({ colorId, itemId }) => - entities.find((e) => e.itemId === itemId && e.colorId === colorId) + entities.find((e) => e.itemId === itemId && e.colorId === colorId), ); }, - { cacheKeyFn: ({ colorId, itemId }) => `${colorId}-${itemId}` } + { cacheKeyFn: ({ colorId, itemId }) => `${colorId}-${itemId}` }, ); const buildItemsThatNeedModelsLoader = (db, loaders) => @@ -661,7 +655,7 @@ const buildItemsThatNeedModelsLoader = (db, loaders) => for (const { colorId, itemId, ...entity } of entities) { loaders.speciesThatNeedModelsForItemLoader.prime( { colorId, itemId }, - entity + entity, ); if (!result.has(colorId)) { @@ -697,7 +691,7 @@ const buildAllSpeciesIdsForColorLoader = (db) => ) GROUP BY color_id; `, - colorIds + colorIds, ); const entities = rows.map(normalizeRow); @@ -705,7 +699,7 @@ const buildAllSpeciesIdsForColorLoader = (db) => return colorIds.map( (colorId) => entities.find((e) => e.colorId === colorId)?.speciesIds?.split(",") || - [] + [], ); }); @@ -731,7 +725,7 @@ const buildItemBodiesWithAppearanceDataLoader = (db) => ORDER BY pet_types.species_id, colors.standard DESC`, - itemIds + itemIds, ); const entities = rows.map(normalizeRow); @@ -749,7 +743,7 @@ const buildItemAllOccupiedZonesLoader = (db) => INNER JOIN swf_assets sa ON sa.id = psa.swf_asset_id WHERE items.id IN (${qs}) GROUP BY items.id;`, - itemIds + itemIds, ); const entities = rows.map(normalizeRow); @@ -787,7 +781,7 @@ const buildItemCompatibleBodiesAndTheirZonesLoader = (db) => -- matches no pet type. Huh! Well, ignore those bodies! HAVING speciesId IS NOT NULL OR bodyId = 0; `, - itemIds + itemIds, ); const entities = rows.map(normalizeRow); @@ -829,7 +823,7 @@ const buildItemTradesLoader = (db, loaders) => `, nestTables: true, }, - values + values, ); const entities = rows.map((row) => ({ @@ -848,16 +842,16 @@ const buildItemTradesLoader = (db, loaders) => .filter( (e) => e.closetHanger.itemId === itemId && - Boolean(e.closetHanger.owned) === isOwned + Boolean(e.closetHanger.owned) === isOwned, ) .map((e) => ({ id: e.closetHanger.id, closetList: e.closetList.id ? e.closetList : null, user: e.user, - })) + })), ); }, - { cacheKeyFn: ({ itemId, isOwned }) => `${itemId}-${isOwned}` } + { cacheKeyFn: ({ itemId, isOwned }) => `${itemId}-${isOwned}` }, ); const buildPetTypeLoader = (db, loaders) => @@ -865,7 +859,7 @@ const buildPetTypeLoader = (db, loaders) => const qs = petTypeIds.map((_) => "?").join(","); const [rows] = await db.execute( `SELECT * FROM pet_types WHERE id IN (${qs})`, - petTypeIds + petTypeIds, ); const entities = rows.map(normalizeRow); @@ -873,12 +867,12 @@ const buildPetTypeLoader = (db, loaders) => for (const petType of entities) { loaders.petTypeBySpeciesAndColorLoader.prime( { speciesId: petType.speciesId, colorId: petType.colorId }, - petType + petType, ); } return petTypeIds.map((petTypeId) => - entities.find((e) => e.id === petTypeId) + entities.find((e) => e.id === petTypeId), ); }); @@ -894,12 +888,12 @@ const buildPetTypeBySpeciesAndColorLoader = (db, loaders) => const [rows] = await db.execute( `SELECT * FROM pet_types WHERE ${conditions.join(" OR ")}`, - values + values, ); const entities = rows.map(normalizeRow); const entitiesBySpeciesAndColorPair = new Map( - entities.map((e) => [`${e.speciesId},${e.colorId}`, e]) + entities.map((e) => [`${e.speciesId},${e.colorId}`, e]), ); for (const petType of entities) { @@ -907,10 +901,10 @@ const buildPetTypeBySpeciesAndColorLoader = (db, loaders) => } return speciesAndColorPairs.map(({ speciesId, colorId }) => - entitiesBySpeciesAndColorPair.get(`${speciesId},${colorId}`) + entitiesBySpeciesAndColorPair.get(`${speciesId},${colorId}`), ); }, - { cacheKeyFn: ({ speciesId, colorId }) => `${speciesId},${colorId}` } + { cacheKeyFn: ({ speciesId, colorId }) => `${speciesId},${colorId}` }, ); const buildPetTypesForColorLoader = (db, loaders) => @@ -918,7 +912,7 @@ const buildPetTypesForColorLoader = (db, loaders) => const qs = colorIds.map((_) => "?").join(","); const [rows] = await db.execute( `SELECT * FROM pet_types WHERE color_id IN (${qs})`, - colorIds + colorIds, ); const entities = rows.map(normalizeRow); @@ -927,12 +921,27 @@ const buildPetTypesForColorLoader = (db, loaders) => loaders.petTypeLoader.prime(petType.id, petType); loaders.petTypeBySpeciesAndColorLoader.prime( { speciesId: petType.speciesId, colorId: petType.colorId }, - petType + petType, ); } return colorIds.map((colorId) => - entities.filter((e) => e.colorId === colorId) + entities.filter((e) => e.colorId === colorId), + ); + }); + +const buildAltStyleLoader = (db) => + new DataLoader(async (altStyleIds) => { + const qs = altStyleIds.map((_) => "?").join(","); + const [rows] = await db.execute( + `SELECT * FROM alt_styles WHERE id IN (${qs})`, + altStyleIds, + ); + + const entities = rows.map(normalizeRow); + + return altStyleIds.map((altStyleId) => + entities.find((e) => e.id === altStyleId), ); }); @@ -941,13 +950,13 @@ const buildSwfAssetLoader = (db) => const qs = swfAssetIds.map((_) => "?").join(","); const [rows] = await db.execute( `SELECT * FROM swf_assets WHERE id IN (${qs})`, - swfAssetIds + swfAssetIds, ); const entities = rows.map(normalizeRow); return swfAssetIds.map((swfAssetId) => - entities.find((e) => e.id === swfAssetId) + entities.find((e) => e.id === swfAssetId), ); }); @@ -960,7 +969,7 @@ const buildSwfAssetCountLoader = (db) => (manifest IS NOT NULL AND manifest != "") AS is_converted FROM swf_assets GROUP BY type, is_converted; - ` + `, ); const entities = rows.map(normalizeRow); @@ -972,7 +981,7 @@ const buildSwfAssetCountLoader = (db) => } if (isConverted != null) { matchingEntities = matchingEntities.filter( - (e) => Boolean(e.isConverted) === isConverted + (e) => Boolean(e.isConverted) === isConverted, ); } @@ -982,7 +991,7 @@ const buildSwfAssetCountLoader = (db) => }, { cacheKeyFn: ({ type, isConverted }) => `${type},${isConverted}`, - } + }, ); const buildSwfAssetByRemoteIdLoader = (db) => @@ -996,16 +1005,16 @@ const buildSwfAssetByRemoteIdLoader = (db) => .flat(); const [rows] = await db.execute( `SELECT * FROM swf_assets WHERE ${qs}`, - values + values, ); const entities = rows.map(normalizeRow); return typeAndRemoteIdPairs.map(({ type, remoteId }) => - entities.find((e) => e.type === type && e.remoteId === remoteId) + entities.find((e) => e.type === type && e.remoteId === remoteId), ); }, - { cacheKeyFn: ({ type, remoteId }) => `${type},${remoteId}` } + { cacheKeyFn: ({ type, remoteId }) => `${type},${remoteId}` }, ); const buildItemSwfAssetLoader = (db, loaders) => @@ -1015,7 +1024,7 @@ const buildItemSwfAssetLoader = (db, loaders) => const values = []; for (const { itemId, bodyId } of itemAndBodyPairs) { conditions.push( - "(rel.parent_id = ? AND (sa.body_id = ? OR sa.body_id = 0))" + "(rel.parent_id = ? AND (sa.body_id = ? OR sa.body_id = 0))", ); values.push(itemId, bodyId); } @@ -1026,7 +1035,7 @@ const buildItemSwfAssetLoader = (db, loaders) => rel.parent_type = "Item" AND rel.swf_asset_id = sa.id WHERE ${conditions.join(" OR ")}`, - values + values, ); const entities = rows.map(normalizeRow); @@ -1038,11 +1047,11 @@ const buildItemSwfAssetLoader = (db, loaders) => return itemAndBodyPairs.map(({ itemId, bodyId }) => entities.filter( (e) => - e.parentId === itemId && (e.bodyId === bodyId || e.bodyId === "0") - ) + e.parentId === itemId && (e.bodyId === bodyId || e.bodyId === "0"), + ), ); }, - { cacheKeyFn: ({ itemId, bodyId }) => `${itemId},${bodyId}` } + { cacheKeyFn: ({ itemId, bodyId }) => `${itemId},${bodyId}` }, ); const buildPetSwfAssetLoader = (db, loaders) => @@ -1054,7 +1063,7 @@ const buildPetSwfAssetLoader = (db, loaders) => rel.parent_type = "PetState" AND rel.swf_asset_id = sa.id WHERE rel.parent_id IN (${qs})`, - petStateIds + petStateIds, ); const entities = rows.map(normalizeRow); @@ -1064,7 +1073,7 @@ const buildPetSwfAssetLoader = (db, loaders) => } return petStateIds.map((petStateId) => - entities.filter((e) => e.parentId === petStateId) + entities.filter((e) => e.parentId === petStateId), ); }); @@ -1073,7 +1082,7 @@ const buildNeopetsConnectionLoader = (db) => const qs = ids.map((_) => "?").join(", "); const [rows] = await db.execute( `SELECT * FROM neopets_connections WHERE id IN (${qs})`, - ids + ids, ); const entities = rows.map(normalizeRow); @@ -1086,7 +1095,7 @@ const buildOutfitLoader = (db) => const qs = outfitIds.map((_) => "?").join(","); const [rows] = await db.execute( `SELECT * FROM outfits WHERE id IN (${qs})`, - outfitIds + outfitIds, ); const entities = rows.map(normalizeRow); @@ -1099,13 +1108,13 @@ const buildItemOutfitRelationshipsLoader = (db) => const qs = outfitIds.map((_) => "?").join(","); const [rows] = await db.execute( `SELECT * FROM item_outfit_relationships WHERE outfit_id IN (${qs})`, - outfitIds + outfitIds, ); const entities = rows.map(normalizeRow); return outfitIds.map((outfitId) => - entities.filter((e) => e.outfitId === outfitId) + entities.filter((e) => e.outfitId === outfitId), ); }); @@ -1114,13 +1123,13 @@ const buildPetStateLoader = (db) => const qs = petStateIds.map((_) => "?").join(","); const [rows] = await db.execute( `SELECT * FROM pet_states WHERE id IN (${qs})`, - petStateIds + petStateIds, ); const entities = rows.map(normalizeRow); return petStateIds.map((petStateId) => - entities.find((e) => e.id === petStateId) + entities.find((e) => e.id === petStateId), ); }); @@ -1132,7 +1141,7 @@ const buildPetStatesForPetTypeLoader = (db, loaders) => WHERE pet_type_id IN (${qs}) ORDER BY (mood_id IS NULL) ASC, mood_id ASC, female DESC, unconverted DESC, glitched ASC, id DESC`, - petTypeIds + petTypeIds, ); const entities = rows.map(normalizeRow); @@ -1142,7 +1151,7 @@ const buildPetStatesForPetTypeLoader = (db, loaders) => } return petTypeIds.map((petTypeId) => - entities.filter((e) => e.petTypeId === petTypeId) + entities.filter((e) => e.petTypeId === petTypeId), ); }); @@ -1184,7 +1193,7 @@ const buildCanonicalPetStateForBodyLoader = (db, loaders) => preferredColorId || "", fallbackColorId, gender === "fem", - ] + ], ); const petState = normalizeRow(rows[0].pet_states); const petType = normalizeRow(rows[0].pet_types); @@ -1196,13 +1205,13 @@ const buildCanonicalPetStateForBodyLoader = (db, loaders) => loaders.petTypeLoader.prime(petType.id, petType); return petState; - }) + }), ); }, { cacheKeyFn: ({ bodyId, preferredColorId, fallbackColorId }) => `${bodyId}-${preferredColorId}-${fallbackColorId}`, - } + }, ); const buildPetStateByPetTypeAndAssetsLoader = (db, loaders) => @@ -1216,7 +1225,7 @@ const buildPetStateByPetTypeAndAssetsLoader = (db, loaders) => .flat(); const [rows] = await db.execute( `SELECT * FROM pet_states WHERE ${qs}`, - values + values, ); const entities = rows.map(normalizeRow); @@ -1227,13 +1236,13 @@ const buildPetStateByPetTypeAndAssetsLoader = (db, loaders) => return petTypeIdAndAssetIdsPairs.map(({ petTypeId, swfAssetIds }) => entities.find( - (e) => e.petTypeId === petTypeId && e.swfAssetIds === swfAssetIds - ) + (e) => e.petTypeId === petTypeId && e.swfAssetIds === swfAssetIds, + ), ); }, { cacheKeyFn: ({ petTypeId, swfAssetIds }) => `${petTypeId}-${swfAssetIds}`, - } + }, ); const buildUserLoader = (db) => @@ -1241,7 +1250,7 @@ const buildUserLoader = (db) => const qs = ids.map((_) => "?").join(","); const [rows] = await db.execute( `SELECT * FROM users WHERE id IN (${qs})`, - ids + ids, ); const entities = rows.map(normalizeRow); @@ -1250,7 +1259,7 @@ const buildUserLoader = (db) => return ids.map( (id) => entitiesById.get(String(id)) || - new Error(`could not find user with ID: ${id}`) + new Error(`could not find user with ID: ${id}`), ); }); @@ -1259,13 +1268,13 @@ const buildUserByNameLoader = (db) => const qs = names.map((_) => "?").join(","); const [rows] = await db.execute( `SELECT * FROM users WHERE name IN (${qs})`, - names + names, ); const entities = rows.map(normalizeRow); return names.map((name) => - entities.find((e) => e.name.toLowerCase() === name.toLowerCase()) + entities.find((e) => e.name.toLowerCase() === name.toLowerCase()), ); }); @@ -1281,7 +1290,7 @@ const buildUserByEmailLoader = (db) => `, nestTables: true, }, - emails + emails, ); const entities = rows.map((row) => ({ @@ -1302,12 +1311,12 @@ const buildUserClosetHangersLoader = (db) => item_translations.item_id = items.id AND locale = "en" WHERE user_id IN (${qs}) ORDER BY item_name`, - userIds + userIds, ); const entities = rows.map(normalizeRow); return userIds.map((userId) => - entities.filter((e) => e.userId === String(userId)) + entities.filter((e) => e.userId === String(userId)), ); }); @@ -1321,12 +1330,12 @@ const buildUserItemClosetHangersLoader = (db) => .flat(); const [rows] = await db.execute( `SELECT * FROM closet_hangers WHERE ${conditions};`, - params + params, ); const entities = rows.map(normalizeRow); return userIdAndItemIdPairs.map(({ userId, itemId }) => - entities.filter((e) => e.userId === userId && e.itemId === itemId) + entities.filter((e) => e.userId === userId && e.itemId === itemId), ); }); @@ -1337,7 +1346,7 @@ const buildUserClosetListsLoader = (db, loaders) => `SELECT * FROM closet_lists WHERE user_id IN (${qs}) ORDER BY name`, - userIds + userIds, ); const entities = rows.map(normalizeRow); @@ -1346,7 +1355,7 @@ const buildUserClosetListsLoader = (db, loaders) => } return userIds.map((userId) => - entities.filter((e) => e.userId === String(userId)) + entities.filter((e) => e.userId === String(userId)), ); }); @@ -1363,7 +1372,7 @@ const buildUserOutfitsLoader = (db, loaders) => WHERE user_id = ? ORDER BY name LIMIT ? OFFSET ?`, - [userId, actualLimit, actualOffset] + [userId, actualLimit, actualOffset], ); const entities = rows.map(normalizeRow); @@ -1382,7 +1391,7 @@ const buildUserNumTotalOutfitsLoader = (db) => `SELECT user_id, COUNT(*) as num_total_outfits FROM outfits WHERE user_id IN (${qs}) GROUP BY user_id`, - userIds + userIds, ); const entities = rows.map(normalizeRow); @@ -1435,7 +1444,7 @@ const buildUserLastTradeActivityLoader = (db) => ) GROUP BY closet_hangers.user_id `, - userIds + userIds, ); const entities = rows.map(normalizeRow); @@ -1451,7 +1460,7 @@ const buildZoneLoader = (db) => { const qs = ids.map((_) => "?").join(","); const [rows] = await db.execute( `SELECT * FROM zones WHERE id IN (${qs})`, - ids + ids, ); const entities = rows.map(normalizeRow); @@ -1460,7 +1469,7 @@ const buildZoneLoader = (db) => { return ids.map( (id) => entitiesById.get(String(id)) || - new Error(`could not find zone with ID: ${id}`) + new Error(`could not find zone with ID: ${id}`), ); }); @@ -1483,7 +1492,7 @@ const buildZoneTranslationLoader = (db) => const qs = zoneIds.map((_) => "?").join(","); const [rows] = await db.execute( `SELECT * FROM zone_translations WHERE zone_id IN (${qs}) AND locale = "en"`, - zoneIds + zoneIds, ); const entities = rows.map(normalizeRow); @@ -1492,7 +1501,7 @@ const buildZoneTranslationLoader = (db) => return zoneIds.map( (zoneId) => entitiesByZoneId.get(String(zoneId)) || - new Error(`could not find translation for zone ${zoneId}`) + new Error(`could not find translation for zone ${zoneId}`), ); }); @@ -1502,41 +1511,37 @@ function buildLoaders(db) { loaders.closetListLoader = buildClosetListLoader(db); loaders.closetHangersForListLoader = buildClosetHangersForListLoader(db); - loaders.closetHangersForDefaultListLoader = buildClosetHangersForDefaultListLoader( - db - ); + loaders.closetHangersForDefaultListLoader = + buildClosetHangersForDefaultListLoader(db); loaders.colorLoader = buildColorLoader(db); loaders.colorTranslationLoader = buildColorTranslationLoader(db); loaders.itemLoader = buildItemLoader(db); loaders.itemTranslationLoader = buildItemTranslationLoader(db); loaders.itemByNameLoader = buildItemByNameLoader(db, loaders); - loaders.itemSearchNumTotalItemsLoader = buildItemSearchNumTotalItemsLoader( - db - ); + loaders.itemSearchNumTotalItemsLoader = + buildItemSearchNumTotalItemsLoader(db); loaders.itemSearchItemsLoader = buildItemSearchItemsLoader(db, loaders); loaders.newestItemsLoader = buildNewestItemsLoader(db, loaders); - loaders.speciesThatNeedModelsForItemLoader = buildSpeciesThatNeedModelsForItemLoader( - db - ); + loaders.speciesThatNeedModelsForItemLoader = + buildSpeciesThatNeedModelsForItemLoader(db); loaders.itemsThatNeedModelsLoader = buildItemsThatNeedModelsLoader( db, - loaders + loaders, ); loaders.allSpeciesIdsForColorLoader = buildAllSpeciesIdsForColorLoader(db); - loaders.itemBodiesWithAppearanceDataLoader = buildItemBodiesWithAppearanceDataLoader( - db - ); + loaders.itemBodiesWithAppearanceDataLoader = + buildItemBodiesWithAppearanceDataLoader(db); loaders.itemAllOccupiedZonesLoader = buildItemAllOccupiedZonesLoader(db); - loaders.itemCompatibleBodiesAndTheirZonesLoader = buildItemCompatibleBodiesAndTheirZonesLoader( - db - ); + loaders.itemCompatibleBodiesAndTheirZonesLoader = + buildItemCompatibleBodiesAndTheirZonesLoader(db); loaders.itemTradesLoader = buildItemTradesLoader(db, loaders); loaders.petTypeLoader = buildPetTypeLoader(db, loaders); loaders.petTypeBySpeciesAndColorLoader = buildPetTypeBySpeciesAndColorLoader( db, - loaders + loaders, ); loaders.petTypesForColorLoader = buildPetTypesForColorLoader(db, loaders); + loaders.altStyleLoader = buildAltStyleLoader(db); loaders.swfAssetLoader = buildSwfAssetLoader(db); loaders.swfAssetCountLoader = buildSwfAssetCountLoader(db); loaders.swfAssetByRemoteIdLoader = buildSwfAssetByRemoteIdLoader(db); @@ -1544,22 +1549,19 @@ function buildLoaders(db) { loaders.petSwfAssetLoader = buildPetSwfAssetLoader(db, loaders); loaders.neopetsConnectionLoader = buildNeopetsConnectionLoader(db); loaders.outfitLoader = buildOutfitLoader(db); - loaders.itemOutfitRelationshipsLoader = buildItemOutfitRelationshipsLoader( - db - ); + loaders.itemOutfitRelationshipsLoader = + buildItemOutfitRelationshipsLoader(db); loaders.petStateLoader = buildPetStateLoader(db); loaders.petStatesForPetTypeLoader = buildPetStatesForPetTypeLoader( db, - loaders + loaders, ); loaders.canonicalPetStateForBodyLoader = buildCanonicalPetStateForBodyLoader( db, - loaders - ); - loaders.petStateByPetTypeAndAssetsLoader = buildPetStateByPetTypeAndAssetsLoader( - db, - loaders + loaders, ); + loaders.petStateByPetTypeAndAssetsLoader = + buildPetStateByPetTypeAndAssetsLoader(db, loaders); loaders.speciesLoader = buildSpeciesLoader(db); loaders.speciesTranslationLoader = buildSpeciesTranslationLoader(db); loaders.tradeMatchesLoader = buildTradeMatchesLoader(db); diff --git a/src/server/types/Item.js b/src/server/types/Item.js index 208e8cb..2c2017f 100644 --- a/src/server/types/Item.js +++ b/src/server/types/Item.js @@ -70,7 +70,11 @@ const typeDefs = gql` How this item appears on the given species/color combo. If it does not fit the pet, we'll return an empty ItemAppearance with no layers. """ - appearanceOn(speciesId: ID!, colorId: ID!): ItemAppearance! @cacheControl(maxAge: 1, staleWhileRevalidate: ${oneDay}) + appearanceOn( + speciesId: ID!, + colorId: ID!, + altStyleId: ID, + ): ItemAppearance! @cacheControl(maxAge: 1, staleWhileRevalidate: ${oneDay}) """ This is set manually by Support users, when the pet is only for e.g. @@ -176,6 +180,7 @@ const typeDefs = gql` input FitsPetSearchFilter { speciesId: ID! colorId: ID! + altStyleId: ID } enum ItemKindSearchFilter { @@ -527,9 +532,18 @@ const resolvers = { appearanceOn: async ( { id }, - { speciesId, colorId }, - { petTypeBySpeciesAndColorLoader }, + { speciesId, colorId, altStyleId }, + { altStyleLoader, petTypeBySpeciesAndColorLoader }, ) => { + // Load based on the alt style's body ID, if present. + if (altStyleId) { + const altStyle = await altStyleLoader.load(altStyleId); + if (altStyle != null) { + return { item: { id }, bodyId: altStyle.bodyId }; + } + } + + // If not, load based on the species/color combo's body ID. const petType = await petTypeBySpeciesAndColorLoader.load({ speciesId, colorId, @@ -763,6 +777,7 @@ const resolvers = { itemSearchNumTotalItemsLoader, itemSearchItemsLoader, petTypeBySpeciesAndColorLoader, + altStyleLoader, currentUserId, }, { cacheControl }, @@ -773,17 +788,28 @@ const resolvers = { let bodyId = null; if (fitsPet) { - const petType = await petTypeBySpeciesAndColorLoader.load({ - speciesId: fitsPet.speciesId, - colorId: fitsPet.colorId, - }); - if (!petType) { - throw new Error( - `pet type not found: speciesId=${fitsPet.speciesId}, ` + - `colorId: ${fitsPet.colorId}`, - ); + // Load based on the alt style's body ID, if present. + if (fitsPet.altStyleId != null) { + const altStyle = altStyleLoader.load(fitsPet.altStyleId); + if (altStyle) { + bodyId = altStyle.bodyId; + } + } + + // If not, load based on the species/color combo's body ID. + if (bodyId == null) { + const petType = await petTypeBySpeciesAndColorLoader.load({ + speciesId: fitsPet.speciesId, + colorId: fitsPet.colorId, + }); + if (!petType) { + throw new Error( + `pet type not found: speciesId=${fitsPet.speciesId}, ` + + `colorId: ${fitsPet.colorId}`, + ); + } + bodyId = petType.bodyId; } - bodyId = petType.bodyId; } const [items, numTotalItems] = await Promise.all([ itemSearchItemsLoader.load({ @@ -811,21 +837,32 @@ const resolvers = { itemSearchV2: async ( _, { query, fitsPet, itemKind, currentUserOwnsOrWants, zoneIds = [] }, - { petTypeBySpeciesAndColorLoader }, + { petTypeBySpeciesAndColorLoader, altStyleLoader }, ) => { let bodyId = null; if (fitsPet) { - const petType = await petTypeBySpeciesAndColorLoader.load({ - speciesId: fitsPet.speciesId, - colorId: fitsPet.colorId, - }); - if (!petType) { - throw new Error( - `pet type not found: speciesId=${fitsPet.speciesId}, ` + - `colorId: ${fitsPet.colorId}`, - ); + // Load based on the alt style's body ID, if present. + if (fitsPet.altStyleId != null) { + const altStyle = await altStyleLoader.load(fitsPet.altStyleId); + if (altStyle != null) { + bodyId = altStyle.bodyId; + } + } + + // If not, load based on the species/color combo's body ID. + if (bodyId == null) { + const petType = await petTypeBySpeciesAndColorLoader.load({ + speciesId: fitsPet.speciesId, + colorId: fitsPet.colorId, + }); + if (!petType) { + throw new Error( + `pet type not found: speciesId=${fitsPet.speciesId}, ` + + `colorId: ${fitsPet.colorId}`, + ); + } + bodyId = petType.bodyId; } - bodyId = petType.bodyId; } // These are the fields that define the search! We provide them to the