fix(issuer/user): fix TypeORM databaseName error in coupon join + wallet frozen column name

- Replace leftJoinAndMapOne(Issuer entity) with two-step query to avoid TypeORM
  metadata lookup error ('Cannot read databaseName of undefined')
- Extract unique issuer IDs from coupon results, then batch-fetch issuers and attach
- Fix wallet entity: map frozenBalance to 'frozen' column (not 'frozen_balance')

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-03-05 06:53:09 -08:00
parent fa512dd2eb
commit 6f70d7a2c2
2 changed files with 16 additions and 8 deletions

View File

@ -1,6 +1,6 @@
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, DataSource } from 'typeorm';
import { Repository, DataSource, In } from 'typeorm';
import { Coupon, CouponStatus } from '../../domain/entities/coupon.entity';
import { Issuer } from '../../domain/entities/issuer.entity';
import {
@ -61,8 +61,6 @@ export class CouponRepository implements ICouponRepository {
const { category, status, search, issuerId, page, limit } = filters;
const qb = this.repo.createQueryBuilder('c');
qb.leftJoinAndMapOne('c.issuer', Issuer, 'i', 'i.id = c.issuer_id');
if (status) qb.andWhere('c.status = :status', { status });
if (issuerId) qb.andWhere('c.issuer_id = :issuerId', { issuerId });
if (category) qb.andWhere('c.category = :category', { category });
@ -76,23 +74,33 @@ export class CouponRepository implements ICouponRepository {
.skip((page - 1) * limit)
.take(limit);
return qb.getManyAndCount();
const [coupons, total] = await qb.getManyAndCount();
await this._attachIssuers(coupons);
return [coupons, total];
}
async findByOwnerWithIssuerJoin(userId: string, filters: CouponListFilters): Promise<[Coupon[], number]> {
const { status, page, limit } = filters;
const qb = this.repo.createQueryBuilder('c');
qb.leftJoinAndMapOne('c.issuer', Issuer, 'i', 'i.id = c.issuer_id');
qb.andWhere('c.owner_user_id = :userId', { userId });
if (status) qb.andWhere('c.status = :status', { status });
qb.orderBy('c.created_at', 'DESC')
.skip((page - 1) * limit)
.take(limit);
return qb.getManyAndCount();
const [coupons, total] = await qb.getManyAndCount();
await this._attachIssuers(coupons);
return [coupons, total];
}
private async _attachIssuers(coupons: Coupon[]): Promise<void> {
if (coupons.length === 0) return;
const issuerIds = [...new Set(coupons.map((c) => c.issuerId))];
const issuers = await this.dataSource.getRepository(Issuer).findBy({ id: In(issuerIds) });
const issuerMap = new Map(issuers.map((i) => [i.id, i]));
coupons.forEach((c) => { c.issuer = issuerMap.get(c.issuerId); });
}
async getOwnerSummary(userId: string): Promise<OwnerSummary> {

View File

@ -9,7 +9,7 @@ export class Wallet {
@PrimaryGeneratedColumn('uuid') id: string;
@Column({ name: 'user_id', type: 'uuid', unique: true }) userId: string;
@Column({ type: 'numeric', precision: 20, scale: 8, default: '0' }) balance: string;
@Column({ name: 'frozen_balance', type: 'numeric', precision: 20, scale: 8, default: '0' }) frozenBalance: string;
@Column({ name: 'frozen', type: 'numeric', precision: 20, scale: 8, default: '0' }) frozenBalance: string;
@Column({ type: 'varchar', length: 10, default: 'USD' }) currency: string;
@Column({ type: 'varchar', length: 20, default: 'active' }) status: string;
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' }) createdAt: Date;