UNPKG

@citrineos/util

Version:

The OCPP util module which supplies helpful utilities like cache and queue connectors, etc.

318 lines 15.5 kB
"use strict"; // Copyright Contributors to the CitrineOS Project // // SPDX-License-Identifier: Apache 2.0 var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DirectusUtil = void 0; const data_1 = require("@citrineos/data"); const sdk_1 = require("@directus/sdk"); const json_schema_faker_1 = require("json-schema-faker"); const tslog_1 = require("tslog"); class DirectusUtil { constructor(config, configFileName, configDir, logger) { // config = config; this._logger = logger ? logger.getSubLogger({ name: this.constructor.name }) : new tslog_1.Logger({ name: this.constructor.name }); let client; if (config.token) { // Auth with static token client = (0, sdk_1.createDirectus)(`http://${config.host}:${config.port}`) .with((0, sdk_1.staticToken)(config.token)) .with((0, sdk_1.rest)()); } else if (config.username && config.password) { // Auth with username and password client = (0, sdk_1.createDirectus)(`http://${config.host}:${config.port}`) .with((0, sdk_1.authentication)()) .with((0, sdk_1.rest)()); this._logger.info(`Logging into Directus as ${config.username}`); client .login(config.username, config.password) .then() .catch((error) => { this._logger.error('DirectusUtil could not perform client login', error); }); } else { // No auth client = (0, sdk_1.createDirectus)(`http://${config.host}:${config.port}`).with((0, sdk_1.rest)()); } this._client = client; this._configFileName = configFileName; this._configDir = configDir; } fetchConfig() { return __awaiter(this, void 0, void 0, function* () { const configString = yield this.getFile(this._configFileName); if (!configString) return null; return JSON.parse(configString); }); } saveConfig(config) { return __awaiter(this, void 0, void 0, function* () { try { const fileId = yield this.saveFile(this._configFileName, Buffer.from(JSON.stringify(config, null, 2)), this._configDir); this._logger.debug(`File saved: ${fileId}`); } catch (error) { this._logger.error(`Error saving file: ${error}`); } }); } addDirectusMessageApiFlowsFastifyRouteHook(routeOptions, schemas) { return __awaiter(this, void 0, void 0, function* () { var _a; const messagePath = routeOptions.url; // 'Url' here means the route specified when the endpoint was added to the fastify server, such as '/ocpp/configuration/reset' if (messagePath.split('/')[1] === 'ocpp') { // Message API check: relies on implementation of _toMessagePath in AbstractModuleApi which prefixes url with '/ocpp/' this._logger.info(`Adding Directus Message API flow for ${messagePath}`); // Parse action from url: relies on implementation of _toMessagePath in AbstractModuleApi which puts CallAction in final path part const lowercaseAction = messagePath.split('/').pop(); const action = lowercaseAction.charAt(0).toUpperCase() + lowercaseAction.slice(1); // _addMessageRoute in AbstractModuleApi adds the bodySchema specified in the @MessageEndpoint decorator to the fastify route schema // These body schemas are the ones generated directly from the specification using the json-schema-processor in 00_Base const bodySchema = (_a = routeOptions.schema) === null || _a === void 0 ? void 0 : _a.body; if (bodySchema && bodySchema.$ref && schemas[bodySchema.$ref]) { yield this.addDirectusFlowForAction(action, messagePath, schemas[bodySchema.$ref]); } } }); } getFile(id) { return __awaiter(this, void 0, void 0, function* () { try { const result = yield this._client.request((0, sdk_1.readAssetBlob)(id)); return String(result.text()); } catch (error) { this._logger.error(`Get file ${id} failed: ${JSON.stringify(error)}`); return undefined; } }); } saveFile(fileName, content, filePath) { return __awaiter(this, void 0, void 0, function* () { let fileType; if (fileName.lastIndexOf('.') > -1 && fileName.lastIndexOf('.') < fileName.length - 1) { fileType = fileName.substring(fileName.lastIndexOf('.')); } const formData = new FormData(); if (fileType) { formData.append('type', fileType); } if (filePath) { formData.append('folder', filePath); } formData.append('file', new Blob([content]), fileName); try { const file = yield this._client.request((0, sdk_1.uploadFiles)(formData)); return file['id']; } catch (error) { this._logger.error('Upload file failed: ', error); throw new Error(`Upload file ${fileName} failed.`); } }); } addDirectusFlowForAction(action, messagePath, bodySchema) { return __awaiter(this, void 0, void 0, function* () { json_schema_faker_1.JSONSchemaFaker.option({ useExamplesValue: true, useDefaultValue: true, requiredOnly: true, pruneProperties: ['customData'], }); const bodyData = json_schema_faker_1.JSONSchemaFaker.generate(bodySchema); const flowOptions = { collections: [data_1.sequelize.ChargingStation.getTableName()], async: true, location: 'item', requireConfirmation: true, confirmationDescription: 'Are you sure you want to execute this flow?', fields: [ { field: 'citrineUrl', type: 'string', name: 'CitrineOS URL', meta: { interface: 'select-dropdown', note: 'The URL of the CitrineOS server. For example: http://localhost:8080/.', width: 'full', required: true, options: { placeholder: 'e.g. http://localhost:8080/', trim: true, iconLeft: 'web_asset', choices: [ { text: 'Localhost (localhost:8080)', value: 'http://localhost:8080', }, { text: 'Docker (citrine:8080)', value: 'http://citrine:8080', }, { text: 'Docker Hybrid (host.docker.internal:8080)', value: 'http://host.docker.internal:8080', }, ], allowOther: true, }, }, }, { field: 'tenantId', type: 'string', name: 'Tenant ID', meta: { interface: 'select-dropdown', note: 'The tenant identifier of the charging station. To be removed in future releases.', width: 'full', required: true, options: { placeholder: 'e.g. T01', trim: true, choices: [ { text: 'Default Tenant (T01)', value: 'T01', }, ], allowOther: true, }, }, }, { field: 'payload', type: 'json', name: 'Payload', meta: { interface: 'input-code', note: 'The payload to be sent in the call to CitrineOS.', width: 'full', required: true, options: { lineWrapping: true, language: 'JSON', template: JSON.stringify(bodyData, null, 2), }, }, }, ], }; const flow = { name: action, color: '#2ECDA7', description: action, status: 'active', trigger: 'manual', accountability: 'all', options: flowOptions, }; const notificationOperation = { name: 'Send Status Notification', key: 'send_status_notification', type: 'notification', position_x: 20, position_y: 17, options: { recipient: '{{$accountability.user}}', subject: `${action} - Success: {{$last.data.success}}`, message: '{{$last.data.payload}}', }, }; const webhookOperation = { name: 'CitrineOS Webhook', key: 'citrine_webhook', type: 'request', position_x: 40, position_y: 1, options: { url: `{{$trigger.body.citrineUrl}}${messagePath}?identifier={{$last.id}}&tenantId={{$trigger.body.tenantId}}`, method: 'POST', body: '{{$trigger.body.payload}}', }, }; const readOperation = { name: 'Read Charging Station Data', key: 'charging_station_read', type: 'item-read', position_x: 20, position_y: 1, options: { collection: data_1.sequelize.ChargingStation.getTableName(), key: '{{$last.body.keys[0]}}', }, }; let errorLogVerb = 'reading'; try { const readFlowsResponse = yield this._client.request((0, sdk_1.readFlows)({ filter: { name: { _eq: action } }, fields: ['id', 'name'], })); if (readFlowsResponse.length > 0) { errorLogVerb = 'updating'; this._logger.info('Flow already exists in Directus for ', action, '. Updating Flow.'); const existingFlow = readFlowsResponse[0]; yield this.updateMessageApiFlow(existingFlow.id, flow, notificationOperation, webhookOperation, readOperation); this._logger.info(`Successfully updated Directus Flow for ${action}`); } else { errorLogVerb = 'creating'; yield this.createMessageApiFlow(flow, notificationOperation, webhookOperation, readOperation); this._logger.info(`Successfully created Directus Flow for ${action}`); } } catch (error) { this._logger.error(`Error ${errorLogVerb} Directus Flow: ${JSON.stringify(error)}`); } }); } createMessageApiFlow(flow, notificationOperation, webhookOperation, readOperation) { return __awaiter(this, void 0, void 0, function* () { // Create flow const flowCreationResponse = yield this._client.request((0, sdk_1.createFlow)(flow)); // Create notification operation notificationOperation.flow = flowCreationResponse.id; const notificationOperationCreationResponse = yield this._client.request((0, sdk_1.createOperation)(notificationOperation)); // Create webhook operation webhookOperation.flow = flowCreationResponse.id; webhookOperation.resolve = notificationOperationCreationResponse.id; const webhookOperationCreationResponse = yield this._client.request((0, sdk_1.createOperation)(webhookOperation)); // Create read operation readOperation.flow = flowCreationResponse.id; readOperation.resolve = webhookOperationCreationResponse.id; const readOperationCreationResponse = yield this._client.request((0, sdk_1.createOperation)(readOperation)); // Update flow with operations yield this._client.request((0, sdk_1.updateFlow)(flowCreationResponse.id, { operation: readOperationCreationResponse.id, })); }); } updateMessageApiFlow(flowId, updatedFlow, notificationOperation, webhookOperation, readOperation) { return __awaiter(this, void 0, void 0, function* () { // Update flow const flowUpdateResponse = yield this._client.request((0, sdk_1.updateFlow)(flowId, updatedFlow)); // Update read operation const readOperationUpdateResponse = yield this._client.request((0, sdk_1.updateOperation)(flowUpdateResponse.operation, readOperation)); // Update webhook operation const webhookOperationUpdateResponse = yield this._client.request((0, sdk_1.updateOperation)(readOperationUpdateResponse.resolve, webhookOperation)); // Update notification operation yield this._client.request((0, sdk_1.updateOperation)(webhookOperationUpdateResponse.resolve, notificationOperation)); }); } } exports.DirectusUtil = DirectusUtil; //# sourceMappingURL=directus.js.map