diff --git a/CHANGELOG.md b/CHANGELOG.md index b256df9da..e3183e7a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,29 @@ All notable changes to this project will be documented in this file. Dates are d Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). -#### [v0.6.5](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/0.6.4...v0.6.5) +#### [v0.6.6](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/0.6.5...v0.6.6) -- updated CHANGELOG.md [`9d9075f`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/9d9075f2117b2eb4b607e7d59ab18c7e655c6ea7) +- feat: Image Cropping Is Optional [`#4199`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4199) +- publish workflow [`#4195`](https://github.com/Ocelot-Social-Community/Ocelot-Social/pull/4195) +- change user roles is working, test fails [`c528269`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/c528269cb2972e6ea937d31ba22d0e11168141f2) +- file upload: refactored [`650e83f`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/650e83f4c250389477933a2e7d21d8245b0ce882) +- change user role: tests are working [`14dfe2a`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/14dfe2ae2cd4a24c06c9229893b33586dfceae4f) + +#### [0.6.5](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/v0.6.4...0.6.5) + +> 8 February 2021 + +- - adjusted changelog to ocelot-social repo [`9603882`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/9603882edebf8967e05abfa94e4e1ebf452d4e24) +- - first steps towards docker image deployment & github autotagging [`5503216`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/5503216ad4a0230ac533042e4a69806590fc2a5a) +- - deploy structure image [`a60400b`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/a60400b4fe6f59bbb80e1073db4def3ba205e1a7) + +#### [v0.6.4](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/0.6.4...v0.6.4) + +> 9 February 2021 + +- chore(release): 0.6.4 [`8b7570d`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/8b7570dc35d0ea431f673a711ac051f1e1320acb) +- change user roles is working, test fails [`8c3310a`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/8c3310abaf87c0e5597fec4f93fb37d27122c9e7) +- change user role: tests are working [`f10da4b`](https://github.com/Ocelot-Social-Community/Ocelot-Social/commit/f10da4b09388fe1e2b85abd53f6ffc67c785d4c1) #### [0.6.4](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/0.6.3...0.6.4) diff --git a/backend/package.json b/backend/package.json index 6b9aeb482..0f1ec1f06 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "ocelot-social-backend", - "version": "0.6.5", + "version": "0.6.6", "description": "GraphQL Backend for ocelot.social", "repository": "https://github.com/Ocelot-Social-Community/Ocelot-Social", "author": "ocelot.social Community", diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index dae6f0e48..af70ca2fd 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -166,6 +166,7 @@ export default shield( unpinPost: isAdmin, UpdateDonations: isAdmin, GenerateInviteCode: isAuthenticated, + switchUserRole: isAdmin, }, User: { email: or(isMyOwn, isAdmin), diff --git a/backend/src/schema/resolvers/users.js b/backend/src/schema/resolvers/users.js index edc81482f..c6bb64125 100644 --- a/backend/src/schema/resolvers/users.js +++ b/backend/src/schema/resolvers/users.js @@ -244,6 +244,31 @@ export default { session.close() } }, + switchUserRole: async (object, args, context, resolveInfo) => { + const { role, id } = args + + if (context.user.id === id) throw new Error('you-cannot-change-your-own-role') + const session = context.driver.session() + const writeTxResultPromise = session.writeTransaction(async (transaction) => { + const switchUserRoleResponse = await transaction.run( + ` + MATCH (user:User {id: $id}) + SET user.role = $role + SET user.updatedAt = toString(datetime()) + RETURN user {.*} + `, + { id, role }, + ) + const [user] = switchUserRoleResponse.records.map((record) => record.get('user')) + return user + }) + try { + const user = await writeTxResultPromise + return user + } finally { + session.close() + } + }, }, User: { email: async (parent, params, context, resolveInfo) => { diff --git a/backend/src/schema/resolvers/users.spec.js b/backend/src/schema/resolvers/users.spec.js index 6509f0e68..e5e818040 100644 --- a/backend/src/schema/resolvers/users.spec.js +++ b/backend/src/schema/resolvers/users.spec.js @@ -45,6 +45,18 @@ const deleteUserMutation = gql` } ` +const switchUserRoleMutation = gql` + mutation($role: UserGroup!, $id: ID!) { + switchUserRole(role: $role, id: $id) { + name + role + id + updatedAt + email + } + } +` + beforeAll(() => { const { server } = createServer({ context: () => { @@ -458,3 +470,71 @@ describe('Delete a User as admin', () => { }) }) }) + +describe('switch user role', () => { + beforeEach(async () => { + user = await Factory.build('user', { + id: 'user', + role: 'user', + }) + admin = await Factory.build('user', { + role: 'admin', + id: 'admin', + }) + }) + + describe('as simple user', () => { + it('cannot change the role', async () => { + authenticatedUser = await user.toJson() + variables = { + id: 'user', + role: 'admin', + } + await expect(mutate({ mutation: switchUserRoleMutation, variables })).resolves.toEqual( + expect.objectContaining({ + errors: [ + expect.objectContaining({ + message: 'Not Authorised!', + }), + ], + }), + ) + }) + }) + + describe('as admin', () => { + it('changes the role of other user', async () => { + authenticatedUser = await admin.toJson() + variables = { + id: 'user', + role: 'moderator', + } + await expect(mutate({ mutation: switchUserRoleMutation, variables })).resolves.toEqual( + expect.objectContaining({ + data: { + switchUserRole: expect.objectContaining({ + role: 'moderator', + }), + }, + }), + ) + }) + + it('cannot change own role', async () => { + authenticatedUser = await admin.toJson() + variables = { + id: 'admin', + role: 'moderator', + } + await expect(mutate({ mutation: switchUserRoleMutation, variables })).resolves.toEqual( + expect.objectContaining({ + errors: [ + expect.objectContaining({ + message: 'you-cannot-change-your-own-role', + }), + ], + }), + ) + }) + }) +}) diff --git a/backend/src/schema/types/type/User.gql b/backend/src/schema/types/type/User.gql index 712eda90c..2290cd4ce 100644 --- a/backend/src/schema/types/type/User.gql +++ b/backend/src/schema/types/type/User.gql @@ -215,4 +215,6 @@ type Mutation { unmuteUser(id: ID!): User blockUser(id: ID!): User unblockUser(id: ID!): User + + switchUserRole(role: UserGroup!, id: ID!): User } diff --git a/package.json b/package.json index 462bfc14b..3510526ef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ocelot-social", - "version": "0.6.5", + "version": "0.6.6", "description": "Fullstack and API tests with cypress and cucumber for ocelot.social", "author": "ocelot.social Community", "license": "MIT", diff --git a/webapp/graphql/admin/Roles.js b/webapp/graphql/admin/Roles.js new file mode 100644 index 000000000..531961656 --- /dev/null +++ b/webapp/graphql/admin/Roles.js @@ -0,0 +1,28 @@ +import gql from 'graphql-tag' + +export const FetchAllRoles = () => { + return gql` + query { + __type(name: "UserGroup") { + name + enumValues { + name + } + } + } + ` +} + +export const updateUserRole = (role, id) => { + return gql` + mutation($role: UserGroup!, $id: ID!) { + switchUserRole(role: $role, id: $id) { + name + role + id + updatedAt + email + } + } + ` +} diff --git a/webapp/locales/de.json b/webapp/locales/de.json index 4d34757d8..9ef86a7d4 100644 --- a/webapp/locales/de.json +++ b/webapp/locales/de.json @@ -63,6 +63,7 @@ "placeholder": "E-Mail, Name oder Beschreibung" }, "name": "Benutzer", + "roleChanged": "Rolle erfolgreich geƤndert!", "table": { "columns": { "createdAt": "Erstellt am", diff --git a/webapp/locales/en.json b/webapp/locales/en.json index afb767c14..3c2af7556 100644 --- a/webapp/locales/en.json +++ b/webapp/locales/en.json @@ -63,6 +63,7 @@ "placeholder": "e-mail, name or description" }, "name": "Users", + "roleChanged": "Role changed successfully!", "table": { "columns": { "createdAt": "Created at", diff --git a/webapp/package.json b/webapp/package.json index 9ad6a157c..65d21d8f2 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -1,6 +1,6 @@ { "name": "ocelot-social-webapp", - "version": "0.6.5", + "version": "0.6.6", "description": "ocelot.social Frontend", "repository": "https://github.com/Ocelot-Social-Community/Ocelot-Social", "author": "ocelot.social Community", diff --git a/webapp/pages/admin/users.spec.js b/webapp/pages/admin/users.spec.js index 37d155b92..e8e624cc1 100644 --- a/webapp/pages/admin/users.spec.js +++ b/webapp/pages/admin/users.spec.js @@ -1,27 +1,54 @@ -import { mount } from '@vue/test-utils' +import { config, mount } from '@vue/test-utils' +import Vuex from 'vuex' import Users from './users.vue' const localVue = global.localVue +config.stubs['nuxt-link'] = '' describe('Users', () => { let wrapper let Wrapper - let mocks + let getters - beforeEach(() => { - mocks = { - $t: jest.fn(), - $apollo: { - loading: false, - }, - } - }) + const mocks = { + $t: jest.fn(), + $apollo: { + loading: false, + mutate: jest + .fn() + .mockRejectedValue({ message: 'Ouch!' }) + .mockResolvedValue({ + data: { + switchUserRole: { + id: 'user', + email: 'user@example.org', + name: 'User', + role: 'moderator', + slug: 'user', + }, + }, + }), + }, + $toast: { + error: jest.fn(), + success: jest.fn(), + }, + } describe('mount', () => { + getters = { + 'auth/isAdmin': () => true, + 'auth/user': () => { + return { id: 'admin' } + }, + } + Wrapper = () => { + const store = new Vuex.Store({ getters }) return mount(Users, { mocks, localVue, + store, }) } @@ -69,5 +96,54 @@ describe('Users', () => { }) }) }) + + describe('change roles', () => { + beforeAll(() => { + wrapper = Wrapper() + wrapper.setData({ + User: [ + { + id: 'admin', + email: 'admin@example.org', + name: 'Admin', + role: 'admin', + slug: 'admin', + }, + { + id: 'user', + email: 'user@example.org', + name: 'User', + role: 'user', + slug: 'user', + }, + ], + userRoles: ['user', 'moderator', 'admin'], + }) + }) + + it('cannot change own role', () => { + const adminRow = wrapper.findAll('tr').at(1) + expect(adminRow.find('select').exists()).toBe(false) + }) + + it('changes the role of another user', () => { + const userRow = wrapper.findAll('tr').at(2) + userRow.findAll('option').at(1).setSelected() + expect(mocks.$apollo.mutate).toHaveBeenCalledWith( + expect.objectContaining({ + variables: { + id: 'user', + role: 'moderator', + }, + }), + ) + }) + + it('toasts a success message after role has changed', () => { + const userRow = wrapper.findAll('tr').at(2) + userRow.findAll('option').at(1).setSelected() + expect(mocks.$toast.success).toHaveBeenCalled() + }) + }) }) }) diff --git a/webapp/pages/admin/users.vue b/webapp/pages/admin/users.vue index 3d9a4f660..e8b4cfdf5 100644 --- a/webapp/pages/admin/users.vue +++ b/webapp/pages/admin/users.vue @@ -48,6 +48,21 @@ + + @@ -58,10 +73,12 @@