mirror of
https://github.com/DeviceFarmer/stf.git
synced 2026-04-18 16:53:23 +02:00
add groups feature
This commit is contained in:
@@ -1,79 +1,527 @@
|
||||
/**
|
||||
* Copyright © 2019 contains code contributed by Orange SA, authors: Denis Barbaron - Licensed under the Apache license 2.0
|
||||
**/
|
||||
|
||||
var _ = require('lodash')
|
||||
var Promise = require('bluebird')
|
||||
|
||||
var dbapi = require('../../../db/api')
|
||||
var logger = require('../../../util/logger')
|
||||
var datautil = require('../../../util/datautil')
|
||||
|
||||
var log = logger.createLogger('api:controllers:devices')
|
||||
|
||||
module.exports = {
|
||||
getDevices: getDevices
|
||||
, getDeviceBySerial: getDeviceBySerial
|
||||
const apiutil = require('../../../util/apiutil')
|
||||
const lockutil = require('../../../util/lockutil')
|
||||
const util = require('util')
|
||||
const uuid = require('uuid')
|
||||
const wire = require('../../../wire')
|
||||
const wireutil = require('../../../wire/util')
|
||||
const wirerouter = require('../../../wire/router')
|
||||
|
||||
/* ------------------------------------ PRIVATE FUNCTIONS ------------------------------- */
|
||||
|
||||
function filterGenericDevices(req, res, devices) {
|
||||
apiutil.respond(res, 200, 'Devices Information', {
|
||||
devices: devices.map(function(device) {
|
||||
return apiutil.filterDevice(req, device)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function getDevices(req, res) {
|
||||
var fields = req.swagger.params.fields.value
|
||||
function getGenericDevices(req, res, loadDevices) {
|
||||
loadDevices(req.user.groups.subscribed).then(function(devices) {
|
||||
filterGenericDevices(req, res, devices)
|
||||
})
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, 'Failed to load device list: ', err.stack)
|
||||
})
|
||||
}
|
||||
|
||||
dbapi.loadDevices()
|
||||
.then(function(cursor) {
|
||||
return Promise.promisify(cursor.toArray, cursor)()
|
||||
.then(function(list) {
|
||||
var deviceList = []
|
||||
|
||||
list.forEach(function(device) {
|
||||
datautil.normalize(device, req.user)
|
||||
var responseDevice = device
|
||||
|
||||
if (fields) {
|
||||
responseDevice = _.pick(device, fields.split(','))
|
||||
}
|
||||
deviceList.push(responseDevice)
|
||||
})
|
||||
|
||||
res.json({
|
||||
success: true
|
||||
, devices: deviceList
|
||||
})
|
||||
})
|
||||
function getDeviceFilteredGroups(serial, fields, bookingOnly) {
|
||||
return dbapi.getDeviceGroups(serial).then(function(groups) {
|
||||
return Promise.map(groups, function(group) {
|
||||
return !bookingOnly || !apiutil.isOriginGroup(group.class) ?
|
||||
group :
|
||||
'filtered'
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Failed to load device list: ', err.stack)
|
||||
res.status(500).json({
|
||||
success: false
|
||||
.then(function(groups) {
|
||||
return _.without(groups, 'filtered').map(function(group) {
|
||||
if (fields) {
|
||||
return _.pick(apiutil.publishGroup(group), fields.split(','))
|
||||
}
|
||||
return apiutil.publishGroup(group)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function extractStandardizableDevices(devices) {
|
||||
return dbapi.getTransientGroups().then(function(groups) {
|
||||
return Promise.map(devices, function(device) {
|
||||
return Promise.map(groups, function(group) {
|
||||
if (group.devices.indexOf(device.serial) > -1) {
|
||||
return Promise.reject('booked')
|
||||
}
|
||||
return true
|
||||
})
|
||||
.then(function() {
|
||||
return device
|
||||
})
|
||||
.catch(function(err) {
|
||||
if (err !== 'booked') {
|
||||
throw err
|
||||
}
|
||||
return err
|
||||
})
|
||||
})
|
||||
.then(function(devices) {
|
||||
return _.without(devices, 'booked')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function getStandardizableDevices(req, res) {
|
||||
dbapi.loadDevicesByOrigin(req.user.groups.subscribed).then(function(devices) {
|
||||
extractStandardizableDevices(devices).then(function(devices) {
|
||||
filterGenericDevices(req, res, devices)
|
||||
})
|
||||
})
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, 'Failed to load device list: ', err.stack)
|
||||
})
|
||||
}
|
||||
|
||||
function removeDevice(serial, req, res) {
|
||||
const presentState = req.swagger.params.present.value
|
||||
const bookingState = req.swagger.params.booked.value
|
||||
const notesState = req.swagger.params.annotated.value
|
||||
const controllingState = req.swagger.params.controlled.value
|
||||
const anyPresentState = typeof presentState === 'undefined'
|
||||
const anyBookingState = typeof bookingState === 'undefined'
|
||||
const anyNotesState = typeof notesState === 'undefined'
|
||||
const anyControllingState = typeof controllingState === 'undefined'
|
||||
const lock = {}
|
||||
|
||||
function deleteGroupDevice(email, id) {
|
||||
const lock = {}
|
||||
|
||||
return dbapi.lockGroupByOwner(email, id).then(function(stats) {
|
||||
if (!stats.replaced) {
|
||||
return apiutil.lightComputeStats(res, stats)
|
||||
}
|
||||
const group = lock.group = stats.changes[0].new_val
|
||||
|
||||
if (group.devices.indexOf(serial) > -1) {
|
||||
return apiutil.isOriginGroup(group.class) ?
|
||||
dbapi.removeOriginGroupDevice(group, serial) :
|
||||
dbapi.removeGroupDevices(group, [serial])
|
||||
}
|
||||
return group
|
||||
})
|
||||
.finally(function() {
|
||||
lockutil.unlockGroup(lock)
|
||||
})
|
||||
}
|
||||
|
||||
function deleteDeviceInDatabase() {
|
||||
function wrappedDeleteDeviceInDatabase() {
|
||||
const result = {
|
||||
status: false
|
||||
, data: 'not deleted'
|
||||
}
|
||||
|
||||
return dbapi.loadDeviceBySerial(serial).then(function(device) {
|
||||
if (device && device.group.id === device.group.origin) {
|
||||
return deleteGroupDevice(device.group.owner.email, device.group.id)
|
||||
.then(function(group) {
|
||||
if (group !== 'not found') {
|
||||
return dbapi.deleteDevice(serial).then(function() {
|
||||
result.status = true
|
||||
result.data = 'deleted'
|
||||
})
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
return false
|
||||
})
|
||||
.then(function() {
|
||||
return result
|
||||
})
|
||||
}
|
||||
return apiutil.setIntervalWrapper(
|
||||
wrappedDeleteDeviceInDatabase
|
||||
, 10
|
||||
, Math.random() * 500 + 50)
|
||||
}
|
||||
|
||||
return dbapi.lockDeviceByOrigin(req.user.groups.subscribed, serial).then(function(stats) {
|
||||
if (!stats.replaced) {
|
||||
return apiutil.lightComputeStats(res, stats)
|
||||
}
|
||||
const device = lock.device = stats.changes[0].new_val
|
||||
|
||||
if (!anyPresentState && device.present !== presentState ||
|
||||
!anyControllingState && (device.owner === null) === controllingState ||
|
||||
!anyNotesState &&
|
||||
(typeof device.notes !== 'undefined' && device.notes !== '') !== notesState ||
|
||||
!anyBookingState && (device.group.id !== device.group.origin && !bookingState ||
|
||||
device.group.class === apiutil.STANDARD && bookingState)) {
|
||||
return 'unchanged'
|
||||
}
|
||||
if (device.group.class === apiutil.STANDARD) {
|
||||
return deleteDeviceInDatabase()
|
||||
}
|
||||
return dbapi.getDeviceTransientGroups(serial).then(function(groups) {
|
||||
if (groups.length && !anyBookingState && !bookingState) {
|
||||
return 'unchanged'
|
||||
}
|
||||
return Promise.each(groups, function(group) {
|
||||
return deleteGroupDevice(group.owner.email, group.id)
|
||||
})
|
||||
.then(function() {
|
||||
if (!groups.length && !anyBookingState && bookingState) {
|
||||
return 'unchanged'
|
||||
}
|
||||
return deleteDeviceInDatabase()
|
||||
})
|
||||
})
|
||||
})
|
||||
.finally(function() {
|
||||
lockutil.unlockDevice(lock)
|
||||
})
|
||||
}
|
||||
|
||||
/* ------------------------------------ PUBLIC FUNCTIONS ------------------------------- */
|
||||
|
||||
function getDevices(req, res) {
|
||||
const target = req.swagger.params.target.value
|
||||
|
||||
switch(target) {
|
||||
case apiutil.BOOKABLE:
|
||||
getGenericDevices(req, res, dbapi.loadBookableDevices)
|
||||
break
|
||||
case apiutil.ORIGIN:
|
||||
getGenericDevices(req, res, dbapi.loadDevicesByOrigin)
|
||||
break
|
||||
case apiutil.STANDARD:
|
||||
getGenericDevices(req, res, dbapi.loadStandardDevices)
|
||||
break
|
||||
case apiutil.STANDARDIZABLE:
|
||||
getStandardizableDevices(req, res)
|
||||
break
|
||||
default:
|
||||
getGenericDevices(req, res, dbapi.loadDevices)
|
||||
}
|
||||
}
|
||||
|
||||
function getDeviceBySerial(req, res) {
|
||||
var serial = req.swagger.params.serial.value
|
||||
var fields = req.swagger.params.fields.value
|
||||
|
||||
dbapi.loadDevice(serial)
|
||||
.then(function(device) {
|
||||
if (!device) {
|
||||
return res.status(404).json({
|
||||
success: false
|
||||
, description: 'Device not found'
|
||||
dbapi.loadDevice(req.user.groups.subscribed, serial)
|
||||
.then(function(cursor) {
|
||||
cursor.next(function(err, device) {
|
||||
if (err) {
|
||||
return res.status(404).json({
|
||||
success: false
|
||||
, description: 'Device not found'
|
||||
})
|
||||
}
|
||||
let responseDevice = apiutil.publishDevice(device, req.user)
|
||||
|
||||
if (fields) {
|
||||
responseDevice = _.pick(device, fields.split(','))
|
||||
}
|
||||
res.json({
|
||||
success: true
|
||||
, device: responseDevice
|
||||
})
|
||||
}
|
||||
|
||||
datautil.normalize(device, req.user)
|
||||
var responseDevice = device
|
||||
|
||||
if (fields) {
|
||||
responseDevice = _.pick(device, fields.split(','))
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true
|
||||
, device: responseDevice
|
||||
})
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Failed to load device "%s": ', req.params.serial, err.stack)
|
||||
log.error('Failed to load device "%s": ', serial, err.stack)
|
||||
res.status(500).json({
|
||||
success: false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function getDeviceGroups(req, res) {
|
||||
const serial = req.swagger.params.serial.value
|
||||
const fields = req.swagger.params.fields.value
|
||||
|
||||
dbapi.loadDevice(req.user.groups.subscribed, serial).then(function(cursor) {
|
||||
return cursor.toArray()
|
||||
})
|
||||
.then(function(devices) {
|
||||
if (!devices.length) {
|
||||
apiutil.respond(res, 404, 'Not Found (device)')
|
||||
}
|
||||
else {
|
||||
getDeviceFilteredGroups(serial, fields, false)
|
||||
.then(function(groups) {
|
||||
return apiutil.respond(res, 200, 'Groups Information', {groups: groups})
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, 'Failed to get device groups: ', err.stack)
|
||||
})
|
||||
}
|
||||
|
||||
function getDeviceBookings(req, res) {
|
||||
const serial = req.swagger.params.serial.value
|
||||
const fields = req.swagger.params.fields.value
|
||||
|
||||
dbapi.loadDevice(req.user.groups.subscribed, serial).then(function(cursor) {
|
||||
return cursor.toArray()
|
||||
})
|
||||
.then(function(devices) {
|
||||
if (!devices.length) {
|
||||
apiutil.respond(res, 404, 'Not Found (device)')
|
||||
}
|
||||
else {
|
||||
getDeviceFilteredGroups(serial, fields, true)
|
||||
.then(function(bookings) {
|
||||
apiutil.respond(res, 200, 'Bookings Information', {bookings: bookings})
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, 'Failed to get device bookings: ', err.stack)
|
||||
})
|
||||
}
|
||||
|
||||
function addOriginGroupDevices(req, res) {
|
||||
const serials = apiutil.getBodyParameter(req.body, 'serials')
|
||||
const fields = apiutil.getQueryParameter(req.swagger.params.fields)
|
||||
const target = apiutil.getQueryParameter(req.swagger.params.redirected) ? 'device' : 'devices'
|
||||
const lock = {}
|
||||
|
||||
function askUpdateDeviceOriginGroup(group, serial) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
const signature = util.format('%s', uuid.v4()).replace(/-/g, '')
|
||||
let messageListener
|
||||
const responseTimer = setTimeout(function() {
|
||||
req.options.channelRouter.removeListener(wireutil.global, messageListener)
|
||||
apiutil.respond(res, 504, 'Gateway Time-out')
|
||||
reject('timeout')
|
||||
}, 5000)
|
||||
|
||||
messageListener = wirerouter()
|
||||
.on(wire.DeviceOriginGroupMessage, function(channel, message) {
|
||||
if (message.signature === signature) {
|
||||
clearTimeout(responseTimer)
|
||||
req.options.channelRouter.removeListener(wireutil.global, messageListener)
|
||||
dbapi.loadDeviceBySerial(serial).then(function(device) {
|
||||
if (fields) {
|
||||
resolve(_.pick(apiutil.publishDevice(device, req.user), fields.split(',')))
|
||||
}
|
||||
else {
|
||||
resolve(apiutil.publishDevice(device, req.user))
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.handler()
|
||||
|
||||
req.options.channelRouter.on(wireutil.global, messageListener)
|
||||
return dbapi.askUpdateDeviceOriginGroup(serial, group, signature)
|
||||
})
|
||||
}
|
||||
|
||||
function updateDeviceOriginGroup(group, serial) {
|
||||
const lock = {}
|
||||
|
||||
return dbapi.lockDeviceByOrigin(req.user.groups.subscribed, serial).then(function(stats) {
|
||||
if (!stats.replaced) {
|
||||
return apiutil.lightComputeStats(res, stats)
|
||||
}
|
||||
lock.device = stats.changes[0].new_val
|
||||
|
||||
return dbapi.isUpdateDeviceOriginGroupAllowed(serial, group)
|
||||
.then(function(updatingAllowed) {
|
||||
if (!updatingAllowed) {
|
||||
apiutil.respond(res, 403, 'Forbidden (device is currently booked)')
|
||||
return Promise.reject('booked')
|
||||
}
|
||||
return askUpdateDeviceOriginGroup(group, serial)
|
||||
})
|
||||
})
|
||||
.finally(function() {
|
||||
lockutil.unlockDevice(lock)
|
||||
})
|
||||
}
|
||||
|
||||
function updateDevicesOriginGroup(group, serials) {
|
||||
let results = []
|
||||
|
||||
return Promise.each(serials, function(serial) {
|
||||
return updateDeviceOriginGroup(group, serial).then(function(result) {
|
||||
results.push(result)
|
||||
})
|
||||
})
|
||||
.then(function() {
|
||||
const result = target === 'device' ? {device: {}} : {devices: []}
|
||||
|
||||
results = _.without(results, 'unchanged')
|
||||
if (!results.length) {
|
||||
return apiutil.respond(res, 200, `Unchanged (${target})`, result)
|
||||
}
|
||||
results = _.without(results, 'not found')
|
||||
if (!results.length) {
|
||||
return apiutil.respond(res, 404, `Not Found (${target})`)
|
||||
}
|
||||
if (target === 'device') {
|
||||
result.device = results[0]
|
||||
}
|
||||
else {
|
||||
result.devices = results
|
||||
}
|
||||
return apiutil.respond(res, 200, `Updated (${target})`, result)
|
||||
})
|
||||
.catch(function(err) {
|
||||
if (err !== 'booked' && err !== 'timeout' && err !== 'busy') {
|
||||
throw err
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return lockutil.lockGroup(req, res, lock).then(function(lockingSuccessed) {
|
||||
if (lockingSuccessed) {
|
||||
const group = lock.group
|
||||
|
||||
if (!apiutil.isOriginGroup(group.class)) {
|
||||
return apiutil.respond(res, 400, 'Bad Request (this group cannot act as an origin one)')
|
||||
}
|
||||
if (typeof serials !== 'undefined') {
|
||||
return updateDevicesOriginGroup(
|
||||
group
|
||||
, _.without(serials.split(','), '').filter(function(serial) {
|
||||
return group.devices.indexOf(serial) < 0
|
||||
})
|
||||
)
|
||||
}
|
||||
return dbapi.loadDevicesByOrigin(req.user.groups.subscribed).then(function(devices) {
|
||||
if (group.class === apiutil.BOOKABLE) {
|
||||
return devices
|
||||
}
|
||||
return extractStandardizableDevices(devices)
|
||||
})
|
||||
.then(function(devices) {
|
||||
const serials = []
|
||||
|
||||
devices.forEach(function(device) {
|
||||
if (group.devices.indexOf(device.serial) < 0) {
|
||||
serials.push(device.serial)
|
||||
}
|
||||
})
|
||||
return updateDevicesOriginGroup(group, serials)
|
||||
})
|
||||
}
|
||||
return false
|
||||
})
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, `Failed to update ${target} origin group: `, err.stack)
|
||||
})
|
||||
.finally(function() {
|
||||
lockutil.unlockGroup(lock)
|
||||
})
|
||||
}
|
||||
|
||||
function addOriginGroupDevice(req, res) {
|
||||
apiutil.redirectApiWrapper('serial', addOriginGroupDevices, req, res)
|
||||
}
|
||||
|
||||
function removeOriginGroupDevices(req, res) {
|
||||
const lock = {}
|
||||
|
||||
return lockutil.lockGroup(req, res, lock).then(function(lockingSuccessed) {
|
||||
if (lockingSuccessed) {
|
||||
const group = lock.group
|
||||
|
||||
if (!apiutil.checkBodyParameter(req.body, 'serials')) {
|
||||
req.body = {serials: group.devices.join()}
|
||||
}
|
||||
return dbapi.getRootGroup().then(function(group) {
|
||||
req.swagger.params.id = {value: group.id}
|
||||
return addOriginGroupDevices(req, res)
|
||||
})
|
||||
}
|
||||
return false
|
||||
})
|
||||
.finally(function() {
|
||||
lockutil.unlockGroup(lock)
|
||||
})
|
||||
}
|
||||
|
||||
function removeOriginGroupDevice(req, res) {
|
||||
apiutil.redirectApiWrapper('serial', removeOriginGroupDevices, req, res)
|
||||
}
|
||||
|
||||
function deleteDevices(req, res) {
|
||||
const serials = apiutil.getBodyParameter(req.body, 'serials')
|
||||
const target = apiutil.getQueryParameter(req.swagger.params.redirected) ? 'device' : 'devices'
|
||||
|
||||
function removeDevices(serials) {
|
||||
let results = []
|
||||
|
||||
return Promise.each(serials, function(serial) {
|
||||
return removeDevice(serial, req, res).then(function(result) {
|
||||
if (result === 'not deleted') {
|
||||
apiutil.respond(res, 503, 'Server too busy [code: 2], please try again later')
|
||||
return Promise.reject('busy')
|
||||
}
|
||||
return results.push(result)
|
||||
})
|
||||
})
|
||||
.then(function() {
|
||||
results = _.without(results, 'unchanged')
|
||||
if (!results.length) {
|
||||
return apiutil.respond(res, 200, `Unchanged (${target})`)
|
||||
}
|
||||
if (!_.without(results, 'not found').length) {
|
||||
return apiutil.respond(res, 404, `Not Found (${target})`)
|
||||
}
|
||||
return apiutil.respond(res, 200, `Deleted (${target})`)
|
||||
})
|
||||
.catch(function(err) {
|
||||
if (err !== 'busy') {
|
||||
throw err
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
(function() {
|
||||
if (typeof serials === 'undefined') {
|
||||
return dbapi.loadDevicesByOrigin(req.user.groups.subscribed).then(function(devices) {
|
||||
return removeDevices(devices.map(function(device) {
|
||||
return device.serial
|
||||
}))
|
||||
})
|
||||
}
|
||||
else {
|
||||
return removeDevices(_.without(serials.split(','), ''))
|
||||
}
|
||||
})()
|
||||
.catch(function(err) {
|
||||
apiutil.internalError(res, `Failed to delete ${target}: `, err.stack)
|
||||
})
|
||||
}
|
||||
|
||||
function deleteDevice(req, res) {
|
||||
apiutil.redirectApiWrapper('serial', deleteDevices, req, res)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getDevices: getDevices
|
||||
, getDeviceBySerial: getDeviceBySerial
|
||||
, getDeviceGroups: getDeviceGroups
|
||||
, getDeviceBookings: getDeviceBookings
|
||||
, addOriginGroupDevice: addOriginGroupDevice
|
||||
, addOriginGroupDevices: addOriginGroupDevices
|
||||
, removeOriginGroupDevice: removeOriginGroupDevice
|
||||
, removeOriginGroupDevices: removeOriginGroupDevices
|
||||
, deleteDevice: deleteDevice
|
||||
, deleteDevices: deleteDevices
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user