From 2b173d423b537bccb163c59be43687cfb53d96e7 Mon Sep 17 00:00:00 2001 From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com> Date: Fri, 22 May 2026 11:20:54 +0200 Subject: [PATCH 1/5] fix: PSQL query regressions - user.repository: replace MSSQL LIKE character-class with PostgreSQL regex operator in getNextRef; the [0-9] class matched 0 rows on Postgres, causing a null deref on every userData update and mail confirm with account merge - config: set relationLoadStrategy 'query' to avoid PostgreSQL's 1664-column target-list limit when finding deep relation trees (broke GET /v1/transaction/single) --- src/config/config.ts | 1 + src/subdomains/generic/user/models/user/user.repository.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/config/config.ts b/src/config/config.ts index 30bd3d0cd0..d0c709b974 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -221,6 +221,7 @@ export class Configuration { synchronize: process.env.SQL_SYNCHRONIZE === 'true', migrationsRun: process.env.SQL_MIGRATE === 'true', migrations: ['migration/*.js'], + relationLoadStrategy: 'query', connectTimeoutMS: 30000, poolSize: +(process.env.SQL_POOL_MAX ?? 10), logging: process.env.SQL_LOGGING as LoggerOptions, diff --git a/src/subdomains/generic/user/models/user/user.repository.ts b/src/subdomains/generic/user/models/user/user.repository.ts index f97896ec9e..a8591b8c9f 100644 --- a/src/subdomains/generic/user/models/user/user.repository.ts +++ b/src/subdomains/generic/user/models/user/user.repository.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { BaseRepository } from 'src/shared/repositories/base.repository'; import { Util } from 'src/shared/utils/util'; -import { EntityManager, Like } from 'typeorm'; +import { EntityManager, Raw } from 'typeorm'; import { KycLevel } from '../user-data/user-data.enum'; import { User } from './user.entity'; @@ -28,7 +28,7 @@ export class UserRepository extends BaseRepository { // get highest numerical ref const nextRef = await this.findOne({ select: { id: true, ref: true }, - where: { ref: Like('%[0-9]-[0-9]%') }, + where: { ref: Raw((alias) => `${alias} ~ '[0-9]-[0-9]'`) }, order: { ref: 'DESC' }, }).then((u) => +u.ref.replace('-', '') + 1); From b672e07555390382acf6c2db62fad147bf0893a7 Mon Sep 17 00:00:00 2001 From: David May Date: Fri, 22 May 2026 11:34:54 +0200 Subject: [PATCH 2/5] fix: improve ref regex --- src/subdomains/generic/user/models/user/user.repository.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/subdomains/generic/user/models/user/user.repository.ts b/src/subdomains/generic/user/models/user/user.repository.ts index a8591b8c9f..8d9b65ca7c 100644 --- a/src/subdomains/generic/user/models/user/user.repository.ts +++ b/src/subdomains/generic/user/models/user/user.repository.ts @@ -28,7 +28,7 @@ export class UserRepository extends BaseRepository { // get highest numerical ref const nextRef = await this.findOne({ select: { id: true, ref: true }, - where: { ref: Raw((alias) => `${alias} ~ '[0-9]-[0-9]'`) }, + where: { ref: Raw((alias) => `${alias} ~ '^[0-9]{3}-[0-9]{3}$'`) }, order: { ref: 'DESC' }, }).then((u) => +u.ref.replace('-', '') + 1); From e2eaabc80723bcd78d62434ce52230a0a14bfbc2 Mon Sep 17 00:00:00 2001 From: David May Date: Fri, 22 May 2026 11:37:35 +0200 Subject: [PATCH 3/5] fix: circular JSON --- src/subdomains/generic/kyc/entities/kyc-step.entity.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/subdomains/generic/kyc/entities/kyc-step.entity.ts b/src/subdomains/generic/kyc/entities/kyc-step.entity.ts index e3354f0f78..7b1d168fdf 100644 --- a/src/subdomains/generic/kyc/entities/kyc-step.entity.ts +++ b/src/subdomains/generic/kyc/entities/kyc-step.entity.ts @@ -466,4 +466,10 @@ export class KycStep extends IEntity { get isManual(): boolean { return this.type === KycStepType.MANUAL; } + + // prevent circular reference: KycStep.userData -> UserData.kycSteps -> KycStep + toJSON(): any { + const { userData, ...rest } = this; + return rest; + } } From 900d4bd6e9ab2d8c96d4d22064303a7c630a1c40 Mon Sep 17 00:00:00 2001 From: David May Date: Fri, 22 May 2026 11:38:19 +0200 Subject: [PATCH 4/5] fix: split transaction reading --- src/config/config.ts | 1 - .../controllers/transaction.controller.ts | 42 ++++++++++++------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/config/config.ts b/src/config/config.ts index d0c709b974..30bd3d0cd0 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -221,7 +221,6 @@ export class Configuration { synchronize: process.env.SQL_SYNCHRONIZE === 'true', migrationsRun: process.env.SQL_MIGRATE === 'true', migrations: ['migration/*.js'], - relationLoadStrategy: 'query', connectTimeoutMS: 30000, poolSize: +(process.env.SQL_POOL_MAX ?? 10), logging: process.env.SQL_LOGGING as LoggerOptions, diff --git a/src/subdomains/core/history/controllers/transaction.controller.ts b/src/subdomains/core/history/controllers/transaction.controller.ts index e00f0be68b..d62b29f3cc 100644 --- a/src/subdomains/core/history/controllers/transaction.controller.ts +++ b/src/subdomains/core/history/controllers/transaction.controller.ts @@ -710,16 +710,8 @@ export class TransactionController { }, accountId?: number, ): Promise { - const relations: FindOptionsRelations = { - buyCrypto: { - buy: true, - cryptoRoute: true, - cryptoInput: true, - bankTx: true, - chargebackOutput: true, - checkoutTx: true, - }, - buyFiat: { sell: true, cryptoInput: true, bankTx: true, fiatOutput: true }, + // Split into two queries to stay under PostgreSQL's 1664 column limit + const baseRelations: FindOptionsRelations = { refReward: true, bankTx: { transaction: true }, cryptoInput: true, @@ -731,25 +723,43 @@ export class TransactionController { }; let tx: Transaction | TransactionRequest; - if (id) tx = await this.transactionService.getTransactionById(+id, relations); + if (id) tx = await this.transactionService.getTransactionById(+id, baseRelations); const uidParam = uid ?? orderUid; if (uidParam) { tx = Config.formats.transactionUid.test(uidParam) - ? await this.transactionService.getTransactionByUid(uidParam, relations) - : ((await this.transactionService.getTransactionByRequestUid(uidParam, relations)) ?? + ? await this.transactionService.getTransactionByUid(uidParam, baseRelations) + : ((await this.transactionService.getTransactionByRequestUid(uidParam, baseRelations)) ?? (await this.transactionRequestService.getTransactionRequestByUid(uidParam, { user: { userData: true } }))); } if (orderId) tx = - (await this.transactionService.getTransactionByRequestId(+orderId, relations)) ?? + (await this.transactionService.getTransactionByRequestId(+orderId, baseRelations)) ?? (await this.transactionRequestService.getTransactionRequest(+orderId, { user: { userData: true } })); if (externalId && accountId) - tx = await this.transactionService.getTransactionByExternalId(externalId, accountId, relations); + tx = await this.transactionService.getTransactionByExternalId(externalId, accountId, baseRelations); + + if (ckoId) tx = await this.transactionService.getTransactionByCkoId(ckoId, baseRelations); - if (ckoId) tx = await this.transactionService.getTransactionByCkoId(ckoId, relations); + // Load buyCrypto/buyFiat separately + if (tx instanceof Transaction) { + tx.buyCrypto = await this.buyCryptoService.getBuyCryptoByTransactionId(tx.id, { + buy: true, + cryptoRoute: true, + cryptoInput: true, + bankTx: true, + chargebackOutput: true, + checkoutTx: true, + }); + tx.buyFiat = await this.buyFiatService.getBuyFiatByTransactionId(tx.id, { + sell: true, + cryptoInput: true, + bankTx: true, + fiatOutput: true, + }); + } return tx; } From af5d1e2c8b90d25eacc823abec58f534ed08b70e Mon Sep 17 00:00:00 2001 From: David May Date: Fri, 22 May 2026 11:43:35 +0200 Subject: [PATCH 5/5] fix: linter --- src/subdomains/generic/kyc/entities/kyc-step.entity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/subdomains/generic/kyc/entities/kyc-step.entity.ts b/src/subdomains/generic/kyc/entities/kyc-step.entity.ts index 7b1d168fdf..f3a6852c4f 100644 --- a/src/subdomains/generic/kyc/entities/kyc-step.entity.ts +++ b/src/subdomains/generic/kyc/entities/kyc-step.entity.ts @@ -469,7 +469,7 @@ export class KycStep extends IEntity { // prevent circular reference: KycStep.userData -> UserData.kycSteps -> KycStep toJSON(): any { - const { userData, ...rest } = this; + const { userData: _userData, ...rest } = this; return rest; } }