import { BaseModel } from '#database/base_model' import { DeviceRecord } from '../domain/device/device_record.js' import { DeviceId } from '../domain/device/device_id.js' import { DeviceComplianceInfo } from '../domain/configuration/compliance.js' import { Filter, Sort } from 'mongodb' export default class DeviceModel extends BaseModel { static collectionName = 'devices' static async findByIdentifier(identifier: string): Promise { return this.findOne({ identifier }) } static async findByDeviceId(deviceId: DeviceId): Promise { return this.findOne({ 'deviceId.manufacturer': deviceId.manufacturer, 'deviceId.oui': deviceId.oui, 'deviceId.productClass': deviceId.productClass, 'deviceId.serialNumber': deviceId.serialNumber, }) } static async paginateDevices( offset: number, limit: number, filter: Filter = {} ): Promise<{ data: DeviceRecord[]; total: number }> { const collection = this.getCollection() const total = await collection.countDocuments(filter) const data = (await collection .find(filter, { projection: { _id: 0 } }) .skip(offset) .limit(limit) .sort({ updatedAt: -1 } as Sort) .toArray()) as unknown as DeviceRecord[] return { data, total } } static async createOrUpdate(device: Partial): Promise { const collection = this.getCollection() let filter: Filter = {} if (device.deviceId) { filter = { deviceId: device.deviceId } } else if (device.identifier) { filter = { identifier: device.identifier } } else { throw new Error('Either identifier or deviceId must be provided') } const result = await collection.findOneAndUpdate( filter, { $set: { ...device, updatedAt: new Date() }, $setOnInsert: { createdAt: new Date() }, }, { upsert: true, returnDocument: 'after', projection: { _id: 0 } } ) return result as unknown as DeviceRecord } static async updateCompliance( identifier: string, compliance: DeviceComplianceInfo ): Promise { await this.updateOne({ identifier }, { compliance }) } static async updateProvisioningCode( identifier: string, provisioningCode: string ): Promise { await this.updateOne({ identifier }, { provisioningCode }) } static async findByComplianceStatus(status: string): Promise { const collection = this.getCollection() return collection .find({ 'compliance.status': status }) .toArray() as unknown as Promise } static async findByConfigurationIdentifier(configurationIdentifier: string): Promise { const collection = this.getCollection() return collection .find({ 'compliance.configurationIdentifier': configurationIdentifier }) .toArray() as unknown as Promise } static async distinctCriteriaValues(): Promise<{ manufacturer: string[] oui: string[] productClass: string[] serialNumber: string[] modelName: string[] hardwareVersion: string[] softwareVersion: string[] parameterNames: string[] }> { const collection = this.getCollection() const distinctProperty = async (paths: string[]): Promise => { const rows = await collection .aggregate([ { $unwind: '$properties' }, { $match: { 'properties.name': { $in: paths } } }, { $group: { _id: '$properties.value' } }, ]) .toArray() return rows.map((r) => String(r._id)).filter(Boolean).sort() } const [manufacturer, oui, productClass, serialNumber, modelName, hardwareVersion, softwareVersion, parameterNames] = await Promise.all([ collection.distinct('deviceId.manufacturer'), collection.distinct('deviceId.oui'), collection.distinct('deviceId.productClass'), collection.distinct('deviceId.serialNumber'), distinctProperty(['Device.DeviceInfo.ModelName', 'InternetGatewayDevice.DeviceInfo.ModelName']), distinctProperty(['Device.DeviceInfo.HardwareVersion', 'InternetGatewayDevice.DeviceInfo.HardwareVersion']), distinctProperty(['Device.DeviceInfo.SoftwareVersion', 'InternetGatewayDevice.DeviceInfo.SoftwareVersion']), collection.distinct('properties.name'), ]) return { manufacturer: (manufacturer as string[]).filter(Boolean).sort(), oui: (oui as string[]).filter(Boolean).sort(), productClass: (productClass as string[]).filter(Boolean).sort(), serialNumber: (serialNumber as string[]).filter(Boolean).sort(), modelName, hardwareVersion, softwareVersion, parameterNames: (parameterNames as string[]).filter(Boolean).sort(), } } static async countByComplianceStatus( configurationIdentifier: string ): Promise<{ compliant: number; in_progress: number; failed: number; pending: number }> { const collection = this.getCollection() const results = await collection .aggregate([ { $match: { 'compliance.configurationIdentifier': configurationIdentifier } }, { $group: { _id: '$compliance.status', count: { $sum: 1 } } }, ]) .toArray() const counts = { compliant: 0, in_progress: 0, failed: 0, pending: 0 } for (const r of results) { if (r._id === 'compliant') counts.compliant = r.count else if (r._id === 'in_progress') counts.in_progress = r.count else if (r._id === 'failed') counts.failed = r.count else if (r._id === 'pending') counts.pending = r.count } return counts } /** * Reset compliance status to 'pending' for all devices linked to a configuration. * Called after a behavioral configuration update to force re-evaluation on next Inform. */ static async resetComplianceForConfiguration(configurationIdentifier: string): Promise { const collection = this.getCollection() await collection.updateMany( { 'compliance.configurationIdentifier': configurationIdentifier }, { $set: { 'compliance.status': 'pending', 'compliance.lastError': null, updatedAt: new Date(), }, } ) } }