Added Car validation schemas + removed security class
ADD
src/assets/server/rest/v1/schemas/car/car-create.json
+{
+ "title": "Create a Car Request",
+ "type": "object",
+ "properties": {
+ "vin": {
+ "$ref": "car.json#/definitions/vin"
+ },
+ "licensePlate": {
+ "$ref": "car.json#/definitions/licensePlate"
+ },
+ "carCatalogID": {
+ "$ref": "car.json#/definitions/carCatalogID"
+ },
+ "forced": {
+ "$ref": "car.json#/definitions/forced"
+ },
+ "type": {
+ "$ref": "car.json#/definitions/type"
+ },
+ "userID": {
+ "$ref": "user.json#/definitions/id"
+ },
+ "default": {
+ "$ref": "car.json#/definitions/default"
+ },
+ "converter": {
+ "$ref": "car.json#/definitions/converter"
+ }
+ },
+ "required": [
+ "vin",
+ "licensePlate",
+ "carCatalogID",
+ "type",
+ "converter"
+ ]
+}

ADD
src/assets/server/rest/v1/schemas/car/car-get.json
+{
+ "title": "Get Car Request",
+ "type": "object",
+ "properties": {
+ "ID": {
+ "$ref": "common.json#/definitions/id"
+ }
+ },
+ "required": [
+ "ID"
+ ]
+}

ADD
src/assets/server/rest/v1/schemas/car/car-update.json
+{
+ "title": "Update a Car Request",
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "car.json#/definitions/id"
+ },
+ "vin": {
+ "$ref": "car.json#/definitions/vin"
+ },
+ "licensePlate": {
+ "$ref": "car.json#/definitions/licensePlate"
+ },
+ "carCatalogID": {
+ "$ref": "car.json#/definitions/carCatalogID"
+ },
+ "forced": {
+ "$ref": "car.json#/definitions/forced"
+ },
+ "type": {
+ "$ref": "car.json#/definitions/type"
+ },
+ "userID": {
+ "$ref": "user.json#/definitions/id"
+ },
+ "default": {
+ "$ref": "car.json#/definitions/default"
+ },
+ "converter": {
+ "$ref": "car.json#/definitions/converter"
+ }
+ },
+ "required": [
+ "id",
+ "vin",
+ "licensePlate",
+ "carCatalogID",
+ "type",
+ "converter"
+ ]
+}

ADD
src/assets/server/rest/v1/schemas/car/car.json
+{
+ "$id": "car.json",
+ "definitions": {
+ "id": {
+ "$ref": "common.json#/definitions/id"
+ },
+ "vin": {
+ "type": "string",
+ "sanitize": "mongo"
+ },
+ "licensePlate": {
+ "type": "string",
+ "sanitize": "mongo",
+ "pattern": "^[A-Z0-9- ]*$"
+ },
+ "carCatalogID": {
+ "type": "number",
+ "sanitize": "mongo"
+ },
+ "forced": {
+ "type": "boolean",
+ "sanitize": "mongo"
+ },
+ "type": {
+ "type": "string",
+ "sanitize": "mongo",
+ "enum": [
+ "P",
+ "C",
+ "PC"
+ ]
+ },
+ "userID": {
+ "$ref": "user.json#/definitions/id"
+ },
+ "default": {
+ "type": "boolean",
+ "sanitize": "mongo"
+ },
+ "converter": {
+ "type": "object",
+ "required": [
+ "amperagePerPhase",
+ "numberOfPhases",
+ "powerWatts",
+ "type"
+ ],
+ "properties": {
+ "amperagePerPhase": {
+ "type": "number",
+ "sanitize": "mongo"
+ },
+ "numberOfPhases": {
+ "type": "number",
+ "sanitize": "mongo"
+ },
+ "type": {
+ "type": "string",
+ "sanitize": "mongo",
+ "enum": [
+ "S",
+ "O",
+ "A"
+ ]
+ },
+ "powerWatts": {
+ "type": "number",
+ "sanitize": "mongo"
+ }
+ }
+ }
+ }
+}

ADD
src/assets/server/rest/v1/schemas/car/carcatalog-get.json
+{
+ "title": "Get all the Car Catalogs Request",
+ "type": "object",
+ "properties": {
+ "ID": {
+ "type": "number",
+ "sanitize": "mongo"
+ }
+ },
+ "required": [
+ "ID"
+ ]
+}

ADD
src/assets/server/rest/v1/schemas/car/carcatalog-images-get.json
+{
+ "title": "Get all the Car Catalog Images Request",
+ "type": "object",
+ "properties": {
+ "ID": {
+ "type": "number",
+ "sanitize": "mongo"
+ },
+ "Limit": {
+ "$ref": "common.json#/definitions/limit"
+ },
+ "Skip": {
+ "$ref": "common.json#/definitions/skip"
+ }
+ },
+ "required": [
+ "ID"
+ ]
+}

ADD
src/assets/server/rest/v1/schemas/car/carcatalogs-get.json
+{
+ "title": "Get all the Car Catalogs Request",
+ "type": "object",
+ "properties": {
+ "Search": {
+ "$ref": "common.json#/definitions/search"
+ },
+ "CarMaker": {
+ "type": "string",
+ "sanitize": "mongo"
+ },
+ "SortFields": {
+ "$ref": "common.json#/definitions/sortFields"
+ },
+ "Limit": {
+ "$ref": "common.json#/definitions/limit"
+ },
+ "Skip": {
+ "$ref": "common.json#/definitions/skip"
+ },
+ "OnlyRecordCount": {
+ "$ref": "common.json#/definitions/onlyRecordCount"
+ },
+ "ProjectFields": {
+ "$ref": "common.json#/definitions/projectFields"
+ }
+ }
+}

ADD
src/assets/server/rest/v1/schemas/car/carmakers-get.json
+{
+ "title": "Get all the Car Makers Request",
+ "type": "object",
+ "properties": {
+ "Search": {
+ "$ref": "common.json#/definitions/search"
+ }
+ }
+}

ADD
src/assets/server/rest/v1/schemas/car/cars-get.json
+{
+ "title": "Get Cars list Request",
+ "type": "object",
+ "properties": {
+ "Search": {
+ "$ref": "common.json#/definitions/search"
+ },
+ "CarMaker": {
+ "type": "string",
+ "sanitize": "mongo"
+ },
+ "WithUser": {
+ "type": "boolean",
+ "sanitize": "mongo"
+ },
+ "UserID": {
+ "$ref": "user.json#/definitions/id"
+ },
+ "SortFields": {
+ "$ref": "common.json#/definitions/sortFields"
+ },
+ "Limit": {
+ "$ref": "common.json#/definitions/limit"
+ },
+ "Skip": {
+ "$ref": "common.json#/definitions/skip"
+ },
+ "OnlyRecordCount": {
+ "$ref": "common.json#/definitions/onlyRecordCount"
+ },
+ "ProjectFields": {
+ "$ref": "common.json#/definitions/projectFields"
+ }
+ }
+}

MODIFY
src/server/rest/v1/service/CarService.ts
src/server/rest/v1/service/CarService.ts
@@ -3,6 +3,7 @@ import { AsyncTaskType, AsyncTasks } from '../../../../types/AsyncTask';
import { CarCatalogDataResult, CarDataResult } from '../../../../types/DataResult';
import { HTTPAuthError, HTTPError } from '../../../../types/HTTPError';
import { NextFunction, Request, Response } from 'express';
+import Tenant, { TenantComponents } from '../../../../types/Tenant';
import AppAuthError from '../../../../exception/AppAuthError';
import AppError from '../../../../exception/AppError';
@@ -10,15 +11,13 @@ import AsyncTaskManager from '../../../../async-task/AsyncTaskManager';
import AuthorizationService from './AuthorizationService';
import Authorizations from '../../../../authorization/Authorizations';
import { Car } from '../../../../types/Car';
-import CarSecurity from './security/CarSecurity';
import CarStorage from '../../../../storage/mongodb/CarStorage';
+import CarValidator from '../validator/CarValidator';
import Constants from '../../../../utils/Constants';
import LockingHelper from '../../../../locking/LockingHelper';
import LockingManager from '../../../../locking/LockingManager';
import Logging from '../../../../utils/Logging';
import { ServerAction } from '../../../../types/Server';
-import Tenant from '../../../../types/Tenant';
-import { TenantComponents } from '../../../../types/Tenant';
import Utils from '../../../../utils/Utils';
import UtilsService from './UtilsService';
@@ -32,7 +31,7 @@ export default class CarService {
Action.LIST, Entity.CAR_CATALOGS, MODULE_NAME, 'handleGetCarCatalogs');
}
// Filter
- const filteredRequest = CarSecurity.filterCarCatalogsRequest(req.query);
+ const filteredRequest = CarValidator.getInstance().validateGetCarCatalogsReq(req.query);
// Check auth
const authorizationCarCatalogsFilter = await AuthorizationService.checkAndGetCarCatalogsAuthorizations(req.tenant, req.user, filteredRequest);
if (!authorizationCarCatalogsFilter.authorized) {
@@ -49,7 +48,7 @@ export default class CarService {
{
limit: filteredRequest.Limit,
skip: filteredRequest.Skip,
- sort: filteredRequest.SortFields,
+ sort: UtilsService.httpSortFieldsToMongoDB(filteredRequest.SortFields),
onlyRecordCount: filteredRequest.OnlyRecordCount
},
authorizationCarCatalogsFilter.projectFields
@@ -69,7 +68,7 @@ export default class CarService {
Action.READ, Entity.CAR_CATALOG, MODULE_NAME, 'handleGetCarCatalog');
}
// Filter
- const filteredRequest = CarSecurity.filterCarCatalogRequest(req.query);
+ const filteredRequest = CarValidator.getInstance().validateGetCarCatalogReq(req.query);
// Check and get Car Catalog
const carCatalog = await UtilsService.checkAndGetCarCatalogAuthorization(
req.tenant, req.user, filteredRequest.ID, Action.READ, action, null, { withImage: true }, true);
@@ -80,7 +79,7 @@ export default class CarService {
public static async handleGetCarCatalogImage(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Unprotected Endpoint: No JWT token is provided
// Filter
- const filteredRequest = CarSecurity.filterCarCatalogRequest(req.query);
+ const filteredRequest = CarValidator.getInstance().validateGetCarCatalogReq(req.query);
UtilsService.assertIdIsProvided(action, filteredRequest.ID, MODULE_NAME, 'handleGetCarCatalogImage', req.user);
// Get the car Image
const carCatalog = await CarStorage.getCarCatalogImage(filteredRequest.ID);
@@ -109,7 +108,7 @@ export default class CarService {
Action.READ, Entity.CAR_CATALOG, MODULE_NAME, 'handleGetCarCatalogImages');
}
// Filter
- const filteredRequest = CarSecurity.filterCarCatalogImagesRequest(req.query);
+ const filteredRequest = CarValidator.getInstance().validateGetCarCatalogImagesReq(req.query);
// Check mandatory fields
UtilsService.assertIdIsProvided(action, filteredRequest.ID, MODULE_NAME, 'handleGetCarCatalogImages', req.user);
// Check dynamic auth
@@ -186,7 +185,7 @@ export default class CarService {
Action.READ, Entity.CAR_CATALOG, MODULE_NAME, 'handleGetCarMakers');
}
// Filter
- const filteredRequest = CarSecurity.filterCarMakersRequest(req.query);
+ const filteredRequest = CarValidator.getInstance().validateGetCarMakersReq(req.query);
// Check auth
if (!await Authorizations.canListCarCatalogs(req.user)) {
throw new AppAuthError({
@@ -207,7 +206,7 @@ export default class CarService {
UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.CAR,
Action.CREATE, Entity.CAR, MODULE_NAME, 'handleCreateCar');
// Filter
- const filteredRequest = CarSecurity.filterCarCreateRequest(req.body);
+ const filteredRequest = CarValidator.getInstance().validateCreateCarReq(req.body);
// Check
UtilsService.checkIfCarValid(filteredRequest, req);
// Check auth
@@ -285,7 +284,7 @@ export default class CarService {
UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.CAR,
Action.UPDATE, Entity.CAR, MODULE_NAME, 'handleUpdateCar');
// Filter
- const filteredRequest = CarSecurity.filterCarUpdateRequest(req.body);
+ const filteredRequest = CarValidator.getInstance().validateUpdateCarReq(req.body);
// Check
UtilsService.checkIfCarValid(filteredRequest, req);
// Check and Get Car
@@ -367,7 +366,7 @@ export default class CarService {
UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.CAR,
Action.LIST, Entity.CARS, MODULE_NAME, 'handleGetCars');
// Filter
- const filteredRequest = CarSecurity.filterCarsRequest(req.query);
+ const filteredRequest = CarValidator.getInstance().validateGetCarsReq(req.query);
// Check auth
const authorizationCarsFilter = await AuthorizationService.checkAndGetCarsAuthorizations(
req.tenant, req.user, filteredRequest);
@@ -387,7 +386,7 @@ export default class CarService {
{
limit: filteredRequest.Limit,
skip: filteredRequest.Skip,
- sort: filteredRequest.SortFields,
+ sort: UtilsService.httpSortFieldsToMongoDB(filteredRequest.SortFields),
onlyRecordCount: filteredRequest.OnlyRecordCount
},
authorizationCarsFilter.projectFields
@@ -404,7 +403,7 @@ export default class CarService {
UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.CAR,
Action.READ, Entity.CAR, MODULE_NAME, 'handleGetCar');
// Filter
- const filteredRequest = CarSecurity.filterCarRequest(req.query);
+ const filteredRequest = CarValidator.getInstance().validateGetCarReq(req.query);
// Check and get Car
const car = await UtilsService.checkAndGetCarAuthorization(
req.tenant, req.user, filteredRequest.ID, Action.READ, action, null, { withUser: true }, true);
@@ -418,7 +417,7 @@ export default class CarService {
UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.CAR,
Action.DELETE, Entity.CAR, MODULE_NAME, 'handleDeleteCar');
// Filter
- const carID = CarSecurity.filterCarRequest(req.query).ID;
+ const carID = CarValidator.getInstance().validateGetCarReq(req.query).ID;
// Check and Get Car
const car = await UtilsService.checkAndGetCarAuthorization(
req.tenant, req.user, carID, Action.DELETE, action);

DELETE
src/server/rest/v1/service/security/CarSecurity.ts
-import { HttpCarCatalogImagesRequest, HttpCarCatalogRequest, HttpCarCatalogsRequest, HttpCarCreateRequest, HttpCarMakersRequest, HttpCarRequest, HttpCarUpdateRequest, HttpCarsRequest } from '../../../../../types/requests/HttpCarRequest';
-
-import Utils from '../../../../../utils/Utils';
-import UtilsSecurity from './UtilsSecurity';
-import sanitize from 'mongo-sanitize';
-
-export default class CarSecurity {
-
- public static filterCarMakersRequest(request: any): HttpCarMakersRequest {
- const filteredRequest: HttpCarMakersRequest = {
- Search: sanitize(request.Search),
- } as HttpCarMakersRequest;
- UtilsSecurity.filterSkipAndLimit(request, filteredRequest);
- UtilsSecurity.filterSort(request, filteredRequest);
- return filteredRequest;
- }
-
- public static filterCarCatalogsRequest(request: any): HttpCarCatalogsRequest {
- const filteredRequest: HttpCarCatalogsRequest = {
- Search: sanitize(request.Search),
- CarMaker: sanitize(request.CarMaker),
- } as HttpCarCatalogsRequest;
- UtilsSecurity.filterSkipAndLimit(request, filteredRequest);
- UtilsSecurity.filterSort(request, filteredRequest);
- return filteredRequest;
- }
-
- public static filterCarCatalogImagesRequest(request: any): HttpCarCatalogImagesRequest {
- const filteredRequest: HttpCarCatalogImagesRequest = {
- ID: sanitize(request.ID),
- } as HttpCarCatalogImagesRequest;
- UtilsSecurity.filterSkipAndLimit(request, filteredRequest);
- UtilsSecurity.filterSort(request, filteredRequest);
- return filteredRequest;
- }
-
- public static filterCarCatalogRequest(request: any): HttpCarCatalogRequest {
- return {
- ID: Utils.convertToInt(sanitize(request.ID)),
- };
- }
-
- public static filterCarCreateRequest(request: any): HttpCarCreateRequest {
- return {
- vin: sanitize(request.vin),
- licensePlate: sanitize(request.licensePlate),
- carCatalogID: Utils.convertToInt(sanitize(request.carCatalogID)),
- forced: UtilsSecurity.filterBoolean(request.forced),
- type: sanitize(request.type),
- userID: sanitize(request.userID),
- default: UtilsSecurity.filterBoolean(request.isDefault),
- converter: {
- amperagePerPhase: sanitize(request.converter.amperagePerPhase),
- numberOfPhases: sanitize(request.converter.numberOfPhases),
- type: sanitize(request.converter.type),
- powerWatts: sanitize(request.converter.powerWatts)
- },
- };
- }
-
- public static filterCarUpdateRequest(request: any): HttpCarUpdateRequest {
- const filteredRequest: HttpCarUpdateRequest = {
- id: sanitize(request.id),
- vin: sanitize(request.vin),
- licensePlate: sanitize(request.licensePlate),
- carCatalogID: Utils.convertToInt(sanitize(request.carCatalogID)),
- type: sanitize(request.type),
- userID: sanitize(request.userID),
- default: UtilsSecurity.filterBoolean(request.isDefault),
- converter: {
- amperagePerPhase: sanitize(request.converter.amperagePerPhase),
- numberOfPhases: sanitize(request.converter.numberOfPhases),
- type: sanitize(request.converter.type),
- powerWatts: sanitize(request.converter.powerWatts)
- },
- };
- return filteredRequest;
- }
-
- public static filterCarsRequest(request: any): HttpCarsRequest {
- const filteredRequest: HttpCarsRequest = {
- Search: sanitize(request.Search),
- CarMaker: sanitize(request.CarMaker),
- WithUser: UtilsSecurity.filterBoolean(request.WithUser),
- UserID: sanitize(request.UserID)
- } as HttpCarsRequest;
- UtilsSecurity.filterSkipAndLimit(request, filteredRequest);
- UtilsSecurity.filterSort(request, filteredRequest);
- return filteredRequest;
- }
-
- public static filterCarRequest(request: any): HttpCarRequest {
- const filteredRequest: HttpCarRequest = {
- ID: sanitize(request.ID),
- } as HttpCarRequest;
- return filteredRequest;
- }
-}

ADD
src/server/rest/v1/validator/CarValidator.ts
+import { HttpCarCatalogImagesRequest, HttpCarCatalogRequest, HttpCarCatalogsRequest, HttpCarCreateRequest, HttpCarMakersRequest, HttpCarRequest, HttpCarUpdateRequest, HttpCarsRequest } from '../../../../types/requests/HttpCarRequest';
+
+import Schema from '../../../../types/validator/Schema';
+import SchemaValidator from './SchemaValidator';
+import fs from 'fs';
+import global from '../../../../types/GlobalType';
+
+export default class CarValidator extends SchemaValidator {
+ private static instance: CarValidator|null = null;
+ private carMakersGet: Schema;
+ private carCatalogsGet: Schema;
+ private carCatalogImagesGet: Schema;
+ private carCatalogGet: Schema;
+ private carCreate: Schema;
+ private carUpdate: Schema;
+ private carsGet: Schema;
+ private carGet: Schema;
+
+ private constructor() {
+ super('CarValidator');
+ this.carMakersGet = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/car/carmakers-get.json`, 'utf8'));
+ this.carCatalogsGet = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/car/carcatalogs-get.json`, 'utf8'));
+ this.carCatalogImagesGet = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/car/carcatalog-images-get.json`, 'utf8'));
+ this.carCatalogGet = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/car/carcatalog-get.json`, 'utf8'));
+ this.carCreate = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/car/car-create.json`, 'utf8'));
+ this.carUpdate = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/car/car-update.json`, 'utf8'));
+ this.carsGet = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/car/cars-get.json`, 'utf8'));
+ this.carGet = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/car/car-get.json`, 'utf8'));
+ }
+
+ public static getInstance(): CarValidator {
+ if (!CarValidator.instance) {
+ CarValidator.instance = new CarValidator();
+ }
+ return CarValidator.instance;
+ }
+
+ public validateGetCarMakersReq(data: any): HttpCarMakersRequest {
+ // Validate schema
+ this.validate(this.carMakersGet, data);
+ return data;
+ }
+
+ public validateGetCarCatalogsReq(data: any): HttpCarCatalogsRequest {
+ // Validate schema
+ this.validate(this.carCatalogsGet, data);
+ return data;
+ }
+
+ public validateGetCarCatalogImagesReq(data: any): HttpCarCatalogImagesRequest {
+ // Validate schema
+ this.validate(this.carCatalogImagesGet, data);
+ return data;
+ }
+
+ public validateGetCarCatalogReq(data: any): HttpCarCatalogRequest {
+ // Validate schema
+ this.validate(this.carCatalogGet, data);
+ return data;
+ }
+
+ public validateCreateCarReq(data: HttpCarCreateRequest): HttpCarCreateRequest {
+ // Validate schema
+ this.validate(this.carCreate, data);
+ return data;
+ }
+
+ public validateUpdateCarReq(data: HttpCarUpdateRequest): HttpCarUpdateRequest {
+ // Validate schema
+ this.validate(this.carUpdate, data);
+ return data;
+ }
+
+ public validateGetCarsReq(data: any): HttpCarsRequest {
+ // Validate schema
+ this.validate(this.carsGet, data);
+ return data;
+ }
+
+ public validateGetCarReq(data: any): HttpCarRequest {
+ // Validate schema
+ this.validate(this.carGet, data);
+ return data;
+ }
+}

MODIFY
src/server/rest/v1/validator/SchemaValidator.ts
src/server/rest/v1/validator/SchemaValidator.ts
@@ -21,6 +21,7 @@ export default class SchemaValidator {
private tagSchema: Schema = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/tag/tag.json`, 'utf8'));
private transactionSchema: Schema = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/transaction/transaction.json`, 'utf8'));
private userSchema: Schema = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/user/user.json`, 'utf8'));
+ private carSchema: Schema = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/car/car.json`, 'utf8'));
constructor(readonly moduleName: string,
config: {
@@ -48,6 +49,7 @@ export default class SchemaValidator {
this.ajv.addSchema(this.tagSchema);
this.ajv.addSchema(this.transactionSchema);
this.ajv.addSchema(this.userSchema);
+ this.ajv.addSchema(this.carSchema);
}
public validate(schema: Schema, content: any): void {

MODIFY
test/api/CarTest.ts
test/api/CarTest.ts
@@ -158,7 +158,6 @@ describe('Car', function() {
}), false
);
expect(response.status).to.equal(StatusCodes.INTERNAL_SERVER_ERROR);
- expect(response.data.message).to.equal('Car type is mandatory');
});
it('Should not be able to create a new car without a car catalog ID', async () => {

Added Car validation schemas + removed security class