Merge pull request #4136 from Ocelot-Social-Community/4124-switch-user-role

feat: 🍰 Switch User Role As Admin
This commit is contained in:
Moriz Wahl 2021-02-10 13:27:17 +01:00 committed by GitHub
commit 35626e6b5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 292 additions and 15 deletions

View File

@ -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)

View File

@ -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",

View File

@ -166,6 +166,7 @@ export default shield(
unpinPost: isAdmin,
UpdateDonations: isAdmin,
GenerateInviteCode: isAuthenticated,
switchUserRole: isAdmin,
},
User: {
email: or(isMyOwn, isAdmin),

View File

@ -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) => {

View File

@ -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',
}),
],
}),
)
})
})
})

View File

@ -215,4 +215,6 @@ type Mutation {
unmuteUser(id: ID!): User
blockUser(id: ID!): User
unblockUser(id: ID!): User
switchUserRole(role: UserGroup!, id: ID!): User
}

View File

@ -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",

View File

@ -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
}
}
`
}

View File

@ -63,6 +63,7 @@
"placeholder": "E-Mail, Name oder Beschreibung"
},
"name": "Benutzer",
"roleChanged": "Rolle erfolgreich geändert!",
"table": {
"columns": {
"createdAt": "Erstellt am",

View File

@ -63,6 +63,7 @@
"placeholder": "e-mail, name or description"
},
"name": "Users",
"roleChanged": "Role changed successfully!",
"table": {
"columns": {
"createdAt": "Created at",

View File

@ -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",

View File

@ -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'] = '<span><slot /></span>'
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()
})
})
})
})

View File

@ -48,6 +48,21 @@
<template #createdAt="scope">
{{ scope.row.createdAt | dateTime }}
</template>
<template slot="role" slot-scope="scope">
<template v-if="userRoles">
<select
v-if="scope.row.id !== currentUser.id"
:value="`${scope.row.role}`"
v-on:change="changeUserRole(scope.row.id, $event)"
>
<option v-for="value in userRoles" :key="value">
{{ value }}
</option>
</select>
<ds-text v-else>{{ scope.row.role }}</ds-text>
</template>
</template>
</ds-table>
<pagination-buttons :hasNext="hasNext" :hasPrevious="hasPrevious" @next="next" @back="back" />
</base-card>
@ -58,10 +73,12 @@
</template>
<script>
import { mapGetters } from 'vuex'
import gql from 'graphql-tag'
import { isEmail } from 'validator'
import normalizeEmail from '~/components/utils/NormalizeEmail'
import PaginationButtons from '~/components/_new/generic/PaginationButtons/PaginationButtons'
import { FetchAllRoles, updateUserRole } from '~/graphql/admin/Roles'
export default {
components: {
@ -77,6 +94,7 @@ export default {
hasNext: false,
email: null,
filter: null,
userRoles: [],
form: {
formData: {
query: '',
@ -88,6 +106,9 @@ export default {
hasPrevious() {
return this.offset > 0
},
...mapGetters({
currentUser: 'auth/user',
}),
fields() {
return {
index: this.$t('admin.users.table.columns.number'),
@ -153,6 +174,14 @@ export default {
return User.map((u, i) => Object.assign({}, u, { index: this.offset + i }))
},
},
userRoles: {
query() {
return FetchAllRoles()
},
update({ __type }) {
return __type.enumValues.map((item) => item.name)
},
},
},
methods: {
back() {
@ -174,6 +203,20 @@ export default {
}
}
},
changeUserRole(id, event) {
const newRole = event.target.value
this.$apollo
.mutate({
mutation: updateUserRole(),
variables: { role: newRole, id },
})
.then(({ data }) => {
this.$toast.success(this.$t('admin.users.roleChanged'))
})
.catch((error) => {
this.$toast.error(error.message)
})
},
},
}
</script>