diff --git a/pages/api/allWakaValues.js b/pages/api/allWakaValues.js index 677d61a..5894eeb 100644 --- a/pages/api/allWakaValues.js +++ b/pages/api/allWakaValues.js @@ -8,127 +8,15 @@ const beeline = require("honeycomb-beeline")({ disableInstrumentationOnLoad: true, }); -import fetch from "node-fetch"; - -import connectToDb from "../../src/server/db"; - async function handle(req, res) { - const allNcItemNamesAndIdsPromise = loadAllNcItemNamesAndIds(); - - let itemValuesByIdOrName; - try { - itemValuesByIdOrName = await loadWakaValuesByIdOrName(); - } catch (e) { - console.error(e); - res.setHeader("Content-Type", "text/plain; charset=utf8"); - res.status(500).send("Error loading Waka data from Google Sheets API"); - return; - } - - // Restructure the value data to use IDs as keys, instead of names. - const allNcItemNamesAndIds = await allNcItemNamesAndIdsPromise; - const itemValues = {}; - for (const { name, id } of allNcItemNamesAndIds) { - if (id in itemValuesByIdOrName) { - itemValues[id] = itemValuesByIdOrName[id]; - } else if (name in itemValuesByIdOrName) { - itemValues[id] = itemValuesByIdOrName[name]; - } - } - - // Cache for 1 minute, and immediately serve stale data for a day after. - // This should keep it fast and responsive, and stay well within our API key - // limits. (This will cause the client to send more requests than necessary, - // but the CDN cache should generally respond quickly with a small 304 Not - // Modified, unless the data really did change.) - res.setHeader( - "Cache-Control", - "public, max-age=3600, stale-while-revalidate=86400" - ); - return res.send(itemValues); -} - -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" - `); - - return rows.map(({ id, name }) => ({ id, name: normalizeItemName(name) })); -} - -/** - * Load all Waka values from the spreadsheet. Returns an object keyed by ID or - * name - that is, if the item ID is provided in the sheet, we use that as the - * key; or if not, we use the name as the key. - */ -async function loadWakaValuesByIdOrName() { - if (!process.env["GOOGLE_API_KEY"]) { - throw new Error(`GOOGLE_API_KEY environment variable must be provided`); - } - - const res = await fetch( - `https://sheets.googleapis.com/v4/spreadsheets/` + - `1DRMrniTSZP0sgZK6OAFFYqpmbT6xY_Ve_i480zghOX0/values/NC%20Values` + - `?fields=values&key=${encodeURIComponent(process.env["GOOGLE_API_KEY"])}` - ); - const json = await res.json(); - - if (!res.ok) { - if (json.error) { - const { code, status, message } = json.error; - throw new Error( - `Google Sheets API returned error ${code} ${status}: ${message}` - ); - } else { - throw new Error( - `Google Sheets API returned unexpected error: ${res.status} ${res.statusText}` - ); - } - } - - // Get the rows from the JSON response - skipping the first-row headers. - const rows = json.values.slice(1); - - // Reformat the rows as a map from item name to value. We offer the item data - // as an object with a single field `value` for extensibility, but we omit - // the spreadsheet columns that we don't use on DTI, like Notes. - // - // NOTE: The Sheets API only returns the first non-empty cells of the row. - // That's why we set `""` as the defaults, in case the value/notes/etc - // aren't provided. - const itemValuesByIdOrName = {}; - for (const [ - itemName, - value = "", - unusedNotes = "", - unusedMarks = "", - itemId = "", - ] of rows) { - const normalizedItemName = normalizeItemName(itemName); - itemValuesByIdOrName[itemId || normalizedItemName] = { value }; - } - - return itemValuesByIdOrName; -} - -function normalizeItemName(name) { - return ( - name - // Remove all spaces, they're a common source of inconsistency - .replace(/\s+/g, "") - // Lower case, because capitalization is another common source - .toLowerCase() - // Remove diacritics: https://stackoverflow.com/a/37511463/107415 - // Waka has some stray ones in item names, not sure why! - .normalize("NFD") - .replace(/[\u0300-\u036f]/g, "") - ); + res.setHeader("Content-Type", "text/plain; charset=utf8"); + res + .status(410) + .send( + "WakaGuide.com is no longer updating its values, so we no longer " + + "serve them from this endpoint. The most recent set of values is " + + "archived here: https://docs.google.com/spreadsheets/d/1DRMrniTSZP0sgZK6OAFFYqpmbT6xY_Ve_i480zghOX0" + ); } async function handleWithBeeline(req, res) { diff --git a/src/server/loaders.js b/src/server/loaders.js index 1477c05..6c61e68 100644 --- a/src/server/loaders.js +++ b/src/server/loaders.js @@ -1,5 +1,4 @@ import DataLoader from "dataloader"; -import fetch from "node-fetch"; import { normalizeRow } from "./util"; const buildClosetListLoader = (db) => @@ -795,30 +794,6 @@ const buildItemTradesLoader = (db, loaders) => { cacheKeyFn: ({ itemId, isOwned }) => `${itemId}-${isOwned}` } ); -const buildItemWakaValueLoader = () => - new DataLoader(async (itemIds) => { - // This loader calls our /api/allWakaValues endpoint, to take advantage of - // the CDN caching. This helps us respond a bit faster than Google Sheets - // API would, and avoid putting pressure on our Google Sheets API quotas. - // (Some kind of internal memcache or process-level cache would be a more - // idiomatic solution in a monolith server environment!) - const url = process.env.VERCEL_URL - ? `https://${process.env.VERCEL_URL}/api/allWakaValues` - : process.env.NODE_ENV === "production" - ? "https://impress-2020.openneo.net/api/allWakaValues" - : "http://localhost:3000/api/allWakaValues"; - const res = await fetch(url); - if (!res.ok) { - throw new Error( - `Error loading /api/allWakaValues: ${res.status} ${res.statusText}` - ); - } - - const allWakaValues = await res.json(); - - return itemIds.map((itemId) => allWakaValues[itemId]); - }); - const buildPetTypeLoader = (db, loaders) => new DataLoader(async (petTypeIds) => { const qs = petTypeIds.map((_) => "?").join(","); @@ -1470,7 +1445,6 @@ function buildLoaders(db) { db ); loaders.itemTradesLoader = buildItemTradesLoader(db, loaders); - loaders.itemWakaValueLoader = buildItemWakaValueLoader(); loaders.petTypeLoader = buildPetTypeLoader(db, loaders); loaders.petTypeBySpeciesAndColorLoader = buildPetTypeBySpeciesAndColorLoader( db, diff --git a/src/server/types/Item.js b/src/server/types/Item.js index 1722dc9..ef6c700 100644 --- a/src/server/types/Item.js +++ b/src/server/types/Item.js @@ -28,9 +28,8 @@ const typeDefs = gql` createdAt: String """ - This item's capsule trade value as text, according to wakaguide.com, as a - human-readable string. Will be null if the value is not known, or if - there's an error connecting to the data source. + Deprecated: This item's capsule trade value as text, according to + wakaguide.com, as a human-readable string. **This now always returns null.** """ wakaValueText: String @cacheControl(maxAge: ${oneHour}) @@ -315,17 +314,9 @@ const resolvers = { const item = await itemLoader.load(id); return item.createdAt && item.createdAt.toISOString(); }, - wakaValueText: async ({ id }, _, { itemWakaValueLoader }) => { - let wakaValue; - try { - wakaValue = await itemWakaValueLoader.load(id); - } catch (e) { - console.error(`Error loading wakaValueText for item ${id}, skipping:`); - console.error(e); - wakaValue = null; - } - - return wakaValue ? wakaValue.value : null; + wakaValueText: () => { + // This feature is deprecated, so now we just always return unknown value. + return null; }, currentUserOwnsThis: async (