@citrineos/ocpprouter
Version:
The ocpprouter module for OCPP v2.0.1. This module is not intended to be used directly, but rather as a dependency for other modules.
224 lines • 12.5 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
import { AbstractModuleApi, AsDataEndpoint, BadRequestError, CacheNamespace, ConfigStoreFactory, DEFAULT_TENANT_ID, getCacheTenantPathMappingKey, HttpMethod, Namespace, NotFoundError, OCPP1_6_Namespace, OCPP2_0_1_Namespace, } from '@citrineos/base';
import { ChargingStationKeyQuerySchema, ConnectionDeleteQuerySchema, CreateSubscriptionSchema, ModelKeyQuerystringSchema, sequelize, Subscription, TenantQuerySchema, WebsocketDeleteQuerySchema, WebsocketGetQuerySchema, WebsocketMappingQuerySchema, WebsocketRequestSchema, } from '@citrineos/data';
import { Logger } from 'tslog';
/**
* Admin API for the OcppRouter.
*/
export class AdminApi extends AbstractModuleApi {
_networkConnection;
_subscriptionRepository;
_serverNetworkProfileRepository;
/**
* Constructs a new instance of the class.
*
* @param {IMessageRouter} ocppRouter - The OcppRouter module.
* @param {INetworkConnection} networkConnection - The network connection instance.
* @param {FastifyInstance} server - The Fastify server instance.
* @param {BootstrapConfig & SystemConfig} config - The configuration instance.
* @param {Logger<ILogObj>} [logger] - The logger instance.
* @param {ISubscriptionRepository} [subscriptionRepository] - The subscription repository instance.
* @param {IServerNetworkProfileRepository} [serverNetworkProfileRepository] - The server network profile repository instance.
*/
constructor(ocppRouter, networkConnection, server, config, logger, subscriptionRepository, serverNetworkProfileRepository) {
super(ocppRouter, server, null, logger);
this._networkConnection = networkConnection;
this._subscriptionRepository =
subscriptionRepository || new sequelize.SequelizeSubscriptionRepository(config, this._logger);
this._serverNetworkProfileRepository =
serverNetworkProfileRepository ||
new sequelize.SequelizeServerNetworkProfileRepository(config, this._logger);
}
// N.B.: When adding subscriptions, chargers may be connected to a different instance of Citrine.
// If this is the case, new subscriptions will not take effect until the charger reconnects.
/**
* Creates a {@link Subscription}.
* Will always create a new entity and return its id.
*
* @param {FastifyRequest<{ Body: Subscription }>} request - The request object, containing the body which is parsed as a {@link Subscription}.
* @return {Promise<number>} The id of the created subscription.
*/
async postSubscription(request) {
const tenantId = request.query.tenantId;
request.body.tenantId = tenantId;
if (!request.body.onClose &&
!request.body.onConnect &&
!request.body.onMessage &&
!request.body.sentMessage) {
throw new BadRequestError('Must specify at least one of onConnect, onClose, onMessage, sentMessage to true.');
}
return this._subscriptionRepository
.create(tenantId, request.body)
.then((subscription) => subscription?.id);
}
async getSubscriptionsByChargingStation(request) {
return this._subscriptionRepository.readAllByStationId(request.query.tenantId, request.query.stationId);
}
async deleteSubscriptionById(request) {
const tenantId = request.query.tenantId ?? DEFAULT_TENANT_ID;
return this._subscriptionRepository
.deleteByKey(tenantId, request.query.id.toString())
.then(() => true);
}
async getWebsocketConfigurations(request) {
if (request.query.id) {
const websocketConfig = this._module.config.util.networkConnection.websocketServers.find((ws) => ws.id === request.query.id);
if (!websocketConfig) {
throw new NotFoundError(`Could not find websocket configuration with id ${request.query.id}`);
}
else {
return websocketConfig;
}
}
else {
// TODO when available (coming soon in a separate feature), filter by tenantId if the tenantId query param exists
return this._module.config.util.networkConnection.websocketServers;
}
}
async createWebsocketConfiguration(request) {
const existingConfig = this._module.config.util.networkConnection.websocketServers.find((ws) => ws.id === request.body.id);
if (existingConfig) {
throw new BadRequestError(`Websocket configuration with id ${request.body.id} already exists.`);
}
else {
this._module.config.util.networkConnection.websocketServers.push(request.body);
await ConfigStoreFactory.getInstance().saveConfig(this._module.config);
return request.body;
}
}
/**
* Adds or updates a mapping from a path segment to a tenant for a specific websocket server.
*/
async putWebsocketMapping(request) {
const { id: serverId, path, tenantId } = request.query;
this._module.config = (await ConfigStoreFactory.getInstance().fetchConfig());
const websocketConfig = this._module.config.util.networkConnection.websocketServers.find((ws) => ws.id === serverId);
if (!websocketConfig) {
throw new NotFoundError(`Websocket configuration with id ${serverId} not found`);
}
if (!websocketConfig.tenantPathMapping) {
websocketConfig.tenantPathMapping = {};
}
if (websocketConfig.tenantPathMapping[path] !== undefined &&
websocketConfig.tenantPathMapping[path] !== tenantId) {
throw new BadRequestError(`Path ${path} is already mapped to tenant ${websocketConfig.tenantPathMapping[path]}`);
}
websocketConfig.tenantPathMapping[path] = tenantId;
websocketConfig.dynamicTenantResolution = true;
await ConfigStoreFactory.getInstance().saveConfig(this._module.config);
await this._serverNetworkProfileRepository.upsertServerNetworkProfile(websocketConfig, this._module.config.maxCallLengthSeconds);
await this._module.cache.set(getCacheTenantPathMappingKey(websocketConfig.id, path), tenantId.toString(), CacheNamespace.TenantPathMapping);
return websocketConfig;
}
/**
* Removes a mapping for a specific path OR all mappings for a specific tenant from a websocket server.
*/
async deleteWebsocketMapping(request) {
const { id: serverId, path, tenantId } = request.query;
this._module.config = (await ConfigStoreFactory.getInstance().fetchConfig());
const websocketConfig = this._module.config.util.networkConnection.websocketServers.find((ws) => ws.id === serverId);
if (!websocketConfig) {
throw new NotFoundError(`Websocket configuration with id ${serverId} not found`);
}
if (websocketConfig.tenantPathMapping) {
if (websocketConfig.tenantPathMapping[path] === undefined) {
throw new NotFoundError(`Mapping for path ${path} not found in websocket configuration ${serverId}`);
}
else if (websocketConfig.tenantPathMapping[path] !== tenantId) {
throw new BadRequestError(`Mapping for path ${path} is not mapped to tenant ${tenantId}`);
}
else if (websocketConfig.tenantPathMapping[path] === tenantId) {
delete websocketConfig.tenantPathMapping[path];
}
await ConfigStoreFactory.getInstance().saveConfig(this._module.config);
await this._serverNetworkProfileRepository.upsertServerNetworkProfile(websocketConfig, this._module.config.maxCallLengthSeconds);
await this._module.cache.remove(getCacheTenantPathMappingKey(websocketConfig.id, path), CacheNamespace.TenantPathMapping);
}
return websocketConfig;
}
async deleteWebsocketConfiguration(request) {
const existingConfigIndex = this._module.config.util.networkConnection.websocketServers.findIndex((ws) => ws.id === request.query.id);
if (existingConfigIndex) {
this._module.config.util.networkConnection.websocketServers.splice(existingConfigIndex, 1);
await ConfigStoreFactory.getInstance().saveConfig(this._module.config);
}
}
// Forcibly disconnect a websocket connection by station id and tenant id and mark the station as offline
async deleteWebsocketConnection(request) {
await this._networkConnection.disconnect(request.query.tenantId, request.query.stationId);
}
/**
* Overrides superclass method to generate the URL path based on the input {@link Namespace}
* and the module's endpoint prefix configuration.
*
* @param {Namespace} input - The input {@link Namespace}.
* @return {string} - The generated URL path.
*/
_toDataPath(input) {
const endpointPrefix = '/ocpprouter';
return super._toDataPath(input, endpointPrefix);
}
}
__decorate([
AsDataEndpoint(OCPP2_0_1_Namespace.Subscription, HttpMethod.Post, TenantQuerySchema, CreateSubscriptionSchema),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], AdminApi.prototype, "postSubscription", null);
__decorate([
AsDataEndpoint(OCPP2_0_1_Namespace.Subscription, HttpMethod.Get, ChargingStationKeyQuerySchema),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], AdminApi.prototype, "getSubscriptionsByChargingStation", null);
__decorate([
AsDataEndpoint(OCPP2_0_1_Namespace.Subscription, HttpMethod.Delete, ModelKeyQuerystringSchema),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], AdminApi.prototype, "deleteSubscriptionById", null);
__decorate([
AsDataEndpoint(Namespace.Websocket, HttpMethod.Get, WebsocketGetQuerySchema),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], AdminApi.prototype, "getWebsocketConfigurations", null);
__decorate([
AsDataEndpoint(Namespace.Websocket, HttpMethod.Post, undefined, WebsocketRequestSchema),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], AdminApi.prototype, "createWebsocketConfiguration", null);
__decorate([
AsDataEndpoint(Namespace.WebsocketMapping, HttpMethod.Put, WebsocketMappingQuerySchema),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], AdminApi.prototype, "putWebsocketMapping", null);
__decorate([
AsDataEndpoint(Namespace.WebsocketMapping, HttpMethod.Delete, WebsocketMappingQuerySchema),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], AdminApi.prototype, "deleteWebsocketMapping", null);
__decorate([
AsDataEndpoint(Namespace.Websocket, HttpMethod.Delete, WebsocketDeleteQuerySchema),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], AdminApi.prototype, "deleteWebsocketConfiguration", null);
__decorate([
AsDataEndpoint(Namespace.Connection, HttpMethod.Delete, ConnectionDeleteQuerySchema),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], AdminApi.prototype, "deleteWebsocketConnection", null);
//# sourceMappingURL=DataApi.js.map