Skip to content

Commit 799a74e

Browse files
committed
Merge branch 'dev' of github.com:ballerine-io/ballerine into dev
2 parents 865da56 + c409a32 commit 799a74e

File tree

6 files changed

+118
-80
lines changed

6 files changed

+118
-80
lines changed

apps/backoffice-v2/src/common/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export const DOWNLOAD_ONLY_MIME_TYPES = [
77

88
export const BALLERINE_CALENDLY_LINK = 'https://calendly.com/d/cp53-ryw-4s3/ballerine-intro';
99

10+
// Taken from https://gist.github.com/dperini/729294, changed a bit to allow only http/https and make protocol optional
1011
export const URL_REGEX =
1112
/((https?):\/\/)?([a-zA-Z0-9-_]+\.)+[a-zA-Z0-9]+(\.[a-z]{2})?(\/[a-zA-Z0-9_#-]+)*(\/)?(\?[a-zA-Z0-9_-]+=[a-zA-Z0-9_-]+(&[a-zA-Z0-9_-]+=[a-zA-Z0-9_-]+)*)?(#[a-zA-Z0-9_-]+)?/;
1213

packages/common/src/consts/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ export const MatchReasonCode = {
172172
} as const;
173173

174174
export const URL_PATTERN =
175-
/^(https?:\/\/)?((([\da-z]([\da-z-]*[\da-z])*)\.)+[a-z]{2,}|((25[0-5]|2[0-4]\d|1\d{2}|\d{1,2})\.){3}(25[0-5]|2[0-4]\d|1\d{2}|\d{1,2})|localhost)(:\d{1,5})?(\/[\w!$%&'()*+,.:;=@~-]*)*(\?([\w!$%&'()*+,.:;=@~-]+=[\w!$%&'()*+,.:;=@~-]*(&[\w!$%&'()*+,.:;=@~-]+=[\w!$%&'()*+,.:;=@~-]*)*)?)?(#[\w!$%&'()*+,.:;=@~-]*)?$/i;
175+
/^(?:https?:\/\/)?(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u00A1-\uFFFF][a-z0-9\u00A1-\uFFFF_-]{0,62})?[a-z0-9\u00A1-\uFFFF]\.)+(?:[a-z\u00A1-\uFFFF]{2,}\.?))(?::\d{2,5})?(?:[/?#]\S*)?$/i;
176176

177177
export const MERCHANT_REPORT_STATUSES = [
178178
'in-progress',

services/workflows-service/src/business-report/business-report.service.ts

Lines changed: 66 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { BadRequestException, Injectable, UnprocessableEntityException } from '@nestjs/common';
2+
import uniqBy from 'lodash/uniqBy';
23
import { Business } from '@prisma/client';
3-
import { TProjectId } from '@/types';
4+
import { type PrismaTransaction, TProjectId } from '@/types';
45
import { parseCsv } from '@/common/utils/parse-csv/parse-csv';
5-
import { BusinessReportRequestSchema } from '@/common/schemas';
6+
import { BusinessReportRequestSchema, TBusinessReportRequest } from '@/common/schemas';
67
import { PrismaService } from '@/prisma/prisma.service';
78
import { BusinessService } from '@/business/business.service';
89
import { env } from '@/env';
@@ -113,6 +114,27 @@ export class BusinessReportService {
113114
return await this.merchantMonitoringClient.count(args);
114115
}
115116

117+
private async createBusiness(
118+
projectId: string,
119+
businessReportRequest: TBusinessReportRequest,
120+
transaction?: PrismaTransaction,
121+
) {
122+
return await this.businessService.create(
123+
{
124+
data: {
125+
...(businessReportRequest.correlationId
126+
? { correlationId: businessReportRequest.correlationId }
127+
: {}),
128+
companyName: businessReportRequest.merchantName || 'Not detected',
129+
website: businessReportRequest.websiteUrl || '',
130+
country: businessReportRequest.countryCode || '',
131+
projectId,
132+
},
133+
},
134+
transaction,
135+
);
136+
}
137+
116138
async processBatchFile({
117139
type,
118140
projectId,
@@ -160,27 +182,54 @@ export class BusinessReportService {
160182

161183
await this.prisma.$transaction(
162184
async transaction => {
163-
const businessCreatePromises = businessReportsRequests.map(async businessReportRequest => {
164-
let business =
165-
businessReportRequest.correlationId &&
166-
(await this.businessService.getByCorrelationId(businessReportRequest.correlationId, [
167-
projectId,
168-
]));
185+
const businessesLookup = new Map<string, Business>();
186+
187+
const allCorrelationIds = new Set(
188+
businessReportsRequests
189+
.map(({ correlationId }) => correlationId)
190+
.filter(Boolean) as string[],
191+
);
169192

170-
business ||= await this.businessService.create(
193+
if (allCorrelationIds.size > 0) {
194+
const businesses = await this.businessService.list(
171195
{
172-
data: {
173-
...(businessReportRequest.correlationId
174-
? { correlationId: businessReportRequest.correlationId }
175-
: {}),
176-
companyName: businessReportRequest.merchantName || 'Not detected',
177-
website: businessReportRequest.websiteUrl || '',
178-
country: businessReportRequest.countryCode || '',
179-
projectId,
196+
where: {
197+
correlationId: {
198+
in: [...allCorrelationIds],
199+
},
180200
},
181201
},
202+
[projectId],
182203
transaction,
183204
);
205+
for (const business of businesses) {
206+
businessesLookup.set(business.correlationId || business.id, business);
207+
}
208+
}
209+
210+
const businessesToCreate = uniqBy(
211+
businessReportsRequests.filter(
212+
({ correlationId }) => !!correlationId && !businessesLookup.has(correlationId),
213+
),
214+
'correlationId',
215+
);
216+
if (businessesToCreate.length > 0) {
217+
const businesses = await Promise.all(
218+
businessesToCreate.map(business =>
219+
this.createBusiness(projectId, business, transaction),
220+
),
221+
);
222+
for (const business of businesses) {
223+
businessesLookup.set(business.correlationId || business.id, business);
224+
}
225+
}
226+
227+
const businessCreatePromises = businessReportsRequests.map(async businessReportRequest => {
228+
let business =
229+
businessReportRequest.correlationId &&
230+
businessesLookup.get(businessReportRequest.correlationId);
231+
232+
business ||= await this.createBusiness(projectId, businessReportRequest, transaction);
184233

185234
return {
186235
businessReportRequest,

services/workflows-service/src/business/business.repository.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,9 @@ export class BusinessRepository {
3333
async findMany<T extends Prisma.BusinessFindManyArgs>(
3434
args: Prisma.SelectSubset<T, Prisma.BusinessFindManyArgs>,
3535
projectIds: TProjectIds,
36+
transaction: PrismaClient | PrismaTransaction = this.prismaService,
3637
) {
37-
return await this.prismaService.business.findMany(
38-
this.scopeService.scopeFindMany(args, projectIds),
39-
);
38+
return await transaction.business.findMany(this.scopeService.scopeFindMany(args, projectIds));
4039
}
4140

4241
async findManyUnscoped<T extends Prisma.BusinessFindManyArgs>(

services/workflows-service/src/business/business.service.ts

Lines changed: 39 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -42,41 +42,18 @@ export class BusinessService {
4242
})(async tx => {
4343
const business = await this.repository.create(args, tx);
4444

45-
const businessPayload = (await this.repository.findByIdUnscoped(
46-
business.id,
47-
{
48-
select: {
49-
id: true,
50-
correlationId: true,
51-
companyName: true,
52-
metadata: true,
53-
createdAt: true,
54-
updatedAt: true,
55-
project: {
56-
select: {
57-
customer: {
58-
select: {
59-
id: true,
60-
config: true,
61-
},
62-
},
63-
},
64-
},
65-
},
66-
},
67-
tx,
68-
)) as unknown as BusinessPayload;
69-
70-
if (env.SYNC_UNIFIED_API) {
71-
await retry(() => this.unifiedApiClient.createOrUpdateBusiness(businessPayload));
72-
}
45+
await this.syncBusiness(business, tx);
7346

7447
return business;
7548
});
7649
}
7750

78-
async list(args: Parameters<BusinessRepository['findMany']>[0], projectIds: TProjectIds) {
79-
return (await this.repository.findMany(args, projectIds)) as Array<
51+
async list(
52+
args: Parameters<BusinessRepository['findMany']>[0],
53+
projectIds: TProjectIds,
54+
transaction?: PrismaTransaction,
55+
) {
56+
return (await this.repository.findMany(args, projectIds, transaction)) as Array<
8057
Business & {
8158
metadata?: {
8259
featureConfig?: TCustomerWithFeatures['features'];
@@ -117,38 +94,43 @@ export class BusinessService {
11794
})(async tx => {
11895
const business = await this.repository.updateById(id, args, tx);
11996

120-
const businessPayload = (await this.repository.findByIdUnscoped(
121-
business.id,
122-
{
123-
select: {
124-
id: true,
125-
correlationId: true,
126-
companyName: true,
127-
metadata: true,
128-
createdAt: true,
129-
updatedAt: true,
130-
project: {
131-
select: {
132-
customer: {
133-
select: {
134-
id: true,
135-
config: true,
136-
},
97+
await this.syncBusiness(business, tx);
98+
99+
return business;
100+
});
101+
}
102+
103+
private syncBusiness = async (business: { id: string }, tx: PrismaTransaction) => {
104+
if (!env.SYNC_UNIFIED_API) {
105+
return;
106+
}
107+
108+
const businessPayload = (await this.repository.findByIdUnscoped(
109+
business.id,
110+
{
111+
select: {
112+
id: true,
113+
correlationId: true,
114+
companyName: true,
115+
metadata: true,
116+
createdAt: true,
117+
updatedAt: true,
118+
project: {
119+
select: {
120+
customer: {
121+
select: {
122+
id: true,
123+
config: true,
137124
},
138125
},
139126
},
140127
},
141128
},
142-
tx,
143-
)) as unknown as BusinessPayload;
144-
145-
if (env.SYNC_UNIFIED_API) {
146-
await retry(() => this.unifiedApiClient.createOrUpdateBusiness(businessPayload));
147-
}
148-
149-
return business;
150-
});
151-
}
129+
},
130+
tx,
131+
)) as unknown as BusinessPayload;
132+
await retry(() => this.unifiedApiClient.createOrUpdateBusiness(businessPayload));
133+
};
152134

153135
async fetchCompanyInformation({
154136
registrationNumber,

services/workflows-service/src/common/utils/parse-csv/parse-csv.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { parse } from 'csv-parse';
22
import { z, ZodSchema } from 'zod';
33
import { AppLoggerService } from '@/common/app-logger/app-logger.service';
4+
import { ForbiddenException } from '@/errors';
45
import fs from 'fs';
56

67
export const parseCsv = async <TSchema extends ZodSchema>(
@@ -43,16 +44,22 @@ export const parseCsv = async <TSchema extends ZodSchema>(
4344
reject(err);
4445
}
4546

47+
let hadErrors = false;
4648
for (const record of records) {
4749
try {
4850
const validatedRecord = schema.parse(record);
4951
results.push(validatedRecord);
5052
} catch (error) {
51-
logger.error('Validation error:', { error });
53+
logger.error('Validation error:', { error, record });
54+
hadErrors = true;
5255
}
5356
}
5457

55-
resolve(results);
58+
if (hadErrors) {
59+
reject(new ForbiddenException('Schema errors in CSV'));
60+
} else {
61+
resolve(results);
62+
}
5663
},
5764
);
5865
});

0 commit comments

Comments
 (0)