add itemByName and itemsByName GQL
This commit is contained in:
parent
d701f51c15
commit
f621391446
4 changed files with 118 additions and 0 deletions
|
@ -147,6 +147,37 @@ const buildItemTranslationLoader = (db) =>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const buildItemByNameLoader = (db, loaders) =>
|
||||||
|
new DataLoader(async (names) => {
|
||||||
|
const qs = names.map((_) => "?").join(", ");
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
names
|
||||||
|
);
|
||||||
|
|
||||||
|
const entities = rows.map((row) => {
|
||||||
|
const item = normalizeRow(row.items);
|
||||||
|
const itemTranslation = normalizeRow(row.item_translations);
|
||||||
|
loaders.itemLoader.prime(item.id, item);
|
||||||
|
loaders.itemTranslationLoader.prime(item.id, itemTranslation);
|
||||||
|
return { item, itemTranslation };
|
||||||
|
});
|
||||||
|
|
||||||
|
return names.map((name) =>
|
||||||
|
entities.find(
|
||||||
|
(e) =>
|
||||||
|
e.itemTranslation.name.trim().toLowerCase() ===
|
||||||
|
name.trim().toLowerCase()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const buildItemSearchLoader = (db, loaders) =>
|
const buildItemSearchLoader = (db, loaders) =>
|
||||||
new DataLoader(async (queries) => {
|
new DataLoader(async (queries) => {
|
||||||
// This isn't actually optimized as a batch query, we're just using a
|
// This isn't actually optimized as a batch query, we're just using a
|
||||||
|
@ -759,6 +790,7 @@ function buildLoaders(db) {
|
||||||
loaders.colorTranslationLoader = buildColorTranslationLoader(db);
|
loaders.colorTranslationLoader = buildColorTranslationLoader(db);
|
||||||
loaders.itemLoader = buildItemLoader(db);
|
loaders.itemLoader = buildItemLoader(db);
|
||||||
loaders.itemTranslationLoader = buildItemTranslationLoader(db);
|
loaders.itemTranslationLoader = buildItemTranslationLoader(db);
|
||||||
|
loaders.itemByNameLoader = buildItemByNameLoader(db, loaders);
|
||||||
loaders.itemSearchLoader = buildItemSearchLoader(db, loaders);
|
loaders.itemSearchLoader = buildItemSearchLoader(db, loaders);
|
||||||
loaders.itemSearchToFitLoader = buildItemSearchToFitLoader(db, loaders);
|
loaders.itemSearchToFitLoader = buildItemSearchToFitLoader(db, loaders);
|
||||||
loaders.itemsThatNeedModelsLoader = buildItemsThatNeedModelsLoader(db);
|
loaders.itemsThatNeedModelsLoader = buildItemsThatNeedModelsLoader(db);
|
||||||
|
|
|
@ -104,6 +104,29 @@ describe("Item", () => {
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("loads items by name", async () => {
|
||||||
|
const res = await query({
|
||||||
|
query: gql`
|
||||||
|
query {
|
||||||
|
itemByName(name: "Moon and Stars Background") {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
thumbnailUrl
|
||||||
|
}
|
||||||
|
itemsByName(names: ["Zafara Agent Robe", "pile of dung"]) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
thumbnailUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res).toHaveNoErrors();
|
||||||
|
expect(res.data).toMatchSnapshot("data");
|
||||||
|
expect(getDbCalls()).toMatchSnapshot("db");
|
||||||
|
});
|
||||||
|
|
||||||
it("loads appearance data", async () => {
|
it("loads appearance data", async () => {
|
||||||
const res = await query({
|
const res = await query({
|
||||||
query: gql`
|
query: gql`
|
||||||
|
|
|
@ -724,6 +724,51 @@ Object {
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`Item loads items by name: data 1`] = `
|
||||||
|
Object {
|
||||||
|
"itemByName": Object {
|
||||||
|
"id": "37375",
|
||||||
|
"name": "Moon and Stars Background",
|
||||||
|
"thumbnailUrl": "http://images.neopets.com/items/bg_moonstars.gif",
|
||||||
|
},
|
||||||
|
"itemsByName": Array [
|
||||||
|
Object {
|
||||||
|
"id": "38912",
|
||||||
|
"name": "Zafara Agent Robe",
|
||||||
|
"thumbnailUrl": "http://images.neopets.com/items/clo_zafara_agent_robe.gif",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "18579",
|
||||||
|
"name": "Pile of Dung",
|
||||||
|
"thumbnailUrl": "http://images.neopets.com/items/med_booby_5.gif",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Item loads items by name: db 1`] = `
|
||||||
|
Array [
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"nestTables": true,
|
||||||
|
"sql": "SELECT items.*, item_translations.* FROM item_translations
|
||||||
|
INNER JOIN items ON items.id = item_translations.item_id
|
||||||
|
WHERE name IN (?, ?, ?) AND locale = \\"en\\"",
|
||||||
|
"values": Array [
|
||||||
|
"Moon and Stars Background",
|
||||||
|
"Zafara Agent Robe",
|
||||||
|
"pile of dung",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Array [
|
||||||
|
"Moon and Stars Background",
|
||||||
|
"Zafara Agent Robe",
|
||||||
|
"pile of dung",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`Item loads items that need models 1`] = `
|
exports[`Item loads items that need models 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"babyItems": Array [
|
"babyItems": Array [
|
||||||
|
|
|
@ -78,6 +78,16 @@ const typeDefs = gql`
|
||||||
extend type Query {
|
extend type Query {
|
||||||
item(id: ID!): Item
|
item(id: ID!): Item
|
||||||
items(ids: [ID!]!): [Item!]!
|
items(ids: [ID!]!): [Item!]!
|
||||||
|
|
||||||
|
# Find items by name. Exact match, except for some tweaks, like
|
||||||
|
# case-insensitivity and trimming extra whitespace. Null if not found.
|
||||||
|
#
|
||||||
|
# NOTE: These aren't used in DTI at time of writing; they're a courtesy API
|
||||||
|
# for the /r/Neopets Discord bot's outfit preview command!
|
||||||
|
itemByName(name: String!): Item
|
||||||
|
itemsByName(names: [String!]!): [Item]!
|
||||||
|
|
||||||
|
# Search for items with fuzzy matching.
|
||||||
itemSearch(query: String!): ItemSearchResult!
|
itemSearch(query: String!): ItemSearchResult!
|
||||||
itemSearchToFit(
|
itemSearchToFit(
|
||||||
query: String!
|
query: String!
|
||||||
|
@ -271,6 +281,14 @@ const resolvers = {
|
||||||
items: (_, { ids }) => {
|
items: (_, { ids }) => {
|
||||||
return ids.map((id) => ({ id }));
|
return ids.map((id) => ({ id }));
|
||||||
},
|
},
|
||||||
|
itemByName: async (_, { name }, { itemByNameLoader }) => {
|
||||||
|
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));
|
||||||
|
},
|
||||||
itemSearch: async (_, { query }, { itemSearchLoader }) => {
|
itemSearch: async (_, { query }, { itemSearchLoader }) => {
|
||||||
const items = await itemSearchLoader.load(query.trim());
|
const items = await itemSearchLoader.load(query.trim());
|
||||||
return { query, items };
|
return { query, items };
|
||||||
|
|
Loading…
Reference in a new issue