diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index ac709279d..27eb3aa08 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -106,6 +106,7 @@ export default shield( notifications: isAuthenticated, Donations: isAuthenticated, userData: isAuthenticated, + MyInviteCodes: isAuthenticated, }, Mutation: { '*': deny, diff --git a/backend/src/schema/resolvers/inviteCodes.js b/backend/src/schema/resolvers/inviteCodes.js index 7a39bc081..0f7666dc2 100644 --- a/backend/src/schema/resolvers/inviteCodes.js +++ b/backend/src/schema/resolvers/inviteCodes.js @@ -11,6 +11,30 @@ const uniqueInviteCode = async (session, code) => { } export default { + Query: { + MyInviteCodes: async (_parent, args, context, _resolveInfo) => { + const { + user: { id: userId }, + } = context + const session = context.driver.session() + const readTxResultPromise = session.readTransaction(async (txc) => { + const result = await txc.run( + `MATCH (user:User {id: $userId})-[:GENERATED]->(ic:InviteCode) + RETURN properties(ic) AS inviteCodes`, + { + userId, + }, + ) + return result.records.map((record) => record.get('inviteCodes')) + }) + try { + const txResult = await readTxResultPromise + return txResult + } finally { + session.close() + } + }, + }, Mutation: { GenerateInviteCode: async (_parent, args, context, _resolveInfo) => { const { @@ -18,7 +42,6 @@ export default { } = context const session = context.driver.session() let code = generateInvieCode() - let response while (!(await uniqueInviteCode(session, code))) { code = generateInvieCode() } @@ -40,15 +63,15 @@ export default { }) try { const txResult = await writeTxResultPromise - response = txResult[0] + return txResult[0] } finally { session.close() } - return response }, }, InviteCode: { ...Resolver('InviteCode', { + idAttribute: 'code', undefinedToNull: ['expiresAt'], hasOne: { generatedBy: '<-[:GENERATED]-(related:User)', diff --git a/backend/src/schema/resolvers/inviteCodes.spec.js b/backend/src/schema/resolvers/inviteCodes.spec.js index 04a8d2a5a..f0fee4b8d 100644 --- a/backend/src/schema/resolvers/inviteCodes.spec.js +++ b/backend/src/schema/resolvers/inviteCodes.spec.js @@ -5,7 +5,7 @@ import createServer from '../../server' import { createTestClient } from 'apollo-server-testing' let user -// let query +let query let mutate const driver = getDriver() @@ -20,6 +20,16 @@ const generateInviteCodeMutation = gql` } ` +const myInviteCodesQuery = gql` + query { + MyInviteCodes { + code + createdAt + expiresAt + } + } +` + beforeAll(async () => { await cleanDatabase() const { server } = createServer({ @@ -30,7 +40,7 @@ beforeAll(async () => { } }, }) - // query = createTestClient(server).query + query = createTestClient(server).query mutate = createTestClient(server).mutate }) @@ -39,75 +49,99 @@ afterAll(async () => { }) describe('inviteCodes', () => { - describe('generate invite code', () => { - describe('as unauthenticated user', () => { - it('returns permission denied error', async () => { - await expect(mutate({ mutation: generateInviteCodeMutation })).resolves.toEqual( - expect.objectContaining({ - errors: expect.arrayContaining([ - expect.objectContaining({ - extensions: { code: 'INTERNAL_SERVER_ERROR' }, - }), - ]), - data: { - GenerateInviteCode: null, - }, - }), - ) - }) + describe('as unauthenticated user', () => { + it('cannot generate invite codes', async () => { + await expect(mutate({ mutation: generateInviteCodeMutation })).resolves.toEqual( + expect.objectContaining({ + errors: expect.arrayContaining([ + expect.objectContaining({ + extensions: { code: 'INTERNAL_SERVER_ERROR' }, + }), + ]), + data: { + GenerateInviteCode: null, + }, + }), + ) }) - describe('as authenticated user', () => { - beforeAll(async () => { - const authenticatedUser = await Factory.build( - 'user', - { - role: 'user', + it('cannot query invite codes', async () => { + await expect(query({ query: myInviteCodesQuery })).resolves.toEqual( + expect.objectContaining({ + errors: expect.arrayContaining([ + expect.objectContaining({ + extensions: { code: 'INTERNAL_SERVER_ERROR' }, + }), + ]), + data: { + MyInviteCodes: null, }, - { - email: 'user@example.org', - password: '1234', - }, - ) - user = await authenticatedUser.toJson() - }) - - it('generates an invite code without expiresAt', async () => { - await expect(mutate({ mutation: generateInviteCodeMutation })).resolves.toEqual( - expect.objectContaining({ - errors: undefined, - data: { - GenerateInviteCode: { - code: expect.stringMatching(/^[0-9A-Z]{6,6}$/), - expiresAt: null, - createdAt: expect.any(String), - }, - }, - }), - ) - }) - - it('generates an invite code with expiresAt', async () => { - const nextWeek = new Date() - nextWeek.setDate(nextWeek.getDate() + 7) - await expect( - mutate({ - mutation: generateInviteCodeMutation, - variables: { expiresAt: nextWeek.toISOString() }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: undefined, - data: { - GenerateInviteCode: { - code: expect.stringMatching(/^[0-9A-Z]{6,6}$/), - expiresAt: nextWeek.toISOString(), - createdAt: expect.any(String), - }, - }, - }), - ) - }) + }), + ) }) }) + + describe('as authenticated user', () => { + beforeAll(async () => { + const authenticatedUser = await Factory.build( + 'user', + { + role: 'user', + }, + { + email: 'user@example.org', + password: '1234', + }, + ) + user = await authenticatedUser.toJson() + }) + + it('generates an invite code without expiresAt', async () => { + await expect(mutate({ mutation: generateInviteCodeMutation })).resolves.toEqual( + expect.objectContaining({ + errors: undefined, + data: { + GenerateInviteCode: { + code: expect.stringMatching(/^[0-9A-Z]{6,6}$/), + expiresAt: null, + createdAt: expect.any(String), + }, + }, + }), + ) + }) + + it('generates an invite code with expiresAt', async () => { + const nextWeek = new Date() + nextWeek.setDate(nextWeek.getDate() + 7) + await expect( + mutate({ + mutation: generateInviteCodeMutation, + variables: { expiresAt: nextWeek.toISOString() }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: undefined, + data: { + GenerateInviteCode: { + code: expect.stringMatching(/^[0-9A-Z]{6,6}$/), + expiresAt: nextWeek.toISOString(), + createdAt: expect.any(String), + }, + }, + }), + ) + }) + + let inviteCodes + + it('returns the created invite codes when queried', async () => { + const response = await query({ query: myInviteCodesQuery }) + inviteCodes = response.data.MyInviteCodes + expect(inviteCodes).toHaveLength(2) + }) + + // const expiringInviteCode = inviteCodes.filter((ic) => ic.expiresAt !== null) + // const unExpiringInviteCode = inviteCodes.filter((ic) => ic.expiresAt === null) + }) }) diff --git a/backend/src/schema/types/type/InviteCode.gql b/backend/src/schema/types/type/InviteCode.gql index 8ed7fbcab..00ca99b75 100644 --- a/backend/src/schema/types/type/InviteCode.gql +++ b/backend/src/schema/types/type/InviteCode.gql @@ -10,3 +10,7 @@ type InviteCode { type Mutation { GenerateInviteCode(expiresAt: String = null): InviteCode } + +type Query { + MyInviteCodes: [InviteCode] +}