Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/shared/dto/file-upload.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface FileUploadData {
fileName: string;
file: string; // Base64 encoded file content
}
Comment on lines +1 to +4
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to base64 encode the file buffer and directly decode it again afterwards?
(see KycService. updateFileData)

13 changes: 10 additions & 3 deletions src/subdomains/generic/kyc/controllers/kyc-admin.controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Body, Controller, Param, Post, Put, Query, UseGuards } from '@nestjs/common';
import { Body, Controller, Param, Post, Put, Query, UploadedFile, UseGuards, UseInterceptors } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { FileInterceptor } from '@nestjs/platform-express';
import { ApiBearerAuth, ApiExcludeController, ApiExcludeEndpoint, ApiTags } from '@nestjs/swagger';
import { GetJwt } from 'src/shared/auth/get-jwt.decorator';
import { JwtPayload } from 'src/shared/auth/jwt-payload.interface';
Expand Down Expand Up @@ -63,8 +64,14 @@ export class KycAdminController {
@ApiBearerAuth()
@UseGuards(AuthGuard(), RoleGuard(UserRole.COMPLIANCE), UserActiveGuard())
@ApiExcludeEndpoint()
async createLog(@GetJwt() jwt: JwtPayload, @Body() dto: CreateKycLogDto): Promise<void> {
await this.kycLogService.createLog(jwt.account, dto);
@UseInterceptors(FileInterceptor('file'))
async createLog(
@GetJwt() jwt: JwtPayload,
@Body() dto: CreateKycLogDto,
@UploadedFile() file?: Express.Multer.File,
Comment on lines +70 to +71
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use body and file in the same request?

): Promise<void> {
const document = file ? { fileName: file.originalname, file: file.buffer.toString('base64') } : undefined;
await this.kycLogService.createLog(jwt.account, dto, document);
}

@Put('log/:id')
Expand Down
56 changes: 32 additions & 24 deletions src/subdomains/generic/kyc/controllers/kyc.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,18 @@ import {
Headers,
InternalServerErrorException,
Param,
ParseFilePipeBuilder,
Post,
Put,
Query,
Req,
Res,
UploadedFile,
UseGuards,
UseInterceptors,
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { FileInterceptor } from '@nestjs/platform-express';
import {
ApiBearerAuth,
ApiConflictResponse,
Expand All @@ -38,13 +42,11 @@ import { UserRole } from 'src/shared/auth/user-role.enum';
import { CountryDtoMapper } from 'src/shared/models/country/dto/country-dto.mapper';
import { CountryDto } from 'src/shared/models/country/dto/country.dto';
import { DfxLogger } from 'src/shared/services/dfx-logger';
import { Util } from 'src/shared/utils/util';
import { IdNowResult } from '../dto/ident-result.dto';
import { IdentStatus } from '../dto/ident.dto';
import {
KycBeneficialData,
KycContactData,
KycFileData,
KycLegalEntityData,
KycManualIdentData,
KycNationalityData,
Expand Down Expand Up @@ -194,13 +196,14 @@ export class KycController {
@Put('data/owner/:id')
@ApiOkResponse({ type: KycStepBase })
@ApiUnauthorizedResponse(MergedResponse)
@UseInterceptors(FileInterceptor('file'))
async updateOwnerDirectoryData(
@Headers(CodeHeaderName) code: string,
@Param('id') id: string,
@Body() data: KycFileData,
@UploadedFile(new ParseFilePipeBuilder().build({ fileIsRequired: true })) file: Express.Multer.File,
): Promise<KycStepBase> {
data.fileName = this.fileName('stock-register', data.fileName);
return this.kycService.updateFileData(code, +id, data, FileType.STOCK_REGISTER);
const document = { fileName: file.originalname, file: file.buffer.toString('base64') };
return this.kycService.updateFileData(code, +id, document, FileType.STOCK_REGISTER);
}

@Put('data/nationality/:id')
Expand All @@ -217,49 +220,54 @@ export class KycController {
@Put('data/legal/:id')
@ApiOkResponse({ type: KycStepBase })
@ApiUnauthorizedResponse(MergedResponse)
@UseInterceptors(FileInterceptor('file'))
async updateCommercialRegisterData(
@Headers(CodeHeaderName) code: string,
@Param('id') id: string,
@Body() data: KycLegalEntityData,
@UploadedFile(new ParseFilePipeBuilder().build({ fileIsRequired: true })) file: Express.Multer.File,
): Promise<KycStepBase> {
data.fileName = this.fileName('commercial-register', data.fileName);
return this.kycService.updateLegalData(code, +id, data, FileType.COMMERCIAL_REGISTER);
const document = { fileName: file.originalname, file: file.buffer.toString('base64') };
return this.kycService.updateLegalData(code, +id, data, document, FileType.COMMERCIAL_REGISTER);
}

@Put('data/residence/:id')
@ApiOkResponse({ type: KycStepBase })
@ApiUnauthorizedResponse(MergedResponse)
@UseInterceptors(FileInterceptor('file'))
async updateResidencePermitData(
@Headers(CodeHeaderName) code: string,
@Param('id') id: string,
@Body() data: KycFileData,
@UploadedFile(new ParseFilePipeBuilder().build({ fileIsRequired: true })) file: Express.Multer.File,
): Promise<KycStepBase> {
data.fileName = this.fileName('residence-permit', data.fileName);
return this.kycService.updateFileData(code, +id, data, FileType.RESIDENCE_PERMIT);
const document = { fileName: file.originalname, file: file.buffer.toString('base64') };
return this.kycService.updateFileData(code, +id, document, FileType.RESIDENCE_PERMIT);
}

@Put('data/statutes/:id')
@ApiOkResponse({ type: KycStepBase })
@ApiUnauthorizedResponse(MergedResponse)
@UseInterceptors(FileInterceptor('file'))
async updateStatutesData(
@Headers(CodeHeaderName) code: string,
@Param('id') id: string,
@Body() data: KycFileData,
@UploadedFile(new ParseFilePipeBuilder().build({ fileIsRequired: true })) file: Express.Multer.File,
): Promise<KycStepBase> {
data.fileName = this.fileName('statutes', data.fileName);
return this.kycService.updateFileData(code, +id, data, FileType.STATUTES);
const document = { fileName: file.originalname, file: file.buffer.toString('base64') };
return this.kycService.updateFileData(code, +id, document, FileType.STATUTES);
}

@Put('data/additional/:id')
@ApiOkResponse({ type: KycStepBase })
@ApiUnauthorizedResponse(MergedResponse)
@UseInterceptors(FileInterceptor('file'))
async updateAdditionalDocumentsData(
@Headers(CodeHeaderName) code: string,
@Param('id') id: string,
@Body() data: KycFileData,
@UploadedFile(new ParseFilePipeBuilder().build({ fileIsRequired: true })) file: Express.Multer.File,
): Promise<KycStepBase> {
data.fileName = this.fileName('additional-documents', data.fileName);
return this.kycService.updateFileData(code, +id, data, FileType.ADDITIONAL_DOCUMENTS);
const document = { fileName: file.originalname, file: file.buffer.toString('base64') };
return this.kycService.updateFileData(code, +id, document, FileType.ADDITIONAL_DOCUMENTS);
}

@Put('data/signatory/:id')
Expand Down Expand Up @@ -298,13 +306,14 @@ export class KycController {
@Put('data/authority/:id')
@ApiOkResponse({ type: KycStepBase })
@ApiUnauthorizedResponse(MergedResponse)
@UseInterceptors(FileInterceptor('file'))
async updateAuthorityData(
@Headers(CodeHeaderName) code: string,
@Param('id') id: string,
@Body() data: KycFileData,
@UploadedFile(new ParseFilePipeBuilder().build({ fileIsRequired: true })) file: Express.Multer.File,
): Promise<KycStepBase> {
data.fileName = this.fileName('authority', data.fileName);
return this.kycService.updateFileData(code, +id, data, FileType.AUTHORITY);
const document = { fileName: file.originalname, file: file.buffer.toString('base64') };
return this.kycService.updateFileData(code, +id, document, FileType.AUTHORITY);
}

@Get('data/financial/:id')
Expand Down Expand Up @@ -365,12 +374,15 @@ export class KycController {
@Put('ident/manual/:id')
@ApiOkResponse({ type: KycStepBase })
@ApiUnauthorizedResponse(MergedResponse)
@UseInterceptors(FileInterceptor('file'))
async updateIdentData(
@Headers(CodeHeaderName) code: string,
@Param('id') id: string,
@Body() data: KycManualIdentData,
@UploadedFile(new ParseFilePipeBuilder().build({ fileIsRequired: true })) file: Express.Multer.File,
): Promise<KycStepBase> {
return this.kycService.updateIdentManual(code, +id, data);
const document = { fileName: file.originalname, file: file.buffer.toString('base64') };
return this.kycService.updateIdentManual(code, +id, data, document);
}

@Post('ident/:type')
Expand Down Expand Up @@ -416,8 +428,4 @@ export class KycController {
.join(';');
res.setHeader('Content-Security-Policy', updatedPolicy);
}

private fileName(type: string, file: string): string {
return `${Util.isoDateTime(new Date())}_${type}_user-upload_${Util.randomId()}_${file}`;
}
}
11 changes: 1 addition & 10 deletions src/subdomains/generic/kyc/dto/input/create-kyc-log.dto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Type } from 'class-transformer';
import { IsDate, IsNotEmpty, IsObject, IsOptional, IsString, ValidateIf, ValidateNested } from 'class-validator';
import { IsDate, IsObject, IsOptional, IsString, ValidateNested } from 'class-validator';
import { EntityDto } from 'src/shared/dto/entity.dto';
import { UserData } from 'src/subdomains/generic/user/models/user-data/user-data.entity';

Expand Down Expand Up @@ -27,13 +27,4 @@ export class CreateKycLogDto extends UpdateKycLogDto {
@ValidateNested()
@Type(() => EntityDto)
userData: UserData;

@IsOptional()
@IsString()
file?: string;

@ValidateIf((d: CreateKycLogDto) => d.file != null)
@IsNotEmpty()
@IsString()
fileName?: string;
}
30 changes: 0 additions & 30 deletions src/subdomains/generic/kyc/dto/input/kyc-data.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,31 +184,7 @@ export class KycNationalityData {
nationality: Country;
}

export class KycFileData {
@ApiProperty({ description: 'Base64 encoded file' })
@IsNotEmpty()
@IsString()
file: string;

@ApiProperty({ description: 'Name of the file' })
@IsNotEmpty()
@IsString()
@Transform(Util.sanitize)
fileName: string;
}

export class KycLegalEntityData {
@ApiProperty({ description: 'Base64 encoded commercial register file' })
@IsNotEmpty()
@IsString()
file: string;

@ApiProperty({ description: 'Name of the commercial register file' })
@IsNotEmpty()
@IsString()
@Transform(Util.sanitize)
fileName: string;

@ApiProperty({ enum: LegalEntity })
@IsNotEmpty()
@IsEnum(LegalEntity)
Expand Down Expand Up @@ -267,12 +243,6 @@ export class KycManualIdentData {
@IsString()
@Transform(Util.sanitize)
documentNumber: string;

@ApiProperty({ type: KycFileData })
@IsNotEmptyObject()
@ValidateNested()
@Type(() => KycFileData)
document: KycFileData;
}

export class PaymentDataDto {
Expand Down
14 changes: 14 additions & 0 deletions src/subdomains/generic/kyc/dto/kyc-file.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,20 @@ export enum FileType {
AUTHORITY = 'Authority',
}

export const fileLabel: { [key in FileType]: string } = {
[FileType.NAME_CHECK]: 'name-check',
[FileType.USER_INFORMATION]: 'user-information',
[FileType.IDENTIFICATION]: 'identification',
[FileType.USER_NOTES]: 'user-notes',
[FileType.TRANSACTION_NOTES]: 'transaction-notes',
[FileType.STOCK_REGISTER]: 'stock-register',
[FileType.COMMERCIAL_REGISTER]: 'commercial-register',
[FileType.RESIDENCE_PERMIT]: 'residence-permit',
[FileType.STATUTES]: 'statutes',
[FileType.ADDITIONAL_DOCUMENTS]: 'additional-documents',
[FileType.AUTHORITY]: 'authority',
};

export enum FileSubType {
GWG_FILE_COVER = 'GwGFileCover',
BLOCKCHAIN_ADDRESS_ANALYSIS = 'BlockchainAddressAnalysis',
Expand Down
9 changes: 5 additions & 4 deletions src/subdomains/generic/kyc/services/kyc-log.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Inject, Injectable, NotFoundException, forwardRef } from '@nestjs/common';
import { FileUploadData } from 'src/shared/dto/file-upload.dto';
import { Util } from 'src/shared/utils/util';
import { UserData } from '../../user/models/user-data/user-data.entity';
import { UserDataService } from '../../user/models/user-data/user-data.service';
Expand Down Expand Up @@ -27,7 +28,7 @@ export class KycLogService {
await this.kycLogRepo.save(entity);
}

async createLog(creatorUserDataId: number, dto: CreateKycLogDto): Promise<void> {
async createLog(creatorUserDataId: number, dto: CreateKycLogDto, document?: FileUploadData): Promise<void> {
const entity = this.kycLogRepo.create({
type: KycLogType.MANUAL,
comment: dto.comment,
Expand All @@ -38,13 +39,13 @@ export class KycLogService {
entity.userData = await this.userDataService.getUserData(dto.userData.id);
if (!entity.userData) throw new NotFoundException('UserData not found');

if (dto.file) {
const { contentType, buffer } = Util.fromBase64(dto.file);
if (document) {
const { contentType, buffer } = Util.fromBase64(document.file);

const { file, url } = await this.kycDocumentService.uploadUserFile(
entity.userData,
FileType.USER_NOTES,
`Manual/${Util.isoDateTime(new Date())}_manual-upload_${Util.randomId()}_${dto.fileName}`,
`Manual/${Util.isoDateTime(new Date())}_manual-upload_${Util.randomId()}_${document.fileName}`,
buffer,
contentType as ContentType,
true,
Expand Down
Loading