diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index 31efb9316..9eda4da3b 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -111,6 +111,11 @@ const noEmailFilter = rule({ return !('email' in args) }) +const pinnedPost = rule({ + cache: 'no_cache', +})(async (_, args) => { + return 'pinned' in args +}) const publicRegistration = rule()(() => !!CONFIG.PUBLIC_REGISTRATION) // Permissions @@ -143,7 +148,7 @@ const permissions = shield( SignupVerification: allow, CreateInvitationCode: and(isAuthenticated, or(not(invitationLimitReached), isAdmin)), UpdateUser: onlyYourself, - CreatePost: isAuthenticated, + CreatePost: or(and(isAuthenticated, not(pinnedPost)), isAdmin), UpdatePost: isAuthor, DeletePost: isAuthor, report: isAuthenticated, diff --git a/backend/src/schema/resolvers/posts.spec.js b/backend/src/schema/resolvers/posts.spec.js index 0e7272e8e..eaf2b6f99 100644 --- a/backend/src/schema/resolvers/posts.spec.js +++ b/backend/src/schema/resolvers/posts.spec.js @@ -17,13 +17,21 @@ const categoryIds = ['cat9', 'cat4', 'cat15'] let variables const createPostMutation = gql` - mutation($id: ID, $title: String!, $content: String!, $language: String, $categoryIds: [ID]) { + mutation( + $id: ID + $title: String! + $content: String! + $language: String + $categoryIds: [ID] + $pinned: Boolean + ) { CreatePost( id: $id title: $title content: $content language: $language categoryIds: $categoryIds + pinned: $pinned ) { id title @@ -31,6 +39,7 @@ const createPostMutation = gql` slug disabled deleted + pinned language author { name @@ -357,6 +366,73 @@ describe('CreatePost', () => { }) }) }) + + describe('pinned posts', () => { + beforeEach(async () => { + variables = { ...variables, categoryIds: ['cat9', 'cat27', 'cat15'], pinned: true } + }) + describe('users cannot create pinned posts', () => { + it('throws authorization error', async () => { + await expect(mutate({ mutation: createPostMutation, variables })).resolves.toMatchObject({ + errors: [{ message: 'Not Authorised!' }], + data: { CreatePost: null }, + }) + }) + }) + + describe('moderator cannot create pinned posts', () => { + let moderator + beforeEach(async () => { + moderator = await user.update({ role: 'moderator', updatedAt: new Date().toISOString() }) + authenticatedUser = await moderator.toJson() + }) + + it('throws authorization error', async () => { + await expect(mutate({ mutation: createPostMutation, variables })).resolves.toMatchObject({ + errors: [{ message: 'Not Authorised!' }], + data: { CreatePost: null }, + }) + }) + }) + + describe('admin can create pinned posts', () => { + let admin + beforeEach(async () => { + admin = await user.update({ + role: 'admin', + name: 'Admin', + updatedAt: new Date().toISOString(), + }) + authenticatedUser = await admin.toJson() + variables = { + ...variables, + title: 'pinned post', + content: + 'this is super important for the community and we promise not to have too many', + } + }) + + it('throws authorization error', async () => { + const expected = { + data: { + CreatePost: { + title: 'pinned post', + content: + 'this is super important for the community and we promise not to have too many', + author: { + name: 'Admin', + }, + pinned: true, + }, + }, + } + + await expect(mutate({ mutation: createPostMutation, variables })).resolves.toMatchObject( + expected, + ) + }) + }) + }) }) }) @@ -386,7 +462,6 @@ describe('UpdatePost', () => { }) variables = { - ...variables, id: 'p9876', title: 'New title', content: 'New content', @@ -395,8 +470,11 @@ describe('UpdatePost', () => { describe('unauthenticated', () => { it('throws authorization error', async () => { - const { errors } = await mutate({ mutation: updatePostMutation, variables }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + authenticatedUser = null + expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject({ + errors: [{ message: 'Not Authorised!' }], + data: { UpdatePost: null }, + }) }) }) diff --git a/backend/src/schema/types/type/Post.gql b/backend/src/schema/types/type/Post.gql index 5b11757d3..69305a66d 100644 --- a/backend/src/schema/types/type/Post.gql +++ b/backend/src/schema/types/type/Post.gql @@ -16,6 +16,7 @@ type Post { createdAt: String updatedAt: String language: String + pinned: Boolean relatedContributions: [Post]! @cypher( statement: """ @@ -68,6 +69,7 @@ type Mutation { language: String categoryIds: [ID] contentExcerpt: String + pinned: Boolean ): Post UpdatePost( id: ID!