Compare commits
8 commits
ca7138d755
...
c3529450bf
| Author | SHA1 | Date | |
|---|---|---|---|
| c3529450bf | |||
| 8948b6567e | |||
| f196e2d8fe | |||
| 28ddc3455b | |||
| c455617362 | |||
| 62531cfe34 | |||
| 6686f13154 | |||
| 1cef0bea20 |
14 changed files with 98 additions and 249 deletions
|
|
@ -82,7 +82,6 @@
|
|||
"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",
|
||||
|
|
|
|||
|
|
@ -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,11 +58,8 @@ async function loadAllNcItemNamesAndIds() {
|
|||
const db = await connectToDb();
|
||||
|
||||
const [rows] = await db.query(`
|
||||
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"
|
||||
SELECT items.id, items.name FROM items
|
||||
WHERE items.rarity_index IN (0, 500) OR is_manually_nc = 1
|
||||
`);
|
||||
|
||||
return rows.map(({ id, name }) => ({ id, name: normalizeItemName(name) }));
|
||||
|
|
@ -75,13 +72,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}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -137,7 +134,7 @@ function normalizeItemName(name) {
|
|||
async function handleWithBeeline(req, res) {
|
||||
beeline.withTrace(
|
||||
{ name: "api/allNCTradeValues", operation_name: "api/allNCTradeValues" },
|
||||
() => handle(req, res)
|
||||
() => handle(req, res),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -115,8 +115,7 @@ async function handle(req, res) {
|
|||
|
||||
if (process.env["SUPPORT_TOOLS_DISCORD_WEBHOOK_URL"]) {
|
||||
try {
|
||||
const { itemLoader, itemTranslationLoader, zoneLoader } =
|
||||
buildLoaders(db);
|
||||
const { itemLoader, zoneLoader } = buildLoaders(db);
|
||||
|
||||
// Copied from setLayerBodyId mutation
|
||||
const itemId = await db
|
||||
|
|
@ -127,9 +126,8 @@ async function handle(req, res) {
|
|||
)
|
||||
.then(([rows]) => normalizeRow(rows[0]).parentId);
|
||||
|
||||
const [item, itemTranslation, zone, bodyName] = await Promise.all([
|
||||
const [item, zone, bodyName] = await Promise.all([
|
||||
itemLoader.load(itemId),
|
||||
itemTranslationLoader.load(itemId),
|
||||
zoneLoader.load(layer.zoneId),
|
||||
loadBodyName(layer.bodyId, db),
|
||||
]);
|
||||
|
|
@ -137,7 +135,7 @@ async function handle(req, res) {
|
|||
await logToDiscord({
|
||||
embeds: [
|
||||
{
|
||||
title: `🛠 ${itemTranslation.name}`,
|
||||
title: `🛠 ${item.name}`,
|
||||
thumbnail: {
|
||||
url: item.thumbnailUrl,
|
||||
height: 80,
|
||||
|
|
|
|||
|
|
@ -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 item_translations \
|
||||
&& yarn run --silent mysqldump openneo_impress alt_styles items \
|
||||
parents_swf_assets pet_states pet_types swf_assets \
|
||||
| gzip -c \
|
||||
> $(dirname $0)/../public-data-from-modeling.sql.gz
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
yarn run --silent mysqldump --no-data openneo_impress closet_hangers closet_lists \
|
||||
colors items item_translations modeling_logs parents_swf_assets pet_types \
|
||||
pet_states species swf_assets users zones \
|
||||
colors items 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 \
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
-- MariaDB dump 10.19 Distrib 10.6.16-MariaDB, for debian-linux-gnu (x86_64)
|
||||
--
|
||||
-- Host: legacy.impress.openneo.net Database: openneo_impress
|
||||
-- Host: impress.openneo.net Database: openneo_impress
|
||||
-- ------------------------------------------------------
|
||||
-- Server version 5.5.62-0ubuntu0.14.04.1-log
|
||||
-- Server version 10.6.16-MariaDB-0ubuntu0.22.04.1
|
||||
|
||||
/*!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;
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=56 DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci;
|
||||
/*!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;
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=119 DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci;
|
||||
/*!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;
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=53 DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci;
|
||||
/*!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-17 12:38:11
|
||||
-- Dump completed on 2024-02-20 15:59:16
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -7,7 +7,6 @@ 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;
|
||||
|
|
@ -18,7 +17,6 @@ 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;
|
||||
|
|
|
|||
|
|
@ -1,72 +0,0 @@
|
|||
/**
|
||||
* 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());
|
||||
|
|
@ -224,55 +224,27 @@ 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.
|
||||
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,
|
||||
},
|
||||
// NOTE: In our MySQL schema, this is a case-insensitive exact search.
|
||||
`SELECT * FROM items WHERE name IN (${qs})`,
|
||||
normalizedNames,
|
||||
);
|
||||
|
||||
const entitiesByName = new Map();
|
||||
for (const row of rows) {
|
||||
const item = normalizeRow(row.items);
|
||||
const itemTranslation = normalizeRow(row.item_translations);
|
||||
const item = normalizeRow(row);
|
||||
loaders.itemLoader.prime(item.id, item);
|
||||
loaders.itemTranslationLoader.prime(item.id, itemTranslation);
|
||||
|
||||
const normalizedName = itemTranslation.name.trim().toLowerCase();
|
||||
entitiesByName.set(normalizedName, { item, itemTranslation });
|
||||
const normalizedName = item.name.trim().toLowerCase();
|
||||
entitiesByName.set(normalizedName, item);
|
||||
}
|
||||
|
||||
return normalizedNames.map(
|
||||
(name) =>
|
||||
entitiesByName.get(name) || { item: null, itemTranslation: null },
|
||||
);
|
||||
return normalizedNames.map((name) => entitiesByName.get(name) || null);
|
||||
},
|
||||
{ cacheKeyFn: (name) => name.trim().toLowerCase() },
|
||||
);
|
||||
|
|
@ -308,7 +280,9 @@ function buildItemSearchConditions({
|
|||
continue;
|
||||
}
|
||||
|
||||
const condition = isNegative ? "t.name NOT LIKE ?" : "t.name LIKE ?";
|
||||
const condition = isNegative
|
||||
? "items.name NOT LIKE ?"
|
||||
: "items.name LIKE ?";
|
||||
const matcher = "%" + word.replace(/_%/g, "\\$0") + "%";
|
||||
|
||||
wordMatchConditions.push(condition);
|
||||
|
|
@ -336,7 +310,6 @@ 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
|
||||
|
|
@ -346,7 +319,7 @@ function buildItemSearchConditions({
|
|||
const queryConditions = `
|
||||
(${wordMatchCondition}) AND (${bodyIdCondition}) AND
|
||||
(${zoneIdsCondition}) AND (${itemKindCondition}) AND
|
||||
(${currentUserCondition}) AND t.locale = "en"
|
||||
(${currentUserCondition})
|
||||
`;
|
||||
const queryConditionValues = [
|
||||
...wordMatchValues,
|
||||
|
|
@ -430,10 +403,10 @@ const buildItemSearchItemsLoader = (db, loaders) =>
|
|||
|
||||
const [rows] = await db.execute(
|
||||
`
|
||||
SELECT DISTINCT items.*, t.name FROM items
|
||||
SELECT DISTINCT items.* FROM items
|
||||
${queryJoins}
|
||||
WHERE ${queryConditions}
|
||||
ORDER BY t.name
|
||||
ORDER BY items.name
|
||||
LIMIT ? OFFSET ?
|
||||
`,
|
||||
[...queryConditionValues, actualLimit, actualOffset],
|
||||
|
|
@ -514,8 +487,7 @@ 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
|
||||
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%"
|
||||
WHERE items.modeling_status_hint IS NULL AND items.name NOT LIKE "%MME%"
|
||||
AND ${itemIdsCondition}
|
||||
ORDER BY item_id
|
||||
) T_ITEMS
|
||||
|
|
@ -1290,10 +1262,8 @@ const buildUserClosetHangersLoader = (db) =>
|
|||
new DataLoader(async (userIds) => {
|
||||
const qs = userIds.map((_) => "?").join(",");
|
||||
const [rows] = await db.execute(
|
||||
`SELECT closet_hangers.*, item_translations.name as item_name FROM closet_hangers
|
||||
`SELECT closet_hangers.*, items.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,
|
||||
|
|
@ -1482,7 +1452,6 @@ 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);
|
||||
|
|
|
|||
|
|
@ -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,46 +172,30 @@ async function savePetTypeAndStateModelingData(
|
|||
}
|
||||
|
||||
async function saveItemModelingData(customPetData, context) {
|
||||
const { db, itemLoader, itemTranslationLoader, addToModelingLogs } = context;
|
||||
const { db, itemLoader, 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 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,
|
||||
}),
|
||||
]);
|
||||
await syncToDb(db, incomingItems, {
|
||||
loader: itemLoader,
|
||||
tableName: "items",
|
||||
buildLoaderKey: (row) => row.id,
|
||||
buildUpdateCondition: (row) => [`id = ?`, row.id],
|
||||
addToModelingLogs,
|
||||
});
|
||||
}
|
||||
|
||||
async function saveSwfAssetModelingData(customPetData, context) {
|
||||
|
|
@ -313,7 +297,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
|
||||
|
|
@ -327,7 +311,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({
|
||||
|
|
@ -364,7 +348,7 @@ async function syncToDb(
|
|||
includeUpdatedAt = true,
|
||||
afterInsert = null,
|
||||
addToModelingLogs,
|
||||
}
|
||||
},
|
||||
) {
|
||||
const loaderKeys = incomingRows.map(buildLoaderKey);
|
||||
const currentRows = await loader.loadMany(loaderKeys);
|
||||
|
|
@ -445,7 +429,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(", ");
|
||||
|
|
@ -453,7 +437,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);
|
||||
|
|
@ -469,15 +453,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);
|
||||
|
|
|
|||
|
|
@ -316,15 +316,15 @@ const typeDefs = gql`
|
|||
|
||||
const resolvers = {
|
||||
Item: {
|
||||
name: async ({ id, name }, _, { itemTranslationLoader }) => {
|
||||
name: async ({ id, name }, _, { itemLoader }) => {
|
||||
if (name) return name;
|
||||
const translation = await itemTranslationLoader.load(id);
|
||||
return translation.name;
|
||||
const item = await itemLoader.load(id);
|
||||
return item.name;
|
||||
},
|
||||
description: async ({ id, description }, _, { itemTranslationLoader }) => {
|
||||
description: async ({ id, description }, _, { itemLoader }) => {
|
||||
if (description) return description;
|
||||
const translation = await itemTranslationLoader.load(id);
|
||||
return translation.description;
|
||||
const item = await itemLoader.load(id);
|
||||
return item.description;
|
||||
},
|
||||
thumbnailUrl: async ({ id, thumbnailUrl }, _, { itemLoader }) => {
|
||||
if (thumbnailUrl) return thumbnailUrl;
|
||||
|
|
@ -340,15 +340,9 @@ const resolvers = {
|
|||
const item = await itemLoader.load(id);
|
||||
return isNC(item);
|
||||
},
|
||||
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(
|
||||
isPb: async ({ id }, _, { itemLoader }) => {
|
||||
const item = await itemLoader.load(id);
|
||||
return item.description.includes(
|
||||
"This item is part of a deluxe paint brush set!",
|
||||
);
|
||||
},
|
||||
|
|
@ -360,20 +354,14 @@ const resolvers = {
|
|||
// This feature is deprecated, so now we just always return unknown value.
|
||||
return null;
|
||||
},
|
||||
ncTradeValueText: async (
|
||||
{ id },
|
||||
_,
|
||||
{ itemLoader, itemTranslationLoader },
|
||||
) => {
|
||||
ncTradeValueText: async ({ id }, _, { itemLoader }) => {
|
||||
// Skip this lookup for non-NC items, as a perf optimization.
|
||||
const item = await itemLoader.load(id);
|
||||
if (!isNC(item)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the item name, which is how we look things up in ~owls.
|
||||
const itemTranslation = await itemTranslationLoader.load(id);
|
||||
let itemName = itemTranslation.name;
|
||||
let itemName = item.name;
|
||||
|
||||
// HACK: The name "Butterfly Dress" is used for two different items.
|
||||
// Here's what ~owls does to distinguish!
|
||||
|
|
@ -755,12 +743,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 (
|
||||
_,
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ const resolvers = {
|
|||
setManualSpecialColor: async (
|
||||
_,
|
||||
{ itemId, colorId, supportSecret },
|
||||
{ itemLoader, itemTranslationLoader, colorLoader, db },
|
||||
{ itemLoader, colorLoader, db },
|
||||
) => {
|
||||
assertSupportSecretOrThrow(supportSecret);
|
||||
|
||||
|
|
@ -125,8 +125,7 @@ const resolvers = {
|
|||
|
||||
if (process.env["SUPPORT_TOOLS_DISCORD_WEBHOOK_URL"]) {
|
||||
try {
|
||||
const [itemTranslation, oldColor, newColor] = await Promise.all([
|
||||
itemTranslationLoader.load(itemId),
|
||||
const [oldColor, newColor] = await Promise.all([
|
||||
oldItem.manualSpecialColorId
|
||||
? colorLoader.load(oldItem.manualSpecialColorId)
|
||||
: Promise.resolve(null),
|
||||
|
|
@ -142,7 +141,7 @@ const resolvers = {
|
|||
await logToDiscord({
|
||||
embeds: [
|
||||
{
|
||||
title: `🛠 ${itemTranslation.name}`,
|
||||
title: `🛠 ${oldItem.name}`,
|
||||
thumbnail: {
|
||||
url: oldItem.thumbnailUrl,
|
||||
height: 80,
|
||||
|
|
@ -172,7 +171,7 @@ const resolvers = {
|
|||
setItemExplicitlyBodySpecific: async (
|
||||
_,
|
||||
{ itemId, explicitlyBodySpecific, supportSecret },
|
||||
{ itemLoader, itemTranslationLoader, db },
|
||||
{ itemLoader, db },
|
||||
) => {
|
||||
assertSupportSecretOrThrow(supportSecret);
|
||||
|
||||
|
|
@ -193,7 +192,6 @@ 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";
|
||||
|
|
@ -203,7 +201,7 @@ const resolvers = {
|
|||
await logToDiscord({
|
||||
embeds: [
|
||||
{
|
||||
title: `🛠 ${itemTranslation.name}`,
|
||||
title: `🛠 ${oldItem.name}`,
|
||||
thumbnail: {
|
||||
url: oldItem.thumbnailUrl,
|
||||
height: 80,
|
||||
|
|
@ -233,7 +231,7 @@ const resolvers = {
|
|||
setItemIsManuallyNc: async (
|
||||
_,
|
||||
{ itemId, isManuallyNc, supportSecret },
|
||||
{ itemLoader, itemTranslationLoader, db },
|
||||
{ itemLoader, db },
|
||||
) => {
|
||||
assertSupportSecretOrThrow(supportSecret);
|
||||
|
||||
|
|
@ -254,7 +252,6 @@ 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";
|
||||
|
|
@ -264,7 +261,7 @@ const resolvers = {
|
|||
await logToDiscord({
|
||||
embeds: [
|
||||
{
|
||||
title: `🛠 ${itemTranslation.name}`,
|
||||
title: `🛠 ${oldItem.name}`,
|
||||
thumbnail: {
|
||||
url: oldItem.thumbnailUrl,
|
||||
height: 80,
|
||||
|
|
@ -294,7 +291,7 @@ const resolvers = {
|
|||
setLayerBodyId: async (
|
||||
_,
|
||||
{ layerId, bodyId, supportSecret },
|
||||
{ itemLoader, itemTranslationLoader, swfAssetLoader, zoneLoader, db },
|
||||
{ itemLoader, swfAssetLoader, zoneLoader, db },
|
||||
) => {
|
||||
assertSupportSecretOrThrow(supportSecret);
|
||||
|
||||
|
|
@ -338,19 +335,17 @@ const resolvers = {
|
|||
)
|
||||
.then(([rows]) => normalizeRow(rows[0]).parentId);
|
||||
|
||||
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),
|
||||
]);
|
||||
const [item, zone, oldBodyName, newBodyName] = await Promise.all([
|
||||
itemLoader.load(itemId),
|
||||
zoneLoader.load(oldSwfAsset.zoneId),
|
||||
loadBodyName(oldSwfAsset.bodyId, db),
|
||||
loadBodyName(bodyId, db),
|
||||
]);
|
||||
|
||||
await logToDiscord({
|
||||
embeds: [
|
||||
{
|
||||
title: `🛠 ${itemTranslation.name}`,
|
||||
title: `🛠 ${item.name}`,
|
||||
thumbnail: {
|
||||
url: item.thumbnailUrl,
|
||||
height: 80,
|
||||
|
|
@ -384,7 +379,6 @@ const resolvers = {
|
|||
{ layerId, knownGlitches, supportSecret },
|
||||
{
|
||||
itemLoader,
|
||||
itemTranslationLoader,
|
||||
swfAssetLoader,
|
||||
zoneLoader,
|
||||
petStateLoader,
|
||||
|
|
@ -439,16 +433,15 @@ const resolvers = {
|
|||
.then(([rows]) => normalizeRow(rows[0]));
|
||||
|
||||
if (parentType === "Item") {
|
||||
const [item, itemTranslation, zone] = await Promise.all([
|
||||
const [item, zone] = await Promise.all([
|
||||
itemLoader.load(parentId),
|
||||
itemTranslationLoader.load(parentId),
|
||||
zoneLoader.load(oldSwfAsset.zoneId),
|
||||
]);
|
||||
|
||||
await logToDiscord({
|
||||
embeds: [
|
||||
{
|
||||
title: `🛠 ${itemTranslation.name}`,
|
||||
title: `🛠 ${item.name}`,
|
||||
thumbnail: {
|
||||
url: item.thumbnailUrl,
|
||||
height: 80,
|
||||
|
|
@ -523,7 +516,7 @@ const resolvers = {
|
|||
bulkAddLayersToItem: async (
|
||||
_,
|
||||
{ itemId, entries, supportSecret },
|
||||
{ itemLoader, itemTranslationLoader, db },
|
||||
{ itemLoader, db },
|
||||
) => {
|
||||
assertSupportSecretOrThrow(supportSecret);
|
||||
|
||||
|
|
@ -562,12 +555,10 @@ const resolvers = {
|
|||
|
||||
if (process.env["SUPPORT_TOOLS_DISCORD_WEBHOOK_URL"]) {
|
||||
try {
|
||||
const itemTranslation = await itemTranslationLoader.load(itemId);
|
||||
|
||||
await logToDiscord({
|
||||
embeds: [
|
||||
{
|
||||
title: `🛠 ${itemTranslation.name}`,
|
||||
title: `🛠 ${item.name}`,
|
||||
thumbnail: {
|
||||
url: item.thumbnailUrl,
|
||||
height: 80,
|
||||
|
|
@ -597,7 +588,7 @@ const resolvers = {
|
|||
removeLayerFromItem: async (
|
||||
_,
|
||||
{ layerId, itemId, supportSecret },
|
||||
{ itemLoader, itemTranslationLoader, swfAssetLoader, zoneLoader, db },
|
||||
{ itemLoader, swfAssetLoader, zoneLoader, db },
|
||||
) => {
|
||||
assertSupportSecretOrThrow(supportSecret);
|
||||
|
||||
|
|
@ -620,9 +611,8 @@ const resolvers = {
|
|||
|
||||
if (process.env["SUPPORT_TOOLS_DISCORD_WEBHOOK_URL"]) {
|
||||
try {
|
||||
const [item, itemTranslation, zone, bodyName] = await Promise.all([
|
||||
const [item, zone, bodyName] = await Promise.all([
|
||||
itemLoader.load(itemId),
|
||||
itemTranslationLoader.load(itemId),
|
||||
zoneLoader.load(oldSwfAsset.zoneId),
|
||||
loadBodyName(oldSwfAsset.bodyId, db),
|
||||
]);
|
||||
|
|
@ -630,7 +620,7 @@ const resolvers = {
|
|||
await logToDiscord({
|
||||
embeds: [
|
||||
{
|
||||
title: `🛠 ${itemTranslation.name}`,
|
||||
title: `🛠 ${item.name}`,
|
||||
thumbnail: {
|
||||
url: item.thumbnailUrl,
|
||||
height: 80,
|
||||
|
|
|
|||
|
|
@ -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,9 +119,8 @@ const resolvers = {
|
|||
petTypeBySpeciesAndColorLoader,
|
||||
petStateByPetTypeAndAssetsLoader,
|
||||
itemLoader,
|
||||
itemTranslationLoader,
|
||||
swfAssetByRemoteIdLoader,
|
||||
}
|
||||
},
|
||||
) => {
|
||||
const [customPetData, petMetaData] = await Promise.all([
|
||||
loadCustomPetData(petName),
|
||||
|
|
@ -141,7 +140,6 @@ const resolvers = {
|
|||
petTypeBySpeciesAndColorLoader,
|
||||
petStateByPetTypeAndAssetsLoader,
|
||||
itemLoader,
|
||||
itemTranslationLoader,
|
||||
swfAssetByRemoteIdLoader,
|
||||
});
|
||||
}
|
||||
|
|
@ -172,7 +170,7 @@ function getPoseFromPetData(petMetaData, petCustomData) {
|
|||
throw new Error(
|
||||
`could not identify pose: ` +
|
||||
`moodId=${moodId}, ` +
|
||||
`genderId=${genderId}`
|
||||
`genderId=${genderId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue