feat: delete user with replace target

pull/1975/head
NGPixel 4 years ago
parent 006dae1221
commit 1f9e5b3fd0

@ -1,5 +1,5 @@
<template lang="pug">
v-card
v-card(flat)
v-card-text(v-if='group.id === 1')
v-alert.radius-7(
:class='$vuetify.theme.dark ? "grey darken-4" : "orange lighten-5"'

@ -1,5 +1,5 @@
<template lang="pug">
v-card
v-card(flat)
v-card-title.pb-4(:class='$vuetify.theme.dark ? `grey darken-3-d3` : `grey lighten-5`')
v-text-field(
outlined
@ -8,6 +8,8 @@
v-model='search'
label='Search Group Users...'
hide-details
dense
style='max-width: 450px;'
)
v-spacer
v-btn(color='primary', depressed, @click='searchUserDialog = true', :disabled='group.id === 2')
@ -65,7 +67,7 @@ export default {
data() {
return {
headers: [
{ text: 'ID', value: 'id', width: 50 },
{ text: 'ID', value: 'id', width: 70 },
{ text: 'Name', value: 'name' },
{ text: 'Email', value: 'email' },
{ text: 'Actions', value: 'actions', sortable: false, width: 50 }
@ -90,7 +92,7 @@ export default {
}
},
methods: {
async assignUser(id) {
async assignUser({ id, email, name }) {
try {
await this.$apollo.mutate({
mutation: assignUserMutation,

@ -43,7 +43,7 @@
v-list-item-icon
v-icon(color='blue') mdi-account-check
v-list-item-title Set as Verified
v-list-item(@click='deleteUserDialog = true', :disabled='user.id == currentUserId || user.isSystem')
v-list-item(@click='deleteUserConfirm', :disabled='user.id == currentUserId || user.isSystem')
v-list-item-icon
v-icon(color='red') mdi-trash-can-outline
v-list-item-title Delete
@ -221,8 +221,9 @@
hide-details
@keydown.esc='editPop.assignGroup = false'
style='max-width: 300px;'
dense
)
v-btn.ml-2.px-4(depressed, color='primary', height='48', @click='assignGroup', :disabled='newGroup === 0')
v-btn.ml-2.px-4(depressed, color='primary', @click='assignGroup', :disabled='newGroup === 0')
v-icon(left) mdi-clipboard-account-outline
span {{$t('admin:users.groupAssign')}}
v-system-bar(window, :color='$vuetify.theme.dark ? `grey darken-4-l3` : `grey lighten-3`')
@ -349,20 +350,32 @@
v-card-text.pt-5
i18next(path='admin:users.deleteConfirmText', tag='span')
strong(place='username') {{ user.email }}
.caption.mt-3 {{$t('admin:users.deleteConfirmForeignNotice')}}
v-card-actions
.mt-3 {{$t('admin:users.deleteConfirmReplaceWarn')}}
v-divider.my-3
.d-flex.align-center.mt-3
v-btn.text-none(color='primary', depressed, @click='deleteSearchUserDialog = true')
v-icon(left) mdi-clipboard-account
| Select User...
.caption.pl-3
strong ID {{deleteReplaceUser.id}}
.caption {{deleteReplaceUser.name}}
em {{deleteReplaceUser.email}}
v-card-chin
v-spacer
v-btn(text, @click='deleteUserDialog = false') {{$t('common:actions.cancel')}}
v-btn(color='red', dark, @click='deleteUser') {{$t('common:actions.delete')}}
user-search(v-model='deleteSearchUserDialog', @select='assignDeleteUser')
</template>
<script>
import _ from 'lodash'
import { get } from 'vuex-pathify'
import gql from 'graphql-tag'
import { StatusIndicator } from 'vue-status-indicator'
import UserSearch from '../common/user-search.vue'
import groupsQuery from 'gql/admin/users/users-query-groups.gql'
export default {
@ -370,11 +383,18 @@ export default {
namespaces: ['admin', 'profile']
},
components: {
StatusIndicator
StatusIndicator,
UserSearch
},
data() {
data () {
return {
deleteUserDialog: false,
deleteSearchUserDialog: false,
deleteReplaceUser: {
id: 1,
name: '',
email: ''
},
editPop: {
email: false,
name: false,
@ -738,13 +758,21 @@ export default {
/**
* Delete a user
*/
deleteUserConfirm () {
this.deleteUserDialog = true
this.deleteReplaceUser = {
id: this.currentUserId,
name: this.$store.get('user/name'),
email: this.$store.get('user/email')
}
},
async deleteUser () {
this.$store.commit(`loadingStart`, 'admin-users-delete')
const resp = await this.$apollo.mutate({
mutation: gql`
mutation ($id: Int!) {
mutation ($id: Int!, $replaceId: Int!) {
users {
delete(id: $id) {
delete(id: $id, replaceId: $replaceId) {
responseResult {
succeeded
errorCode
@ -756,7 +784,8 @@ export default {
}
`,
variables: {
id: this.user.id
id: this.user.id,
replaceId: this.deleteReplaceUser.id
}
})
if (_.get(resp, 'data.users.delete.responseResult.succeeded', false)) {
@ -776,6 +805,23 @@ export default {
this.deleteUserDialog = false
this.$store.commit(`loadingStop`, 'admin-users-delete')
},
assignDeleteUser (selUsr) {
if (selUsr.id === this.user.id) {
this.$store.commit('showNotification', {
style: 'red',
message: 'You cannot select the account you\'re about to delete!',
icon: 'warning'
})
} else if (selUsr.id === 2) {
this.$store.commit('showNotification', {
style: 'red',
message: 'You cannot use the guest account for this operation.',
icon: 'warning'
})
} else {
this.deleteReplaceUser = selUsr
}
},
/**
* Update a user
*/

@ -30,7 +30,7 @@
dense
)
template(v-for='(usr, idx) in items')
v-list-item(:key='usr.id', @click='setUser(usr.id)')
v-list-item(:key='usr.id', @click='setUser(usr)')
v-list-item-avatar(size='40', color='primary')
span.body-1.white--text {{usr.name | initials}}
v-list-item-content
@ -50,8 +50,7 @@
<script>
import _ from 'lodash'
import searchUsersQuery from 'gql/common/common-users-query-search.gql'
import gql from 'graphql-tag'
export default {
filters: {
@ -96,8 +95,8 @@ export default {
close() {
this.$emit('input', false)
},
setUser(id) {
this.$emit('select', id)
setUser(usr) {
this.$emit('select', usr)
this.close()
},
searchFilter(item, queryText, itemText) {
@ -106,7 +105,18 @@ export default {
},
apollo: {
items: {
query: searchUsersQuery,
query: gql`
query ($query: String!) {
users {
search(query:$query) {
id
name
email
providerKey
}
}
}
`,
variables() {
return {
query: this.search

@ -1,10 +0,0 @@
query ($query: String!) {
users {
search(query:$query) {
id
name
email
providerKey
}
}
}

@ -183,10 +183,12 @@ module.exports = {
startYear: 2017,
endYear: (new Date().getFullYear()) + 5
}),
new CopyWebpackPlugin([
{ from: 'client/static' },
{ from: './node_modules/prismjs/components', to: 'js/prism' }
], {}),
new CopyWebpackPlugin({
patterns: [
{ from: 'client/static' },
{ from: './node_modules/prismjs/components', to: 'js/prism' }
]
}),
new HtmlWebpackPlugin({
template: 'dev/templates/master.pug',
filename: '../server/views/master.pug',

@ -190,10 +190,12 @@ module.exports = {
startYear: 2017,
endYear: (new Date().getFullYear()) + 5
}),
new CopyWebpackPlugin([
{ from: 'client/static' },
{ from: './node_modules/prismjs/components', to: 'js/prism' }
], {}),
new CopyWebpackPlugin({
patterns: [
{ from: 'client/static' },
{ from: './node_modules/prismjs/components', to: 'js/prism' }
]
}),
new MiniCssExtractPlugin({
filename: 'css/bundle.[hash].css',
chunkFilename: 'css/[name].[chunkhash].css'

@ -36,8 +36,8 @@
},
"dependencies": {
"@aoberoi/passport-slack": "1.0.5",
"@azure/storage-blob": "12.1.1",
"@bugsnag/js": "7.0.2",
"@azure/storage-blob": "12.1.2",
"@bugsnag/js": "7.1.1",
"@exlinc/keycloak-passport": "1.0.2",
"@root/csr": "0.8.1",
"@root/keypairs": "0.9.0",
@ -49,7 +49,7 @@
"apollo-server": "2.13.1",
"apollo-server-express": "2.13.1",
"auto-load": "3.0.4",
"aws-sdk": "2.678.0",
"aws-sdk": "2.686.0",
"azure-search-client": "3.1.5",
"bcryptjs-then": "1.0.1",
"bluebird": "3.7.2",
@ -74,14 +74,14 @@
"elasticsearch6": "npm:@elastic/elasticsearch@6",
"elasticsearch7": "npm:@elastic/elasticsearch@7",
"emoji-regex": "9.0.0",
"eventemitter2": "6.4.0",
"eventemitter2": "6.4.1",
"express": "4.17.1",
"express-brute": "1.0.1",
"express-session": "1.17.1",
"file-type": "14.4.0",
"file-type": "14.5.0",
"filesize": "6.1.0",
"fs-extra": "9.0.0",
"getos": "3.2.0",
"getos": "3.2.1",
"graphql": "14.6.0",
"graphql-list-fields": "2.0.2",
"graphql-rate-limit-directive": "1.2.1",
@ -89,22 +89,22 @@
"graphql-tools": "4.0.7",
"he": "1.2.0",
"highlight.js": "10.0.3",
"i18next": "19.4.4",
"i18next": "19.4.5",
"i18next-express-middleware": "2.0.0",
"i18next-node-fs-backend": "2.1.3",
"image-size": "0.8.3",
"js-base64": "2.5.2",
"js-binary": "1.2.0",
"js-yaml": "3.13.1",
"js-yaml": "3.14.0",
"jsdom": "16.2.2",
"jsonwebtoken": "8.5.1",
"katex": "0.11.1",
"klaw": "3.0.0",
"knex": "0.21.1",
"lodash": "4.17.15",
"markdown-it": "10.0.0",
"markdown-it": "11.0.0",
"markdown-it-abbr": "1.0.4",
"markdown-it-attrs": "3.0.2",
"markdown-it-attrs": "3.0.3",
"markdown-it-emoji": "1.4.0",
"markdown-it-expand-tabs": "1.0.13",
"markdown-it-external-links": "0.0.6",
@ -117,18 +117,18 @@
"markdown-it-task-lists": "2.1.1",
"mathjax": "3.0.5",
"mime-types": "2.1.27",
"moment": "2.25.3",
"moment-timezone": "0.5.29",
"mongodb": "3.5.7",
"moment": "2.26.0",
"moment-timezone": "0.5.31",
"mongodb": "3.5.8",
"ms": "2.1.2",
"mssql": "6.2.0",
"multer": "1.4.2",
"mysql2": "2.1.0",
"nanoid": "3.1.8",
"nanoid": "3.1.9",
"node-2fa": "1.1.2",
"node-cache": "5.1.0",
"nodemailer": "6.4.6",
"objection": "2.1.3",
"nodemailer": "6.4.8",
"objection": "2.1.5",
"passport": "0.4.1",
"passport-auth0": "1.3.2",
"passport-azure-ad": "4.2.1",
@ -154,7 +154,7 @@
"pg-pubsub": "0.5.0",
"pg-query-stream": "3.1.1",
"pg-tsquery": "8.1.0",
"pug": "2.0.4",
"pug": "3.0.0",
"punycode": "2.1.1",
"qr-image": "3.2.0",
"raven": "2.6.4",
@ -166,7 +166,7 @@
"scim-query-filter-parser": "2.0.4",
"semver": "7.3.2",
"serve-favicon": "2.5.0",
"simple-git": "2.4.0",
"simple-git": "2.5.0",
"solr-node": "1.2.1",
"sqlite3": "4.2.0",
"ssh2": "0.8.9",
@ -176,30 +176,30 @@
"tar-fs": "2.1.0",
"twemoji": "13.0.0",
"uslug": "1.0.4",
"uuid": "8.0.0",
"uuid": "8.1.0",
"validate.js": "0.13.1",
"winston": "3.2.1",
"xss": "1.0.6",
"yargs": "15.3.1"
},
"devDependencies": {
"@babel/cli": "^7.8.4",
"@babel/core": "^7.9.6",
"@babel/plugin-proposal-class-properties": "^7.8.3",
"@babel/plugin-proposal-decorators": "^7.8.3",
"@babel/plugin-proposal-export-namespace-from": "^7.8.3",
"@babel/plugin-proposal-function-sent": "^7.8.3",
"@babel/plugin-proposal-json-strings": "^7.8.3",
"@babel/plugin-proposal-numeric-separator": "^7.8.3",
"@babel/plugin-proposal-throw-expressions": "^7.8.3",
"@babel/cli": "^7.10.1",
"@babel/core": "^7.10.1",
"@babel/plugin-proposal-class-properties": "^7.10.1",
"@babel/plugin-proposal-decorators": "^7.10.1",
"@babel/plugin-proposal-export-namespace-from": "^7.10.1",
"@babel/plugin-proposal-function-sent": "^7.10.1",
"@babel/plugin-proposal-json-strings": "^7.10.1",
"@babel/plugin-proposal-numeric-separator": "^7.10.1",
"@babel/plugin-proposal-throw-expressions": "^7.10.1",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-syntax-import-meta": "^7.8.3",
"@babel/polyfill": "^7.8.7",
"@babel/preset-env": "^7.9.6",
"@mdi/font": "5.2.45",
"@babel/plugin-syntax-import-meta": "^7.10.1",
"@babel/polyfill": "^7.10.1",
"@babel/preset-env": "^7.10.1",
"@mdi/font": "5.3.45",
"@panter/vue-i18next": "0.15.2",
"@requarks/ckeditor5": "12.4.0-wiki.16",
"@vue/babel-preset-app": "4.3.1",
"@vue/babel-preset-app": "4.4.1",
"animate-sass": "0.8.2",
"animated-number-vue": "1.0.0",
"apollo-cache-inmemory": "1.6.6",
@ -211,7 +211,7 @@
"apollo-link-persisted-queries": "0.2.2",
"apollo-link-ws": "1.0.20",
"apollo-utilities": "1.3.4",
"autoprefixer": "9.7.6",
"autoprefixer": "9.8.0",
"babel-eslint": "10.1.0",
"babel-jest": "26.0.1",
"babel-loader": "^8.1.0",
@ -224,15 +224,15 @@
"chart.js": "2.9.3",
"clean-webpack-plugin": "3.0.0",
"clipboard": "2.0.6",
"codemirror": "5.53.2",
"copy-webpack-plugin": "5.1.1",
"codemirror": "5.54.0",
"copy-webpack-plugin": "6.0.1",
"core-js": "3.6.5",
"css-loader": "3.5.3",
"cssnano": "4.1.10",
"d3": "5.16.0",
"duplicate-package-checker-webpack-plugin": "3.0.0",
"epic-spinners": "1.1.0",
"eslint": "7.0.0",
"eslint": "7.1.0",
"eslint-config-requarks": "1.0.7",
"eslint-config-standard": "14.1.1",
"eslint-plugin-import": "2.20.2",
@ -242,7 +242,7 @@
"eslint-plugin-vue": "6.2.2",
"fibers": "5.0.0",
"file-loader": "6.0.0",
"filepond": "4.13.6",
"filepond": "4.14.0",
"filepond-plugin-file-validate-type": "1.2.5",
"filesize.js": "2.0.0",
"graphql-persisted-document-loader": "2.0.0",
@ -257,7 +257,7 @@
"jest": "26.0.1",
"js-beautify": "1.11.0",
"js-cookie": "2.2.1",
"mermaid": "8.5.0",
"mermaid": "8.5.1",
"mini-css-extract-plugin": "0.9.0",
"moment-duration-format": "2.3.2",
"moment-timezone-data-webpack-plugin": "1.3.0",
@ -277,13 +277,13 @@
"pug-plain-loader": "1.0.0",
"raw-loader": "4.0.1",
"resolve-url-loader": "3.1.1",
"sass": "1.26.5",
"sass": "1.26.7",
"sass-loader": "8.0.2",
"sass-resources-loader": "2.0.3",
"script-ext-html-webpack-plugin": "2.1.4",
"simple-progress-webpack-plugin": "1.1.2",
"style-loader": "1.2.1",
"terser": "4.6.13",
"terser": "4.7.0",
"twemoji-awesome": "1.0.6",
"url-loader": "4.1.0",
"velocity-animate": "1.5.2",
@ -296,19 +296,19 @@
"vue-hot-reload-api": "2.3.4",
"vue-loader": "15.9.2",
"vue-moment": "4.1.0",
"vue-router": "3.1.6",
"vue-router": "3.3.2",
"vue-status-indicator": "1.2.1",
"vue-template-compiler": "2.6.11",
"vue2-animate": "2.1.3",
"vuedraggable": "2.23.2",
"vuescroll": "4.15.0",
"vuetify": "2.2.28",
"vuetify-loader": "1.4.3",
"vuescroll": "4.15.1",
"vuetify": "2.2.30",
"vuetify-loader": "1.4.4",
"vuex": "3.4.0",
"vuex-pathify": "1.4.1",
"vuex-persistedstate": "3.0.1",
"webpack": "4.43.0",
"webpack-bundle-analyzer": "3.7.0",
"webpack-bundle-analyzer": "3.8.0",
"webpack-cli": "3.3.11",
"webpack-dev-middleware": "3.7.2",
"webpack-hot-middleware": "2.25.0",

@ -72,7 +72,7 @@ module.exports = {
if (args.id <= 2) {
throw new WIKI.Error.UserDeleteProtected()
}
await WIKI.models.users.deleteUser(args.id)
await WIKI.models.users.deleteUser(args.id, args.replaceId)
return {
responseResult: graphHelper.generateSuccess('User deleted successfully')
}

@ -63,6 +63,7 @@ type UserMutation {
delete(
id: Int!
replaceId: Int!
): DefaultResponse @auth(requires: ["manage:users", "manage:system"])
verify(

@ -71,13 +71,19 @@ module.exports = {
['description', page.description],
['published', page.isPublished.toString()],
['date', page.updatedAt],
['tags', page.tags ? page.tags.map(t => t.tag).join(', ') : '']
['tags', page.tags ? page.tags.map(t => t.tag).join(', ') : ''],
['editor', page.editorKey]
]
switch (page.contentType) {
case 'markdown':
return '---\n' + meta.map(mt => `${mt[0]}: ${mt[1]}`).join('\n') + '\n---\n\n' + page.content
case 'html':
return '<!--\n' + meta.map(mt => `${mt[0]}: ${mt[1]}`).join('\n') + '\n-->\n\n' + page.content
case 'json':
return {
...page.content,
_meta: _.fromPairs(meta)
}
default:
return page.content
}

@ -128,8 +128,10 @@ module.exports = class CommentProvider extends Model {
} else {
WIKI.data.commentProvider = {
...WIKI.data.commentProvider,
...require(`../modules/comments/${commentProvider.key}/comment`)
...require(`../modules/comments/${commentProvider.key}/comment`),
config: commentProvider.config
}
await WIKI.data.commentProvider.init()
}
WIKI.data.commentProvider.config = commentProvider.config
}

@ -613,9 +613,15 @@ module.exports = class User extends Model {
*
* @param {*} id User ID
*/
static async deleteUser (id) {
static async deleteUser (id, replaceId) {
const usr = await WIKI.models.users.query().findById(id)
if (usr) {
await WIKI.models.assets.query().patch({ authorId: replaceId }).where('authorId', id)
await WIKI.models.comments.query().patch({ authorId: replaceId }).where('authorId', id)
await WIKI.models.pageHistory.query().patch({ authorId: replaceId }).where('authorId', id)
await WIKI.models.pages.query().patch({ authorId: replaceId }).where('authorId', id)
await WIKI.models.pages.query().patch({ creatorId: replaceId }).where('creatorId', id)
await WIKI.models.userKeys.query().delete().where('userId', id)
await WIKI.models.users.query().deleteById(id)
} else {

@ -10,8 +10,6 @@ const { AkismetClient } = require('akismet-api')
const window = new JSDOM('').window
const DOMPurify = createDOMPurify(window)
md.use(mdEmoji)
let akismetClient = null
// ------------------------------------
@ -23,6 +21,7 @@ module.exports = {
* Init
*/
async init (config) {
WIKI.logger.info('(COMMENTS/DEFAULT) Initializing...')
if (WIKI.data.commentProvider.config.akismet && WIKI.data.commentProvider.config.akismet.length > 2) {
akismetClient = new AkismetClient({
key: WIKI.data.commentProvider.config.akismet,
@ -33,14 +32,19 @@ module.exports = {
try {
const isValid = await akismetClient.verifyKey()
if (!isValid) {
WIKI.logger.warn('Akismet Key is invalid!')
akismetClient = null
WIKI.logger.warn('(COMMENTS/DEFAULT) Akismet Key is invalid! [ DISABLED ]')
} else {
WIKI.logger.info('(COMMENTS/DEFAULT) Akismet key is valid. [ OK ]')
}
} catch (err) {
WIKI.logger.warn('Unable to verify Akismet Key: ' + err.message)
akismetClient = null
WIKI.logger.warn('(COMMENTS/DEFAULT) Unable to verify Akismet Key: ' + err.message)
}
} else {
akismetClient = null
}
WIKI.logger.info('(COMMENTS/DEFAULT) Initialization completed.')
},
/**
* Create New Comment
@ -56,6 +60,8 @@ module.exports = {
}
})
mkdown.use(mdEmoji)
// -> Build New Comment
const newComment = {
content,

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save