diff --git a/src/subdomains/core/aml/enums/aml-error.enum.ts b/src/subdomains/core/aml/enums/aml-error.enum.ts index 43ecce0e8d..dc51a0e583 100644 --- a/src/subdomains/core/aml/enums/aml-error.enum.ts +++ b/src/subdomains/core/aml/enums/aml-error.enum.ts @@ -96,6 +96,13 @@ export const ManualPassWhitelistErrors: AmlError[] = [ AmlError.REFERRAL_NO_TRADE_HISTORY, ]; +export const ManualPassBlacklistErrors: AmlError[] = [ + AmlError.BANK_DATA_NOT_ACTIVE, + AmlError.BANK_DATA_MANUAL_REVIEW, + AmlError.BANK_DATA_MISSING, + AmlError.BANK_DATA_USER_MISMATCH, +]; + export function canManualPass(comment: string | null | undefined): boolean { const errors = (comment ?? '') .split(';') diff --git a/src/subdomains/core/buy-crypto/process/services/buy-crypto.service.ts b/src/subdomains/core/buy-crypto/process/services/buy-crypto.service.ts index 78fcdf71df..126534742e 100644 --- a/src/subdomains/core/buy-crypto/process/services/buy-crypto.service.ts +++ b/src/subdomains/core/buy-crypto/process/services/buy-crypto.service.ts @@ -57,7 +57,7 @@ import { TransactionService } from 'src/subdomains/supporting/payment/services/t import { PriceValidity } from 'src/subdomains/supporting/pricing/services/pricing.service'; import { Between, FindOptionsRelations, In, IsNull, MoreThan, Not } from 'typeorm'; import { ManualAmlCheckDto } from '../../../aml/dto/manual-aml-check.dto'; -import { canManualPass } from '../../../aml/enums/aml-error.enum'; +import { canManualPass, ManualPassBlacklistErrors } from '../../../aml/enums/aml-error.enum'; import { AmlReason, PhoneAmlReasons } from '../../../aml/enums/aml-reason.enum'; import { CheckStatus } from '../../../aml/enums/check-status.enum'; import { Buy } from '../../routes/buy/buy.entity'; @@ -299,6 +299,9 @@ export class BuyCryptoService implements OnModuleInit { manualApproved: dto.bankDataManualApproved, }); + if (dto.amlCheck === CheckStatus.PASS && ManualPassBlacklistErrors.some((b) => entity.comment?.includes(b))) + throw new BadRequestException('Blacklisted aml error cannot set Pass'); + if (dto.chargebackAllowedDate) { if (entity.bankTx && !entity.chargebackOutput) { if (!dto.chargebackCreditorName && !entity.creditorData) @@ -729,8 +732,12 @@ export class BuyCryptoService implements OnModuleInit { throw new BadRequestException('BuyCrypto is already complete or chargeback initiated'); if ([CheckStatus.PASS, CheckStatus.FAIL].includes(entity.amlCheck)) throw new BadRequestException('BuyCrypto amlCheck is already finalized'); - if (dto.amlCheck === CheckStatus.PASS && !canManualPass(entity.comment)) - throw new BadRequestException('Manual pass only allowed when all errors are phone-related'); + if (dto.amlCheck === CheckStatus.PASS) { + if (ManualPassBlacklistErrors.some((b) => entity.comment?.includes(b))) + throw new BadRequestException('Blacklisted aml error cannot set Pass'); + if (!canManualPass(entity.comment)) + throw new BadRequestException('Manual pass only allowed when all errors are phone-related'); + } return this.update(id, { amlCheck: dto.amlCheck, diff --git a/src/subdomains/core/buy-crypto/routes/buy/buy.controller.ts b/src/subdomains/core/buy-crypto/routes/buy/buy.controller.ts index 9224b7ce36..e22f12b605 100644 --- a/src/subdomains/core/buy-crypto/routes/buy/buy.controller.ts +++ b/src/subdomains/core/buy-crypto/routes/buy/buy.controller.ts @@ -5,6 +5,7 @@ import { Controller, Get, Param, + ParseIntPipe, Post, Put, UseGuards, @@ -249,24 +250,28 @@ export class BuyController { @ApiBearerAuth() @UseGuards(AuthGuard(), RoleGuard(UserRole.USER), BuyActiveGuard()) @ApiExcludeEndpoint() - async updateBuyRoute(@GetJwt() jwt: JwtPayload, @Param('id') id: string, @Body() dto: UpdateBuyDto): Promise { - return this.buyService.updateBuy(jwt.user, +id, dto).then((b) => this.toDto(jwt.user, b)); + async updateBuyRoute( + @GetJwt() jwt: JwtPayload, + @Param('id', ParseIntPipe) id: number, + @Body() dto: UpdateBuyDto, + ): Promise { + return this.buyService.updateBuy(jwt.user, id, dto).then((b) => this.toDto(jwt.user, b)); } @Get(':id') @ApiBearerAuth() @UseGuards(AuthGuard(), RoleGuard(UserRole.USER), BuyActiveGuard()) @ApiOkResponse({ type: BuyDto }) - async getBuy(@GetJwt() jwt: JwtPayload, @Param('id') id: string): Promise { - return this.buyService.get(jwt.account, +id).then((l) => this.toDto(jwt.user, l)); + async getBuy(@GetJwt() jwt: JwtPayload, @Param('id', ParseIntPipe) id: number): Promise { + return this.buyService.get(jwt.account, id).then((l) => this.toDto(jwt.user, l)); } @Get(':id/history') @ApiBearerAuth() @UseGuards(AuthGuard(), RoleGuard(UserRole.USER)) @ApiExcludeEndpoint() - async getBuyRouteHistory(@GetJwt() jwt: JwtPayload, @Param('id') id: string): Promise { - return this.buyCryptoService.getBuyHistory(jwt.user, +id); + async getBuyRouteHistory(@GetJwt() jwt: JwtPayload, @Param('id', ParseIntPipe) id: number): Promise { + return this.buyCryptoService.getBuyHistory(jwt.user, id); } // --- DTO --- // diff --git a/src/subdomains/core/buy-crypto/routes/swap/swap.controller.ts b/src/subdomains/core/buy-crypto/routes/swap/swap.controller.ts index dd546cea18..26765f0c34 100644 --- a/src/subdomains/core/buy-crypto/routes/swap/swap.controller.ts +++ b/src/subdomains/core/buy-crypto/routes/swap/swap.controller.ts @@ -6,6 +6,7 @@ import { Get, NotFoundException, Param, + ParseIntPipe, Post, Put, Query, @@ -75,8 +76,8 @@ export class SwapController { @ApiBearerAuth() @UseGuards(AuthGuard(), RoleGuard(UserRole.USER), SwapActiveGuard()) @ApiOkResponse({ type: SwapDto }) - async getSwap(@GetJwt() jwt: JwtPayload, @Param('id') id: string): Promise { - return this.swapService.get(jwt.user, +id).then((l) => this.toDto(jwt.user, l)); + async getSwap(@GetJwt() jwt: JwtPayload, @Param('id', ParseIntPipe) id: number): Promise { + return this.swapService.get(jwt.user, id).then((l) => this.toDto(jwt.user, l)); } @Post() @@ -206,18 +207,21 @@ export class SwapController { @ApiExcludeEndpoint() async updateSwapRoute( @GetJwt() jwt: JwtPayload, - @Param('id') id: string, + @Param('id', ParseIntPipe) id: number, @Body() updateCryptoDto: UpdateSwapDto, ): Promise { - return this.swapService.updateSwap(jwt.user, +id, updateCryptoDto).then((b) => this.toDto(jwt.user, b)); + return this.swapService.updateSwap(jwt.user, id, updateCryptoDto).then((b) => this.toDto(jwt.user, b)); } @Get(':id/history') @ApiBearerAuth() @UseGuards(AuthGuard(), RoleGuard(UserRole.USER)) @ApiExcludeEndpoint() - async getSwapRouteHistory(@GetJwt() jwt: JwtPayload, @Param('id') id: string): Promise { - return this.buyCryptoService.getCryptoHistory(jwt.user, +id); + async getSwapRouteHistory( + @GetJwt() jwt: JwtPayload, + @Param('id', ParseIntPipe) id: number, + ): Promise { + return this.buyCryptoService.getCryptoHistory(jwt.user, id); } // --- DTO --- // diff --git a/src/subdomains/core/payment-link/services/payment-link-payment.service.ts b/src/subdomains/core/payment-link/services/payment-link-payment.service.ts index 884baa29d6..7161152200 100644 --- a/src/subdomains/core/payment-link/services/payment-link-payment.service.ts +++ b/src/subdomains/core/payment-link/services/payment-link-payment.service.ts @@ -153,12 +153,12 @@ export class PaymentLinkPaymentService { .innerJoin( (qb) => qb - .select('plp2.linkId', 'linkId') + .select('plp2."linkId"', 'linkId') .addSelect('MAX(plp2.id)', 'maxId') .from(PaymentLinkPayment, 'plp2') - .groupBy('plp2.linkId'), + .groupBy('plp2."linkId"'), 'latest', - 'latest.linkId = plp.linkId AND latest.maxId = plp.id', + 'latest."linkId" = plp."linkId" AND latest."maxId" = plp.id', ) .innerJoinAndSelect('plp.currency', 'currency') .innerJoinAndSelect('plp.link', 'link') diff --git a/src/subdomains/core/referral/reward/services/ref-reward.service.ts b/src/subdomains/core/referral/reward/services/ref-reward.service.ts index d85c277a39..7503d0d12f 100644 --- a/src/subdomains/core/referral/reward/services/ref-reward.service.ts +++ b/src/subdomains/core/referral/reward/services/ref-reward.service.ts @@ -261,7 +261,7 @@ export class RefRewardService { .innerJoin('r.user', 'u') .select('u.userDataId', 'userDataId') .addSelect('COUNT(*)', 'count') - .addSelect('ROUND(SUM(r.amountInChf), 0)', 'totalChf') + .addSelect('ROUND(SUM(r.amountInChf)::numeric, 0)', 'totalChf') .where('r.status != :excluded', { excluded: RewardStatus.USER_SWITCH }) .groupBy('u.userDataId') .orderBy('totalChf', 'DESC'); diff --git a/src/subdomains/core/sell-crypto/process/services/buy-fiat.service.ts b/src/subdomains/core/sell-crypto/process/services/buy-fiat.service.ts index a710a87dde..6c43cc9971 100644 --- a/src/subdomains/core/sell-crypto/process/services/buy-fiat.service.ts +++ b/src/subdomains/core/sell-crypto/process/services/buy-fiat.service.ts @@ -30,7 +30,7 @@ import { SupportLogService } from 'src/subdomains/supporting/support-issue/servi import { Between, FindOptionsRelations, In, IsNull, MoreThan } from 'typeorm'; import { FiatOutputService } from '../../../../supporting/fiat-output/fiat-output.service'; import { ManualAmlCheckDto } from '../../../aml/dto/manual-aml-check.dto'; -import { canManualPass } from '../../../aml/enums/aml-error.enum'; +import { canManualPass, ManualPassBlacklistErrors } from '../../../aml/enums/aml-error.enum'; import { AmlReason, PhoneAmlReasons } from '../../../aml/enums/aml-reason.enum'; import { CheckStatus } from '../../../aml/enums/check-status.enum'; import { BuyCryptoService } from '../../../buy-crypto/process/services/buy-crypto.service'; @@ -203,6 +203,9 @@ export class BuyFiatService implements OnModuleInit { approved: dto.bankDataActive, }); + if (dto.amlCheck === CheckStatus.PASS && ManualPassBlacklistErrors.some((b) => entity.comment?.includes(b))) + throw new BadRequestException('Blacklisted aml error cannot set Pass'); + const forceUpdate: Partial = { ...((BuyFiatEditableAmlCheck.includes(entity.amlCheck) || (entity.amlCheck === CheckStatus.FAIL && dto.amlCheck === CheckStatus.GSHEET)) && @@ -460,8 +463,12 @@ export class BuyFiatService implements OnModuleInit { throw new BadRequestException('BuyFiat is already complete or chargeback initiated'); if ([CheckStatus.PASS, CheckStatus.FAIL].includes(entity.amlCheck)) throw new BadRequestException('BuyFiat amlCheck is already finalized'); - if (dto.amlCheck === CheckStatus.PASS && !canManualPass(entity.comment)) - throw new BadRequestException('Manual pass only allowed when all errors are phone-related'); + if (dto.amlCheck === CheckStatus.PASS) { + if (ManualPassBlacklistErrors.some((b) => entity.comment?.includes(b))) + throw new BadRequestException('Blacklisted aml error cannot set Pass'); + if (!canManualPass(entity.comment)) + throw new BadRequestException('Manual pass only allowed when all errors are phone-related'); + } return this.update(id, { amlCheck: dto.amlCheck, diff --git a/src/subdomains/core/sell-crypto/route/sell.controller.ts b/src/subdomains/core/sell-crypto/route/sell.controller.ts index 750890c2cd..b8f1d32864 100644 --- a/src/subdomains/core/sell-crypto/route/sell.controller.ts +++ b/src/subdomains/core/sell-crypto/route/sell.controller.ts @@ -6,6 +6,7 @@ import { Get, NotFoundException, Param, + ParseIntPipe, Post, Put, Query, @@ -75,8 +76,8 @@ export class SellController { @ApiBearerAuth() @UseGuards(AuthGuard(), RoleGuard(UserRole.USER), SellActiveGuard()) @ApiOkResponse({ type: SellDto }) - async getSell(@GetJwt() jwt: JwtPayload, @Param('id') id: string): Promise { - return this.sellService.get(jwt.user, +id).then((l) => this.toDto(l)); + async getSell(@GetJwt() jwt: JwtPayload, @Param('id', ParseIntPipe) id: number): Promise { + return this.sellService.get(jwt.user, id).then((l) => this.toDto(l)); } @Post() @@ -215,16 +216,23 @@ export class SellController { @ApiBearerAuth() @UseGuards(AuthGuard(), RoleGuard(UserRole.USER), SellActiveGuard()) @ApiExcludeEndpoint() - async updateSell(@GetJwt() jwt: JwtPayload, @Param('id') id: string, @Body() dto: UpdateSellDto): Promise { - return this.sellService.updateSell(jwt.user, +id, dto).then((s) => this.toDto(s)); + async updateSell( + @GetJwt() jwt: JwtPayload, + @Param('id', ParseIntPipe) id: number, + @Body() dto: UpdateSellDto, + ): Promise { + return this.sellService.updateSell(jwt.user, id, dto).then((s) => this.toDto(s)); } @Get(':id/history') @ApiBearerAuth() @UseGuards(AuthGuard(), RoleGuard(UserRole.USER)) @ApiExcludeEndpoint() - async getSellRouteHistory(@GetJwt() jwt: JwtPayload, @Param('id') id: string): Promise { - return this.buyFiatService.getSellHistory(jwt.user, +id); + async getSellRouteHistory( + @GetJwt() jwt: JwtPayload, + @Param('id', ParseIntPipe) id: number, + ): Promise { + return this.buyFiatService.getSellHistory(jwt.user, id); } // --- DTO --- // diff --git a/src/subdomains/generic/user/models/bank-data/bank-data.service.ts b/src/subdomains/generic/user/models/bank-data/bank-data.service.ts index 5869117445..cad164ebce 100644 --- a/src/subdomains/generic/user/models/bank-data/bank-data.service.ts +++ b/src/subdomains/generic/user/models/bank-data/bank-data.service.ts @@ -375,7 +375,7 @@ export class BankDataService { .createQueryBuilder() .update('bank_data') .set({ active: false, default: false }) - .where('bank_data.userDataId = :userDataId', { userDataId }) + .where('bank_data."userDataId" = :userDataId', { userDataId }) .andWhere('bank_data.id != :id', { id: entity.id }) .andWhere('bank_data.iban = :iban', { iban: entity.iban }) .execute(); diff --git a/src/subdomains/supporting/address-pool/route/deposit-route.service.ts b/src/subdomains/supporting/address-pool/route/deposit-route.service.ts index 022cc525bc..df1c4a4c96 100644 --- a/src/subdomains/supporting/address-pool/route/deposit-route.service.ts +++ b/src/subdomains/supporting/address-pool/route/deposit-route.service.ts @@ -89,7 +89,7 @@ export class DepositRouteService { .innerJoinAndSelect('depositRoute.user', 'user') .innerJoinAndSelect('user.userData', 'userData') .where( - `EXISTS (SELECT 1 FROM jsonb_array_elements_text((userData."paymentLinksConfig")::jsonb -> 'accessKeys') AS k WHERE k = :key)`, + `EXISTS (SELECT 1 FROM jsonb_array_elements_text(("userData"."paymentLinksConfig")::jsonb -> 'accessKeys') AS k WHERE k = :key)`, { key }, ) .andWhere('depositRoute.active = :active', { active: true }) diff --git a/src/subdomains/supporting/support-issue/services/support-issue.service.ts b/src/subdomains/supporting/support-issue/services/support-issue.service.ts index 457c727e21..05300b4a29 100644 --- a/src/subdomains/supporting/support-issue/services/support-issue.service.ts +++ b/src/subdomains/supporting/support-issue/services/support-issue.service.ts @@ -294,7 +294,7 @@ export class SupportIssueService { for (let i = 0; i < termCount; i++) { const param = `term${i}`; qb.andWhere( - `(issue.name LIKE :${param} OR issue.uid LIKE :${param} OR issue.clerk LIKE :${param} OR userData.firstname LIKE :${param} OR userData.surname LIKE :${param} OR userData.organizationName LIKE :${param} OR EXISTS (SELECT 1 FROM support_message m WHERE m.issueId = issue.id AND m.message LIKE :${param}))`, + `(issue.name LIKE :${param} OR issue.uid LIKE :${param} OR issue.clerk LIKE :${param} OR "userData".firstname LIKE :${param} OR "userData".surname LIKE :${param} OR "userData"."organizationName" LIKE :${param} OR EXISTS (SELECT 1 FROM support_message m WHERE m."issueId" = issue.id AND m.message LIKE :${param}))`, { [param]: `%${terms[i]}%` }, ); } @@ -327,14 +327,14 @@ export class SupportIssueService { (chunk): Promise<{ issueId: string; count: string; lastDate: Date | null; lastAuthor: string | null }[]> => this.messageRepo .createQueryBuilder('m') - .select('m.issueId', 'issueId') + .select('m."issueId"', 'issueId') .addSelect('COUNT(*)', 'count') .addSelect( (sub) => sub .select('m2.created') .from(SupportMessage, 'm2') - .where('m2.issueId = m.issueId') + .where('m2."issueId" = m."issueId"') .orderBy('m2.id', 'DESC') .limit(1), 'lastDate', @@ -344,13 +344,13 @@ export class SupportIssueService { sub .select('m2.author') .from(SupportMessage, 'm2') - .where('m2.issueId = m.issueId') + .where('m2."issueId" = m."issueId"') .orderBy('m2.id', 'DESC') .limit(1), 'lastAuthor', ) - .where('m.issueId IN (:...ids)', { ids: chunk }) - .groupBy('m.issueId') + .where('m."issueId" IN (:...ids)', { ids: chunk }) + .groupBy('m."issueId"') .getRawMany(), 1000, );