Fix handling of assets with https://images.neopets.com/temp
SWF URLs
Weird! Well! This caused two issues. One is that we used to filter down to only assets whose urls end in `.swf`, because I never added support for the sound ones :p The other is that we were using the SWF URL to infer the potential manifest URLs, which we can't do when the SWF URL is a weird placeholder! Thankfully, just yesterday (wow!) we happened to have Classic DTI keep track of `manifest_url` during the modeling process, and we backfilled everything. So the most recent `temp` assets have their manifest! But slightly older ones (not that old tho!!) didn't, so I manually inferred the manifest URL from the asset's `remote_id` field instead (which worked cuz there was no hash component to the manifest URL, unlike some manifests). The item "Flowing Black Cloak" (86668) on the Zafara is the one we were looking at and testing with!
This commit is contained in:
parent
c3a9c24d22
commit
4cd5cf5800
4 changed files with 102 additions and 101 deletions
|
@ -24,10 +24,10 @@ async function cacheAssetManifests(db) {
|
||||||
: `manifest IS NULL OR manifest = ""`;
|
: `manifest IS NULL OR manifest = ""`;
|
||||||
|
|
||||||
const [rows] = await db.execute(
|
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 >= ? ` +
|
`WHERE ${condition} AND id >= ? ` +
|
||||||
`ORDER BY id`,
|
`ORDER BY id`,
|
||||||
[argv.start || 0]
|
[argv.start || 0],
|
||||||
);
|
);
|
||||||
|
|
||||||
const numRowsTotal = rows.length;
|
const numRowsTotal = rows.length;
|
||||||
|
@ -37,7 +37,10 @@ async function cacheAssetManifests(db) {
|
||||||
|
|
||||||
async function cacheAssetManifest(row) {
|
async function cacheAssetManifest(row) {
|
||||||
try {
|
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
|
// 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
|
// string if there was no manifest, to signify that it doesn't exist, so
|
||||||
|
@ -55,20 +58,18 @@ async function cacheAssetManifests(db) {
|
||||||
console.info(
|
console.info(
|
||||||
`UPDATE swf_assets SET manifest = ${escapedManifest}, ` +
|
`UPDATE swf_assets SET manifest = ${escapedManifest}, ` +
|
||||||
`manifest_cached_at = CURRENT_TIMESTAMP() ` +
|
`manifest_cached_at = CURRENT_TIMESTAMP() ` +
|
||||||
`WHERE id = ${row.id} LIMIT 1;`
|
`WHERE id = ${row.id} LIMIT 1;`,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const [
|
const [result] = await db.execute(
|
||||||
result,
|
|
||||||
] = await db.execute(
|
|
||||||
`UPDATE swf_assets SET manifest = ?, ` +
|
`UPDATE swf_assets SET manifest = ?, ` +
|
||||||
`manifest_cached_at = CURRENT_TIMESTAMP() ` +
|
`manifest_cached_at = CURRENT_TIMESTAMP() ` +
|
||||||
`WHERE id = ? LIMIT 1;`,
|
`WHERE id = ? LIMIT 1;`,
|
||||||
[manifest, row.id]
|
[manifest, row.id],
|
||||||
);
|
);
|
||||||
if (result.affectedRows !== 1) {
|
if (result.affectedRows !== 1) {
|
||||||
throw new Error(
|
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(
|
console.error(
|
||||||
`${percent}% ${numRowsDone}/${numRowsTotal} ` +
|
`${percent}% ${numRowsDone}/${numRowsTotal} ` +
|
||||||
`(Exists? ${Boolean(manifest)}. Updated? ${updated}. ` +
|
`(Exists? ${Boolean(manifest)}. Updated? ${updated}. ` +
|
||||||
`Layer: ${row.id}, ${row.url}.)`
|
`Layer: ${row.id}, ${row.url}.)`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import fetch from "node-fetch";
|
import fetch from "node-fetch";
|
||||||
|
|
||||||
async function loadAssetManifest(swfUrl) {
|
async function loadAssetManifest(manifestUrl, swfUrl) {
|
||||||
const possibleManifestUrls = convertSwfUrlToPossibleManifestUrls(swfUrl);
|
const possibleManifestUrls =
|
||||||
|
manifestUrl != null
|
||||||
|
? [manifestUrl]
|
||||||
|
: convertSwfUrlToPossibleManifestUrls(swfUrl);
|
||||||
|
|
||||||
const responses = await Promise.all(
|
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
|
// 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) {
|
if (!res.ok && res.status !== 404) {
|
||||||
console.error(
|
console.error(
|
||||||
`for asset manifest, images.neopets.com returned: ` +
|
`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) {
|
function convertSwfUrlToPossibleManifestUrls(swfUrl) {
|
||||||
const match = new URL(swfUrl, "https://images.neopets.com")
|
const match = new URL(swfUrl, "https://images.neopets.com")
|
||||||
|
|
|
@ -198,7 +198,7 @@ const typeDefs = gql`
|
||||||
const ALL_KNOWN_GLITCH_VALUES = new Set(
|
const ALL_KNOWN_GLITCH_VALUES = new Set(
|
||||||
typeDefs.definitions
|
typeDefs.definitions
|
||||||
.find((d) => d.name.value === "AppearanceLayerKnownGlitch")
|
.find((d) => d.name.value === "AppearanceLayerKnownGlitch")
|
||||||
.values.map((v) => v.name.value)
|
.values.map((v) => v.name.value),
|
||||||
);
|
);
|
||||||
|
|
||||||
const resolvers = {
|
const resolvers = {
|
||||||
|
@ -222,11 +222,8 @@ const resolvers = {
|
||||||
imageUrl: async ({ id }, { size = "SIZE_150" }, { swfAssetLoader, db }) => {
|
imageUrl: async ({ id }, { size = "SIZE_150" }, { swfAssetLoader, db }) => {
|
||||||
const layer = await swfAssetLoader.load(id);
|
const layer = await swfAssetLoader.load(id);
|
||||||
|
|
||||||
const {
|
const { format, jsAssetUrl, pngAssetUrl } =
|
||||||
format,
|
await loadAndCacheAssetDataFromManifest(db, layer);
|
||||||
jsAssetUrl,
|
|
||||||
pngAssetUrl,
|
|
||||||
} = await loadAndCacheAssetDataFromManifest(db, layer);
|
|
||||||
|
|
||||||
// For the largest size, try to use the official Neopets PNG!
|
// For the largest size, try to use the official Neopets PNG!
|
||||||
if (size === "SIZE_600") {
|
if (size === "SIZE_600") {
|
||||||
|
@ -280,11 +277,8 @@ const resolvers = {
|
||||||
imageUrlV2: async ({ id }, { idealSize }, { swfAssetLoader, db }) => {
|
imageUrlV2: async ({ id }, { idealSize }, { swfAssetLoader, db }) => {
|
||||||
const layer = await swfAssetLoader.load(id);
|
const layer = await swfAssetLoader.load(id);
|
||||||
|
|
||||||
const {
|
const { format, jsAssetUrl, pngAssetUrl } =
|
||||||
format,
|
await loadAndCacheAssetDataFromManifest(db, layer);
|
||||||
jsAssetUrl,
|
|
||||||
pngAssetUrl,
|
|
||||||
} = await loadAndCacheAssetDataFromManifest(db, layer);
|
|
||||||
|
|
||||||
// If there's an official single-image PNG we can use, use it! This is
|
// 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.
|
// what the official /customise editor uses at time of writing.
|
||||||
|
@ -350,11 +344,8 @@ const resolvers = {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { format, jsAssetUrl, svgAssetUrl } =
|
||||||
format,
|
await loadAndCacheAssetDataFromManifest(db, layer);
|
||||||
jsAssetUrl,
|
|
||||||
svgAssetUrl,
|
|
||||||
} = await loadAndCacheAssetDataFromManifest(db, layer);
|
|
||||||
|
|
||||||
// If there's an official single-image SVG we can use, use it! The NC
|
// 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
|
// Mall player uses this at time of writing, and we generally prefer it
|
||||||
|
@ -377,7 +368,7 @@ const resolvers = {
|
||||||
|
|
||||||
const { format, jsAssetUrl } = await loadAndCacheAssetDataFromManifest(
|
const { format, jsAssetUrl } = await loadAndCacheAssetDataFromManifest(
|
||||||
db,
|
db,
|
||||||
layer
|
layer,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (format === "lod" && jsAssetUrl) {
|
if (format === "lod" && jsAssetUrl) {
|
||||||
|
@ -395,7 +386,7 @@ const resolvers = {
|
||||||
SELECT parent_id FROM parents_swf_assets
|
SELECT parent_id FROM parents_swf_assets
|
||||||
WHERE swf_asset_id = ? AND parent_type = "Item" LIMIT 1;
|
WHERE swf_asset_id = ? AND parent_type = "Item" LIMIT 1;
|
||||||
`,
|
`,
|
||||||
[id]
|
[id],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (rows.length === 0) {
|
if (rows.length === 0) {
|
||||||
|
@ -419,7 +410,7 @@ const resolvers = {
|
||||||
knownGlitches = knownGlitches.filter((knownGlitch) => {
|
knownGlitches = knownGlitches.filter((knownGlitch) => {
|
||||||
if (!ALL_KNOWN_GLITCH_VALUES.has(knownGlitch)) {
|
if (!ALL_KNOWN_GLITCH_VALUES.has(knownGlitch)) {
|
||||||
console.warn(
|
console.warn(
|
||||||
`Layer ${id}: Skipping unexpected knownGlitches value: ${knownGlitch}`
|
`Layer ${id}: Skipping unexpected knownGlitches value: ${knownGlitch}`,
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -434,17 +425,17 @@ const resolvers = {
|
||||||
itemAppearanceLayersByRemoteId: async (
|
itemAppearanceLayersByRemoteId: async (
|
||||||
_,
|
_,
|
||||||
{ remoteIds },
|
{ remoteIds },
|
||||||
{ swfAssetByRemoteIdLoader }
|
{ swfAssetByRemoteIdLoader },
|
||||||
) => {
|
) => {
|
||||||
const layers = await swfAssetByRemoteIdLoader.loadMany(
|
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 }));
|
return layers.filter((l) => l).map(({ id }) => ({ id }));
|
||||||
},
|
},
|
||||||
numAppearanceLayersConverted: async (
|
numAppearanceLayersConverted: async (
|
||||||
_,
|
_,
|
||||||
{ type },
|
{ type },
|
||||||
{ swfAssetCountLoader }
|
{ swfAssetCountLoader },
|
||||||
) => {
|
) => {
|
||||||
const count = await swfAssetCountLoader.load({
|
const count = await swfAssetCountLoader.load({
|
||||||
type: convertLayerTypeToSwfAssetType(type),
|
type: convertLayerTypeToSwfAssetType(type),
|
||||||
|
@ -461,7 +452,7 @@ const resolvers = {
|
||||||
appearanceLayerByRemoteId: async (
|
appearanceLayerByRemoteId: async (
|
||||||
_,
|
_,
|
||||||
{ type, remoteId },
|
{ type, remoteId },
|
||||||
{ swfAssetByRemoteIdLoader }
|
{ swfAssetByRemoteIdLoader },
|
||||||
) => {
|
) => {
|
||||||
const swfAsset = await swfAssetByRemoteIdLoader.load({
|
const swfAsset = await swfAssetByRemoteIdLoader.load({
|
||||||
type: type === "PET_LAYER" ? "biology" : "object",
|
type: type === "PET_LAYER" ? "biology" : "object",
|
||||||
|
@ -512,7 +503,7 @@ async function loadAndCacheAssetDataFromManifest(db, layer) {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(
|
console.error(
|
||||||
`Layer ${layer.id} has invalid manifest JSON: ` +
|
`Layer ${layer.id} has invalid manifest JSON: ` +
|
||||||
`${JSON.stringify(layer.manifest)}`
|
`${JSON.stringify(layer.manifest)}`,
|
||||||
);
|
);
|
||||||
manifest = null;
|
manifest = null;
|
||||||
}
|
}
|
||||||
|
@ -526,7 +517,7 @@ async function loadAndCacheAssetDataFromManifest(db, layer) {
|
||||||
if (manifest === null || manifestAgeInMs > MAX_MANIFEST_AGE_IN_MS) {
|
if (manifest === null || manifestAgeInMs > MAX_MANIFEST_AGE_IN_MS) {
|
||||||
console.info(
|
console.info(
|
||||||
`[Layer ${layer.id}] Updating manifest cache, ` +
|
`[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);
|
manifest = await loadAndCacheAssetManifest(db, layer);
|
||||||
}
|
}
|
||||||
|
@ -540,7 +531,7 @@ async function loadAndCacheAssetDataFromManifest(db, layer) {
|
||||||
|
|
||||||
const format = asset.format;
|
const format = asset.format;
|
||||||
const assetUrls = asset.assetData.map(
|
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
|
// 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
|
// 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
|
// included in our cached copy. That would probably be more
|
||||||
// reliable!
|
// reliable!
|
||||||
(url) => path.extname(url.pathname) === ".js"
|
(url) => path.extname(url.pathname) === ".js",
|
||||||
);
|
);
|
||||||
|
|
||||||
const svgAssetUrl = assetUrls.find(
|
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
|
// 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
|
// included in our cached copy. That would probably be more
|
||||||
// reliable!
|
// 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
|
// 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
|
// 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
|
// included in our cached copy. That would probably be more
|
||||||
// reliable!
|
// reliable!
|
||||||
(url) => path.extname(url.pathname) === ".png"
|
(url) => path.extname(url.pathname) === ".png",
|
||||||
);
|
);
|
||||||
|
|
||||||
return { format, jsAssetUrl, svgAssetUrl, pngAssetUrl };
|
return { format, jsAssetUrl, svgAssetUrl, pngAssetUrl };
|
||||||
|
@ -586,14 +577,14 @@ async function loadAndCacheAssetManifest(db, layer) {
|
||||||
let manifest;
|
let manifest;
|
||||||
try {
|
try {
|
||||||
manifest = await Promise.race([
|
manifest = await Promise.race([
|
||||||
loadAssetManifest(layer.url),
|
loadAssetManifest(layer.manifestUrl, layer.url),
|
||||||
new Promise((_, reject) =>
|
new Promise((_, reject) =>
|
||||||
setTimeout(() => reject(new Error(`manifest request timed out`)), 2000)
|
setTimeout(() => reject(new Error(`manifest request timed out`)), 2000),
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(
|
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);
|
console.error(e);
|
||||||
return null;
|
return null;
|
||||||
|
@ -610,7 +601,7 @@ async function loadAndCacheAssetManifest(db, layer) {
|
||||||
if (manifestJson.length > 16777215) {
|
if (manifestJson.length > 16777215) {
|
||||||
console.warn(
|
console.warn(
|
||||||
`Skipping saving asset manifest for layer ${layer.id}, because its ` +
|
`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;
|
return manifest;
|
||||||
}
|
}
|
||||||
|
@ -621,16 +612,16 @@ async function loadAndCacheAssetManifest(db, layer) {
|
||||||
SET manifest = ?, manifest_cached_at = CURRENT_TIMESTAMP()
|
SET manifest = ?, manifest_cached_at = CURRENT_TIMESTAMP()
|
||||||
WHERE id = ? LIMIT 1;
|
WHERE id = ? LIMIT 1;
|
||||||
`,
|
`,
|
||||||
[manifestJson, layer.id]
|
[manifestJson, layer.id],
|
||||||
);
|
);
|
||||||
if (result.affectedRows !== 1) {
|
if (result.affectedRows !== 1) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Expected to affect 1 asset, but affected ${result.affectedRows}`
|
`Expected to affect 1 asset, but affected ${result.affectedRows}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
console.info(
|
console.info(
|
||||||
`Loaded and saved manifest for ${layer.type} ${layer.remoteId}. ` +
|
`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;
|
return manifest;
|
||||||
|
|
|
@ -339,12 +339,12 @@ const resolvers = {
|
||||||
const translation = await itemTranslationLoader.load(id);
|
const translation = await itemTranslationLoader.load(id);
|
||||||
if (!translation) {
|
if (!translation) {
|
||||||
console.warn(
|
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 false;
|
||||||
}
|
}
|
||||||
return translation.description.includes(
|
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 }) => {
|
createdAt: async ({ id }, _, { itemLoader }) => {
|
||||||
|
@ -358,7 +358,7 @@ const resolvers = {
|
||||||
ncTradeValueText: async (
|
ncTradeValueText: async (
|
||||||
{ id },
|
{ id },
|
||||||
_,
|
_,
|
||||||
{ itemLoader, itemTranslationLoader }
|
{ itemLoader, itemTranslationLoader },
|
||||||
) => {
|
) => {
|
||||||
// Skip this lookup for non-NC items, as a perf optimization.
|
// Skip this lookup for non-NC items, as a perf optimization.
|
||||||
const item = await itemLoader.load(id);
|
const item = await itemLoader.load(id);
|
||||||
|
@ -382,7 +382,7 @@ const resolvers = {
|
||||||
ncTradeValue = await getOWLSTradeValue(itemName);
|
ncTradeValue = await getOWLSTradeValue(itemName);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(
|
console.error(
|
||||||
`Error loading ncTradeValueText for item ${id}, skipping:`
|
`Error loading ncTradeValueText for item ${id}, skipping:`,
|
||||||
);
|
);
|
||||||
console.error(e);
|
console.error(e);
|
||||||
ncTradeValue = null;
|
ncTradeValue = null;
|
||||||
|
@ -395,7 +395,7 @@ const resolvers = {
|
||||||
currentUserOwnsThis: async (
|
currentUserOwnsThis: async (
|
||||||
{ id },
|
{ id },
|
||||||
_,
|
_,
|
||||||
{ currentUserId, userItemClosetHangersLoader }
|
{ currentUserId, userItemClosetHangersLoader },
|
||||||
) => {
|
) => {
|
||||||
if (currentUserId == null) return false;
|
if (currentUserId == null) return false;
|
||||||
const closetHangers = await userItemClosetHangersLoader.load({
|
const closetHangers = await userItemClosetHangersLoader.load({
|
||||||
|
@ -407,7 +407,7 @@ const resolvers = {
|
||||||
currentUserWantsThis: async (
|
currentUserWantsThis: async (
|
||||||
{ id },
|
{ id },
|
||||||
_,
|
_,
|
||||||
{ currentUserId, userItemClosetHangersLoader }
|
{ currentUserId, userItemClosetHangersLoader },
|
||||||
) => {
|
) => {
|
||||||
if (currentUserId == null) return false;
|
if (currentUserId == null) return false;
|
||||||
const closetHangers = await userItemClosetHangersLoader.load({
|
const closetHangers = await userItemClosetHangersLoader.load({
|
||||||
|
@ -419,7 +419,7 @@ const resolvers = {
|
||||||
currentUserHasInLists: async (
|
currentUserHasInLists: async (
|
||||||
{ id },
|
{ id },
|
||||||
_,
|
_,
|
||||||
{ currentUserId, userItemClosetHangersLoader }
|
{ currentUserId, userItemClosetHangersLoader },
|
||||||
) => {
|
) => {
|
||||||
if (currentUserId == null) return false;
|
if (currentUserId == null) return false;
|
||||||
const closetHangers = await userItemClosetHangersLoader.load({
|
const closetHangers = await userItemClosetHangersLoader.load({
|
||||||
|
@ -444,7 +444,7 @@ const resolvers = {
|
||||||
numUsersOfferingThis: async (
|
numUsersOfferingThis: async (
|
||||||
{ id },
|
{ id },
|
||||||
_,
|
_,
|
||||||
{ itemTradesLoader, userLastTradeActivityLoader }
|
{ itemTradesLoader, userLastTradeActivityLoader },
|
||||||
) => {
|
) => {
|
||||||
// First, get the trades themselves. TODO: Optimize into one query?
|
// First, get the trades themselves. TODO: Optimize into one query?
|
||||||
const trades = await itemTradesLoader.load({
|
const trades = await itemTradesLoader.load({
|
||||||
|
@ -455,7 +455,7 @@ const resolvers = {
|
||||||
// Then, get the last active dates for those users.
|
// Then, get the last active dates for those users.
|
||||||
const userIds = trades.map((t) => t.user.id);
|
const userIds = trades.map((t) => t.user.id);
|
||||||
const lastActiveDates = await userLastTradeActivityLoader.loadMany(
|
const lastActiveDates = await userLastTradeActivityLoader.loadMany(
|
||||||
userIds
|
userIds,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Finally, count how many of those dates were in the last 6 months.
|
// Finally, count how many of those dates were in the last 6 months.
|
||||||
|
@ -469,7 +469,7 @@ const resolvers = {
|
||||||
numUsersSeekingThis: async (
|
numUsersSeekingThis: async (
|
||||||
{ id },
|
{ id },
|
||||||
_,
|
_,
|
||||||
{ itemTradesLoader, userLastTradeActivityLoader }
|
{ itemTradesLoader, userLastTradeActivityLoader },
|
||||||
) => {
|
) => {
|
||||||
// First, get the trades themselves. TODO: Optimize into one query?
|
// First, get the trades themselves. TODO: Optimize into one query?
|
||||||
const trades = await itemTradesLoader.load({
|
const trades = await itemTradesLoader.load({
|
||||||
|
@ -480,7 +480,7 @@ const resolvers = {
|
||||||
// Then, get the last active dates for those users.
|
// Then, get the last active dates for those users.
|
||||||
const userIds = trades.map((t) => t.user.id);
|
const userIds = trades.map((t) => t.user.id);
|
||||||
const lastActiveDates = await userLastTradeActivityLoader.loadMany(
|
const lastActiveDates = await userLastTradeActivityLoader.loadMany(
|
||||||
userIds
|
userIds,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Finally, count how many of those dates were in the last 6 months.
|
// Finally, count how many of those dates were in the last 6 months.
|
||||||
|
@ -528,7 +528,7 @@ const resolvers = {
|
||||||
appearanceOn: async (
|
appearanceOn: async (
|
||||||
{ id },
|
{ id },
|
||||||
{ speciesId, colorId },
|
{ speciesId, colorId },
|
||||||
{ petTypeBySpeciesAndColorLoader }
|
{ petTypeBySpeciesAndColorLoader },
|
||||||
) => {
|
) => {
|
||||||
const petType = await petTypeBySpeciesAndColorLoader.load({
|
const petType = await petTypeBySpeciesAndColorLoader.load({
|
||||||
speciesId,
|
speciesId,
|
||||||
|
@ -553,7 +553,7 @@ const resolvers = {
|
||||||
speciesThatNeedModels: async (
|
speciesThatNeedModels: async (
|
||||||
{ id },
|
{ id },
|
||||||
{ colorId = "8" }, // Blue
|
{ colorId = "8" }, // Blue
|
||||||
{ speciesThatNeedModelsForItemLoader, allSpeciesIdsForColorLoader }
|
{ speciesThatNeedModelsForItemLoader, allSpeciesIdsForColorLoader },
|
||||||
) => {
|
) => {
|
||||||
// NOTE: If we're running this in the context of `itemsThatNeedModels`,
|
// NOTE: If we're running this in the context of `itemsThatNeedModels`,
|
||||||
// this loader should already be primed, no extra query!
|
// this loader should already be primed, no extra query!
|
||||||
|
@ -567,25 +567,25 @@ const resolvers = {
|
||||||
|
|
||||||
const modeledSpeciesIds = row.modeledSpeciesIds.split(",");
|
const modeledSpeciesIds = row.modeledSpeciesIds.split(",");
|
||||||
const allSpeciesIdsForThisColor = await allSpeciesIdsForColorLoader.load(
|
const allSpeciesIdsForThisColor = await allSpeciesIdsForColorLoader.load(
|
||||||
colorId
|
colorId,
|
||||||
);
|
);
|
||||||
|
|
||||||
let allModelableSpeciesIds = allSpeciesIdsForThisColor;
|
let allModelableSpeciesIds = allSpeciesIdsForThisColor;
|
||||||
if (!row.supportsVandagyre) {
|
if (!row.supportsVandagyre) {
|
||||||
allModelableSpeciesIds = allModelableSpeciesIds.filter(
|
allModelableSpeciesIds = allModelableSpeciesIds.filter(
|
||||||
(s) => s !== "55"
|
(s) => s !== "55",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const unmodeledSpeciesIds = allModelableSpeciesIds.filter(
|
const unmodeledSpeciesIds = allModelableSpeciesIds.filter(
|
||||||
(id) => !modeledSpeciesIds.includes(id)
|
(id) => !modeledSpeciesIds.includes(id),
|
||||||
);
|
);
|
||||||
return unmodeledSpeciesIds.map((id) => ({ id }));
|
return unmodeledSpeciesIds.map((id) => ({ id }));
|
||||||
},
|
},
|
||||||
canonicalAppearance: async (
|
canonicalAppearance: async (
|
||||||
{ id },
|
{ id },
|
||||||
{ preferredSpeciesId, preferredColorId },
|
{ preferredSpeciesId, preferredColorId },
|
||||||
{ db }
|
{ db },
|
||||||
) => {
|
) => {
|
||||||
const [rows] = await db.query(
|
const [rows] = await db.query(
|
||||||
`
|
`
|
||||||
|
@ -607,7 +607,7 @@ const resolvers = {
|
||||||
colors.standard DESC
|
colors.standard DESC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
`,
|
`,
|
||||||
[id, preferredSpeciesId || "<ignore>", preferredColorId || "<ignore>"]
|
[id, preferredSpeciesId || "<ignore>", preferredColorId || "<ignore>"],
|
||||||
);
|
);
|
||||||
if (rows.length === 0) {
|
if (rows.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -649,7 +649,7 @@ const resolvers = {
|
||||||
parents_swf_assets.swf_asset_id = swf_assets.id
|
parents_swf_assets.swf_asset_id = swf_assets.id
|
||||||
WHERE items.id = ?
|
WHERE items.id = ?
|
||||||
`,
|
`,
|
||||||
[id]
|
[id],
|
||||||
);
|
);
|
||||||
const bodyIds = rows.map((row) => row.body_id);
|
const bodyIds = rows.map((row) => row.body_id);
|
||||||
const bodies = bodyIds.map((id) => ({ id }));
|
const bodies = bodyIds.map((id) => ({ id }));
|
||||||
|
@ -658,7 +658,7 @@ const resolvers = {
|
||||||
compatibleBodiesAndTheirZones: async (
|
compatibleBodiesAndTheirZones: async (
|
||||||
{ id },
|
{ id },
|
||||||
_,
|
_,
|
||||||
{ itemCompatibleBodiesAndTheirZonesLoader }
|
{ itemCompatibleBodiesAndTheirZonesLoader },
|
||||||
) => {
|
) => {
|
||||||
const rows = await itemCompatibleBodiesAndTheirZonesLoader.load(id);
|
const rows = await itemCompatibleBodiesAndTheirZonesLoader.load(id);
|
||||||
return rows.map((row) => ({
|
return rows.map((row) => ({
|
||||||
|
@ -682,7 +682,7 @@ const resolvers = {
|
||||||
parents_swf_assets.swf_asset_id = swf_assets.id
|
parents_swf_assets.swf_asset_id = swf_assets.id
|
||||||
WHERE items.id = ?
|
WHERE items.id = ?
|
||||||
`,
|
`,
|
||||||
[id]
|
[id],
|
||||||
);
|
);
|
||||||
const bodyIds = rows.map((row) => String(row.body_id));
|
const bodyIds = rows.map((row) => String(row.body_id));
|
||||||
return bodyIds.map((bodyId) => ({ item: { id }, bodyId }));
|
return bodyIds.map((bodyId) => ({ item: { id }, bodyId }));
|
||||||
|
@ -698,7 +698,12 @@ const resolvers = {
|
||||||
bodyId,
|
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
|
// If there are no body-specific assets in this appearance, then remove
|
||||||
// assets with the glitch flag REQUIRES_OTHER_BODY_SPECIFIC_ASSETS: the
|
// assets with the glitch flag REQUIRES_OTHER_BODY_SPECIFIC_ASSETS: the
|
||||||
|
@ -717,7 +722,7 @@ const resolvers = {
|
||||||
restrictedZones: async (
|
restrictedZones: async (
|
||||||
{ item: { id: itemId }, bodyId },
|
{ item: { id: itemId }, bodyId },
|
||||||
_,
|
_,
|
||||||
{ itemSwfAssetLoader, itemLoader }
|
{ itemSwfAssetLoader, itemLoader },
|
||||||
) => {
|
) => {
|
||||||
// Check whether this appearance is empty. If so, restrict no zones.
|
// Check whether this appearance is empty. If so, restrict no zones.
|
||||||
const allSwfAssets = await itemSwfAssetLoader.load({ itemId, bodyId });
|
const allSwfAssets = await itemSwfAssetLoader.load({ itemId, bodyId });
|
||||||
|
@ -760,7 +765,7 @@ const resolvers = {
|
||||||
petTypeBySpeciesAndColorLoader,
|
petTypeBySpeciesAndColorLoader,
|
||||||
currentUserId,
|
currentUserId,
|
||||||
},
|
},
|
||||||
{ cacheControl }
|
{ cacheControl },
|
||||||
) => {
|
) => {
|
||||||
if (currentUserOwnsOrWants != null) {
|
if (currentUserOwnsOrWants != null) {
|
||||||
cacheControl.setCacheHint({ scope: "PRIVATE" });
|
cacheControl.setCacheHint({ scope: "PRIVATE" });
|
||||||
|
@ -775,7 +780,7 @@ const resolvers = {
|
||||||
if (!petType) {
|
if (!petType) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`pet type not found: speciesId=${fitsPet.speciesId}, ` +
|
`pet type not found: speciesId=${fitsPet.speciesId}, ` +
|
||||||
`colorId: ${fitsPet.colorId}`
|
`colorId: ${fitsPet.colorId}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
bodyId = petType.bodyId;
|
bodyId = petType.bodyId;
|
||||||
|
@ -806,7 +811,7 @@ const resolvers = {
|
||||||
itemSearchV2: async (
|
itemSearchV2: async (
|
||||||
_,
|
_,
|
||||||
{ query, fitsPet, itemKind, currentUserOwnsOrWants, zoneIds = [] },
|
{ query, fitsPet, itemKind, currentUserOwnsOrWants, zoneIds = [] },
|
||||||
{ petTypeBySpeciesAndColorLoader }
|
{ petTypeBySpeciesAndColorLoader },
|
||||||
) => {
|
) => {
|
||||||
let bodyId = null;
|
let bodyId = null;
|
||||||
if (fitsPet) {
|
if (fitsPet) {
|
||||||
|
@ -817,7 +822,7 @@ const resolvers = {
|
||||||
if (!petType) {
|
if (!petType) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`pet type not found: speciesId=${fitsPet.speciesId}, ` +
|
`pet type not found: speciesId=${fitsPet.speciesId}, ` +
|
||||||
`colorId: ${fitsPet.colorId}`
|
`colorId: ${fitsPet.colorId}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
bodyId = petType.bodyId;
|
bodyId = petType.bodyId;
|
||||||
|
@ -851,7 +856,7 @@ const resolvers = {
|
||||||
offset,
|
offset,
|
||||||
limit,
|
limit,
|
||||||
},
|
},
|
||||||
{ petTypeBySpeciesAndColorLoader, itemSearchLoader, currentUserId }
|
{ petTypeBySpeciesAndColorLoader, itemSearchLoader, currentUserId },
|
||||||
) => {
|
) => {
|
||||||
const petType = await petTypeBySpeciesAndColorLoader.load({
|
const petType = await petTypeBySpeciesAndColorLoader.load({
|
||||||
speciesId,
|
speciesId,
|
||||||
|
@ -859,7 +864,7 @@ const resolvers = {
|
||||||
});
|
});
|
||||||
if (!petType) {
|
if (!petType) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`pet type not found: speciesId=${speciesId}, colorId: ${colorId}`
|
`pet type not found: speciesId=${speciesId}, colorId: ${colorId}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const { bodyId } = petType;
|
const { bodyId } = petType;
|
||||||
|
@ -883,10 +888,10 @@ const resolvers = {
|
||||||
itemsThatNeedModels: async (
|
itemsThatNeedModels: async (
|
||||||
_,
|
_,
|
||||||
{ colorId = "8" }, // Defaults to Blue
|
{ colorId = "8" }, // Defaults to Blue
|
||||||
{ itemsThatNeedModelsLoader }
|
{ itemsThatNeedModelsLoader },
|
||||||
) => {
|
) => {
|
||||||
const speciesIdsByColorIdAndItemId = await itemsThatNeedModelsLoader.load(
|
const speciesIdsByColorIdAndItemId = await itemsThatNeedModelsLoader.load(
|
||||||
"all"
|
"all",
|
||||||
);
|
);
|
||||||
const speciesIdsByItemIds = speciesIdsByColorIdAndItemId.get(colorId);
|
const speciesIdsByItemIds = speciesIdsByColorIdAndItemId.get(colorId);
|
||||||
const itemIds = (speciesIdsByItemIds && speciesIdsByItemIds.keys()) || [];
|
const itemIds = (speciesIdsByItemIds && speciesIdsByItemIds.keys()) || [];
|
||||||
|
@ -899,7 +904,7 @@ const resolvers = {
|
||||||
{ query, bodyId, itemKind, currentUserOwnsOrWants, zoneIds },
|
{ query, bodyId, itemKind, currentUserOwnsOrWants, zoneIds },
|
||||||
{ offset, limit },
|
{ offset, limit },
|
||||||
{ currentUserId, itemSearchNumTotalItemsLoader },
|
{ currentUserId, itemSearchNumTotalItemsLoader },
|
||||||
{ cacheControl }
|
{ cacheControl },
|
||||||
) => {
|
) => {
|
||||||
if (currentUserOwnsOrWants != null) {
|
if (currentUserOwnsOrWants != null) {
|
||||||
cacheControl.setCacheHint({ scope: "PRIVATE" });
|
cacheControl.setCacheHint({ scope: "PRIVATE" });
|
||||||
|
@ -920,7 +925,7 @@ const resolvers = {
|
||||||
{ query, bodyId, itemKind, currentUserOwnsOrWants, zoneIds },
|
{ query, bodyId, itemKind, currentUserOwnsOrWants, zoneIds },
|
||||||
{ offset, limit },
|
{ offset, limit },
|
||||||
{ currentUserId, itemSearchItemsLoader },
|
{ currentUserId, itemSearchItemsLoader },
|
||||||
{ cacheControl }
|
{ cacheControl },
|
||||||
) => {
|
) => {
|
||||||
if (currentUserOwnsOrWants != null) {
|
if (currentUserOwnsOrWants != null) {
|
||||||
cacheControl.setCacheHint({ scope: "PRIVATE" });
|
cacheControl.setCacheHint({ scope: "PRIVATE" });
|
||||||
|
@ -944,7 +949,7 @@ const resolvers = {
|
||||||
addToItemsCurrentUserOwns: async (
|
addToItemsCurrentUserOwns: async (
|
||||||
_,
|
_,
|
||||||
{ itemId },
|
{ itemId },
|
||||||
{ currentUserId, db, itemLoader }
|
{ currentUserId, db, itemLoader },
|
||||||
) => {
|
) => {
|
||||||
if (currentUserId == null) {
|
if (currentUserId == null) {
|
||||||
throw new Error(`must be logged in`);
|
throw new Error(`must be logged in`);
|
||||||
|
@ -969,7 +974,7 @@ const resolvers = {
|
||||||
WHERE item_id = ? AND user_id = ? AND owned = ?
|
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 };
|
return { id: itemId };
|
||||||
|
@ -977,7 +982,7 @@ const resolvers = {
|
||||||
removeFromItemsCurrentUserOwns: async (
|
removeFromItemsCurrentUserOwns: async (
|
||||||
_,
|
_,
|
||||||
{ itemId },
|
{ itemId },
|
||||||
{ currentUserId, db, itemLoader }
|
{ currentUserId, db, itemLoader },
|
||||||
) => {
|
) => {
|
||||||
if (currentUserId == null) {
|
if (currentUserId == null) {
|
||||||
throw new Error(`must be logged in`);
|
throw new Error(`must be logged in`);
|
||||||
|
@ -991,7 +996,7 @@ const resolvers = {
|
||||||
await db.query(
|
await db.query(
|
||||||
`DELETE FROM closet_hangers
|
`DELETE FROM closet_hangers
|
||||||
WHERE item_id = ? AND user_id = ? AND owned = ?;`,
|
WHERE item_id = ? AND user_id = ? AND owned = ?;`,
|
||||||
[itemId, currentUserId, true]
|
[itemId, currentUserId, true],
|
||||||
);
|
);
|
||||||
|
|
||||||
return { id: itemId };
|
return { id: itemId };
|
||||||
|
@ -999,7 +1004,7 @@ const resolvers = {
|
||||||
addToItemsCurrentUserWants: async (
|
addToItemsCurrentUserWants: async (
|
||||||
_,
|
_,
|
||||||
{ itemId },
|
{ itemId },
|
||||||
{ currentUserId, db, itemLoader }
|
{ currentUserId, db, itemLoader },
|
||||||
) => {
|
) => {
|
||||||
if (currentUserId == null) {
|
if (currentUserId == null) {
|
||||||
throw new Error(`must be logged in`);
|
throw new Error(`must be logged in`);
|
||||||
|
@ -1034,7 +1039,7 @@ const resolvers = {
|
||||||
itemId,
|
itemId,
|
||||||
currentUserId,
|
currentUserId,
|
||||||
false,
|
false,
|
||||||
]
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
return { id: itemId };
|
return { id: itemId };
|
||||||
|
@ -1042,7 +1047,7 @@ const resolvers = {
|
||||||
removeFromItemsCurrentUserWants: async (
|
removeFromItemsCurrentUserWants: async (
|
||||||
_,
|
_,
|
||||||
{ itemId },
|
{ itemId },
|
||||||
{ currentUserId, db, itemLoader }
|
{ currentUserId, db, itemLoader },
|
||||||
) => {
|
) => {
|
||||||
if (currentUserId == null) {
|
if (currentUserId == null) {
|
||||||
throw new Error(`must be logged in`);
|
throw new Error(`must be logged in`);
|
||||||
|
@ -1056,7 +1061,7 @@ const resolvers = {
|
||||||
await db.query(
|
await db.query(
|
||||||
`DELETE FROM closet_hangers
|
`DELETE FROM closet_hangers
|
||||||
WHERE item_id = ? AND user_id = ? AND owned = ?;`,
|
WHERE item_id = ? AND user_id = ? AND owned = ?;`,
|
||||||
[itemId, currentUserId, false]
|
[itemId, currentUserId, false],
|
||||||
);
|
);
|
||||||
|
|
||||||
return { id: itemId };
|
return { id: itemId };
|
||||||
|
@ -1064,11 +1069,11 @@ const resolvers = {
|
||||||
addItemToClosetList: async (
|
addItemToClosetList: async (
|
||||||
_,
|
_,
|
||||||
{ listId, itemId, removeFromDefaultList },
|
{ listId, itemId, removeFromDefaultList },
|
||||||
{ currentUserId, db, closetListLoader }
|
{ currentUserId, db, closetListLoader },
|
||||||
) => {
|
) => {
|
||||||
const closetListRef = await loadClosetListOrDefaultList(
|
const closetListRef = await loadClosetListOrDefaultList(
|
||||||
listId,
|
listId,
|
||||||
closetListLoader
|
closetListLoader,
|
||||||
);
|
);
|
||||||
if (closetListRef == null) {
|
if (closetListRef == null) {
|
||||||
throw new Error(`list ${listId} not found`);
|
throw new Error(`list ${listId} not found`);
|
||||||
|
@ -1094,7 +1099,7 @@ const resolvers = {
|
||||||
AND owned = ?
|
AND owned = ?
|
||||||
LIMIT 1;
|
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)
|
(item_id, user_id, owned, list_id, quantity, created_at, updated_at)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?);
|
VALUES (?, ?, ?, ?, ?, ?, ?);
|
||||||
`,
|
`,
|
||||||
[itemId, userId, ownsOrWantsItems === "OWNS", listId, 1, now, now]
|
[itemId, userId, ownsOrWantsItems === "OWNS", listId, 1, now, now],
|
||||||
);
|
);
|
||||||
|
|
||||||
await connection.commit();
|
await connection.commit();
|
||||||
|
@ -1126,11 +1131,11 @@ const resolvers = {
|
||||||
removeItemFromClosetList: async (
|
removeItemFromClosetList: async (
|
||||||
_,
|
_,
|
||||||
{ listId, itemId, ensureInSomeList },
|
{ listId, itemId, ensureInSomeList },
|
||||||
{ currentUserId, db, closetListLoader }
|
{ currentUserId, db, closetListLoader },
|
||||||
) => {
|
) => {
|
||||||
const closetListRef = await loadClosetListOrDefaultList(
|
const closetListRef = await loadClosetListOrDefaultList(
|
||||||
listId,
|
listId,
|
||||||
closetListLoader
|
closetListLoader,
|
||||||
);
|
);
|
||||||
if (closetListRef == null) {
|
if (closetListRef == null) {
|
||||||
throw new Error(`list ${listId} not found`);
|
throw new Error(`list ${listId} not found`);
|
||||||
|
@ -1157,7 +1162,7 @@ const resolvers = {
|
||||||
DELETE FROM closet_hangers
|
DELETE FROM closet_hangers
|
||||||
WHERE ${listMatcherCondition} AND item_id = ? LIMIT 1;
|
WHERE ${listMatcherCondition} AND item_id = ? LIMIT 1;
|
||||||
`,
|
`,
|
||||||
[...listMatcherValues, itemId]
|
[...listMatcherValues, itemId],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (ensureInSomeList) {
|
if (ensureInSomeList) {
|
||||||
|
@ -1168,7 +1173,7 @@ const resolvers = {
|
||||||
SELECT COUNT(*) AS count FROM closet_hangers
|
SELECT COUNT(*) AS count FROM closet_hangers
|
||||||
WHERE user_id = ? AND item_id = ? AND owned = ?
|
WHERE user_id = ? AND item_id = ? AND owned = ?
|
||||||
`,
|
`,
|
||||||
[userId, itemId, ownsOrWantsItems === "OWNS"]
|
[userId, itemId, ownsOrWantsItems === "OWNS"],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (rows[0].count === 0) {
|
if (rows[0].count === 0) {
|
||||||
|
@ -1179,7 +1184,7 @@ const resolvers = {
|
||||||
(item_id, user_id, owned, list_id, quantity, created_at, updated_at)
|
(item_id, user_id, owned, list_id, quantity, created_at, updated_at)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?);
|
VALUES (?, ?, ?, ?, ?, ?, ?);
|
||||||
`,
|
`,
|
||||||
[itemId, userId, ownsOrWantsItems === "OWNS", null, 1, now, now]
|
[itemId, userId, ownsOrWantsItems === "OWNS", null, 1, now, now],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue