diff --git a/src/integration/charging-station-vendor/wallbox/WALLBOXChargingStationVendorIntegration.ts b/src/integration/charging-station-vendor/wallbox/WALLBOXChargingStationVendorIntegration.ts index 3a20b262cb..0c92d06c74 100644 --- a/src/integration/charging-station-vendor/wallbox/WALLBOXChargingStationVendorIntegration.ts +++ b/src/integration/charging-station-vendor/wallbox/WALLBOXChargingStationVendorIntegration.ts @@ -6,7 +6,7 @@ import { OCPPSetChargingProfileResponse } from '../../../types/ocpp/OCPPClient'; import Tenant from '../../../types/Tenant'; export default class WALLBOXChargingStationVendorIntegration extends ChargingStationVendorIntegration { - constructor(chargingStation: ChargingStation) { + public constructor(chargingStation: ChargingStation) { super(chargingStation); } diff --git a/src/notification/NotificationFacilities.ts b/src/notification/NotificationFacilities.ts deleted file mode 100644 index a67d0006f1..0000000000 --- a/src/notification/NotificationFacilities.ts +++ /dev/null @@ -1,29 +0,0 @@ -import Configuration from '../utils/Configuration'; -import EMailNotificationTask from './email/EMailNotificationTask'; -import { NotificationSource } from '../types/UserNotifications'; -import RemotePushNotificationTask from './remote-push-notification/RemotePushNotificationTask'; -import User from '../types/User'; - -// const MODULE_NAME = 'NotificationFacilities'; - -export default class UserNotificationFacilities { - private static notificationConfig = Configuration.getNotificationConfig(); - private static notificationChannels: NotificationSource[] = [ - { - channel: 'email', - notificationTask: new EMailNotificationTask(), - enabled: !!UserNotificationFacilities.notificationConfig.Email?.enabled - }, - { - channel: 'remote-push-notification', - notificationTask: new RemotePushNotificationTask(), - enabled: !!UserNotificationFacilities.notificationConfig.RemotePushNotification?.enabled - } - ]; - - public static notifyUser(user: User, doIt: (channel: NotificationSource) => void): void { - for (const channel of UserNotificationFacilities.notificationChannels.filter((_channel) => _channel.enabled)) { - doIt(channel); - } - } -} diff --git a/src/notification/remote-push-notification/RemotePushNotificationTask.ts b/src/notification/remote-push-notification/RemotePushNotificationTask.ts index 2da770814a..cfffbc274b 100644 --- a/src/notification/remote-push-notification/RemotePushNotificationTask.ts +++ b/src/notification/remote-push-notification/RemotePushNotificationTask.ts @@ -1,3 +1,4 @@ +/* eslint-disable max-len */ import { AccountVerificationNotification, BillingAccountActivationNotification, BillingAccountCreationLinkNotification, BillingInvoiceSynchronizationFailedNotification, BillingNewInvoiceNotification, BillingPeriodicOperationFailedNotification, BillingUserSynchronizationFailedNotification, CarCatalogSynchronizationFailedNotification, ChargingStationRegisteredNotification, ChargingStationStatusErrorNotification, ComputeAndApplyChargingProfilesFailedNotification, EndOfChargeNotification, EndOfSessionNotification, EndOfSignedSessionNotification, EndUserErrorNotification, NewRegisteredUserNotification, NotificationResult, NotificationSeverity, OCPIPatchChargingStationsStatusesErrorNotification, OICPPatchChargingStationsErrorNotification, OICPPatchChargingStationsStatusesErrorNotification, OfflineChargingStationNotification, OptimalChargeReachedNotification, PreparingSessionNotStartedNotification, RequestPasswordNotification, SessionNotStartedNotification, TransactionStartedNotification, UnknownUserBadgedNotification, UserAccountInactivityNotification, UserAccountStatusChangedNotification, UserNotificationType, VerificationEmailNotification } from '../../types/UserNotifications'; import User, { UserStatus } from '../../types/User'; @@ -15,36 +16,42 @@ import admin from 'firebase-admin'; const MODULE_NAME = 'RemotePushNotificationTask'; export default class RemotePushNotificationTask implements NotificationTask { - private firebaseConfig = Configuration.getFirebaseConfig(); - private defaultApp: admin.app.App; - private alternativeApp: admin.app.App; - private tenantFirebaseApps: Map = new Map(); - private initialized = false; + private static firebaseConfig = Configuration.getFirebaseConfig(); + private static defaultApp: admin.app.App; + private static alternativeApp: admin.app.App; + private static tenantFirebaseApps: Map = new Map(); + private static initialized = false; public constructor() { - if (this.firebaseConfig?.type?.length > 0) { + if (!RemotePushNotificationTask.initialized) { + RemotePushNotificationTask.initialize(); + } + } + + private static initialize() { + if (RemotePushNotificationTask.firebaseConfig?.type?.length > 0) { try { // Init default conf - this.defaultApp = admin.initializeApp({ + RemotePushNotificationTask.defaultApp = admin.initializeApp({ credential: admin.credential.cert({ - projectId: this.firebaseConfig.projectID, - clientEmail: this.firebaseConfig.clientEmail, - privateKey: this.firebaseConfig.privateKey + projectId: RemotePushNotificationTask.firebaseConfig.projectID, + clientEmail: RemotePushNotificationTask.firebaseConfig.clientEmail, + privateKey: RemotePushNotificationTask.firebaseConfig.privateKey }) }); // Init alternative conf - if (this.firebaseConfig.alternativeConfiguration) { - this.alternativeApp = admin.initializeApp({ + if (RemotePushNotificationTask.firebaseConfig.alternativeConfiguration) { + RemotePushNotificationTask.alternativeApp = admin.initializeApp({ credential: admin.credential.cert({ - projectId: this.firebaseConfig.alternativeConfiguration.projectID, - clientEmail: this.firebaseConfig.alternativeConfiguration.clientEmail, - privateKey: this.firebaseConfig.alternativeConfiguration.privateKey + projectId: RemotePushNotificationTask.firebaseConfig.alternativeConfiguration.projectID, + clientEmail: RemotePushNotificationTask.firebaseConfig.alternativeConfiguration.clientEmail, + privateKey: RemotePushNotificationTask.firebaseConfig.alternativeConfiguration.privateKey }) }, 'alternativeApp'); } // Init tenant conf - if (!Utils.isEmptyArray(this.firebaseConfig.tenants)) { - for (const tenantConfig of this.firebaseConfig.tenants) { + if (!Utils.isEmptyArray(RemotePushNotificationTask.firebaseConfig.tenants)) { + for (const tenantConfig of RemotePushNotificationTask.firebaseConfig.tenants) { // Create the app const app = admin.initializeApp({ credential: admin.credential.cert({ @@ -57,7 +64,7 @@ export default class RemotePushNotificationTask implements NotificationTask { this.tenantFirebaseApps.set(tenantConfig.tenantID, app); } } - this.initialized = true; + RemotePushNotificationTask.initialized = true; } catch (error) { void Logging.logError({ tenantID: Constants.DEFAULT_TENANT_ID, @@ -133,10 +140,12 @@ export default class RemotePushNotificationTask implements NotificationTask { return {}; } + // eslint-disable-next-line @typescript-eslint/no-unused-vars public async sendNewRegisteredUser(data: NewRegisteredUserNotification, user: User, tenant: Tenant, severity: NotificationSeverity): Promise { return Promise.resolve({}); } + // eslint-disable-next-line @typescript-eslint/no-unused-vars public async sendRequestPassword(data: RequestPasswordNotification, user: User, tenant: Tenant, severity: NotificationSeverity): Promise { return Promise.resolve({}); } @@ -192,10 +201,12 @@ export default class RemotePushNotificationTask implements NotificationTask { return {}; } + // eslint-disable-next-line @typescript-eslint/no-unused-vars public async sendEndOfSignedSession(data: EndOfSignedSessionNotification, user: User, tenant: Tenant, severity: NotificationSeverity): Promise { return Promise.resolve({}); } + // eslint-disable-next-line @typescript-eslint/no-unused-vars public async sendCarCatalogSynchronizationFailed(data: CarCatalogSynchronizationFailedNotification, user: User, tenant: Tenant, severity: NotificationSeverity): Promise { return Promise.resolve({}); } @@ -293,10 +304,12 @@ export default class RemotePushNotificationTask implements NotificationTask { return {}; } + // eslint-disable-next-line @typescript-eslint/no-unused-vars public async sendVerificationEmail(data: VerificationEmailNotification, user: User, tenant: Tenant, severity: NotificationSeverity): Promise { return Promise.resolve({}); } + // eslint-disable-next-line @typescript-eslint/no-unused-vars public async sendVerificationEmailUserImport(data: VerificationEmailNotification, user: User, tenant: Tenant, severity: NotificationSeverity): Promise { return Promise.resolve({}); } @@ -471,11 +484,12 @@ export default class RemotePushNotificationTask implements NotificationTask { let startTime: number; let message = {} as admin.messaging.MessagingPayload; try { - startTime = Logging.traceNotificationStart(); - // Checks - if (!this.initialized) { + // Checks consistency + if (!RemotePushNotificationTask.initialized) { return Promise.resolve(); } + // Do it + startTime = Logging.traceNotificationStart(); if (!user?.mobileData?.mobileToken) { await Logging.logDebug({ tenantID: tenant.id, @@ -582,13 +596,13 @@ export default class RemotePushNotificationTask implements NotificationTask { } private getFirebaseAppsFromTenant(tenant: Tenant): Array { - const apps = [this.defaultApp]; - const tenantApp = this.tenantFirebaseApps.get(tenant.id); + const apps = [RemotePushNotificationTask.defaultApp]; + const tenantApp = RemotePushNotificationTask.tenantFirebaseApps.get(tenant.id); if (tenantApp) { return [tenantApp]; } - if (this.alternativeApp) { - apps.push(this.alternativeApp); + if (RemotePushNotificationTask.alternativeApp) { + apps.push(RemotePushNotificationTask.alternativeApp); } return apps; } diff --git a/src/server/ocpp/services/OCPPService.ts b/src/server/ocpp/services/OCPPService.ts index d679f43d4a..efb2d6d26e 100644 --- a/src/server/ocpp/services/OCPPService.ts +++ b/src/server/ocpp/services/OCPPService.ts @@ -1723,21 +1723,10 @@ export default class OCPPService { } private notifyBootNotification(tenant: Tenant, chargingStation: ChargingStation) { - NotificationHandler.sendChargingStationRegistered( + NotificationHelper.sendChargingStationRegistered( tenant, - Utils.generateUUID(), chargingStation, - { - chargeBoxID: chargingStation.id, - siteID: chargingStation.siteID, - siteAreaID: chargingStation.siteAreaID, - companyID: chargingStation.companyID, - evseDashboardURL: Utils.buildEvseURL(tenant.subdomain), - evseDashboardChargingStationURL: Utils.buildEvseChargingStationURL(tenant.subdomain, chargingStation, '#all') - } - ).catch((error) => { - Logging.logPromiseError(error, tenant?.id); - }); + ); } private enrichAuthorize(user: User, chargingStation: ChargingStation, headers: OCPPHeader, authorize: OCPPAuthorizeRequestExtended) { diff --git a/src/utils/NotificationHelper.ts b/src/utils/NotificationHelper.ts index d86841d2ac..808c6088ae 100644 --- a/src/utils/NotificationHelper.ts +++ b/src/utils/NotificationHelper.ts @@ -1,15 +1,19 @@ -import { EndOfChargeNotification, EndOfSessionNotification, EndOfSignedSessionNotification, NotificationSeverity, NotificationSource, OptimalChargeReachedNotification, TransactionStartedNotification } from '../types/UserNotifications'; +/* eslint-disable @typescript-eslint/member-ordering */ +import { ChargingStationRegisteredNotification, EndOfChargeNotification, EndOfSessionNotification, EndOfSignedSessionNotification, NotificationSeverity, NotificationSource, OptimalChargeReachedNotification, TransactionStartedNotification } from '../types/UserNotifications'; +import User, { UserRole } from '../types/User'; import ChargingStation from '../types/ChargingStation'; +import Configuration from './Configuration'; import Constants from './Constants'; +import EMailNotificationTask from '../notification/email/EMailNotificationTask'; import I18nManager from './I18nManager'; import Logging from './Logging'; import RawNotificationStorage from '../storage/mongodb/RawNotificationStorage'; +import RemotePushNotificationTask from '../notification/remote-push-notification/RemotePushNotificationTask'; import { ServerAction } from '../types/Server'; import Tenant from '../types/Tenant'; import Transaction from '../types/Transaction'; -import User from '../types/User'; -import UserNotificationFacilities from '../notification/NotificationFacilities'; +import UserStorage from '../storage/mongodb/UserStorage'; import Utils from './Utils'; import moment from 'moment'; @@ -17,10 +21,17 @@ import moment from 'moment'; export default class NotificationHelper { + private static notificationConfig = Configuration.getNotificationConfig(); + protected tenant: Tenant; + + public constructor(tenant: Tenant) { + this.tenant = tenant; + } + public static notifyStartTransaction(tenant: Tenant, transaction: Transaction, chargingStation: ChargingStation, user: User) { if (user?.notificationsActive && user.notifications.sendSessionStarted) { setTimeout(() => { - NotificationHelper.getInstance(tenant, transaction, chargingStation, user).notifyStartTransaction(); + NotificationHelper.getSessionNotificationHelper(tenant, transaction, chargingStation, user).notifyStartTransaction(); }, 500); } } @@ -28,7 +39,7 @@ export default class NotificationHelper { public static notifyStopTransaction(tenant: Tenant, transaction: Transaction, chargingStation: ChargingStation, user: User, alternateUser?: User) { if (user?.notificationsActive && user.notifications.sendEndOfSession) { setTimeout(() => { - NotificationHelper.getInstance(tenant, transaction, chargingStation, user).notifyStopTransaction(alternateUser); + NotificationHelper.getSessionNotificationHelper(tenant, transaction, chargingStation, user).notifyStopTransaction(alternateUser); }, 500); } } @@ -36,7 +47,7 @@ export default class NotificationHelper { public static notifyEndOfCharge(tenant: Tenant, transaction: Transaction, chargingStation: ChargingStation, user: User) { if (user?.notificationsActive && user.notifications.sendEndOfCharge) { setTimeout(() => { - NotificationHelper.getInstance(tenant, transaction, chargingStation, user).notifyEndOfCharge(); + NotificationHelper.getSessionNotificationHelper(tenant, transaction, chargingStation, user).notifyEndOfCharge(); }, 500); } } @@ -44,28 +55,167 @@ export default class NotificationHelper { public static notifyOptimalChargeReached(tenant: Tenant, transaction: Transaction, chargingStation: ChargingStation, user: User) { if (user?.notificationsActive && user.notifications.sendOptimalChargeReached) { setTimeout(() => { - NotificationHelper.getInstance(tenant, transaction, chargingStation, user).notifyOptimalChargeReached(); + NotificationHelper.getSessionNotificationHelper(tenant, transaction, chargingStation, user).notifyOptimalChargeReached(); }, 500); } } - private static getInstance(tenant: Tenant, transaction: Transaction, chargingStation: ChargingStation, user: User): UserNotificationHelper { - return new UserNotificationHelper(tenant, transaction, chargingStation, user); + public static sendChargingStationRegistered(tenant: Tenant, chargingStation: ChargingStation) { + setTimeout(() => { + NotificationHelper.getChargerNotificationHelper(tenant, chargingStation).notifyChargingStationRegistered(); + }, 500); + } + + private static getSessionNotificationHelper(tenant: Tenant, transaction: Transaction, chargingStation: ChargingStation, user: User): SessionNotificationHelper { + return new SessionNotificationHelper(tenant, transaction, chargingStation, user); + } + + private static getChargerNotificationHelper(tenant: Tenant, chargingStation: ChargingStation): ChargerNotificationHelper { + return new ChargerNotificationHelper(tenant, chargingStation); + } + + private static notificationChannels: NotificationSource[] = [ + { + channel: 'email', + notificationTask: new EMailNotificationTask(), + enabled: !!NotificationHelper.notificationConfig.Email?.enabled + }, + { + channel: 'remote-push-notification', + notificationTask: new RemotePushNotificationTask(), + enabled: !!NotificationHelper.notificationConfig.RemotePushNotification?.enabled + } + ]; + + public static notifySingleUser(doIt: (channel: NotificationSource) => void): void { + for (const channel of NotificationHelper.notificationChannels.filter((_channel) => _channel.enabled)) { + doIt(channel); + } + } + + public static notifyAdminUser(adminUser: User, doIt: (adminUser: User, channel: NotificationSource) => void): void { + for (const channel of NotificationHelper.notificationChannels.filter((_channel) => _channel.enabled)) { + doIt(adminUser, channel); + } + } + + protected notifyUserOnlyOnce(serverAction: ServerAction, discriminator: string, data: any, doIt: (channel: NotificationSource) => void): void { + this.checkNotificationAlreadySent(serverAction, discriminator, data).then((done: boolean) => { + if (!done) { + NotificationHelper.notifySingleUser(doIt); + } + }).catch((error) => { + Logging.logPromiseError(error, this.tenant?.id); + }); + } + + private async checkNotificationAlreadySent(serverAction: ServerAction, discriminator: string, data: any): Promise { + let done = false ; + try { + // Get the Notification - the discriminator + serverAction should be unique! + const notificationFound = await RawNotificationStorage.getRawNotification( + this.tenant, + { + discriminator, + serverAction + } + ); + if (notificationFound !== null) { + done = true; + } else { + // Save it to prevent sending it again + await RawNotificationStorage.saveRawNotification(this.tenant, { + timestamp: new Date(), + discriminator, + serverAction, + data + }); + } + } catch (error) { + await Logging.logActionExceptionMessage(this.tenant.id, ServerAction.NOTIFICATION, error as Error); + } + return done; + } + + protected notifyAllAdmins(notificationKey: string, doIt: (adminUser: User, channel: NotificationSource) => void): void { + const tenantId = this.tenant.id; + this._notifyAllAdmins(notificationKey, doIt).catch((error) => { + Logging.logPromiseError(error, tenantId); + }); + } + + protected async _notifyAllAdmins(notificationKey: string,doIt: (adminUser: User, channel: NotificationSource) => void) { + const adminUsers = await ChargerNotificationHelper.getAdminUsers(this.tenant); + const filteredAdmins = adminUsers.filter((adminUser) => adminUser.notifications[notificationKey] === true); + filteredAdmins.forEach((adminUser) => { + NotificationHelper.notifyAdminUser(adminUser, doIt); + }); + } + + // eslint-disable-next-line @typescript-eslint/member-ordering + protected static async getAdminUsers(tenant: Tenant): Promise { + // Get admin users + // TODO - add here a cache + const params = { + roles: [ (tenant.id === Constants.DEFAULT_TENANT_ID) ? UserRole.SUPER_ADMIN : UserRole.ADMIN], + notificationsActive: true, + }; + const adminUsers = await UserStorage.getUsers(tenant, params, Constants.DB_PARAMS_MAX_LIMIT); + // Found + if (adminUsers.count > 0) { + return adminUsers.result; + } + return []; } } -export class UserNotificationHelper { - // TODO - rethink that part! - we should avoid keeping references to these big objects - private tenant: Tenant; +export class ChargerNotificationHelper extends NotificationHelper { + + // TODO - rethink that part! + // We should avoid keeping references to these big objects + // Notification should be done by a dedicated services + protected tenant: Tenant; + protected chargingStation: ChargingStation; + + public constructor(tenant: Tenant, chargingStation: ChargingStation) { + super(tenant); + this.chargingStation = chargingStation; + } + + public notifyChargingStationRegistered() { + const tenant = this.tenant; + const chargingStation = this.chargingStation; + // Notification data - ACHTUNG - this data is common to all admin users + const data: ChargingStationRegisteredNotification = { + chargeBoxID: chargingStation.id, + siteID: chargingStation.siteID, + siteAreaID: chargingStation.siteAreaID, + companyID: chargingStation.companyID, + evseDashboardURL: Utils.buildEvseURL(tenant.subdomain), + evseDashboardChargingStationURL: Utils.buildEvseChargingStationURL(tenant.subdomain, chargingStation, '#all'), + }; + // Do it + this.notifyAllAdmins('sendChargingStationRegistered', (adminUser: User, channel: NotificationSource) => { + channel.notificationTask.sendChargingStationRegistered(data, adminUser, tenant, NotificationSeverity.INFO).catch((error) => { + Logging.logPromiseError(error, tenant?.id); + }); + }); + } + +} + +export class SessionNotificationHelper extends ChargerNotificationHelper { + + // TODO - rethink that part! + // We should avoid keeping references to these big objects + // Notification should be done by a dedicated services private transaction: Transaction; - private chargingStation: ChargingStation; private user: User; public constructor(tenant: Tenant, transaction: Transaction, chargingStation: ChargingStation, user: User) { - this.tenant = tenant; + super(tenant, chargingStation); this.transaction = transaction; - this.chargingStation = chargingStation; this.user = user; } @@ -87,7 +237,7 @@ export class UserNotificationHelper { evseDashboardChargingStationURL: Utils.buildEvseTransactionURL(tenant.subdomain, transaction.id, '#inprogress') }; // Do it - this.notifyUser((channel: NotificationSource) => { + NotificationHelper.notifySingleUser((channel: NotificationSource) => { channel.notificationTask.sendSessionStarted(data, user, tenant, NotificationSeverity.INFO).catch((error) => { Logging.logPromiseError(error, tenant?.id); }); @@ -118,6 +268,12 @@ export class UserNotificationHelper { }; // Do it this.notifyUserOnlyOnce(ServerAction.END_OF_CHARGE, + `tx-${transaction.id}`, + { + userID: user.id, + transactionID: transaction.id, + chargeBoxID: chargingStation.id, + }, (channel: NotificationSource) => { channel.notificationTask.sendEndOfCharge(data, user, tenant, NotificationSeverity.INFO).catch((error) => { Logging.logPromiseError(error, tenant?.id); @@ -149,6 +305,12 @@ export class UserNotificationHelper { }; // Do it this.notifyUserOnlyOnce(ServerAction.OPTIMAL_CHARGE_REACHED, + `tx-${transaction.id}`, + { + userID: user.id, + transactionID: transaction.id, + chargeBoxID: chargingStation.id, + }, (channel: NotificationSource) => { channel.notificationTask.sendOptimalChargeReached(data, user, tenant, NotificationSeverity.INFO).catch((error) => { Logging.logPromiseError(error, tenant?.id); @@ -182,11 +344,18 @@ export class UserNotificationHelper { evseDashboardURL: Utils.buildEvseURL(tenant.subdomain) }; // Do it - this.notifyUserOnlyOnce(ServerAction.END_OF_SESSION, (channel: NotificationSource) => { - channel.notificationTask.sendEndOfSession(data, user, tenant, NotificationSeverity.INFO).catch((error) => { - Logging.logPromiseError(error, tenant?.id); + this.notifyUserOnlyOnce(ServerAction.END_OF_SESSION, + `tx-${transaction.id}`, + { + userID: user.id, + transactionID: transaction.id, + chargeBoxID: chargingStation.id, + }, + (channel: NotificationSource) => { + channel.notificationTask.sendEndOfSession(data, user, tenant, NotificationSeverity.INFO).catch((error) => { + Logging.logPromiseError(error, tenant?.id); + }); }); - }); // Notify Signed Data if (transaction.stop.signedData !== '') { const locale = user.locale ? user.locale.replace('_', '-') : Constants.DEFAULT_LOCALE.replace('_', '-'); @@ -200,12 +369,9 @@ export class UserNotificationHelper { tagId: transaction.tagID, startDate: transaction.timestamp.toLocaleString(locale), endDate: transaction.stop.timestamp.toLocaleString(locale), - meterStart: (transaction.meterStart / 1000).toLocaleString(locale, { - minimumIntegerDigits: 1, minimumFractionDigits: 4, maximumFractionDigits: 4 }), - meterStop: (transaction.stop.meterStop / 1000).toLocaleString(locale, { - minimumIntegerDigits: 1, minimumFractionDigits: 4, maximumFractionDigits: 4 }), - totalConsumption: (transaction.stop.totalConsumptionWh / 1000).toLocaleString(locale,{ - minimumIntegerDigits: 1, minimumFractionDigits: 4, maximumFractionDigits: 4 }), + meterStart: (transaction.meterStart / 1000).toLocaleString(locale, { minimumIntegerDigits: 1, minimumFractionDigits: 4, maximumFractionDigits: 4 }), + meterStop: (transaction.stop.meterStop / 1000).toLocaleString(locale, { minimumIntegerDigits: 1, minimumFractionDigits: 4, maximumFractionDigits: 4 }), + totalConsumption: (transaction.stop.totalConsumptionWh / 1000).toLocaleString(locale, { minimumIntegerDigits: 1, minimumFractionDigits: 4, maximumFractionDigits: 4 }), price: transaction.stop.price, relativeCost: (transaction.stop.price / (transaction.stop.totalConsumptionWh / 1000)), startSignedData: transaction.signedData, @@ -213,8 +379,8 @@ export class UserNotificationHelper { evseDashboardChargingStationURL: Utils.buildEvseTransactionURL(tenant.subdomain, transaction.id, '#history'), evseDashboardURL: Utils.buildEvseURL(tenant.subdomain) }; - // Do it - this.notifyUser((channel: NotificationSource) => { + // Do it + NotificationHelper.notifySingleUser((channel: NotificationSource) => { channel.notificationTask.sendEndOfSignedSession(signedData, user, tenant, NotificationSeverity.INFO).catch((error) => { Logging.logPromiseError(error, tenant?.id); }); @@ -225,7 +391,6 @@ export class UserNotificationHelper { private transactionInactivityToString(i18nHourShort = 'h') { const transaction = this.transaction; const user = this.user; - const i18nManager = I18nManager.getInstanceForLocale(user ? user.locale : Constants.DEFAULT_LANGUAGE); // Get total const totalInactivitySecs = transaction.stop.totalInactivitySecs; @@ -237,51 +402,5 @@ export class UserNotificationHelper { const totalInactivityPercent = i18nManager.formatPercentage(Math.round((totalInactivitySecs / transaction.stop.totalDurationSecs) * 100) / 100); return moment.duration(totalInactivitySecs, 's').format(`h[${i18nHourShort}]mm`, { trim: false }) + ` (${totalInactivityPercent})`; } - - private notifyUser(doIt: (channel: NotificationSource) => void): void { - UserNotificationFacilities.notifyUser(this.user, doIt); - } - - private notifyUserOnlyOnce(serverAction: ServerAction, doIt: (channel: NotificationSource) => void): void { - this.checkNotificationAlreadySent(serverAction).then((done: boolean) => { - if (!done) { - this.notifyUser(doIt); - } - }).catch((error) => { - Logging.logPromiseError(error, this.tenant?.id); - }); - } - - private async checkNotificationAlreadySent(serverAction: ServerAction): Promise { - let done = false ; - try { - const discriminator = `tx-${this.transaction.id}`; - // Get the Notification - const notificationFound = await RawNotificationStorage.getRawNotification( - this.tenant, - { - discriminator, - serverAction - } - ); - if (notificationFound !== null) { - done = true; - } else { - // Save it to prevent sending it again - await RawNotificationStorage.saveRawNotification(this.tenant, { - timestamp: new Date(), - discriminator, - serverAction, - data: { - userID: this.user.id, - transactionID: this.transaction.id, - chargeBoxID: this.chargingStation.id, - } - }); - } - } catch (error) { - await Logging.logActionExceptionMessage(this.tenant.id, ServerAction.NOTIFICATION, error); - } - return done; - } } + diff --git a/src/validator/SchemaValidator.ts b/src/validator/SchemaValidator.ts index 2976c8b190..01b58a6349 100644 --- a/src/validator/SchemaValidator.ts +++ b/src/validator/SchemaValidator.ts @@ -46,12 +46,12 @@ export default class SchemaValidator { protected constructor(moduleName: string, config: Options = { strict: false, // When 'true', it fails with anyOf required fields: https://github.com/ajv-validator/ajv/issues/1571 - allErrors: true, + allErrors: false, removeAdditional: 'all', // Careful with 'All' and usage of anyOf/oneOf/allOf: https://github.com/ajv-validator/ajv/issues/1784 allowUnionTypes: true, useDefaults: true, coerceTypes: true, - verbose: true, + verbose: false, }) { this.moduleName = moduleName; // Create AJV