diff --git a/scripts/cache-asset-manifests.js b/scripts/cache-asset-manifests.js index f3435a0..596549b 100644 --- a/scripts/cache-asset-manifests.js +++ b/scripts/cache-asset-manifests.js @@ -24,10 +24,10 @@ async function cacheAssetManifests(db) { : `manifest IS NULL OR manifest = ""`; const [rows] = await db.execute( - `SELECT id, url, manifest FROM swf_assets ` + + `SELECT id, url, manifest, manifest_url FROM swf_assets ` + `WHERE ${condition} AND id >= ? ` + `ORDER BY id`, - [argv.start || 0] + [argv.start || 0], ); const numRowsTotal = rows.length; @@ -37,7 +37,10 @@ async function cacheAssetManifests(db) { async function cacheAssetManifest(row) { try { - let manifest = await neopetsAssets.loadAssetManifest(row.url); + let manifest = await neopetsAssets.loadAssetManifest( + row.manifest_url, + row.url, + ); // After loading, write the new manifest. We make sure to write an empty // string if there was no manifest, to signify that it doesn't exist, so @@ -55,20 +58,18 @@ async function cacheAssetManifests(db) { console.info( `UPDATE swf_assets SET manifest = ${escapedManifest}, ` + `manifest_cached_at = CURRENT_TIMESTAMP() ` + - `WHERE id = ${row.id} LIMIT 1;` + `WHERE id = ${row.id} LIMIT 1;`, ); } else { - const [ - result, - ] = await db.execute( + const [result] = await db.execute( `UPDATE swf_assets SET manifest = ?, ` + `manifest_cached_at = CURRENT_TIMESTAMP() ` + `WHERE id = ? LIMIT 1;`, - [manifest, row.id] + [manifest, row.id], ); if (result.affectedRows !== 1) { throw new Error( - `Expected to affect 1 asset, but affected ${result.affectedRows}` + `Expected to affect 1 asset, but affected ${result.affectedRows}`, ); } } @@ -85,7 +86,7 @@ async function cacheAssetManifests(db) { console.error( `${percent}% ${numRowsDone}/${numRowsTotal} ` + `(Exists? ${Boolean(manifest)}. Updated? ${updated}. ` + - `Layer: ${row.id}, ${row.url}.)` + `Layer: ${row.id}, ${row.url}.)`, ); } } catch (e) { diff --git a/src/server/neopets-assets.js b/src/server/neopets-assets.js index dfe0464..39d96af 100644 --- a/src/server/neopets-assets.js +++ b/src/server/neopets-assets.js @@ -1,10 +1,13 @@ import fetch from "node-fetch"; -async function loadAssetManifest(swfUrl) { - const possibleManifestUrls = convertSwfUrlToPossibleManifestUrls(swfUrl); +async function loadAssetManifest(manifestUrl, swfUrl) { + const possibleManifestUrls = + manifestUrl != null + ? [manifestUrl] + : convertSwfUrlToPossibleManifestUrls(swfUrl); const responses = await Promise.all( - possibleManifestUrls.map((url) => fetch(url)) + possibleManifestUrls.map((url) => fetch(url)), ); // Print errors for any responses with unexpected statuses. We'll do this @@ -13,7 +16,7 @@ async function loadAssetManifest(swfUrl) { if (!res.ok && res.status !== 404) { console.error( `for asset manifest, images.neopets.com returned: ` + - `${res.status} ${res.statusText}. (${res.url})` + `${res.status} ${res.statusText}. (${res.url})`, ); } } @@ -34,7 +37,8 @@ async function loadAssetManifest(swfUrl) { }; } -const SWF_URL_PATTERN = /^https?:\/\/images\.neopets\.com\/cp\/(bio|items)\/swf\/(.+?)_([a-z0-9]+)\.swf$/; +const SWF_URL_PATTERN = + /^https?:\/\/images\.neopets\.com\/cp\/(bio|items)\/swf\/(.+?)_([a-z0-9]+)\.swf$/; function convertSwfUrlToPossibleManifestUrls(swfUrl) { const match = new URL(swfUrl, "https://images.neopets.com") diff --git a/src/server/types/AppearanceLayer.js b/src/server/types/AppearanceLayer.js index 2c94a29..59a4791 100644 --- a/src/server/types/AppearanceLayer.js +++ b/src/server/types/AppearanceLayer.js @@ -198,7 +198,7 @@ const typeDefs = gql` const ALL_KNOWN_GLITCH_VALUES = new Set( typeDefs.definitions .find((d) => d.name.value === "AppearanceLayerKnownGlitch") - .values.map((v) => v.name.value) + .values.map((v) => v.name.value), ); const resolvers = { @@ -222,11 +222,8 @@ const resolvers = { imageUrl: async ({ id }, { size = "SIZE_150" }, { swfAssetLoader, db }) => { const layer = await swfAssetLoader.load(id); - const { - format, - jsAssetUrl, - pngAssetUrl, - } = await loadAndCacheAssetDataFromManifest(db, layer); + const { format, jsAssetUrl, pngAssetUrl } = + await loadAndCacheAssetDataFromManifest(db, layer); // For the largest size, try to use the official Neopets PNG! if (size === "SIZE_600") { @@ -280,11 +277,8 @@ const resolvers = { imageUrlV2: async ({ id }, { idealSize }, { swfAssetLoader, db }) => { const layer = await swfAssetLoader.load(id); - const { - format, - jsAssetUrl, - pngAssetUrl, - } = await loadAndCacheAssetDataFromManifest(db, layer); + const { format, jsAssetUrl, pngAssetUrl } = + await loadAndCacheAssetDataFromManifest(db, layer); // If there's an official single-image PNG we can use, use it! This is // what the official /customise editor uses at time of writing. @@ -350,11 +344,8 @@ const resolvers = { return null; } - const { - format, - jsAssetUrl, - svgAssetUrl, - } = await loadAndCacheAssetDataFromManifest(db, layer); + const { format, jsAssetUrl, svgAssetUrl } = + await loadAndCacheAssetDataFromManifest(db, layer); // If there's an official single-image SVG we can use, use it! The NC // Mall player uses this at time of writing, and we generally prefer it @@ -377,7 +368,7 @@ const resolvers = { const { format, jsAssetUrl } = await loadAndCacheAssetDataFromManifest( db, - layer + layer, ); if (format === "lod" && jsAssetUrl) { @@ -395,7 +386,7 @@ const resolvers = { SELECT parent_id FROM parents_swf_assets WHERE swf_asset_id = ? AND parent_type = "Item" LIMIT 1; `, - [id] + [id], ); if (rows.length === 0) { @@ -419,7 +410,7 @@ const resolvers = { knownGlitches = knownGlitches.filter((knownGlitch) => { if (!ALL_KNOWN_GLITCH_VALUES.has(knownGlitch)) { console.warn( - `Layer ${id}: Skipping unexpected knownGlitches value: ${knownGlitch}` + `Layer ${id}: Skipping unexpected knownGlitches value: ${knownGlitch}`, ); return false; } @@ -434,17 +425,17 @@ const resolvers = { itemAppearanceLayersByRemoteId: async ( _, { remoteIds }, - { swfAssetByRemoteIdLoader } + { swfAssetByRemoteIdLoader }, ) => { const layers = await swfAssetByRemoteIdLoader.loadMany( - remoteIds.map((remoteId) => ({ type: "object", remoteId })) + remoteIds.map((remoteId) => ({ type: "object", remoteId })), ); return layers.filter((l) => l).map(({ id }) => ({ id })); }, numAppearanceLayersConverted: async ( _, { type }, - { swfAssetCountLoader } + { swfAssetCountLoader }, ) => { const count = await swfAssetCountLoader.load({ type: convertLayerTypeToSwfAssetType(type), @@ -461,7 +452,7 @@ const resolvers = { appearanceLayerByRemoteId: async ( _, { type, remoteId }, - { swfAssetByRemoteIdLoader } + { swfAssetByRemoteIdLoader }, ) => { const swfAsset = await swfAssetByRemoteIdLoader.load({ type: type === "PET_LAYER" ? "biology" : "object", @@ -512,7 +503,7 @@ async function loadAndCacheAssetDataFromManifest(db, layer) { } catch (e) { console.error( `Layer ${layer.id} has invalid manifest JSON: ` + - `${JSON.stringify(layer.manifest)}` + `${JSON.stringify(layer.manifest)}`, ); manifest = null; } @@ -526,7 +517,7 @@ async function loadAndCacheAssetDataFromManifest(db, layer) { if (manifest === null || manifestAgeInMs > MAX_MANIFEST_AGE_IN_MS) { console.info( `[Layer ${layer.id}] Updating manifest cache, ` + - `last updated ${manifestAgeInMs}ms ago: ${layer.manifestCachedAt}` + `last updated ${manifestAgeInMs}ms ago: ${layer.manifestCachedAt}`, ); manifest = await loadAndCacheAssetManifest(db, layer); } @@ -540,7 +531,7 @@ async function loadAndCacheAssetDataFromManifest(db, layer) { const format = asset.format; const assetUrls = asset.assetData.map( - (ad) => new URL(ad.path, "https://images.neopets.com") + (ad) => new URL(ad.path, "https://images.neopets.com"), ); // In the case of JS assets, we want the *last* one in the list, because @@ -554,7 +545,7 @@ async function loadAndCacheAssetDataFromManifest(db, layer) { // TODO: There's a file_ext field in the full manifest, but it's not // included in our cached copy. That would probably be more // reliable! - (url) => path.extname(url.pathname) === ".js" + (url) => path.extname(url.pathname) === ".js", ); const svgAssetUrl = assetUrls.find( @@ -563,7 +554,7 @@ async function loadAndCacheAssetDataFromManifest(db, layer) { // TODO: There's a file_ext field in the full manifest, but it's not // included in our cached copy. That would probably be more // reliable! - (url) => path.extname(url.pathname) === ".svg" + (url) => path.extname(url.pathname) === ".svg", ); // NOTE: Unlike movies, we *must* use the first PNG and not the last, because @@ -576,7 +567,7 @@ async function loadAndCacheAssetDataFromManifest(db, layer) { // TODO: There's a file_ext field in the full manifest, but it's not // included in our cached copy. That would probably be more // reliable! - (url) => path.extname(url.pathname) === ".png" + (url) => path.extname(url.pathname) === ".png", ); return { format, jsAssetUrl, svgAssetUrl, pngAssetUrl }; @@ -586,14 +577,14 @@ async function loadAndCacheAssetManifest(db, layer) { let manifest; try { manifest = await Promise.race([ - loadAssetManifest(layer.url), + loadAssetManifest(layer.manifestUrl, layer.url), new Promise((_, reject) => - setTimeout(() => reject(new Error(`manifest request timed out`)), 2000) + setTimeout(() => reject(new Error(`manifest request timed out`)), 2000), ), ]); } catch (e) { console.error( - new Error("Error loading asset manifest, caused by the error below") + new Error("Error loading asset manifest, caused by the error below"), ); console.error(e); return null; @@ -610,7 +601,7 @@ async function loadAndCacheAssetManifest(db, layer) { if (manifestJson.length > 16777215) { console.warn( `Skipping saving asset manifest for layer ${layer.id}, because its ` + - `length is ${manifestJson.length}, which exceeds the database limit.` + `length is ${manifestJson.length}, which exceeds the database limit.`, ); return manifest; } @@ -621,16 +612,16 @@ async function loadAndCacheAssetManifest(db, layer) { SET manifest = ?, manifest_cached_at = CURRENT_TIMESTAMP() WHERE id = ? LIMIT 1; `, - [manifestJson, layer.id] + [manifestJson, layer.id], ); if (result.affectedRows !== 1) { throw new Error( - `Expected to affect 1 asset, but affected ${result.affectedRows}` + `Expected to affect 1 asset, but affected ${result.affectedRows}`, ); } console.info( `Loaded and saved manifest for ${layer.type} ${layer.remoteId}. ` + - `DTI ID: ${layer.id}. Exists?: ${Boolean(manifest)}` + `DTI ID: ${layer.id}. Exists?: ${Boolean(manifest)}`, ); return manifest; diff --git a/src/server/types/Item.js b/src/server/types/Item.js index b2a65f5..208e8cb 100644 --- a/src/server/types/Item.js +++ b/src/server/types/Item.js @@ -339,12 +339,12 @@ const resolvers = { const translation = await itemTranslationLoader.load(id); if (!translation) { console.warn( - `Item.isPb: Translation not found for item ${id}. Returning false.` + `Item.isPb: Translation not found for item ${id}. Returning false.`, ); return false; } return translation.description.includes( - "This item is part of a deluxe paint brush set!" + "This item is part of a deluxe paint brush set!", ); }, createdAt: async ({ id }, _, { itemLoader }) => { @@ -358,7 +358,7 @@ const resolvers = { ncTradeValueText: async ( { id }, _, - { itemLoader, itemTranslationLoader } + { itemLoader, itemTranslationLoader }, ) => { // Skip this lookup for non-NC items, as a perf optimization. const item = await itemLoader.load(id); @@ -382,7 +382,7 @@ const resolvers = { ncTradeValue = await getOWLSTradeValue(itemName); } catch (e) { console.error( - `Error loading ncTradeValueText for item ${id}, skipping:` + `Error loading ncTradeValueText for item ${id}, skipping:`, ); console.error(e); ncTradeValue = null; @@ -395,7 +395,7 @@ const resolvers = { currentUserOwnsThis: async ( { id }, _, - { currentUserId, userItemClosetHangersLoader } + { currentUserId, userItemClosetHangersLoader }, ) => { if (currentUserId == null) return false; const closetHangers = await userItemClosetHangersLoader.load({ @@ -407,7 +407,7 @@ const resolvers = { currentUserWantsThis: async ( { id }, _, - { currentUserId, userItemClosetHangersLoader } + { currentUserId, userItemClosetHangersLoader }, ) => { if (currentUserId == null) return false; const closetHangers = await userItemClosetHangersLoader.load({ @@ -419,7 +419,7 @@ const resolvers = { currentUserHasInLists: async ( { id }, _, - { currentUserId, userItemClosetHangersLoader } + { currentUserId, userItemClosetHangersLoader }, ) => { if (currentUserId == null) return false; const closetHangers = await userItemClosetHangersLoader.load({ @@ -444,7 +444,7 @@ const resolvers = { numUsersOfferingThis: async ( { id }, _, - { itemTradesLoader, userLastTradeActivityLoader } + { itemTradesLoader, userLastTradeActivityLoader }, ) => { // First, get the trades themselves. TODO: Optimize into one query? const trades = await itemTradesLoader.load({ @@ -455,7 +455,7 @@ const resolvers = { // Then, get the last active dates for those users. const userIds = trades.map((t) => t.user.id); const lastActiveDates = await userLastTradeActivityLoader.loadMany( - userIds + userIds, ); // Finally, count how many of those dates were in the last 6 months. @@ -469,7 +469,7 @@ const resolvers = { numUsersSeekingThis: async ( { id }, _, - { itemTradesLoader, userLastTradeActivityLoader } + { itemTradesLoader, userLastTradeActivityLoader }, ) => { // First, get the trades themselves. TODO: Optimize into one query? const trades = await itemTradesLoader.load({ @@ -480,7 +480,7 @@ const resolvers = { // Then, get the last active dates for those users. const userIds = trades.map((t) => t.user.id); const lastActiveDates = await userLastTradeActivityLoader.loadMany( - userIds + userIds, ); // Finally, count how many of those dates were in the last 6 months. @@ -528,7 +528,7 @@ const resolvers = { appearanceOn: async ( { id }, { speciesId, colorId }, - { petTypeBySpeciesAndColorLoader } + { petTypeBySpeciesAndColorLoader }, ) => { const petType = await petTypeBySpeciesAndColorLoader.load({ speciesId, @@ -553,7 +553,7 @@ const resolvers = { speciesThatNeedModels: async ( { id }, { colorId = "8" }, // Blue - { speciesThatNeedModelsForItemLoader, allSpeciesIdsForColorLoader } + { speciesThatNeedModelsForItemLoader, allSpeciesIdsForColorLoader }, ) => { // NOTE: If we're running this in the context of `itemsThatNeedModels`, // this loader should already be primed, no extra query! @@ -567,25 +567,25 @@ const resolvers = { const modeledSpeciesIds = row.modeledSpeciesIds.split(","); const allSpeciesIdsForThisColor = await allSpeciesIdsForColorLoader.load( - colorId + colorId, ); let allModelableSpeciesIds = allSpeciesIdsForThisColor; if (!row.supportsVandagyre) { allModelableSpeciesIds = allModelableSpeciesIds.filter( - (s) => s !== "55" + (s) => s !== "55", ); } const unmodeledSpeciesIds = allModelableSpeciesIds.filter( - (id) => !modeledSpeciesIds.includes(id) + (id) => !modeledSpeciesIds.includes(id), ); return unmodeledSpeciesIds.map((id) => ({ id })); }, canonicalAppearance: async ( { id }, { preferredSpeciesId, preferredColorId }, - { db } + { db }, ) => { const [rows] = await db.query( ` @@ -607,7 +607,7 @@ const resolvers = { colors.standard DESC LIMIT 1 `, - [id, preferredSpeciesId || "", preferredColorId || ""] + [id, preferredSpeciesId || "", preferredColorId || ""], ); if (rows.length === 0) { return null; @@ -649,7 +649,7 @@ const resolvers = { parents_swf_assets.swf_asset_id = swf_assets.id WHERE items.id = ? `, - [id] + [id], ); const bodyIds = rows.map((row) => row.body_id); const bodies = bodyIds.map((id) => ({ id })); @@ -658,7 +658,7 @@ const resolvers = { compatibleBodiesAndTheirZones: async ( { id }, _, - { itemCompatibleBodiesAndTheirZonesLoader } + { itemCompatibleBodiesAndTheirZonesLoader }, ) => { const rows = await itemCompatibleBodiesAndTheirZonesLoader.load(id); return rows.map((row) => ({ @@ -682,7 +682,7 @@ const resolvers = { parents_swf_assets.swf_asset_id = swf_assets.id WHERE items.id = ? `, - [id] + [id], ); const bodyIds = rows.map((row) => String(row.body_id)); return bodyIds.map((bodyId) => ({ item: { id }, bodyId })); @@ -698,7 +698,12 @@ const resolvers = { bodyId, }); - let assets = allSwfAssets.filter((sa) => sa.url.endsWith(".swf")); + // NOTE: Previously, I used this to filter assets to just SWFs, to avoid + // dealing with the audio assets altogether cuz I never built support for + // them. But now, some assets have the url `https://images.neopets.com/temp` + // instead? So uhh. Disabling this for now. + // let assets = allSwfAssets.filter((sa) => sa.url.endsWith(".swf")); + let assets = allSwfAssets; // If there are no body-specific assets in this appearance, then remove // assets with the glitch flag REQUIRES_OTHER_BODY_SPECIFIC_ASSETS: the @@ -717,7 +722,7 @@ const resolvers = { restrictedZones: async ( { item: { id: itemId }, bodyId }, _, - { itemSwfAssetLoader, itemLoader } + { itemSwfAssetLoader, itemLoader }, ) => { // Check whether this appearance is empty. If so, restrict no zones. const allSwfAssets = await itemSwfAssetLoader.load({ itemId, bodyId }); @@ -760,7 +765,7 @@ const resolvers = { petTypeBySpeciesAndColorLoader, currentUserId, }, - { cacheControl } + { cacheControl }, ) => { if (currentUserOwnsOrWants != null) { cacheControl.setCacheHint({ scope: "PRIVATE" }); @@ -775,7 +780,7 @@ const resolvers = { if (!petType) { throw new Error( `pet type not found: speciesId=${fitsPet.speciesId}, ` + - `colorId: ${fitsPet.colorId}` + `colorId: ${fitsPet.colorId}`, ); } bodyId = petType.bodyId; @@ -806,7 +811,7 @@ const resolvers = { itemSearchV2: async ( _, { query, fitsPet, itemKind, currentUserOwnsOrWants, zoneIds = [] }, - { petTypeBySpeciesAndColorLoader } + { petTypeBySpeciesAndColorLoader }, ) => { let bodyId = null; if (fitsPet) { @@ -817,7 +822,7 @@ const resolvers = { if (!petType) { throw new Error( `pet type not found: speciesId=${fitsPet.speciesId}, ` + - `colorId: ${fitsPet.colorId}` + `colorId: ${fitsPet.colorId}`, ); } bodyId = petType.bodyId; @@ -851,7 +856,7 @@ const resolvers = { offset, limit, }, - { petTypeBySpeciesAndColorLoader, itemSearchLoader, currentUserId } + { petTypeBySpeciesAndColorLoader, itemSearchLoader, currentUserId }, ) => { const petType = await petTypeBySpeciesAndColorLoader.load({ speciesId, @@ -859,7 +864,7 @@ const resolvers = { }); if (!petType) { throw new Error( - `pet type not found: speciesId=${speciesId}, colorId: ${colorId}` + `pet type not found: speciesId=${speciesId}, colorId: ${colorId}`, ); } const { bodyId } = petType; @@ -883,10 +888,10 @@ const resolvers = { itemsThatNeedModels: async ( _, { colorId = "8" }, // Defaults to Blue - { itemsThatNeedModelsLoader } + { itemsThatNeedModelsLoader }, ) => { const speciesIdsByColorIdAndItemId = await itemsThatNeedModelsLoader.load( - "all" + "all", ); const speciesIdsByItemIds = speciesIdsByColorIdAndItemId.get(colorId); const itemIds = (speciesIdsByItemIds && speciesIdsByItemIds.keys()) || []; @@ -899,7 +904,7 @@ const resolvers = { { query, bodyId, itemKind, currentUserOwnsOrWants, zoneIds }, { offset, limit }, { currentUserId, itemSearchNumTotalItemsLoader }, - { cacheControl } + { cacheControl }, ) => { if (currentUserOwnsOrWants != null) { cacheControl.setCacheHint({ scope: "PRIVATE" }); @@ -920,7 +925,7 @@ const resolvers = { { query, bodyId, itemKind, currentUserOwnsOrWants, zoneIds }, { offset, limit }, { currentUserId, itemSearchItemsLoader }, - { cacheControl } + { cacheControl }, ) => { if (currentUserOwnsOrWants != null) { cacheControl.setCacheHint({ scope: "PRIVATE" }); @@ -944,7 +949,7 @@ const resolvers = { addToItemsCurrentUserOwns: async ( _, { itemId }, - { currentUserId, db, itemLoader } + { currentUserId, db, itemLoader }, ) => { if (currentUserId == null) { throw new Error(`must be logged in`); @@ -969,7 +974,7 @@ const resolvers = { WHERE item_id = ? AND user_id = ? AND owned = ? ) `, - [itemId, currentUserId, 1, now, now, true, itemId, currentUserId, true] + [itemId, currentUserId, 1, now, now, true, itemId, currentUserId, true], ); return { id: itemId }; @@ -977,7 +982,7 @@ const resolvers = { removeFromItemsCurrentUserOwns: async ( _, { itemId }, - { currentUserId, db, itemLoader } + { currentUserId, db, itemLoader }, ) => { if (currentUserId == null) { throw new Error(`must be logged in`); @@ -991,7 +996,7 @@ const resolvers = { await db.query( `DELETE FROM closet_hangers WHERE item_id = ? AND user_id = ? AND owned = ?;`, - [itemId, currentUserId, true] + [itemId, currentUserId, true], ); return { id: itemId }; @@ -999,7 +1004,7 @@ const resolvers = { addToItemsCurrentUserWants: async ( _, { itemId }, - { currentUserId, db, itemLoader } + { currentUserId, db, itemLoader }, ) => { if (currentUserId == null) { throw new Error(`must be logged in`); @@ -1034,7 +1039,7 @@ const resolvers = { itemId, currentUserId, false, - ] + ], ); return { id: itemId }; @@ -1042,7 +1047,7 @@ const resolvers = { removeFromItemsCurrentUserWants: async ( _, { itemId }, - { currentUserId, db, itemLoader } + { currentUserId, db, itemLoader }, ) => { if (currentUserId == null) { throw new Error(`must be logged in`); @@ -1056,7 +1061,7 @@ const resolvers = { await db.query( `DELETE FROM closet_hangers WHERE item_id = ? AND user_id = ? AND owned = ?;`, - [itemId, currentUserId, false] + [itemId, currentUserId, false], ); return { id: itemId }; @@ -1064,11 +1069,11 @@ const resolvers = { addItemToClosetList: async ( _, { listId, itemId, removeFromDefaultList }, - { currentUserId, db, closetListLoader } + { currentUserId, db, closetListLoader }, ) => { const closetListRef = await loadClosetListOrDefaultList( listId, - closetListLoader + closetListLoader, ); if (closetListRef == null) { throw new Error(`list ${listId} not found`); @@ -1094,7 +1099,7 @@ const resolvers = { AND owned = ? LIMIT 1; `, - [itemId, userId, ownsOrWantsItems === "OWNS"] + [itemId, userId, ownsOrWantsItems === "OWNS"], ); } @@ -1105,7 +1110,7 @@ const resolvers = { (item_id, user_id, owned, list_id, quantity, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?); `, - [itemId, userId, ownsOrWantsItems === "OWNS", listId, 1, now, now] + [itemId, userId, ownsOrWantsItems === "OWNS", listId, 1, now, now], ); await connection.commit(); @@ -1126,11 +1131,11 @@ const resolvers = { removeItemFromClosetList: async ( _, { listId, itemId, ensureInSomeList }, - { currentUserId, db, closetListLoader } + { currentUserId, db, closetListLoader }, ) => { const closetListRef = await loadClosetListOrDefaultList( listId, - closetListLoader + closetListLoader, ); if (closetListRef == null) { throw new Error(`list ${listId} not found`); @@ -1157,7 +1162,7 @@ const resolvers = { DELETE FROM closet_hangers WHERE ${listMatcherCondition} AND item_id = ? LIMIT 1; `, - [...listMatcherValues, itemId] + [...listMatcherValues, itemId], ); if (ensureInSomeList) { @@ -1168,7 +1173,7 @@ const resolvers = { SELECT COUNT(*) AS count FROM closet_hangers WHERE user_id = ? AND item_id = ? AND owned = ? `, - [userId, itemId, ownsOrWantsItems === "OWNS"] + [userId, itemId, ownsOrWantsItems === "OWNS"], ); if (rows[0].count === 0) { @@ -1179,7 +1184,7 @@ const resolvers = { (item_id, user_id, owned, list_id, quantity, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?); `, - [itemId, userId, ownsOrWantsItems === "OWNS", null, 1, now, now] + [itemId, userId, ownsOrWantsItems === "OWNS", null, 1, now, now], ); } }