fix(api): add error handling to remaining 51 CQRS handlers across 8 modules
Wraps every handler's execute() method in a try-catch block that: - Re-throws DomainExceptions to preserve structured error responses - Logs unexpected infrastructure errors with full context - Throws InternalServerErrorException with Vietnamese user message Modules updated: - auth (11 handlers: register, refresh-token, verify-kyc, deletions, profile queries) - listings (7 handlers: create, moderate, upload, status, search, queries) - payments (5 handlers: create, callback, refund, status, transactions) - subscriptions (7 handlers: create, cancel, upgrade, meter, quota, billing, plans) - analytics (8 handlers: reports, events, market-index, district, heatmap, trends, valuation) - search (9 handlers: saved-search CRUD, reindex, sync, geo-search, properties) - notifications (1 handler: send-notification) - agents (3 handlers: quality-score, dashboard, public-profile) Combined with the previous commit (29 handlers in admin, inquiries, leads, reviews), all 80+ CQRS handlers now have comprehensive error handling. Verification: - pnpm typecheck: 0 errors - pnpm test: 1387 tests passed (228 files) Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable, InternalServerErrorException } from '@nestjs/common';
|
||||
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs';
|
||||
import { type PrismaService } from '@modules/shared';
|
||||
import { type PrismaService, DomainException, LoggerService } from '@modules/shared';
|
||||
import { GetAgentByUserIdQuery } from './get-agent-by-user-id.query';
|
||||
|
||||
export interface AgentDto {
|
||||
@@ -20,27 +20,40 @@ export interface AgentDto {
|
||||
@Injectable()
|
||||
@QueryHandler(GetAgentByUserIdQuery)
|
||||
export class GetAgentByUserIdHandler implements IQueryHandler<GetAgentByUserIdQuery> {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly logger: LoggerService,
|
||||
) {}
|
||||
|
||||
async execute(query: GetAgentByUserIdQuery): Promise<AgentDto | null> {
|
||||
const agent = await this.prisma.agent.findUnique({
|
||||
where: { userId: query.userId },
|
||||
});
|
||||
try {
|
||||
const agent = await this.prisma.agent.findUnique({
|
||||
where: { userId: query.userId },
|
||||
});
|
||||
|
||||
if (!agent) return null;
|
||||
if (!agent) return null;
|
||||
|
||||
return {
|
||||
id: agent.id,
|
||||
userId: agent.userId,
|
||||
licenseNumber: agent.licenseNumber,
|
||||
agency: agent.agency,
|
||||
qualityScore: agent.qualityScore,
|
||||
totalDeals: agent.totalDeals,
|
||||
responseTimeAvg: agent.responseTimeAvg,
|
||||
bio: agent.bio,
|
||||
serviceAreas: agent.serviceAreas,
|
||||
isVerified: agent.isVerified,
|
||||
createdAt: agent.createdAt,
|
||||
};
|
||||
return {
|
||||
id: agent.id,
|
||||
userId: agent.userId,
|
||||
licenseNumber: agent.licenseNumber,
|
||||
agency: agent.agency,
|
||||
qualityScore: agent.qualityScore,
|
||||
totalDeals: agent.totalDeals,
|
||||
responseTimeAvg: agent.responseTimeAvg,
|
||||
bio: agent.bio,
|
||||
serviceAreas: agent.serviceAreas,
|
||||
isVerified: agent.isVerified,
|
||||
createdAt: agent.createdAt,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof DomainException) throw error;
|
||||
this.logger.error(
|
||||
`Failed to get agent by user ID: ${error instanceof Error ? error.message : error}`,
|
||||
error instanceof Error ? error.stack : undefined,
|
||||
this.constructor.name,
|
||||
);
|
||||
throw new InternalServerErrorException('Không thể lấy thông tin đại lý');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { Inject, InternalServerErrorException } from '@nestjs/common';
|
||||
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs';
|
||||
import { NotFoundException, CacheService, CachePrefix, CacheTTL } from '@modules/shared';
|
||||
import { DomainException, LoggerService, NotFoundException, CacheService, CachePrefix, CacheTTL } from '@modules/shared';
|
||||
import { USER_REPOSITORY, type IUserRepository } from '../../../domain/repositories/user.repository';
|
||||
import { GetProfileQuery } from './get-profile.query';
|
||||
|
||||
@@ -21,33 +21,44 @@ export class GetProfileHandler implements IQueryHandler<GetProfileQuery> {
|
||||
constructor(
|
||||
@Inject(USER_REPOSITORY) private readonly userRepo: IUserRepository,
|
||||
private readonly cache: CacheService,
|
||||
private readonly logger: LoggerService,
|
||||
) {}
|
||||
|
||||
async execute(query: GetProfileQuery): Promise<UserProfileDto> {
|
||||
const cacheKey = CacheService.buildKey(CachePrefix.USER_PROFILE, query.userId);
|
||||
try {
|
||||
const cacheKey = CacheService.buildKey(CachePrefix.USER_PROFILE, query.userId);
|
||||
|
||||
return this.cache.getOrSet(
|
||||
cacheKey,
|
||||
async () => {
|
||||
const user = await this.userRepo.findById(query.userId);
|
||||
if (!user) {
|
||||
throw new NotFoundException('Người dùng', query.userId);
|
||||
}
|
||||
return this.cache.getOrSet(
|
||||
cacheKey,
|
||||
async () => {
|
||||
const user = await this.userRepo.findById(query.userId);
|
||||
if (!user) {
|
||||
throw new NotFoundException('Người dùng', query.userId);
|
||||
}
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
email: user.email?.value ?? null,
|
||||
phone: user.phone.value,
|
||||
fullName: user.fullName,
|
||||
avatarUrl: user.avatarUrl,
|
||||
role: user.role,
|
||||
kycStatus: user.kycStatus,
|
||||
isActive: user.isActive,
|
||||
createdAt: user.createdAt,
|
||||
};
|
||||
},
|
||||
CacheTTL.USER_PROFILE,
|
||||
'user_profile',
|
||||
);
|
||||
return {
|
||||
id: user.id,
|
||||
email: user.email?.value ?? null,
|
||||
phone: user.phone.value,
|
||||
fullName: user.fullName,
|
||||
avatarUrl: user.avatarUrl,
|
||||
role: user.role,
|
||||
kycStatus: user.kycStatus,
|
||||
isActive: user.isActive,
|
||||
createdAt: user.createdAt,
|
||||
};
|
||||
},
|
||||
CacheTTL.USER_PROFILE,
|
||||
'user_profile',
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof DomainException) throw error;
|
||||
this.logger.error(
|
||||
`Failed to get user profile: ${error instanceof Error ? error.message : error}`,
|
||||
error instanceof Error ? error.stack : undefined,
|
||||
this.constructor.name,
|
||||
);
|
||||
throw new InternalServerErrorException('Không thể lấy thông tin hồ sơ người dùng');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user