Remote Start Transaction refactoring
MODIFY
src/assets/server/rest/v1/schemas/chargingstation/chargingstation-action.json
src/assets/server/rest/v1/schemas/chargingstation/chargingstation-action.json
"stackLevel": {
"$ref": "chargingstation.json#/definitions/chargingProfile/properties/profile/properties/stackLevel"
},
- "tagID": {
- "$ref": "tag.json#/definitions/id"
- },
"visualTagID": {
"type": "string",
"sanitize": "mongo"

MODIFY
src/assets/server/rest/v1/schemas/transaction/transactions-get.json
src/assets/server/rest/v1/schemas/transaction/transactions-get.json
"UserID": {
"$ref": "common.json#/definitions/ids"
},
- "TagID": {
- "$ref": "tag.json#/definitions/ids"
+ "VisualTagID": {
+ "type": "string",
+ "sanitize": "mongo"
},
"StartDateTime": {
"type": "string",
"Statistics": {
"type": "string",
"sanitize": "mongo",
- "enum": ["refund", "history", "ongoing"]
+ "enum": [
+ "refund",
+ "history",
+ "ongoing"
+ ]
},
"ReportIDs": {
"$ref": "common.json#/definitions/ids"
"Status": {
"type": "string",
"sanitize": "mongo",
- "enum": ["completed", "active"]
+ "enum": [
+ "completed",
+ "active"
+ ]
},
"Search": {
"$ref": "common.json#/definitions/search"

ADD
src/assets/server/rest/v1/schemas/user/user-get-default-tag-car.json
+{
+ "title": "Get user default tag and car",
+ "type": "object",
+ "definitions": {
+ "UserID": {
+ "$ref": "common.json#/definitions/id"
+ },
+ "SiteID": {
+ "$ref": "common.json#/definitions/id"
+ },
+ "ProjectFields": {
+ "$ref": "common.json#/definitions/projectFields"
+ }
+ },
+ "required": [
+ "UserID", "SiteID"
+ ]
+}

MODIFY
src/assets/server/rest/v1/schemas/user/users-get.json
src/assets/server/rest/v1/schemas/user/users-get.json
"ExcludeSiteID": {
"$ref": "common.json#/definitions/ids"
},
- "TagID": {
- "$ref": "tag.json#/definitions/ids"
+ "VisualTagID": {
+ "type": "string",
+ "sanitize": "mongo"
},
"ExcludeUserIDs": {
"$ref": "user.json#/definitions/ids"

MODIFY
src/authorization/Authorizations.ts
src/authorization/Authorizations.ts
@@ -808,6 +808,48 @@ export default class Authorizations {
return result;
}
+ public static async isChargingStationValidInOrganization(action: ServerAction, tenant: Tenant, chargingStation: ChargingStation): Promise<boolean> {
+ // Org component enabled?
+ if (Utils.isTenantComponentActive(tenant, TenantComponents.ORGANIZATION)) {
+ let foundSiteArea = true;
+ // Site Area -----------------------------------------------
+ if (!chargingStation.siteAreaID) {
+ foundSiteArea = false;
+ } else if (!chargingStation.siteArea) {
+ chargingStation.siteArea = await SiteAreaStorage.getSiteArea(
+ tenant.id, chargingStation.siteAreaID, { withSite: true });
+ if (!chargingStation.siteArea) {
+ foundSiteArea = false;
+ }
+ }
+ // Site is mandatory
+ if (!foundSiteArea) {
+ // Reject Site Not Found
+ throw new BackendError({
+ source: chargingStation.id,
+ action: action,
+ module: MODULE_NAME, method: 'isTagIDAuthorizedOnChargingStation',
+ message: `Charging Station '${chargingStation.id}' is not assigned to a Site Area!`,
+ detailedMessages: { chargingStation }
+ });
+ }
+ // Site -----------------------------------------------------
+ chargingStation.siteArea.site = chargingStation.siteArea.site ??
+ (chargingStation.siteArea.siteID ? await SiteStorage.getSite(tenant, chargingStation.siteArea.siteID) : null);
+ if (!chargingStation.siteArea.site) {
+ // Reject Site Not Found
+ throw new BackendError({
+ source: chargingStation.id,
+ action: action,
+ module: MODULE_NAME, method: 'isTagIDAuthorizedOnChargingStation',
+ message: `Site Area '${chargingStation.siteArea.name}' is not assigned to a Site!`,
+ detailedMessages: { chargingStation }
+ });
+ }
+ return true;
+ }
+ }
+
private static async isTagIDAuthorizedOnChargingStation(tenant: Tenant, chargingStation: ChargingStation,
transaction: Transaction, tagID: string, action: ServerAction, authAction: Action): Promise<User> {
// Check Organization
@@ -1042,48 +1084,6 @@ export default class Authorizations {
return tag;
}
- private static async isChargingStationValidInOrganization(action: ServerAction, tenant: Tenant, chargingStation: ChargingStation): Promise<boolean> {
- // Org component enabled?
- if (Utils.isTenantComponentActive(tenant, TenantComponents.ORGANIZATION)) {
- let foundSiteArea = true;
- // Site Area -----------------------------------------------
- if (!chargingStation.siteAreaID) {
- foundSiteArea = false;
- } else if (!chargingStation.siteArea) {
- chargingStation.siteArea = await SiteAreaStorage.getSiteArea(
- tenant.id, chargingStation.siteAreaID, { withSite: true });
- if (!chargingStation.siteArea) {
- foundSiteArea = false;
- }
- }
- // Site is mandatory
- if (!foundSiteArea) {
- // Reject Site Not Found
- throw new BackendError({
- source: chargingStation.id,
- action: action,
- module: MODULE_NAME, method: 'isTagIDAuthorizedOnChargingStation',
- message: `Charging Station '${chargingStation.id}' is not assigned to a Site Area!`,
- detailedMessages: { chargingStation }
- });
- }
- // Site -----------------------------------------------------
- chargingStation.siteArea.site = chargingStation.siteArea.site ??
- (chargingStation.siteArea.siteID ? await SiteStorage.getSite(tenant, chargingStation.siteArea.siteID) : null);
- if (!chargingStation.siteArea.site) {
- // Reject Site Not Found
- throw new BackendError({
- source: chargingStation.id,
- action: action,
- module: MODULE_NAME, method: 'isTagIDAuthorizedOnChargingStation',
- message: `Site Area '${chargingStation.siteArea.name}' is not assigned to a Site!`,
- detailedMessages: { chargingStation }
- });
- }
- return true;
- }
- }
-
private static getConfiguration() {
if (!Authorizations.configuration) {
Authorizations.configuration = Configuration.getAuthorizationConfig();

MODIFY
src/authorization/AuthorizationsDefinition.ts
src/authorization/AuthorizationsDefinition.ts
@@ -113,7 +113,7 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
resource: Entity.TAG, action: Action.READ,
attributes: [
'id', 'userID', 'issuer', 'active', 'description', 'visualID', 'default', 'user.id',
- 'user.name', 'user.firstName', 'user.email', 'user.issuer'
+ 'user.name', 'user.firstName', 'user.email'
]
},
{ resource: Entity.TAG, action: [Action.CREATE, Action.UPDATE, Action.DELETE] },

MODIFY
src/server/rest/v1/service/ChargingStationService.ts
src/server/rest/v1/service/ChargingStationService.ts
@@ -1231,7 +1231,8 @@ export default class ChargingStationService {
throw new AppError({
source: Constants.CENTRAL_SERVER,
errorCode: HTTPError.USER_NO_BADGE_ERROR,
- message: 'The user does not have any badge',
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+ message: `The user does not own a badge with visual ID '${filteredRequest.args.visualTagID}'`,
module: MODULE_NAME,
method: 'handleAction',
user: req.user,
@@ -1249,44 +1250,7 @@ export default class ChargingStationService {
detailedMessages: { tag }
});
}
- // Org component enabled?
- if (Utils.isTenantComponentActive(req.tenant, TenantComponents.ORGANIZATION)) {
- let foundSiteArea = true;
- // Site Area -----------------------------------------------
- if (!chargingStation.siteAreaID) {
- foundSiteArea = false;
- } else if (!chargingStation.siteArea) {
- chargingStation.siteArea = await SiteAreaStorage.getSiteArea(
- req.tenant.id, chargingStation.siteAreaID, { withSite: true });
- if (!chargingStation.siteArea) {
- foundSiteArea = false;
- }
- }
- // Site is mandatory
- if (!foundSiteArea) {
- // Reject Site Not Found
- throw new BackendError({
- source: chargingStation.id,
- action: action,
- module: MODULE_NAME, method: 'handleAction',
- message: `Charging Station '${chargingStation.id}' is not assigned to a Site Area!`,
- detailedMessages: { chargingStation }
- });
- }
- // Site -----------------------------------------------------
- chargingStation.siteArea.site = chargingStation.siteArea.site ??
- (chargingStation.siteArea.siteID ? await SiteStorage.getSite(req.tenant, chargingStation.siteArea.siteID) : null);
- if (!chargingStation.siteArea.site) {
- // Reject Site Not Found
- throw new BackendError({
- source: chargingStation.id,
- action: action,
- module: MODULE_NAME, method: 'handleAction',
- message: `Site Area '${chargingStation.siteArea.name}' is not assigned to a Site!`,
- detailedMessages: { chargingStation }
- });
- }
- }
+ await Authorizations.isChargingStationValidInOrganization(action, req.tenant, chargingStation);
// Ok: Execute it
result = await ChargingStationService.handleChargingStationCommand(
req.tenant, req.user, chargingStation, action, command, { tagID: tag.id, connectorId: filteredRequest.args.connectorId });

MODIFY
src/server/rest/v1/service/TransactionService.ts
src/server/rest/v1/service/TransactionService.ts
@@ -1088,7 +1088,6 @@ export default class TransactionService {
chargeBoxIDs: filteredRequest.ChargingStationID ? filteredRequest.ChargingStationID.split('|') : null,
issuer: Utils.objectHasProperty(filteredRequest, 'Issuer') ? filteredRequest.Issuer : null,
userIDs: filteredRequest.UserID ? filteredRequest.UserID.split('|') : null,
- tagIDs: filteredRequest.TagID ? filteredRequest.TagID.split('|') : null,
visualTagIDs: filteredRequest.VisualTagID ? filteredRequest.VisualTagID.split('|') : null,
ownerID: Authorizations.isBasic(req.user) ? req.user.id : null,
siteAreaIDs: filteredRequest.SiteAreaID ? filteredRequest.SiteAreaID.split('|') : null,

MODIFY
src/server/rest/v1/service/UserService.ts
src/server/rest/v1/service/UserService.ts
@@ -33,7 +33,6 @@ import { StartTransactionErrorCode } from '../../../../types/Transaction';
import TagStorage from '../../../../storage/mongodb/TagStorage';
import Tenant from '../../../../types/Tenant';
import TenantComponents from '../../../../types/TenantComponents';
-import TenantStorage from '../../../../storage/mongodb/TenantStorage';
import { UserInErrorType } from '../../../../types/InError';
import UserNotifications from '../../../../types/UserNotifications';
import UserStorage from '../../../../storage/mongodb/UserStorage';
@@ -50,11 +49,11 @@ export default class UserService {
public static async handleGetUserDefaultTagCar(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Filter
- const userID = UserValidator.getInstance().validateUserGetByID(req.query).ID.toString();
- UtilsService.assertIdIsProvided(action, userID, MODULE_NAME, 'handleGetUserDefaultTagCar', req.user);
+ const filteredRequest = UserValidator.getInstance().validateUserDefaultTagCar(req.query);
+ UtilsService.assertIdIsProvided(action, filteredRequest.UserID, MODULE_NAME, 'handleGetUserDefaultTagCar', req.user);
// Check and Get User
const user = await UtilsService.checkAndGetUserAuthorization(
- req.tenant, req.user, userID, Action.READ, action);
+ req.tenant, req.user, filteredRequest.UserID, Action.READ, action);
// Handle Tag
// Get the default Tag
let tag = await TagStorage.getDefaultUserTag(req.user.tenantID, user.id, {
@@ -70,12 +69,12 @@ export default class UserService {
let car: Car;
if (Utils.isComponentActiveFromToken(req.user, TenantComponents.CAR)) {
// Get the default Car
- car = await CarStorage.getDefaultUserCar(req.tenant, userID, {},
+ car = await CarStorage.getDefaultUserCar(req.tenant, filteredRequest.UserID, {},
['id', 'type', 'licensePlate', 'carCatalog.vehicleMake', 'carCatalog.vehicleModel', 'carCatalog.vehicleModelVersion']
);
if (!car) {
// Get the first available car
- car = await CarStorage.getFirstAvailableUserCar(req.tenant, userID,
+ car = await CarStorage.getFirstAvailableUserCar(req.tenant, filteredRequest.UserID,
['id', 'type', 'licensePlate', 'carCatalog.vehicleMake', 'carCatalog.vehicleModel', 'carCatalog.vehicleModelVersion']
);
}
@@ -83,8 +82,11 @@ export default class UserService {
const errorCodes: Array<StartTransactionErrorCode> = [];
// Check Billing errors
await UserService.checkBillingErrorCodes(action, req.tenant, req.user, user, errorCodes);
+ // Get authorization filters for users
+ const authorizationUsersFilters = await AuthorizationService.checkAndGetUsersAuthorizationFilters(
+ req.tenant, req.user, { SiteID: filteredRequest.SiteID });
res.json({
- tag, car, errorCodes,
+ tag, car, errorCodes, canListUsers: authorizationUsersFilters.authorized
});
next();
}
@@ -782,7 +784,6 @@ export default class UserService {
issuer: Utils.isBoolean(filteredRequest.Issuer) || filteredRequest.Issuer ? Utils.convertToBoolean(filteredRequest.Issuer) : null,
siteIDs: (filteredRequest.SiteID ? filteredRequest.SiteID.split('|') : null),
userIDs: (filteredRequest.UserID ? filteredRequest.UserID.split('|') : null),
- tagIDs: (filteredRequest.TagID ? filteredRequest.TagID.split('|') : null),
visualTagIDs: (filteredRequest.VisualTagID ? filteredRequest.VisualTagID.split('|') : null),
roles: (filteredRequest.Role ? filteredRequest.Role.split('|') : null),
statuses: (filteredRequest.Status ? filteredRequest.Status.split('|') : null),

MODIFY
src/server/rest/v1/service/security/TransactionSecurity.ts
src/server/rest/v1/service/security/TransactionSecurity.ts
@@ -58,7 +58,6 @@ export default class TransactionSecurity {
filteredRequest.StartDateTime = sanitize(request.StartDateTime);
filteredRequest.EndDateTime = sanitize(request.EndDateTime);
filteredRequest.SiteID = sanitize(request.SiteID);
- filteredRequest.TagID = sanitize(request.TagID);
filteredRequest.SiteAreaID = sanitize(request.SiteAreaID);
filteredRequest.Search = sanitize(request.Search);
filteredRequest.InactivityStatus = sanitize(request.InactivityStatus);

MODIFY
src/server/rest/v1/validator/UserValidator.ts
src/server/rest/v1/validator/UserValidator.ts
-import { HttpUserAssignSitesRequest, HttpUserMobileTokenRequest, HttpUserSitesRequest, HttpUsersInErrorRequest, HttpUsersRequest } from '../../../../types/requests/HttpUserRequest';
+import { HttpUserAssignSitesRequest, HttpUserDefaultTagCar, HttpUserMobileTokenRequest, HttpUserSitesRequest, HttpUsersInErrorRequest, HttpUsersRequest } from '../../../../types/requests/HttpUserRequest';
import User, { ImportedUser } from '../../../../types/User';
import HttpByIDRequest from '../../../../types/requests/HttpByIDRequest';
@@ -18,6 +18,7 @@ export default class UserValidator extends SchemaValidator {
private userGetSites: Schema;
private userUpdate: Schema;
private userUpdateMobileToken: Schema;
+ private userGetDefaultTagCar: Schema;
private constructor() {
super('UserValidator');
@@ -30,6 +31,7 @@ export default class UserValidator extends SchemaValidator {
this.userGetSites = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/user/user-get-sites.json`, 'utf8'));
this.userUpdate = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/user/user-update.json`, 'utf8'));
this.userUpdateMobileToken = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/user/user-update-mobile-token.json`, 'utf8'));
+ this.userGetDefaultTagCar = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/user/user-get-default-tag-car.json`, 'utf8'));
}
public static getInstance(): UserValidator {
@@ -82,4 +84,9 @@ export default class UserValidator extends SchemaValidator {
this.validate(this.userUpdateMobileToken, data);
return data;
}
+
+ validateUserDefaultTagCar(data: any): HttpUserDefaultTagCar {
+ this.validate(this.userGetDefaultTagCar, data);
+ return data;
+ }
}

MODIFY
src/storage/mongodb/UserStorage.ts
src/storage/mongodb/UserStorage.ts
@@ -541,7 +541,7 @@ export default class UserStorage {
userIDs?: string[]; email?: string; issuer?: boolean; passwordResetHash?: string; roles?: string[];
statuses?: string[]; withImage?: boolean; billingUserID?: string;
notSynchronizedBillingData?: boolean; withTestBillingData?: boolean;
- notifications?: any; noLoginSince?: Date; tagIDs?: string[]; visualTagIDs?: string[]
+ notifications?: any; noLoginSince?: Date; visualTagIDs?: string[]
},
dbParams: DbParams, projectFields?: string[]): Promise<DataResult<User>> {
// Debug
@@ -658,21 +658,14 @@ export default class UserStorage {
});
}
// Add Tags
- if (!Utils.isEmptyArray(params.tagIDs) || !Utils.isEmptyArray(params.visualTagIDs)) {
+ if (!Utils.isEmptyArray(params.visualTagIDs)) {
DatabaseUtils.pushTagLookupInAggregation({
tenantID, aggregation, localField: '_id', foreignField: 'userID', asField: 'tag'
});
- if (!Utils.isEmptyArray(params.tagIDs)) {
- aggregation.push({
- $match: { 'tag.id': { $in: params.tagIDs } }
- });
- }
- if (!Utils.isEmptyArray(params.visualTagIDs)) {
aggregation.push({
$match: { 'tag.visualID': { $in: params.visualTagIDs } }
});
}
- }
// Add Site
if (params.siteIDs || params.excludeSiteID) {
DatabaseUtils.pushSiteUserLookupInAggregation({

MODIFY
src/types/requests/HttpTransactionRequest.ts
src/types/requests/HttpTransactionRequest.ts
@@ -29,7 +29,6 @@ export interface HttpTransactionsRequest extends HttpDatabaseRequest {
SiteAreaID?: string;
SiteID?: string;
UserID?: string;
- TagID?: string;
VisualTagID?: string;
StartDateTime?: Date;
EndDateTime?: Date;

MODIFY
src/types/requests/HttpUserRequest.ts
src/types/requests/HttpUserRequest.ts
@@ -39,7 +39,6 @@ export interface HttpUsersRequest extends HttpDatabaseRequest {
Role: string;
Status: string;
ErrorType?: string;
- TagID?: string;
VisualTagID?: string;
ExcludeSiteID: string;
ExcludeUserIDs: string;
@@ -100,3 +99,8 @@ export interface HttpResendVerificationMailRequest {
export interface HttpEulaRequest {
Language: string;
}
+
+export interface HttpUserDefaultTagCar {
+ UserID: string;
+ SiteID: string;
+}

Remote Start Transaction refactoring