From 0c1935c0d662e57ce913db44ccb031d2f6ed91e9 Mon Sep 17 00:00:00 2001 From: Alex Braz Date: Tue, 5 Dec 2023 11:09:47 -0300 Subject: [PATCH] adding the endpoint to retrieve the fflags --- .../unit/features.controller.unit.ts | 22 ++++++ src/controllers/features.controller.ts | 68 ++++++++++++++++++ src/dependency-injection-bindings.ts | 1 + src/dependency-injection-handler.ts | 6 ++ src/models/features-data.model.ts | 72 +++++++++++++++++++ src/models/features.model.ts | 56 +++++++++++++++ src/models/index.ts | 1 + src/services/features-data.service.ts | 6 ++ src/services/features-mongo.service.ts | 48 +++++++++++++ src/services/index.ts | 1 + 10 files changed, 281 insertions(+) create mode 100644 src/__tests__/unit/features.controller.unit.ts create mode 100644 src/controllers/features.controller.ts create mode 100644 src/models/features-data.model.ts create mode 100644 src/models/features.model.ts create mode 100644 src/services/features-data.service.ts create mode 100644 src/services/features-mongo.service.ts diff --git a/src/__tests__/unit/features.controller.unit.ts b/src/__tests__/unit/features.controller.unit.ts new file mode 100644 index 00000000..d0e6cb92 --- /dev/null +++ b/src/__tests__/unit/features.controller.unit.ts @@ -0,0 +1,22 @@ +import { + expect, stubExpressContext, + } from '@loopback/testlab'; +import { FeaturesController } from '../../controllers/features.controller'; +import { FeaturesDataService } from '../../services'; + + describe('FeaturesController (unit)', () => { + const mockedService = {}; + let context = stubExpressContext(); + + + 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(); + }); + }); + + }); + \ No newline at end of file diff --git a/src/controllers/features.controller.ts b/src/controllers/features.controller.ts new file mode 100644 index 00000000..c32f1c1a --- /dev/null +++ b/src/controllers/features.controller.ts @@ -0,0 +1,68 @@ +import { inject } from '@loopback/core'; +import { getLogger, Logger } from 'log4js'; +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'; + +export class FeaturesController { + logger: Logger; + private featuresDatService: FeaturesDataService; + HTTP_SUCCESS_OK = 200; + HTTP_ERROR = 500; + constructor( + @inject(RestBindings.Http.RESPONSE) private response: Response, + @inject(ServicesBindings.FEATURES_SERVICE) + featuresDatService: FeaturesDataService, + ) { + this.featuresDatService = featuresDatService; + this.logger = getLogger('features-controller'); + } + + @get('/features', { + responses: { + '200': { + description: 'Get the feature flags info', + content: { + 'application/json': { + schema: { + type: 'array', + items: getModelSchemaRef(FeaturesDbDataModel, { + includeRelations: true, + }), + }, + }, + }, + }, + '500': { + description: 'Could not retrieve the features', + content: { + 'application/json': { + schema: { + type: 'array', + items: getModelSchemaRef(FeaturesDbDataModel, { + includeRelations: true, + }), + }, + }, + }, + }, + }, + }) + public async get(): Promise { + this.logger.debug('[get] started'); + let retorno = [new FeaturesDbDataModel()]; + let responseCode = this.HTTP_ERROR; + try { + retorno = await this.featuresDatService.getAll(); + responseCode = this.HTTP_SUCCESS_OK; + this.logger.info(`[get] Retrieved the features: ${retorno}`); + } catch (e) { + this.logger.warn(`[get] Got an error: ${e}`); + } + this.response.contentType('application/json').status(responseCode).send( + retorno + ); + return this.response; + } +} diff --git a/src/dependency-injection-bindings.ts b/src/dependency-injection-bindings.ts index df0b2bdf..4d5fdfb1 100644 --- a/src/dependency-injection-bindings.ts +++ b/src/dependency-injection-bindings.ts @@ -36,4 +36,5 @@ export const ServicesBindings = { PEGOUT_STATUS_SERVICE: 'services.PegoutStatusService', UTXO_PROVIDER_SERVICE: 'services.UtxoProvider', REGISTER_SERVICE: 'services.RegisterService', + FEATURES_SERVICE: 'services.FeaturesDataService', }; diff --git a/src/dependency-injection-handler.ts b/src/dependency-injection-handler.ts index c257d5f5..4df8edc7 100644 --- a/src/dependency-injection-handler.ts +++ b/src/dependency-injection-handler.ts @@ -20,6 +20,7 @@ import {RskChainSyncService} from './services/rsk-chain-sync.service'; 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'; export class DependencyInjectionHandler { public static configureDependencies(app: Application): void { @@ -153,5 +154,10 @@ export class DependencyInjectionHandler { .bind(ServicesBindings.REGISTER_SERVICE) .toClass(RegisterService) .inScope(BindingScope.SINGLETON); + + app + .bind(ServicesBindings.FEATURES_SERVICE) + .toClass(FeaturesMongoDbDataService) + .inScope(BindingScope.SINGLETON); } } diff --git a/src/models/features-data.model.ts b/src/models/features-data.model.ts new file mode 100644 index 00000000..cedb5f4b --- /dev/null +++ b/src/models/features-data.model.ts @@ -0,0 +1,72 @@ +import { SearchableModel } from "./rsk/searchable-model"; +import {Entity, model, property} from '@loopback/repository'; + +export interface FeaturesDataModel { + creationDate: Date; + lastUpdateDate: Date; + name: string; + value: string; + version: number; +} + +export class FeaturesAppDataModel implements FeaturesDataModel{ + constructor(data?: Partial) { + Object.assign(this, data); + } + + creationDate: Date; + lastUpdateDate: Date; + name: string; + value: string; + version: number; +} + +@model({settings: {strict: false}}) +export class FeaturesDbDataModel implements SearchableModel, FeaturesDataModel { + + @property({ + type: 'date', + defaultFn: 'now' + }) + creationDate: Date; + + @property({ + type: 'date', + defaultFn: 'now' + }) + lastUpdateDate: Date; + + @property({ + type: 'string', + }) + name: string; + + @property({ + type: 'string', + }) + value: string; + + @property({ + type: 'number', + }) + version: number; + + getId() { + return this.name; + } + getIdFieldName(): string { + return 'name'; + } + + 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; + return features; + } + +} + \ No newline at end of file diff --git a/src/models/features.model.ts b/src/models/features.model.ts new file mode 100644 index 00000000..52873d37 --- /dev/null +++ b/src/models/features.model.ts @@ -0,0 +1,56 @@ +import {Entity, model, property} from '@loopback/repository'; + + +export class Features extends Entity { + + @property({ + type: 'date', + defaultFn: 'now' + }) + creationDate: Date; + + @property({ + type: 'date', + defaultFn: 'now' + }) + lastUpdateDate: Date; + + @property({ + type: 'string', + }) + name: string; + + @property({ + type: 'string', + }) + value: string; + + @property({ + type: 'number', + }) + version: number; + + @property({ + type: 'number', + id: true, + generated: true, + }) + id?: number; + + // Define well-known properties here + + // Indexer property to allow additional data + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [prop: string]: any; + + constructor(data?: Partial) { + super(data); + } + getIdFieldName(): string { + return 'id'; + } + getId() { + return this.id; + } + +} \ No newline at end of file diff --git a/src/models/index.ts b/src/models/index.ts index 83dfdae2..f20a3d2c 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -17,3 +17,4 @@ export * from '../models/rsk/sync-status.model'; export *from '../models/btc-last-block.model'; export * from './fee-amount.model'; export * from './register-payload.model'; +export * from './features.model'; diff --git a/src/services/features-data.service.ts b/src/services/features-data.service.ts new file mode 100644 index 00000000..ec8ebcdf --- /dev/null +++ b/src/services/features-data.service.ts @@ -0,0 +1,6 @@ +import { GenericDataService } from './generic-data-service'; +import { FeaturesDbDataModel } from '../models/features-data.model'; + +export interface FeaturesDataService extends GenericDataService { + getAll(): Promise; +} diff --git a/src/services/features-mongo.service.ts b/src/services/features-mongo.service.ts new file mode 100644 index 00000000..f42d09fe --- /dev/null +++ b/src/services/features-mongo.service.ts @@ -0,0 +1,48 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-floating-promises */ +import mongoose from 'mongoose'; +import { FeaturesDataService } from './features-data.service'; +import {FeaturesDbDataModel} from '../models/features-data.model'; +import {MongoDbDataService} from '../services/mongodb-data.service'; + +/* +- THESE MODEL INTERFACES AND CLASSES ARE REQUIRED FOR MONGO BUT WE DON'T WANT THEM EXPOSED OUT OF THIS LAYER +*/ +interface FeaturesMongoModel extends mongoose.Document, FeaturesDbDataModel { +} + +const FeaturesSchema = new mongoose.Schema({ + creationDate: {type: Date}, + lastUpdateDate: {type: Date}, + name: {type: String, required: true}, + value: {type: String, required: true}, + version: {type: Number, required: true}, +}); + +const FeaturesConnector = mongoose.model("Features", FeaturesSchema); + +export class FeaturesMongoDbDataService extends MongoDbDataService implements FeaturesDataService { + protected getByIdFilter(id: any) { + throw new Error('Method not implemented.'); + } + protected getManyFilter(filter?: any) { + throw new Error('Method not implemented.'); + } + protected getLoggerName(): string { + return 'FeaturesMongoService'; + } + protected getConnector(): mongoose.Model { + this.verifyAndCreateConnectionIfIsNecessary(); + return FeaturesConnector; + } + async verifyAndCreateConnectionIfIsNecessary() { + await this.ensureConnection(); + } + public async getAll(): Promise { + const documents = await this.getConnector() + .find({}) + .exec(); + return documents.map(FeaturesDbDataModel.clone); + } + +} diff --git a/src/services/index.ts b/src/services/index.ts index cb53c6c0..47bbdcde 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -13,3 +13,4 @@ export * from './btc-last-block.service'; export * from './sync-status-data.service'; export * from './rsk-node.service'; export * from './register.service'; +export * from './features-data.service';