From f566012386161a6a2bd51c45363e98ad8b085862 Mon Sep 17 00:00:00 2001 From: Emi Matchu Date: Thu, 1 Feb 2024 04:59:09 -0800 Subject: [PATCH] Add support for altStyleId to item search and appearance lookups I'm not planning to port full Alt Style support over to the 2020 frontend, I really am winding that down, but adding a couple lil API parameters are *by far* the easiest way to get Alt Styles working in the main app because of how it calls the 2020 API. So here we are, adding new API calls but not the frontend changes! --- src/server/loaders.js | 308 ++++++++++++++++++++------------------- src/server/types/Item.js | 85 ++++++++--- 2 files changed, 216 insertions(+), 177 deletions(-) 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