@citrineos/util
Version:
The OCPP util module which supplies helpful utilities like cache and queue connectors, etc.
318 lines • 15.5 kB
JavaScript
;
// 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