mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Merge pull request #4136 from Ocelot-Social-Community/4124-switch-user-role
feat: 🍰 Switch User Role As Admin
This commit is contained in:
commit
35626e6b5f
24
CHANGELOG.md
24
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).
|
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)
|
#### [0.6.4](https://github.com/Ocelot-Social-Community/Ocelot-Social/compare/0.6.3...0.6.4)
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ocelot-social-backend",
|
"name": "ocelot-social-backend",
|
||||||
"version": "0.6.5",
|
"version": "0.6.6",
|
||||||
"description": "GraphQL Backend for ocelot.social",
|
"description": "GraphQL Backend for ocelot.social",
|
||||||
"repository": "https://github.com/Ocelot-Social-Community/Ocelot-Social",
|
"repository": "https://github.com/Ocelot-Social-Community/Ocelot-Social",
|
||||||
"author": "ocelot.social Community",
|
"author": "ocelot.social Community",
|
||||||
|
|||||||
@ -166,6 +166,7 @@ export default shield(
|
|||||||
unpinPost: isAdmin,
|
unpinPost: isAdmin,
|
||||||
UpdateDonations: isAdmin,
|
UpdateDonations: isAdmin,
|
||||||
GenerateInviteCode: isAuthenticated,
|
GenerateInviteCode: isAuthenticated,
|
||||||
|
switchUserRole: isAdmin,
|
||||||
},
|
},
|
||||||
User: {
|
User: {
|
||||||
email: or(isMyOwn, isAdmin),
|
email: or(isMyOwn, isAdmin),
|
||||||
|
|||||||
@ -244,6 +244,31 @@ export default {
|
|||||||
session.close()
|
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: {
|
User: {
|
||||||
email: async (parent, params, context, resolveInfo) => {
|
email: async (parent, params, context, resolveInfo) => {
|
||||||
|
|||||||
@ -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(() => {
|
beforeAll(() => {
|
||||||
const { server } = createServer({
|
const { server } = createServer({
|
||||||
context: () => {
|
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',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@ -215,4 +215,6 @@ type Mutation {
|
|||||||
unmuteUser(id: ID!): User
|
unmuteUser(id: ID!): User
|
||||||
blockUser(id: ID!): User
|
blockUser(id: ID!): User
|
||||||
unblockUser(id: ID!): User
|
unblockUser(id: ID!): User
|
||||||
|
|
||||||
|
switchUserRole(role: UserGroup!, id: ID!): User
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ocelot-social",
|
"name": "ocelot-social",
|
||||||
"version": "0.6.5",
|
"version": "0.6.6",
|
||||||
"description": "Fullstack and API tests with cypress and cucumber for ocelot.social",
|
"description": "Fullstack and API tests with cypress and cucumber for ocelot.social",
|
||||||
"author": "ocelot.social Community",
|
"author": "ocelot.social Community",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
28
webapp/graphql/admin/Roles.js
Normal file
28
webapp/graphql/admin/Roles.js
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
@ -63,6 +63,7 @@
|
|||||||
"placeholder": "E-Mail, Name oder Beschreibung"
|
"placeholder": "E-Mail, Name oder Beschreibung"
|
||||||
},
|
},
|
||||||
"name": "Benutzer",
|
"name": "Benutzer",
|
||||||
|
"roleChanged": "Rolle erfolgreich geändert!",
|
||||||
"table": {
|
"table": {
|
||||||
"columns": {
|
"columns": {
|
||||||
"createdAt": "Erstellt am",
|
"createdAt": "Erstellt am",
|
||||||
|
|||||||
@ -63,6 +63,7 @@
|
|||||||
"placeholder": "e-mail, name or description"
|
"placeholder": "e-mail, name or description"
|
||||||
},
|
},
|
||||||
"name": "Users",
|
"name": "Users",
|
||||||
|
"roleChanged": "Role changed successfully!",
|
||||||
"table": {
|
"table": {
|
||||||
"columns": {
|
"columns": {
|
||||||
"createdAt": "Created at",
|
"createdAt": "Created at",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ocelot-social-webapp",
|
"name": "ocelot-social-webapp",
|
||||||
"version": "0.6.5",
|
"version": "0.6.6",
|
||||||
"description": "ocelot.social Frontend",
|
"description": "ocelot.social Frontend",
|
||||||
"repository": "https://github.com/Ocelot-Social-Community/Ocelot-Social",
|
"repository": "https://github.com/Ocelot-Social-Community/Ocelot-Social",
|
||||||
"author": "ocelot.social Community",
|
"author": "ocelot.social Community",
|
||||||
|
|||||||
@ -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'
|
import Users from './users.vue'
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
config.stubs['nuxt-link'] = '<span><slot /></span>'
|
||||||
|
|
||||||
describe('Users', () => {
|
describe('Users', () => {
|
||||||
let wrapper
|
let wrapper
|
||||||
let Wrapper
|
let Wrapper
|
||||||
let mocks
|
let getters
|
||||||
|
|
||||||
beforeEach(() => {
|
const mocks = {
|
||||||
mocks = {
|
|
||||||
$t: jest.fn(),
|
$t: jest.fn(),
|
||||||
$apollo: {
|
$apollo: {
|
||||||
loading: false,
|
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', () => {
|
describe('mount', () => {
|
||||||
|
getters = {
|
||||||
|
'auth/isAdmin': () => true,
|
||||||
|
'auth/user': () => {
|
||||||
|
return { id: 'admin' }
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
Wrapper = () => {
|
Wrapper = () => {
|
||||||
|
const store = new Vuex.Store({ getters })
|
||||||
return mount(Users, {
|
return mount(Users, {
|
||||||
mocks,
|
mocks,
|
||||||
localVue,
|
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()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -48,6 +48,21 @@
|
|||||||
<template #createdAt="scope">
|
<template #createdAt="scope">
|
||||||
{{ scope.row.createdAt | dateTime }}
|
{{ scope.row.createdAt | dateTime }}
|
||||||
</template>
|
</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>
|
</ds-table>
|
||||||
<pagination-buttons :hasNext="hasNext" :hasPrevious="hasPrevious" @next="next" @back="back" />
|
<pagination-buttons :hasNext="hasNext" :hasPrevious="hasPrevious" @next="next" @back="back" />
|
||||||
</base-card>
|
</base-card>
|
||||||
@ -58,10 +73,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { mapGetters } from 'vuex'
|
||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
import { isEmail } from 'validator'
|
import { isEmail } from 'validator'
|
||||||
import normalizeEmail from '~/components/utils/NormalizeEmail'
|
import normalizeEmail from '~/components/utils/NormalizeEmail'
|
||||||
import PaginationButtons from '~/components/_new/generic/PaginationButtons/PaginationButtons'
|
import PaginationButtons from '~/components/_new/generic/PaginationButtons/PaginationButtons'
|
||||||
|
import { FetchAllRoles, updateUserRole } from '~/graphql/admin/Roles'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -77,6 +94,7 @@ export default {
|
|||||||
hasNext: false,
|
hasNext: false,
|
||||||
email: null,
|
email: null,
|
||||||
filter: null,
|
filter: null,
|
||||||
|
userRoles: [],
|
||||||
form: {
|
form: {
|
||||||
formData: {
|
formData: {
|
||||||
query: '',
|
query: '',
|
||||||
@ -88,6 +106,9 @@ export default {
|
|||||||
hasPrevious() {
|
hasPrevious() {
|
||||||
return this.offset > 0
|
return this.offset > 0
|
||||||
},
|
},
|
||||||
|
...mapGetters({
|
||||||
|
currentUser: 'auth/user',
|
||||||
|
}),
|
||||||
fields() {
|
fields() {
|
||||||
return {
|
return {
|
||||||
index: this.$t('admin.users.table.columns.number'),
|
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 }))
|
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: {
|
methods: {
|
||||||
back() {
|
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>
|
</script>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user