Compare commits

..

No commits in common. "c3529450bf0412508b832f395261a453c9e8a1aa" and "ca7138d7553bab9b4e264f1d91cae4a200d28b88" have entirely different histories.

14 changed files with 249 additions and 98 deletions

View file

@ -82,6 +82,7 @@
"delete-user": "yarn run-script scripts/delete-user.js",
"export-users-to-auth0": "yarn run-script scripts/export-users-to-auth0.js",
"model-needed-items": "yarn run-script scripts/model-needed-items.js",
"validate-owls-data": "yarn run-script scripts/validate-owls-data.js",
"aws": "AWS_ACCESS_KEY_ID=$(dotenv -p ARCHIVE_STORAGE_READWRITE_ACCESS_KEY) AWS_SECRET_ACCESS_KEY=$(dotenv -p ARCHIVE_STORAGE_READWRITE_SECRET_KEY) aws --endpoint=https://$(dotenv -p ARCHIVE_STORAGE_HOST)",
"archive:full": "yarn archive:prepare:full && yarn archive:create:full && yarn archive:upload:full",
"archive:delta": "yarn archive:prepare:delta && yarn archive:create:delta && yarn archive:upload:delta",

View file

@ -49,7 +49,7 @@ async function handle(req, res) {
// Modified, unless the data really did change.)
res.setHeader(
"Cache-Control",
"public, max-age=3600, stale-while-revalidate=86400",
"public, max-age=3600, stale-while-revalidate=86400"
);
return res.send(itemValues);
}
@ -58,8 +58,11 @@ async function loadAllNcItemNamesAndIds() {
const db = await connectToDb();
const [rows] = await db.query(`
SELECT items.id, items.name FROM items
WHERE items.rarity_index IN (0, 500) OR is_manually_nc = 1
SELECT items.id, item_translations.name FROM items
INNER JOIN item_translations ON item_translations.item_id = items.id
WHERE
(items.rarity_index IN (0, 500) OR is_manually_nc = 1)
AND item_translations.locale = "en"
`);
return rows.map(({ id, name }) => ({ id, name: normalizeItemName(name) }));
@ -72,13 +75,13 @@ async function loadAllNcItemNamesAndIds() {
*/
async function loadOWLSValuesByIdOrName() {
const res = await fetch(
`https://neo-owls.herokuapp.com/itemdata/owls_script/`,
`https://neo-owls.herokuapp.com/itemdata/owls_script/`
);
const json = await res.json();
if (!res.ok) {
throw new Error(
`Could not load OWLS Pricer data: ${res.status} ${res.statusText}`,
`Could not load OWLS Pricer data: ${res.status} ${res.statusText}`
);
}
@ -134,7 +137,7 @@ function normalizeItemName(name) {
async function handleWithBeeline(req, res) {
beeline.withTrace(
{ name: "api/allNCTradeValues", operation_name: "api/allNCTradeValues" },
() => handle(req, res),
() => handle(req, res)
);
}

View file

@ -115,7 +115,8 @@ async function handle(req, res) {
if (process.env["SUPPORT_TOOLS_DISCORD_WEBHOOK_URL"]) {
try {
const { itemLoader, zoneLoader } = buildLoaders(db);
const { itemLoader, itemTranslationLoader, zoneLoader } =
buildLoaders(db);
// Copied from setLayerBodyId mutation
const itemId = await db
@ -126,8 +127,9 @@ async function handle(req, res) {
)
.then(([rows]) => normalizeRow(rows[0]).parentId);
const [item, zone, bodyName] = await Promise.all([
const [item, itemTranslation, zone, bodyName] = await Promise.all([
itemLoader.load(itemId),
itemTranslationLoader.load(itemId),
zoneLoader.load(layer.zoneId),
loadBodyName(layer.bodyId, db),
]);
@ -135,7 +137,7 @@ async function handle(req, res) {
await logToDiscord({
embeds: [
{
title: `🛠 ${item.name}`,
title: `🛠 ${itemTranslation.name}`,
thumbnail: {
url: item.thumbnailUrl,
height: 80,

View file

@ -1,6 +1,6 @@
yarn run --silent mysqldump openneo_impress species colors zones \
> $(dirname $0)/../public-data-constants.sql \
&& yarn run --silent mysqldump openneo_impress alt_styles items \
&& yarn run --silent mysqldump openneo_impress alt_styles items item_translations \
parents_swf_assets pet_states pet_types swf_assets \
| gzip -c \
> $(dirname $0)/../public-data-from-modeling.sql.gz

View file

@ -1,6 +1,6 @@
yarn run --silent mysqldump --no-data openneo_impress closet_hangers closet_lists \
colors items modeling_logs parents_swf_assets pet_types pet_states species \
swf_assets users zones \
colors items item_translations modeling_logs parents_swf_assets pet_types \
pet_states species swf_assets users zones \
| \
sed 's/ AUTO_INCREMENT=[0-9]*//g' \
> $(dirname $0)/../schema-for-impress.sql \

View file

@ -1,8 +1,8 @@
-- MariaDB dump 10.19 Distrib 10.6.16-MariaDB, for debian-linux-gnu (x86_64)
--
-- Host: impress.openneo.net Database: openneo_impress
-- Host: legacy.impress.openneo.net Database: openneo_impress
-- ------------------------------------------------------
-- Server version 10.6.16-MariaDB-0ubuntu0.22.04.1
-- Server version 5.5.62-0ubuntu0.14.04.1-log
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
@ -26,7 +26,7 @@ CREATE TABLE `species` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=56 DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci;
) ENGINE=InnoDB AUTO_INCREMENT=56 DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@ -50,10 +50,10 @@ CREATE TABLE `colors` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`basic` tinyint(1) DEFAULT NULL,
`standard` tinyint(1) DEFAULT NULL,
`prank` tinyint(1) NOT NULL DEFAULT 0,
`prank` tinyint(1) NOT NULL DEFAULT '0',
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=119 DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci;
) ENGINE=InnoDB AUTO_INCREMENT=119 DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@ -80,7 +80,7 @@ CREATE TABLE `zones` (
`label` varchar(255) NOT NULL,
`plain_label` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=53 DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci;
) ENGINE=InnoDB AUTO_INCREMENT=53 DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
--
@ -102,4 +102,4 @@ UNLOCK TABLES;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2024-02-20 15:59:16
-- Dump completed on 2024-02-17 12:38:11

View file

@ -7,6 +7,7 @@ GRANT SELECT ON colors TO impress2020;
GRANT SELECT ON donation_features TO impress2020;
GRANT SELECT ON donations TO impress2020;
GRANT SELECT ON items TO impress2020;
GRANT SELECT ON item_translations TO impress2020;
GRANT SELECT ON modeling_logs TO impress2020;
GRANT SELECT ON parents_swf_assets TO impress2020;
GRANT SELECT ON pet_types TO impress2020;
@ -17,6 +18,7 @@ GRANT SELECT ON zones TO impress2020;
-- Public data tables: write. Used in modeling and support tools.
GRANT INSERT, UPDATE ON items TO impress2020;
GRANT INSERT, UPDATE ON item_translations TO impress2020;
GRANT INSERT, UPDATE, DELETE ON parents_swf_assets TO impress2020;
GRANT INSERT, UPDATE ON pet_types TO impress2020;
GRANT INSERT, UPDATE ON pet_states TO impress2020;

View file

@ -0,0 +1,72 @@
/**
* This script compares the data we get from the OWLS bulk endpoint to the data
* we get by loading the data with the new individual endpoint! This will help
* us check for bugs as we switch over!
*/
const fetch = require("node-fetch");
const connectToDb = require("../src/server/db");
const buildLoaders = require("../src/server/loaders");
const { getOWLSTradeValue } = require("../src/server/nc-trade-values");
async function main() {
const db = await connectToDb();
const { itemTranslationLoader } = buildLoaders(db);
// Load the bulk data. We're gonna loop through it all!
const bulkEndpointRes = await fetch(
`http://localhost:3000/api/allNCTradeValues`
);
const bulkEndpointData = await bulkEndpointRes.json();
// Load the item names, bc our bulk data is keyed by item ID.
const itemIds = Object.keys(bulkEndpointData);
const itemTranslations = await itemTranslationLoader.loadMany(itemIds);
// Load the OWLs data! I don't do any of it in parallel, because I'm worried
// about upsetting the server, y'know?
let numChecked = 0;
let numSuccesses = 0;
let numFailures = 0;
for (const { name, itemId } of itemTranslations) {
if (numChecked % 100 === 0) {
console.info(`Checked ${numChecked} items`);
}
numChecked++;
const expectedValueText = bulkEndpointData[itemId].valueText;
let actualValue;
try {
actualValue = await getOWLSTradeValue(name);
} catch (error) {
console.error(`[${itemId} / ${name}]: Error loading data:\n`, error);
numFailures++;
continue;
}
if (actualValue == null) {
console.error(`[${itemId} / ${name}]: No value found.`);
numFailures++;
continue;
}
const actualValueText = actualValue.valueText;
if (expectedValueText !== actualValueText) {
console.error(
`[${itemId}]: Value did not match. ` +
`Expected: ${JSON.stringify(expectedValueText)}. ` +
`Actual: ${JSON.stringify(actualValueText)}`
);
numFailures++;
continue;
}
numSuccesses++;
}
console.info(
`Checked all ${numChecked} items. ` +
`${numSuccesses} successes, ${numFailures} failures.`
);
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.then(() => process.exit());

View file

@ -224,27 +224,55 @@ const buildItemLoader = (db) =>
);
});
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(String(itemId)) ||
new Error(`could not find translation for item ${itemId}`),
);
});
const buildItemByNameLoader = (db, loaders) =>
new DataLoader(
async (names) => {
const qs = names.map((_) => "?").join(", ");
const normalizedNames = names.map((name) => name.trim().toLowerCase());
const [rows] = await db.execute(
// NOTE: In our MySQL schema, this is a case-insensitive exact search.
`SELECT * FROM items WHERE name IN (${qs})`,
{
// NOTE: In our MySQL schema, this is a case-insensitive exact search.
sql: `SELECT items.*, item_translations.* FROM item_translations
INNER JOIN items ON items.id = item_translations.item_id
WHERE name IN (${qs}) AND locale = "en"`,
nestTables: true,
},
normalizedNames,
);
const entitiesByName = new Map();
for (const row of rows) {
const item = normalizeRow(row);
const item = normalizeRow(row.items);
const itemTranslation = normalizeRow(row.item_translations);
loaders.itemLoader.prime(item.id, item);
loaders.itemTranslationLoader.prime(item.id, itemTranslation);
const normalizedName = item.name.trim().toLowerCase();
entitiesByName.set(normalizedName, item);
const normalizedName = itemTranslation.name.trim().toLowerCase();
entitiesByName.set(normalizedName, { item, itemTranslation });
}
return normalizedNames.map((name) => entitiesByName.get(name) || null);
return normalizedNames.map(
(name) =>
entitiesByName.get(name) || { item: null, itemTranslation: null },
);
},
{ cacheKeyFn: (name) => name.trim().toLowerCase() },
);
@ -280,9 +308,7 @@ function buildItemSearchConditions({
continue;
}
const condition = isNegative
? "items.name NOT LIKE ?"
: "items.name LIKE ?";
const condition = isNegative ? "t.name NOT LIKE ?" : "t.name LIKE ?";
const matcher = "%" + word.replace(/_%/g, "\\$0") + "%";
wordMatchConditions.push(condition);
@ -310,6 +336,7 @@ function buildItemSearchConditions({
: [];
const queryJoins = `
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
@ -319,7 +346,7 @@ function buildItemSearchConditions({
const queryConditions = `
(${wordMatchCondition}) AND (${bodyIdCondition}) AND
(${zoneIdsCondition}) AND (${itemKindCondition}) AND
(${currentUserCondition})
(${currentUserCondition}) AND t.locale = "en"
`;
const queryConditionValues = [
...wordMatchValues,
@ -403,10 +430,10 @@ const buildItemSearchItemsLoader = (db, loaders) =>
const [rows] = await db.execute(
`
SELECT DISTINCT items.* FROM items
SELECT DISTINCT items.*, t.name FROM items
${queryJoins}
WHERE ${queryConditions}
ORDER BY items.name
ORDER BY t.name
LIMIT ? OFFSET ?
`,
[...queryConditionValues, actualLimit, actualOffset],
@ -487,7 +514,8 @@ async function runItemModelingQuery(db, filterToItemIds) {
INNER JOIN parents_swf_assets psa ON psa.parent_type = "Item"
AND psa.parent_id = items.id
INNER JOIN swf_assets ON swf_assets.id = psa.swf_asset_id
WHERE items.modeling_status_hint IS NULL AND items.name NOT LIKE "%MME%"
INNER JOIN item_translations it ON it.item_id = items.id AND it.locale = "en"
WHERE items.modeling_status_hint IS NULL AND it.name NOT LIKE "%MME%"
AND ${itemIdsCondition}
ORDER BY item_id
) T_ITEMS
@ -1262,8 +1290,10 @@ const buildUserClosetHangersLoader = (db) =>
new DataLoader(async (userIds) => {
const qs = userIds.map((_) => "?").join(",");
const [rows] = await db.execute(
`SELECT closet_hangers.*, items.name as item_name FROM closet_hangers
`SELECT closet_hangers.*, item_translations.name as item_name FROM closet_hangers
INNER JOIN items ON items.id = closet_hangers.item_id
INNER JOIN item_translations ON
item_translations.item_id = items.id AND locale = "en"
WHERE user_id IN (${qs})
ORDER BY item_name`,
userIds,
@ -1452,6 +1482,7 @@ function buildLoaders(db) {
buildClosetHangersForDefaultListLoader(db);
loaders.colorLoader = buildColorLoader(db);
loaders.itemLoader = buildItemLoader(db);
loaders.itemTranslationLoader = buildItemTranslationLoader(db);
loaders.itemByNameLoader = buildItemByNameLoader(db, loaders);
loaders.itemSearchNumTotalItemsLoader =
buildItemSearchNumTotalItemsLoader(db);

View file

@ -33,7 +33,7 @@ async function saveModelingData(customPetData, petMetaData, context) {
const { db } = context;
await db.execute(
`INSERT INTO modeling_logs (log_json, pet_name) VALUES (?, ?)`,
[JSON.stringify(modelingLogs, null, 4), petMetaData.name],
[JSON.stringify(modelingLogs, null, 4), petMetaData.name]
);
}
}
@ -41,7 +41,7 @@ async function saveModelingData(customPetData, petMetaData, context) {
async function savePetTypeAndStateModelingData(
customPetData,
petMetaData,
context,
context
) {
// NOTE: When we automatically model items with "@imageHash" pet names, we
// can't load corresponding metadata. That's fine, the script is just looking
@ -133,7 +133,7 @@ async function savePetTypeAndStateModelingData(
biologyAssets.map((asset) => ({
type: "biology",
remoteId: String(asset.part_id),
})),
}))
),
]);
swfAssets = swfAssets.filter((sa) => sa != null);
@ -158,7 +158,7 @@ async function savePetTypeAndStateModelingData(
await db.execute(
`INSERT INTO parents_swf_assets (parent_type, parent_id, swf_asset_id)
VALUES ${qs};`,
values,
values
);
addToModelingLogs({
@ -172,30 +172,46 @@ async function savePetTypeAndStateModelingData(
}
async function saveItemModelingData(customPetData, context) {
const { db, itemLoader, addToModelingLogs } = context;
const { db, itemLoader, itemTranslationLoader, addToModelingLogs } = context;
const objectInfos = Object.values(customPetData.object_info_registry);
const incomingItems = objectInfos.map((objectInfo) => ({
id: String(objectInfo.obj_info_id),
name: objectInfo.name,
description: objectInfo.description,
zonesRestrict: objectInfo.zones_restrict,
thumbnailUrl: objectInfo.thumbnail_url,
category: objectInfo.category,
type: objectInfo.type,
rarity: objectInfo.rarity,
rarityIndex: objectInfo.rarity_index,
price: objectInfo.price,
weightLbs: objectInfo.weight_lbs,
}));
const incomingItemTranslations = objectInfos.map((objectInfo) => ({
itemId: String(objectInfo.obj_info_id),
locale: "en",
name: objectInfo.name,
description: objectInfo.description,
rarity: objectInfo.rarity,
}));
await syncToDb(db, incomingItems, {
loader: itemLoader,
tableName: "items",
buildLoaderKey: (row) => row.id,
buildUpdateCondition: (row) => [`id = ?`, row.id],
addToModelingLogs,
});
await Promise.all([
syncToDb(db, incomingItems, {
loader: itemLoader,
tableName: "items",
buildLoaderKey: (row) => row.id,
buildUpdateCondition: (row) => [`id = ?`, row.id],
addToModelingLogs,
}),
syncToDb(db, incomingItemTranslations, {
loader: itemTranslationLoader,
tableName: "item_translations",
buildLoaderKey: (row) => row.itemId,
buildUpdateCondition: (row) => [
`item_id = ? AND locale = "en"`,
row.itemId,
],
addToModelingLogs,
}),
]);
}
async function saveSwfAssetModelingData(customPetData, context) {
@ -297,7 +313,7 @@ async function saveSwfAssetModelingData(customPetData, context) {
// asset, despite only having remote_id available here. This saves
// us from another round-trip to SELECT the inserted IDs.
`(?, ?, ` +
`(SELECT id FROM swf_assets WHERE type = "object" AND remote_id = ?))`,
`(SELECT id FROM swf_assets WHERE type = "object" AND remote_id = ?))`
)
.join(", ");
const values = relationshipInserts
@ -311,7 +327,7 @@ async function saveSwfAssetModelingData(customPetData, context) {
await db.execute(
`INSERT INTO parents_swf_assets (parent_type, parent_id, swf_asset_id)
VALUES ${qs}`,
values,
values
);
addToModelingLogs({
@ -348,7 +364,7 @@ async function syncToDb(
includeUpdatedAt = true,
afterInsert = null,
addToModelingLogs,
},
}
) {
const loaderKeys = incomingRows.map(buildLoaderKey);
const currentRows = await loader.loadMany(loaderKeys);
@ -429,7 +445,7 @@ async function syncToDb(
// underscore-case instead of camel-case.
const rowKeys = Object.keys(inserts[0]).sort();
const columnNames = rowKeys.map((key) =>
key.replace(/[A-Z]/g, (m) => "_" + m[0].toLowerCase()),
key.replace(/[A-Z]/g, (m) => "_" + m[0].toLowerCase())
);
const columnsStr = columnNames.join(", ");
const qs = columnNames.map((_) => "?").join(", ");
@ -437,7 +453,7 @@ async function syncToDb(
const rowValues = inserts.map((row) => rowKeys.map((key) => row[key]));
await db.execute(
`INSERT INTO ${tableName} (${columnsStr}) VALUES ${rowQs};`,
rowValues.flat(),
rowValues.flat()
);
if (afterInsert) {
await afterInsert(inserts);
@ -453,15 +469,15 @@ async function syncToDb(
const rowKeys = Object.keys(update).sort();
const rowValues = rowKeys.map((k) => update[k]);
const columnNames = rowKeys.map((key) =>
key.replace(/[A-Z]/g, (m) => "_" + m[0].toLowerCase()),
key.replace(/[A-Z]/g, (m) => "_" + m[0].toLowerCase())
);
const qs = columnNames.map((c) => `${c} = ?`).join(", ");
const [conditionQs, ...conditionValues] = buildUpdateCondition(incomingRow);
updatePromises.push(
db.execute(
`UPDATE ${tableName} SET ${qs} WHERE ${conditionQs} LIMIT 1;`,
[...rowValues, ...conditionValues],
),
[...rowValues, ...conditionValues]
)
);
}
await Promise.all(updatePromises);

View file

@ -316,15 +316,15 @@ const typeDefs = gql`
const resolvers = {
Item: {
name: async ({ id, name }, _, { itemLoader }) => {
name: async ({ id, name }, _, { itemTranslationLoader }) => {
if (name) return name;
const item = await itemLoader.load(id);
return item.name;
const translation = await itemTranslationLoader.load(id);
return translation.name;
},
description: async ({ id, description }, _, { itemLoader }) => {
description: async ({ id, description }, _, { itemTranslationLoader }) => {
if (description) return description;
const item = await itemLoader.load(id);
return item.description;
const translation = await itemTranslationLoader.load(id);
return translation.description;
},
thumbnailUrl: async ({ id, thumbnailUrl }, _, { itemLoader }) => {
if (thumbnailUrl) return thumbnailUrl;
@ -340,9 +340,15 @@ const resolvers = {
const item = await itemLoader.load(id);
return isNC(item);
},
isPb: async ({ id }, _, { itemLoader }) => {
const item = await itemLoader.load(id);
return item.description.includes(
isPb: async ({ id }, _, { itemTranslationLoader }) => {
const translation = await itemTranslationLoader.load(id);
if (!translation) {
console.warn(
`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!",
);
},
@ -354,14 +360,20 @@ const resolvers = {
// This feature is deprecated, so now we just always return unknown value.
return null;
},
ncTradeValueText: async ({ id }, _, { itemLoader }) => {
ncTradeValueText: async (
{ id },
_,
{ itemLoader, itemTranslationLoader },
) => {
// Skip this lookup for non-NC items, as a perf optimization.
const item = await itemLoader.load(id);
if (!isNC(item)) {
return;
}
let itemName = item.name;
// Get the item name, which is how we look things up in ~owls.
const itemTranslation = await itemTranslationLoader.load(id);
let itemName = itemTranslation.name;
// HACK: The name "Butterfly Dress" is used for two different items.
// Here's what ~owls does to distinguish!
@ -743,12 +755,12 @@ const resolvers = {
return ids.map((id) => ({ id }));
},
itemByName: async (_, { name }, { itemByNameLoader }) => {
const item = await itemByNameLoader.load(name);
const { item } = await itemByNameLoader.load(name);
return item ? { id: item.id } : null;
},
itemsByName: async (_, { names }, { itemByNameLoader }) => {
const items = await itemByNameLoader.loadMany(names);
return items.map((item) => (item ? { id: item.id } : null));
return items.map(({ item }) => (item ? { id: item.id } : null));
},
itemSearch: async (
_,

View file

@ -104,7 +104,7 @@ const resolvers = {
setManualSpecialColor: async (
_,
{ itemId, colorId, supportSecret },
{ itemLoader, colorLoader, db },
{ itemLoader, itemTranslationLoader, colorLoader, db },
) => {
assertSupportSecretOrThrow(supportSecret);
@ -125,7 +125,8 @@ const resolvers = {
if (process.env["SUPPORT_TOOLS_DISCORD_WEBHOOK_URL"]) {
try {
const [oldColor, newColor] = await Promise.all([
const [itemTranslation, oldColor, newColor] = await Promise.all([
itemTranslationLoader.load(itemId),
oldItem.manualSpecialColorId
? colorLoader.load(oldItem.manualSpecialColorId)
: Promise.resolve(null),
@ -141,7 +142,7 @@ const resolvers = {
await logToDiscord({
embeds: [
{
title: `🛠 ${oldItem.name}`,
title: `🛠 ${itemTranslation.name}`,
thumbnail: {
url: oldItem.thumbnailUrl,
height: 80,
@ -171,7 +172,7 @@ const resolvers = {
setItemExplicitlyBodySpecific: async (
_,
{ itemId, explicitlyBodySpecific, supportSecret },
{ itemLoader, db },
{ itemLoader, itemTranslationLoader, db },
) => {
assertSupportSecretOrThrow(supportSecret);
@ -192,6 +193,7 @@ const resolvers = {
if (process.env["SUPPORT_TOOLS_DISCORD_WEBHOOK_URL"]) {
try {
const itemTranslation = await itemTranslationLoader.load(itemId);
const oldRuleName = oldItem.explicitlyBodySpecific
? "Body specific"
: "Auto-detect";
@ -201,7 +203,7 @@ const resolvers = {
await logToDiscord({
embeds: [
{
title: `🛠 ${oldItem.name}`,
title: `🛠 ${itemTranslation.name}`,
thumbnail: {
url: oldItem.thumbnailUrl,
height: 80,
@ -231,7 +233,7 @@ const resolvers = {
setItemIsManuallyNc: async (
_,
{ itemId, isManuallyNc, supportSecret },
{ itemLoader, db },
{ itemLoader, itemTranslationLoader, db },
) => {
assertSupportSecretOrThrow(supportSecret);
@ -252,6 +254,7 @@ const resolvers = {
if (process.env["SUPPORT_TOOLS_DISCORD_WEBHOOK_URL"]) {
try {
const itemTranslation = await itemTranslationLoader.load(itemId);
const oldRuleName = oldItem.isManuallyNc
? "Manually set: Yes"
: "Auto-detect";
@ -261,7 +264,7 @@ const resolvers = {
await logToDiscord({
embeds: [
{
title: `🛠 ${oldItem.name}`,
title: `🛠 ${itemTranslation.name}`,
thumbnail: {
url: oldItem.thumbnailUrl,
height: 80,
@ -291,7 +294,7 @@ const resolvers = {
setLayerBodyId: async (
_,
{ layerId, bodyId, supportSecret },
{ itemLoader, swfAssetLoader, zoneLoader, db },
{ itemLoader, itemTranslationLoader, swfAssetLoader, zoneLoader, db },
) => {
assertSupportSecretOrThrow(supportSecret);
@ -335,17 +338,19 @@ const resolvers = {
)
.then(([rows]) => normalizeRow(rows[0]).parentId);
const [item, zone, oldBodyName, newBodyName] = await Promise.all([
itemLoader.load(itemId),
zoneLoader.load(oldSwfAsset.zoneId),
loadBodyName(oldSwfAsset.bodyId, db),
loadBodyName(bodyId, db),
]);
const [item, itemTranslation, zone, oldBodyName, newBodyName] =
await Promise.all([
itemLoader.load(itemId),
itemTranslationLoader.load(itemId),
zoneLoader.load(oldSwfAsset.zoneId),
loadBodyName(oldSwfAsset.bodyId, db),
loadBodyName(bodyId, db),
]);
await logToDiscord({
embeds: [
{
title: `🛠 ${item.name}`,
title: `🛠 ${itemTranslation.name}`,
thumbnail: {
url: item.thumbnailUrl,
height: 80,
@ -379,6 +384,7 @@ const resolvers = {
{ layerId, knownGlitches, supportSecret },
{
itemLoader,
itemTranslationLoader,
swfAssetLoader,
zoneLoader,
petStateLoader,
@ -433,15 +439,16 @@ const resolvers = {
.then(([rows]) => normalizeRow(rows[0]));
if (parentType === "Item") {
const [item, zone] = await Promise.all([
const [item, itemTranslation, zone] = await Promise.all([
itemLoader.load(parentId),
itemTranslationLoader.load(parentId),
zoneLoader.load(oldSwfAsset.zoneId),
]);
await logToDiscord({
embeds: [
{
title: `🛠 ${item.name}`,
title: `🛠 ${itemTranslation.name}`,
thumbnail: {
url: item.thumbnailUrl,
height: 80,
@ -516,7 +523,7 @@ const resolvers = {
bulkAddLayersToItem: async (
_,
{ itemId, entries, supportSecret },
{ itemLoader, db },
{ itemLoader, itemTranslationLoader, db },
) => {
assertSupportSecretOrThrow(supportSecret);
@ -555,10 +562,12 @@ const resolvers = {
if (process.env["SUPPORT_TOOLS_DISCORD_WEBHOOK_URL"]) {
try {
const itemTranslation = await itemTranslationLoader.load(itemId);
await logToDiscord({
embeds: [
{
title: `🛠 ${item.name}`,
title: `🛠 ${itemTranslation.name}`,
thumbnail: {
url: item.thumbnailUrl,
height: 80,
@ -588,7 +597,7 @@ const resolvers = {
removeLayerFromItem: async (
_,
{ layerId, itemId, supportSecret },
{ itemLoader, swfAssetLoader, zoneLoader, db },
{ itemLoader, itemTranslationLoader, swfAssetLoader, zoneLoader, db },
) => {
assertSupportSecretOrThrow(supportSecret);
@ -611,8 +620,9 @@ const resolvers = {
if (process.env["SUPPORT_TOOLS_DISCORD_WEBHOOK_URL"]) {
try {
const [item, zone, bodyName] = await Promise.all([
const [item, itemTranslation, zone, bodyName] = await Promise.all([
itemLoader.load(itemId),
itemTranslationLoader.load(itemId),
zoneLoader.load(oldSwfAsset.zoneId),
loadBodyName(oldSwfAsset.bodyId, db),
]);
@ -620,7 +630,7 @@ const resolvers = {
await logToDiscord({
embeds: [
{
title: `🛠 ${item.name}`,
title: `🛠 ${itemTranslation.name}`,
thumbnail: {
url: item.thumbnailUrl,
height: 80,

View file

@ -27,7 +27,7 @@ const resolvers = {
petAppearance: async (
{ name, customPetData, petMetaData },
_,
{ petTypeBySpeciesAndColorLoader, petStatesForPetTypeLoader },
{ petTypeBySpeciesAndColorLoader, petStatesForPetTypeLoader }
) => {
if (customPetData == null) {
return null;
@ -46,7 +46,7 @@ const resolvers = {
// First, look for a pet state containing exactly the same assets as this
// one.
const swfAssetIdsString = Object.values(
customPetData.custom_pet.biology_by_zone,
customPetData.custom_pet.biology_by_zone
)
.map((b) => b.part_id)
.sort((a, b) => Number(a) - Number(b))
@ -69,7 +69,7 @@ const resolvers = {
console.warn(
`Warning: For pet "${name}", fell back to pet state ${petState.id} ` +
`because it matches pose ${pose}. Actual pet state for these ` +
`assets not found: ${swfAssetIdsString}`,
`assets not found: ${swfAssetIdsString}`
);
return { id: petState.id };
}
@ -83,7 +83,7 @@ const resolvers = {
console.warn(
`Warning: For pet "${name}", fell back to pet state ${petState.id} ` +
`as an UNKNOWN fallback pose. Actual pet state for these ` +
`assets not found: ${swfAssetIdsString}`,
`assets not found: ${swfAssetIdsString}`
);
return { id: petState.id };
}
@ -93,7 +93,7 @@ const resolvers = {
// hasn't been saved yet.)
throw new Error(
`This pet's modeling data isn't loaded into our database yet, ` +
`sorry! Try using the Modeling Hub on Classic DTI to upload it first?`,
`sorry! Try using the Modeling Hub on Classic DTI to upload it first?`
);
},
wornItems: ({ customPetData }) => {
@ -119,8 +119,9 @@ const resolvers = {
petTypeBySpeciesAndColorLoader,
petStateByPetTypeAndAssetsLoader,
itemLoader,
itemTranslationLoader,
swfAssetByRemoteIdLoader,
},
}
) => {
const [customPetData, petMetaData] = await Promise.all([
loadCustomPetData(petName),
@ -140,6 +141,7 @@ const resolvers = {
petTypeBySpeciesAndColorLoader,
petStateByPetTypeAndAssetsLoader,
itemLoader,
itemTranslationLoader,
swfAssetByRemoteIdLoader,
});
}
@ -170,7 +172,7 @@ function getPoseFromPetData(petMetaData, petCustomData) {
throw new Error(
`could not identify pose: ` +
`moodId=${moodId}, ` +
`genderId=${genderId}`,
`genderId=${genderId}`
);
}
}