Added new session context endpoint
MODIFY
src/assets/server/rest/v1/docs/e-mobility-oas.json
src/assets/server/rest/v1/docs/e-mobility-oas.json
}
}
},
+ "/api/users/{id}/session-context": {
+ "get": {
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ],
+ "description": "Get the User's session context",
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/userID"
+ },
+ {
+ "$ref": "#/components/parameters/ChargingStationID"
+ }
+ ],
+ "tags": [
+ "Users"
+ ],
+ "responses": {
+ "200": {
+ "description": "Retrieved user session context",
+ "content": {
+ "application/json": {
+ "example": {
+ "tag": {
+ "active": true,
+ "description": "###",
+ "id": "###",
+ "default": "###"
+ },
+ "car": {
+ "licensePlate": "###",
+ "default": "###",
+ "type": "#",
+ "converter": {
+ "powerWatts": "###",
+ "numberOfPhases": "###"
+ },
+ "carCatalog": {
+ "vehicleMake": "###",
+ "vehicleModel": "###",
+ "vehicleModelVersion": "###",
+ "image": "###",
+ "batteryCapacityFull": "###",
+ "fastChargePowerMax": "###"
+ },
+ "id": "###"
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "$ref": "#/components/responses/UnauthorizedError"
+ },
+ "403": {
+ "$ref": "#/components/responses/ForbiddenError"
+ },
+ "500": {
+ "$ref": "#/components/responses/BackendError"
+ },
+ "404": {
+ "$ref": "#/components/responses/ResourceDoesNotExists"
+ }
+ }
+ }
+ },
"/api/users/{id}/sites": {
"get": {
"security": [

ADD
src/assets/server/rest/v1/schemas/user/user-session-context-get.json
+{
+ "$id": "user-session-context-get",
+ "title": "Get user session context",
+ "type": "object",
+ "properties": {
+ "UserID": {
+ "$ref": "user#/definitions/id"
+ },
+ "ChargingStationID": {
+ "$ref": "chargingstation#/definitions/id"
+ },
+ "ProjectFields": {
+ "$ref": "common#/definitions/projectFields"
+ }
+ },
+ "required": [
+ "UserID",
+ "ChargingStationID"
+ ]
+}

MODIFY
src/server/rest/v1/router/api/UserRouter.ts
src/server/rest/v1/router/api/UserRouter.ts
@@ -18,6 +18,7 @@ export default class UserRouter {
this.buildRouteUpdateUser();
this.buildRouteDeleteUser();
this.buildRouteUserDefaultCarTag();
+ this.buildRouteUserSessionContext();
this.buildRouteUserGetSites();
this.buildRouteUserSiteAssign();
this.buildRouteUserSiteUnassign();
@@ -74,6 +75,13 @@ export default class UserRouter {
});
}
+ private buildRouteUserSessionContext(): void {
+ this.router.get(`/${RESTServerRoute.REST_USER_SESSION_CONTEXT}`, (req: Request, res: Response, next: NextFunction) => {
+ req.query.UserID = req.params.id;
+ void RouterUtils.handleRestServerAction(UserService.handleGetUserSessionContext.bind(this), ServerAction.USER_SESSION_CONTEXT, req, res, next);
+ });
+ }
+
private buildRouteUserGetSites(): void {
this.router.get(`/${RESTServerRoute.REST_USER_SITES}`, (req: Request, res: Response, next: NextFunction) => {
req.query.UserID = req.params.id;

MODIFY
src/server/rest/v1/service/UserService.ts
src/server/rest/v1/service/UserService.ts
@@ -28,8 +28,10 @@ import OCPIClientFactory from '../../../../client/ocpi/OCPIClientFactory';
import { OCPIRole } from '../../../../types/ocpi/OCPIRole';
import { OCPITokenWhitelist } from '../../../../types/ocpi/OCPIToken';
import OCPIUtils from '../../../ocpi/OCPIUtils';
+import { PrioritizationParameters } from '../../../../types/Setting';
import { Readable } from 'stream';
import { ServerAction } from '../../../../types/Server';
+import SettingStorage from '../../../../storage/mongodb/SettingStorage';
import { StartTransactionErrorCode } from '../../../../types/Transaction';
import { StatusCodes } from 'http-status-codes';
import Tag from '../../../../types/Tag';
@@ -49,6 +51,70 @@ import moment from 'moment';
const MODULE_NAME = 'UserService';
export default class UserService {
+ public static async handleGetUserSessionContext(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
+ // Filter
+ const filteredRequest = UserValidatorRest.getInstance().validateUserSessionContextReq(req.query);
+ UtilsService.assertIdIsProvided(action, filteredRequest.UserID, MODULE_NAME, 'handleGetUserSessionContext', req.user);
+ // Check and Get User
+ const user = await UtilsService.checkAndGetUserAuthorization(
+ req.tenant, req.user, filteredRequest.UserID, Action.READ, action);
+ // Handle Tag
+ // We retrieve Tag auth to get the projected fields here to fit with what's in auth definition
+ const tagAuthorization = await AuthorizationService.checkAndGetTagAuthorizations(req.tenant, req.user, {}, Action.READ);
+ let tag: Tag;
+ // Get the default Tag
+ if (tagAuthorization.authorized) {
+ tag = await TagStorage.getDefaultUserTag(req.tenant, user.id, {
+ issuer: true
+ }, tagAuthorization.projectFields);
+ if (!tag) {
+ // Get the first active Tag
+ tag = await TagStorage.getFirstActiveUserTag(req.tenant, user.id, {
+ issuer: true
+ }, tagAuthorization.projectFields);
+ }
+ }
+ // Handle Car
+ // We retrieve Car auth to get the projected fields here to fit with what's in auth definition
+ const carAuthorization = await AuthorizationService.checkAndGetCarAuthorizations(req.tenant, req.user, {}, Action.READ);
+ let car: Car;
+ if (Utils.isComponentActiveFromToken(req.user, TenantComponents.CAR) && carAuthorization.authorized) {
+ // Get the default Car
+ car = await CarStorage.getDefaultUserCar(req.tenant, filteredRequest.UserID, {}, carAuthorization.projectFields);
+ if (!car) {
+ // Get the first available car
+ car = await CarStorage.getFirstAvailableUserCar(req.tenant, filteredRequest.UserID, carAuthorization.projectFields);
+ }
+ }
+ let withBillingChecks = true ;
+ const chargingStation = await UtilsService.checkAndGetChargingStationAuthorization(req.tenant, req.user, filteredRequest.ChargingStationID, Action.READ,
+ action, null, { withSiteArea: true });
+ if (!chargingStation.siteArea.accessControl) {
+ // The access control is switched off - so billing checks are useless
+ withBillingChecks = false;
+ }
+ // Check for billing errors
+ const errorCodes: Array<StartTransactionErrorCode> = [];
+ if (withBillingChecks) {
+ // Check for the billing prerequisites (such as the user's payment method)
+ await UserService.checkBillingErrorCodes(action, req.tenant, req.user, user, errorCodes);
+ }
+ const settingAuthorization = await AuthorizationService.checkAndGetSettingAuthorizations(req.tenant, req.user, Action.READ);
+ let prioritizationParameters: PrioritizationParameters;
+ // Handle Smart Charging
+ if (chargingStation.siteArea?.smartCharging && !chargingStation.excludeFromSmartCharging && settingAuthorization.authorized
+ && chargingStation.capabilities.supportChargingProfiles && Utils.isComponentActiveFromToken(req.user, TenantComponents.SMART_CHARGING)) {
+ const smartChargingSettings = await SettingStorage.getSmartChargingSettings(req.tenant);
+ if (smartChargingSettings.sapSmartCharging.prioritizationParameters?.active) {
+ prioritizationParameters = smartChargingSettings.sapSmartCharging.prioritizationParameters;
+ }
+ }
+ res.json({
+ tag, car, errorCodes, prioritizationParameters
+ });
+ next();
+ }
+
public static async handleGetUserDefaultTagCar(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Filter
const filteredRequest = UserValidatorRest.getInstance().validateUserDefaultTagCarGetReq(req.query);

MODIFY
src/server/rest/v1/validator/UserValidatorRest.ts
src/server/rest/v1/validator/UserValidatorRest.ts
-import { HttpUserCreateRequest, HttpUserDefaultTagCarGetRequest, HttpUserDeleteRequest, HttpUserGetRequest, HttpUserMobileTokenUpdateRequest, HttpUserSitesAssignRequest, HttpUserSitesGetRequest, HttpUserUpdateRequest, HttpUsersGetRequest, HttpUsersInErrorGetRequest } from '../../../../types/requests/HttpUserRequest';
+import { HttpUserCreateRequest, HttpUserDefaultTagCarGetRequest, HttpUserDeleteRequest, HttpUserGetRequest, HttpUserMobileTokenUpdateRequest, HttpUserSessionContextGetRequest, HttpUserSitesAssignRequest, HttpUserSitesGetRequest, HttpUserUpdateRequest, HttpUsersGetRequest, HttpUsersInErrorGetRequest } from '../../../../types/requests/HttpUserRequest';
import { ImportedUser } from '../../../../types/User';
import Schema from '../../../../types/validator/Schema';
@@ -19,6 +19,7 @@ export default class UserValidatorRest extends SchemaValidator {
private userUpdate: Schema = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/user/user-update.json`, 'utf8'));
private userMobileDataUpdate: Schema = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/user/user-mobile-data-update.json`, 'utf8'));
private userDefaultTagCarGet: Schema = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/user/user-default-tag-car-get.json`, 'utf8'));
+ private userSessionContextGet: Schema = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/user/user-session-context-get.json`, 'utf8'));
private constructor() {
super('UserValidatorRest');
@@ -74,4 +75,8 @@ export default class UserValidatorRest extends SchemaValidator {
public validateUserDefaultTagCarGetReq(data: Record<string, unknown>): HttpUserDefaultTagCarGetRequest {
return this.validate(this.userDefaultTagCarGet, data);
}
+
+ public validateUserSessionContextReq(data: Record<string, unknown>): HttpUserSessionContextGetRequest {
+ return this.validate(this.userSessionContextGet, data);
+ }
}

MODIFY
src/storage/mongodb/SettingStorage.ts
src/storage/mongodb/SettingStorage.ts
@@ -279,6 +279,12 @@ export default class SettingStorage {
stickyLimitation: config.sapSmartCharging.stickyLimitation ? config.sapSmartCharging.stickyLimitation : false,
limitBufferDC: config.sapSmartCharging.limitBufferDC ? config.sapSmartCharging.limitBufferDC : 0,
limitBufferAC: config.sapSmartCharging.limitBufferAC ? config.sapSmartCharging.limitBufferAC : 0,
+ prioritizationParameters: {
+ active: config.sapSmartCharging.prioritizationParameters?.active ?? false,
+ defaultSessionTimeHours: config.sapSmartCharging.prioritizationParameters?.defaultSessionTimeHours ?? null,
+ defaultInitialStateOfCharge: config.sapSmartCharging.prioritizationParameters?.defaultInitialStateOfCharge ?? null,
+ defaultTargetStateOfCharge: config.sapSmartCharging.prioritizationParameters?.defaultTargetStateOfCharge ?? null
+ }
};
}
}

MODIFY
src/types/Server.ts
src/types/Server.ts
@@ -426,6 +426,7 @@ export enum ServerAction {
TAG = 'Tag',
TAG_BY_VISUAL_ID= 'TagByVisualID',
USER_DEFAULT_TAG_CAR = 'UserDefaultTagCar',
+ USER_SESSION_CONTEXT = 'UserSessionContext',
TAG_CREATE = 'TagCreate',
TAG_UPDATE = 'TagUpdate',
TAG_UPDATE_BY_VISUAL_ID = 'TagUpdateByVisualID',
@@ -573,6 +574,7 @@ export enum RESTServerRoute {
REST_USERS = 'users',
REST_USER = 'users/:id',
REST_USER_DEFAULT_TAG_CAR = 'users/:id/default-car-tag',
+ REST_USER_SESSION_CONTEXT = 'users/:id/session-context',
REST_USER_SITES = 'users/:id/sites',
REST_USER_UPDATE_MOBILE_TOKEN = 'users/:id/mobile-token',
REST_USER_UPDATE_MOBILE_DATA = 'users/:id/mobile-data',

MODIFY
src/types/Setting.ts
src/types/Setting.ts
@@ -207,6 +207,14 @@ export interface SapSmartChargingSetting extends SmartChargingSetting {
stickyLimitation: boolean;
limitBufferDC: number;
limitBufferAC: number;
+ prioritizationParameters: PrioritizationParameters;
+}
+
+export interface PrioritizationParameters {
+ active: boolean;
+ defaultSessionTimeHours: number;
+ defaultInitialStateOfCharge: number;
+ defaultTargetStateOfCharge: number;
}
export enum RefundSettingsType {

MODIFY
src/types/requests/HttpUserRequest.ts
src/types/requests/HttpUserRequest.ts
@@ -116,3 +116,8 @@ export interface HttpUserDefaultTagCarGetRequest {
UserID: string;
ChargingStationID?: string; // TODO: Backward-compatibility issue - This should be mandatory! - change it as soon as possible
}
+
+export interface HttpUserSessionContextGetRequest {
+ UserID: string;
+ ChargingStationID: string;
+}

MODIFY
test/api/TagTest.ts
test/api/TagTest.ts
@@ -232,7 +232,18 @@ describe('Tag', () => {
expect(response.status).to.equal(StatusCodes.CREATED);
testData.createdTags.push(testData.newTag);
// Retrieve it
- response = await testData.userService.userApi.getDefaultTagCar(testData.newUser.id);
+ response = await testData.userService.userApi.getUserSessionContext({
+ userID: testData.newUser.id, chargingStationID: testData.chargingStationContext.getChargingStation().id });
+ expect(response.status).to.be.eq(StatusCodes.OK);
+ expect(response.data.tag.visualID).to.be.eq(testData.newTag.visualID);
+ expect(response.data.car).to.be.undefined;
+ expect(response.data.errorCodes).to.be.not.null;
+ expect(response.data.prioritizationParameters).to.be.undefined;
+ });
+
+ it('Should get the user default car tag with deprecated method', async () => {
+ // Retrieve it
+ const response = await testData.userService.userApi.getDefaultTagCar(testData.newUser.id);
expect(response.status).to.be.eq(StatusCodes.OK);
expect(response.data.tag.visualID).to.be.eq(testData.newTag.visualID);
expect(response.data.car).to.be.undefined;
@@ -290,7 +301,7 @@ describe('Tag', () => {
testData.newTagUnassigned.id = id;
});
- it('Should get the user default car tag', async () => {
+ it('Should get the user default car tag with deprecated method', async () => {
// Retrieve it
const response = await testData.userService.userApi.getDefaultTagCar(testData.newTagUnassigned.userID);
expect(response.status).to.be.eq(StatusCodes.OK);
@@ -299,6 +310,17 @@ describe('Tag', () => {
expect(response.data.errorCodes).to.be.not.null;
});
+ it('Should get the user default car tag', async () => {
+ // Retrieve it
+ const response = await testData.userService.userApi.getUserSessionContext({
+ userID: testData.newTagUnassigned.userID, chargingStationID: testData.chargingStationContext.getChargingStation().id });
+ expect(response.status).to.be.eq(StatusCodes.OK);
+ expect(response.data.tag.visualID).to.be.eq(testData.newTagUnassigned.visualID);
+ expect(response.data.car).to.be.undefined;
+ expect(response.data.errorCodes).to.be.not.null;
+ expect(response.data.prioritizationParameters).to.be.undefined;
+ });
+
it('Should be able to unassign his own badge', async () => {
let response = await testData.userService.tagApi.unassignTag(testData.newTagUnassigned);
expect(response.status).to.equal(StatusCodes.OK);

MODIFY
test/api/UserTest.ts
test/api/UserTest.ts
@@ -225,7 +225,17 @@ describe('User', () => {
expect(response.status).to.equal(StatusCodes.CREATED);
testData.createdTags.push(testData.newTag);
// Retrieve it
- response = await testData.userService.userApi.getDefaultTagCar(testData.newUser.id);
+ response = await testData.userService.userApi.getUserSessionContext({
+ userID: testData.newUser.id, chargingStationID: testData.chargingStationContext.getChargingStation().id });
+ expect(response.status).to.be.eq(StatusCodes.OK);
+ expect(response.data.tag.visualID).to.be.eq(testData.newTag.visualID);
+ expect(response.data.car).to.be.undefined;
+ expect(response.data.errorCodes).to.be.not.null;
+ expect(response.data.prioritizationParameters).to.be.undefined;
+ });
+
+ it('Should get the user default car tag with deprecated method', async () => {
+ const response = await testData.userService.userApi.getDefaultTagCar(testData.newUser.id);
expect(response.status).to.be.eq(StatusCodes.OK);
expect(response.data.tag.visualID).to.be.eq(testData.newTag.visualID);
expect(response.data.car).to.be.undefined;

MODIFY
test/api/client/UserApi.ts
test/api/client/UserApi.ts
@@ -60,4 +60,9 @@ export default class UserApi extends CrudApi {
const url = this.buildRestEndpointUrl(RESTServerRoute.REST_USER_DEFAULT_TAG_CAR, { });
return super.read({ UserID: userID }, url);
}
+
+ public async getUserSessionContext(params) {
+ const url = this.buildRestEndpointUrl(RESTServerRoute.REST_USER_SESSION_CONTEXT, { id: params.userID });
+ return super.read({ ChargingStationID: params.chargingStationID }, url);
+ }
}

MODIFY
test/api/context/ContextDefinition.ts
test/api/context/ContextDefinition.ts
@@ -185,6 +185,12 @@ export default class ContextDefinition {
stickyLimitation: true,
limitBufferDC: 20,
limitBufferAC: 10,
+ prioritizationParameters:{
+ active: false,
+ defaultSessionTimeHours: 8,
+ defaultInitialStateOfCharge: 20,
+ defaultTargetStateOfCharge: 80,
+ }
}
}
},
@@ -363,6 +369,12 @@ export default class ContextDefinition {
stickyLimitation: true,
limitBufferDC: 20,
limitBufferAC: 10,
+ prioritizationParameters:{
+ active: false,
+ defaultSessionTimeHours: 8,
+ defaultInitialStateOfCharge: 20,
+ defaultTargetStateOfCharge: 80,
+ }
}
}
},

Added new session context endpoint