From 6ed5cd7d4fe20696918e717791a064cf19644f93 Mon Sep 17 00:00:00 2001 From: manelcecs Date: Mon, 13 Nov 2023 16:53:22 +0100 Subject: [PATCH] Ref: add types instead of any --- .../categories/category-service.ts | 5 +- .../flow-object/flow-object-service.ts | 11 ++ src/domain-services/flow-object/model.ts | 4 + .../flows/flow-search-service.ts | 183 +++++++++++------- src/domain-services/flows/model.ts | 4 + .../flows/strategy/flow-search-strategy.ts | 6 +- .../impl/flow-object-conditions-strategy.ts | 2 +- .../impl/only-flow-conditions-strategy.ts | 4 +- 8 files changed, 140 insertions(+), 79 deletions(-) create mode 100644 src/domain-services/flow-object/model.ts create mode 100644 src/domain-services/flows/model.ts diff --git a/src/domain-services/categories/category-service.ts b/src/domain-services/categories/category-service.ts index ec19c9f9..61099d94 100644 --- a/src/domain-services/categories/category-service.ts +++ b/src/domain-services/categories/category-service.ts @@ -5,7 +5,6 @@ import { Category } from './graphql/types'; import { InstanceDataOfModel } from '@unocha/hpc-api-core/src/db/util/raw-model'; import { Op } from '@unocha/hpc-api-core/src/db/util/conditions'; -// TODO: add proper type for flowLinks @Service() export class CategoryService { async getCategoriesForFlows( @@ -50,7 +49,7 @@ export class CategoryService { categoriesMap.set(flowId, []); } - const categoriesForFlow = categoriesMap.get(flowId)!; + const categoriesPerFlow = categoriesMap.get(flowId)!; const category = categories.find((cat) => cat.id === catRef.categoryID); @@ -58,7 +57,7 @@ export class CategoryService { throw new Error(`Category with ID ${catRef.categoryID} does not exist`); } - categoriesForFlow.push(this.mapCategoryToFlowCategory(category, catRef)); + categoriesPerFlow.push(this.mapCategoryToFlowCategory(category, catRef)); }); return categoriesMap; diff --git a/src/domain-services/flow-object/flow-object-service.ts b/src/domain-services/flow-object/flow-object-service.ts index c7c23be3..049bd03d 100644 --- a/src/domain-services/flow-object/flow-object-service.ts +++ b/src/domain-services/flow-object/flow-object-service.ts @@ -1,5 +1,6 @@ import { Database } from '@unocha/hpc-api-core/src/db'; import { FlowId } from '@unocha/hpc-api-core/src/db/models/flow'; +import { Op } from '@unocha/hpc-api-core/src/db/util/conditions'; import { Service } from 'typedi'; @Service() @@ -14,4 +15,14 @@ export class FlowObjectService { // Keep only not duplicated flowIDs return [...new Set(flowObjects.map((flowObject) => flowObject.flowID))]; } + + async getFlowObjectByFlowId(models: Database, flowIds: FlowId[]) { + return await models.flowObject.find({ + where: { + flowID: { + [Op.IN]: flowIds, + }, + }, + }); + } } diff --git a/src/domain-services/flow-object/model.ts b/src/domain-services/flow-object/model.ts new file mode 100644 index 00000000..1d666288 --- /dev/null +++ b/src/domain-services/flow-object/model.ts @@ -0,0 +1,4 @@ +import { Database } from '@unocha/hpc-api-core/src/db'; +import { InstanceOfModel } from '@unocha/hpc-api-core/src/db/util/types'; + +export type FlowObject = InstanceOfModel; diff --git a/src/domain-services/flows/flow-search-service.ts b/src/domain-services/flows/flow-search-service.ts index b60f2e89..bbf48472 100644 --- a/src/domain-services/flows/flow-search-service.ts +++ b/src/domain-services/flows/flow-search-service.ts @@ -7,7 +7,11 @@ import { SearchFlowsArgs, SearchFlowsFilters, } from './graphql/args'; -import { FlowParkedParentSource, FlowSearchResult } from './graphql/types'; +import { + FlowPaged, + FlowParkedParentSource, + FlowSearchResult, +} from './graphql/types'; import { FlowSearchStrategy } from './strategy/flow-search-strategy'; import { OnlyFlowFiltersStrategy } from './strategy/impl/only-flow-conditions-strategy'; import { FlowObjectFiltersStrategy } from './strategy/impl/flow-object-conditions-strategy'; @@ -20,6 +24,14 @@ import { PlanService } from '../plans/plan-service'; import { ReportDetailService } from '../report-details/report-detail-service'; import { UsageYearService } from '../usage-years/usage-year-service'; import { FlowLinkService } from '../flow-link/flow-link-service'; +import { FlowObject } from '../flow-object/model'; +import { FlowObjectService } from '../flow-object/flow-object-service'; +import { FlowEntity } from './model'; +import { Category } from '../categories/graphql/types'; +import { Organization } from '../organizations/graphql/types'; +import { BaseLocation } from '../location/graphql/types'; +import { BasePlan } from '../plans/graphql/types'; +import { UsageYear } from '../usage-years/grpahql/types'; @Service() export class FlowSearchService { @@ -33,7 +45,8 @@ export class FlowSearchService { private readonly categoryService: CategoryService, private readonly flowLinkService: FlowLinkService, private readonly externalReferenceService: ExternalReferenceService, - private readonly reportDetailService: ReportDetailService + private readonly reportDetailService: ReportDetailService, + private readonly flowObjectService: FlowObjectService ) {} async search( @@ -42,25 +55,6 @@ export class FlowSearchService { ): Promise { const { limit, afterCursor, beforeCursor, sortField, sortOrder } = filters; - if (beforeCursor && afterCursor) { - throw new Error('Cannot use before and after cursor at the same time'); - } - - let cursorCondition; - if (afterCursor) { - cursorCondition = { - id: { - [Op.GT]: createBrandedValue(afterCursor), - }, - }; - } else if (beforeCursor) { - cursorCondition = { - id: { - [Op.LT]: createBrandedValue(beforeCursor), - }, - }; - } - const orderBy = { column: sortField ?? 'updatedAt', order: sortOrder ?? 'desc', @@ -68,15 +62,21 @@ export class FlowSearchService { const { flowFilters, flowObjectFilters } = filters; + const cursorCondition = this.buildCursorCondition( + beforeCursor, + afterCursor + ); + + // Determine strategy of how to search for flows const { strategy, conditions } = this.determineStrategy( flowFilters, - flowObjectFilters, - cursorCondition + flowObjectFilters ); // Fetch one more item to check for hasNextPage const limitComputed = limit + 1; + // Obtain flows and its count based on the strategy selected const { flows, count } = await strategy.search( conditions, orderBy, @@ -93,31 +93,36 @@ export class FlowSearchService { const flowIds: FlowId[] = flows.map((flow) => flow.id); - const organizationsFO: any[] = []; - const locationsFO: any[] = []; - const plansFO: any[] = []; - const usageYearsFO: any[] = []; - - const [externalReferencesMap] = await Promise.all([ + // Obtain external references and flow objects in parallel + const [externalReferencesMap, flowObjects] = await Promise.all([ this.externalReferenceService.getExternalReferencesForFlows( flowIds, models ), - this.getFlowObjects( - flowIds, - models, - organizationsFO, - locationsFO, - plansFO, - usageYearsFO - ), + this.flowObjectService.getFlowObjectByFlowId(models, flowIds), ]); + // Map flow objects to their respective arrays + const organizationsFO: FlowObject[] = []; + const locationsFO: FlowObject[] = []; + const plansFO: FlowObject[] = []; + const usageYearsFO: FlowObject[] = []; + + this.mapFlowObjects( + flowObjects, + organizationsFO, + locationsFO, + plansFO, + usageYearsFO + ); + + // Obtain flow links const flowLinksMap = await this.flowLinkService.getFlowLinksForFlows( flowIds, models ); + // Perform all nested queries in parallel const [ categoriesMap, organizationsMap, @@ -171,16 +176,8 @@ export class FlowSearchService { ) .map((flowLink) => flowLink.parentID.valueOf()) as number[]; - return { - // Mandatory fields - id: flow.id.valueOf(), - versionID: flow.versionID, - amountUSD: flow.amountUSD.toString(), - createdAt: flow.createdAt.toISOString(), - updatedAt: flow.updatedAt.toISOString(), - activeStatus: flow.activeStatus, - restricted: flow.restricted, - // Optional fields + return this.buildFlowDTO( + flow, categories, organizations, locations, @@ -188,15 +185,10 @@ export class FlowSearchService { usageYears, childIDs, parentIDs, - origAmount: flow.origAmount ? flow.origAmount.toString() : '', - origCurrency: flow.origCurrency ? flow.origCurrency.toString() : '', externalReferences, reportDetails, - parkedParentSource: - parkedParentSource.length > 0 ? parkedParentSource : null, - // Paged item field - cursor: flow.id.valueOf(), - }; + parkedParentSource + ); }) ); @@ -260,9 +252,9 @@ export class FlowSearchService { determineStrategy( flowFilters: SearchFlowsFilters, - flowObjectFilters: FlowObjectFilters[], - conditions: any + flowObjectFilters: FlowObjectFilters[] ): { strategy: FlowSearchStrategy; conditions: any } { + let conditions = {}; if ( (!flowFilters && (!flowObjectFilters || flowObjectFilters.length === 0)) || @@ -310,22 +302,13 @@ export class FlowSearchService { conditionsMap.set('flow', flowConditions); return conditionsMap; } - private async getFlowObjects( - flowIds: FlowId[], - models: Database, + private mapFlowObjects( + flowObjects: FlowObject[], organizationsFO: any[], locationsFO: any[], plansFO: any[], usageYearsFO: any[] - ): Promise { - const flowObjects = await models.flowObject.find({ - where: { - flowID: { - [Op.IN]: flowIds, - }, - }, - }); - + ) { flowObjects.forEach((flowObject) => { if (flowObject.objectType === 'organization') { organizationsFO.push(flowObject); @@ -381,4 +364,68 @@ export class FlowSearchService { return parentFlows; } + + private buildCursorCondition(beforeCursor: number, afterCursor: number) { + if (beforeCursor && afterCursor) { + throw new Error('Cannot use before and after cursor at the same time'); + } + + let cursorCondition; + if (afterCursor) { + cursorCondition = { + id: { + [Op.GT]: createBrandedValue(afterCursor), + }, + }; + } else if (beforeCursor) { + cursorCondition = { + id: { + [Op.LT]: createBrandedValue(beforeCursor), + }, + }; + } + + return cursorCondition; + } + + private buildFlowDTO( + flow: FlowEntity, + categories: Category[], + organizations: Organization[], + locations: BaseLocation[], + plans: BasePlan[], + usageYears: UsageYear[], + childIDs: number[], + parentIDs: number[], + externalReferences: any[], + reportDetails: any[], + parkedParentSource: FlowParkedParentSource[] + ): FlowPaged { + return { + // Mandatory fields + id: flow.id.valueOf(), + versionID: flow.versionID, + amountUSD: flow.amountUSD.toString(), + createdAt: flow.createdAt.toISOString(), + updatedAt: flow.updatedAt.toISOString(), + activeStatus: flow.activeStatus, + restricted: flow.restricted, + // Optional fields + categories, + organizations, + locations, + plans, + usageYears, + childIDs, + parentIDs, + origAmount: flow.origAmount ? flow.origAmount.toString() : '', + origCurrency: flow.origCurrency ? flow.origCurrency.toString() : '', + externalReferences, + reportDetails, + parkedParentSource: + parkedParentSource.length > 0 ? parkedParentSource : null, + // Paged item field + cursor: flow.id.valueOf(), + }; + } } diff --git a/src/domain-services/flows/model.ts b/src/domain-services/flows/model.ts new file mode 100644 index 00000000..b5d35436 --- /dev/null +++ b/src/domain-services/flows/model.ts @@ -0,0 +1,4 @@ +import { Database } from '@unocha/hpc-api-core/src/db'; +import { InstanceDataOfModel } from '@unocha/hpc-api-core/src/db/util/raw-model'; + +export type FlowEntity = InstanceDataOfModel; diff --git a/src/domain-services/flows/strategy/flow-search-strategy.ts b/src/domain-services/flows/strategy/flow-search-strategy.ts index 76f8b6a3..cd15f90e 100644 --- a/src/domain-services/flows/strategy/flow-search-strategy.ts +++ b/src/domain-services/flows/strategy/flow-search-strategy.ts @@ -1,10 +1,8 @@ import { Database } from '@unocha/hpc-api-core/src/db'; -import { FlowSearchResult } from '../graphql/types'; -import { InstanceOfModel } from '@unocha/hpc-api-core/src/db/util/types'; -import { InstanceDataOfModel } from '@unocha/hpc-api-core/src/db/util/raw-model'; +import { FlowEntity } from '../model'; export interface FlowSearchStrategyResponse { - flows: InstanceDataOfModel[]; + flows: FlowEntity[]; count: number; } diff --git a/src/domain-services/flows/strategy/impl/flow-object-conditions-strategy.ts b/src/domain-services/flows/strategy/impl/flow-object-conditions-strategy.ts index 6bfcc6ea..3ccb19bd 100644 --- a/src/domain-services/flows/strategy/impl/flow-object-conditions-strategy.ts +++ b/src/domain-services/flows/strategy/impl/flow-object-conditions-strategy.ts @@ -53,7 +53,7 @@ export class FlowObjectFiltersStrategy implements FlowSearchStrategy { // and flow conditions const [flows, countRes] = await Promise.all([ this.flowService.getFlows(models, conditions, orderBy, limit), - this.flowService.getFlowsCount(models, conditions), + this.flowService.getFlowsCount(models, mergedFlowConditions), ]); // Map count result query to count object diff --git a/src/domain-services/flows/strategy/impl/only-flow-conditions-strategy.ts b/src/domain-services/flows/strategy/impl/only-flow-conditions-strategy.ts index 12f97057..2af8506b 100644 --- a/src/domain-services/flows/strategy/impl/only-flow-conditions-strategy.ts +++ b/src/domain-services/flows/strategy/impl/only-flow-conditions-strategy.ts @@ -1,12 +1,10 @@ import { Database } from '@unocha/hpc-api-core/src/db'; import { Service } from 'typedi'; -import { FlowId } from '@unocha/hpc-api-core/src/db/models/flow'; import { FlowService } from '../../flow-service'; import { FlowSearchStrategy, FlowSearchStrategyResponse, } from '../flow-search-strategy'; -import { Op } from '@unocha/hpc-api-core/src/db/util/conditions'; @Service() export class OnlyFlowFiltersStrategy implements FlowSearchStrategy { @@ -24,7 +22,7 @@ export class OnlyFlowFiltersStrategy implements FlowSearchStrategy { const [flows, countRes] = await Promise.all([ this.flowService.getFlows(models, conditions, orderBy, limit), - this.flowService.getFlowsCount(models, conditions), + this.flowService.getFlowsCount(models, flowConditions), ]); // Map count result query to count object