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 { 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 {
// 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) {
const array = Array.isArray(models)
if (!array) {
models = [models]
}
const { Model } = this
map(models, model => (model instanceof Model ? model.properties : model), models)
models = await this._add(models, opts)
this.emit('add', models)
return array ? models : new this.Model(models[0])
return array ? models : models[0]
}
async first(properties) {
@ -49,8 +30,7 @@ export default class Collection extends EventEmitter {
properties = properties !== undefined ? { id: properties } : {}
}
const model = await this._first(properties)
return model && new this.Model(model)
return await this._first(properties)
}
async get(properties) {
@ -93,33 +73,18 @@ export default class Collection extends EventEmitter {
models = [models]
}
const { Model } = this
map(
models,
model => {
if (!(model instanceof Model)) {
// 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.forEach(model => {
// Missing models should be added not updated.
if (model.id === undefined) {
// FIXME: should not throw an exception but return a rejected promise.
throw new Error('a model without an id cannot be updated')
}
})
models = await this._update(models)
this.emit('update', models)
return array ? models : new this.Model(models[0])
return array ? models : models[0]
}
// 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 Model from '../model.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 {
get Model() {
return Acl
}
create(subject, object, action) {
return multiKeyHash(subject, object, action)
.then(
hash =>
new Acl({
id: hash,
subject,
object,
action,
})
)
.then(acl => this.add(acl))
return multiKeyHash(subject, object, action).then(hash =>
this.add({
id: hash,
subject,
object,
action,
})
)
}
delete(subject, object, action) {

View File

@ -1,7 +1,6 @@
import isEmpty from 'lodash/isEmpty.js'
import Collection from '../collection/redis.mjs'
import Model from '../model.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 {
get Model() {
return Group
}
create(name, provider, providerGroupId) {
return this.add(new Group({ name, provider, providerGroupId }))
return this.add({ name, provider, providerGroupId })
}
async save(group) {

View File

@ -1,5 +1,4 @@
import Collection from '../collection/redis.mjs'
import Model from '../model.mjs'
import { createLogger } from '@xen-orchestra/log'
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 {
get Model() {
return PluginMetadata
}
async save({ id, autoload, configuration }) {
return /* await */ this.update({
id,
@ -31,7 +22,7 @@ export class PluginsMetadata extends Collection {
}
return /* await */ this.save({
...pluginMetadata.properties,
...pluginMetadata,
...data,
})
}

View File

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

View File

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

View File

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

View File

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

View File

@ -55,7 +55,7 @@ export function extractProperty(obj, prop) {
// -------------------------------------------------------------------
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 patch from '../patch.mjs'
import Token, { Tokens } from '../models/token.mjs'
import { Tokens } from '../models/token.mjs'
import { forEach, generateToken } from '../utils.mjs'
// ===================================================================
@ -183,18 +183,17 @@ export default class {
}
const now = Date.now()
const token = new Token({
const token = {
created_at: now,
description,
id: await generateToken(),
user_id: userId,
expiration: now + duration,
})
}
await this._tokens.add(token)
// TODO: use plain properties directly.
return token.properties
return token
}
async deleteAuthenticationToken(id) {
@ -225,12 +224,11 @@ export default class {
async getAuthenticationToken(properties) {
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) {
throw noSuchAuthenticationToken(id)
}
token = token.properties
unserialize(token)
if (!(token.expiration > Date.now())) {

View File

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

View File

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

View File

@ -49,9 +49,8 @@ export default class {
return plugin
}
async _getPluginMetadata(id) {
const metadata = await this._pluginsMetadata.first(id)
return metadata?.properties
_getPluginMetadata(id) {
return this._pluginsMetadata.first(id)
}
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 extractProperties = _ => _.properties
const synchronizedWrite = synchronized()
const log = createLogger('xo:proxy')
@ -165,7 +164,7 @@ export default class Proxy {
if (proxy === undefined) {
throw noSuchObject(id, 'proxy')
}
return extractProperties(proxy)
return proxy
}
async getProxy(id) {
@ -190,7 +189,7 @@ export default class Proxy {
)
patch(proxy, { address, authenticationToken, name, vmUuid })
return this._db.update(proxy).then(extractProperties)
return this._db.update(proxy)
}
async upgradeProxyAppliance(id, ignoreRunningJobs = false) {

View File

@ -153,7 +153,7 @@ export default class {
if (remote === undefined) {
throw noSuchObject(id, 'remote')
}
return remote.properties
return remote
}
async getRemoteWithCredentials(id) {
@ -184,7 +184,7 @@ export default class {
params.options = options
}
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 }) {
@ -215,7 +215,7 @@ export default class {
patch(remote, props)
return (await this._remotes.update(remote)).properties
return await this._remotes.update(remote)
}
async removeRemote(id) {

View File

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

View File

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

View File

@ -120,7 +120,7 @@ export default class {
username,
})
return server.properties
return server
}
async unregisterXenServer(id) {
@ -148,39 +148,39 @@ export default class {
throw new Error('this entry require disconnecting the server to update it')
}
if (label !== undefined) server.set('label', label || undefined)
if (host) server.set('host', host)
if (username) server.set('username', username)
if (password) server.set('password', password)
if (label !== undefined) server.label = label || undefined
if (host) server.host = host
if (username) server.username = username
if (password) server.password = password
if (error !== undefined) {
server.set('error', error)
server.error = error
}
if (enabled !== undefined) {
server.set('enabled', enabled)
server.enabled = enabled
}
if (readOnly !== undefined) {
server.set('readOnly', readOnly)
server.readOnly = readOnly
if (xapi !== undefined) {
xapi.readOnly = readOnly
}
}
if (allowUnauthorized !== undefined) {
server.set('allowUnauthorized', allowUnauthorized)
server.allowUnauthorized = allowUnauthorized
}
if (httpProxy !== undefined) {
// 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)
}
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