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')) { if (permission && id === this.session.get('user_id')) {
throw new InvalidParameters('a user cannot change it\'s own permission') 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' set.description = 'changes the properties of an existing user'
@ -72,7 +72,8 @@ set.params = {
id: { type: 'string' }, id: { type: 'string' },
email: { type: 'string', optional: true }, email: { type: 'string', optional: true },
password: { 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. // Handles both properties and wrapped models.
const properties = user.properties || user 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) { throw (errorId, data) {

View File

@ -1,3 +1,5 @@
import isEmpty from 'lodash/isEmpty'
import Collection from '../collection/redis' import Collection from '../collection/redis'
import Model from '../model' import Model from '../model'
import { forEach } from '../utils' 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 { export class Users extends Collection {
get Model () { get Model () {
return User return User
@ -35,7 +49,13 @@ export class Users extends Collection {
async save (user) { async save (user) {
// Serializes. // 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) return /* await */ this.update(user)
} }
@ -45,13 +65,11 @@ export class Users extends Collection {
// Deserializes // Deserializes
forEach(users, user => { forEach(users, user => {
const {groups} = user let tmp
try { user.groups = ((tmp = parseProp(user, 'groups')) && tmp.length)
user.groups = groups ? JSON.parse(groups) : [] ? tmp
} catch (_) { : undefined
console.warn('cannot parse user.groups:', groups) user.preferences = parseProp(user, 'preferences')
user.groups = []
}
}) })
return users return users

View File

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

View File

@ -27,16 +27,19 @@ export default class {
} }
async _getAclsForUser (userId) { 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 acls = []
const pushAcls = (function (push) { const pushAcls = (push => entries => {
return function (entries) { push.apply(acls, entries)
push.apply(acls, entries)
}
})(acls.push) })(acls.push)
const {_acls: collection} = this const collection = this._acls
await Promise.all(mapToArray( await Promise.all(mapToArray(
subjects, subjects,
subject => collection.get({subject}).then(pushAcls) subject => collection.get({subject}).then(pushAcls)

View File

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