feat(user.set): support preferences

This commit is contained in:
Julien Fontanet 2016-07-05 17:19:38 +02:00
parent a2f7ad627e
commit 5165e0a54c
6 changed files with 88 additions and 55 deletions

View File

@ -57,11 +57,11 @@ getAll.permission = 'admin'
// -------------------------------------------------------------------
export async function set ({id, email, password, permission}) {
export async function set ({id, email, password, permission, preferences}) {
if (permission && id === this.session.get('user_id')) {
throw new InvalidParameters('a user cannot change it\'s own permission')
}
await this.updateUser(id, {email, password, permission})
await this.updateUser(id, {email, password, permission, preferences})
}
set.description = 'changes the properties of an existing user'
@ -72,7 +72,8 @@ set.params = {
id: { type: 'string' },
email: { type: 'string', optional: true },
password: { type: 'string', optional: true },
permission: { type: 'string', optional: true }
permission: { type: 'string', optional: true },
preferences: { type: 'object', optional: true }
}
// -------------------------------------------------------------------

View File

@ -418,7 +418,7 @@ const apiHelpers = {
// Handles both properties and wrapped models.
const properties = user.properties || user
return pick(properties, 'id', 'email', 'groups', 'permission', 'provider')
return pick(properties, 'id', 'email', 'groups', 'permission', 'preferences', 'provider')
},
throw (errorId, data) {

View File

@ -1,3 +1,5 @@
import isEmpty from 'lodash/isEmpty'
import Collection from '../collection/redis'
import Model from '../model'
import { forEach } from '../utils'
@ -12,6 +14,18 @@ User.prototype.default = {
// -------------------------------------------------------------------
const parseProp = (obj, name) => {
const value = obj[name]
if (value == null) {
return
}
try {
return JSON.parse(value)
} catch (error) {
console.warn('cannot parse user[%s] (%s):', name, value, error)
}
}
export class Users extends Collection {
get Model () {
return User
@ -35,7 +49,13 @@ export class Users extends Collection {
async save (user) {
// Serializes.
user.groups = JSON.stringify(user.groups)
let tmp
if (!isEmpty(tmp = user.groups)) {
user.groups = JSON.stringify(tmp)
}
if (!isEmpty(tmp = user.preferences)) {
user.preferences = JSON.stringify(tmp)
}
return /* await */ this.update(user)
}
@ -45,13 +65,11 @@ export class Users extends Collection {
// Deserializes
forEach(users, user => {
const {groups} = user
try {
user.groups = groups ? JSON.parse(groups) : []
} catch (_) {
console.warn('cannot parse user.groups:', groups)
user.groups = []
}
let tmp
user.groups = ((tmp = parseProp(user, 'groups')) && tmp.length)
? tmp
: undefined
user.preferences = parseProp(user, 'preferences')
})
return users

View File

@ -8,6 +8,7 @@ import humanFormat from 'human-format'
import invert from 'lodash/invert'
import isArray from 'lodash/isArray'
import isString from 'lodash/isString'
import keys from 'lodash/keys'
import kindOf from 'kindof'
import multiKeyHashInt from 'multikey-hash'
import xml2js from 'xml2js'
@ -232,10 +233,12 @@ export const parseXml = (function () {
// - methods are already bound and chainable
export const lightSet = collection => {
const data = createRawObject()
collection && forEach(collection, value => {
data[value] = true
})
collection = null
if (collection) {
forEach(collection, value => {
data[value] = true
})
collection = null
}
const set = {
add: value => {
@ -252,7 +255,8 @@ export const lightSet = collection => {
delete data[value]
return set
},
has: value => data[value]
has: value => data[value],
toArray: () => keys(data)
}
return set
}

View File

@ -27,16 +27,19 @@ export default class {
}
async _getAclsForUser (userId) {
const subjects = (await this._xo.getUser(userId)).groups.concat(userId)
const user = await this._xo.getUser(userId)
const { groups } = user
const subjects = groups
? groups.concat(userId)
: [ userId ]
const acls = []
const pushAcls = (function (push) {
return function (entries) {
push.apply(acls, entries)
}
const pushAcls = (push => entries => {
push.apply(acls, entries)
})(acls.push)
const {_acls: collection} = this
const collection = this._acls
await Promise.all(mapToArray(
subjects,
subject => collection.get({subject}).then(pushAcls)

View File

@ -17,8 +17,9 @@ import {
Users
} from '../models/user'
import {
createRawObject,
forEach,
isEmpty,
lightSet,
mapToArray,
noop,
pCatch
@ -38,6 +39,11 @@ class NoSuchUser extends NoSuchObject {
}
}
const addToArraySet = (set, value) => set && !includes(set, value)
? set.concat(value)
: [ value ]
const removeFromArraySet = (set, value) => set && filter(set, current => current !== value)
// ===================================================================
export default class {
@ -109,7 +115,8 @@ export default class {
name = email,
password,
permission
permission,
preferences
}) {
const user = await this.getUser(id)
@ -123,6 +130,18 @@ export default class {
user.pw_hash = await hash(password)
}
const newPreferences = { ...user.preferences }
forEach(preferences, (value, name) => {
if (value == null) {
delete newPreferences[name]
} else {
newPreferences[name] = value
}
})
user.preferences = isEmpty(newPreferences)
? undefined
: newPreferences
// TODO: remove
user.email = user.name
delete user.name
@ -264,15 +283,8 @@ export default class {
this.getGroup(groupId)
])
const {groups} = user
if (!includes(groups, groupId)) {
user.groups.push(groupId)
}
const {users} = group
if (!includes(users, userId)) {
group.users.push(userId)
}
user.groups = addToArraySet(user.groups, groupId)
group.users = addToArraySet(group.users, userId)
await Promise.all([
this._users.save(user),
@ -281,14 +293,12 @@ export default class {
}
async _removeUserFromGroup (userId, group) {
// TODO: maybe not iterating through the whole arrays?
group.users = filter(group.users, id => id !== userId)
group.users = removeFromArraySet(group.users, userId)
return this._groups.save(group)
}
async _removeGroupFromUser (groupId, user) {
// TODO: maybe not iterating through the whole arrays?
user.groups = filter(user.groups, id => id !== groupId)
user.groups = removeFromArraySet(user.groups, groupId)
return this._users.save(user)
}
@ -307,39 +317,36 @@ export default class {
async setGroupUsers (groupId, userIds) {
const group = await this.getGroup(groupId)
const newUsersIds = createRawObject()
const oldUsersIds = createRawObject()
forEach(userIds, id => {
newUsersIds[id] = null
})
let newUsersIds = lightSet(userIds)
const oldUsersIds = []
forEach(group.users, id => {
if (id in newUsersIds) {
delete newUsersIds[id]
if (newUsersIds.has(id)) {
newUsersIds.delete(id)
} else {
oldUsersIds[id] = null
oldUsers.push(id)
}
})
newUsersIds = newUsersIds.toArray()
const getUser = ::this.getUser
const [newUsers, oldUsers] = await Promise.all([
Promise.all(mapToArray(newUsersIds, (_, id) => this.getUser(id))),
Promise.all(mapToArray(oldUsersIds, (_, id) => this.getUser(id)))
Promise.all(newUsersIds.map(getUser)),
Promise.all(oldUsersIds.map(getUser))
])
forEach(newUsers, user => {
const {groups} = user
if (!includes(groups, groupId)) {
user.groups.push(groupId)
}
user.groups = addToArraySet(user.groups, groupId)
})
forEach(oldUsers, user => {
user.groups = filter(user.groups, id => id !== groupId)
user.groups = removeFromArraySet(user.groups, groupId)
})
group.users = userIds
const saveUser = ::this._users.save
await Promise.all([
Promise.all(mapToArray(newUsers, ::this._users.save)),
Promise.all(mapToArray(oldUsers, ::this._users.save)),
Promise.all(mapToArray(newUsers, saveUser)),
Promise.all(mapToArray(oldUsers, saveUser)),
this._groups.save(group)
])
}