diff --git a/.gitignore b/.gitignore index 20129325..9d94d6f4 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,5 @@ index.js.map # Doc doc/framework frontmatter-errors.json + +.vscode \ No newline at end of file diff --git a/doc/1/classes/decoder/constructor/index.md b/doc/1/classes/decoder/constructor/index.md index 2d964389..14672952 100644 --- a/doc/1/classes/decoder/constructor/index.md +++ b/doc/1/classes/decoder/constructor/index.md @@ -26,7 +26,7 @@ import { Decoder } from 'kuzzle-plugin-device-manager'; class KarakoyDecoder extends Decoder { constructor () { - super('Karakoy'); + super("Karakoy", ["temperature"]); } // This method must be implemented diff --git a/doc/1/controllers/decoder/list-decoders/index.md b/doc/1/controllers/decoder/list-decoders/index.md new file mode 100644 index 00000000..4ed15332 --- /dev/null +++ b/doc/1/controllers/decoder/list-decoders/index.md @@ -0,0 +1,68 @@ +--- +code: true +type: page +title: list +description: List available registered decoders +--- + +# List + +List available registered decoders. + +--- + +## Query Syntax + +### HTTP + +```http +URL: http://localhost:7512/_/device-manager/decoders/_list +Method: GET +``` + +### Other protocols + +```js +{ + "controller": "device-manager/decoders", + "action": "list", +} +``` + +### Kourou + +```bash +kourou device-manager/decoders:list +``` +--- + +## Response + +```js +{ + "action": "list", + "controller": "device-manager/decoders", + "error": null, + "node": "knode-nine-hydra-22631", + "requestId": "d888a8e1-2f80-4849-99d0-86ea70fe91e4", + "result": { + "decoders": [ + { + "deviceModel": "DummyTemp", + "deviceMeasures": [ + "temperature" + ] + }, + { + "deviceModel": "DummyTempPosition", + "deviceMeasures": [ + "temperature", + "position" + ] + } + ] + }, + "status": 200, + "volatile": null +} +``` diff --git a/doc/1/guides/decoders/index.md b/doc/1/guides/decoders/index.md index f14e4ec1..6fea9dfd 100644 --- a/doc/1/guides/decoders/index.md +++ b/doc/1/guides/decoders/index.md @@ -37,7 +37,7 @@ const deviceManager = new DeviceManagerPlugin(); class KarakoyDecoder extends Decoder { constructor () { - super("Karakoy"); + super("Karakoy", ["temperature"]); } async decode (payload: JSONObject, request: KuzzleRequest): Promise { @@ -74,7 +74,7 @@ You can specify a custom API action and custom HTTP routes by defining the `acti ```js class KarakoyDecoder extends Decoder { constructor () { - super("Karakoy"); + super("Karakoy", ["temperature"]); // Generated API action: "device-manager/payload:karakoy-v1" this.action = 'karakoy-v1'; diff --git a/features/DecodersController.feature b/features/DecodersController.feature new file mode 100644 index 00000000..32a9f4a6 --- /dev/null +++ b/features/DecodersController.feature @@ -0,0 +1,6 @@ +Feature: Device Manager decoders controller + + Scenario: List all registered decoders + When When I successfully execute the action "device-manager/decoders":"list" + Then I should receive a result matching: + | decoders | [{"deviceModel":"DummyTemp","deviceMeasures":["temperature"]},{"deviceModel":"DummyTempPosition","deviceMeasures":["temperature","position"]}] | \ No newline at end of file diff --git a/features/fixtures/application/decoders/DummyTempDecoder.ts b/features/fixtures/application/decoders/DummyTempDecoder.ts index d1840f5f..0c1632e1 100644 --- a/features/fixtures/application/decoders/DummyTempDecoder.ts +++ b/features/fixtures/application/decoders/DummyTempDecoder.ts @@ -3,7 +3,7 @@ import { JSONObject, KuzzleRequest, PreconditionError } from 'kuzzle'; export class DummyTempDecoder extends Decoder { constructor () { - super('DummyTemp'); + super('DummyTemp', ["temperature"]); this.payloadsMappings = { deviceEUI: { type: 'keyword' } diff --git a/features/fixtures/application/decoders/DummyTempPositionDecoder.ts b/features/fixtures/application/decoders/DummyTempPositionDecoder.ts index aec6a6d8..37ff3f7a 100644 --- a/features/fixtures/application/decoders/DummyTempPositionDecoder.ts +++ b/features/fixtures/application/decoders/DummyTempPositionDecoder.ts @@ -3,7 +3,7 @@ import { JSONObject, KuzzleRequest, PreconditionError } from 'kuzzle'; export class DummyTempPositionDecoder extends Decoder { constructor () { - super('DummyTempPosition'); + super('DummyTempPosition', ["temperature", "position"]); } async validate (payload: JSONObject, request: KuzzleRequest) { diff --git a/lib/DeviceManagerPlugin.ts b/lib/DeviceManagerPlugin.ts index 95799604..3ecdaf1e 100644 --- a/lib/DeviceManagerPlugin.ts +++ b/lib/DeviceManagerPlugin.ts @@ -15,6 +15,7 @@ import { EngineController } from 'kuzzle-plugin-commons'; import { AssetController, DeviceController, + DecodersController } from './controllers'; import { DeviceManagerEngine, @@ -26,6 +27,7 @@ import { PayloadHandler, AssetMappingsManager, DeviceMappingsManager, + DecodersService } from './core-classes'; import { assetsMappings, @@ -86,10 +88,12 @@ export class DeviceManagerPlugin extends Plugin { private assetController: AssetController; private deviceController: DeviceController; private engineController: EngineController; + private decodersController: DecodersController; private payloadService: PayloadService; private deviceManagerEngine: DeviceManagerEngine; private deviceService: DeviceService; + private decodersService: DecodersService; private migrationService: MigrationService; private batchWriter: BatchWriter; @@ -195,15 +199,18 @@ export class DeviceManagerPlugin extends Plugin { this.payloadService = new PayloadService(this, this.batchWriter); this.deviceService = new DeviceService(this, this.decoders); + this.decodersService = new DecodersService(this, this.decoders); this.migrationService = new MigrationService('device-manager', this); this.deviceManagerEngine = new DeviceManagerEngine(this, this.assetMappings, this.deviceMappings); this.assetController = new AssetController(this); this.deviceController = new DeviceController(this, this.deviceService); + this.decodersController = new DecodersController(this, this.decodersService); this.engineController = new EngineController('device-manager', this, this.deviceManagerEngine); this.api['device-manager/asset'] = this.assetController.definition; this.api['device-manager/device'] = this.deviceController.definition; + this.api['device-manager/decoders'] = this.decodersController.definition; this.pipes = { 'device-manager/device:beforeUpdate': this.pipeCheckEngine.bind(this), diff --git a/lib/controllers/DecodersController.ts b/lib/controllers/DecodersController.ts new file mode 100644 index 00000000..ad3dea7f --- /dev/null +++ b/lib/controllers/DecodersController.ts @@ -0,0 +1,40 @@ +import { + EmbeddedSDK, + Plugin, +} from 'kuzzle'; + +import { CRUDController } from './CRUDController'; +import { DecodersService } from '../core-classes'; + +export class DecodersController extends CRUDController { + private decodersService: DecodersService; + + get sdk(): EmbeddedSDK { + return this.context.accessors.sdk; + } + + constructor(plugin: Plugin, decodersService: DecodersService) { + super(plugin, 'decoders'); + + this.decodersService = decodersService; + + this.definition = { + actions: { + list: { + handler: this.list.bind(this), + http: [{ verb: 'get', path: 'device-manager/decoders' }] + } + } + }; + } + + /** + * List all available decoders + */ + async list () { + const decoders = await this.decodersService.list(); + + return { decoders }; + } + +} diff --git a/lib/controllers/DeviceController.ts b/lib/controllers/DeviceController.ts index d7b3c5e7..a3876ff7 100644 --- a/lib/controllers/DeviceController.ts +++ b/lib/controllers/DeviceController.ts @@ -72,7 +72,7 @@ export class DeviceController extends CRUDController { prunePayloads: { handler: this.prunePayloads.bind(this), http: [{ verb: 'delete', path: 'device-manager/devices/_prunePayloads' }] - }, + } } }; } diff --git a/lib/controllers/index.ts b/lib/controllers/index.ts index 771b3361..5ed9a330 100644 --- a/lib/controllers/index.ts +++ b/lib/controllers/index.ts @@ -1,3 +1,4 @@ export * from './AssetController'; export * from './CRUDController'; export * from './DeviceController'; +export * from './DecodersController'; diff --git a/lib/core-classes/Decoder.ts b/lib/core-classes/Decoder.ts index ac13732f..808fddcf 100644 --- a/lib/core-classes/Decoder.ts +++ b/lib/core-classes/Decoder.ts @@ -4,11 +4,12 @@ import { HttpRoute, PreconditionError, } from 'kuzzle'; -import _ from 'lodash'; + +import has from 'lodash/has'; import { Device, BaseAsset } from '../models'; -import { AssetDeviceMeasures, DeviceContent } from '../types'; +import { DeviceContent, DecoderContent, AssetDeviceMeasures } from '../types'; /* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-empty-function */ @@ -26,6 +27,11 @@ export abstract class Decoder { */ deviceModel: string; + /** + * Array of device measure type + */ + deviceMeasures: string[]; + /** * Custom name for the associated API action in the "payload" controller */ @@ -62,9 +68,11 @@ export abstract class Decoder { /** * @param deviceModel Device model for this decoder + * @param deviceMeasures Devices measure types for this decoder */ - constructor (deviceModel: string) { + constructor (deviceModel: string, deviceMeasures: string[]) { this.deviceModel = deviceModel; + this.deviceMeasures = deviceMeasures; } /** @@ -199,11 +207,18 @@ export abstract class Decoder { */ ensureProperties (payload: JSONObject, paths: string[]): void | never { for (const path of paths) { - if (! _.has(payload, path)) { + if (! has(payload, path)) { throw new PreconditionError(`Missing property "${path}" in payload`); } } } + + serialize(): DecoderContent { + return { + deviceModel: this.deviceModel, + deviceMeasures: this.deviceMeasures + } + } } /* eslint-enable @typescript-eslint/no-unused-vars */ diff --git a/lib/core-classes/DecodersService.ts b/lib/core-classes/DecodersService.ts new file mode 100644 index 00000000..b4199a9d --- /dev/null +++ b/lib/core-classes/DecodersService.ts @@ -0,0 +1,31 @@ +import { PluginContext, EmbeddedSDK, Plugin } from "kuzzle"; + +import { Decoder } from "./Decoder"; +import { DeviceManagerConfig } from "../DeviceManagerPlugin"; +import { DecoderContent } from '../types/decoders/DecodersContent'; + +export class DecodersService { + private config: DeviceManagerConfig; + private context: PluginContext; + + private decoders: Map; + + get sdk(): EmbeddedSDK { + return this.context.accessors.sdk; + } + + constructor(plugin: Plugin, decoders: Map) { + this.config = plugin.config as any; + this.context = plugin.context; + + this.decoders = decoders; + } + + async list(): Promise { + const decoders = Array + .from(this.decoders.values()) + .map(decoder => decoder.serialize()) + + return decoders; + } +} diff --git a/lib/core-classes/index.ts b/lib/core-classes/index.ts index b3b125ab..2b431cf8 100644 --- a/lib/core-classes/index.ts +++ b/lib/core-classes/index.ts @@ -10,6 +10,8 @@ export * from './BatchProcessing'; export * from './Decoder'; +export * from './DecodersService'; + export * from './CustomMappings/AssetMappingsManager'; export * from './CustomMappings/DeviceMappingsManager'; diff --git a/lib/types/decoders/DecodersContent.ts b/lib/types/decoders/DecodersContent.ts new file mode 100644 index 00000000..910f2639 --- /dev/null +++ b/lib/types/decoders/DecodersContent.ts @@ -0,0 +1,4 @@ +export interface DecoderContent { + deviceModel: string + deviceMeasures: string[] +} \ No newline at end of file diff --git a/lib/types/index.ts b/lib/types/index.ts index d00fd9ac..f605ddf8 100644 --- a/lib/types/index.ts +++ b/lib/types/index.ts @@ -2,6 +2,8 @@ export * from './asset/AssetContentBase'; export * from './asset/AssetDeviceMeasures'; +export * from './asset/AssetContentBase'; + export * from './asset/AssetHistoryContent'; export * from './asset/AssetMeasureOrigin'; @@ -9,3 +11,5 @@ export * from './asset/AssetMeasureOrigin'; export * from './device/DeviceContent'; export * from './device/DeviceMeasures'; + +export * from './decoders/DecodersContent'; \ No newline at end of file