Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
wiki-js
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
1
Issues
1
List
Board
Labels
Milestones
Merge Requests
1
Merge Requests
1
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Jacklull
wiki-js
Commits
823ff1bc
You need to sign in or sign up before continuing.
Commit
823ff1bc
authored
Aug 17, 2019
by
Nick
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: update user
parent
379d58d0
Show whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
250 additions
and
60 deletions
+250
-60
admin-groups-edit.vue
client/components/admin/admin-groups-edit.vue
+1
-1
admin-locale.vue
client/components/admin/admin-locale.vue
+1
-1
admin-theme.vue
client/components/admin/admin-theme.vue
+2
-2
admin-users-create.vue
client/components/admin/admin-users-create.vue
+3
-1
admin-users-edit.vue
client/components/admin/admin-users-edit.vue
+109
-11
nav-header.vue
client/components/common/nav-header.vue
+8
-3
users-mutation-update.gql
client/graph/admin/users/users-mutation-update.gql
+12
-0
users-query-groups.gql
client/graph/admin/users/users-query-groups.gql
+9
-0
page.vue
client/themes/default/components/page.vue
+5
-5
app.scss
client/themes/default/scss/app.scss
+12
-18
user.js
server/graph/resolvers/user.js
+10
-10
user.graphql
server/graph/schemas/user.graphql
+6
-8
error.js
server/helpers/error.js
+4
-0
users.js
server/models/users.js
+68
-0
No files found.
client/components/admin/admin-groups-edit.vue
View file @
823ff1bc
...
...
@@ -14,7 +14,7 @@
v-icon mdi-arrow-left
v-dialog(v-model='deleteGroupDialog', max-width='500', v-if='!group.isSystem')
template(v-slot:activator='{ on }')
v-btn(color='red', large, outlined, v-on='{ on }')
v-btn
.ml-2
(color='red', large, outlined, v-on='{ on }')
v-icon(color='red') mdi-trash-can-outline
v-card
.dialog-header.is-red Delete Group?
...
...
client/components/admin/admin-locale.vue
View file @
823ff1bc
...
...
@@ -120,7 +120,7 @@
v
-
btn
(
v
-
else
-
if
=
'item.isInstalled && item.installDate < item.updatedAt'
,
icon
,
small
,
@
click
=
'download(item)'
)
v
-
icon
.
blue
--
text
mdi
-
cached
v
-
btn
(
v
-
else
-
if
=
'item.isInstalled'
,
icon
,
small
,
@
click
=
'download(item)'
)
v
-
icon
.
green
--
text
mdi
-
check
v
-
icon
.
green
--
text
mdi
-
check
-
bold
v
-
btn
(
v
-
else
,
icon
,
small
,
@
click
=
'download(item)'
)
v
-
icon
.
grey
--
text
mdi
-
cloud
-
download
v
-
card
.
wiki
-
form
.
mt
-
3
.
animated
.
fadeInUp
.
wait
-
p5s
...
...
client/components/admin/admin-theme.vue
View file @
823ff1bc
...
...
@@ -114,7 +114,7 @@
item-key='value',
:items-per-page='1000'
)
template(v-slot:item
s
='thm')
template(v-slot:item='thm')
td
strong
{{
thm
.
item
.
text
}}
td
...
...
@@ -124,7 +124,7 @@
v-btn(v-else-if='thm.item.isInstalled && thm.item.installDate < thm.item.updatedAt', icon)
v-icon.blue--text mdi-cached
v-btn(v-else-if='thm.item.isInstalled', icon)
v-icon.green--text mdi-check
v-icon.green--text mdi-check
-bold
v-btn(v-else, icon)
v-icon.grey--text mdi-cloud-download
</
template
>
...
...
client/components/admin/admin-users-create.vue
View file @
823ff1bc
...
...
@@ -67,10 +67,12 @@
:items='groups'
item-text='name'
item-value='id'
item-disabled='isSystem'
outlined
prepend-icon='mdi-account-group'
v-model='group'
label='Assign to Group(s)...'
dense
clearable
multiple
)
...
...
@@ -104,7 +106,7 @@ import _ from 'lodash'
import
createUserMutation
from
'gql/admin/users/users-mutation-create.gql'
import
providersQuery
from
'gql/admin/users/users-query-strategies.gql'
import
groupsQuery
from
'gql/admin/
auth/auth
-query-groups.gql'
import
groupsQuery
from
'gql/admin/
users/users
-query-groups.gql'
export
default
{
props
:
{
...
...
client/components/admin/admin-users-edit.vue
View file @
823ff1bc
...
...
@@ -14,7 +14,7 @@
v-icon mdi-arrow-left
v-dialog(v-model='deleteUserDialog', max-width='500', v-if='user.id !== currentUserId && !user.isSystem')
template(v-slot:activator='{ on }')
v-btn.ml-3.animated.fadeInDown.wait-p1s(color='red', large, outlined, v-on='on')
v-btn.ml-3.animated.fadeInDown.wait-p1s(color='red', large, outlined, v-on='on'
, disabled
)
v-icon(color='red') mdi-trash-can-outline
v-card
.dialog-header.is-red Delete User?
...
...
@@ -113,15 +113,35 @@
v-list-item-title Password
v-list-item-subtitle ••••••••
v-list-item-action
v-menu(
v-model='editPop.newPassword'
:close-on-content-click='false'
min-width='350'
left
)
template(v-slot:activator='{ on: menu }')
v-tooltip(top)
template(v-slot:activator='{ on
}')
v-btn(icon, color='grey', x-small, v-on='on
')
template(v-slot:activator='{ on: tooltip
}')
v-btn(icon, color='grey', x-small, v-on='{ ...menu, ...tooltip }', @click='focusField(`iptNewPassword`)
')
v-icon mdi-cached
span Change Password
v-card
v-text-field(
ref='iptNewPassword'
v-model='newPassword'
label='New Password'
solo
hide-details
append-icon='mdi-check'
type='password'
@click:append='editPop.newPassword = false'
@keydown.enter='editPop.newPassword = false'
@keydown.esc='editPop.newPassword = false'
)
v-list-item-action
v-tooltip(top)
template(v-slot:activator='{ on }')
v-btn(icon, color='grey', x-small, v-on='on')
v-btn(icon, color='grey', x-small, v-on='on'
, disabled
)
v-icon mdi-email
span Send Password Reset Email
v-divider
...
...
@@ -151,22 +171,37 @@
span User Groups
v-list(dense)
template(v-for='(group, idx) in user.groups')
v-list-item
v-list-item
(:key='`group-` + group.id')
v-list-item-avatar(size='32')
v-icon mdi-account-group-outline
v-list-item-content
v-list-item-title
{{
group
.
name
}}
v-list-item-action(v-if='!user.isSystem')
v-btn(icon, color='red', x-small)
v-btn(icon, color='red', x-small
, @click='unassignGroup(group.id)'
)
v-icon mdi-close
v-divider(v-if='idx < user.groups.length - 1')
v-alert.mx-3(v-if='user.groups.length < 1', outlined, color='grey darken-1', icon='mdi-alert')
.caption This user is not assigned to any group yet. You must assign at least 1 group to a user.
v-card-chin(v-if='!user.isSystem')
v-spacer
v-btn(color='primary', text)
v-icon(left) mdi-clipboard-account
span Assign to group
v-select(
ref='iptAssignGroup'
:items='groups'
v-model='newGroup'
label='Select Group...'
item-value='id'
item-text='name'
item-disabled='isSystem'
solo
flat
dense
hide-details
@keydown.esc='editPop.assignGroup = false'
style='max-width: 300px;'
)
v-btn.ml-2.px-4(depressed, color='primary', height='48', @click='assignGroup', :disabled='newGroup === 0')
v-icon(left) mdi-clipboard-account-outline
span Assign
v-flex(xs6)
v-card.animated.fadeInUp.wait-p2s
v-toolbar(color='primary', dense, dark, flat)
...
...
@@ -274,6 +309,8 @@ import _ from 'lodash'
import
{
get
}
from
'vuex-pathify'
import
userQuery
from
'gql/admin/users/users-query-single.gql'
import
groupsQuery
from
'gql/admin/users/users-query-groups.gql'
import
updateUserMutation
from
'gql/admin/users/users-mutation-update.gql'
export
default
{
data
()
{
...
...
@@ -285,10 +322,18 @@ export default {
pwd
:
false
,
location
:
false
,
jobTitle
:
false
,
timezone
:
false
timezone
:
false
,
newPassword
:
false
,
assignGroup
:
false
},
newGroup
:
0
,
newPassword
:
''
,
user
:
{
email
:
''
,
name
:
''
,
location
:
''
,
jobTitle
:
''
,
timezone
:
''
,
groups
:
[]
},
timezones
:
[
...
...
@@ -550,13 +595,58 @@ export default {
},
methods
:
{
deleteUser
()
{},
updateUser
()
{},
async
updateUser
()
{
this
.
$store
.
commit
(
`loadingStart`
,
'admin-users-update'
)
const
resp
=
await
this
.
$apollo
.
mutate
({
mutation
:
updateUserMutation
,
variables
:
{
id
:
this
.
user
.
id
,
email
:
this
.
user
.
email
,
name
:
this
.
user
.
name
,
newPassword
:
this
.
newPassword
,
groups
:
_
.
map
(
this
.
user
.
groups
,
'id'
),
location
:
this
.
user
.
location
,
jobTitle
:
this
.
user
.
jobTitle
,
timezone
:
this
.
user
.
timezone
}
})
if
(
_
.
get
(
resp
,
'data.users.update.responseResult.succeeded'
,
false
))
{
this
.
$store
.
commit
(
'showNotification'
,
{
style
:
'success'
,
message
:
'User updated successfully.'
,
icon
:
'check'
})
this
.
$router
.
push
(
'/users'
)
}
else
{
this
.
$store
.
commit
(
'showNotification'
,
{
style
:
'red'
,
message
:
_
.
get
(
resp
,
'data.users.update.responseResult.message'
,
'An unexpected error occured.'
),
icon
:
'warning'
})
}
this
.
$store
.
commit
(
`loadingStop`
,
'admin-users-update'
)
},
focusField
(
ipt
)
{
this
.
$nextTick
(()
=>
{
_
.
delay
(()
=>
{
this
.
$refs
[
ipt
].
focus
()
},
200
)
})
},
assignGroup
()
{
if
(
_
.
some
(
this
.
user
.
groups
,
[
'id'
,
this
.
newGroup
]))
{
this
.
$store
.
commit
(
'showNotification'
,
{
message
:
'User is already assigned to this group!'
,
style
:
'error'
,
icon
:
'alert'
})
}
else
{
this
.
user
.
groups
.
push
(
_
.
find
(
this
.
groups
,
[
'id'
,
this
.
newGroup
]))
this
.
newGroup
=
0
}
},
unassignGroup
(
gid
)
{
this
.
user
.
groups
=
_
.
reject
(
this
.
user
.
groups
,
[
'id'
,
gid
])
}
},
apollo
:
{
...
...
@@ -572,6 +662,14 @@ export default {
watchLoading
(
isLoading
)
{
this
.
$store
.
commit
(
`loading
${
isLoading
?
'Start'
:
'Stop'
}
`
,
'admin-users-refresh'
)
}
},
groups
:
{
query
:
groupsQuery
,
fetchPolicy
:
'network-only'
,
update
:
(
data
)
=>
data
.
groups
.
list
,
watchLoading
(
isLoading
)
{
this
.
$store
.
commit
(
`loading
${
isLoading
?
'Start'
:
'Stop'
}
`
,
'admin-groups-refresh'
)
}
}
}
}
...
...
client/components/common/nav-header.vue
View file @
823ff1bc
...
...
@@ -40,8 +40,8 @@
v-list-item-avatar(size='24'): v-icon(color='indigo') mdi-file-document-edit-outline
v-list-item-title.body-2
{{
$t
(
'common:header.edit'
)
}}
v-list-item.pl-4(@click='pageHistory', v-if='mode !== `history`')
v-list-item-avatar(size='24'): v-icon(color='
indigo
') mdi-history
v-list-item-title.body-2
{{
$t
(
'common:header.history'
)
}}
v-list-item-avatar(size='24'): v-icon(color='
grey lighten-2
') mdi-history
v-list-item-title.body-2
.grey--text.text--ligten-2
{{
$t
(
'common:header.history'
)
}}
v-list-item.pl-4(@click='pageSource', v-if='mode !== `source`')
v-list-item-avatar(size='24'): v-icon(color='indigo') mdi-code-tags
v-list-item-title.body-2
{{
$t
(
'common:header.viewSource'
)
}}
...
...
@@ -309,7 +309,12 @@ export default {
window
.
location
.
assign
(
`/e/
${
this
.
locale
}
/
${
this
.
path
}
`
)
},
pageHistory
()
{
window
.
location
.
assign
(
`/h/
${
this
.
locale
}
/
${
this
.
path
}
`
)
this
.
$store
.
commit
(
'showNotification'
,
{
style
:
'indigo'
,
message
:
`Coming soon...`
,
icon
:
'ferry'
})
// window.location.assign(`/h/${this.locale}/${this.path}`)
},
pageSource
()
{
window
.
location
.
assign
(
`/s/
${
this
.
locale
}
/
${
this
.
path
}
`
)
...
...
client/graph/admin/users/users-mutation-update.gql
0 → 100644
View file @
823ff1bc
mutation
(
$id
:
Int
!,
$email
:
String
,
$name
:
String
,
$newPassword
:
String
,
$groups
:
[
Int
],
$location
:
String
,
$jobTitle
:
String
,
$timezone
:
String
)
{
users
{
update
(
id
:
$id
,
email
:
$email
,
name
:
$name
,
newPassword
:
$newPassword
,
groups
:
$groups
,
location
:
$location
,
jobTitle
:
$jobTitle
,
timezone
:
$timezone
)
{
responseResult
{
succeeded
errorCode
slug
message
}
}
}
}
client/graph/admin/users/users-query-groups.gql
0 → 100644
View file @
823ff1bc
query
{
groups
{
list
{
id
name
isSystem
}
}
}
client/themes/default/components/page.vue
View file @
823ff1bc
...
...
@@ -47,15 +47,15 @@
.caption.red--text
{{
$t
(
'common:page.unpublished'
)
}}
status-indicator.ml-3(negative, pulse)
v-divider
v-toolbar.px-2(:color='darkMode ? `grey darken-4-l3` : `grey lighten-4`', flat, :height='90')
div(style='padding-left: 376px;')
v-container.grey.pa-0(fluid, :class='darkMode ? `darken-4-l3` : `lighten-4`')
v-row(no-gutters, align-content='center', style='height: 90px;')
v-col.pl-4(offset-xl='2', offset-lg='3')
.headline.grey--text(:class='darkMode ? `text--lighten-2` : `text--darken-3`')
{{
title
}}
.caption.grey--text.text--darken-1
{{
description
}}
v-spacer
v-divider
v-container.pl-5.pt-
2(fill-height,
fluid, grid-list-xl)
v-container.pl-5.pt-
4(
fluid, grid-list-xl)
v-layout(row)
v-flex.page-col-sd(lg3, xl2,
fill-height,
v-if='$vuetify.breakpoint.lgAndUp', style='margin-top: -90px;')
v-flex.page-col-sd(lg3, xl2, v-if='$vuetify.breakpoint.lgAndUp', style='margin-top: -90px;')
v-card(v-if='toc.length')
.overline.pa-5.pb-0(:class='darkMode ? `blue--text text--lighten-2` : `primary--text`')
{{
$t
(
'common:page.toc'
)
}}
v-list.pb-3(dense, nav, :class='darkMode ? `darken-3-d3` : ``')
...
...
client/themes/default/scss/app.scss
View file @
823ff1bc
...
...
@@ -25,6 +25,16 @@
h1
,
h2
,
h3
,
h4
,
h5
,
h6
{
position
:
relative
;
&
:before
{
display
:
block
;
content
:
" "
;
width
:
1px
;
margin-top
:
-75px
;
height
:
75px
;
visibility
:
hidden
;
z-index
:
-1
;
}
&
:first-child
{
padding-top
:
0
;
}
...
...
@@ -84,7 +94,6 @@
}
h2
{
margin
:
1rem
0
0
0
;
padding
:
8px
0
0
0
;
color
:
mc
(
'grey'
,
'800'
);
position
:
relative
;
...
...
@@ -114,8 +123,7 @@
}
}
h3
{
margin
:
0
;
padding
:
8px
0
0
0
;
margin
:
8px
0
0
0
;
color
:
mc
(
'grey'
,
'700'
);
position
:
relative
;
...
...
@@ -135,8 +143,7 @@
}
h4
,
h5
,
h6
{
font-size
:
1rem
;
margin
:
0
;
padding
:
8px
0
0
0
;
margin
:
8px
0
0
0
;
color
:
mc
(
'grey'
,
'700'
);
position
:
relative
;
...
...
@@ -165,19 +172,6 @@
}
}
// scroll offset fix
h1
:before
,
h2
:before
,
h3
:before
,
h4
:before
,
h5
:before
,
h6
:before
{
display
:
block
;
content
:
" "
;
width
:
1px
;
height
:
1px
;
margin-top
:
-75px
;
height
:
75px
;
visibility
:
hidden
;
z-index
:
-1
;
}
// ---------------------------------
// PARAGRAPHS
// ---------------------------------
...
...
server/graph/resolvers/user.js
View file @
823ff1bc
...
...
@@ -43,19 +43,19 @@ module.exports = {
delete
(
obj
,
args
)
{
return
WIKI
.
models
.
users
.
query
().
deleteById
(
args
.
id
)
},
update
(
obj
,
args
)
{
return
WIKI
.
models
.
users
.
query
().
patch
({
email
:
args
.
email
,
name
:
args
.
name
,
provider
:
args
.
provider
,
providerId
:
args
.
providerId
}).
where
(
'id'
,
args
.
id
)
async
update
(
obj
,
args
)
{
try
{
await
WIKI
.
models
.
users
.
updateUser
(
args
)
return
{
responseResult
:
graphHelper
.
generateSuccess
(
'User created successfully'
)
}
}
catch
(
err
)
{
return
graphHelper
.
generateError
(
err
)
}
},
resetPassword
(
obj
,
args
)
{
return
false
},
setPassword
(
obj
,
args
)
{
return
false
}
},
User
:
{
...
...
server/graph/schemas/user.graphql
View file @
823ff1bc
...
...
@@ -48,9 +48,12 @@ type UserMutation {
id
:
Int
!
email
:
String
name
:
String
providerKey
:
String
providerId
:
String
):
UserResponse
@
auth
(
requires
:
[
"
manage
:
users
"
,
"
manage
:
system
"
])
newPassword
:
String
groups
:
[
Int
]
location
:
String
jobTitle
:
String
timezone
:
String
):
DefaultResponse
@
auth
(
requires
:
[
"
manage
:
users
"
,
"
manage
:
system
"
])
delete
(
id
:
Int
!
...
...
@@ -59,11 +62,6 @@ type UserMutation {
resetPassword
(
id
:
Int
!
):
DefaultResponse
setPassword
(
id
:
Int
!
passwordRaw
:
String
!
):
DefaultResponse
}
# -----------------------------------------------
...
...
server/helpers/error.js
View file @
823ff1bc
...
...
@@ -140,5 +140,9 @@ module.exports = {
UserCreationFailed
:
CustomError
(
'UserCreationFailed'
,
{
message
:
'An unexpected error occured during user creation.'
,
code
:
1009
}),
UserNotFound
:
CustomError
(
'UserNotFound'
,
{
message
:
'This user does not exist.'
,
code
:
1016
})
}
server/models/users.js
View file @
823ff1bc
...
...
@@ -374,6 +374,11 @@ module.exports = class User extends Model {
throw
new
WIKI
.
Error
.
AuthTFAInvalid
()
}
/**
* Create a new user
*
* @param {Object} param0 User Fields
*/
static
async
createNewUser
({
providerKey
,
email
,
passwordRaw
,
name
,
groups
,
mustChangePassword
,
sendWelcomeEmail
})
{
// Input sanitization
email
=
_
.
toLower
(
email
)
...
...
@@ -487,6 +492,69 @@ module.exports = class User extends Model {
}
}
/**
* Update an existing user
*
* @param {Object} param0 User ID and fields to update
*/
static
async
updateUser
({
id
,
email
,
name
,
newPassword
,
groups
,
location
,
jobTitle
,
timezone
})
{
const
usr
=
await
WIKI
.
models
.
users
.
query
().
findById
(
id
)
if
(
usr
)
{
let
usrData
=
{}
if
(
!
_
.
isEmpty
(
email
)
&&
email
!==
usr
.
email
)
{
const
dupUsr
=
await
WIKI
.
models
.
users
.
query
().
select
(
'id'
).
where
({
email
,
providerKey
:
usr
.
providerKey
})
if
(
dupUsr
)
{
throw
new
WIKI
.
Error
.
AuthAccountAlreadyExists
()
}
usrData
.
email
=
email
}
if
(
!
_
.
isEmpty
(
name
)
&&
name
!==
usr
.
name
)
{
usrData
.
name
=
_
.
trim
(
name
)
}
if
(
!
_
.
isEmpty
(
newPassword
))
{
if
(
newPassword
.
length
<
6
)
{
throw
new
WIKI
.
Error
.
InputInvalid
(
'Password must be at least 6 characters!'
)
}
usrData
.
password
=
newPassword
}
if
(
!
_
.
isEmpty
(
groups
))
{
const
usrGroupsRaw
=
await
usr
.
$relatedQuery
(
'groups'
)
const
usrGroups
=
_
.
map
(
usrGroupsRaw
,
'id'
)
// Relate added groups
const
addUsrGroups
=
_
.
difference
(
groups
,
usrGroups
)
for
(
const
grp
of
addUsrGroups
)
{
await
usr
.
$relatedQuery
(
'groups'
).
relate
(
grp
)
}
// Unrelate removed groups
const
remUsrGroups
=
_
.
difference
(
usrGroups
,
groups
)
for
(
const
grp
of
remUsrGroups
)
{
await
usr
.
$relatedQuery
(
'groups'
).
unrelate
().
where
(
'groupId'
,
grp
)
}
}
if
(
!
_
.
isEmpty
(
location
)
&&
location
!==
usr
.
location
)
{
usrData
.
location
=
_
.
trim
(
location
)
}
if
(
!
_
.
isEmpty
(
jobTitle
)
&&
jobTitle
!==
usr
.
jobTitle
)
{
usrData
.
jobTitle
=
_
.
trim
(
jobTitle
)
}
if
(
!
_
.
isEmpty
(
timezone
)
&&
timezone
!==
usr
.
timezone
)
{
usrData
.
timezone
=
timezone
}
await
WIKI
.
models
.
users
.
query
().
patch
(
usrData
).
findById
(
id
)
}
else
{
throw
new
WIKI
.
Error
.
UserNotFound
()
}
}
/**
* Register a new user (client-side registration)
*
* @param {Object} param0 User fields
* @param {Object} context GraphQL Context
*/
static
async
register
({
email
,
password
,
name
,
verify
=
false
,
bypassChecks
=
false
},
context
)
{
const
localStrg
=
await
WIKI
.
models
.
authentication
.
getStrategy
(
'local'
)
// Check if self-registration is enabled
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment