@flowfuse/flowfuse
Version:
An open source low-code development platform
193 lines (186 loc) • 8.16 kB
JavaScript
/**
* A DeviceGroup.
* A logical grouping of devices for the primary intent of group deployments in the pipeline stages.
* @namespace forge.db.models.DeviceGroup
*/
const { DataTypes, literal } = require('sequelize')
const { buildPaginationSearchClause } = require('../utils')
const nameValidator = { msg: 'Device Group name cannot be empty' }
module.exports = {
name: 'DeviceGroup',
schema: {
name: {
type: DataTypes.STRING,
allowNull: false,
validate: {
notEmpty: nameValidator,
notNull: nameValidator
}
},
description: { type: DataTypes.TEXT },
targetSnapshotId: { type: DataTypes.INTEGER, allowNull: true },
settings: {
type: DataTypes.TEXT,
set (value) {
this.setDataValue('settings', JSON.stringify(value))
},
get () {
const rawValue = this.getDataValue('settings') || '{}'
return JSON.parse(rawValue)
}
}
},
associations: function (M) {
this.belongsTo(M.Application, { onDelete: 'CASCADE' })
this.belongsTo(M.ProjectSnapshot, { as: 'targetSnapshot' })
this.hasMany(M.Device)
},
hooks: function (M, app) {
return {
afterUpdate: async (deviceGroup, options) => {
// if `settings` is updated, we need to update the settings hash
// of any member devices
if (deviceGroup.changed('settings')) {
const include = [
{ model: M.ProjectSnapshot, as: 'targetSnapshot', attributes: ['id', 'hashid', 'name'] },
{
model: M.DeviceGroup,
attributes: ['hashid', 'id', 'ApplicationId', 'settings']
},
{ model: M.Application, attributes: ['hashid', 'id', 'name', 'links'] }
]
const where = {
DeviceGroupId: deviceGroup.id,
ApplicationId: deviceGroup.ApplicationId
}
const devices = await M.Device.findAll({ where, include })
const updateAndSave = async (device) => {
await device.updateSettingsHash()
await device.save({
hooks: false, // skip the afterSave hook for device model (we have only updated the settings hash)
transaction: options?.transaction // pass the transaction (if any)
})
}
await Promise.all(devices.map(updateAndSave))
}
}
}
},
finders: function (M) {
const self = this
const deviceCountLiteral = literal(`(
SELECT COUNT(*)
FROM "Devices" AS "device"
WHERE
"device"."DeviceGroupId" = "DeviceGroup"."id"
AND
"device"."ApplicationId" = "DeviceGroup"."ApplicationId"
)`)
return {
static: {
byId: async function (id) {
if (typeof id === 'string') {
id = M.DeviceGroup.decodeHashid(id)
}
// find one, include application, devices and device count
return self.findOne({
where: { id },
include: [
{ model: M.Application, attributes: ['hashid', 'id', 'name', 'TeamId'] },
{
model: M.Device,
attributes: ['hashid', 'id', 'name', 'type', 'TeamId', 'ApplicationId', 'ProjectId', 'ownerType'],
where: {
ApplicationId: literal('"Devices"."ApplicationId" = "Application"."id"')
},
required: false
},
{
model: M.ProjectSnapshot,
as: 'targetSnapshot',
attributes: ['hashid', 'id', 'name']
}
],
attributes: {
include: [
[
deviceCountLiteral,
'deviceCount'
]
]
}
})
},
getAll: async (pagination = {}, where = {}, options = {}) => {
const { includeApplication = false } = options
const limit = parseInt(pagination.limit) || 1000
if (pagination.cursor) {
pagination.cursor = M.DeviceGroup.decodeHashid(pagination.cursor)
}
if (where.ApplicationId) {
if (typeof where.ApplicationId === 'string') {
where.ApplicationId = M.Application.decodeHashid(where.ApplicationId)
} else if (Array.isArray(where.ApplicationId)) {
where.ApplicationId = where.ApplicationId.map(hashId => {
if (typeof hashId === 'string') {
return M.Application.decodeHashid(hashId)
}
return hashId
})
}
}
const [rows, count] = await Promise.all([
this.findAll({
where: buildPaginationSearchClause(pagination, where, ['DeviceGroup.name', 'DeviceGroup.description']),
include: [
{
model: M.ProjectSnapshot,
as: 'targetSnapshot',
attributes: ['hashid', 'id', 'name']
},
...(includeApplication
? [{
model: M.Application,
as: 'Application',
attributes: ['hashid', 'id', 'name']
}]
: [])
],
attributes: {
include: [
[
deviceCountLiteral,
'deviceCount'
]
]
},
order: [['id', 'ASC']],
limit
}),
this.count({ where })
])
return {
meta: {
next_cursor: rows.length === limit ? rows[rows.length - 1].hashid : undefined
},
count,
groups: rows
}
}
},
instance: {
deviceCount: async function () {
return await M.Device.count({ where: { DeviceGroupId: this.id, ApplicationId: this.ApplicationId } })
},
getDevices: async function () {
return await M.Device.findAll({
where: {
DeviceGroupId: this.id,
ApplicationId: this.ApplicationId
}
})
}
}
}
}
}