From fe934a70041c33a98e0b3481ebd20cfd05c32cfc Mon Sep 17 00:00:00 2001 From: Nikhil Warke Date: Tue, 26 Feb 2019 07:15:16 +0530 Subject: [PATCH] Separate out agent and room models Use parent id to associate child with parent as one to many relationship This way we dont have to maintain child list with parent and a parent's children can be found by simple find query Closes #39 Related #34 --- src/app.ts | 6 +++ src/device-group/device-group-model.ts | 7 ++-- src/iot-agent/iot-agent-hook.ts | 42 +++++++++++++++++++ src/iot-agent/iot-agent-model.ts | 10 +++++ src/iot-device/iot-device-hook.ts | 57 ++++++++------------------ src/iot-device/iot-device-model.ts | 6 +-- src/room/iot-room-model.ts | 8 ---- src/room/room-hook.ts | 46 +++++++++++++++++++++ src/room/room-model.ts | 10 +++++ src/utility.ts | 29 +++++++++++++ 10 files changed, 168 insertions(+), 53 deletions(-) create mode 100644 src/iot-agent/iot-agent-hook.ts create mode 100644 src/iot-agent/iot-agent-model.ts delete mode 100644 src/room/iot-room-model.ts create mode 100644 src/room/room-hook.ts create mode 100644 src/room/room-model.ts diff --git a/src/app.ts b/src/app.ts index a9b4e14..6520110 100644 --- a/src/app.ts +++ b/src/app.ts @@ -19,7 +19,9 @@ import { PushController } from './controllers/push'; // Hooks import { clientHooks } from './client-device/client-hook'; +import { iotAgentHooks } from './iot-agent/iot-agent-hook'; import { iotDeviceHooks } from './iot-device/iot-device-hook'; +import { roomHooks } from './room/room-hook'; // Database import { databaseService } from './database'; @@ -59,10 +61,14 @@ app.use('/clients', databaseService('users')); app.use('/users', databaseService('users')); app.use('/devices', databaseService('devices')); +app.use('/agents', databaseService('agents')); +app.use('/rooms', databaseService('rooms')); app.use('/groups', databaseService('groups')); app.service('clients').hooks(clientHooks); app.service('devices').hooks(iotDeviceHooks); +app.service('agents').hooks(iotAgentHooks); +app.service('rooms').hooks(roomHooks); logger.verbose('Service initialization complete'); // #endregion diff --git a/src/device-group/device-group-model.ts b/src/device-group/device-group-model.ts index 35ac638..080a362 100644 --- a/src/device-group/device-group-model.ts +++ b/src/device-group/device-group-model.ts @@ -1,10 +1,11 @@ -import { IIotRoom } from '../room/iot-room-model'; +import { IRoom } from '../room/room-model'; export interface IDeviceGroup { _id: string; // join([IUser._id, name], '-') name: string; ownerId: string; - userIds: string[]; - rooms: IIotRoom[]; + userIds: string[]; // IUser._id + + rooms?: IRoom[]; } diff --git a/src/iot-agent/iot-agent-hook.ts b/src/iot-agent/iot-agent-hook.ts new file mode 100644 index 0000000..cb358e4 --- /dev/null +++ b/src/iot-agent/iot-agent-hook.ts @@ -0,0 +1,42 @@ +import { HookContext, HooksObject } from '@feathersjs/feathers'; + +import { logger } from '../logger'; +import { Utility } from '../utility'; + +import { IRoom } from '../room/room-model'; +import { IIotAgent } from './iot-agent-model'; + +export const iotAgentHooks: Partial = { + before: { + async create(context: HookContext) { + const data = context.data; + const params = context.params; + const { roomId }: { roomId: string } = params.query; + + const room: IRoom = await context.app.service('rooms').get(roomId); + const agentId = Utility.generateId(roomId, data.site); + + // #region Validation + if (room === undefined) { + const message = + 'Given device group does not exist. Try different group or create one'; + logger.warn(message, { device: data, room }); + throw new Error(message); + } + + if (await Utility.isChild(agentId, context.service, { roomId })) { + const message = 'Agent already exists for given room'; + logger.warn(message, { device: data, room }); + throw new Error(message); + } + // #endregion + + // Add reverse reference + data.roomId = roomId; + + data._id = agentId; + + return context; + } + } +}; diff --git a/src/iot-agent/iot-agent-model.ts b/src/iot-agent/iot-agent-model.ts new file mode 100644 index 0000000..28e5df0 --- /dev/null +++ b/src/iot-agent/iot-agent-model.ts @@ -0,0 +1,10 @@ +import { IIotDevice } from '../iot-device/iot-device-model'; + +export interface IIotAgent { + _id: string; // join([IRoom._id, site], '-') | Also used for channel + site: string; + + roomId: string; // IRoom._id + + devices?: IIotDevice[]; +} diff --git a/src/iot-device/iot-device-hook.ts b/src/iot-device/iot-device-hook.ts index 171c373..1a3bcd5 100644 --- a/src/iot-device/iot-device-hook.ts +++ b/src/iot-device/iot-device-hook.ts @@ -3,8 +3,9 @@ import * as lodash from 'lodash'; import { Mqtt } from '../iot/mqtt'; import { logger } from '../logger'; +import { Utility } from '../utility'; -import { IDeviceGroup } from '../device-group/device-group-model'; +import { IIotAgent } from '../iot-agent/iot-agent-model'; import { IIotDevice } from './iot-device-model'; export const iotDeviceHooks: Partial = { @@ -76,55 +77,33 @@ export const iotDeviceHooks: Partial = { async create(context: HookContext) { const data = context.data; const params = context.params; - const { - roomId, - deviceGroupId - }: { roomId: string; deviceGroupId: string } = params.query; + const { agentId }: { agentId: string } = params.query; - // Default state when creating - data.isOn = false; - - const deviceGroup: IDeviceGroup = await context.app - .service('groups') - .get(deviceGroupId); + const agent: IIotAgent = await context.app.service('agents').get(agentId); + const deviceId = Utility.generateId(agentId, data.name); - const room = lodash.find(deviceGroup.rooms, { _id: roomId }); - - // #region Parameter checking - if (room === undefined) { + if (agent === undefined) { const message = - 'room does not exist. Please check room id or create new room'; - logger.warn(message, params.query); + 'Given agent does not exist. Try different agent or create one'; + logger.warn(message, { device: data, agentId }); throw new Error(message); } - const deviceId = lodash - .join([deviceGroupId, roomId, data.name], '-') - .toLowerCase(); - - if (lodash.includes(room.deviceIds, deviceId)) { - const message = 'Device already exists in the given room'; - logger.warn(message, { device: data, room: roomId }); + if ( + await Utility.isChild(deviceId, context.service, { agentId }) + ) { + const message = 'Device already exist for given agent'; + logger.warn(message, { data }); throw new Error(message); } // #endregion - // Add Room and Group Reference - data.roomId = roomId; - data.groupId = deviceGroupId; - // Add Device Reference - room.deviceIds.push(deviceId); - // For Updating group in after hook when successfully saved - params.deviceGroup = deviceGroup; - - return context; - } - }, - after: { - async create(context: HookContext) { - const deviceGroup: IDeviceGroup = context.params.deviceGroup; + // Default state when creating + data.isOn = false; + // Add Agent reverse reference + data.agentId = agent._id; - await context.app.service('groups').patch(deviceGroup._id, deviceGroup); + data._id = deviceId; return context; } diff --git a/src/iot-device/iot-device-model.ts b/src/iot-device/iot-device-model.ts index 809be4b..b1a34bf 100644 --- a/src/iot-device/iot-device-model.ts +++ b/src/iot-device/iot-device-model.ts @@ -1,8 +1,8 @@ export interface IIotDevice { - _id: string; // join([IDeviceGroup._id, IIotRoom._id, name], '-') + _id: string; // join([IIotAgent._id, name], '-') name: string; pin: number; isOn: boolean; - roomId: string; - groupId: string; + + agentId: string; // IIotAgent._id } diff --git a/src/room/iot-room-model.ts b/src/room/iot-room-model.ts deleted file mode 100644 index c580590..0000000 --- a/src/room/iot-room-model.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { IIotDevice } from '../iot-device/iot-device-model'; - -export interface IIotRoom { - devices?: IIotDevice[]; - deviceIds: string[]; - _id: string; // join([IDeviceGroup._id, name], '-') - name: string; -} diff --git a/src/room/room-hook.ts b/src/room/room-hook.ts new file mode 100644 index 0000000..0f8d5a8 --- /dev/null +++ b/src/room/room-hook.ts @@ -0,0 +1,46 @@ +import { HookContext, HooksObject } from '@feathersjs/feathers'; + +import { logger } from '../logger'; +import { Utility } from '../utility'; + +import { IDeviceGroup } from '../device-group/device-group-model'; +import { IRoom } from './room-model'; + +export const roomHooks: Partial = { + before: { + async create(context: HookContext) { + const data = context.data; + const params = context.params; + const { deviceGroupId }: { deviceGroupId: string } = params.query; + + const deviceGroup: IDeviceGroup = await context.app + .service('groups') + .get(deviceGroupId); + const roomId = Utility.generateId(deviceGroupId, data.name); + + //#region Validation + if (deviceGroup === undefined) { + const message = + 'Given device group does not exist. Try different group or create one'; + logger.warn(message, { device: data, deviceGroupId }); + throw new Error(message); + } + + if ( + await Utility.isChild(roomId, context.service, { deviceGroupId }) + ) { + const message = 'Room already exists for given device group'; + logger.warn(message, { data }); + throw new Error(message); + } + // #endregion + + // Add reverse reference + data.deviceGroupId = deviceGroupId; + + data._id = roomId; + + return context; + } + } +}; diff --git a/src/room/room-model.ts b/src/room/room-model.ts new file mode 100644 index 0000000..22bddb8 --- /dev/null +++ b/src/room/room-model.ts @@ -0,0 +1,10 @@ +import { IIotAgent } from '../iot-agent/iot-agent-model'; + +export interface IRoom { + _id: string; // join([IDeviceGroup._id, name], '-') + name: string; + + deviceGroupId: string; // IDeviceGroup._id + + agents?: IIotAgent[]; +} diff --git a/src/utility.ts b/src/utility.ts index 16d355b..2a48ddd 100644 --- a/src/utility.ts +++ b/src/utility.ts @@ -1,5 +1,34 @@ +import { Service } from '@feathersjs/feathers'; +import * as lodash from 'lodash'; + export namespace Utility { export const equalsIgnoreCase = (string1: string, string2: string) => { return string1.toUpperCase() === string2.toUpperCase(); }; + + export const findIdsWhere = (service: Service, parentIdKvPair) => + service.find({ + query: { $select: ['_id'], ...parentIdKvPair } + }); + + export const includes = async (id: string, ids: string[]) => { + return ( + !(lodash.isNil(ids) || lodash.isEmpty(ids)) && lodash.includes(ids, id) + ); + }; + + export const isChild = async (childId, childStore, parentIdKvPair) => { + return includes(childId, await findIdsWhere(childStore, parentIdKvPair)); + }; + + export const generateId = (parentId, param) => { + const updatedId = lodash + .chain(param) + .map(char => lodash.toLower(char)) + .filter(char => char >= 'a' && char <= 'z') + .join('') + .value(); + + return lodash.join([parentId, updatedId], '-'); + }; }