Optimized Site service
MODIFY
src/server/rest/CentralRestServerAuthentication.ts
src/server/rest/CentralRestServerAuthentication.ts
@@ -20,19 +20,16 @@ export default class CentralRestServerAuthentication {
// Delegate
await AuthService.handleLogIn(action, req, res, next);
break;
-
// Register User
case ServerAction.REGISTER_USER:
// Delegate
await AuthService.handleRegisterUser(action, req, res, next);
break;
-
// Reset password
case ServerAction.PASSWORD_RESET:
// Delegate
await AuthService.handleUserPasswordReset(action, req, res, next);
break;
-
// Resend verification email
case ServerAction.RESEND_VERIFICATION_MAIL:
// Delegate
@@ -54,25 +51,21 @@ export default class CentralRestServerAuthentication {
// Delegate
AuthService.handleUserLogOut(action, req, res, next);
break;
-
// End-user license agreement
case ServerAction.END_USER_LICENSE_AGREEMENT:
// Delegate
await AuthService.handleGetEndUserLicenseAgreement(action, req, res, next);
break;
-
// Check Eula
case ServerAction.CHECK_END_USER_LICENSE_AGREEMENT:
// Delegate
await AuthService.handleCheckEndUserLicenseAgreement(action, req, res, next);
break;
-
// Verify Email
case ServerAction.VERIFY_EMAIL:
// Delegate
await AuthService.handleVerifyEmail(action, req, res, next);
break;
-
default:
// Delegate
UtilsService.handleUnknownAction(action, req, res, next);

MODIFY
src/server/rest/CentralRestServerService.ts
src/server/rest/CentralRestServerService.ts
@@ -138,7 +138,6 @@ class RequestMapper {
[ServerAction.ASSET_CONSUMPTION]: AssetService.handleGetAssetConsumption.bind(this),
[ServerAction.SITES]: SiteService.handleGetSites.bind(this),
[ServerAction.SITE]: SiteService.handleGetSite.bind(this),
- [ServerAction.SITE_IMAGE]: SiteService.handleGetSiteImage.bind(this),
[ServerAction.SITE_USERS]: SiteService.handleGetUsers.bind(this),
[ServerAction.TENANTS]: TenantService.handleGetTenants.bind(this),
[ServerAction.TENANT]: TenantService.handleGetTenant.bind(this),
@@ -302,6 +301,9 @@ export default class CentralRestServerService {
case ServerAction.COMPANY_LOGO:
await CompanyService.handleGetCompanyLogo(action, req, res, next);
break;
+ case ServerAction.SITE_IMAGE:
+ await SiteService.handleGetSiteImage(action, req, res, next);
+ break;
// Firmware Download
case ServerAction.FIRMWARE_DOWNLOAD:
await ChargingStationService.handleGetFirmware(action, req, res, next);

MODIFY
src/server/rest/v1/service/SiteService.ts
src/server/rest/v1/service/SiteService.ts
@@ -382,13 +382,12 @@ export default class SiteService {
}
// Get it
const site = await SiteStorage.getSite(req.user.tenantID, filteredRequest.ID,
- { withCompany: filteredRequest.WithCompany });
+ { withCompany: filteredRequest.WithCompany },
+ [ 'id', 'name', 'issuer', 'image', 'address', 'companyID', 'company.name', 'autoUserSiteAssignment', 'public' ]);
UtilsService.assertObjectExists(action, site, `Site with ID '${filteredRequest.ID}' does not exist`,
MODULE_NAME, 'handleGetSite', req.user);
// Return
- res.json(
- SiteSecurity.filterSiteResponse(site, req.user)
- );
+ res.json(site);
next();
}
@@ -407,6 +406,11 @@ export default class SiteService {
}
// Filter
const filteredRequest = SiteSecurity.filterSitesRequest(req.query);
+ // Check User
+ let userProject: string[] = [];
+ if (Authorizations.canListUsers(req.user)) {
+ userProject = [ 'createdBy.name', 'createdBy.firstName', 'lastChangedBy.name', 'lastChangedBy.firstName' ];
+ }
// Get the sites
const sites = await SiteStorage.getSites(req.user.tenantID,
{
@@ -427,43 +431,37 @@ export default class SiteService {
sort: filteredRequest.Sort,
onlyRecordCount: filteredRequest.OnlyRecordCount
},
- ['id', 'name', 'address', 'companyID', 'company.name', 'autoUserSiteAssignment', 'issuer',
- 'autoUserSiteAssignment', 'distanceMeters', 'public', 'createdOn', 'createdBy', 'lastChangedOn', 'lastChangedBy']
+ [
+ 'id', 'name', 'address', 'companyID', 'company.name', 'autoUserSiteAssignment', 'issuer',
+ 'autoUserSiteAssignment', 'distanceMeters', 'public', 'createdOn', 'lastChangedOn',
+ ...userProject
+ ]
);
- // Build the result
- if (sites.result && sites.result.length > 0) {
- // Filter
- SiteSecurity.filterSitesResponse(sites, req.user);
- }
res.json(sites);
next();
}
public static async handleGetSiteImage(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
- // Check if component is active
- UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.ORGANIZATION,
- Action.READ, Entity.SITE, MODULE_NAME, 'handleGetSiteImage');
// Filter
- const siteID = SiteSecurity.filterSiteRequestByID(req.query);
- UtilsService.assertIdIsProvided(action, siteID, MODULE_NAME, 'handleGetSiteImage', req.user);
- // Check auth
- if (!Authorizations.canReadSite(req.user, siteID)) {
- throw new AppAuthError({
- errorCode: HTTPAuthError.ERROR,
- user: req.user,
- action: Action.READ, entity: Entity.SITE,
- module: MODULE_NAME, method: 'handleGetSiteImage',
- value: siteID
- });
- }
- // Get it
- const site = await SiteStorage.getSite(req.user.tenantID, siteID);
- UtilsService.assertObjectExists(action, site, `Site with ID '${siteID}' does not exist`,
- MODULE_NAME, 'handleGetSiteImage', req.user);
+ const filteredRequest = SiteSecurity.filterSiteImageRequest(req.query);
+ UtilsService.assertIdIsProvided(action, filteredRequest.ID, MODULE_NAME, 'handleGetSiteImage', req.user);
// Get the image
- const siteImage = await SiteStorage.getSiteImage(req.user.tenantID, siteID);
+ const siteImage = await SiteStorage.getSiteImage(filteredRequest.TenantID, filteredRequest.ID);
// Return
- res.json(siteImage);
+ if (siteImage?.image) {
+ let header = 'image';
+ let encoding: BufferEncoding = 'base64';
+ // Remove encoding header
+ if (siteImage.image.startsWith('data:image/')) {
+ header = siteImage.image.substring(5, siteImage.image.indexOf(';'));
+ encoding = siteImage.image.substring(siteImage.image.indexOf(';') + 1, siteImage.image.indexOf(',')) as BufferEncoding;
+ siteImage.image = siteImage.image.substring(siteImage.image.indexOf(',') + 1);
+ }
+ res.setHeader('content-type', header);
+ res.send(siteImage.image ? Buffer.from(siteImage.image, encoding) : null);
+ } else {
+ res.send(null);
+ }
next();
}
@@ -507,7 +505,7 @@ export default class SiteService {
createdOn: new Date()
} as Site;
// Save
- site.id = await SiteStorage.saveSite(req.user.tenantID, site, true);
+ site.id = await SiteStorage.saveSite(req.user.tenantID, site);
// Log
Logging.logSecurityInfo({
tenantID: req.user.tenantID,
@@ -527,8 +525,6 @@ export default class SiteService {
Action.UPDATE, Entity.SITE, MODULE_NAME, 'handleUpdateSite');
// Filter
const filteredRequest = SiteSecurity.filterSiteUpdateRequest(req.body);
- // Check
- Utils.checkIfSiteValid(filteredRequest, req);
// Check auth
if (!Authorizations.canUpdateSite(req.user, filteredRequest.id)) {
throw new AppAuthError({
@@ -543,6 +539,8 @@ export default class SiteService {
const company = await CompanyStorage.getCompany(req.user.tenantID, filteredRequest.companyID);
UtilsService.assertObjectExists(action, company, `Company ID '${filteredRequest.companyID}' does not exist`,
MODULE_NAME, 'handleUpdateSite', req.user);
+ // Check
+ Utils.checkIfSiteValid(filteredRequest, req);
// OCPI Company
if (!company.issuer) {
throw new AppError({
@@ -570,10 +568,18 @@ export default class SiteService {
});
}
// Update
+ site.name = filteredRequest.name;
+ site.public = filteredRequest.public;
+ site.autoUserSiteAssignment = filteredRequest.autoUserSiteAssignment;
+ site.companyID = filteredRequest.companyID;
+ site.address = filteredRequest.address;
site.lastChangedBy = { 'id': req.user.id };
site.lastChangedOn = new Date();
+ if (Utils.objectHasProperty(filteredRequest, 'image')) {
+ site.image = filteredRequest.image;
+ }
// Save
- await SiteStorage.saveSite(req.user.tenantID, { ...site, ...filteredRequest }, true);
+ await SiteStorage.saveSite(req.user.tenantID, site, Utils.objectHasProperty(filteredRequest, 'image') ? true : false);
// Log
Logging.logSecurityInfo({
tenantID: req.user.tenantID,

MODIFY
src/server/rest/v1/service/security/SiteSecurity.ts
src/server/rest/v1/service/security/SiteSecurity.ts
-import { HttpSiteAssignUsersRequest, HttpSiteOwnerRequest, HttpSiteRequest, HttpSiteUserAdminRequest, HttpSiteUsersRequest, HttpSitesRequest } from '../../../../../types/requests/HttpSiteRequest';
+import { HttpSiteAssignUsersRequest, HttpSiteImageRequest, HttpSiteOwnerRequest, HttpSiteRequest, HttpSiteUserAdminRequest, HttpSiteUsersRequest, HttpSitesRequest } from '../../../../../types/requests/HttpSiteRequest';
import Authorizations from '../../../../../authorization/Authorizations';
-import CompanySecurity from './CompanySecurity';
import { DataResult } from '../../../../../types/DataResult';
import Site from '../../../../../types/Site';
import SiteAreaSecurity from './SiteAreaSecurity';
@@ -52,6 +51,13 @@ export default class SiteSecurity {
return sanitize(request.ID);
}
+ public static filterSiteImageRequest(request: any): HttpSiteImageRequest {
+ return {
+ ID: sanitize(request.ID),
+ TenantID: sanitize(request.TenantID),
+ };
+ }
+
public static filterSiteUsersRequest(request: any): HttpSiteUsersRequest {
const filteredRequest: HttpSiteUsersRequest = {} as HttpSiteUsersRequest;
filteredRequest.SiteID = sanitize(request.SiteID);
@@ -104,11 +110,13 @@ export default class SiteSecurity {
const filteredRequest: any = {};
filteredRequest.name = sanitize(request.name);
filteredRequest.address = UtilsSecurity.filterAddressRequest(request.address);
- filteredRequest.image = sanitize(request.image);
filteredRequest.public = UtilsSecurity.filterBoolean(request.public);
filteredRequest.autoUserSiteAssignment =
UtilsSecurity.filterBoolean(request.autoUserSiteAssignment);
filteredRequest.companyID = sanitize(request.companyID);
+ if (Utils.objectHasProperty(request, 'image')) {
+ filteredRequest.image = sanitize(request.image);
+ }
return filteredRequest;
}

MODIFY
src/storage/mongodb/SiteStorage.ts
src/storage/mongodb/SiteStorage.ts
@@ -17,11 +17,12 @@ const MODULE_NAME = 'SiteStorage';
export default class SiteStorage {
public static async getSite(tenantID: string, id: string = Constants.UNKNOWN_OBJECT_ID,
- params: { withCompany?: boolean } = {}): Promise<Site> {
+ params: { withCompany?: boolean } = {}, projectFields?: string[]): Promise<Site> {
const sitesMDB = await SiteStorage.getSites(tenantID, {
siteIDs: [id],
- withCompany: params.withCompany
- }, Constants.DB_PARAMS_SINGLE_RECORD);
+ withCompany: params.withCompany,
+ withImage: true,
+ }, Constants.DB_PARAMS_SINGLE_RECORD, projectFields);
return sitesMDB.count === 1 ? sitesMDB.result[0] : null;
}
@@ -304,7 +305,7 @@ export default class SiteStorage {
search?: string; companyIDs?: string[]; withAutoUserAssignment?: boolean; siteIDs?: string[];
userID?: string; excludeSitesOfUserID?: boolean; issuer?: boolean; onlyPublicSite?: boolean;
withAvailableChargingStations?: boolean; withOnlyChargingStations?: boolean; withCompany?: boolean;
- locCoordinates?: number[]; locMaxDistanceMeters?: number;
+ locCoordinates?: number[]; locMaxDistanceMeters?: number; withImage?: boolean;
} = {},
dbParams: DbParams, projectFields?: string[]): Promise<DataResult<Site>> {
// Debug
@@ -424,6 +425,21 @@ export default class SiteStorage {
asField: 'company', oneToOneCardinality: true
});
}
+ // Site Image
+ if (params.withImage) {
+ aggregation.push({
+ $addFields: {
+ image: {
+ $concat: [
+ `${Utils.buildRestServerURL()}/client/util/SiteImage?ID=`,
+ { $toString: '$_id' },
+ `&TenantID=${tenantID}&LastChangedOn=`,
+ { $toString: '$lastChangedOn' }
+ ]
+ }
+ }
+ });
+ }
// Convert Object ID to string
DatabaseUtils.pushConvertObjectIDToString(aggregation, 'companyID');
// Add Last Changed / Created

MODIFY
src/types/requests/HttpSiteRequest.ts
src/types/requests/HttpSiteRequest.ts
@@ -18,6 +18,10 @@ export interface HttpSitesRequest extends HttpDatabaseRequest {
LocMaxDistanceMeters?: number;
}
+export interface HttpSiteImageRequest extends HttpByIDRequest {
+ TenantID: string;
+}
+
export interface HttpSiteAssignUsersRequest {
siteID: string;
userIDs: string[];

Optimized Site service