chore(xo-server): remove Model wrapping

This commit is contained in:
Julien Fontanet 2022-06-20 17:38:15 +02:00
parent 9ef2c7da4c
commit 8a71f84733
19 changed files with 66 additions and 235 deletions

View File

@ -1,7 +1,6 @@
import Model from './model.mjs'
import { BaseError } from 'make-error' import { BaseError } from 'make-error'
import { EventEmitter } from 'events' import { EventEmitter } from 'events'
import { isObject, map } from './utils.mjs' import { isObject } from './utils.mjs'
// =================================================================== // ===================================================================
@ -14,34 +13,16 @@ export class ModelAlreadyExists extends BaseError {
// =================================================================== // ===================================================================
export default class Collection extends EventEmitter { export default class Collection extends EventEmitter {
// Default value for Model.
get Model() {
return Model
}
// Make this property writable.
set Model(Model) {
Object.defineProperty(this, 'Model', {
configurable: true,
enumerale: true,
value: Model,
writable: true,
})
}
async add(models, opts) { async add(models, opts) {
const array = Array.isArray(models) const array = Array.isArray(models)
if (!array) { if (!array) {
models = [models] models = [models]
} }
const { Model } = this
map(models, model => (model instanceof Model ? model.properties : model), models)
models = await this._add(models, opts) models = await this._add(models, opts)
this.emit('add', models) this.emit('add', models)
return array ? models : new this.Model(models[0]) return array ? models : models[0]
} }
async first(properties) { async first(properties) {
@ -49,8 +30,7 @@ export default class Collection extends EventEmitter {
properties = properties !== undefined ? { id: properties } : {} properties = properties !== undefined ? { id: properties } : {}
} }
const model = await this._first(properties) return await this._first(properties)
return model && new this.Model(model)
} }
async get(properties) { async get(properties) {
@ -93,33 +73,18 @@ export default class Collection extends EventEmitter {
models = [models] models = [models]
} }
const { Model } = this models.forEach(model => {
map( // Missing models should be added not updated.
models, if (model.id === undefined) {
model => { // FIXME: should not throw an exception but return a rejected promise.
if (!(model instanceof Model)) { throw new Error('a model without an id cannot be updated')
// TODO: Problems, we may be mixing in some default }
// properties which will overwrite existing ones. })
model = new Model(model)
}
const id = model.get('id')
// Missing models should be added not updated.
if (id === undefined) {
// FIXME: should not throw an exception but return a rejected promise.
throw new Error('a model without an id cannot be updated')
}
return model.properties
},
models
)
models = await this._update(models) models = await this._update(models)
this.emit('update', models) this.emit('update', models)
return array ? models : new this.Model(models[0]) return array ? models : models[0]
} }
// Methods to override in implementations. // Methods to override in implementations.

View File

@ -1,61 +0,0 @@
import { EventEmitter } from 'events'
import { forEach, isEmpty } from './utils.mjs'
// ===================================================================
export default class Model extends EventEmitter {
constructor(properties) {
super()
this.properties = { ...this.default }
if (properties) {
this.set(properties)
}
}
// Get a property.
get(name, def) {
const value = this.properties[name]
return value !== undefined ? value : def
}
// Check whether a property exists.
has(name) {
return this.properties[name] !== undefined
}
// Set properties.
set(properties, value) {
// This method can also be used with two arguments to set a single
// property.
if (typeof properties === 'string') {
properties = { [properties]: value }
}
const previous = {}
forEach(properties, (value, name) => {
const prev = this.properties[name]
if (value !== prev) {
previous[name] = prev
if (value === undefined) {
delete this.properties[name]
} else {
this.properties[name] = value
}
}
})
if (!isEmpty(previous)) {
this.emit('change', previous)
forEach(previous, (value, name) => {
this.emit('change:' + name, value)
})
}
}
}

View File

@ -1,5 +1,4 @@
import Collection from '../collection/redis.mjs' import Collection from '../collection/redis.mjs'
import Model from '../model.mjs'
import { forEach, multiKeyHash } from '../utils.mjs' import { forEach, multiKeyHash } from '../utils.mjs'
// =================================================================== // ===================================================================
@ -10,27 +9,16 @@ const DEFAULT_ACTION = 'admin'
// =================================================================== // ===================================================================
export default class Acl extends Model {}
// -------------------------------------------------------------------
export class Acls extends Collection { export class Acls extends Collection {
get Model() {
return Acl
}
create(subject, object, action) { create(subject, object, action) {
return multiKeyHash(subject, object, action) return multiKeyHash(subject, object, action).then(hash =>
.then( this.add({
hash => id: hash,
new Acl({ subject,
id: hash, object,
subject, action,
object, })
action, )
})
)
.then(acl => this.add(acl))
} }
delete(subject, object, action) { delete(subject, object, action) {

View File

@ -1,7 +1,6 @@
import isEmpty from 'lodash/isEmpty.js' import isEmpty from 'lodash/isEmpty.js'
import Collection from '../collection/redis.mjs' import Collection from '../collection/redis.mjs'
import Model from '../model.mjs'
import { forEach } from '../utils.mjs' import { forEach } from '../utils.mjs'
@ -9,17 +8,9 @@ import { parseProp } from './utils.mjs'
// =================================================================== // ===================================================================
export default class Group extends Model {}
// ===================================================================
export class Groups extends Collection { export class Groups extends Collection {
get Model() {
return Group
}
create(name, provider, providerGroupId) { create(name, provider, providerGroupId) {
return this.add(new Group({ name, provider, providerGroupId })) return this.add({ name, provider, providerGroupId })
} }
async save(group) { async save(group) {

View File

@ -1,5 +1,4 @@
import Collection from '../collection/redis.mjs' import Collection from '../collection/redis.mjs'
import Model from '../model.mjs'
import { createLogger } from '@xen-orchestra/log' import { createLogger } from '@xen-orchestra/log'
import { forEach } from '../utils.mjs' import { forEach } from '../utils.mjs'
@ -7,15 +6,7 @@ const log = createLogger('xo:plugin-metadata')
// =================================================================== // ===================================================================
export default class PluginMetadata extends Model {}
// ===================================================================
export class PluginsMetadata extends Collection { export class PluginsMetadata extends Collection {
get Model() {
return PluginMetadata
}
async save({ id, autoload, configuration }) { async save({ id, autoload, configuration }) {
return /* await */ this.update({ return /* await */ this.update({
id, id,
@ -31,7 +22,7 @@ export class PluginsMetadata extends Collection {
} }
return /* await */ this.save({ return /* await */ this.save({
...pluginMetadata.properties, ...pluginMetadata,
...data, ...data,
}) })
} }

View File

@ -1,18 +1,11 @@
import Collection from '../collection/redis.mjs' import Collection from '../collection/redis.mjs'
import Model from '../model.mjs'
import { forEach, serializeError } from '../utils.mjs' import { forEach, serializeError } from '../utils.mjs'
import { parseProp } from './utils.mjs' import { parseProp } from './utils.mjs'
// =================================================================== // ===================================================================
export default class Remote extends Model {}
export class Remotes extends Collection { export class Remotes extends Collection {
get Model() {
return Remote
}
async get(properties) { async get(properties) {
const remotes = await super.get(properties) const remotes = await super.get(properties)
forEach(remotes, remote => { forEach(remotes, remote => {

View File

@ -1,20 +1,11 @@
import Collection from '../collection/redis.mjs' import Collection from '../collection/redis.mjs'
import Model from '../model.mjs'
import { forEach, serializeError } from '../utils.mjs' import { forEach, serializeError } from '../utils.mjs'
import { parseProp } from './utils.mjs' import { parseProp } from './utils.mjs'
// =================================================================== // ===================================================================
export default class Server extends Model {}
// -------------------------------------------------------------------
export class Servers extends Collection { export class Servers extends Collection {
get Model() {
return Server
}
async create(params) { async create(params) {
const { host } = params const { host } = params

View File

@ -1,10 +1,5 @@
import Collection from '../collection/redis.mjs' import Collection from '../collection/redis.mjs'
import Model from '../model.mjs'
// =================================================================== // ===================================================================
export default class Token extends Model {}
// -------------------------------------------------------------------
export class Tokens extends Collection {} export class Tokens extends Collection {}

View File

@ -1,20 +1,11 @@
import isEmpty from 'lodash/isEmpty.js' import isEmpty from 'lodash/isEmpty.js'
import Collection from '../collection/redis.mjs' import Collection from '../collection/redis.mjs'
import Model from '../model.mjs'
import { parseProp } from './utils.mjs' import { parseProp } from './utils.mjs'
// =================================================================== // ===================================================================
export default class User extends Model {}
User.prototype.default = {
permission: 'none',
}
// -------------------------------------------------------------------
const serialize = user => { const serialize = user => {
let tmp let tmp
return { return {
@ -26,6 +17,7 @@ const serialize = user => {
} }
const deserialize = user => ({ const deserialize = user => ({
permission: 'none',
...user, ...user,
authProviders: parseProp('user', user, 'authProviders', undefined), authProviders: parseProp('user', user, 'authProviders', undefined),
groups: parseProp('user', user, 'groups', []), groups: parseProp('user', user, 'groups', []),
@ -33,10 +25,6 @@ const deserialize = user => ({
}) })
export class Users extends Collection { export class Users extends Collection {
get Model() {
return User
}
async create(properties) { async create(properties) {
const { email } = properties const { email } = properties
@ -45,11 +33,8 @@ export class Users extends Collection {
throw new Error(`the user ${email} already exists`) throw new Error(`the user ${email} already exists`)
} }
// Create the user object.
const user = new User(serialize(properties))
// Adds the user to the collection. // Adds the user to the collection.
return /* await */ this.add(user) return /* await */ this.add(serialize(properties))
} }
async save(user) { async save(user) {

View File

@ -55,7 +55,7 @@ export function extractProperty(obj, prop) {
// ------------------------------------------------------------------- // -------------------------------------------------------------------
export const getUserPublicProperties = user => export const getUserPublicProperties = user =>
pick(user.properties || user, 'authProviders', 'id', 'email', 'groups', 'permission', 'preferences') pick(user, 'authProviders', 'id', 'email', 'groups', 'permission', 'preferences')
// ------------------------------------------------------------------- // -------------------------------------------------------------------

View File

@ -5,7 +5,7 @@ import { invalidCredentials, noSuchObject } from 'xo-common/api-errors.js'
import { parseDuration } from '@vates/parse-duration' import { parseDuration } from '@vates/parse-duration'
import patch from '../patch.mjs' import patch from '../patch.mjs'
import Token, { Tokens } from '../models/token.mjs' import { Tokens } from '../models/token.mjs'
import { forEach, generateToken } from '../utils.mjs' import { forEach, generateToken } from '../utils.mjs'
// =================================================================== // ===================================================================
@ -183,18 +183,17 @@ export default class {
} }
const now = Date.now() const now = Date.now()
const token = new Token({ const token = {
created_at: now, created_at: now,
description, description,
id: await generateToken(), id: await generateToken(),
user_id: userId, user_id: userId,
expiration: now + duration, expiration: now + duration,
}) }
await this._tokens.add(token) await this._tokens.add(token)
// TODO: use plain properties directly. return token
return token.properties
} }
async deleteAuthenticationToken(id) { async deleteAuthenticationToken(id) {
@ -225,12 +224,11 @@ export default class {
async getAuthenticationToken(properties) { async getAuthenticationToken(properties) {
const id = typeof properties === 'string' ? properties : properties.id const id = typeof properties === 'string' ? properties : properties.id
let token = await this._tokens.first(properties) const token = await this._tokens.first(properties)
if (token === undefined) { if (token === undefined) {
throw noSuchAuthenticationToken(id) throw noSuchAuthenticationToken(id)
} }
token = token.properties
unserialize(token) unserialize(token)
if (!(token.expiration > Date.now())) { if (!(token.expiration > Date.now())) {

View File

@ -28,7 +28,7 @@ export default class {
} }
createCloudConfig(cloudConfig) { createCloudConfig(cloudConfig) {
return this._db.add(cloudConfig).properties return this._db.add(cloudConfig)
} }
async updateCloudConfig({ id, name, template }) { async updateCloudConfig({ id, name, template }) {
@ -54,6 +54,6 @@ export default class {
if (cloudConfig === undefined) { if (cloudConfig === undefined) {
throw noSuchObject(id, 'cloud config') throw noSuchObject(id, 'cloud config')
} }
return cloudConfig.properties return cloudConfig
} }
} }

View File

@ -49,7 +49,7 @@ const serialize = job => {
class JobsDb extends Collection { class JobsDb extends Collection {
async create(job) { async create(job) {
return normalize((await this.add(serialize(job))).properties) return normalize(await this.add(serialize(job)))
} }
async save(job) { async save(job) {
@ -137,12 +137,11 @@ export default class Jobs {
} }
async getJob(id, type) { async getJob(id, type) {
let job = await this._jobs.first(id) const job = await this._jobs.first(id)
if (job === undefined || (type !== undefined && job.properties.type !== type)) { if (job === undefined || (type !== undefined && job.type !== type)) {
throw noSuchObject(id, 'job') throw noSuchObject(id, 'job')
} }
job = job.properties
job.runId = this._runningJobs[id] job.runId = this._runningJobs[id]
return job return job

View File

@ -49,9 +49,8 @@ export default class {
return plugin return plugin
} }
async _getPluginMetadata(id) { _getPluginMetadata(id) {
const metadata = await this._pluginsMetadata.first(id) return this._pluginsMetadata.first(id)
return metadata?.properties
} }
async registerPlugin(name, instance, configurationSchema, configurationPresets, description, testSchema, version) { async registerPlugin(name, instance, configurationSchema, configurationPresets, description, testSchema, version) {

View File

@ -29,7 +29,6 @@ import { generateToken } from '../utils.mjs'
const DEBOUNCE_TIME_PROXY_STATE = 60000 const DEBOUNCE_TIME_PROXY_STATE = 60000
const extractProperties = _ => _.properties
const synchronizedWrite = synchronized() const synchronizedWrite = synchronized()
const log = createLogger('xo:proxy') const log = createLogger('xo:proxy')
@ -165,7 +164,7 @@ export default class Proxy {
if (proxy === undefined) { if (proxy === undefined) {
throw noSuchObject(id, 'proxy') throw noSuchObject(id, 'proxy')
} }
return extractProperties(proxy) return proxy
} }
async getProxy(id) { async getProxy(id) {
@ -190,7 +189,7 @@ export default class Proxy {
) )
patch(proxy, { address, authenticationToken, name, vmUuid }) patch(proxy, { address, authenticationToken, name, vmUuid })
return this._db.update(proxy).then(extractProperties) return this._db.update(proxy)
} }
async upgradeProxyAppliance(id, ignoreRunningJobs = false) { async upgradeProxyAppliance(id, ignoreRunningJobs = false) {

View File

@ -153,7 +153,7 @@ export default class {
if (remote === undefined) { if (remote === undefined) {
throw noSuchObject(id, 'remote') throw noSuchObject(id, 'remote')
} }
return remote.properties return remote
} }
async getRemoteWithCredentials(id) { async getRemoteWithCredentials(id) {
@ -184,7 +184,7 @@ export default class {
params.options = options params.options = options
} }
const remote = await this._remotes.add(params) const remote = await this._remotes.add(params)
return /* await */ this.updateRemote(remote.get('id'), { enabled: true }) return /* await */ this.updateRemote(remote.id, { enabled: true })
} }
updateRemote(id, { enabled, name, options, proxy, url }) { updateRemote(id, { enabled, name, options, proxy, url }) {
@ -215,7 +215,7 @@ export default class {
patch(remote, props) patch(remote, props)
return (await this._remotes.update(remote)).properties return await this._remotes.update(remote)
} }
async removeRemote(id) { async removeRemote(id) {

View File

@ -73,16 +73,14 @@ export default class Scheduling {
} }
async createSchedule({ cron, enabled, jobId, name = '', timezone, userId }) { async createSchedule({ cron, enabled, jobId, name = '', timezone, userId }) {
const schedule = ( const schedule = await this._db.add({
await this._db.add({ cron,
cron, enabled,
enabled, jobId,
jobId, name,
name, timezone,
timezone, userId,
userId, })
})
).properties
this._start(schedule) this._start(schedule)
return schedule return schedule
} }
@ -92,7 +90,7 @@ export default class Scheduling {
if (schedule === undefined) { if (schedule === undefined) {
throw noSuchObject(id, 'schedule') throw noSuchObject(id, 'schedule')
} }
return schedule.properties return schedule
} }
async getAllSchedules() { async getAllSchedules() {

View File

@ -85,7 +85,7 @@ export default class {
// TODO: use plain objects // TODO: use plain objects
const user = await this._users.create(properties) const user = await this._users.create(properties)
return user.properties return user
} }
async deleteUser(id) { async deleteUser(id) {
@ -183,7 +183,7 @@ export default class {
// TODO: this method will no longer be async when users are // TODO: this method will no longer be async when users are
// integrated to the main collection. // integrated to the main collection.
async getUser(id) { async getUser(id) {
const user = (await this._getUser(id)).properties const user = await this._getUser(id)
// TODO: remove when no longer the email property has been // TODO: remove when no longer the email property has been
// completely eradicated. // completely eradicated.
@ -200,7 +200,7 @@ export default class {
// TODO: change `email` by `username`. // TODO: change `email` by `username`.
const user = await this._users.first({ email: username }) const user = await this._users.first({ email: username })
if (user !== undefined) { if (user !== undefined) {
return user.properties return user
} }
if (returnNullIfMissing) { if (returnNullIfMissing) {
@ -323,7 +323,7 @@ export default class {
async createGroup({ name, provider, providerGroupId }) { async createGroup({ name, provider, providerGroupId }) {
// TODO: use plain objects. // TODO: use plain objects.
const group = (await this._groups.create(name, provider, providerGroupId)).properties const group = await this._groups.create(name, provider, providerGroupId)
return group return group
} }
@ -362,7 +362,7 @@ export default class {
throw noSuchObject(id, 'group') throw noSuchObject(id, 'group')
} }
return group.properties return group
} }
async getAllGroups() { async getAllGroups() {

View File

@ -120,7 +120,7 @@ export default class {
username, username,
}) })
return server.properties return server
} }
async unregisterXenServer(id) { async unregisterXenServer(id) {
@ -148,39 +148,39 @@ export default class {
throw new Error('this entry require disconnecting the server to update it') throw new Error('this entry require disconnecting the server to update it')
} }
if (label !== undefined) server.set('label', label || undefined) if (label !== undefined) server.label = label || undefined
if (host) server.set('host', host) if (host) server.host = host
if (username) server.set('username', username) if (username) server.username = username
if (password) server.set('password', password) if (password) server.password = password
if (error !== undefined) { if (error !== undefined) {
server.set('error', error) server.error = error
} }
if (enabled !== undefined) { if (enabled !== undefined) {
server.set('enabled', enabled) server.enabled = enabled
} }
if (readOnly !== undefined) { if (readOnly !== undefined) {
server.set('readOnly', readOnly) server.readOnly = readOnly
if (xapi !== undefined) { if (xapi !== undefined) {
xapi.readOnly = readOnly xapi.readOnly = readOnly
} }
} }
if (allowUnauthorized !== undefined) { if (allowUnauthorized !== undefined) {
server.set('allowUnauthorized', allowUnauthorized) server.allowUnauthorized = allowUnauthorized
} }
if (httpProxy !== undefined) { if (httpProxy !== undefined) {
// if value is null, pass undefined to the model , so it will delete this optionnal property from the Server object // if value is null, pass undefined to the model , so it will delete this optionnal property from the Server object
server.set('httpProxy', httpProxy === null ? undefined : httpProxy) server.httpProxy = httpProxy === null ? undefined : httpProxy
} }
await this._servers.update(server) await this._servers.update(server)
} }
async getXenServer(id) { async getXenServer(id) {
return (await this._getXenServer(id)).properties return await this._getXenServer(id)
} }
// TODO: this method will no longer be async when servers are // TODO: this method will no longer be async when servers are