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
Commit
823ff1bc
authored
Aug 17, 2019
by
Nick
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: update user
parent
379d58d0
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
255 additions
and
65 deletions
+255
-65
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
+112
-14
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
+7
-7
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-tooltip(top)
template(v-slot:activator='{ on }')
v-btn(icon, color='grey', x-small, v-on='on')
v-icon mdi-cached
span Change Password
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: 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: 376
px;')
.headline.grey--text(:class='darkMode ? `text--lighten-2` : `text--darken-3`')
{{
title
}}
.caption.grey--text.text--darken-1
{{
description
}}
v-spacer
v-
container.grey.pa-0(fluid, :class='darkMode ? `darken-4-l3` : `lighten-4`
')
v-row(no-gutters, align-content='center', style='height: 90
px;')
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-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