diff --git a/package.json b/package.json index 92aad302..f84e2bb3 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "lint": "yarn lint-prettier && yarn lint-eslint" }, "dependencies": { - "@unocha/hpc-api-core": "^6.0.0", + "@unocha/hpc-api-core": "https://github.com/UN-OCHA/hpc-api-core.git#7443efb9039a0eab7833ee9fa8393038e1c286a6", "apollo-server-hapi": "^3.12.0", "bunyan": "^1.8.15", "class-validator": "^0.14.0", diff --git a/src/domain-services/categories/category-service.ts b/src/domain-services/categories/category-service.ts index 7d8464f8..ec19c9f9 100644 --- a/src/domain-services/categories/category-service.ts +++ b/src/domain-services/categories/category-service.ts @@ -1,40 +1,46 @@ import { Database } from '@unocha/hpc-api-core/src/db'; -import { Op } from '@unocha/hpc-api-core/src/db/util/conditions'; import { createBrandedValue } from '@unocha/hpc-api-core/src/util/types'; import { Service } from 'typedi'; -import { FlowCategory } from '../flows/graphql/types'; +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( - flowLinks: Map, + flowLinks: Map[]>, models: Database - ): Promise> { + ): Promise> { const flowLinksBrandedIds = []; for (const flowLink of flowLinks.keys()) { flowLinksBrandedIds.push(createBrandedValue(flowLink)); } - const categoriesRef = await models.categoryRef.find({ - where: { - objectID: { - [Op.IN]: flowLinksBrandedIds, - }, - }, - }); + // Group categories by flow ID for easy mapping + const categoriesMap = new Map(); - const categories = await models.category.find({ - where: { - id: { - [Op.IN]: categoriesRef.map((catRef) => catRef.categoryID), + if (flowLinksBrandedIds.length === 0) { + return categoriesMap; + } + + const categoriesRef: InstanceDataOfModel[] = + await models.categoryRef.find({ + where: { + objectID: { + [Op.IN]: flowLinksBrandedIds, + }, }, - }, - }); + }); - // Group categories by flow ID for easy mapping - const categoriesMap = new Map(); + const categories: InstanceDataOfModel[] = + await models.category.find({ + where: { + id: { + [Op.IN]: categoriesRef.map((catRef) => catRef.categoryID), + }, + }, + }); // Populate the map with categories for each flow categoriesRef.forEach((catRef) => { @@ -61,7 +67,7 @@ export class CategoryService { private mapCategoryToFlowCategory( category: InstanceDataOfModel, categoryRef: InstanceDataOfModel - ): FlowCategory { + ): Category { return { id: category.id, name: category.name, diff --git a/src/domain-services/categories/graphql/types.ts b/src/domain-services/categories/graphql/types.ts new file mode 100644 index 00000000..5cf55816 --- /dev/null +++ b/src/domain-services/categories/graphql/types.ts @@ -0,0 +1,44 @@ +import { Field, ObjectType } from 'type-graphql'; +import { BaseType } from '../../../utils/graphql/base-types'; + +@ObjectType() +export class CategoryRef extends BaseType { + @Field({ nullable: false }) + objectID: number; + + @Field({ nullable: false }) + versionID: number; + + @Field({ nullable: false }) + objectType: string; + + @Field({ nullable: false }) + categoryID: number; +} + +@ObjectType() +export class Category extends BaseType { + @Field({ nullable: true }) + id: number; + + @Field({ nullable: false }) + name: string; + + @Field({ nullable: false }) + group: string; + + @Field({ nullable: true }) + description: string; + + @Field({ nullable: true }) + parentID: number; + + @Field({ nullable: true }) + code: string; + + @Field({ nullable: true }) + includeTotals: boolean; + + @Field(() => CategoryRef, { nullable: true }) + categoryRef: CategoryRef; +} diff --git a/src/domain-services/flows/flow-search-service.ts b/src/domain-services/flows/flow-search-service.ts index 37a8d858..005dae56 100644 --- a/src/domain-services/flows/flow-search-service.ts +++ b/src/domain-services/flows/flow-search-service.ts @@ -1,5 +1,9 @@ import { Service } from 'typedi'; -import { FlowSearchResult, FlowSortField } from './graphql/types'; +import { + FlowParkedParentSource, + FlowSearchResult, + FlowSortField, +} from './graphql/types'; import { Database } from '@unocha/hpc-api-core/src/db/type'; import { createBrandedValue } from '@unocha/hpc-api-core/src/util/types'; import { OrganizationService } from '../organizations/organization-service'; @@ -84,7 +88,7 @@ export class FlowSearchService { const count = countRes[0] as { count: number }; - const flowIds = flows.map((flow) => flow.id); + const flowIds: FlowId[] = flows.map((flow) => flow.id); const organizationsFO: any[] = []; const locationsFO: any[] = []; @@ -131,17 +135,23 @@ export class FlowSearchService { ]); const items = flows.map((flow) => { + const flowLink = flowLinksMap.get(flow.id) || []; const categories = categoriesMap.get(flow.id) || []; const organizations = organizationsMap.get(flow.id) || []; - const locations = locationsMap.get(flow.id) || []; + const locations = [...locationsMap.get(flow.id) || []] ; const plans = plansMap.get(flow.id) || []; const usageYears = usageYearsMap.get(flow.id) || []; const externalReferences = externalReferencesMap.get(flow.id) || []; const reportDetails = reportDetailsMap.get(flow.id) || []; - const childIDs: number[] = (flowLinksMap.get(flow.id) || []).map( - (flowLink) => flowLink.childID.valueOf() - ) as number[]; + const parkedParentSource: FlowParkedParentSource[] = []; + if (flow.activeStatus && flowLink.length > 0) { + this.getParketParents(flow, flowLink, models, parkedParentSource); + } + + const childIDs: number[] = flowLinksMap + .get(flow.id) + ?.map((flowLink) => flowLink.childID.valueOf()) as number[]; const parentIDs: number[] = flowLinksMap .get(flow.id) @@ -152,6 +162,7 @@ export class FlowSearchService { 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, @@ -167,7 +178,7 @@ export class FlowSearchService { origCurrency: flow.origCurrency ? flow.origCurrency.toString() : '', externalReferences, reportDetails, - parkedParentSource: 'placeholder', + parkedParentSource, // Paged item field cursor: flow.id.valueOf(), }; @@ -214,4 +225,46 @@ export class FlowSearchService { } }); } + + private async getParketParents( + flow: any, + flowLink: any[], + models: Database, + parkedParentSource: FlowParkedParentSource[] + ): Promise { + const flowLinksDepth0 = flowLink.filter((flowLink) => flowLink.depth === 0); + + const flowLinksParent = flowLinksDepth0.filter( + (flowLink) => flowLink.parentID === flow.id + ); + + const parentFlowIds = flowLinksParent.map((flowLink) => + flowLink.parentID.valueOf() + ); + + const categories = await models.category.find({ + where: { + group: 'flowType', + name: 'parked', + }, + }); + + const categoriesIDs = categories.map((category) => category.id); + + const categoryRef = await models.categoryRef.find({ + where: { + categoryID: { + [Op.IN]: categoriesIDs, + }, + versionID: flow.versionID, + }, + }); + + const parentFlows = flowLinksParent.filter((flowLink) => { + return categoryRef.some( + (categoryRef) => + categoryRef.objectID.valueOf() === flowLink.parentID.valueOf() + ); + }); + } } diff --git a/src/domain-services/flows/graphql/resolver.ts b/src/domain-services/flows/graphql/resolver.ts index 342a0df9..394a8915 100644 --- a/src/domain-services/flows/graphql/resolver.ts +++ b/src/domain-services/flows/graphql/resolver.ts @@ -1,4 +1,4 @@ -import Flow, { FlowSearchResult, FlowSortField } from './types'; +import { FlowPaged, FlowSearchResult, FlowSortField } from './types'; import { Service } from 'typedi'; import { Arg, Args, Ctx, Query, Resolver } from 'type-graphql'; import { FlowSearchService } from '../flow-search-service'; @@ -7,7 +7,7 @@ import { SearchFlowsFilters } from './args'; import { PaginationArgs } from '../../../utils/graphql/pagination'; @Service() -@Resolver(Flow) +@Resolver(FlowPaged) export default class FlowResolver { constructor(private flowSearchService: FlowSearchService) {} diff --git a/src/domain-services/flows/graphql/types.ts b/src/domain-services/flows/graphql/types.ts index 105b9c7a..b36c0113 100644 --- a/src/domain-services/flows/graphql/types.ts +++ b/src/domain-services/flows/graphql/types.ts @@ -1,98 +1,12 @@ import { Field, ObjectType } from 'type-graphql'; -import { ItemPaged, PageInfo } from '../../../utils/graphql/pagination'; - -@ObjectType() -export class FlowCategoryRef { - @Field({ nullable: false }) - objectID: number; - - @Field({ nullable: false }) - versionID: number; - - @Field({ nullable: false }) - objectType: string; - - @Field({ nullable: false }) - categoryID: number; - - @Field({ nullable: false }) - createdAt: string; - - @Field({ nullable: false }) - updatedAt: string; -} - -@ObjectType() -export class FlowCategory { - @Field({ nullable: true }) - id: number; - - @Field({ nullable: false }) - name: string; - - @Field({ nullable: false }) - group: string; - - @Field({ nullable: true }) - createdAt: string; - - @Field({ nullable: true }) - updatedAt: string; - - @Field({ nullable: true }) - description: string; - - @Field({ nullable: true }) - parentID: number; - - @Field({ nullable: true }) - code: string; - - @Field({ nullable: true }) - includeTotals: boolean; - - @Field(() => FlowCategoryRef, { nullable: true }) - categoryRef: FlowCategoryRef; -} - -@ObjectType() -export class FlowOrganization { - @Field({ nullable: false }) - id: number; - - @Field({ nullable: false }) - refDirection: string; - - @Field({ nullable: false }) - name: string; -} - -@ObjectType() -export class FlowLocation { - @Field({ nullable: false }) - id: number; - - @Field({ nullable: false }) - name: string; -} - -@ObjectType() -export class FlowPlan { - @Field({ nullable: false }) - id: number; - - @Field({ nullable: false }) - name: string; -} - -@ObjectType() -export class FlowUsageYear { - @Field({ nullable: false }) - year: string; - - @Field({ nullable: false }) - direction: string; -} +import { IItemPaged, PageInfo } from '../../../utils/graphql/pagination'; +import { Category } from '../../categories/graphql/types'; +import { BaseLocation } from '../../location/graphql/types'; +import { Organization } from '../../organizations/graphql/types'; +import { BasePlan } from '../../plans/graphql/types'; +import { ReportDetail } from '../../report-details/graphql/types'; +import { UsageYear } from '../../usage-years/grpahql/types'; +import { BaseType } from '../../../utils/graphql/base-types'; @ObjectType() export class FlowExternalReference { @@ -119,47 +33,16 @@ export class FlowExternalReference { } @ObjectType() -export class FlowReportDetail { - @Field({ nullable: false }) - id: number; - - @Field({ nullable: false }) - flowID: number; +export class FlowParkedParentSource { + @Field(() => [Number], { nullable: false }) + organization: number[]; - @Field({ nullable: false }) - versionID: number; - - @Field({ nullable: false }) - contactInfo: string; - - @Field({ nullable: false }) - source: string; - - @Field({ nullable: false }) - date: string; - - @Field({ nullable: false }) - sourceID: string; - - @Field({ nullable: false }) - refCode: string; - - @Field({ nullable: false }) - verified: boolean; - - @Field({ nullable: false }) - createdAt: string; - - @Field({ nullable: false }) - updatedAt: string; - - @Field({ nullable: false }) - organizationID: number; + @Field(() => [String], { nullable: false }) + orgName: string[]; } @ObjectType() -export default class Flow extends ItemPaged { - // Mandatory fields +export class BaseFlow extends BaseType { @Field({ nullable: false }) id: number; @@ -169,30 +52,29 @@ export default class Flow extends ItemPaged { @Field({ nullable: false }) amountUSD: string; - @Field({ nullable: false }) - updatedAt: string; - @Field({ nullable: false }) activeStatus: boolean; @Field({ nullable: false }) restricted: boolean; +} - // Optional fields - @Field(() => [FlowCategory], { nullable: true }) - categories: FlowCategory[]; +@ObjectType() +export class Flow extends BaseFlow { + @Field(() => [Category], { nullable: true }) + categories: Category[]; - @Field(() => [FlowOrganization], { nullable: true }) - organizations: FlowOrganization[]; + @Field(() => [Organization], { nullable: true }) + organizations: Organization[]; - @Field(() => [FlowPlan], { nullable: true }) - plans: FlowPlan[]; + @Field(() => [BasePlan], { nullable: true }) + plans: BasePlan[]; - @Field(() => [FlowLocation], { nullable: true }) - locations: FlowLocation[]; + @Field(() => [BaseLocation], { nullable: true }) + locations: BaseLocation[]; - @Field(() => [FlowUsageYear], { nullable: true }) - usageYears: FlowUsageYear[]; + @Field(() => [UsageYear], { nullable: true }) + usageYears: UsageYear[]; @Field(() => [Number], { nullable: true }) childIDs: number[]; @@ -209,18 +91,23 @@ export default class Flow extends ItemPaged { @Field(() => [FlowExternalReference], { nullable: true }) externalReferences: FlowExternalReference[]; - @Field(() => [FlowReportDetail], { nullable: true }) - reportDetails: FlowReportDetail[]; + @Field(() => [ReportDetail], { nullable: true }) + reportDetails: ReportDetail[]; - // Missing fields & new Types - @Field({ nullable: true }) - parkedParentSource: string; + @Field(() => [FlowParkedParentSource], { nullable: true }) + parkedParentSource: FlowParkedParentSource[]; +} + +@ObjectType() +export class FlowPaged extends Flow implements IItemPaged { + @Field({ nullable: false }) + cursor: number; } @ObjectType() export class FlowSearchResult extends PageInfo { - @Field(() => [Flow], { nullable: false }) - flows: Flow[]; + @Field(() => [FlowPaged], { nullable: false }) + flows: FlowPaged[]; } export type FlowSortField = @@ -230,7 +117,6 @@ export type FlowSortField = | 'updatedAt' | 'activeStatus' | 'restricted' - // | 'newMoney' | 'flowDate' | 'decisionDate' diff --git a/src/domain-services/location/graphql/types.ts b/src/domain-services/location/graphql/types.ts index f6a9bca5..76f10cf3 100644 --- a/src/domain-services/location/graphql/types.ts +++ b/src/domain-services/location/graphql/types.ts @@ -1,4 +1,4 @@ -import { BaseType } from '../../base-types'; +import { BaseType } from '../../../utils/graphql/base-types'; import { Brand } from '@unocha/hpc-api-core/src/util/types'; import { MaxLength } from 'class-validator'; import { Field, ID, Int, ObjectType, registerEnumType } from 'type-graphql'; @@ -53,3 +53,15 @@ export default class Location extends BaseType { @Field({ defaultValue: true }) itosSync: boolean; // Accidentally optional } + +@ObjectType() +export class BaseLocation extends BaseType { + @Field({ nullable: false }) + id: number; + + @Field(() => String, { nullable: false }) + name: string | null; + + @Field({ nullable: false }) + direction: string; +} diff --git a/src/domain-services/location/location-service.ts b/src/domain-services/location/location-service.ts index c46a8b6d..d6118bb8 100644 --- a/src/domain-services/location/location-service.ts +++ b/src/domain-services/location/location-service.ts @@ -2,8 +2,9 @@ import { Database } from '@unocha/hpc-api-core/src/db/type'; import { Service } from 'typedi'; import { createBrandedValue } from '@unocha/hpc-api-core/src/util/types'; import { InstanceDataOfModel } from '@unocha/hpc-api-core/src/db/util/raw-model'; -import { FlowLocation } from '../flows/graphql/types'; +import { BaseLocation } from './graphql/types'; import { Op } from '@unocha/hpc-api-core/src/db/util/conditions'; +import { LocationId } from '@unocha/hpc-api-core/src/db/models/location'; @Service() export class LocationService { @@ -30,39 +31,50 @@ export class LocationService { } async getLocationsForFlows( - locationsFO: any[], + locationsFO: InstanceDataOfModel[], models: Database - ): Promise> { - const locations = await models.location.find({ - where: { - id: { - [Op.IN]: locationsFO.map((locFO) => locFO.objectID), + ): Promise>> { + const locationObjectsIDs: LocationId[] = locationsFO.map((locFO) => + createBrandedValue(locFO.objectID) + ); + + const locations: InstanceDataOfModel[] = + await models.location.find({ + where: { + id: { + [Op.IN]: locationObjectsIDs, + }, }, - }, - }); + }); - const locationsMap = new Map(); + const locationsMap = new Map>(); locationsFO.forEach((locFO) => { const flowId = locFO.flowID; if (!locationsMap.has(flowId)) { - locationsMap.set(flowId, []); + locationsMap.set(flowId, new Set()); } const location = locations.find((loc) => loc.id === locFO.objectID); if (!location) { throw new Error(`Location with ID ${locFO.objectID} does not exist`); } - const locationMapped = this.mapLocationsToFlowLocations(location); - locationsMap.get(flowId)!.push(locationMapped); + const locationMapped = this.mapLocationsToFlowLocations(location, locFO); + locationsMap.get(flowId)!.add(locationMapped); }); return locationsMap; } - private mapLocationsToFlowLocations(location: any) { + private mapLocationsToFlowLocations( + location: InstanceDataOfModel, + locationFO: InstanceDataOfModel + ) { return { id: location.id, name: location.name, + direction: locationFO.refDirection, + createdAt: location.createdAt.toISOString(), + updatedAt: location.updatedAt.toISOString(), }; } } diff --git a/src/domain-services/organizations/graphql/types.ts b/src/domain-services/organizations/graphql/types.ts new file mode 100644 index 00000000..e4a52057 --- /dev/null +++ b/src/domain-services/organizations/graphql/types.ts @@ -0,0 +1,14 @@ +import { Field, ObjectType } from 'type-graphql'; +import { BaseType } from '../../../utils/graphql/base-types'; + +@ObjectType() +export class Organization extends BaseType { + @Field({ nullable: false }) + id: number; + + @Field({ nullable: true }) + direction: string; + + @Field({ nullable: true }) + name: string; +} diff --git a/src/domain-services/organizations/organization-service.ts b/src/domain-services/organizations/organization-service.ts index 215045ff..1bac9e1d 100644 --- a/src/domain-services/organizations/organization-service.ts +++ b/src/domain-services/organizations/organization-service.ts @@ -1,6 +1,7 @@ import { Database } from '@unocha/hpc-api-core/src/db'; import { Service } from 'typedi'; import { Op } from '@unocha/hpc-api-core/src/db/util/conditions'; +import { Organization } from './graphql/types'; @Service() export class OrganizationService { @@ -13,10 +14,11 @@ export class OrganizationService { }, }); - const organizationsMap = new Map(); + const organizationsMap = new Map(); organizationsFO.forEach((orgFO) => { const flowId = orgFO.flowID; + if (!organizationsMap.has(flowId)) { organizationsMap.set(flowId, []); } @@ -29,17 +31,14 @@ export class OrganizationService { `Organization with ID ${orgFO.objectID} does not exist` ); } - organizationsMap.get(flowId)!.push(organization); - }); - organizations.forEach((org) => { - const refDirection = organizationsFO.find( - (orgFO) => orgFO.objectID === org.id - ).refDirection; - - organizationsMap.set( - org.id.valueOf(), - this.mapOrganizationsToOrganizationFlows(org, refDirection) - ); + + const organizationMapped: Organization = + this.mapOrganizationsToOrganizationFlows( + organization, + orgFO.refDirection + ); + + organizationsMap.get(flowId)!.push(organizationMapped); }); return organizationsMap; @@ -48,11 +47,13 @@ export class OrganizationService { private mapOrganizationsToOrganizationFlows( organization: any, refDirection: any - ) { + ): Organization { return { id: organization.id, - refDirection: refDirection, + direction: refDirection, name: organization.name, + createdAt: organization.createdAt.toISOString(), + updatedAt: organization.updatedAt.toISOString(), }; } } diff --git a/src/domain-services/plans/graphql/types.ts b/src/domain-services/plans/graphql/types.ts index c947291d..3316b717 100644 --- a/src/domain-services/plans/graphql/types.ts +++ b/src/domain-services/plans/graphql/types.ts @@ -2,6 +2,7 @@ import { Brand } from '@unocha/hpc-api-core/src/util/types'; import { MaxLength } from 'class-validator'; import { Field, ID, Int, ObjectType } from 'type-graphql'; import PlanTag from '../../plan-tag/graphql/types'; +import { BaseType } from '../../../utils/graphql/base-types'; @ObjectType() export class PlanCaseload { @@ -95,3 +96,15 @@ export default class Plan { @Field(() => [PlanTag]) tags: PlanTag[]; } + +@ObjectType() +export class BasePlan extends BaseType { + @Field({ nullable: false }) + id: number; + + @Field({ nullable: false }) + name: string; + + @Field({ nullable: false }) + direction: string; +} diff --git a/src/domain-services/plans/plan-service.ts b/src/domain-services/plans/plan-service.ts index 33430d04..c8748560 100644 --- a/src/domain-services/plans/plan-service.ts +++ b/src/domain-services/plans/plan-service.ts @@ -4,7 +4,8 @@ import { Op } from '@unocha/hpc-api-core/src/db/util/conditions'; import { NotFoundError } from '@unocha/hpc-api-core/src/util/error'; import { createBrandedValue } from '@unocha/hpc-api-core/src/util/types'; import { Service } from 'typedi'; -import { FlowPlan } from '../flows/graphql/types'; +import { BasePlan } from './graphql/types'; +import { InstanceDataOfModel } from '@unocha/hpc-api-core/src/db/util/raw-model'; @Service() export class PlanService { @@ -48,18 +49,22 @@ export class PlanService { } async getPlansForFlows( - plansFO: any[], + plansFO: InstanceDataOfModel[], models: Database - ): Promise> { - const plans = await models.plan.find({ - where: { - id: { - [Op.IN]: plansFO.map((planFO) => planFO.objectID), + ): Promise> { + const planObjectsIDs: PlanId[] = plansFO.map((planFO) => + createBrandedValue(planFO.objectID) + ); + const plans: InstanceDataOfModel[] = + await models.plan.find({ + where: { + id: { + [Op.IN]: planObjectsIDs, + }, }, - }, - }); + }); - const plansMap = new Map(); + const plansMap = new Map(); for (const plan of plans) { const planVersion = await models.planVersion.find({ @@ -69,22 +74,37 @@ export class PlanService { }, }); - const planMapped = { - id: plan.id.valueOf(), - name: planVersion[0].name, - }; - - const flowId = plansFO.find( + + const planFlowOobject = plansFO.find( (planFO) => planFO.objectID === plan.id - ).flowID; - - if (!plansMap.has(flowId)) { - plansMap.set(flowId, []); + ); + + const flowId = planFlowOobject && planFlowOobject.flowID; + + const planMapped = this.mapPlansToFlowPlans(plan, planVersion[0], planFlowOobject?.refDirection || null); + + if (flowId) { + if (!plansMap.has(flowId)) { + plansMap.set(flowId, []); + } + + plansMap.get(flowId)!.push(planMapped); } - - plansMap.get(flowId)!.push(planMapped); } return plansMap; } + + private mapPlansToFlowPlans( + plan: InstanceDataOfModel, + planVersion: InstanceDataOfModel, + direction: string | null): BasePlan { + return { + id: plan.id.valueOf(), + name: planVersion.name, + createdAt: plan.createdAt.toISOString(), + updatedAt: plan.updatedAt.toISOString(), + direction: direction ?? '', + }; + } } diff --git a/src/domain-services/report-details/graphql/types.ts b/src/domain-services/report-details/graphql/types.ts new file mode 100644 index 00000000..3d4ddfa9 --- /dev/null +++ b/src/domain-services/report-details/graphql/types.ts @@ -0,0 +1,35 @@ +import { Field, ObjectType } from 'type-graphql'; +import { BaseType } from '../../../utils/graphql/base-types'; + +@ObjectType() +export class ReportDetail extends BaseType { + @Field({ nullable: false }) + id: number; + + @Field({ nullable: false }) + flowID: number; + + @Field({ nullable: false }) + versionID: number; + + @Field(() => String, { nullable: true }) + contactInfo: string | null; + + @Field({ nullable: false }) + source: string; + + @Field(() => String, { nullable: true }) + date: string | null; + + @Field(() => String, { nullable: true }) + sourceID: string | null; + + @Field(() => String, { nullable: true }) + refCode: string | null; + + @Field({ nullable: false }) + verified: boolean; + + @Field(() => Number, { nullable: true }) + organizationID: number | null; +} diff --git a/src/domain-services/report-details/report-detail-service.ts b/src/domain-services/report-details/report-detail-service.ts index ce347b2b..70b8b015 100644 --- a/src/domain-services/report-details/report-detail-service.ts +++ b/src/domain-services/report-details/report-detail-service.ts @@ -3,13 +3,13 @@ import { FlowId } from '@unocha/hpc-api-core/src/db/models/flow'; import { Op } from '@unocha/hpc-api-core/src/db/util/conditions'; import { InstanceDataOfModel } from '@unocha/hpc-api-core/src/db/util/raw-model'; import { Service } from 'typedi'; -import { FlowReportDetail } from '../flows/graphql/types'; +import { ReportDetail } from './graphql/types'; @Service() export class ReportDetailService { async getReportDetailsForFlows( flowIds: FlowId[], models: Database - ): Promise> { + ): Promise> { const reportDetails: InstanceDataOfModel[] = await models.reportDetail.find({ where: { @@ -20,7 +20,7 @@ export class ReportDetailService { skipValidation: true, }); - const reportDetailsMap = new Map(); + const reportDetailsMap = new Map(); flowIds.forEach((flowId: FlowId) => { if (!reportDetailsMap.has(flowId)) { @@ -41,21 +41,21 @@ export class ReportDetailService { } private mapReportDetailsToFlowReportDetail( - reportDetail: any - ): FlowReportDetail { + reportDetail: InstanceDataOfModel + ): ReportDetail { return { id: reportDetail.id, - flowID: reportDetail.flowId, + flowID: reportDetail.flowID, versionID: reportDetail.versionID, contactInfo: reportDetail.contactInfo, source: reportDetail.source, - date: reportDetail.date.toISOString(), + date: reportDetail.date, sourceID: reportDetail.sourceID, refCode: reportDetail.refCode, verified: reportDetail.verified, createdAt: reportDetail.createdAt.toISOString(), updatedAt: reportDetail.updatedAt.toISOString(), - organizationID: reportDetail.organizationId, + organizationID: reportDetail.organizationID, }; } } diff --git a/src/domain-services/usage-years/grpahql/types.ts b/src/domain-services/usage-years/grpahql/types.ts new file mode 100644 index 00000000..470e06b3 --- /dev/null +++ b/src/domain-services/usage-years/grpahql/types.ts @@ -0,0 +1,11 @@ +import { Field, ObjectType } from 'type-graphql'; +import { BaseType } from '../../../utils/graphql/base-types'; + +@ObjectType() +export class UsageYear extends BaseType { + @Field({ nullable: false }) + year: string; + + @Field({ nullable: false }) + direction: string; +} diff --git a/src/domain-services/usage-years/usage-year-service.ts b/src/domain-services/usage-years/usage-year-service.ts index e09197d2..bb74c9a6 100644 --- a/src/domain-services/usage-years/usage-year-service.ts +++ b/src/domain-services/usage-years/usage-year-service.ts @@ -1,23 +1,25 @@ import { Database } from '@unocha/hpc-api-core/src/db'; import { Op } from '@unocha/hpc-api-core/src/db/util/conditions'; import { Service } from 'typedi'; -import { FlowUsageYear } from '../flows/graphql/types'; +import { UsageYear } from './grpahql/types'; +import { InstanceDataOfModel } from '@unocha/hpc-api-core/src/db/util/raw-model'; @Service() export class UsageYearService { async getUsageYearsForFlows( usageYearsFO: any[], models: Database - ): Promise> { - const usageYears = await models.usageYear.find({ - where: { - id: { - [Op.IN]: usageYearsFO.map((usageYearFO) => usageYearFO.objectID), + ): Promise> { + const usageYears: InstanceDataOfModel[] = + await models.usageYear.find({ + where: { + id: { + [Op.IN]: usageYearsFO.map((usageYearFO) => usageYearFO.objectID), + }, }, - }, - }); + }); - const usageYearsMap = new Map(); + const usageYearsMap = new Map(); usageYearsFO.forEach((usageYearFO) => { const flowId = usageYearFO.flowID; @@ -25,7 +27,7 @@ export class UsageYearService { usageYearsMap.set(flowId, []); } const usageYear = usageYears.find( - (usageYear) => usageYear.id === usageYearFO.objectID + (uYear) => uYear.id === usageYearFO.objectID ); if (!usageYear) { @@ -47,6 +49,8 @@ export class UsageYearService { return { year: usageYear.year, direction: refDirection, + createdAt: usageYear.createdAt, + updatedAt: usageYear.updatedAt, }; } } diff --git a/src/domain-services/base-types.ts b/src/utils/graphql/base-types.ts similarity index 61% rename from src/domain-services/base-types.ts rename to src/utils/graphql/base-types.ts index 24db78d8..749bc046 100644 --- a/src/domain-services/base-types.ts +++ b/src/utils/graphql/base-types.ts @@ -2,11 +2,11 @@ import { Field, ObjectType } from 'type-graphql'; @ObjectType() export class BaseType { - @Field() - createdAt: Date; + @Field(() => String, { nullable: false }) + createdAt: string; - @Field() - updatedAt: Date; + @Field(() => String, { nullable: false }) + updatedAt: string; } @ObjectType() diff --git a/src/utils/graphql/pagination.ts b/src/utils/graphql/pagination.ts index 650ddaba..c4765253 100644 --- a/src/utils/graphql/pagination.ts +++ b/src/utils/graphql/pagination.ts @@ -8,12 +8,6 @@ export interface IItemPaged { cursor: number; } -@ObjectType() -export class ItemPaged implements IItemPaged { - @Field({ nullable: false }) - cursor: number; -} - @ObjectType() export class PageInfo { @Field({ nullable: false }) diff --git a/yarn.lock b/yarn.lock index d5eed099..60e25945 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1416,9 +1416,9 @@ "@types/istanbul-lib-report" "*" "@types/jest@^29.5.5": - version "29.5.5" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.5.tgz#727204e06228fe24373df9bae76b90f3e8236a2a" - integrity sha512-ebylz2hnsWR9mYvmBFbXJXr+33UPc4+ZdxyDXh5w0FlPBTfCVN3wPL+kuOiQt3xvrK419v7XWeAs+AeOksafXg== + version "29.5.7" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.7.tgz#2c0dafe2715dd958a455bc10e2ec3e1ec47b5036" + integrity sha512-HLyetab6KVPSiF+7pFcUyMeLsx25LDNDemw9mGsJBkai/oouwrjTycocSDYopMEwFhN2Y4s9oPyOCZNofgSt2g== dependencies: expect "^29.0.0" pretty-format "^29.0.0" @@ -1428,6 +1428,13 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== +"@types/knex@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@types/knex/-/knex-0.16.1.tgz#619678407265c675463c563ed38323461a49515d" + integrity sha512-54gWD1HWwdVx5iLHaJ1qxH3I6KyBsj5fFqzRpXFn7REWiEB2jwspeVCombNsocSrqPd7IRPqKrsIME7/cD+TFQ== + dependencies: + knex "*" + "@types/lodash@^4.14.194": version "4.14.194" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.194.tgz#b71eb6f7a0ff11bff59fc987134a093029258a76" @@ -1611,11 +1618,11 @@ "@typescript-eslint/types" "5.61.0" eslint-visitor-keys "^3.3.0" -"@unocha/hpc-api-core@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@unocha/hpc-api-core/-/hpc-api-core-6.0.0.tgz#2abf57f103255966de808eb31637543c985ad41c" - integrity sha512-n7CqIdsgWeTu4chX7NTU3SnZxFglZ0HG6awXu7lBCqz+KuVaGb9Er3DjKyKgRNa1qgt3GWNTAzACOehNd5vNNw== +"@unocha/hpc-api-core@https://github.com/UN-OCHA/hpc-api-core.git#7443efb9039a0eab7833ee9fa8393038e1c286a6": + version "6.2.0" + resolved "https://github.com/UN-OCHA/hpc-api-core.git#7443efb9039a0eab7833ee9fa8393038e1c286a6" dependencies: + "@types/knex" "^0.16.1" "@types/lodash" "^4.14.194" "@types/node-fetch" "2.6.3" fp-ts "^2.14.0" @@ -1624,6 +1631,7 @@ lodash "^4.17.21" node-fetch "2.6.9" pg "^8.10.0" + ts-node "^10.9.1" "@unocha/hpc-repo-tools@^3.0.1": version "3.0.1" @@ -2347,7 +2355,7 @@ colorette@1.1.0: resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.1.0.tgz#1f943e5a357fac10b4e0f5aaef3b14cdc1af6ec7" integrity sha512-6S062WDQUXi6hOfkO/sBPVwE5ASXY4G2+b4atvhJfSsuUUhIaUKlkjLe9692Ipyt5/a+IPF5aVTu3V5gvXq5cg== -colorette@^2.0.19: +colorette@2.0.19, colorette@^2.0.19: version "2.0.19" resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== @@ -2488,6 +2496,13 @@ debug@4.1.1: dependencies: ms "^2.1.1" +debug@4.3.4, debug@^4.1.0, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + debug@^2.2.0, debug@^2.3.3: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -2502,13 +2517,6 @@ debug@^3.2.6: dependencies: ms "^2.1.1" -debug@^4.1.0, debug@^4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - debug@^4.1.1: version "4.3.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" @@ -3194,6 +3202,11 @@ getopts@2.2.5: resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.2.5.tgz#67a0fe471cacb9c687d817cab6450b96dde8313b" integrity sha512-9jb7AW5p3in+IiJWhQiZmmwkpLaR/ccTWdWQCtZM66HJcHHLegowh4q4tSD7gouUyeNvFWRavfK9GXosQHDpFA== +getopts@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.3.0.tgz#71e5593284807e03e2427449d4f6712a268666f4" + integrity sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA== + git-node-fs@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/git-node-fs/-/git-node-fs-1.0.0.tgz#49b215e242ebe43aa4c7561bbba499521752080f" @@ -3509,7 +3522,7 @@ ini@^1.3.4, ini@^1.3.5: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== -interpret@^2.0.0: +interpret@^2.0.0, interpret@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== @@ -4258,6 +4271,26 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== +knex@*: + version "3.0.1" + resolved "https://registry.yarnpkg.com/knex/-/knex-3.0.1.tgz#b12f3173c30d8c7b6d69dc257cc9c84db00ad60e" + integrity sha512-ruASxC6xPyDklRdrcDy6a9iqK+R9cGK214aiQa+D9gX2ZnHZKv6o6JC9ZfgxILxVAul4bZ13c3tgOAHSuQ7/9g== + dependencies: + colorette "2.0.19" + commander "^10.0.0" + debug "4.3.4" + escalade "^3.1.1" + esm "^3.2.25" + get-package-type "^0.1.0" + getopts "2.3.0" + interpret "^2.2.0" + lodash "^4.17.21" + pg-connection-string "2.6.1" + rechoir "^0.8.0" + resolve-from "^5.0.0" + tarn "^3.0.2" + tildify "2.0.0" + knex@0.21.1: version "0.21.1" resolved "https://registry.yarnpkg.com/knex/-/knex-0.21.1.tgz#4fba7e6c58c9f459846c3090be157a732fc75e41" @@ -4983,6 +5016,11 @@ pg-connection-string@2.2.0: resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.2.0.tgz#caab4d38a9de4fdc29c9317acceed752897de41c" integrity sha512-xB/+wxcpFipUZOQcSzcgkjcNOosGhEoPSjz06jC89lv1dj7mc9bZv6wLVy8M2fVjP0a/xN0N988YDq1L0FhK3A== +pg-connection-string@2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.1.tgz#78c23c21a35dd116f48e12e23c0965e8d9e2cbfb" + integrity sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg== + pg-connection-string@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34" @@ -5367,6 +5405,13 @@ rechoir@^0.6.2: dependencies: resolve "^1.1.6" +rechoir@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.8.0.tgz#49f866e0d32146142da3ad8f0eff352b3215ff22" + integrity sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ== + dependencies: + resolve "^1.20.0" + reflect-metadata@^0.1.13: version "0.1.13" resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" @@ -5942,6 +5987,11 @@ tarn@^3.0.0: resolved "https://registry.yarnpkg.com/tarn/-/tarn-3.0.1.tgz#ebac2c6dbc6977d34d4526e0a7814200386a8aec" integrity sha512-6usSlV9KyHsspvwu2duKH+FMUhqJnAh6J5J/4MITl8s94iSUQTLkJggdiewKv4RyARQccnigV48Z+khiuVZDJw== +tarn@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/tarn/-/tarn-3.0.2.tgz#73b6140fbb881b71559c4f8bfde3d9a4b3d27693" + integrity sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ== + test-exclude@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e"