diff --git a/src/__tests__/unit/features.controller.unit.ts b/src/__tests__/unit/features.controller.unit.ts index d0e6cb9..c306639 100644 --- a/src/__tests__/unit/features.controller.unit.ts +++ b/src/__tests__/unit/features.controller.unit.ts @@ -1,20 +1,36 @@ import { + createStubInstance, expect, stubExpressContext, } from '@loopback/testlab'; import { FeaturesController } from '../../controllers/features.controller'; import { FeaturesDataService } from '../../services'; +import { TermsDataService } from '../../services/terms-data.service'; +import { StubbedInstanceWithSinonAccessor} from "@loopback/testlab"; +import { FeaturesMongoDbDataService } from '../../services/features-mongo.service'; +import { TermsMongoDbDataService } from '../../services/terms-mongo.service'; describe('FeaturesController (unit)', () => { - const mockedService = {}; + let mockedService: StubbedInstanceWithSinonAccessor; + let mockedTermsService:StubbedInstanceWithSinonAccessor; + let featuresGetAllStub: sinon.SinonStub; let context = stubExpressContext(); - + mockedService = createStubInstance(FeaturesMongoDbDataService); + mockedTermsService = createStubInstance(TermsMongoDbDataService); + featuresGetAllStub = mockedService.getAll as sinon.SinonStub; describe('get()',() => { it('retrieves the features flags Information', async() => { - const controller = new FeaturesController(context.response, mockedService); - await controller.get(); - let result = await context.result; - expect(result.payload).not.null(); + featuresGetAllStub.resolves([{ + name: 'feature1', value: true, + creationDate: new Date(), + lastUpdateDate: new Date(), + enabled: false, + version: 0, + }]); + mockedTermsService.stubs.getVersion.resolves(); + const controller = new FeaturesController(context.response, mockedService, mockedTermsService); + const response = await controller.get(); + expect(response.statusCode).to.equal(200); }); }); diff --git a/src/controllers/features.controller.ts b/src/controllers/features.controller.ts index 705e5ef..cde4eb7 100644 --- a/src/controllers/features.controller.ts +++ b/src/controllers/features.controller.ts @@ -4,18 +4,23 @@ import { RestBindings, get, getModelSchemaRef, Response, } from '@loopback/rest' import { ServicesBindings } from '../dependency-injection-bindings'; import { FeaturesDataService } from '../services/features-data.service'; import { FeaturesDbDataModel } from '../models/features-data.model'; +import { TermsDataService } from '../services/terms-data.service'; export class FeaturesController { logger: Logger; private featuresDatService: FeaturesDataService; + private termsDatService: TermsDataService; HTTP_SUCCESS_OK = 200; HTTP_ERROR = 500; constructor( @inject(RestBindings.Http.RESPONSE) private response: Response, @inject(ServicesBindings.FEATURES_SERVICE) featuresDatService: FeaturesDataService, + @inject(ServicesBindings.TERMS_SERVICE) + termsDatService: TermsDataService, ) { this.featuresDatService = featuresDatService; + this.termsDatService = termsDatService; this.logger = getLogger('features-controller'); } @@ -50,19 +55,34 @@ export class FeaturesController { }, }) public async get(): Promise { - this.logger.debug('[get] started'); - let retorno = [new FeaturesDbDataModel()]; - let responseCode = this.HTTP_ERROR; - try { - retorno = await this.featuresDatService.getAll(); + return new Promise((resolve) => { + this.logger.debug('[get] started'); + let features = [new FeaturesDbDataModel()]; + let responseCode = this.HTTP_ERROR; + this.featuresDatService.getAll() + .then((featuresFromDb) => { + features = featuresFromDb; + const termsIdx = featuresFromDb.findIndex((feature) => feature.name === 'terms_and_conditions'); + if (!termsIdx) { + responseCode = this.HTTP_SUCCESS_OK; + this.response.contentType('application/json').status(responseCode) + .send(features); + resolve(this.response); + } + return Promise.all([this.termsDatService.getVersion(features[termsIdx].version), termsIdx]); + }) + .then(([terms, termsIdx]) => { + features[termsIdx].value = terms ? terms.value : 'Version not found'; + this.logger.info(`[get] Retrieved the features: ${JSON.stringify(features)}`); responseCode = this.HTTP_SUCCESS_OK; - this.logger.info(`[get] Retrieved the features: ${JSON.stringify(retorno)}`); - } catch (e) { - this.logger.warn(`[get] Got an error: ${e}`); - } - this.response.contentType('application/json').status(responseCode).send( - retorno - ); - return this.response; + this.response.contentType('application/json').status(responseCode) + .send(features); + resolve(this.response); + }) + .catch((error) => { + this.logger.warn(`[get] Got an error: ${error}`); + resolve(this.response); + }); + }); } } diff --git a/src/dependency-injection-bindings.ts b/src/dependency-injection-bindings.ts index fecac51..e806619 100644 --- a/src/dependency-injection-bindings.ts +++ b/src/dependency-injection-bindings.ts @@ -38,4 +38,5 @@ export const ServicesBindings = { REGISTER_SERVICE: 'services.RegisterService', FEATURES_SERVICE: 'services.FeaturesDataService', FLYOVER_SERVICE: 'services.FlyoverService', + TERMS_SERVICE: 'services.TermsDataService', }; diff --git a/src/dependency-injection-handler.ts b/src/dependency-injection-handler.ts index 4df8edc..1b29e04 100644 --- a/src/dependency-injection-handler.ts +++ b/src/dependency-injection-handler.ts @@ -21,6 +21,7 @@ import {RskNodeService} from './services/rsk-node.service'; import {SyncStatusMongoService} from './services/sync-status-mongo.service'; import { PegoutDataProcessor } from './services/pegout-data.processor'; import { FeaturesMongoDbDataService } from './services/features-mongo.service'; +import { TermsMongoDbDataService } from './services/terms-mongo.service'; export class DependencyInjectionHandler { public static configureDependencies(app: Application): void { @@ -155,9 +156,14 @@ export class DependencyInjectionHandler { .toClass(RegisterService) .inScope(BindingScope.SINGLETON); - app - .bind(ServicesBindings.FEATURES_SERVICE) - .toClass(FeaturesMongoDbDataService) - .inScope(BindingScope.SINGLETON); + app + .bind(ServicesBindings.FEATURES_SERVICE) + .toClass(FeaturesMongoDbDataService) + .inScope(BindingScope.SINGLETON); + + app + .bind(ServicesBindings.TERMS_SERVICE) + .toClass(TermsMongoDbDataService) + .inScope(BindingScope.SINGLETON); } } diff --git a/src/models/features-data.model.ts b/src/models/features-data.model.ts index a07fc33..2c3cd33 100644 --- a/src/models/features-data.model.ts +++ b/src/models/features-data.model.ts @@ -5,7 +5,7 @@ export interface FeaturesDataModel { creationDate: Date; lastUpdateDate: Date; name: string; - value: string; + enabled: boolean; version: number; } @@ -17,7 +17,7 @@ export class FeaturesAppDataModel implements FeaturesDataModel{ creationDate: Date; lastUpdateDate: Date; name: string; - value: string; + enabled: boolean; version: number; } @@ -42,15 +42,18 @@ export class FeaturesDbDataModel implements SearchableModel, FeaturesDataModel { name: string; @property({ - type: 'string', + type: 'boolean', }) - value: string; + enabled: boolean; @property({ type: 'number', }) version: number; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [prop: string]: any; + getId() { return this.name; } @@ -58,13 +61,14 @@ export class FeaturesDbDataModel implements SearchableModel, FeaturesDataModel { return 'name'; } - public static clone(other: FeaturesDbDataModel): FeaturesDbDataModel { + public static clone(other:FeaturesDbDataModel): FeaturesDbDataModel { const features: FeaturesDbDataModel = new FeaturesDbDataModel(); features.creationDate = other.creationDate; features.lastUpdateDate = other.lastUpdateDate; features.name = other.name; features.value = other.value; features.version = other.version; + features.enabled = other.enabled; return features; } diff --git a/src/models/index.ts b/src/models/index.ts index 088a3de..7ebc80c 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -19,3 +19,4 @@ export * from './fee-amount.model'; export * from './register-payload.model'; export * from './features.model'; export * from './pegout-status.model'; +export * from './terms-db-data.model'; diff --git a/src/models/terms-db-data.model.ts b/src/models/terms-db-data.model.ts new file mode 100644 index 0000000..aa312e1 --- /dev/null +++ b/src/models/terms-db-data.model.ts @@ -0,0 +1,31 @@ +import { model, property} from '@loopback/repository'; +import { SearchableModel } from "./rsk/searchable-model"; + +@model() +export class TermsDbDataModel implements SearchableModel { + + @property({ + type: 'number', + required: true, + }) + version: number; + + @property({ + type: 'string', + required: true, + }) + value: string; + + + constructor(data?: Partial) { + Object.assign(this, data);; + } + + getId() { + return this.version; + } + // eslint-disable-next-line class-methods-use-this + getIdFieldName(): string { + return "version"; + } +} diff --git a/src/services/features-mongo.service.ts b/src/services/features-mongo.service.ts index 57b8ce2..335792b 100644 --- a/src/services/features-mongo.service.ts +++ b/src/services/features-mongo.service.ts @@ -15,7 +15,7 @@ const FeaturesSchema = new mongoose.Schema({ creationDate: {type: Date}, lastUpdateDate: {type: Date}, name: {type: String, required: true}, - value: {type: String, required: true}, + enabled: {type: Boolean, required: true}, version: {type: Number, required: true}, }); diff --git a/src/services/terms-data.service.ts b/src/services/terms-data.service.ts new file mode 100644 index 0000000..5059ee7 --- /dev/null +++ b/src/services/terms-data.service.ts @@ -0,0 +1,6 @@ +import { GenericDataService } from './generic-data-service'; +import { TermsDbDataModel } from '../models'; + +export interface TermsDataService extends GenericDataService { + getVersion(version: number): Promise; +} diff --git a/src/services/terms-mongo.service.ts b/src/services/terms-mongo.service.ts new file mode 100644 index 0000000..a25b3d4 --- /dev/null +++ b/src/services/terms-mongo.service.ts @@ -0,0 +1,44 @@ +/* eslint-disable @typescript-eslint/no-floating-promises */ +import mongoose from 'mongoose'; +import {MongoDbDataService} from './mongodb-data.service'; +import { TermsDbDataModel } from '../models'; +import { TermsDataService } from './terms-data.service'; + +/* +- THESE MODEL INTERFACES AND CLASSES ARE REQUIRED FOR MONGO BUT WE DON'T WANT THEM EXPOSED OUT OF THIS LAYER +*/ +interface TermsMongoModel extends mongoose.Document, TermsDbDataModel { +} + +const TermsSchema = new mongoose.Schema({ + version: {type: Number, required: true}, + value: {type: String, required: true}, +}); + +const TermsConnector = mongoose.model("Terms", TermsSchema); + +export class TermsMongoDbDataService extends MongoDbDataService implements TermsDataService { + protected getByIdFilter(id: any) { + throw new Error('Method not implemented.'); + } + protected getManyFilter(filter?: any) { + throw new Error('Method not implemented.'); + } + protected getLoggerName(): string { + return 'TermsMongoService'; + } + protected getConnector(): mongoose.Model { + this.verifyAndCreateConnectionIfIsNecessary(); + return TermsConnector; + } + async verifyAndCreateConnectionIfIsNecessary() { + await this.ensureConnection(); + } + public async getVersion(version: number): Promise { + const [document] = await this.getConnector() + .find({ version }) + .exec(); + return document; + } + +}