kuzzle-device-manager 0.4.8 → 0.4.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +4 -0
- package/index.js +17 -0
- package/lib/DeviceManagerPlugin.d.ts +100 -0
- package/lib/DeviceManagerPlugin.js +237 -0
- package/lib/controllers/AssetController.d.ts +7 -0
- package/lib/controllers/AssetController.js +53 -0
- package/lib/controllers/CRUDController.d.ts +34 -0
- package/lib/controllers/CRUDController.js +65 -0
- package/lib/controllers/DeviceController.d.ts +56 -0
- package/lib/controllers/DeviceController.js +218 -0
- package/lib/controllers/index.d.ts +3 -0
- package/lib/controllers/index.js +16 -0
- package/lib/core-classes/BatchProcessing/BatchController.d.ts +32 -0
- package/lib/core-classes/BatchProcessing/BatchController.js +86 -0
- package/lib/core-classes/BatchProcessing/BatchWriter.d.ts +110 -0
- package/lib/core-classes/BatchProcessing/BatchWriter.js +264 -0
- package/lib/core-classes/BatchProcessing/InstrumentablePromise.d.ts +6 -0
- package/lib/core-classes/BatchProcessing/InstrumentablePromise.js +13 -0
- package/lib/core-classes/BatchProcessing/index.d.ts +3 -0
- package/lib/core-classes/BatchProcessing/index.js +16 -0
- package/lib/core-classes/CustomMappings/AssetMappingsManager.d.ts +31 -0
- package/lib/core-classes/CustomMappings/AssetMappingsManager.js +79 -0
- package/lib/core-classes/CustomMappings/DeviceMappingsManager.d.ts +43 -0
- package/lib/core-classes/CustomMappings/DeviceMappingsManager.js +71 -0
- package/lib/core-classes/Decoder.d.ts +133 -0
- package/lib/core-classes/Decoder.js +166 -0
- package/lib/core-classes/DeviceManagerEngine.d.ts +21 -0
- package/lib/core-classes/DeviceManagerEngine.js +64 -0
- package/lib/core-classes/DeviceService.d.ts +41 -0
- package/lib/core-classes/DeviceService.js +264 -0
- package/lib/core-classes/MigrationService.d.ts +13 -0
- package/lib/core-classes/MigrationService.js +49 -0
- package/lib/core-classes/PayloadService.d.ts +43 -0
- package/lib/core-classes/PayloadService.js +191 -0
- package/lib/core-classes/index.d.ts +8 -0
- package/lib/core-classes/index.js +21 -0
- package/lib/models/BaseAsset.d.ts +56 -0
- package/lib/models/BaseAsset.js +58 -0
- package/lib/models/Catalog.d.ts +55 -0
- package/lib/models/Catalog.js +38 -0
- package/lib/models/Config.d.ts +33 -0
- package/lib/models/Config.js +26 -0
- package/lib/models/Device.d.ts +102 -0
- package/lib/models/Device.js +91 -0
- package/lib/models/index.d.ts +3 -0
- package/lib/models/index.js +16 -0
- package/lib/types/AssetsHistoryContent.d.ts +9 -0
- package/lib/types/AssetsHistoryContent.js +3 -0
- package/lib/types/BaseAssetContent.d.ts +8 -0
- package/lib/types/BaseAssetContent.js +3 -0
- package/lib/types/DeviceContent.d.ts +62 -0
- package/lib/types/DeviceContent.js +3 -0
- package/lib/types/Measure.d.ts +54 -0
- package/lib/types/Measure.js +3 -0
- package/lib/types/index.d.ts +3 -0
- package/lib/types/index.js +16 -0
- package/lib/utils/events.d.ts +8 -0
- package/lib/utils/events.js +17 -0
- package/lib/utils/index.d.ts +1 -0
- package/lib/utils/index.js +14 -0
- package/package.json +1 -1
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Decoder = void 0;
|
|
7
|
+
const kuzzle_1 = require("kuzzle");
|
|
8
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
9
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
10
|
+
/* eslint-disable @typescript-eslint/no-empty-function */
|
|
11
|
+
/**
|
|
12
|
+
* Base class to implement a decoder for a device model.
|
|
13
|
+
* The device model must be passed to the parent constructor.
|
|
14
|
+
* The abstract "decode" method must be implemented.
|
|
15
|
+
*/
|
|
16
|
+
class Decoder {
|
|
17
|
+
/**
|
|
18
|
+
* @param deviceModel Device model for this decoder
|
|
19
|
+
*/
|
|
20
|
+
constructor(deviceModel) {
|
|
21
|
+
/**
|
|
22
|
+
* Custom mappings for the payload collection.
|
|
23
|
+
* It will be injected in the "payload" property and it should allows to index
|
|
24
|
+
* the device model unique identifier field.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* this.payloadsMappings = {
|
|
28
|
+
* device_properties: {
|
|
29
|
+
* properties: {
|
|
30
|
+
* deveui: { type: 'keyword' }
|
|
31
|
+
* }
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
*/
|
|
35
|
+
this.payloadsMappings = {};
|
|
36
|
+
this.deviceModel = deviceModel;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Define custom HTTP routes
|
|
40
|
+
*
|
|
41
|
+
* @param http HttpRoute array
|
|
42
|
+
*/
|
|
43
|
+
set http(http) {
|
|
44
|
+
this._http = http;
|
|
45
|
+
}
|
|
46
|
+
get http() {
|
|
47
|
+
return this._http || [{ verb: 'post', path: `device-manager/payload/${this.action}` }];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Validate the payload format before processing.
|
|
51
|
+
*
|
|
52
|
+
* If the method:
|
|
53
|
+
* - return true: the payload will be processed (status 200)
|
|
54
|
+
* - return false: the payload will be skipped (status 200)
|
|
55
|
+
* - throw an error: the payload will be skipped (status 4** or 5**)
|
|
56
|
+
*
|
|
57
|
+
* @param payload Raw payload received in the API action body
|
|
58
|
+
* @param request Original request
|
|
59
|
+
*
|
|
60
|
+
* @return A boolean indicating if the payload is valid
|
|
61
|
+
*/
|
|
62
|
+
async validate(payload, request) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Hook executed before processing the payload but after validation
|
|
67
|
+
*
|
|
68
|
+
* @param payload Raw payload received in the API action body
|
|
69
|
+
* @param request Original request
|
|
70
|
+
*/
|
|
71
|
+
async beforeProcessing(payload, request) {
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Enrichment hook executed before registering a device
|
|
75
|
+
*
|
|
76
|
+
* @param device Densor before being persisted
|
|
77
|
+
* @param request Original request
|
|
78
|
+
*
|
|
79
|
+
* @returns Enriched device
|
|
80
|
+
*/
|
|
81
|
+
async beforeRegister(device, request) {
|
|
82
|
+
return device;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Hook executed after registering a device.
|
|
86
|
+
* Return value of this method will be returned in the API action result.
|
|
87
|
+
*
|
|
88
|
+
* @param device Device after being persisted
|
|
89
|
+
* @param request Original request
|
|
90
|
+
*
|
|
91
|
+
* @returns Result of the corresponding API action
|
|
92
|
+
*/
|
|
93
|
+
async afterRegister(device, request) {
|
|
94
|
+
return {
|
|
95
|
+
tenantId: device._source.tenantId,
|
|
96
|
+
device: device.serialize(),
|
|
97
|
+
asset: null,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Enrichment hook executed before updating a device
|
|
102
|
+
*
|
|
103
|
+
* @param device Device before being updated
|
|
104
|
+
* @param request Original request
|
|
105
|
+
*
|
|
106
|
+
* @returns Enriched device
|
|
107
|
+
*/
|
|
108
|
+
async beforeUpdate(device, request) {
|
|
109
|
+
return device;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Hook executed after updating a device.
|
|
113
|
+
* Return value of this method will be returned in the API action result.
|
|
114
|
+
*
|
|
115
|
+
* @param device Device after being updated
|
|
116
|
+
* @param request Original request
|
|
117
|
+
*
|
|
118
|
+
* @returns Result of the corresponding API action
|
|
119
|
+
*/
|
|
120
|
+
async afterUpdate(device, asset, request) {
|
|
121
|
+
return {
|
|
122
|
+
tenantId: device._source.tenantId,
|
|
123
|
+
device: device.serialize(),
|
|
124
|
+
asset: asset ? asset.serialize() : null,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Build the "measures" property that will be persisted in the asset document
|
|
129
|
+
*
|
|
130
|
+
* @param device Device after being updated
|
|
131
|
+
*
|
|
132
|
+
* @returns Content of the "measures" property
|
|
133
|
+
*/
|
|
134
|
+
async copyToAsset(device) {
|
|
135
|
+
const measures = {};
|
|
136
|
+
for (const [measureType, measure] of Object.entries(device._source.measures)) {
|
|
137
|
+
measures[measureType] = {
|
|
138
|
+
id: device._id,
|
|
139
|
+
model: device._source.model,
|
|
140
|
+
reference: device._source.reference,
|
|
141
|
+
...measure,
|
|
142
|
+
qos: device._source.qos,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return measures;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Checks if the provided properties are present in the payload
|
|
149
|
+
*
|
|
150
|
+
* @param payload Raw payload received in the API action body
|
|
151
|
+
* @param paths Paths of properties (lodash style)
|
|
152
|
+
*
|
|
153
|
+
* @throws
|
|
154
|
+
*/
|
|
155
|
+
ensureProperties(payload, paths) {
|
|
156
|
+
for (const path of paths) {
|
|
157
|
+
if (!lodash_1.default.has(payload, path)) {
|
|
158
|
+
throw new kuzzle_1.PreconditionError(`Missing property "${path}" in payload`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
exports.Decoder = Decoder;
|
|
164
|
+
/* eslint-enable @typescript-eslint/no-unused-vars */
|
|
165
|
+
/* eslint-enable @typescript-eslint/no-empty-function */
|
|
166
|
+
//# sourceMappingURL=Decoder.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Plugin } from 'kuzzle';
|
|
2
|
+
import { AbstractEngine } from 'kuzzle-plugin-commons';
|
|
3
|
+
import { DeviceManagerConfig, DeviceManagerPlugin } from '../DeviceManagerPlugin';
|
|
4
|
+
import { AssetMappingsManager } from './CustomMappings/AssetMappingsManager';
|
|
5
|
+
import { DeviceMappingsManager } from './CustomMappings/DeviceMappingsManager';
|
|
6
|
+
export declare class DeviceManagerEngine extends AbstractEngine<DeviceManagerPlugin> {
|
|
7
|
+
config: DeviceManagerConfig;
|
|
8
|
+
private assetMappings;
|
|
9
|
+
private deviceMappings;
|
|
10
|
+
constructor(plugin: Plugin, assetMappings: AssetMappingsManager, deviceMappings: DeviceMappingsManager);
|
|
11
|
+
onCreate(index: string, group?: string): Promise<{
|
|
12
|
+
collections: string[];
|
|
13
|
+
}>;
|
|
14
|
+
onUpdate(index: string, group?: string): Promise<{
|
|
15
|
+
collections: string[];
|
|
16
|
+
}>;
|
|
17
|
+
onDelete(index: string): Promise<{
|
|
18
|
+
collections: string[];
|
|
19
|
+
}>;
|
|
20
|
+
private getAssetsHistoryMappings;
|
|
21
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DeviceManagerEngine = void 0;
|
|
4
|
+
const kuzzle_plugin_commons_1 = require("kuzzle-plugin-commons");
|
|
5
|
+
const models_1 = require("../models");
|
|
6
|
+
class DeviceManagerEngine extends kuzzle_plugin_commons_1.AbstractEngine {
|
|
7
|
+
constructor(plugin, assetMappings, deviceMappings) {
|
|
8
|
+
super('device-manager', plugin, plugin.config.adminIndex, plugin.config.configCollection);
|
|
9
|
+
this.assetMappings = assetMappings;
|
|
10
|
+
this.deviceMappings = deviceMappings;
|
|
11
|
+
}
|
|
12
|
+
async onCreate(index, group = 'commons') {
|
|
13
|
+
const promises = [];
|
|
14
|
+
promises.push(this.sdk.collection.create(index, 'assets', {
|
|
15
|
+
mappings: this.assetMappings.get(group)
|
|
16
|
+
}));
|
|
17
|
+
promises.push(this.sdk.collection.create(index, 'asset-history', {
|
|
18
|
+
mappings: this.getAssetsHistoryMappings(group)
|
|
19
|
+
}));
|
|
20
|
+
promises.push(this.sdk.collection.create(index, 'devices', {
|
|
21
|
+
mappings: this.deviceMappings.get()
|
|
22
|
+
}));
|
|
23
|
+
promises.push(this.sdk.collection.create(index, this.config.configCollection, {
|
|
24
|
+
mappings: {
|
|
25
|
+
dynamic: 'strict',
|
|
26
|
+
properties: {
|
|
27
|
+
type: { type: 'keyword' },
|
|
28
|
+
catalog: models_1.catalogMappings,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}));
|
|
32
|
+
await Promise.all(promises);
|
|
33
|
+
return { collections: ['assets', 'asset-history', this.config.configCollection, 'devices'] };
|
|
34
|
+
}
|
|
35
|
+
async onUpdate(index, group = 'commons') {
|
|
36
|
+
const promises = [];
|
|
37
|
+
promises.push(this.sdk.collection.create(index, 'assets', {
|
|
38
|
+
mappings: this.assetMappings.get(group)
|
|
39
|
+
}));
|
|
40
|
+
promises.push(this.sdk.collection.create(index, 'asset-history', {
|
|
41
|
+
mappings: this.getAssetsHistoryMappings(group)
|
|
42
|
+
}));
|
|
43
|
+
promises.push(this.sdk.collection.create(index, 'devices', {
|
|
44
|
+
mappings: this.deviceMappings.get()
|
|
45
|
+
}));
|
|
46
|
+
await Promise.all(promises);
|
|
47
|
+
return { collections: ['assets', 'asset-history', 'devices'] };
|
|
48
|
+
}
|
|
49
|
+
async onDelete(index) {
|
|
50
|
+
const promises = [];
|
|
51
|
+
promises.push(this.sdk.collection.delete(index, 'assets'));
|
|
52
|
+
promises.push(this.sdk.collection.delete(index, 'asset-history'));
|
|
53
|
+
promises.push(this.sdk.collection.delete(index, 'devices'));
|
|
54
|
+
await Promise.all(promises);
|
|
55
|
+
return { collections: ['assets', 'asset-history', 'devices'] };
|
|
56
|
+
}
|
|
57
|
+
getAssetsHistoryMappings(group) {
|
|
58
|
+
const mappings = JSON.parse(JSON.stringify(this.config.collections['asset-history']));
|
|
59
|
+
mappings.properties.asset = this.assetMappings.get(group);
|
|
60
|
+
return mappings;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
exports.DeviceManagerEngine = DeviceManagerEngine;
|
|
64
|
+
//# sourceMappingURL=DeviceManagerEngine.js.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { JSONObject, EmbeddedSDK, Plugin } from 'kuzzle';
|
|
2
|
+
import { DeviceBulkContent, DeviceMAttachementContent } from '../types';
|
|
3
|
+
import { Decoder } from './Decoder';
|
|
4
|
+
import { Device } from '../models';
|
|
5
|
+
export declare class DeviceService {
|
|
6
|
+
private config;
|
|
7
|
+
private context;
|
|
8
|
+
private decoders;
|
|
9
|
+
get sdk(): EmbeddedSDK;
|
|
10
|
+
constructor(plugin: Plugin, decoders: Map<string, Decoder>);
|
|
11
|
+
mAttach(devices: Device[], bulkData: DeviceBulkContent[], { strict, options }: {
|
|
12
|
+
strict?: boolean;
|
|
13
|
+
options?: JSONObject;
|
|
14
|
+
}): Promise<DeviceMAttachementContent>;
|
|
15
|
+
mDetach(devices: Device[], bulkData: DeviceBulkContent[], { strict, options }: {
|
|
16
|
+
strict?: boolean;
|
|
17
|
+
options?: JSONObject;
|
|
18
|
+
}): Promise<{
|
|
19
|
+
errors: any[];
|
|
20
|
+
successes: any[];
|
|
21
|
+
}>;
|
|
22
|
+
mLink(devices: Device[], bulkData: DeviceBulkContent[], { strict, options }: {
|
|
23
|
+
strict?: boolean;
|
|
24
|
+
options?: JSONObject;
|
|
25
|
+
}): Promise<{
|
|
26
|
+
errors: any[];
|
|
27
|
+
successes: any[];
|
|
28
|
+
}>;
|
|
29
|
+
mUnlink(devices: Device[], { strict, options }: {
|
|
30
|
+
strict?: boolean;
|
|
31
|
+
options?: JSONObject;
|
|
32
|
+
}): Promise<{
|
|
33
|
+
errors: any[];
|
|
34
|
+
successes: any[];
|
|
35
|
+
}>;
|
|
36
|
+
private eraseAssetMeasure;
|
|
37
|
+
private tenantExists;
|
|
38
|
+
private buildBulkDevices;
|
|
39
|
+
private formatDevicesContent;
|
|
40
|
+
private writeToDatabase;
|
|
41
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DeviceService = void 0;
|
|
4
|
+
const kuzzle_1 = require("kuzzle");
|
|
5
|
+
class DeviceService {
|
|
6
|
+
constructor(plugin, decoders) {
|
|
7
|
+
this.config = plugin.config;
|
|
8
|
+
this.context = plugin.context;
|
|
9
|
+
this.decoders = decoders;
|
|
10
|
+
}
|
|
11
|
+
get sdk() {
|
|
12
|
+
return this.context.accessors.sdk;
|
|
13
|
+
}
|
|
14
|
+
async mAttach(devices, bulkData, { strict, options }) {
|
|
15
|
+
const attachedDevices = devices.filter(device => device._source.tenantId);
|
|
16
|
+
if (strict && attachedDevices.length > 0) {
|
|
17
|
+
const ids = attachedDevices.map(device => device._id).join(',');
|
|
18
|
+
throw new kuzzle_1.BadRequestError(`These devices "${ids}" are already attached to a tenant`);
|
|
19
|
+
}
|
|
20
|
+
const documents = this.buildBulkDevices(bulkData);
|
|
21
|
+
const results = {
|
|
22
|
+
errors: [],
|
|
23
|
+
successes: [],
|
|
24
|
+
};
|
|
25
|
+
for (let i = 0; i < documents.length; i++) {
|
|
26
|
+
const document = documents[i];
|
|
27
|
+
const tenantExists = await this.tenantExists(document.tenantId);
|
|
28
|
+
if (strict && !tenantExists) {
|
|
29
|
+
throw new kuzzle_1.BadRequestError(`Tenant "${document.tenantId}" does not have a device-manager engine`);
|
|
30
|
+
}
|
|
31
|
+
else if (!strict && !tenantExists) {
|
|
32
|
+
results.errors.push(`Tenant "${document.tenantId}" does not have a device-manager engine`);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const deviceDocuments = this.formatDevicesContent(devices, document);
|
|
36
|
+
const { errors, successes } = await this.writeToDatabase(deviceDocuments, async (deviceDocuments) => {
|
|
37
|
+
const updated = await this.sdk.document.mUpdate(this.config.adminIndex, 'devices', deviceDocuments, { strict, ...options });
|
|
38
|
+
await this.sdk.document.mCreate(document.tenantId, 'devices', deviceDocuments, { strict, ...options });
|
|
39
|
+
return {
|
|
40
|
+
successes: results.successes.concat(updated.successes),
|
|
41
|
+
errors: results.errors.concat(updated.errors)
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
results.successes.concat(successes);
|
|
45
|
+
results.errors.concat(errors);
|
|
46
|
+
}
|
|
47
|
+
return results;
|
|
48
|
+
}
|
|
49
|
+
async mDetach(devices, bulkData, { strict, options }) {
|
|
50
|
+
const detachedDevices = devices.filter(device => {
|
|
51
|
+
return !device._source.tenantId || device._source.tenantId === null;
|
|
52
|
+
});
|
|
53
|
+
if (strict && detachedDevices.length > 0) {
|
|
54
|
+
const ids = detachedDevices.map(device => device._id).join(',');
|
|
55
|
+
throw new kuzzle_1.BadRequestError(`Devices "${ids}" are not attached to a tenant`);
|
|
56
|
+
}
|
|
57
|
+
const linkedAssets = devices.filter(device => device._source.assetId);
|
|
58
|
+
if (strict && linkedAssets.length > 0) {
|
|
59
|
+
const ids = linkedAssets.map(device => device._id).join(',');
|
|
60
|
+
throw new kuzzle_1.BadRequestError(`Devices "${ids}" are still linked to an asset`);
|
|
61
|
+
}
|
|
62
|
+
const builder = bulkData.map(data => {
|
|
63
|
+
const { deviceId } = data;
|
|
64
|
+
const device = devices.find(s => s._id === deviceId);
|
|
65
|
+
return { tenantId: device._source.tenantId, deviceId };
|
|
66
|
+
});
|
|
67
|
+
const documents = this.buildBulkDevices(builder);
|
|
68
|
+
const results = {
|
|
69
|
+
errors: [],
|
|
70
|
+
successes: [],
|
|
71
|
+
};
|
|
72
|
+
for (let i = 0; i < documents.length; i++) {
|
|
73
|
+
const document = documents[i];
|
|
74
|
+
const devicesContent = devices.filter(({ _id }) => document.deviceIds.includes(_id));
|
|
75
|
+
const deviceDocuments = devicesContent.map(device => {
|
|
76
|
+
return { _id: device._id, body: { tenantId: null } };
|
|
77
|
+
});
|
|
78
|
+
const { errors, successes } = await this.writeToDatabase(deviceDocuments, async (deviceDocuments) => {
|
|
79
|
+
const updated = await this.sdk.document.mUpdate(this.config.adminIndex, 'devices', deviceDocuments, { strict, ...options });
|
|
80
|
+
await this.sdk.document.mDelete(document.tenantId, 'devices', deviceDocuments.map(device => device._id), { strict, ...options });
|
|
81
|
+
return {
|
|
82
|
+
successes: results.successes.concat(updated.successes),
|
|
83
|
+
errors: results.errors.concat(updated.errors)
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
results.successes.concat(successes);
|
|
87
|
+
results.errors.concat(errors);
|
|
88
|
+
}
|
|
89
|
+
return results;
|
|
90
|
+
}
|
|
91
|
+
async mLink(devices, bulkData, { strict, options }) {
|
|
92
|
+
const detachedDevices = devices.filter(device => {
|
|
93
|
+
return !device._source.tenantId || device._source.tenantId === null;
|
|
94
|
+
});
|
|
95
|
+
if (strict && detachedDevices.length > 0) {
|
|
96
|
+
const ids = detachedDevices.map(device => device._id).join(',');
|
|
97
|
+
throw new kuzzle_1.PreconditionError(`Devices "${ids}" are not attached to a tenant`);
|
|
98
|
+
}
|
|
99
|
+
const builder = bulkData.map(data => {
|
|
100
|
+
const { deviceId, assetId } = data;
|
|
101
|
+
const device = devices.find(s => s._id === deviceId);
|
|
102
|
+
return { tenantId: device._source.tenantId, deviceId, assetId };
|
|
103
|
+
});
|
|
104
|
+
const documents = this.buildBulkDevices(builder);
|
|
105
|
+
const results = {
|
|
106
|
+
errors: [],
|
|
107
|
+
successes: [],
|
|
108
|
+
};
|
|
109
|
+
for (let i = 0; i < documents.length; i++) {
|
|
110
|
+
const document = documents[i];
|
|
111
|
+
const existingAssets = await this.sdk.document.mGet(document.tenantId, 'assets', document.assetIds);
|
|
112
|
+
if (strict && existingAssets.errors.length > 0) {
|
|
113
|
+
throw new kuzzle_1.NotFoundError(`Assets "${existingAssets.errors}" do not exist`);
|
|
114
|
+
}
|
|
115
|
+
const devicesContent = devices.filter(({ _id }) => document.deviceIds.includes(_id));
|
|
116
|
+
const deviceDocuments = [];
|
|
117
|
+
const assetDocuments = [];
|
|
118
|
+
for (const device of devicesContent) {
|
|
119
|
+
const decoder = this.decoders.get(device._source.model);
|
|
120
|
+
const measures = await decoder.copyToAsset(device);
|
|
121
|
+
const { assetId } = bulkData.find(data => data.deviceId === device._id);
|
|
122
|
+
deviceDocuments.push({ _id: device._id, body: { assetId } });
|
|
123
|
+
assetDocuments.push({ _id: assetId, body: { measures } });
|
|
124
|
+
}
|
|
125
|
+
const updatedDevice = await this.writeToDatabase(deviceDocuments, async (deviceDocuments) => {
|
|
126
|
+
const updated = await this.sdk.document.mUpdate(this.config.adminIndex, 'devices', deviceDocuments, { strict, ...options });
|
|
127
|
+
await this.sdk.document.mUpdate(document.tenantId, 'devices', deviceDocuments, { strict, ...options });
|
|
128
|
+
return {
|
|
129
|
+
successes: results.successes.concat(updated.successes),
|
|
130
|
+
errors: results.errors.concat(updated.errors)
|
|
131
|
+
};
|
|
132
|
+
});
|
|
133
|
+
const updatedAssets = await this.writeToDatabase(assetDocuments, async (assetDocuments) => {
|
|
134
|
+
const updated = await this.sdk.document.mUpdate(document.tenantId, 'assets', assetDocuments, { strict, ...options });
|
|
135
|
+
return {
|
|
136
|
+
successes: results.successes.concat(updated.successes),
|
|
137
|
+
errors: results.errors.concat(updated.errors)
|
|
138
|
+
};
|
|
139
|
+
});
|
|
140
|
+
results.successes.concat(updatedDevice.successes, updatedAssets.successes);
|
|
141
|
+
results.errors.concat(updatedDevice.errors, updatedDevice.errors);
|
|
142
|
+
}
|
|
143
|
+
return results;
|
|
144
|
+
}
|
|
145
|
+
async mUnlink(devices, { strict, options }) {
|
|
146
|
+
const unlinkedDevices = devices.filter(device => !device._source.assetId);
|
|
147
|
+
if (strict && unlinkedDevices.length > 0) {
|
|
148
|
+
const ids = unlinkedDevices.map(d => d._id);
|
|
149
|
+
throw new kuzzle_1.BadRequestError(`Devices "${ids}" are not linked to an asset`);
|
|
150
|
+
}
|
|
151
|
+
const builder = devices.map(device => {
|
|
152
|
+
const { _id, _source } = device;
|
|
153
|
+
return { tenantId: _source.tenantId, deviceId: _id, assetId: _source.assetId };
|
|
154
|
+
});
|
|
155
|
+
const documents = this.buildBulkDevices(builder);
|
|
156
|
+
const results = {
|
|
157
|
+
errors: [],
|
|
158
|
+
successes: [],
|
|
159
|
+
};
|
|
160
|
+
for (let i = 0; i < documents.length; i++) {
|
|
161
|
+
const document = documents[i];
|
|
162
|
+
const devicesContent = devices.filter(({ _id }) => document.deviceIds.includes(_id));
|
|
163
|
+
const deviceDocuments = [];
|
|
164
|
+
const assetDocuments = [];
|
|
165
|
+
for (const device of devicesContent) {
|
|
166
|
+
deviceDocuments.push({ _id: device._id, body: { assetId: null } });
|
|
167
|
+
const measures = await this.eraseAssetMeasure(document.tenantId, device);
|
|
168
|
+
assetDocuments.push({ _id: device._source.assetId, body: { measures } });
|
|
169
|
+
}
|
|
170
|
+
const updatedDevice = await this.writeToDatabase(deviceDocuments, async (deviceDocuments) => {
|
|
171
|
+
const updated = await this.sdk.document.mUpdate(this.config.adminIndex, 'devices', deviceDocuments, { strict, ...options });
|
|
172
|
+
await this.sdk.document.mUpdate(document.tenantId, 'devices', deviceDocuments, { strict, ...options });
|
|
173
|
+
return {
|
|
174
|
+
successes: results.successes.concat(updated.successes),
|
|
175
|
+
errors: results.errors.concat(updated.errors)
|
|
176
|
+
};
|
|
177
|
+
});
|
|
178
|
+
const updatedAssets = await this.writeToDatabase(assetDocuments, async (assetDocuments) => {
|
|
179
|
+
const updated = await this.sdk.document.mUpdate(document.tenantId, 'assets', assetDocuments, { strict, ...options });
|
|
180
|
+
return {
|
|
181
|
+
successes: results.successes.concat(updated.successes),
|
|
182
|
+
errors: results.errors.concat(updated.errors)
|
|
183
|
+
};
|
|
184
|
+
});
|
|
185
|
+
results.successes.concat(updatedDevice.successes, updatedAssets.successes);
|
|
186
|
+
results.errors.concat(updatedDevice.errors, updatedDevice.errors);
|
|
187
|
+
}
|
|
188
|
+
return results;
|
|
189
|
+
}
|
|
190
|
+
async eraseAssetMeasure(tenantId, device) {
|
|
191
|
+
const { _source: { measures } } = await this.sdk.document.get(tenantId, 'assets', device._source.assetId);
|
|
192
|
+
for (const [measureName] of Object.entries(device._source.measures)) {
|
|
193
|
+
if (measures[measureName]) {
|
|
194
|
+
measures[measureName] = undefined;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return measures;
|
|
198
|
+
}
|
|
199
|
+
async tenantExists(tenantId) {
|
|
200
|
+
const { result: tenantExists } = await this.sdk.query({
|
|
201
|
+
controller: 'device-manager/engine',
|
|
202
|
+
action: 'exists',
|
|
203
|
+
index: tenantId,
|
|
204
|
+
});
|
|
205
|
+
return tenantExists;
|
|
206
|
+
}
|
|
207
|
+
buildBulkDevices(bulkData) {
|
|
208
|
+
const documents = [];
|
|
209
|
+
for (let i = 0; i < bulkData.length; i++) {
|
|
210
|
+
const { tenantId, deviceId, assetId } = bulkData[i];
|
|
211
|
+
const document = documents.find(doc => doc.tenantId === tenantId);
|
|
212
|
+
if (document) {
|
|
213
|
+
document.deviceIds.push(deviceId);
|
|
214
|
+
document.assetIds.push(assetId);
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
documents.push({ tenantId, deviceIds: [deviceId], assetIds: [assetId] });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return documents;
|
|
221
|
+
}
|
|
222
|
+
formatDevicesContent(devices, document) {
|
|
223
|
+
const devicesContent = devices.filter(device => document.deviceIds.includes(device._id));
|
|
224
|
+
const devicesDocuments = devicesContent.map(device => {
|
|
225
|
+
device._source.tenantId = document.tenantId;
|
|
226
|
+
return { _id: device._id, body: device._source };
|
|
227
|
+
});
|
|
228
|
+
return devicesDocuments;
|
|
229
|
+
}
|
|
230
|
+
async writeToDatabase(deviceDocuments, writer) {
|
|
231
|
+
const results = {
|
|
232
|
+
errors: [],
|
|
233
|
+
successes: [],
|
|
234
|
+
};
|
|
235
|
+
const limit = global.kuzzle.config.limits.documentsWriteCount;
|
|
236
|
+
if (deviceDocuments.length <= limit) {
|
|
237
|
+
const { successes, errors } = await writer(deviceDocuments);
|
|
238
|
+
results.successes.push(successes);
|
|
239
|
+
results.errors.push(errors);
|
|
240
|
+
return results;
|
|
241
|
+
}
|
|
242
|
+
const writeMany = async (start, end) => {
|
|
243
|
+
const devices = deviceDocuments.slice(start, end);
|
|
244
|
+
const { successes, errors } = await writer(devices);
|
|
245
|
+
results.successes.push(successes);
|
|
246
|
+
results.errors.push(errors);
|
|
247
|
+
};
|
|
248
|
+
let offset = 0;
|
|
249
|
+
let offsetLimit = limit;
|
|
250
|
+
let done = false;
|
|
251
|
+
while (!done) {
|
|
252
|
+
await writeMany(offset, offsetLimit);
|
|
253
|
+
offset += limit;
|
|
254
|
+
offsetLimit += limit;
|
|
255
|
+
if (offsetLimit >= deviceDocuments.length) {
|
|
256
|
+
done = true;
|
|
257
|
+
await writeMany(offset, deviceDocuments.length);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return results;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
exports.DeviceService = DeviceService;
|
|
264
|
+
//# sourceMappingURL=DeviceService.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Plugin } from 'kuzzle';
|
|
2
|
+
export declare class MigrationService {
|
|
3
|
+
private config;
|
|
4
|
+
private context;
|
|
5
|
+
private prefix;
|
|
6
|
+
get sdk(): import("kuzzle").EmbeddedSDK;
|
|
7
|
+
constructor(pluginName: string, plugin: Plugin);
|
|
8
|
+
run(): Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Migrate the engine documents into the new config collection
|
|
11
|
+
*/
|
|
12
|
+
private engineCollection;
|
|
13
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MigrationService = void 0;
|
|
4
|
+
class MigrationService {
|
|
5
|
+
constructor(pluginName, plugin) {
|
|
6
|
+
this.config = plugin.config;
|
|
7
|
+
this.context = plugin.context;
|
|
8
|
+
this.prefix = pluginName;
|
|
9
|
+
}
|
|
10
|
+
get sdk() {
|
|
11
|
+
return this.context.accessors.sdk;
|
|
12
|
+
}
|
|
13
|
+
async run() {
|
|
14
|
+
global.app.install(`${this.prefix}-O.3.0`, async () => {
|
|
15
|
+
await this.engineCollection();
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Migrate the engine documents into the new config collection
|
|
20
|
+
*/
|
|
21
|
+
async engineCollection() {
|
|
22
|
+
if (!await this.sdk.collection.exists(this.config.adminIndex, 'engines')) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
this.context.log.info('Migrate engine documents into the config collection...');
|
|
26
|
+
let result = await this.sdk.document.search(this.config.adminIndex, 'engines', {}, { scroll: '1s', size: 100 });
|
|
27
|
+
let count = 0;
|
|
28
|
+
while (result) {
|
|
29
|
+
const documents = [];
|
|
30
|
+
for (const { _id, _source } of result.hits) {
|
|
31
|
+
documents.push({
|
|
32
|
+
_id,
|
|
33
|
+
body: {
|
|
34
|
+
type: 'engine-device-manager',
|
|
35
|
+
engine: { index: _source.index },
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
await this.sdk.document.mCreate(this.config.adminIndex, this.config.configCollection, documents, {
|
|
40
|
+
strict: true,
|
|
41
|
+
});
|
|
42
|
+
count += documents.length;
|
|
43
|
+
result = await result.next();
|
|
44
|
+
}
|
|
45
|
+
this.context.log.info(`Successfully migrated ${count} documents.`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
exports.MigrationService = MigrationService;
|
|
49
|
+
//# sourceMappingURL=MigrationService.js.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { KuzzleRequest, EmbeddedSDK, Plugin } from 'kuzzle';
|
|
2
|
+
import { Decoder } from './Decoder';
|
|
3
|
+
import { BatchWriter } from './BatchProcessing';
|
|
4
|
+
export declare type PayloadHandler = (request: KuzzleRequest, decoder: Decoder) => Promise<any>;
|
|
5
|
+
export declare class PayloadService {
|
|
6
|
+
private config;
|
|
7
|
+
private context;
|
|
8
|
+
private batchController;
|
|
9
|
+
get sdk(): EmbeddedSDK;
|
|
10
|
+
constructor(plugin: Plugin, batchWriter: BatchWriter);
|
|
11
|
+
process(request: KuzzleRequest, decoder: Decoder, { refresh }?: {
|
|
12
|
+
refresh?: any;
|
|
13
|
+
}): Promise<any>;
|
|
14
|
+
private register;
|
|
15
|
+
/**
|
|
16
|
+
* Device provisioning strategy.
|
|
17
|
+
*
|
|
18
|
+
* If autoProvisionning is on, device is automatically registered, otherwise
|
|
19
|
+
* we request the admin provisioning catalog to ensure this device is allowed
|
|
20
|
+
* to register.
|
|
21
|
+
*
|
|
22
|
+
* After registration, we look at the admin provisioning catalog entry to:
|
|
23
|
+
* - attach the device to a tenant
|
|
24
|
+
* - link the device to an asset of this tenant
|
|
25
|
+
*
|
|
26
|
+
* Then we look into the tenant provisioning catalog entry to:
|
|
27
|
+
* - link the device to an asset of this tenant
|
|
28
|
+
*/
|
|
29
|
+
private deviceProvisionning;
|
|
30
|
+
/**
|
|
31
|
+
* Get the device entry from the provisioning catalog in corresponding index
|
|
32
|
+
*/
|
|
33
|
+
private getCatalogEntry;
|
|
34
|
+
private update;
|
|
35
|
+
/**
|
|
36
|
+
* Propagate the measures inside the asset document.
|
|
37
|
+
*/
|
|
38
|
+
private propagateToAsset;
|
|
39
|
+
/**
|
|
40
|
+
* Creates an history entry for an asset
|
|
41
|
+
*/
|
|
42
|
+
private historizeAsset;
|
|
43
|
+
}
|