eMSP Site Name with right separator. Async Task framework keeps tasks after execution + log duration.
MODIFY
src/async-task/AsyncTaskManager.ts
src/async-task/AsyncTaskManager.ts
@@ -10,8 +10,6 @@ import LockingManager from '../locking/LockingManager';
import Logging from '../utils/Logging';
import { ServerAction } from '../types/Server';
import TagsImportAsyncTask from './tasks/TagsImportAsyncTask';
-import User from '../types/User';
-import UserToken from '../types/UserToken';
import UsersImportAsyncTask from './tasks/UsersImportAsyncTask';
import Utils from '../utils/Utils';
@@ -26,13 +24,11 @@ export default class AsyncTaskManager {
// Active?
if (AsyncTaskManager.asyncTaskConfig?.active) {
// Turn all Running task to Pending
- const updatedAsyncTasks = await AsyncTaskStorage.updateRunningAsyncTaskToPending();
+ await AsyncTaskStorage.updateRunningAsyncTaskToPending();
// Run it
- if (updatedAsyncTasks > 0) {
void AsyncTaskManager.handleAsyncTasks();
}
}
- }
public static async handleAsyncTasks(): Promise<void> {
// Active?
@@ -54,7 +50,8 @@ export default class AsyncTaskManager {
nbrTasksInParallel = this.asyncTaskConfig.nbrTasksInParallel;
}
// Get the tasks
- const asyncTasks = await AsyncTaskStorage.getAsyncTasks( { status: AsyncTaskStatus.PENDING }, Constants.DB_PARAMS_MAX_LIMIT);
+ const asyncTasks = await AsyncTaskStorage.getAsyncTasks(
+ { status: AsyncTaskStatus.PENDING }, Constants.DB_PARAMS_MAX_LIMIT);
// Process them
let abstractAsyncTask: AbstractAsyncTask;
if (!Utils.isEmptyArray(asyncTasks.result)) {
@@ -86,12 +83,13 @@ export default class AsyncTaskManager {
// Get the lock
const asyncTaskLock = await LockingHelper.createAsyncTaskLock(Constants.DEFAULT_TENANT, asyncTask);
if (asyncTaskLock) {
- try {
const startAsyncTaskTime = new Date().getTime();
+ try {
// Update the task
asyncTask.execTimestamp = new Date();
asyncTask.execHost = Utils.getHostname();
asyncTask.status = AsyncTaskStatus.RUNNING;
+ asyncTask.lastChangedOn = asyncTask.execTimestamp;
await AsyncTaskStorage.saveAsyncTask(asyncTask);
// Log
await Logging.logInfo({
@@ -102,11 +100,15 @@ export default class AsyncTaskManager {
});
// Run
await abstractAsyncTask.run();
- // Delete the task
- await AsyncTaskStorage.deleteAsyncTask(asyncTask.id);
+ // Duration
+ const asyncTaskTotalDurationSecs = Utils.truncTo((new Date().getTime() - startAsyncTaskTime) / 1000, 2);
+ // Mark the task
+ asyncTask.status = AsyncTaskStatus.SUCCESS;
+ asyncTask.execDurationSecs = asyncTaskTotalDurationSecs;
+ asyncTask.lastChangedOn = new Date();
+ await AsyncTaskStorage.saveAsyncTask(asyncTask);
processedTask.inSuccess++;
// Log
- const asyncTaskTotalDurationSecs = Math.trunc((new Date().getTime() - startAsyncTaskTime) / 1000);
await Logging.logInfo({
tenantID: Constants.DEFAULT_TENANT,
action: ServerAction.ASYNC_TASK,
@@ -118,6 +120,8 @@ export default class AsyncTaskManager {
// Update the task
asyncTask.status = AsyncTaskStatus.ERROR;
asyncTask.message = error.message;
+ asyncTask.execDurationSecs = Utils.truncTo((new Date().getTime() - startAsyncTaskTime) / 1000, 2);
+ asyncTask.lastChangedOn = new Date();
await AsyncTaskStorage.saveAsyncTask(asyncTask);
// Log error
Logging.logError({
@@ -136,7 +140,7 @@ export default class AsyncTaskManager {
},
{ concurrency: nbrTasksInParallel }).then(() => {
// Log result
- const totalDurationSecs = Math.trunc((new Date().getTime() - startTime) / 1000);
+ const totalDurationSecs = Utils.truncTo((new Date().getTime() - startTime) / 1000, 2);
void Logging.logActionsResponse(Constants.DEFAULT_TENANT, ServerAction.ASYNC_TASK,
MODULE_NAME, 'handleAsyncTasks', processedTask,
`{{inSuccess}} Async Task(s) were successfully processed in ${totalDurationSecs} secs`,
@@ -156,7 +160,7 @@ export default class AsyncTaskManager {
}
}
- public static async createAndSaveAsyncTasks(asyncTask: Omit<AsyncTask, 'id'>, user?: UserToken|User|string): Promise<void> {
+ public static async createAndSaveAsyncTasks(asyncTask: Omit<AsyncTask, 'id'>): Promise<void> {
// Check
if (Utils.isNullOrUndefined(asyncTask)) {
throw new Error("The Async Task must not be null");
@@ -173,7 +177,6 @@ export default class AsyncTaskManager {
}
// Set
asyncTask.status = AsyncTaskStatus.PENDING;
- asyncTask.createdBy = user as User ?? null;
asyncTask.createdOn = new Date();
// Save
await AsyncTaskStorage.saveAsyncTask(asyncTask as AsyncTask);

MODIFY
src/client/ocpi/EmspOCPIClient.ts
src/client/ocpi/EmspOCPIClient.ts
-import { OCPILocation, OCPILocationOptions } from '../../types/ocpi/OCPILocation';
import { OCPIToken, OCPITokenType, OCPITokenWhitelist } from '../../types/ocpi/OCPIToken';
import BackendError from '../../exception/BackendError';
@@ -16,6 +15,7 @@ import { OCPICommandType } from '../../types/ocpi/OCPICommandType';
import OCPIEndpoint from '../../types/ocpi/OCPIEndpoint';
import OCPIEndpointStorage from '../../storage/mongodb/OCPIEndpointStorage';
import { OCPIEvseStatus } from '../../types/ocpi/OCPIEvse';
+import { OCPILocation } from '../../types/ocpi/OCPILocation';
import { OCPIResult } from '../../types/ocpi/OCPIResult';
import { OCPIRole } from '../../types/ocpi/OCPIRole';
import { OCPISession } from '../../types/ocpi/OCPISession';
@@ -24,7 +24,6 @@ import { OCPIStopSession } from '../../types/ocpi/OCPIStopSession';
import OCPIUtils from '../../server/ocpi/OCPIUtils';
import OCPIUtilsService from '../../server/ocpi/ocpi-services-impl/ocpi-2.1.1/OCPIUtilsService';
import { OcpiSetting } from '../../types/Setting';
-import RoamingUtils from '../../utils/RoamingUtils';
import { ServerAction } from '../../types/Server';
import Site from '../../types/Site';
import SiteArea from '../../types/SiteArea';
@@ -67,7 +66,7 @@ export default class EmspOCPIClient extends OCPIClient {
let tokens: DataResult<OCPIToken>;
do {
// Get all tokens
- tokens = await OCPIUtilsService.getAllTokens(
+ tokens = await OCPIUtilsService.getTokens(
this.tenant, Constants.DB_RECORD_COUNT_DEFAULT, currentSkip);
for (const token of tokens.result) {
result.total++;
@@ -299,11 +298,11 @@ export default class EmspOCPIClient extends OCPIClient {
return result;
}
- public async processLocation(location: OCPILocation, company: Company, sites: Site[]): Promise<void> {
+ public async processLocation(location: OCPILocation, company: Company, existingSites: Site[]): Promise<void> {
// Handle Site
let site: Site;
const siteName = location.operator?.name ?? OCPIUtils.buildOperatorName(this.ocpiEndpoint.countryCode, this.ocpiEndpoint.partyId);
- site = sites.find((value) => value.name === siteName);
+ site = existingSites.find((existingSite) => existingSite.name === siteName);
if (!site) {
// Create Site
site = {
@@ -319,7 +318,7 @@ export default class EmspOCPIClient extends OCPIClient {
coordinates: []
}
} as Site;
- if (location.coordinates && location.coordinates.latitude && location.coordinates.longitude) {
+ if (location.coordinates?.latitude && location.coordinates?.longitude) {
site.address.coordinates = [
Utils.convertToFloat(location.coordinates.longitude),
Utils.convertToFloat(location.coordinates.latitude)
@@ -327,15 +326,14 @@ export default class EmspOCPIClient extends OCPIClient {
}
site.id = await SiteStorage.saveSite(this.tenant.id, site, false);
// Push the Site then it can be retrieve in the next round
- sites.push(site);
+ existingSites.push(site);
}
- const locationName = site.name + '-' + location.id;
+ const locationName = site.name + Constants.OCPI_SEPARATOR + location.id;
// Handle Site Area
- const siteAreas = await SiteAreaStorage.getSiteAreas(this.tenant.id, {
- siteIDs: [site.id],
- search: locationName
- }, Constants.DB_PARAMS_SINGLE_RECORD);
- let siteArea = siteAreas && siteAreas.result.length === 1 ? siteAreas.result[0] : null;
+ const siteAreas = await SiteAreaStorage.getSiteAreas(this.tenant.id,
+ { siteIDs: [site.id], name: locationName, issuer: false },
+ Constants.DB_PARAMS_SINGLE_RECORD);
+ let siteArea = !Utils.isEmptyArray(siteAreas.result) ? siteAreas.result[0] : null;
if (!siteArea) {
// Create Site Area
siteArea = {
@@ -352,7 +350,7 @@ export default class EmspOCPIClient extends OCPIClient {
coordinates: []
}
} as SiteArea;
- if (location.coordinates && location.coordinates.latitude && location.coordinates.longitude) {
+ if (location.coordinates?.latitude && location.coordinates?.longitude) {
siteArea.address.coordinates = [
Utils.convertToFloat(location.coordinates.longitude),
Utils.convertToFloat(location.coordinates.latitude)

MODIFY
src/client/ocpi/OCPIClient.ts
src/client/ocpi/OCPIClient.ts
@@ -168,9 +168,6 @@ export default abstract class OCPIClient {
return response;
}
- /**
- * GET /ocpi/{role}/{version}
- */
public async getServices(): Promise<any> {
// Log
Logging.logInfo({
@@ -208,9 +205,6 @@ export default abstract class OCPIClient {
return response;
}
- /**
- * POST /ocpi/{role}/{version}/credentials
- */
public async postCredentials(): Promise<AxiosResponse<OCPICredential>> {
// Get credentials url
const credentialsUrl = this.getEndpointUrl('credentials', ServerAction.OCPI_POST_CREDENTIALS);

MODIFY
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/OCPIUtilsService.ts
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/OCPIUtilsService.ts
@@ -81,12 +81,12 @@ export default class OCPIUtilsService {
evses: [evse]
}
} as ChargingStation;
- if (evse.coordinates && evse.coordinates.latitude && evse.coordinates.longitude) {
+ if (evse.coordinates?.latitude && evse.coordinates?.longitude) {
chargingStation.coordinates = [
Utils.convertToFloat(evse.coordinates.longitude),
Utils.convertToFloat(evse.coordinates.latitude)
];
- } else if (location && location.coordinates && location.coordinates.latitude && location.coordinates.longitude) {
+ } else if (location?.coordinates?.latitude && location?.coordinates?.longitude) {
chargingStation.coordinates = [
Utils.convertToFloat(location.coordinates.longitude),
Utils.convertToFloat(location.coordinates.latitude)
@@ -148,7 +148,8 @@ export default class OCPIUtilsService {
return ocpiLocationsResult;
}
- public static async getAllTokens(tenant: Tenant, limit: number, skip: number, dateFrom?: Date, dateTo?: Date): Promise<DataResult<OCPIToken>> {
+ public static async getTokens(tenant: Tenant, limit: number, skip: number,
+ dateFrom?: Date, dateTo?: Date): Promise<DataResult<OCPIToken>> {
// Result
const tokens: OCPIToken[] = [];
// Get all tokens
@@ -768,8 +769,7 @@ export default class OCPIUtilsService {
});
}
const evse = session.location.evses[0];
- const chargingStation = await ChargingStationStorage.getChargingStationByOcpiEvseID(
- tenantId, evse.evse_id);
+ const chargingStation = await ChargingStationStorage.getChargingStationByOcpiEvseID(tenantId, evse.evse_id);
if (!chargingStation) {
throw new AppError({
source: Constants.CENTRAL_SERVER,

MODIFY
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/emsp/EMSPTokensEndpoint.ts
src/server/ocpi/ocpi-services-impl/ocpi-2.1.1/emsp/EMSPTokensEndpoint.ts
@@ -39,7 +39,7 @@ export default class EMSPTokensEndpoint extends AbstractEndpoint {
const offset = (req.query.offset) ? Utils.convertToInt(req.query.offset) : 0;
const limit = (req.query.limit && Utils.convertToInt(req.query.limit) < Constants.OCPI_RECORDS_LIMIT) ? Utils.convertToInt(req.query.limit) : Constants.OCPI_RECORDS_LIMIT;
// Get all tokens
- const tokens = await OCPIUtilsService.getAllTokens(tenant, limit, offset, Utils.convertToDate(req.query.date_from), Utils.convertToDate(req.query.date_to));
+ const tokens = await OCPIUtilsService.getTokens(tenant, limit, offset, Utils.convertToDate(req.query.date_from), Utils.convertToDate(req.query.date_to));
// Set header
res.set({
'X-Total-Count': tokens.count,

MODIFY
src/server/rest/v1/service/TagService.ts
src/server/rest/v1/service/TagService.ts
@@ -595,7 +595,7 @@ export default class TagService {
tenantID: req.tenant.id,
module: MODULE_NAME,
method: 'handleImportTags',
- }, req.user);
+ });
// Respond
res.json({ ...result, ...Constants.REST_RESPONSE_SUCCESS });
next();

MODIFY
src/server/rest/v1/service/UserService.ts
src/server/rest/v1/service/UserService.ts
@@ -993,7 +993,7 @@ export default class UserService {
tenantID: req.tenant.id,
module: MODULE_NAME,
method: 'handleImportUsers',
- }, req.user);
+ });
// Respond
res.json({ ...result, ...Constants.REST_RESPONSE_SUCCESS });
next();

MODIFY
src/storage/mongodb/AsyncTaskStorage.ts
src/storage/mongodb/AsyncTaskStorage.ts
import AsyncTask, { AsyncTaskStatus } from '../../types/AsyncTask';
+import { ObjectID, ObjectId } from 'bson';
import global, { FilterParams } from '../../types/GlobalType';
import Constants from '../../utils/Constants';
@@ -25,7 +26,7 @@ export default class AsyncTaskStorage {
const uniqueTimerID = Logging.traceStart(Constants.DEFAULT_TENANT, MODULE_NAME, 'saveAsyncTask');
// Set
const asyncTaskMDB: any = {
- _id: asyncTaskToSave.id ?? Cypher.hash(`${asyncTaskToSave.tenantID}~${asyncTaskToSave.name}~${asyncTaskToSave.parameters ? JSON.stringify(asyncTaskToSave.parameters) : ''}`),
+ _id: asyncTaskToSave.id ? Utils.convertToObjectID(asyncTaskToSave.id) : new ObjectID(),
name: asyncTaskToSave.name,
action: asyncTaskToSave.action,
type: asyncTaskToSave.type,
@@ -34,6 +35,7 @@ export default class AsyncTaskStorage {
parent: asyncTaskToSave.parent,
execHost: asyncTaskToSave.execHost,
execTimestamp: Utils.convertToDate(asyncTaskToSave.execTimestamp),
+ execDurationSecs: Utils.convertToFloat(asyncTaskToSave.execDurationSecs),
module: asyncTaskToSave.module,
method: asyncTaskToSave.method,
message: asyncTaskToSave.message,
@@ -76,6 +78,10 @@ export default class AsyncTaskStorage {
if (params.status) {
filters.status = params.status;
}
+ // Add filters
+ aggregation.push({
+ $match: filters
+ });
// Limit records?
if (!dbParams.onlyRecordCount) {
// Always limit the nbr of record to avoid perfs issues

MODIFY
src/storage/mongodb/SiteAreaStorage.ts
src/storage/mongodb/SiteAreaStorage.ts
@@ -136,7 +136,7 @@ export default class SiteAreaStorage {
public static async getSiteAreas(tenantID: string,
params: {
- siteAreaIDs?: string[]; search?: string; siteIDs?: string[]; withSite?: boolean; issuer?: boolean;
+ siteAreaIDs?: string[]; search?: string; siteIDs?: string[]; withSite?: boolean; issuer?: boolean; name?: string;
withChargingStations?: boolean; withOnlyChargingStations?: boolean; withAvailableChargingStations?: boolean;
locCoordinates?: number[]; locMaxDistanceMeters?: number; smartCharging?: boolean; withImage?: boolean;
} = {},
@@ -190,9 +190,12 @@ export default class SiteAreaStorage {
if (Utils.objectHasProperty(params, 'issuer') && Utils.isBoolean(params.issuer)) {
filters.issuer = params.issuer;
}
- if (params.smartCharging === true || params.smartCharging === false) {
+ if (Utils.objectHasProperty(params, 'smartCharging') && Utils.isBoolean(params.smartCharging)) {
filters.smartCharging = params.smartCharging;
}
+ if (params.name) {
+ filters.name = params.name;
+ }
// Filters
if (filters) {
aggregation.push({
@@ -290,6 +293,7 @@ export default class SiteAreaStorage {
if (siteAreasMDB && siteAreasMDB.length > 0) {
// Create
for (const siteAreaMDB of siteAreasMDB) {
+ if (siteAreaMDB.issuer) {
// Skip site area with no charging stations if asked
if (params.withOnlyChargingStations && Utils.isEmptyArray(siteAreaMDB.chargingStations)) {
continue;
@@ -303,6 +307,7 @@ export default class SiteAreaStorage {
if (!params.withChargingStations && siteAreaMDB.chargingStations) {
delete siteAreaMDB.chargingStations;
}
+ }
// Add
siteAreas.push(siteAreaMDB);
}

MODIFY
src/types/AsyncTask.ts
src/types/AsyncTask.ts
@@ -11,6 +11,7 @@ export default interface AsyncTask extends CreatedUpdatedProps {
parent?: string;
execHost?: string;
execTimestamp?: Date;
+ execDurationSecs?: number;
module: string;
method: string;
message?: string;
@@ -25,6 +26,7 @@ export enum AsyncTaskType {
export enum AsyncTaskStatus {
PENDING = 'P',
RUNNING = 'R',
+ SUCCESS = 'S',
ERROR = 'E',
}

MODIFY
src/utils/Constants.ts
src/utils/Constants.ts
@@ -232,8 +232,11 @@ export default class Constants {
public static readonly CHARGING_STATION_CONFIGURATION = 'Configuration';
public static readonly CENTRAL_SERVER = 'Central Server';
+
public static readonly OCPI_SERVER = 'OCPI Server';
+ public static readonly OCPI_SEPARATOR = '*';
public static readonly OCPI_RECORDS_LIMIT = 25;
+
public static readonly OICP_SERVER = 'OICP Server';
// OICP constants

eMSP Site Name with right separator. Async Task framework keeps tasks after execution + log duration.