diff --git a/backend/src/middleware/dateTimeMiddleware.js b/backend/src/middleware/dateTimeMiddleware.js index 73b75070c..11b6498a4 100644 --- a/backend/src/middleware/dateTimeMiddleware.js +++ b/backend/src/middleware/dateTimeMiddleware.js @@ -13,9 +13,11 @@ export default { CreatePost: setCreatedAt, CreateComment: setCreatedAt, CreateOrganization: setCreatedAt, + CreateNotification: setCreatedAt, UpdateUser: setUpdatedAt, UpdatePost: setUpdatedAt, UpdateComment: setUpdatedAt, - UpdateOrganization: setUpdatedAt + UpdateOrganization: setUpdatedAt, + UpdateNotification: setUpdatedAt } } diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index 736ce20a9..495bc9145 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -44,6 +44,7 @@ const isAuthor = rule({ cache: 'no_cache' })(async (parent, args, { user, driver // Permissions const permissions = shield({ Query: { + Notification: isAdmin, statistics: allow, currentUser: allow, Post: or(onlyEnabledContent, isModerator) diff --git a/backend/src/resolvers/notifications.spec.js b/backend/src/resolvers/notifications.spec.js new file mode 100644 index 000000000..50ded7bc4 --- /dev/null +++ b/backend/src/resolvers/notifications.spec.js @@ -0,0 +1,120 @@ + +import Factory from '../seed/factories' +import { GraphQLClient } from 'graphql-request' +import { host, login } from '../jest/helpers' + +const factory = Factory() +let client + +beforeEach(async () => { + await factory.create('User', { + id: 'you', + email: 'test@example.org', + password: '1234' + }) +}) + +afterEach(async () => { + await factory.cleanDatabase() +}) + +describe('Notification', () => { + const query = `{ + Notification { + id + } + }` + + describe('unauthenticated', () => { + it('throws authorization error', async () => { + client = new GraphQLClient(host) + await expect(client.request(query)).rejects.toThrow('Not Authorised') + }) + }) +}) + +describe('currentUser { notifications }', () => { + let variables = {} + + describe('authenticated', () => { + let headers + beforeEach(async () => { + headers = await login({ email: 'test@example.org', password: '1234' }) + client = new GraphQLClient(host, { headers }) + }) + + describe('given some notifications', () => { + beforeEach(async () => { + const neighborParams = { + email: 'neighbor@example.org', + password: '1234', + id: 'neighbor' + } + await Promise.all([ + factory.create('User', neighborParams), + factory.create('Notification', { id: 'not-for-you' }), + factory.create('Notification', { id: 'already-seen', read: true }) + ]) + await factory.create('Notification', { id: 'unseen' }) + await factory.authenticateAs(neighborParams) + await factory.create('Post', { id: 'p1' }) + await Promise.all([ + factory.relate('Notification', 'User', { from: 'not-for-you', to: 'neighbor' }), + factory.relate('Notification', 'Post', { from: 'p1', to: 'not-for-you' }), + factory.relate('Notification', 'User', { from: 'unseen', to: 'you' }), + factory.relate('Notification', 'Post', { from: 'p1', to: 'unseen' }), + factory.relate('Notification', 'User', { from: 'already-seen', to: 'you' }), + factory.relate('Notification', 'Post', { from: 'p1', to: 'already-seen' }) + ]) + }) + + describe('filter for read: false', () => { + const query = `query($read: Boolean) { + currentUser { + notifications(read: $read, orderBy: createdAt_desc) { + id + post { + id + } + } + } + }` + let variables = { read: false } + it('returns only unread notifications of current user', async () => { + const expected = { + currentUser: { + notifications: [ + { id: 'unseen', post: { id: 'p1' } } + ] + } + } + await expect(client.request(query, variables)).resolves.toEqual(expected) + }) + }) + + describe('no filters', () => { + const query = `{ + currentUser { + notifications(orderBy: createdAt_desc) { + id + post { + id + } + } + } + }` + it('returns all notifications of current user', async () => { + const expected = { + currentUser: { + notifications: [ + { id: 'unseen', post: { id: 'p1' } }, + { id: 'already-seen', post: { id: 'p1' } } + ] + } + } + await expect(client.request(query, variables)).resolves.toEqual(expected) + }) + }) + }) + }) +}) diff --git a/backend/src/schema.graphql b/backend/src/schema.graphql index 3b1d95a95..db468471a 100644 --- a/backend/src/schema.graphql +++ b/backend/src/schema.graphql @@ -69,6 +69,14 @@ type Statistics { countShouts: Int! } +type Notification { + id: ID! + read: Boolean, + user: User @relation(name: "NOTIFIED", direction: "OUT") + post: Post @relation(name: "NOTIFIED", direction: "IN") + createdAt: String +} + scalar Date scalar Time scalar DateTime @@ -124,6 +132,8 @@ type User { createdAt: String updatedAt: String + notifications(read: Boolean): [Notification]! @relation(name: "NOTIFIED", direction: "IN") + friends: [User]! @relation(name: "FRIENDS", direction: "BOTH") friendsCount: Int! @cypher(statement: "MATCH (this)<-[:FRIENDS]->(r:User) RETURN COUNT(DISTINCT r)") diff --git a/backend/src/seed/factories/index.js b/backend/src/seed/factories/index.js index e19239ece..7c23226cb 100644 --- a/backend/src/seed/factories/index.js +++ b/backend/src/seed/factories/index.js @@ -8,6 +8,7 @@ import createComment from './comments.js' import createCategory from './categories.js' import createTag from './tags.js' import createReport from './reports.js' +import createNotification from './notifications.js' export const seedServerHost = 'http://127.0.0.1:4001' @@ -29,7 +30,8 @@ const factories = { Comment: createComment, Category: createCategory, Tag: createTag, - Report: createReport + Report: createReport, + Notification: createNotification } export const cleanDatabase = async (options = {}) => { diff --git a/backend/src/seed/factories/notifications.js b/backend/src/seed/factories/notifications.js new file mode 100644 index 000000000..2e2abdd55 --- /dev/null +++ b/backend/src/seed/factories/notifications.js @@ -0,0 +1,17 @@ +import uuid from 'uuid/v4' + +export default function (params) { + const { + id = uuid(), + read = false + } = params + + return ` + mutation { + CreateNotification( + id: "${id}", + read: ${read}, + ) { id, read } + } + ` +}