GraphQL for user's itemsTheyOwn
This commit is contained in:
parent
df49e08bff
commit
e2b5486168
4 changed files with 158 additions and 8 deletions
|
@ -19,6 +19,7 @@ GRANT UPDATE ON openneo_impress.pet_states TO impress2020;
|
||||||
GRANT UPDATE ON openneo_impress.swf_assets TO impress2020;
|
GRANT UPDATE ON openneo_impress.swf_assets TO impress2020;
|
||||||
|
|
||||||
-- User data tables
|
-- User data tables
|
||||||
|
GRANT SELECT ON openneo_impress.closet_hangers TO impress2020;
|
||||||
GRANT SELECT ON openneo_impress.item_outfit_relationships TO impress2020;
|
GRANT SELECT ON openneo_impress.item_outfit_relationships TO impress2020;
|
||||||
GRANT SELECT ON openneo_impress.outfits TO impress2020;
|
GRANT SELECT ON openneo_impress.outfits TO impress2020;
|
||||||
GRANT SELECT ON openneo_impress.users TO impress2020;
|
GRANT SELECT ON openneo_impress.users TO impress2020;
|
||||||
|
|
|
@ -229,6 +229,7 @@ const typeDefs = gql`
|
||||||
type User {
|
type User {
|
||||||
id: ID!
|
id: ID!
|
||||||
username: String!
|
username: String!
|
||||||
|
itemsTheyOwn: [Item!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
|
@ -622,13 +623,38 @@ const resolvers = {
|
||||||
const user = await userLoader.load(id);
|
const user = await userLoader.load(id);
|
||||||
return user.name;
|
return user.name;
|
||||||
},
|
},
|
||||||
|
itemsTheyOwn: async (
|
||||||
|
{ id },
|
||||||
|
_,
|
||||||
|
{ currentUserId, userLoader, userOwnedClosetHangersLoader }
|
||||||
|
) => {
|
||||||
|
const user = await userLoader.load(id);
|
||||||
|
const hangersAreVisible =
|
||||||
|
user.ownedClosetHangersVisibility >= 2 || user.id === currentUserId;
|
||||||
|
if (!hangersAreVisible) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const allClosetHangers = await userOwnedClosetHangersLoader.load(id);
|
||||||
|
const closetHangersWithNoList = allClosetHangers.filter(
|
||||||
|
(h) => h.listId == null
|
||||||
|
);
|
||||||
|
|
||||||
|
const items = closetHangersWithNoList.map((h) => ({
|
||||||
|
id: h.itemId,
|
||||||
|
// We get this for the ORDER BY clause anyway - may as well include it
|
||||||
|
// here to avoid an extra lookup!
|
||||||
|
name: h.itemName,
|
||||||
|
}));
|
||||||
|
return items;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Query: {
|
Query: {
|
||||||
allColors: async (_, { ids }, { colorLoader }) => {
|
allColors: async (_, __, { colorLoader }) => {
|
||||||
const allColors = await colorLoader.loadAll();
|
const allColors = await colorLoader.loadAll();
|
||||||
return allColors;
|
return allColors;
|
||||||
},
|
},
|
||||||
allSpecies: async (_, { ids }, { speciesLoader }) => {
|
allSpecies: async (_, __, { speciesLoader }) => {
|
||||||
const allSpecies = await speciesLoader.loadAll();
|
const allSpecies = await speciesLoader.loadAll();
|
||||||
return allSpecies;
|
return allSpecies;
|
||||||
},
|
},
|
||||||
|
@ -708,7 +734,7 @@ const resolvers = {
|
||||||
outfit: (_, { id }) => ({ id }),
|
outfit: (_, { id }) => ({ id }),
|
||||||
user: async (_, { id }, { userLoader }) => {
|
user: async (_, { id }, { userLoader }) => {
|
||||||
try {
|
try {
|
||||||
const user = await userLoader.load(id);
|
await userLoader.load(id);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.message.includes("could not find user")) {
|
if (e.message.includes("could not find user")) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -725,7 +751,7 @@ const resolvers = {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const user = await userLoader.load(currentUserId);
|
await userLoader.load(currentUserId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.message.includes("could not find user")) {
|
if (e.message.includes("could not find user")) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -425,7 +425,8 @@ const buildPetStatesForPetTypeLoader = (db, loaders) =>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const buildUserLoader = (db) => new DataLoader(async (ids) => {
|
const buildUserLoader = (db) =>
|
||||||
|
new DataLoader(async (ids) => {
|
||||||
const qs = ids.map((_) => "?").join(",");
|
const qs = ids.map((_) => "?").join(",");
|
||||||
const [rows, _] = await db.execute(
|
const [rows, _] = await db.execute(
|
||||||
`SELECT * FROM users WHERE id IN (${qs})`,
|
`SELECT * FROM users WHERE id IN (${qs})`,
|
||||||
|
@ -440,7 +441,26 @@ const buildUserLoader = (db) => new DataLoader(async (ids) => {
|
||||||
entitiesById.get(String(id)) ||
|
entitiesById.get(String(id)) ||
|
||||||
new Error(`could not find user with ID: ${id}`)
|
new Error(`could not find user with ID: ${id}`)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const buildUserOwnedClosetHangersLoader = (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
|
||||||
|
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}) AND owned = 1
|
||||||
|
ORDER BY item_name`,
|
||||||
|
userIds
|
||||||
|
);
|
||||||
|
const entities = rows.map(normalizeRow);
|
||||||
|
|
||||||
|
return userIds.map((userId) =>
|
||||||
|
entities.filter((e) => e.userId === String(userId))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const buildZoneLoader = (db) => {
|
const buildZoneLoader = (db) => {
|
||||||
const zoneLoader = new DataLoader(async (ids) => {
|
const zoneLoader = new DataLoader(async (ids) => {
|
||||||
|
@ -522,6 +542,7 @@ function buildLoaders(db) {
|
||||||
loaders.speciesLoader = buildSpeciesLoader(db);
|
loaders.speciesLoader = buildSpeciesLoader(db);
|
||||||
loaders.speciesTranslationLoader = buildSpeciesTranslationLoader(db);
|
loaders.speciesTranslationLoader = buildSpeciesTranslationLoader(db);
|
||||||
loaders.userLoader = buildUserLoader(db);
|
loaders.userLoader = buildUserLoader(db);
|
||||||
|
loaders.userOwnedClosetHangersLoader = buildUserOwnedClosetHangersLoader(db);
|
||||||
loaders.zoneLoader = buildZoneLoader(db);
|
loaders.zoneLoader = buildZoneLoader(db);
|
||||||
loaders.zoneTranslationLoader = buildZoneTranslationLoader(db);
|
loaders.zoneTranslationLoader = buildZoneTranslationLoader(db);
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ const { query, getDbCalls, logInAsTestUser } = require("./setup.js");
|
||||||
describe("User", () => {
|
describe("User", () => {
|
||||||
it("looks up a user", async () => {
|
it("looks up a user", async () => {
|
||||||
// TODO: I'm not sure why this is taking extra time, maybe the db conn?
|
// TODO: I'm not sure why this is taking extra time, maybe the db conn?
|
||||||
jest.setTimeout(10000);
|
jest.setTimeout(20000);
|
||||||
|
|
||||||
const res = await query({
|
const res = await query({
|
||||||
query: gql`
|
query: gql`
|
||||||
|
@ -115,4 +115,106 @@ describe("User", () => {
|
||||||
expect(res.data).toEqual({ currentUser: null });
|
expect(res.data).toEqual({ currentUser: null });
|
||||||
expect(getDbCalls()).toMatchInlineSnapshot(`Array []`);
|
expect(getDbCalls()).toMatchInlineSnapshot(`Array []`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("gets private items they own for current user", async () => {
|
||||||
|
await logInAsTestUser();
|
||||||
|
|
||||||
|
const res = await query({
|
||||||
|
query: gql`
|
||||||
|
query {
|
||||||
|
user(id: "44743") {
|
||||||
|
id
|
||||||
|
username
|
||||||
|
itemsTheyOwn {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res).toHaveNoErrors();
|
||||||
|
expect(res.data).toMatchInlineSnapshot(`
|
||||||
|
Object {
|
||||||
|
"user": Object {
|
||||||
|
"id": "44743",
|
||||||
|
"itemsTheyOwn": Array [
|
||||||
|
Object {
|
||||||
|
"id": "74967",
|
||||||
|
"name": "17th Birthday Party Hat",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "49026",
|
||||||
|
"name": "Abominable Snowman Hat",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "40319",
|
||||||
|
"name": "Blue Jelly Tiara",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"username": "dti-test",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
expect(getDbCalls()).toMatchInlineSnapshot(`
|
||||||
|
Array [
|
||||||
|
Array [
|
||||||
|
"SELECT * FROM users WHERE id IN (?)",
|
||||||
|
Array [
|
||||||
|
"44743",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
Array [
|
||||||
|
"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 (44743) AND owned = 1
|
||||||
|
ORDER BY item_name",
|
||||||
|
Array [
|
||||||
|
"44743",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("hides private items they own from other users", async () => {
|
||||||
|
const res = await query({
|
||||||
|
query: gql`
|
||||||
|
query {
|
||||||
|
user(id: "44743") {
|
||||||
|
id
|
||||||
|
username
|
||||||
|
itemsTheyOwn {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res).toHaveNoErrors();
|
||||||
|
expect(res.data).toMatchInlineSnapshot(`
|
||||||
|
Object {
|
||||||
|
"user": Object {
|
||||||
|
"id": "44743",
|
||||||
|
"itemsTheyOwn": Array [],
|
||||||
|
"username": "dti-test",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
expect(getDbCalls()).toMatchInlineSnapshot(`
|
||||||
|
Array [
|
||||||
|
Array [
|
||||||
|
"SELECT * FROM users WHERE id IN (?)",
|
||||||
|
Array [
|
||||||
|
"44743",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue