const DataLoader = require("dataloader"); const { normalizeRow } = require("./util"); const loadAllColors = (db) => async () => { const [rows, _] = await db.execute(`SELECT * FROM colors WHERE prank = 0`); const entities = rows.map(normalizeRow); return entities; }; const buildColorTranslationLoader = (db) => new DataLoader(async (colorIds) => { const qs = colorIds.map((_) => "?").join(","); const [rows, _] = await db.execute( `SELECT * FROM color_translations WHERE color_id IN (${qs}) AND locale = "en"`, colorIds ); const entities = rows.map(normalizeRow); const entitiesByColorId = new Map(entities.map((e) => [e.colorId, e])); return colorIds.map( (colorId) => entitiesByColorId.get(String(colorId)) || new Error(`could not find translation for species ${colorId}`) ); }); const loadAllSpecies = (db) => async () => { const [rows, _] = await db.execute(`SELECT * FROM species`); const entities = rows.map(normalizeRow); return entities; }; const buildSpeciesTranslationLoader = (db) => new DataLoader(async (speciesIds) => { const qs = speciesIds.map((_) => "?").join(","); const [rows, _] = await db.execute( `SELECT * FROM species_translations WHERE species_id IN (${qs}) AND locale = "en"`, speciesIds ); const entities = rows.map(normalizeRow); const entitiesBySpeciesId = new Map(entities.map((e) => [e.speciesId, e])); return speciesIds.map( (speciesId) => entitiesBySpeciesId.get(String(speciesId)) || new Error(`could not find translation for species ${speciesId}`) ); }); const loadAllPetTypes = (db) => async () => { const [rows, _] = await db.execute( `SELECT species_id, color_id FROM pet_types` ); const entities = rows.map(normalizeRow); return entities; }; const buildItemsLoader = (db) => new DataLoader(async (ids) => { const qs = ids.map((_) => "?").join(","); const [rows, _] = await db.execute( `SELECT * FROM items WHERE id IN (${qs})`, ids ); const entities = rows.map(normalizeRow); const entitiesById = new Map(entities.map((e) => [e.id, e])); return ids.map( (id) => entitiesById.get(id) || new Error(`could not find item with ID: ${id}`) ); }); const buildItemTranslationLoader = (db) => new DataLoader(async (itemIds) => { const qs = itemIds.map((_) => "?").join(","); const [rows, _] = await db.execute( `SELECT * FROM item_translations WHERE item_id IN (${qs}) AND locale = "en"`, itemIds ); const entities = rows.map(normalizeRow); const entitiesByItemId = new Map(entities.map((e) => [e.itemId, e])); return itemIds.map( (itemId) => entitiesByItemId.get(itemId) || new Error(`could not find translation for item ${itemId}`) ); }); const buildItemSearchLoader = (db) => new DataLoader(async (queries) => { // This isn't actually optimized as a batch query, we're just using a // DataLoader API consistency with our other loaders! const queryPromises = queries.map(async (query) => { const queryForMysql = "%" + query.replace(/_%/g, "\\$0") + "%"; const [rows, _] = await db.execute( `SELECT items.*, t.name FROM items INNER JOIN item_translations t ON t.item_id = items.id WHERE t.name LIKE ? AND t.locale="en" ORDER BY t.name LIMIT 30`, [queryForMysql] ); const entities = rows.map(normalizeRow); return entities; }); const responses = await Promise.all(queryPromises); return responses; }); const buildItemSearchToFitLoader = (db) => new DataLoader(async (queryAndBodyIdPairs) => { // This isn't actually optimized as a batch query, we're just using a // DataLoader API consistency with our other loaders! const queryPromises = queryAndBodyIdPairs.map( async ({ query, bodyId, offset, limit }) => { const actualOffset = offset || 0; const actualLimit = Math.min(limit || 30, 30); const queryForMysql = "%" + query.replace(/_%/g, "\\$0") + "%"; const [rows, _] = await db.execute( `SELECT DISTINCT items.*, t.name FROM items INNER JOIN item_translations t ON t.item_id = items.id INNER JOIN parents_swf_assets rel ON rel.parent_type = "Item" AND rel.parent_id = items.id INNER JOIN swf_assets ON rel.swf_asset_id = swf_assets.id WHERE t.name LIKE ? AND t.locale="en" AND (swf_assets.body_id = ? OR swf_assets.body_id = 0) ORDER BY t.name LIMIT ? OFFSET ?`, [queryForMysql, bodyId, actualLimit, actualOffset] ); const entities = rows.map(normalizeRow); return entities; } ); const responses = await Promise.all(queryPromises); return responses; }); const buildPetTypeLoader = (db) => new DataLoader(async (petTypeIds) => { const qs = petTypeIds.map((_) => "?").join(","); const [rows, _] = await db.execute( `SELECT * FROM pet_types WHERE id IN (${qs})`, petTypeIds ); const entities = rows.map(normalizeRow); return petTypeIds.map((petTypeId) => entities.find((e) => e.id === petTypeId) ); }); const buildPetTypeBySpeciesAndColorLoader = (db, loaders) => new DataLoader(async (speciesAndColorPairs) => { const conditions = []; const values = []; for (const { speciesId, colorId } of speciesAndColorPairs) { conditions.push("(species_id = ? AND color_id = ?)"); values.push(speciesId, colorId); } const [rows, _] = await db.execute( `SELECT * FROM pet_types WHERE ${conditions.join(" OR ")}`, values ); const entities = rows.map(normalizeRow); const entitiesBySpeciesAndColorPair = new Map( entities.map((e) => [`${e.speciesId},${e.colorId}`, e]) ); for (const petType of entities) { loaders.petTypeLoader.prime(petType.id, petType); } return speciesAndColorPairs.map(({ speciesId, colorId }) => entitiesBySpeciesAndColorPair.get(`${speciesId},${colorId}`) ); }); const buildItemSwfAssetLoader = (db) => new DataLoader(async (itemAndBodyPairs) => { const conditions = []; const values = []; for (const { itemId, bodyId } of itemAndBodyPairs) { conditions.push( "(rel.parent_id = ? AND (sa.body_id = ? OR sa.body_id = 0))" ); values.push(itemId, bodyId); } const [rows, _] = await db.execute( `SELECT sa.*, rel.parent_id FROM swf_assets sa INNER JOIN parents_swf_assets rel ON rel.parent_type = "Item" AND rel.swf_asset_id = sa.id WHERE ${conditions.join(" OR ")}`, values ); const entities = rows.map(normalizeRow); return itemAndBodyPairs.map(({ itemId, bodyId }) => entities.filter( (e) => e.parentId === itemId && (e.bodyId === bodyId || e.bodyId === "0") ) ); }); const buildPetSwfAssetLoader = (db) => new DataLoader(async (petStateIds) => { const qs = petStateIds.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 = "PetState" AND rel.swf_asset_id = sa.id WHERE rel.parent_id IN (${qs})`, petStateIds ); const entities = rows.map(normalizeRow); return petStateIds.map((petStateId) => entities.filter((e) => e.parentId === petStateId) ); }); const buildOutfitLoader = (db) => new DataLoader(async (outfitIds) => { const qs = outfitIds.map((_) => "?").join(","); const [rows, _] = await db.execute( `SELECT * FROM outfits WHERE id IN (${qs})`, outfitIds ); const entities = rows.map(normalizeRow); return outfitIds.map((outfitId) => entities.find((e) => e.id === outfitId)); }); const buildItemOutfitRelationshipsLoader = (db) => new DataLoader(async (outfitIds) => { const qs = outfitIds.map((_) => "?").join(","); const [rows, _] = await db.execute( `SELECT * FROM item_outfit_relationships WHERE outfit_id IN (${qs})`, outfitIds ); const entities = rows.map(normalizeRow); return outfitIds.map((outfitId) => entities.filter((e) => e.outfitId === outfitId) ); }); const buildPetStateLoader = (db) => new DataLoader(async (petStateIds) => { const qs = petStateIds.map((_) => "?").join(","); const [rows, _] = await db.execute( `SELECT * FROM pet_states WHERE id IN (${qs})`, petStateIds ); const entities = rows.map(normalizeRow); return petStateIds.map((petStateId) => entities.find((e) => e.id === petStateId) ); }); const buildPetStatesForPetTypeLoader = (db, loaders) => new DataLoader(async (petTypeIds) => { const qs = petTypeIds.map((_) => "?").join(","); const [rows, _] = await db.execute( `SELECT * FROM pet_states WHERE pet_type_id IN (${qs}) AND glitched = 0 ORDER BY (mood_id = 1) DESC`, petTypeIds ); const entities = rows.map(normalizeRow); for (const petState of entities) { loaders.petStateLoader.prime(petState.id, petState); } return petTypeIds.map((petTypeId) => entities.filter((e) => e.petTypeId === petTypeId) ); }); const buildZoneLoader = (db) => new DataLoader(async (ids) => { const qs = ids.map((_) => "?").join(","); const [rows, _] = await db.execute( `SELECT * FROM zones WHERE id IN (${qs})`, ids ); const entities = rows.map(normalizeRow); const entitiesById = new Map(entities.map((e) => [e.id, e])); return ids.map( (id) => entitiesById.get(String(id)) || new Error(`could not find zone with ID: ${id}`) ); }); const buildZoneTranslationLoader = (db) => new DataLoader(async (zoneIds) => { const qs = zoneIds.map((_) => "?").join(","); const [rows, _] = await db.execute( `SELECT * FROM zone_translations WHERE zone_id IN (${qs}) AND locale = "en"`, zoneIds ); const entities = rows.map(normalizeRow); const entitiesByZoneId = new Map(entities.map((e) => [e.zoneId, e])); return zoneIds.map( (zoneId) => entitiesByZoneId.get(String(zoneId)) || new Error(`could not find translation for zone ${zoneId}`) ); }); function buildLoaders(db) { const loaders = {}; loaders.loadAllColors = loadAllColors(db); loaders.loadAllSpecies = loadAllSpecies(db); loaders.loadAllPetTypes = loadAllPetTypes(db); loaders.colorTranslationLoader = buildColorTranslationLoader(db); loaders.itemLoader = buildItemsLoader(db); loaders.itemTranslationLoader = buildItemTranslationLoader(db); loaders.itemSearchLoader = buildItemSearchLoader(db); loaders.itemSearchToFitLoader = buildItemSearchToFitLoader(db); loaders.petTypeLoader = buildPetTypeLoader(db); loaders.petTypeBySpeciesAndColorLoader = buildPetTypeBySpeciesAndColorLoader( db, loaders ); loaders.itemSwfAssetLoader = buildItemSwfAssetLoader(db); loaders.petSwfAssetLoader = buildPetSwfAssetLoader(db); loaders.outfitLoader = buildOutfitLoader(db); loaders.itemOutfitRelationshipsLoader = buildItemOutfitRelationshipsLoader( db ); loaders.petStateLoader = buildPetStateLoader(db); loaders.petStatesForPetTypeLoader = buildPetStatesForPetTypeLoader( db, loaders ); loaders.speciesTranslationLoader = buildSpeciesTranslationLoader(db); loaders.zoneLoader = buildZoneLoader(db); loaders.zoneTranslationLoader = buildZoneTranslationLoader(db); return loaders; } module.exports = buildLoaders;