fix(api): add error handling to 29 CQRS handlers in admin, inquiries, leads, reviews
Add standardized try-catch error handling pattern to all command and query handlers in the four priority modules: - admin (15 handlers): commands + queries, added LoggerService injection - inquiries (4 handlers): commands + queries - leads (5 handlers): commands + queries - reviews (5 handlers): commands + queries Each handler now: - Wraps execute() in try-catch - Re-throws DomainException subclasses (NotFoundException, etc.) - Logs infrastructure errors via LoggerService - Throws InternalServerErrorException for unexpected failures Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { Inject, InternalServerErrorException } from '@nestjs/common';
|
||||
import { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
||||
import { createId } from '@paralleldrive/cuid2';
|
||||
import { NotFoundException, type PrismaService, type LoggerService } from '@modules/shared';
|
||||
import { DomainException, NotFoundException, type PrismaService, type LoggerService } from '@modules/shared';
|
||||
import { InquiryEntity } from '../../../domain/entities/inquiry.entity';
|
||||
import { INQUIRY_REPOSITORY, type IInquiryRepository } from '../../../domain/repositories/inquiry.repository';
|
||||
import { CreateInquiryCommand } from './create-inquiry.command';
|
||||
@@ -22,38 +22,48 @@ export class CreateInquiryHandler implements ICommandHandler<CreateInquiryComman
|
||||
) {}
|
||||
|
||||
async execute(command: CreateInquiryCommand): Promise<CreateInquiryResult> {
|
||||
// Validate listing exists
|
||||
const listing = await this.prisma.listing.findUnique({
|
||||
where: { id: command.listingId },
|
||||
select: { id: true },
|
||||
});
|
||||
if (!listing) {
|
||||
throw new NotFoundException('Listing', command.listingId);
|
||||
try {
|
||||
// Validate listing exists
|
||||
const listing = await this.prisma.listing.findUnique({
|
||||
where: { id: command.listingId },
|
||||
select: { id: true },
|
||||
});
|
||||
if (!listing) {
|
||||
throw new NotFoundException('Listing', command.listingId);
|
||||
}
|
||||
|
||||
const id = createId();
|
||||
const inquiry = InquiryEntity.createNew(
|
||||
id,
|
||||
command.listingId,
|
||||
command.userId,
|
||||
command.message,
|
||||
command.phone,
|
||||
);
|
||||
|
||||
await this.inquiryRepo.save(inquiry);
|
||||
|
||||
// Publish domain events
|
||||
const events = inquiry.clearDomainEvents();
|
||||
for (const event of events) {
|
||||
this.eventBus.publish(event);
|
||||
}
|
||||
|
||||
this.logger.log(`Inquiry ${id} created by user ${command.userId} for listing ${command.listingId}`, 'CreateInquiryHandler');
|
||||
|
||||
return {
|
||||
id,
|
||||
listingId: command.listingId,
|
||||
createdAt: inquiry.createdAt.toISOString(),
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof DomainException) throw error;
|
||||
this.logger.error(
|
||||
`Failed to create inquiry: ${error instanceof Error ? error.message : String(error)}`,
|
||||
error instanceof Error ? error.stack : undefined,
|
||||
'CreateInquiryHandler',
|
||||
);
|
||||
throw new InternalServerErrorException('Lỗi khi tạo yêu cầu tư vấn');
|
||||
}
|
||||
|
||||
const id = createId();
|
||||
const inquiry = InquiryEntity.createNew(
|
||||
id,
|
||||
command.listingId,
|
||||
command.userId,
|
||||
command.message,
|
||||
command.phone,
|
||||
);
|
||||
|
||||
await this.inquiryRepo.save(inquiry);
|
||||
|
||||
// Publish domain events
|
||||
const events = inquiry.clearDomainEvents();
|
||||
for (const event of events) {
|
||||
this.eventBus.publish(event);
|
||||
}
|
||||
|
||||
this.logger.log(`Inquiry ${id} created by user ${command.userId} for listing ${command.listingId}`, 'CreateInquiryHandler');
|
||||
|
||||
return {
|
||||
id,
|
||||
listingId: command.listingId,
|
||||
createdAt: inquiry.createdAt.toISOString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { Inject, InternalServerErrorException } from '@nestjs/common';
|
||||
import { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
||||
import { ForbiddenException, NotFoundException, type PrismaService, type LoggerService } from '@modules/shared';
|
||||
import { DomainException, ForbiddenException, NotFoundException, type PrismaService, type LoggerService } from '@modules/shared';
|
||||
import { INQUIRY_REPOSITORY, type IInquiryRepository } from '../../../domain/repositories/inquiry.repository';
|
||||
import { MarkInquiryReadCommand } from './mark-inquiry-read.command';
|
||||
|
||||
@@ -14,37 +14,47 @@ export class MarkInquiryReadHandler implements ICommandHandler<MarkInquiryReadCo
|
||||
) {}
|
||||
|
||||
async execute(command: MarkInquiryReadCommand): Promise<void> {
|
||||
const inquiry = await this.inquiryRepo.findById(command.inquiryId);
|
||||
if (!inquiry) {
|
||||
throw new NotFoundException('Inquiry', command.inquiryId);
|
||||
try {
|
||||
const inquiry = await this.inquiryRepo.findById(command.inquiryId);
|
||||
if (!inquiry) {
|
||||
throw new NotFoundException('Inquiry', command.inquiryId);
|
||||
}
|
||||
|
||||
// Verify the requesting user is the listing's agent
|
||||
const listing = await this.prisma.listing.findUnique({
|
||||
where: { id: inquiry.listingId },
|
||||
select: { agentId: true },
|
||||
});
|
||||
if (!listing) {
|
||||
throw new NotFoundException('Listing', inquiry.listingId);
|
||||
}
|
||||
|
||||
const agent = await this.prisma.agent.findUnique({
|
||||
where: { userId: command.agentUserId },
|
||||
select: { id: true },
|
||||
});
|
||||
if (!agent || listing.agentId !== agent.id) {
|
||||
throw new ForbiddenException('Bạn không có quyền đánh dấu yêu cầu tư vấn này');
|
||||
}
|
||||
|
||||
inquiry.markAsRead();
|
||||
await this.inquiryRepo.markAsRead(command.inquiryId);
|
||||
|
||||
// Publish domain events
|
||||
const events = inquiry.clearDomainEvents();
|
||||
for (const event of events) {
|
||||
this.eventBus.publish(event);
|
||||
}
|
||||
|
||||
this.logger.log(`Inquiry ${command.inquiryId} marked as read by agent ${command.agentUserId}`, 'MarkInquiryReadHandler');
|
||||
} catch (error) {
|
||||
if (error instanceof DomainException) throw error;
|
||||
this.logger.error(
|
||||
`Failed to mark inquiry as read: ${error instanceof Error ? error.message : String(error)}`,
|
||||
error instanceof Error ? error.stack : undefined,
|
||||
'MarkInquiryReadHandler',
|
||||
);
|
||||
throw new InternalServerErrorException('Lỗi khi đánh dấu đã đọc');
|
||||
}
|
||||
|
||||
// Verify the requesting user is the listing's agent
|
||||
const listing = await this.prisma.listing.findUnique({
|
||||
where: { id: inquiry.listingId },
|
||||
select: { agentId: true },
|
||||
});
|
||||
if (!listing) {
|
||||
throw new NotFoundException('Listing', inquiry.listingId);
|
||||
}
|
||||
|
||||
const agent = await this.prisma.agent.findUnique({
|
||||
where: { userId: command.agentUserId },
|
||||
select: { id: true },
|
||||
});
|
||||
if (!agent || listing.agentId !== agent.id) {
|
||||
throw new ForbiddenException('Bạn không có quyền đánh dấu yêu cầu tư vấn này');
|
||||
}
|
||||
|
||||
inquiry.markAsRead();
|
||||
await this.inquiryRepo.markAsRead(command.inquiryId);
|
||||
|
||||
// Publish domain events
|
||||
const events = inquiry.clearDomainEvents();
|
||||
for (const event of events) {
|
||||
this.eventBus.publish(event);
|
||||
}
|
||||
|
||||
this.logger.log(`Inquiry ${command.inquiryId} marked as read by agent ${command.agentUserId}`, 'MarkInquiryReadHandler');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { Inject, InternalServerErrorException } from '@nestjs/common';
|
||||
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs';
|
||||
import { NotFoundException, type PrismaService } from '@modules/shared';
|
||||
import { DomainException, NotFoundException, type PrismaService, type LoggerService } from '@modules/shared';
|
||||
import { type InquiryReadDto } from '../../../domain/repositories/inquiry-read.dto';
|
||||
import { INQUIRY_REPOSITORY, type IInquiryRepository, type PaginatedResult } from '../../../domain/repositories/inquiry.repository';
|
||||
import { GetInquiriesByAgentQuery } from './get-inquiries-by-agent.query';
|
||||
@@ -10,21 +10,32 @@ export class GetInquiriesByAgentHandler implements IQueryHandler<GetInquiriesByA
|
||||
constructor(
|
||||
@Inject(INQUIRY_REPOSITORY) private readonly inquiryRepo: IInquiryRepository,
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly logger: LoggerService,
|
||||
) {}
|
||||
|
||||
async execute(query: GetInquiriesByAgentQuery): Promise<PaginatedResult<InquiryReadDto>> {
|
||||
const agent = await this.prisma.agent.findUnique({
|
||||
where: { userId: query.agentUserId },
|
||||
select: { id: true },
|
||||
});
|
||||
if (!agent) {
|
||||
throw new NotFoundException('Agent', query.agentUserId);
|
||||
}
|
||||
try {
|
||||
const agent = await this.prisma.agent.findUnique({
|
||||
where: { userId: query.agentUserId },
|
||||
select: { id: true },
|
||||
});
|
||||
if (!agent) {
|
||||
throw new NotFoundException('Agent', query.agentUserId);
|
||||
}
|
||||
|
||||
return this.inquiryRepo.findByAgent(
|
||||
agent.id,
|
||||
query.page,
|
||||
query.limit,
|
||||
);
|
||||
return this.inquiryRepo.findByAgent(
|
||||
agent.id,
|
||||
query.page,
|
||||
query.limit,
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof DomainException) throw error;
|
||||
this.logger.error(
|
||||
`Failed to get inquiries by agent: ${error instanceof Error ? error.message : String(error)}`,
|
||||
error instanceof Error ? error.stack : undefined,
|
||||
'GetInquiriesByAgentHandler',
|
||||
);
|
||||
throw new InternalServerErrorException('Lỗi khi lấy danh sách yêu cầu tư vấn');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { Inject, InternalServerErrorException } from '@nestjs/common';
|
||||
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs';
|
||||
import { DomainException, type LoggerService } from '@modules/shared';
|
||||
import { type InquiryReadDto } from '../../../domain/repositories/inquiry-read.dto';
|
||||
import { INQUIRY_REPOSITORY, type IInquiryRepository, type PaginatedResult } from '../../../domain/repositories/inquiry.repository';
|
||||
import { GetInquiriesByListingQuery } from './get-inquiries-by-listing.query';
|
||||
@@ -8,13 +9,24 @@ import { GetInquiriesByListingQuery } from './get-inquiries-by-listing.query';
|
||||
export class GetInquiriesByListingHandler implements IQueryHandler<GetInquiriesByListingQuery> {
|
||||
constructor(
|
||||
@Inject(INQUIRY_REPOSITORY) private readonly inquiryRepo: IInquiryRepository,
|
||||
private readonly logger: LoggerService,
|
||||
) {}
|
||||
|
||||
async execute(query: GetInquiriesByListingQuery): Promise<PaginatedResult<InquiryReadDto>> {
|
||||
return this.inquiryRepo.findByListing(
|
||||
query.listingId,
|
||||
query.page,
|
||||
query.limit,
|
||||
);
|
||||
try {
|
||||
return await this.inquiryRepo.findByListing(
|
||||
query.listingId,
|
||||
query.page,
|
||||
query.limit,
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof DomainException) throw error;
|
||||
this.logger.error(
|
||||
`Failed to get inquiries by listing: ${error instanceof Error ? error.message : String(error)}`,
|
||||
error instanceof Error ? error.stack : undefined,
|
||||
'GetInquiriesByListingHandler',
|
||||
);
|
||||
throw new InternalServerErrorException('Lỗi khi lấy danh sách yêu cầu tư vấn');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user