diff --git a/apps/backoffice-v2/src/domains/business-reports/fetchers.ts b/apps/backoffice-v2/src/domains/business-reports/fetchers.ts index 5741baf46d..aa6b517cb6 100644 --- a/apps/backoffice-v2/src/domains/business-reports/fetchers.ts +++ b/apps/backoffice-v2/src/domains/business-reports/fetchers.ts @@ -3,12 +3,13 @@ import { apiClient } from '@/common/api-client/api-client'; import { Method } from '@/common/enums'; import { handleZodError } from '@/common/utils/handle-zod-error/handle-zod-error'; -export const BusinessReportSchema = z.object({ - report: z.object({ - reportFileId: z.string(), - }), -}); -export const BusinessReportsSchema = z.array(BusinessReportSchema); +export const BusinessReportSchema = z + .object({ + report: z.object({ + reportFileId: z.string(), + }), + }) + .optional(); export const fetchBusinessReports = async ({ businessId, diff --git a/apps/backoffice-v2/src/lib/blocks/components/AmlBlock/utils/aml-adapter.ts b/apps/backoffice-v2/src/lib/blocks/components/AmlBlock/utils/aml-adapter.ts index 60d3c9c06b..7f40029275 100644 --- a/apps/backoffice-v2/src/lib/blocks/components/AmlBlock/utils/aml-adapter.ts +++ b/apps/backoffice-v2/src/lib/blocks/components/AmlBlock/utils/aml-adapter.ts @@ -6,74 +6,75 @@ const SourceInfoSchema = z.object({ date: z.string().optional().nullable(), }); -const ListingRelatedToMatchSchema = z.object({ - warnings: z.array(SourceInfoSchema).optional().nullable(), - sanctions: z.array(SourceInfoSchema).optional().nullable(), - pep: z.array(SourceInfoSchema).optional().nullable(), - adverseMedia: z.array(SourceInfoSchema).optional().nullable(), -}); - const HitSchema = z.object({ matchedName: z.string().optional().nullable(), dateOfBirth: z.string().optional().nullable(), countries: z.array(z.string()).optional().nullable(), matchTypes: z.array(z.string()).optional().nullable(), aka: z.array(z.string()).optional().nullable(), - listingsRelatedToMatch: ListingRelatedToMatchSchema.optional().nullable(), + warnings: z.array(SourceInfoSchema).optional().nullable(), + sanctions: z.array(SourceInfoSchema).optional().nullable(), + pep: z.array(SourceInfoSchema).optional().nullable(), + adverseMedia: z.array(SourceInfoSchema).optional().nullable(), }); export const AmlSchema = z.object({ hits: z.array(HitSchema).optional().nullable(), createdAt: z.string().optional().nullable(), - totalHits: z.number().optional().nullable(), }); export type TAml = z.output; export const amlAdapter = (aml: TAml) => { - const { hits, totalHits, createdAt, ...rest } = aml; + const { hits, createdAt, ...rest } = aml; return { - totalMatches: totalHits ?? 0, + totalMatches: hits?.length ?? 0, fullReport: rest, dateOfCheck: createdAt, matches: hits?.map( - ({ matchedName, dateOfBirth, countries, matchTypes, aka, listingsRelatedToMatch }) => { - const { sanctions, warnings, pep, adverseMedia } = listingsRelatedToMatch ?? {}; - - return { - matchedName, - dateOfBirth, - countries: countries?.join(', ') ?? '', - matchTypes: matchTypes?.join(', ') ?? '', - aka: aka?.join(', ') ?? '', - sanctions: - sanctions?.map(sanction => ({ - sanction: sanction?.sourceName, - date: sanction?.date, - source: sanction?.sourceUrl, - })) ?? [], - warnings: - warnings?.map(warning => ({ - warning: warning?.sourceName, - date: warning?.date, - source: warning?.sourceUrl, - })) ?? [], - pep: - pep?.map(pepItem => ({ - person: pepItem?.sourceName, - date: pepItem?.date, - source: pepItem?.sourceUrl, - })) ?? [], - adverseMedia: - adverseMedia?.map(adverseMediaItem => ({ - entry: adverseMediaItem?.sourceName, - date: adverseMediaItem?.date, - source: adverseMediaItem?.sourceUrl, - })) ?? [], - }; - }, + ({ + matchedName, + dateOfBirth, + countries, + matchTypes, + aka, + sanctions, + warnings, + pep, + adverseMedia, + }) => ({ + matchedName, + dateOfBirth, + countries: countries?.join(', ') ?? '', + matchTypes: matchTypes?.join(', ') ?? '', + aka: aka?.join(', ') ?? '', + sanctions: + sanctions?.map(sanction => ({ + sanction: sanction?.sourceName, + date: sanction?.date, + source: sanction?.sourceUrl, + })) ?? [], + warnings: + warnings?.map(warning => ({ + warning: warning?.sourceName, + date: warning?.date, + source: warning?.sourceUrl, + })) ?? [], + pep: + pep?.map(pepItem => ({ + person: pepItem?.sourceName, + date: pepItem?.date, + source: pepItem?.sourceUrl, + })) ?? [], + adverseMedia: + adverseMedia?.map(adverseMediaItem => ({ + entry: adverseMediaItem?.sourceName, + date: adverseMediaItem?.date, + source: adverseMediaItem?.sourceUrl, + })) ?? [], + }), ) ?? [], }; }; diff --git a/apps/backoffice-v2/src/pages/Entity/hooks/useEntityLogic/mock-workflow-with-children.ts b/apps/backoffice-v2/src/pages/Entity/hooks/useEntityLogic/mock-workflow-with-children.ts index 0e5d3a76e9..f088ce054b 100644 --- a/apps/backoffice-v2/src/pages/Entity/hooks/useEntityLogic/mock-workflow-with-children.ts +++ b/apps/backoffice-v2/src/pages/Entity/hooks/useEntityLogic/mock-workflow-with-children.ts @@ -650,7 +650,6 @@ export const workflow = { result: { vendorResult: { aml: { - totalHits: 1, createdAt: faker.date.recent().toISOString(), hits: [ { @@ -659,52 +658,49 @@ export const workflow = { countries: ['US', 'GB'], matchTypes: ['year_of_birth', 'full_name', 'last_name'], matchedName: `John Doe`, - listingsRelatedToMatch: { - warnings: [ - { - sourceUrl: - 'http://www.treasury.gov/resource-center/sanctions/SDN-List/Pages/default.aspx', - sourceName: 'FBI Most Wanted', - date: faker.date.recent().toISOString(), - }, - { - sourceUrl: - 'http://www.treasury.gov/resource-center/sanctions/SDN-List/Pages/default.aspx', - sourceName: 'FBI Most Wanted', - date: faker.date.recent().toISOString(), - }, - ], - sanctions: [ - { - sourceUrl: - 'http://www.treasury.gov/resource-center/sanctions/SDN-List/Pages/default.aspx', - sourceName: 'OFAC SDN List', - date: faker.date.recent().toISOString(), - }, - { - sourceUrl: - 'http://www.treasury.gov/resource-center/sanctions/SDN-List/Pages/default.aspx', - sourceName: 'OFAC SDN List', - date: faker.date.recent().toISOString(), - }, - ], - pep: [ - { - sourceName: - 'United Kingdom Insolvency Service Disqualified Directors', - sourceUrl: 'https://www.navy.mil/Leadership/Biographies', - date: '2020-01-01', - }, - ], - adverseMedia: [ - { - sourceName: "SNA's Old Salt Award Passed to Adm. Davidson", - sourceUrl: - 'https://www.marinelink.com/amp/news/snas-old-salt-award-passed-adm-davidson-443093', - date: '2021-03-09', - }, - ], - }, + warnings: [ + { + sourceUrl: + 'http://www.treasury.gov/resource-center/sanctions/SDN-List/Pages/default.aspx', + sourceName: 'FBI Most Wanted', + date: faker.date.recent().toISOString(), + }, + { + sourceUrl: + 'http://www.treasury.gov/resource-center/sanctions/SDN-List/Pages/default.aspx', + sourceName: 'FBI Most Wanted', + date: faker.date.recent().toISOString(), + }, + ], + sanctions: [ + { + sourceUrl: + 'http://www.treasury.gov/resource-center/sanctions/SDN-List/Pages/default.aspx', + sourceName: 'OFAC SDN List', + date: faker.date.recent().toISOString(), + }, + { + sourceUrl: + 'http://www.treasury.gov/resource-center/sanctions/SDN-List/Pages/default.aspx', + sourceName: 'OFAC SDN List', + date: faker.date.recent().toISOString(), + }, + ], + pep: [ + { + sourceName: 'United Kingdom Insolvency Service Disqualified Directors', + sourceUrl: 'https://www.navy.mil/Leadership/Biographies', + date: '2020-01-01', + }, + ], + adverseMedia: [ + { + sourceName: "SNA's Old Salt Award Passed to Adm. Davidson", + sourceUrl: + 'https://www.marinelink.com/amp/news/snas-old-salt-award-passed-adm-davidson-443093', + date: '2021-03-09', + }, + ], }, { aka: ['John Doe', 'John Smith'], @@ -712,52 +708,49 @@ export const workflow = { countries: ['US', 'GB'], matchTypes: ['year_of_birth', 'full_name', 'last_name'], matchedName: `John Doe`, - listingsRelatedToMatch: { - warnings: [ - { - sourceUrl: - 'http://www.treasury.gov/resource-center/sanctions/SDN-List/Pages/default.aspx', - sourceName: 'FBI Most Wanted', - date: faker.date.recent().toISOString(), - }, - { - sourceUrl: - 'http://www.treasury.gov/resource-center/sanctions/SDN-List/Pages/default.aspx', - sourceName: 'FBI Most Wanted', - date: faker.date.recent().toISOString(), - }, - ], - sanctions: [ - { - sourceUrl: - 'http://www.treasury.gov/resource-center/sanctions/SDN-List/Pages/default.aspx', - sourceName: 'OFAC SDN List', - date: faker.date.recent().toISOString(), - }, - { - sourceUrl: - 'http://www.treasury.gov/resource-center/sanctions/SDN-List/Pages/default.aspx', - sourceName: 'OFAC SDN List', - date: faker.date.recent().toISOString(), - }, - ], - pep: [ - { - sourceName: - 'United Kingdom Insolvency Service Disqualified Directors', - sourceUrl: 'https://www.navy.mil/Leadership/Biographies', - date: '2020-01-01', - }, - ], - adverseMedia: [ - { - sourceName: "SNA's Old Salt Award Passed to Adm. Davidson", - sourceUrl: - 'https://www.marinelink.com/amp/news/snas-old-salt-award-passed-adm-davidson-443093', - date: '2021-03-09', - }, - ], - }, + warnings: [ + { + sourceUrl: + 'http://www.treasury.gov/resource-center/sanctions/SDN-List/Pages/default.aspx', + sourceName: 'FBI Most Wanted', + date: faker.date.recent().toISOString(), + }, + { + sourceUrl: + 'http://www.treasury.gov/resource-center/sanctions/SDN-List/Pages/default.aspx', + sourceName: 'FBI Most Wanted', + date: faker.date.recent().toISOString(), + }, + ], + sanctions: [ + { + sourceUrl: + 'http://www.treasury.gov/resource-center/sanctions/SDN-List/Pages/default.aspx', + sourceName: 'OFAC SDN List', + date: faker.date.recent().toISOString(), + }, + { + sourceUrl: + 'http://www.treasury.gov/resource-center/sanctions/SDN-List/Pages/default.aspx', + sourceName: 'OFAC SDN List', + date: faker.date.recent().toISOString(), + }, + ], + pep: [ + { + sourceName: 'United Kingdom Insolvency Service Disqualified Directors', + sourceUrl: 'https://www.navy.mil/Leadership/Biographies', + date: '2020-01-01', + }, + ], + adverseMedia: [ + { + sourceName: "SNA's Old Salt Award Passed to Adm. Davidson", + sourceUrl: + 'https://www.marinelink.com/amp/news/snas-old-salt-award-passed-adm-davidson-443093', + date: '2021-03-09', + }, + ], }, ], }, diff --git a/services/workflows-service/prisma/data-migrations b/services/workflows-service/prisma/data-migrations index bb819e13b9..419f5c7495 160000 --- a/services/workflows-service/prisma/data-migrations +++ b/services/workflows-service/prisma/data-migrations @@ -1 +1 @@ -Subproject commit bb819e13b921c3e00810de85fa92caee687fcc34 +Subproject commit 419f5c749531b5bb7ca663a3cc02581a3a9c756a diff --git a/services/workflows-service/scripts/workflows/workflow-runtime.ts b/services/workflows-service/scripts/workflows/workflow-runtime.ts index 16ba827a41..02b8a7714d 100644 --- a/services/workflows-service/scripts/workflows/workflow-runtime.ts +++ b/services/workflows-service/scripts/workflows/workflow-runtime.ts @@ -403,7 +403,6 @@ const createAmlData = ({ ubo }: { ubo: Workflow['ubos'][number] }) => { name: `${ubo.firstName} ${ubo.lastName}`, year: ubo.dateOfBirth.getFullYear(), }, - totalHits: 1, createdAt: faker.date.recent().toISOString(), hits: [ { @@ -413,19 +412,17 @@ const createAmlData = ({ ubo }: { ubo: Workflow['ubos'][number] }) => { dateOfBirth: ubo.dateOfBirth.toISOString().split('T')[0], dateOfDeath: null, matchedName: `${ubo.firstName} ${ubo.lastName}`, - listingsRelatedToMatch: { - pep: [], - warnings: [], - sanctions: [ - { - sourceUrl: - 'http://www.treasury.gov/resource-center/sanctions/SDN-List/Pages/default.aspx', - sourceName: 'OFAC SDN List', - }, - ], - fitnessProbity: [], - adverseMedia: [], - }, + pep: [], + warnings: [], + sanctions: [ + { + sourceUrl: + 'http://www.treasury.gov/resource-center/sanctions/SDN-List/Pages/default.aspx', + sourceName: 'OFAC SDN List', + }, + ], + fitnessProbity: [], + adverseMedia: [], }, ], }; diff --git a/services/workflows-service/src/webhooks/dtos/individual-aml-webhook-input.ts b/services/workflows-service/src/webhooks/dtos/individual-aml-webhook-input.ts index 7f4ec6469e..0373409744 100644 --- a/services/workflows-service/src/webhooks/dtos/individual-aml-webhook-input.ts +++ b/services/workflows-service/src/webhooks/dtos/individual-aml-webhook-input.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsOptional, IsString } from 'class-validator'; +import { IsNumber, IsOptional, IsString } from 'class-validator'; export class IndividualAmlWebhookInput { @ApiProperty({ @@ -13,15 +13,15 @@ export class IndividualAmlWebhookInput { required: true, type: Number, }) - @IsString() + @IsNumber() apiVersion!: number; @ApiProperty({ required: true, - type: String, + type: Number, }) - @IsString() - timestamp!: string; + @IsNumber() + timestamp!: number; @ApiProperty({ required: true, @@ -30,14 +30,6 @@ export class IndividualAmlWebhookInput { @IsString() eventName!: string; - @ApiProperty({ - required: true, - type: String, - }) - @IsString() - @IsOptional() - entityId!: string; - @ApiProperty({ required: false, type: String, diff --git a/services/workflows-service/src/webhooks/webhooks.controller.ts b/services/workflows-service/src/webhooks/webhooks.controller.ts index 1c3c202b9d..bb31754959 100644 --- a/services/workflows-service/src/webhooks/webhooks.controller.ts +++ b/services/workflows-service/src/webhooks/webhooks.controller.ts @@ -6,6 +6,8 @@ import { AmlWebhookInput } from './dtos/aml-webhook-input'; import { IndividualAmlWebhookInput } from '@/webhooks/dtos/individual-aml-webhook-input'; import { WebhooksService } from '@/webhooks/webhooks.service'; import { VerifyUnifiedApiSignatureDecorator } from '@/common/decorators/verify-unified-api-signature.decorator'; +import { BadRequestException } from '@nestjs/common'; +import { isObject } from '@ballerine/common'; const Webhook = { AML_INDIVIDUAL_MONITORING_UPDATE: 'aml.individuals.monitoring.update', @@ -30,14 +32,16 @@ export class WebhooksController { @VerifyUnifiedApiSignatureDecorator() async amlHook( @common.Param() { entityType }: AmlWebhookInput, - @common.Body() data: IndividualAmlWebhookInput, + @common.Body() { eventName, data }: IndividualAmlWebhookInput, ) { + if (!(isObject(data) && 'endUserId' in data && data.endUserId)) { + throw new BadRequestException('Missing endUserId'); + } + try { if (entityType === EntityType.INDIVIDUAL) { - const { eventName } = data; - if (eventName === Webhook.AML_INDIVIDUAL_MONITORING_UPDATE) { - await this.webhooksService.handleIndividualAmlHit(data as IndividualAmlWebhookInput); + await this.webhooksService.handleIndividualAmlHit({ endUserId: data.endUserId, data }); } } } catch (error) { diff --git a/services/workflows-service/src/webhooks/webhooks.service.ts b/services/workflows-service/src/webhooks/webhooks.service.ts index b6fbd6c741..995c76db34 100644 --- a/services/workflows-service/src/webhooks/webhooks.service.ts +++ b/services/workflows-service/src/webhooks/webhooks.service.ts @@ -14,8 +14,14 @@ export class WebhooksService { private readonly workflowDefinitionService: WorkflowDefinitionService, ) {} - async handleIndividualAmlHit({ entityId, data }: IndividualAmlWebhookInput) { - const { projectId, ...rest } = await this.endUserRepository.findByIdUnscoped(entityId, { + async handleIndividualAmlHit({ + endUserId, + data, + }: { + endUserId: string; + data: IndividualAmlWebhookInput['data']; + }) { + const { projectId, ...rest } = await this.endUserRepository.findByIdUnscoped(endUserId, { select: { approvalState: true, stateReason: true, @@ -68,8 +74,8 @@ export class WebhooksService { ...rest, additionalInfo: rest.additionalInfo ?? {}, }, - id: entityId, - ballerineEntityId: entityId, + id: endUserId, + ballerineEntityId: endUserId, type: 'individual', }, documents: [],