diff --git a/setup-mysql-user.sql b/setup-mysql-user.sql index afe2fa2..1bd1428 100644 --- a/setup-mysql-user.sql +++ b/setup-mysql-user.sql @@ -19,6 +19,7 @@ GRANT UPDATE ON openneo_impress.pet_states TO impress2020; GRANT UPDATE ON openneo_impress.swf_assets TO impress2020; -- 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.outfits TO impress2020; GRANT SELECT ON openneo_impress.users TO impress2020; diff --git a/src/server/index.js b/src/server/index.js index 98fd781..652e7e6 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -229,6 +229,7 @@ const typeDefs = gql` type User { id: ID! username: String! + itemsTheyOwn: [Item!]! } type Query { @@ -622,13 +623,38 @@ const resolvers = { const user = await userLoader.load(id); 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: { - allColors: async (_, { ids }, { colorLoader }) => { + allColors: async (_, __, { colorLoader }) => { const allColors = await colorLoader.loadAll(); return allColors; }, - allSpecies: async (_, { ids }, { speciesLoader }) => { + allSpecies: async (_, __, { speciesLoader }) => { const allSpecies = await speciesLoader.loadAll(); return allSpecies; }, @@ -708,7 +734,7 @@ const resolvers = { outfit: (_, { id }) => ({ id }), user: async (_, { id }, { userLoader }) => { try { - const user = await userLoader.load(id); + await userLoader.load(id); } catch (e) { if (e.message.includes("could not find user")) { return null; @@ -725,7 +751,7 @@ const resolvers = { } try { - const user = await userLoader.load(currentUserId); + await userLoader.load(currentUserId); } catch (e) { if (e.message.includes("could not find user")) { return null; diff --git a/src/server/loaders.js b/src/server/loaders.js index 1c4c316..1ff1756 100644 --- a/src/server/loaders.js +++ b/src/server/loaders.js @@ -425,8 +425,9 @@ const buildPetStatesForPetTypeLoader = (db, loaders) => ); }); -const buildUserLoader = (db) => new DataLoader(async (ids) => { - const qs = ids.map((_) => "?").join(","); +const buildUserLoader = (db) => + new DataLoader(async (ids) => { + const qs = ids.map((_) => "?").join(","); const [rows, _] = await db.execute( `SELECT * FROM users WHERE id IN (${qs})`, ids @@ -440,7 +441,26 @@ const buildUserLoader = (db) => new DataLoader(async (ids) => { entitiesById.get(String(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 zoneLoader = new DataLoader(async (ids) => { @@ -522,6 +542,7 @@ function buildLoaders(db) { loaders.speciesLoader = buildSpeciesLoader(db); loaders.speciesTranslationLoader = buildSpeciesTranslationLoader(db); loaders.userLoader = buildUserLoader(db); + loaders.userOwnedClosetHangersLoader = buildUserOwnedClosetHangersLoader(db); loaders.zoneLoader = buildZoneLoader(db); loaders.zoneTranslationLoader = buildZoneTranslationLoader(db); diff --git a/src/server/query-tests/User.test.js b/src/server/query-tests/User.test.js index 2031bd0..574cf5a 100644 --- a/src/server/query-tests/User.test.js +++ b/src/server/query-tests/User.test.js @@ -4,7 +4,7 @@ const { query, getDbCalls, logInAsTestUser } = require("./setup.js"); describe("User", () => { it("looks up a user", async () => { // 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({ query: gql` @@ -115,4 +115,106 @@ describe("User", () => { expect(res.data).toEqual({ currentUser: null }); 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", + ], + ], + ] + `); + }); });