feat: internationalize API error responses by locale
Add shared error codes with zh/en/ms messages, coded app exceptions, and locale-aware global filter. Frontends send X-Locale so error text matches the active UI language. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import { Injectable, BadRequestException, NotFoundException } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from '../../shared/prisma/prisma.service';
|
||||
import { WalletService } from '../ledger/wallet.service';
|
||||
import { AgentsService } from '../agent/agents.service';
|
||||
import { Decimal } from '@prisma/client/runtime/library';
|
||||
import { appBadRequest, appNotFound } from '../../shared/common/app-error';
|
||||
import { generateBatchNo } from '../../shared/common/decorators';
|
||||
import {
|
||||
settleSelection,
|
||||
@@ -95,14 +96,14 @@ export class SettlementService {
|
||||
const match = await this.prisma.match.findFirst({
|
||||
where: { id: matchId, deletedAt: null },
|
||||
});
|
||||
if (!match) throw new NotFoundException('Match not found');
|
||||
if (!match) throw appNotFound('MATCH_NOT_FOUND');
|
||||
|
||||
if (match.isOutright) {
|
||||
if (!winnerTeamId) {
|
||||
throw new BadRequestException('冠军盘结算需指定获胜球队 winnerTeamId');
|
||||
throw appBadRequest('SETTLEMENT_WINNER_REQUIRED');
|
||||
}
|
||||
const team = await this.prisma.team.findUnique({ where: { id: winnerTeamId } });
|
||||
if (!team) throw new BadRequestException('获胜球队不存在');
|
||||
if (!team) throw appBadRequest('SETTLEMENT_WINNER_NOT_FOUND');
|
||||
const outrightSel = await this.prisma.marketSelection.findFirst({
|
||||
where: {
|
||||
market: { matchId, marketType: 'OUTRIGHT_WINNER' },
|
||||
@@ -110,7 +111,7 @@ export class SettlementService {
|
||||
},
|
||||
});
|
||||
if (!outrightSel) {
|
||||
throw new BadRequestException('该球队不在本冠军盘选项中');
|
||||
throw appBadRequest('SETTLEMENT_WINNER_NOT_IN_MARKET');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,7 +150,7 @@ export class SettlementService {
|
||||
opts?: { page?: number; pageSize?: number },
|
||||
) {
|
||||
const score = await this.prisma.matchScore.findUnique({ where: { matchId } });
|
||||
if (!score) throw new BadRequestException('Score not recorded');
|
||||
if (!score) throw appBadRequest('SCORE_NOT_RECORDED');
|
||||
|
||||
const computation = await this.computePreviewComputation(matchId);
|
||||
const batch = await this.prisma.settlementBatch.create({
|
||||
@@ -176,9 +177,9 @@ export class SettlementService {
|
||||
opts?: { page?: number; pageSize?: number },
|
||||
) {
|
||||
const batch = await this.prisma.settlementBatch.findUnique({ where: { id: batchId } });
|
||||
if (!batch) throw new NotFoundException('Batch not found');
|
||||
if (!batch) throw appNotFound('SETTLEMENT_BATCH_NOT_FOUND');
|
||||
if (batch.status !== 'PREVIEW') {
|
||||
throw new BadRequestException('Batch is not in preview');
|
||||
throw appBadRequest('SETTLEMENT_BATCH_NOT_PREVIEW');
|
||||
}
|
||||
|
||||
const computation = await this.computePreviewComputation(batch.matchId);
|
||||
@@ -270,7 +271,7 @@ export class SettlementService {
|
||||
|
||||
private async computePreviewComputation(matchId: bigint) {
|
||||
const score = await this.prisma.matchScore.findUnique({ where: { matchId } });
|
||||
if (!score) throw new BadRequestException('Score not recorded');
|
||||
if (!score) throw appBadRequest('SCORE_NOT_RECORDED');
|
||||
|
||||
const scoreInput: ScoreInput = {
|
||||
htHome: score.htHomeScore ?? 0,
|
||||
@@ -484,13 +485,13 @@ export class SettlementService {
|
||||
where: { id: batchId },
|
||||
include: { match: true },
|
||||
});
|
||||
if (!batch) throw new NotFoundException('Batch not found');
|
||||
if (batch.status !== 'PREVIEW') throw new BadRequestException('Batch already confirmed');
|
||||
if (!batch) throw appNotFound('SETTLEMENT_BATCH_NOT_FOUND');
|
||||
if (batch.status !== 'PREVIEW') throw appBadRequest('SETTLEMENT_BATCH_ALREADY_CONFIRMED');
|
||||
|
||||
const score = await this.prisma.matchScore.findUnique({
|
||||
where: { matchId: batch.matchId },
|
||||
});
|
||||
if (!score) throw new BadRequestException('Score not found');
|
||||
if (!score) throw appBadRequest('SCORE_NOT_FOUND');
|
||||
|
||||
const scoreInput: ScoreInput = {
|
||||
htHome: score.htHomeScore ?? 0,
|
||||
@@ -708,7 +709,7 @@ export class SettlementService {
|
||||
const match = await this.prisma.match.findFirst({
|
||||
where: { id: matchId, deletedAt: null },
|
||||
});
|
||||
if (!match) throw new NotFoundException('Match not found');
|
||||
if (!match) throw appNotFound('MATCH_NOT_FOUND');
|
||||
|
||||
const legs = await this.prisma.betSelection.findMany({
|
||||
where: { matchId },
|
||||
@@ -908,7 +909,7 @@ export class SettlementService {
|
||||
legUpdates.set(sel.id.toString(), result);
|
||||
} else {
|
||||
if (!sel.resultStatus) {
|
||||
throw new BadRequestException(`Parlay bet ${bet.id} has unsettled legs`);
|
||||
throw appBadRequest('PARLAY_UNSETTLED_LEGS', { betId: bet.id.toString() });
|
||||
}
|
||||
result = sel.resultStatus as SelectionResult;
|
||||
}
|
||||
@@ -936,9 +937,9 @@ export class SettlementService {
|
||||
const match = await this.prisma.match.findFirst({
|
||||
where: { id: matchId, deletedAt: null },
|
||||
});
|
||||
if (!match) throw new NotFoundException('Match not found');
|
||||
if (!match) throw appNotFound('MATCH_NOT_FOUND');
|
||||
if (match.status !== 'SETTLED') {
|
||||
throw new BadRequestException('Only settled matches can be resettled');
|
||||
throw appBadRequest('RESETTLE_SETTLED_ONLY');
|
||||
}
|
||||
|
||||
const winnerTeamCode = winnerTeamId
|
||||
@@ -1027,9 +1028,9 @@ export class SettlementService {
|
||||
where: { id: batchId },
|
||||
include: { match: true },
|
||||
});
|
||||
if (!batch) throw new NotFoundException('Batch not found');
|
||||
if (!batch.isResettle) throw new BadRequestException('Not a resettle batch');
|
||||
if (batch.status !== 'PREVIEW') throw new BadRequestException('Batch already confirmed');
|
||||
if (!batch) throw appNotFound('SETTLEMENT_BATCH_NOT_FOUND');
|
||||
if (!batch.isResettle) throw appBadRequest('RESETTLE_BATCH_ONLY');
|
||||
if (batch.status !== 'PREVIEW') throw appBadRequest('SETTLEMENT_BATCH_ALREADY_CONFIRMED');
|
||||
|
||||
const scoreInput: ScoreInput = {
|
||||
htHome: batch.htHomeScore ?? 0,
|
||||
|
||||
Reference in New Issue
Block a user