Added company validation schemas + removed security class
ADD
src/assets/server/rest/v1/schemas/company/companies-get.json
+{
+ "title": "Get all the Companies",
+ "type": "object",
+ "properties": {
+ "Search": {
+ "$ref": "common.json#/definitions/search"
+ },
+ "WithSite": {
+ "type": "boolean",
+ "sanitize": "mongo"
+ },
+ "WithLogo": {
+ "type": "boolean",
+ "sanitize": "mongo"
+ },
+ "Issuer": {
+ "type": "boolean",
+ "sanitize": "mongo"
+ },
+ "LocLongitude": {
+ "type": "number",
+ "format": "longitude",
+ "sanitize": "mongo"
+ },
+ "LocLatitude": {
+ "type": "number",
+ "format": "latitude",
+ "sanitize": "mongo"
+ },
+ "LocMaxDistanceMeters": {
+ "type": "number",
+ "sanitize": "mongo",
+ "exclusiveMinimum": 0
+ },
+ "Limit": {
+ "$ref": "common.json#/definitions/limit"
+ },
+ "Skip": {
+ "$ref": "common.json#/definitions/limit"
+ },
+ "SortFields": {
+ "$ref": "common.json#/definitions/sortFields"
+ },
+ "ProjectFields": {
+ "$ref": "common.json#/definitions/projectFields"
+ }
+ },
+ "required": []
+}

ADD
src/assets/server/rest/v1/schemas/company/company-create.json
+{
+ "title": "Create a Company",
+ "type": "object",
+ "properties": {
+ "name": {
+ "$ref": "company.json#/definitions/name"
+ },
+ "address": {
+ "$ref": "company.json#/definitions/address"
+ },
+ "logo": {
+ "$ref": "common.json#/definitions/logo"
+ }
+ },
+ "required": [
+ "name"
+ ]
+}

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

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

ADD
src/assets/server/rest/v1/schemas/company/company-update.json
+{
+ "title": "Update a Company",
+ "type": "object",
+ "properties": {
+ "id": {
+ "$ref": "company.json#/definitions/id"
+ },
+ "name": {
+ "$ref": "company.json#/definitions/name"
+ },
+ "address": {
+ "$ref": "company.json#/definitions/address"
+ },
+ "logo": {
+ "$ref": "common.json#/definitions/logo"
+ }
+ },
+ "required": [
+ "id",
+ "name"
+ ]
+}

ADD
src/assets/server/rest/v1/schemas/company/company.json
+{
+ "$id": "company.json",
+ "definitions": {
+ "id": {
+ "$ref": "common.json#/definitions/id"
+ },
+ "ids": {
+ "$ref": "common.json#/definitions/ids"
+ },
+ "name": {
+ "type": "string",
+ "sanitize": "mongo"
+ },
+ "address": {
+ "$ref": "common.json#/definitions/address"
+ }
+ }
+}

MODIFY
src/server/rest/v1/router/api/CompanyRouter.ts
src/server/rest/v1/router/api/CompanyRouter.ts
@@ -30,6 +30,7 @@ export default class CompanyRouter {
protected buildRouteCompany(): void {
this.router.get(`/${ServerRoute.REST_COMPANY}`, async (req: Request, res: Response, next: NextFunction) => {
+ req.query.ID = req.params.id;
await RouterUtils.handleServerAction(CompanyService.handleGetCompany.bind(this), ServerAction.COMPANY, req, res, next);
});
}
@@ -42,18 +43,21 @@ export default class CompanyRouter {
protected buildRouteUpdateCompany(): void {
this.router.put(`/${ServerRoute.REST_COMPANY}`, async (req: Request, res: Response, next: NextFunction) => {
+ req.query.ID = req.params.id;
await RouterUtils.handleServerAction(CompanyService.handleUpdateCompany.bind(this), ServerAction.COMPANY_UPDATE, req, res, next);
});
}
protected buildRouteDeleteCompany(): void {
this.router.delete(`/${ServerRoute.REST_COMPANY}`, async (req: Request, res: Response, next: NextFunction) => {
+ req.query.ID = req.params.id;
await RouterUtils.handleServerAction(CompanyService.handleDeleteCompany.bind(this), ServerAction.COMPANY_DELETE, req, res, next);
});
}
protected buildRouteGetCompanyLogo(): void {
this.router.get(`/${ServerRoute.REST_COMPANY_LOGO}`, async (req: Request, res: Response, next: NextFunction) => {
+ req.query.ID = req.params.id;
await RouterUtils.handleServerAction(CompanyService.handleGetCompanyLogo.bind(this), ServerAction.COMPANY_LOGO, req, res, next);
});
}

MODIFY
src/server/rest/v1/service/CompanyService.ts
src/server/rest/v1/service/CompanyService.ts
@@ -5,8 +5,8 @@ import AppAuthError from '../../../../exception/AppAuthError';
import AuthorizationService from './AuthorizationService';
import Company from '../../../../types/Company';
import { CompanyDataResult } from '../../../../types/DataResult';
-import CompanySecurity from './security/CompanySecurity';
import CompanyStorage from '../../../../storage/mongodb/CompanyStorage';
+import CompanyValidator from '../validator/CompanyValidator';
import Constants from '../../../../utils/Constants';
import { HTTPAuthError } from '../../../../types/HTTPError';
import Logging from '../../../../utils/Logging';
@@ -25,7 +25,7 @@ export default class CompanyService {
UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.ORGANIZATION,
Action.DELETE, Entity.COMPANY, MODULE_NAME, 'handleDeleteCompany');
// Filter
- const companyID = CompanySecurity.filterCompanyRequestByID(req.query);
+ const companyID = CompanyValidator.getInstance().validateCompanyGet(req.query).ID;
UtilsService.assertIdIsProvided(action, companyID, MODULE_NAME, 'handleDeleteCompany', req.user);
// Check and Get Company
const company = await UtilsService.checkAndGetCompanyAuthorization(
@@ -50,7 +50,7 @@ export default class CompanyService {
UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.ORGANIZATION,
Action.READ, Entity.COMPANY, MODULE_NAME, 'handleGetCompany');
// Filter
- const filteredRequest = CompanySecurity.filterCompanyRequest(req.query);
+ const filteredRequest = CompanyValidator.getInstance().validateCompanyGet(req.query);
UtilsService.assertIdIsProvided(action, filteredRequest.ID, MODULE_NAME, 'handleGetCompany', req.user);
// Check and Get Company
const company = await UtilsService.checkAndGetCompanyAuthorization(
@@ -63,7 +63,7 @@ export default class CompanyService {
public static async handleGetCompanyLogo(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Filter
- const filteredRequest = CompanySecurity.filterCompanyLogoRequest(req.query);
+ const filteredRequest = CompanyValidator.getInstance().validateCompanyGetLogo(req.query);
UtilsService.assertIdIsProvided(action, filteredRequest.ID, MODULE_NAME, 'handleGetCompanyLogo', req.user);
// Fetch Tenant Object by Tenant ID
const tenant = await TenantStorage.getTenant(filteredRequest.TenantID);
@@ -93,8 +93,19 @@ export default class CompanyService {
// Check if component is active
UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.ORGANIZATION,
Action.LIST, Entity.COMPANIES, MODULE_NAME, 'handleGetCompanies');
+ // Maintain retro-compatibility
+ if (req.query.WithSites) {
+ req.query.WithSite = req.query.WithSites;
+ }
// Filter
- const filteredRequest = CompanySecurity.filterCompaniesRequest(req.query);
+ const filteredRequest = CompanyValidator.getInstance().validateCompaniesGet(req.query);
+ // Create GPS Coordinates
+ if (filteredRequest.LocLongitude && filteredRequest.LocLatitude) {
+ filteredRequest.LocCoordinates = [
+ Utils.convertToFloat(filteredRequest.LocLongitude),
+ Utils.convertToFloat(filteredRequest.LocLatitude)
+ ];
+ }
// Check dynamic auth
const authorizationCompaniesFilter = await AuthorizationService.checkAndGetCompaniesAuthorizations(
req.tenant, req.user, filteredRequest);
@@ -107,7 +118,7 @@ export default class CompanyService {
{
search: filteredRequest.Search,
issuer: filteredRequest.Issuer,
- withSites: filteredRequest.WithSites,
+ withSite: filteredRequest.WithSite,
withLogo: filteredRequest.WithLogo,
locCoordinates: filteredRequest.LocCoordinates,
locMaxDistanceMeters: filteredRequest.LocMaxDistanceMeters,
@@ -116,7 +127,7 @@ export default class CompanyService {
{
limit: filteredRequest.Limit,
skip: filteredRequest.Skip,
- sort: filteredRequest.SortFields,
+ sort: UtilsService.httpSortFieldsToMongoDB(filteredRequest.SortFields),
onlyRecordCount: filteredRequest.OnlyRecordCount
},
authorizationCompaniesFilter.projectFields
@@ -133,12 +144,10 @@ export default class CompanyService {
UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.ORGANIZATION,
Action.CREATE, Entity.COMPANY, MODULE_NAME, 'handleCreateCompany');
// Filter
- const filteredRequest = CompanySecurity.filterCompanyCreateRequest(req.body);
- // Check
- UtilsService.checkIfCompanyValid(filteredRequest, req);
+ const filteredRequest = CompanyValidator.getInstance().validateCompanyCreate(req.body);
// Get dynamic auth
const authorizationFilter = await AuthorizationService.checkAndGetCompanyAuthorizations(
- req.tenant, req.user, {}, Action.CREATE, filteredRequest as Company);
+ req.tenant, req.user, {}, Action.CREATE, filteredRequest);
if (!authorizationFilter.authorized) {
throw new AppAuthError({
errorCode: HTTPAuthError.FORBIDDEN,
@@ -174,12 +183,10 @@ export default class CompanyService {
UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.ORGANIZATION,
Action.UPDATE, Entity.COMPANY, MODULE_NAME, 'handleUpdateCompany');
// Filter
- const filteredRequest = CompanySecurity.filterCompanyUpdateRequest(req.body);
- // Check Mandatory fields
- UtilsService.checkIfCompanyValid(filteredRequest, req);
+ const filteredRequest = CompanyValidator.getInstance().validateCompanyUpdate(req.body);
// Check and Get Company
const company = await UtilsService.checkAndGetCompanyAuthorization(
- req.tenant, req.user, filteredRequest.id, Action.UPDATE, action, filteredRequest as Company);
+ req.tenant, req.user, filteredRequest.id, Action.UPDATE, action, filteredRequest);
// Update
company.name = filteredRequest.name;
company.address = filteredRequest.address;

MODIFY
src/server/rest/v1/service/UtilsService.ts
src/server/rest/v1/service/UtilsService.ts
@@ -1265,27 +1265,6 @@ export default class UtilsService {
}
}
- public static checkIfCompanyValid(company: Partial<Company>, req: Request): void {
- if (req.method !== 'POST' && !company.id) {
- throw new AppError({
- source: Constants.CENTRAL_SERVER,
- errorCode: HTTPError.GENERAL_ERROR,
- message: 'Company ID is mandatory',
- module: MODULE_NAME, method: 'checkIfCompanyValid',
- user: req.user.id
- });
- }
- if (!company.name) {
- throw new AppError({
- source: Constants.CENTRAL_SERVER,
- errorCode: HTTPError.GENERAL_ERROR,
- message: 'Company Name is mandatory',
- module: MODULE_NAME, method: 'checkIfCompanyValid',
- user: req.user.id
- });
- }
- }
-
public static checkIfAssetValid(asset: Partial<Asset>, req: Request): void {
if (req.method !== 'POST' && !asset.id) {
throw new AppError({

DELETE
src/server/rest/v1/service/security/CompanySecurity.ts
-import { HttpCompaniesRequest, HttpCompanyLogoRequest, HttpCompanyRequest } from '../../../../../types/requests/HttpCompanyRequest';
-
-import Company from '../../../../../types/Company';
-import Utils from '../../../../../utils/Utils';
-import UtilsSecurity from './UtilsSecurity';
-import sanitize from 'mongo-sanitize';
-
-export default class CompanySecurity {
-
- public static filterCompanyRequestByID(request: any): string {
- return sanitize(request.ID);
- }
-
- public static filterCompanyLogoRequest(request: any): HttpCompanyLogoRequest {
- return {
- ID: sanitize(request.ID),
- TenantID: sanitize(request.TenantID),
- };
- }
-
- public static filterCompanyRequest(request: any): HttpCompanyRequest {
- return {
- ID: sanitize(request.ID)
- };
- }
-
- public static filterCompaniesRequest(request: any): HttpCompaniesRequest {
- const filteredRequest: HttpCompaniesRequest = {
- Search: sanitize(request.Search),
- WithSites: UtilsSecurity.filterBoolean(request.WithSites),
- WithLogo: UtilsSecurity.filterBoolean(request.WithLogo)
- } as HttpCompaniesRequest;
- if (Utils.objectHasProperty(request, 'Issuer')) {
- filteredRequest.Issuer = UtilsSecurity.filterBoolean(request.Issuer);
- }
- if (Utils.containsGPSCoordinates([request.LocLongitude, request.LocLatitude])) {
- filteredRequest.LocCoordinates = [
- Utils.convertToFloat(sanitize(request.LocLongitude)),
- Utils.convertToFloat(sanitize(request.LocLatitude))
- ];
- if (Utils.objectHasProperty(request, 'LocMaxDistanceMeters')) {
- request.LocMaxDistanceMeters = Utils.convertToInt(sanitize(request.LocMaxDistanceMeters));
- if (request.LocMaxDistanceMeters > 0) {
- filteredRequest.LocMaxDistanceMeters = request.LocMaxDistanceMeters;
- }
- }
- }
- UtilsSecurity.filterSkipAndLimit(request, filteredRequest);
- UtilsSecurity.filterSort(request, filteredRequest);
- return filteredRequest;
- }
-
- static filterCompanyUpdateRequest(request: any): Partial<Company> {
- const filteredRequest = CompanySecurity._filterCompanyRequest(request);
- return {
- id: sanitize(request.id),
- ...filteredRequest
- };
- }
-
- public static filterCompanyCreateRequest(request: any): Partial<Company> {
- return CompanySecurity._filterCompanyRequest(request);
- }
-
- public static _filterCompanyRequest(request: any): Partial<Company> {
- const filteredRequest = {
- name: sanitize(request.name),
- address: UtilsSecurity.filterAddressRequest(request.address)
- } as Partial<Company>;
- if (Utils.objectHasProperty(request, 'logo')) {
- filteredRequest.logo = sanitize(request.logo);
- }
- return filteredRequest;
- }
-}

ADD
src/server/rest/v1/validator/CompanyValidator.ts
+import { HttpCompaniesRequest, HttpCompanyLogoRequest, HttpCompanyRequest } from '../../../../types/requests/HttpCompanyRequest';
+
+import Company from '../../../../types/Company';
+import Schema from '../../../../types/validator/Schema';
+import SchemaValidator from './SchemaValidator';
+import fs from 'fs';
+import global from '../../../../types/GlobalType';
+
+export default class CompanyValidator extends SchemaValidator {
+ private static instance: CompanyValidator|null = null;
+ private companiesGet: Schema;
+ private companyGet: Schema;
+ private companyCreate: Schema;
+ private companyUpdate: Schema;
+ private companyGetLogo: Schema;
+
+ private constructor() {
+ super('CompanyValidator');
+ this.companiesGet = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/company/companies-get.json`, 'utf8'));
+ this.companyGet = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/company/company-get.json`, 'utf8'));
+ this.companyCreate = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/company/company-create.json`, 'utf8'));
+ this.companyUpdate = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/company/company-update.json`, 'utf8'));
+ this.companyGetLogo = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/company/company-get-logo.json`, 'utf8'));
+ }
+
+ public static getInstance(): CompanyValidator {
+ if (!CompanyValidator.instance) {
+ CompanyValidator.instance = new CompanyValidator();
+ }
+ return CompanyValidator.instance;
+ }
+
+ public validateCompaniesGet(data: any): HttpCompaniesRequest {
+ // Validate schema
+ this.validate(this.companiesGet, data);
+ return data;
+ }
+
+ public validateCompanyGet(data: any): HttpCompanyRequest {
+ // Validate schema
+ this.validate(this.companyGet, data);
+ return data;
+ }
+
+ public validateCompanyCreate(data: Company): Company {
+ // Validate schema
+ this.validate(this.companyCreate, data);
+ return data;
+ }
+
+ public validateCompanyUpdate(data: Company): Company {
+ // Validate schema
+ this.validate(this.companyUpdate, data);
+ return data;
+ }
+
+ public validateCompanyGetLogo(data: any): HttpCompanyLogoRequest {
+ // Validate schema
+ this.validate(this.companyGetLogo, 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 companySchema: Schema = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/company/company.json`, 'utf8'));
constructor(readonly moduleName: string,
config: {
@@ -41,16 +42,19 @@ export default class SchemaValidator {
type: 'number',
validate: (c) => Constants.REGEX_VALIDATION_LONGITUDE.test(c.toString())
});
- this.ajv.addSchema(this.commonSchema);
- this.ajv.addSchema(this.tenantSchema);
- this.ajv.addSchema(this.tenantComponentSchema);
- this.ajv.addSchema(this.chargingStationSchema);
- this.ajv.addSchema(this.tagSchema);
- this.ajv.addSchema(this.transactionSchema);
- this.ajv.addSchema(this.userSchema);
+ this.ajv.addSchema([
+ this.commonSchema,
+ this.tenantSchema,
+ this.tenantComponentSchema,
+ this.chargingStationSchema,
+ this.tagSchema,
+ this.transactionSchema,
+ this.userSchema,
+ this.companySchema
+ ]);
}
- public validate(schema: Schema, content: any): void {
+ protected validate(schema: Schema, content: any): void {
const fnValidate = this.ajv.compile(schema);
if (!fnValidate(content)) {
if (!fnValidate.errors) {

MODIFY
src/storage/mongodb/CompanyStorage.ts
src/storage/mongodb/CompanyStorage.ts
@@ -83,7 +83,7 @@ export default class CompanyStorage {
}
public static async getCompanies(tenant: Tenant,
- params: { search?: string; issuer?: boolean; companyIDs?: string[]; withSites?: boolean; withLogo?: boolean;
+ params: { search?: string; issuer?: boolean; companyIDs?: string[]; withSite?: boolean; withLogo?: boolean;
locCoordinates?: number[]; locMaxDistanceMeters?: number; } = {},
dbParams?: DbParams, projectFields?: string[]): Promise<DataResult<Company>> {
// Debug
@@ -181,7 +181,7 @@ export default class CompanyStorage {
$limit: (dbParams.limit > 0 && dbParams.limit < Constants.DB_RECORD_COUNT_CEIL) ? dbParams.limit : Constants.DB_RECORD_COUNT_CEIL
});
// Site
- if (params.withSites) {
+ if (params.withSite) {
DatabaseUtils.pushSiteLookupInAggregation(
{ tenantID: tenant.id, aggregation, localField: '_id', foreignField: 'companyID', asField: 'sites' });
}

MODIFY
src/types/requests/HttpCompanyRequest.ts
src/types/requests/HttpCompanyRequest.ts
@@ -8,9 +8,11 @@ export interface HttpCompanyRequest extends HttpByIDRequest {
export interface HttpCompaniesRequest extends HttpDatabaseRequest {
Search?: string;
Issuer?: boolean;
- WithSites?: boolean;
+ WithSite?: boolean;
WithLogo?: boolean;
LocCoordinates?: number[];
+ LocLongitude?: number;
+ LocLatitude?: number;
LocMaxDistanceMeters?: number;
}

MODIFY
test/api/CompanyOrgTest.ts
test/api/CompanyOrgTest.ts
@@ -5,8 +5,10 @@ import Company from '../../src/types/Company';
import ContextDefinition from './context/ContextDefinition';
import ContextProvider from './context/ContextProvider';
import Factory from '../factories/Factory';
+import Site from '../../src/types/Site';
import { StatusCodes } from 'http-status-codes';
import TenantContext from './context/TenantContext';
+import User from '../../src/types/User';
import chaiSubset from 'chai-subset';
chai.use(chaiSubset);
@@ -17,13 +19,13 @@ class TestData {
public centralUserService: CentralServerService;
public userContext: any;
public userService: CentralServerService;
- public newCompany: any;
+ public newCompany: Company;
public companyWithSite: Company;
public companyWithNoSite: Company;
- public newSite: any;
- public createdCompanies: any[] = [];
- public createdUsers: any[] = [];
- public createdSites: any[] =[];
+ public newSite: Site;
+ public createdCompanies: Company[] = [];
+ public createdUsers: User[] = [];
+ public createdSites: Site[] =[];
}
const testData = new TestData();

MODIFY
test/api/client/CompanyApi.ts
test/api/client/CompanyApi.ts
+import AuthenticatedBaseApi from './utils/AuthenticatedBaseApi';
import CrudApi from './utils/CrudApi';
+import { ServerRoute } from '../../../src/types/Server';
import TestConstants from './utils/TestConstants';
export default class CompanyApi extends CrudApi {
- public constructor(authenticatedApi) {
+ public constructor(authenticatedApi: AuthenticatedBaseApi) {
super(authenticatedApi);
}
public async readById(id) {
- return super.readById(id, '/client/api/Company');
+ return super.readById(id, this.buildRestEndpointUrl(ServerRoute.REST_COMPANY, { id }));
}
public async readAll(params, paging = TestConstants.DEFAULT_PAGING, ordering = TestConstants.DEFAULT_ORDERING) {
- return super.readAll(params, paging, ordering, '/client/api/Companies');
+ return super.readAll(params, paging, ordering, this.buildRestEndpointUrl(ServerRoute.REST_COMPANIES));
}
public async create(data) {
- return super.create(data, '/client/api/CompanyCreate');
+ return super.create(data, this.buildRestEndpointUrl(ServerRoute.REST_COMPANIES));
}
public async update(data) {
- return super.update(data, '/client/api/CompanyUpdate');
+ return super.update(data, this.buildRestEndpointUrl(ServerRoute.REST_COMPANY, { id: data.id }));
}
public async delete(id) {
- return super.delete(id, '/client/api/CompanyDelete');
+ return super.delete(id, this.buildRestEndpointUrl(ServerRoute.REST_COMPANY, { id }));
}
}

Added company validation schemas + removed security class