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 { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { type PlanTier } from '@prisma/client';
|
import { type PlanTier } from '@prisma/client';
|
||||||
import { NotFoundException, ValidationException, type PrismaService } from '@modules/shared';
|
import { DomainException, NotFoundException, ValidationException, type PrismaService, type LoggerService } from '@modules/shared';
|
||||||
import { SUBSCRIPTION_REPOSITORY, type ISubscriptionRepository } from '@modules/subscriptions';
|
import { SUBSCRIPTION_REPOSITORY, type ISubscriptionRepository } from '@modules/subscriptions';
|
||||||
import { SubscriptionAdjustedEvent } from '../../../domain/events/subscription-adjusted.event';
|
import { SubscriptionAdjustedEvent } from '../../../domain/events/subscription-adjusted.event';
|
||||||
import { AdjustSubscriptionCommand } from './adjust-subscription.command';
|
import { AdjustSubscriptionCommand } from './adjust-subscription.command';
|
||||||
@@ -20,9 +20,11 @@ export class AdjustSubscriptionHandler implements ICommandHandler<AdjustSubscrip
|
|||||||
@Inject(SUBSCRIPTION_REPOSITORY) private readonly subscriptionRepo: ISubscriptionRepository,
|
@Inject(SUBSCRIPTION_REPOSITORY) private readonly subscriptionRepo: ISubscriptionRepository,
|
||||||
private readonly prisma: PrismaService,
|
private readonly prisma: PrismaService,
|
||||||
private readonly eventBus: EventBus,
|
private readonly eventBus: EventBus,
|
||||||
|
private readonly logger: LoggerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(command: AdjustSubscriptionCommand): Promise<AdjustSubscriptionResult> {
|
async execute(command: AdjustSubscriptionCommand): Promise<AdjustSubscriptionResult> {
|
||||||
|
try {
|
||||||
const tier = command.newPlanTier as PlanTier;
|
const tier = command.newPlanTier as PlanTier;
|
||||||
if (!VALID_TIERS.includes(tier)) {
|
if (!VALID_TIERS.includes(tier)) {
|
||||||
throw new ValidationException(`Gói không hợp lệ: ${command.newPlanTier}`, {
|
throw new ValidationException(`Gói không hợp lệ: ${command.newPlanTier}`, {
|
||||||
@@ -63,5 +65,14 @@ export class AdjustSubscriptionHandler implements ICommandHandler<AdjustSubscrip
|
|||||||
newPlanTier: tier,
|
newPlanTier: tier,
|
||||||
message: `Subscription đã được chuyển sang gói ${tier}`,
|
message: `Subscription đã được chuyển sang gói ${tier}`,
|
||||||
};
|
};
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof DomainException) throw error;
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to adjust subscription for user ${command.userId}: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
error instanceof Error ? error.stack : undefined,
|
||||||
|
'AdjustSubscriptionHandler',
|
||||||
|
);
|
||||||
|
throw new InternalServerErrorException('Lỗi khi điều chỉnh subscription');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { USER_REPOSITORY, type IUserRepository } from '@modules/auth';
|
import { USER_REPOSITORY, type IUserRepository } from '@modules/auth';
|
||||||
import { NotFoundException, ValidationException } from '@modules/shared';
|
import { DomainException, NotFoundException, ValidationException, type LoggerService } from '@modules/shared';
|
||||||
import { KycApprovedEvent } from '../../../domain/events/kyc-approved.event';
|
import { KycApprovedEvent } from '../../../domain/events/kyc-approved.event';
|
||||||
import { ApproveKycCommand } from './approve-kyc.command';
|
import { ApproveKycCommand } from './approve-kyc.command';
|
||||||
|
|
||||||
@@ -16,9 +16,11 @@ export class ApproveKycHandler implements ICommandHandler<ApproveKycCommand> {
|
|||||||
constructor(
|
constructor(
|
||||||
@Inject(USER_REPOSITORY) private readonly userRepo: IUserRepository,
|
@Inject(USER_REPOSITORY) private readonly userRepo: IUserRepository,
|
||||||
private readonly eventBus: EventBus,
|
private readonly eventBus: EventBus,
|
||||||
|
private readonly logger: LoggerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(command: ApproveKycCommand): Promise<ApproveKycResult> {
|
async execute(command: ApproveKycCommand): Promise<ApproveKycResult> {
|
||||||
|
try {
|
||||||
const user = await this.userRepo.findById(command.userId);
|
const user = await this.userRepo.findById(command.userId);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new NotFoundException('Người dùng không tồn tại');
|
throw new NotFoundException('Người dùng không tồn tại');
|
||||||
@@ -41,5 +43,14 @@ export class ApproveKycHandler implements ICommandHandler<ApproveKycCommand> {
|
|||||||
kycStatus: 'VERIFIED',
|
kycStatus: 'VERIFIED',
|
||||||
message: 'KYC đã được duyệt thành công',
|
message: 'KYC đã được duyệt thành công',
|
||||||
};
|
};
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof DomainException) throw error;
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to approve KYC for user ${command.userId}: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
error instanceof Error ? error.stack : undefined,
|
||||||
|
'ApproveKycHandler',
|
||||||
|
);
|
||||||
|
throw new InternalServerErrorException('Lỗi khi duyệt KYC');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { LISTING_REPOSITORY, type IListingRepository } from '@modules/listings';
|
import { LISTING_REPOSITORY, type IListingRepository } from '@modules/listings';
|
||||||
import { NotFoundException, ValidationException } from '@modules/shared';
|
import { DomainException, NotFoundException, ValidationException, type LoggerService } from '@modules/shared';
|
||||||
import { ListingApprovedEvent } from '../../../domain/events/listing-approved.event';
|
import { ListingApprovedEvent } from '../../../domain/events/listing-approved.event';
|
||||||
import { ApproveListingCommand } from './approve-listing.command';
|
import { ApproveListingCommand } from './approve-listing.command';
|
||||||
|
|
||||||
@@ -16,9 +16,11 @@ export class ApproveListingHandler implements ICommandHandler<ApproveListingComm
|
|||||||
constructor(
|
constructor(
|
||||||
@Inject(LISTING_REPOSITORY) private readonly listingRepo: IListingRepository,
|
@Inject(LISTING_REPOSITORY) private readonly listingRepo: IListingRepository,
|
||||||
private readonly eventBus: EventBus,
|
private readonly eventBus: EventBus,
|
||||||
|
private readonly logger: LoggerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(command: ApproveListingCommand): Promise<ApproveListingResult> {
|
async execute(command: ApproveListingCommand): Promise<ApproveListingResult> {
|
||||||
|
try {
|
||||||
const listing = await this.listingRepo.findById(command.listingId);
|
const listing = await this.listingRepo.findById(command.listingId);
|
||||||
if (!listing) {
|
if (!listing) {
|
||||||
throw new NotFoundException('Listing không tồn tại');
|
throw new NotFoundException('Listing không tồn tại');
|
||||||
@@ -48,5 +50,14 @@ export class ApproveListingHandler implements ICommandHandler<ApproveListingComm
|
|||||||
status: 'ACTIVE',
|
status: 'ACTIVE',
|
||||||
message: 'Listing đã được duyệt thành công',
|
message: 'Listing đã được duyệt thành công',
|
||||||
};
|
};
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof DomainException) throw error;
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to approve listing ${command.listingId}: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
error instanceof Error ? error.stack : undefined,
|
||||||
|
'ApproveListingHandler',
|
||||||
|
);
|
||||||
|
throw new InternalServerErrorException('Lỗi khi duyệt listing');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { USER_REPOSITORY, type IUserRepository } from '@modules/auth';
|
import { USER_REPOSITORY, type IUserRepository } from '@modules/auth';
|
||||||
import { NotFoundException, ValidationException } from '@modules/shared';
|
import { DomainException, NotFoundException, ValidationException, type LoggerService } from '@modules/shared';
|
||||||
import { UserBannedEvent } from '../../../domain/events/user-banned.event';
|
import { UserBannedEvent } from '../../../domain/events/user-banned.event';
|
||||||
import { UserUnbannedEvent } from '../../../domain/events/user-unbanned.event';
|
import { UserUnbannedEvent } from '../../../domain/events/user-unbanned.event';
|
||||||
import { BanUserCommand } from './ban-user.command';
|
import { BanUserCommand } from './ban-user.command';
|
||||||
@@ -17,9 +17,11 @@ export class BanUserHandler implements ICommandHandler<BanUserCommand> {
|
|||||||
constructor(
|
constructor(
|
||||||
@Inject(USER_REPOSITORY) private readonly userRepo: IUserRepository,
|
@Inject(USER_REPOSITORY) private readonly userRepo: IUserRepository,
|
||||||
private readonly eventBus: EventBus,
|
private readonly eventBus: EventBus,
|
||||||
|
private readonly logger: LoggerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(command: BanUserCommand): Promise<BanUserResult> {
|
async execute(command: BanUserCommand): Promise<BanUserResult> {
|
||||||
|
try {
|
||||||
const user = await this.userRepo.findById(command.userId);
|
const user = await this.userRepo.findById(command.userId);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new NotFoundException('Người dùng không tồn tại');
|
throw new NotFoundException('Người dùng không tồn tại');
|
||||||
@@ -66,5 +68,14 @@ export class BanUserHandler implements ICommandHandler<BanUserCommand> {
|
|||||||
isActive: false,
|
isActive: false,
|
||||||
message: 'Người dùng đã bị ban',
|
message: 'Người dùng đã bị ban',
|
||||||
};
|
};
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof DomainException) throw error;
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to ban/unban user ${command.userId}: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
error instanceof Error ? error.stack : undefined,
|
||||||
|
'BanUserHandler',
|
||||||
|
);
|
||||||
|
throw new InternalServerErrorException('Lỗi khi ban/unban người dùng');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { LISTING_REPOSITORY, type IListingRepository } from '@modules/listings';
|
import { LISTING_REPOSITORY, type IListingRepository } from '@modules/listings';
|
||||||
import { ValidationException } from '@modules/shared';
|
import { DomainException, ValidationException, type LoggerService } from '@modules/shared';
|
||||||
import { ListingApprovedEvent } from '../../../domain/events/listing-approved.event';
|
import { ListingApprovedEvent } from '../../../domain/events/listing-approved.event';
|
||||||
import { ListingRejectedEvent } from '../../../domain/events/listing-rejected.event';
|
import { ListingRejectedEvent } from '../../../domain/events/listing-rejected.event';
|
||||||
import { BulkModerateListingsCommand } from './bulk-moderate-listings.command';
|
import { BulkModerateListingsCommand } from './bulk-moderate-listings.command';
|
||||||
@@ -17,9 +17,11 @@ export class BulkModerateListingsHandler implements ICommandHandler<BulkModerate
|
|||||||
constructor(
|
constructor(
|
||||||
@Inject(LISTING_REPOSITORY) private readonly listingRepo: IListingRepository,
|
@Inject(LISTING_REPOSITORY) private readonly listingRepo: IListingRepository,
|
||||||
private readonly eventBus: EventBus,
|
private readonly eventBus: EventBus,
|
||||||
|
private readonly logger: LoggerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(command: BulkModerateListingsCommand): Promise<BulkModerateResult> {
|
async execute(command: BulkModerateListingsCommand): Promise<BulkModerateResult> {
|
||||||
|
try {
|
||||||
if (command.listingIds.length === 0) {
|
if (command.listingIds.length === 0) {
|
||||||
throw new ValidationException('Danh sách listing không được rỗng', {});
|
throw new ValidationException('Danh sách listing không được rỗng', {});
|
||||||
}
|
}
|
||||||
@@ -72,5 +74,14 @@ export class BulkModerateListingsHandler implements ICommandHandler<BulkModerate
|
|||||||
succeeded,
|
succeeded,
|
||||||
failed,
|
failed,
|
||||||
};
|
};
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof DomainException) throw error;
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to bulk moderate listings: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
error instanceof Error ? error.stack : undefined,
|
||||||
|
'BulkModerateListingsHandler',
|
||||||
|
);
|
||||||
|
throw new InternalServerErrorException('Lỗi khi duyệt/từ chối hàng loạt listing');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { USER_REPOSITORY, type IUserRepository } from '@modules/auth';
|
import { USER_REPOSITORY, type IUserRepository } from '@modules/auth';
|
||||||
import { NotFoundException, ValidationException } from '@modules/shared';
|
import { DomainException, NotFoundException, ValidationException, type LoggerService } from '@modules/shared';
|
||||||
import { KycRejectedEvent } from '../../../domain/events/kyc-rejected.event';
|
import { KycRejectedEvent } from '../../../domain/events/kyc-rejected.event';
|
||||||
import { RejectKycCommand } from './reject-kyc.command';
|
import { RejectKycCommand } from './reject-kyc.command';
|
||||||
|
|
||||||
@@ -16,9 +16,11 @@ export class RejectKycHandler implements ICommandHandler<RejectKycCommand> {
|
|||||||
constructor(
|
constructor(
|
||||||
@Inject(USER_REPOSITORY) private readonly userRepo: IUserRepository,
|
@Inject(USER_REPOSITORY) private readonly userRepo: IUserRepository,
|
||||||
private readonly eventBus: EventBus,
|
private readonly eventBus: EventBus,
|
||||||
|
private readonly logger: LoggerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(command: RejectKycCommand): Promise<RejectKycResult> {
|
async execute(command: RejectKycCommand): Promise<RejectKycResult> {
|
||||||
|
try {
|
||||||
const user = await this.userRepo.findById(command.userId);
|
const user = await this.userRepo.findById(command.userId);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new NotFoundException('Người dùng không tồn tại');
|
throw new NotFoundException('Người dùng không tồn tại');
|
||||||
@@ -41,5 +43,14 @@ export class RejectKycHandler implements ICommandHandler<RejectKycCommand> {
|
|||||||
kycStatus: 'REJECTED',
|
kycStatus: 'REJECTED',
|
||||||
message: 'KYC đã bị từ chối',
|
message: 'KYC đã bị từ chối',
|
||||||
};
|
};
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof DomainException) throw error;
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to reject KYC for user ${command.userId}: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
error instanceof Error ? error.stack : undefined,
|
||||||
|
'RejectKycHandler',
|
||||||
|
);
|
||||||
|
throw new InternalServerErrorException('Lỗi khi từ chối KYC');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { LISTING_REPOSITORY, type IListingRepository } from '@modules/listings';
|
import { LISTING_REPOSITORY, type IListingRepository } from '@modules/listings';
|
||||||
import { NotFoundException, ValidationException } from '@modules/shared';
|
import { DomainException, NotFoundException, ValidationException, type LoggerService } from '@modules/shared';
|
||||||
import { ListingRejectedEvent } from '../../../domain/events/listing-rejected.event';
|
import { ListingRejectedEvent } from '../../../domain/events/listing-rejected.event';
|
||||||
import { RejectListingCommand } from './reject-listing.command';
|
import { RejectListingCommand } from './reject-listing.command';
|
||||||
|
|
||||||
@@ -16,9 +16,11 @@ export class RejectListingHandler implements ICommandHandler<RejectListingComman
|
|||||||
constructor(
|
constructor(
|
||||||
@Inject(LISTING_REPOSITORY) private readonly listingRepo: IListingRepository,
|
@Inject(LISTING_REPOSITORY) private readonly listingRepo: IListingRepository,
|
||||||
private readonly eventBus: EventBus,
|
private readonly eventBus: EventBus,
|
||||||
|
private readonly logger: LoggerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(command: RejectListingCommand): Promise<RejectListingResult> {
|
async execute(command: RejectListingCommand): Promise<RejectListingResult> {
|
||||||
|
try {
|
||||||
const listing = await this.listingRepo.findById(command.listingId);
|
const listing = await this.listingRepo.findById(command.listingId);
|
||||||
if (!listing) {
|
if (!listing) {
|
||||||
throw new NotFoundException('Listing không tồn tại');
|
throw new NotFoundException('Listing không tồn tại');
|
||||||
@@ -43,5 +45,14 @@ export class RejectListingHandler implements ICommandHandler<RejectListingComman
|
|||||||
status: 'REJECTED',
|
status: 'REJECTED',
|
||||||
message: 'Listing đã bị từ chối',
|
message: 'Listing đã bị từ chối',
|
||||||
};
|
};
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof DomainException) throw error;
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to reject listing ${command.listingId}: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
error instanceof Error ? error.stack : undefined,
|
||||||
|
'RejectListingHandler',
|
||||||
|
);
|
||||||
|
throw new InternalServerErrorException('Lỗi khi từ chối listing');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { USER_REPOSITORY, type IUserRepository } from '@modules/auth';
|
import { USER_REPOSITORY, type IUserRepository } from '@modules/auth';
|
||||||
import { NotFoundException, ValidationException } from '@modules/shared';
|
import { DomainException, NotFoundException, ValidationException, type LoggerService } from '@modules/shared';
|
||||||
import { UserBannedEvent } from '../../../domain/events/user-banned.event';
|
import { UserBannedEvent } from '../../../domain/events/user-banned.event';
|
||||||
import { UserUnbannedEvent } from '../../../domain/events/user-unbanned.event';
|
import { UserUnbannedEvent } from '../../../domain/events/user-unbanned.event';
|
||||||
import { UpdateUserStatusCommand } from './update-user-status.command';
|
import { UpdateUserStatusCommand } from './update-user-status.command';
|
||||||
@@ -17,9 +17,11 @@ export class UpdateUserStatusHandler implements ICommandHandler<UpdateUserStatus
|
|||||||
constructor(
|
constructor(
|
||||||
@Inject(USER_REPOSITORY) private readonly userRepo: IUserRepository,
|
@Inject(USER_REPOSITORY) private readonly userRepo: IUserRepository,
|
||||||
private readonly eventBus: EventBus,
|
private readonly eventBus: EventBus,
|
||||||
|
private readonly logger: LoggerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(command: UpdateUserStatusCommand): Promise<UpdateUserStatusResult> {
|
async execute(command: UpdateUserStatusCommand): Promise<UpdateUserStatusResult> {
|
||||||
|
try {
|
||||||
const user = await this.userRepo.findById(command.userId);
|
const user = await this.userRepo.findById(command.userId);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new NotFoundException('Người dùng không tồn tại');
|
throw new NotFoundException('Người dùng không tồn tại');
|
||||||
@@ -59,5 +61,14 @@ export class UpdateUserStatusHandler implements ICommandHandler<UpdateUserStatus
|
|||||||
isActive: false,
|
isActive: false,
|
||||||
message: 'Người dùng đã bị vô hiệu hóa',
|
message: 'Người dùng đã bị vô hiệu hóa',
|
||||||
};
|
};
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof DomainException) throw error;
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to update user status for ${command.userId}: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
error instanceof Error ? error.stack : undefined,
|
||||||
|
'UpdateUserStatusHandler',
|
||||||
|
);
|
||||||
|
throw new InternalServerErrorException('Lỗi khi cập nhật trạng thái người dùng');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Inject } from '@nestjs/common';
|
import { Inject, InternalServerErrorException } from '@nestjs/common';
|
||||||
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
||||||
|
import { DomainException, type LoggerService } from '@modules/shared';
|
||||||
import {
|
import {
|
||||||
AUDIT_LOG_REPOSITORY,
|
AUDIT_LOG_REPOSITORY,
|
||||||
type IAuditLogRepository,
|
type IAuditLogRepository,
|
||||||
@@ -11,10 +12,12 @@ import { GetAuditLogsQuery } from './get-audit-logs.query';
|
|||||||
export class GetAuditLogsHandler implements IQueryHandler<GetAuditLogsQuery> {
|
export class GetAuditLogsHandler implements IQueryHandler<GetAuditLogsQuery> {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(AUDIT_LOG_REPOSITORY) private readonly auditRepo: IAuditLogRepository,
|
@Inject(AUDIT_LOG_REPOSITORY) private readonly auditRepo: IAuditLogRepository,
|
||||||
|
private readonly logger: LoggerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(query: GetAuditLogsQuery): Promise<AuditLogListResult> {
|
async execute(query: GetAuditLogsQuery): Promise<AuditLogListResult> {
|
||||||
return this.auditRepo.findAll({
|
try {
|
||||||
|
return await this.auditRepo.findAll({
|
||||||
page: query.page,
|
page: query.page,
|
||||||
limit: query.limit,
|
limit: query.limit,
|
||||||
action: query.action,
|
action: query.action,
|
||||||
@@ -24,5 +27,14 @@ export class GetAuditLogsHandler implements IQueryHandler<GetAuditLogsQuery> {
|
|||||||
startDate: query.startDate,
|
startDate: query.startDate,
|
||||||
endDate: query.endDate,
|
endDate: query.endDate,
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof DomainException) throw error;
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to get audit logs: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
error instanceof Error ? error.stack : undefined,
|
||||||
|
'GetAuditLogsHandler',
|
||||||
|
);
|
||||||
|
throw new InternalServerErrorException('Lỗi khi lấy nhật ký kiểm toán');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Inject } from '@nestjs/common';
|
import { Inject, InternalServerErrorException } from '@nestjs/common';
|
||||||
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
||||||
|
import { DomainException, type LoggerService } from '@modules/shared';
|
||||||
import { ADMIN_QUERY_REPOSITORY, type IAdminQueryRepository, type DashboardStats } from '../../../domain/repositories/admin-query.repository';
|
import { ADMIN_QUERY_REPOSITORY, type IAdminQueryRepository, type DashboardStats } from '../../../domain/repositories/admin-query.repository';
|
||||||
import { GetDashboardStatsQuery } from './get-dashboard-stats.query';
|
import { GetDashboardStatsQuery } from './get-dashboard-stats.query';
|
||||||
|
|
||||||
@@ -7,9 +8,20 @@ import { GetDashboardStatsQuery } from './get-dashboard-stats.query';
|
|||||||
export class GetDashboardStatsHandler implements IQueryHandler<GetDashboardStatsQuery> {
|
export class GetDashboardStatsHandler implements IQueryHandler<GetDashboardStatsQuery> {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(ADMIN_QUERY_REPOSITORY) private readonly adminQueryRepo: IAdminQueryRepository,
|
@Inject(ADMIN_QUERY_REPOSITORY) private readonly adminQueryRepo: IAdminQueryRepository,
|
||||||
|
private readonly logger: LoggerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(_query: GetDashboardStatsQuery): Promise<DashboardStats> {
|
async execute(_query: GetDashboardStatsQuery): Promise<DashboardStats> {
|
||||||
return this.adminQueryRepo.getDashboardStats();
|
try {
|
||||||
|
return await this.adminQueryRepo.getDashboardStats();
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof DomainException) throw error;
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to get dashboard stats: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
error instanceof Error ? error.stack : undefined,
|
||||||
|
'GetDashboardStatsHandler',
|
||||||
|
);
|
||||||
|
throw new InternalServerErrorException('Lỗi khi lấy thống kê dashboard');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Inject } from '@nestjs/common';
|
import { Inject, InternalServerErrorException } from '@nestjs/common';
|
||||||
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
||||||
|
import { DomainException, type LoggerService } from '@modules/shared';
|
||||||
import { ADMIN_QUERY_REPOSITORY, type IAdminQueryRepository, type KycQueueResult } from '../../../domain/repositories/admin-query.repository';
|
import { ADMIN_QUERY_REPOSITORY, type IAdminQueryRepository, type KycQueueResult } from '../../../domain/repositories/admin-query.repository';
|
||||||
import { GetKycQueueQuery } from './get-kyc-queue.query';
|
import { GetKycQueueQuery } from './get-kyc-queue.query';
|
||||||
|
|
||||||
@@ -7,9 +8,20 @@ import { GetKycQueueQuery } from './get-kyc-queue.query';
|
|||||||
export class GetKycQueueHandler implements IQueryHandler<GetKycQueueQuery> {
|
export class GetKycQueueHandler implements IQueryHandler<GetKycQueueQuery> {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(ADMIN_QUERY_REPOSITORY) private readonly adminQueryRepo: IAdminQueryRepository,
|
@Inject(ADMIN_QUERY_REPOSITORY) private readonly adminQueryRepo: IAdminQueryRepository,
|
||||||
|
private readonly logger: LoggerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(query: GetKycQueueQuery): Promise<KycQueueResult> {
|
async execute(query: GetKycQueueQuery): Promise<KycQueueResult> {
|
||||||
return this.adminQueryRepo.getKycQueue(query.page, query.limit);
|
try {
|
||||||
|
return await this.adminQueryRepo.getKycQueue(query.page, query.limit);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof DomainException) throw error;
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to get KYC queue: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
error instanceof Error ? error.stack : undefined,
|
||||||
|
'GetKycQueueHandler',
|
||||||
|
);
|
||||||
|
throw new InternalServerErrorException('Lỗi khi lấy danh sách KYC');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Inject } from '@nestjs/common';
|
import { Inject, InternalServerErrorException } from '@nestjs/common';
|
||||||
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
||||||
|
import { DomainException, type LoggerService } from '@modules/shared';
|
||||||
import { ADMIN_QUERY_REPOSITORY, type IAdminQueryRepository, type ModerationQueueResult } from '../../../domain/repositories/admin-query.repository';
|
import { ADMIN_QUERY_REPOSITORY, type IAdminQueryRepository, type ModerationQueueResult } from '../../../domain/repositories/admin-query.repository';
|
||||||
import { GetModerationQueueQuery } from './get-moderation-queue.query';
|
import { GetModerationQueueQuery } from './get-moderation-queue.query';
|
||||||
|
|
||||||
@@ -7,9 +8,20 @@ import { GetModerationQueueQuery } from './get-moderation-queue.query';
|
|||||||
export class GetModerationQueueHandler implements IQueryHandler<GetModerationQueueQuery> {
|
export class GetModerationQueueHandler implements IQueryHandler<GetModerationQueueQuery> {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(ADMIN_QUERY_REPOSITORY) private readonly adminQueryRepo: IAdminQueryRepository,
|
@Inject(ADMIN_QUERY_REPOSITORY) private readonly adminQueryRepo: IAdminQueryRepository,
|
||||||
|
private readonly logger: LoggerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(query: GetModerationQueueQuery): Promise<ModerationQueueResult> {
|
async execute(query: GetModerationQueueQuery): Promise<ModerationQueueResult> {
|
||||||
return this.adminQueryRepo.getModerationQueue(query.page, query.limit);
|
try {
|
||||||
|
return await this.adminQueryRepo.getModerationQueue(query.page, query.limit);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof DomainException) throw error;
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to get moderation queue: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
error instanceof Error ? error.stack : undefined,
|
||||||
|
'GetModerationQueueHandler',
|
||||||
|
);
|
||||||
|
throw new InternalServerErrorException('Lỗi khi lấy danh sách duyệt');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Inject } from '@nestjs/common';
|
import { Inject, InternalServerErrorException } from '@nestjs/common';
|
||||||
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
||||||
|
import { DomainException, type LoggerService } from '@modules/shared';
|
||||||
import { ADMIN_QUERY_REPOSITORY, type IAdminQueryRepository, type RevenueStatsItem } from '../../../domain/repositories/admin-query.repository';
|
import { ADMIN_QUERY_REPOSITORY, type IAdminQueryRepository, type RevenueStatsItem } from '../../../domain/repositories/admin-query.repository';
|
||||||
import { GetRevenueStatsQuery } from './get-revenue-stats.query';
|
import { GetRevenueStatsQuery } from './get-revenue-stats.query';
|
||||||
|
|
||||||
@@ -7,9 +8,20 @@ import { GetRevenueStatsQuery } from './get-revenue-stats.query';
|
|||||||
export class GetRevenueStatsHandler implements IQueryHandler<GetRevenueStatsQuery> {
|
export class GetRevenueStatsHandler implements IQueryHandler<GetRevenueStatsQuery> {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(ADMIN_QUERY_REPOSITORY) private readonly adminQueryRepo: IAdminQueryRepository,
|
@Inject(ADMIN_QUERY_REPOSITORY) private readonly adminQueryRepo: IAdminQueryRepository,
|
||||||
|
private readonly logger: LoggerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(query: GetRevenueStatsQuery): Promise<RevenueStatsItem[]> {
|
async execute(query: GetRevenueStatsQuery): Promise<RevenueStatsItem[]> {
|
||||||
return this.adminQueryRepo.getRevenueStats(query.startDate, query.endDate, query.groupBy);
|
try {
|
||||||
|
return await this.adminQueryRepo.getRevenueStats(query.startDate, query.endDate, query.groupBy);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof DomainException) throw error;
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to get revenue stats: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
error instanceof Error ? error.stack : undefined,
|
||||||
|
'GetRevenueStatsHandler',
|
||||||
|
);
|
||||||
|
throw new InternalServerErrorException('Lỗi khi lấy thống kê doanh thu');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Inject } from '@nestjs/common';
|
import { Inject, InternalServerErrorException } from '@nestjs/common';
|
||||||
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
||||||
import { NotFoundException } from '@modules/shared';
|
import { DomainException, NotFoundException, type LoggerService } from '@modules/shared';
|
||||||
import { ADMIN_QUERY_REPOSITORY, type IAdminQueryRepository, type UserDetail } from '../../../domain/repositories/admin-query.repository';
|
import { ADMIN_QUERY_REPOSITORY, type IAdminQueryRepository, type UserDetail } from '../../../domain/repositories/admin-query.repository';
|
||||||
import { GetUserDetailQuery } from './get-user-detail.query';
|
import { GetUserDetailQuery } from './get-user-detail.query';
|
||||||
|
|
||||||
@@ -8,13 +8,24 @@ import { GetUserDetailQuery } from './get-user-detail.query';
|
|||||||
export class GetUserDetailHandler implements IQueryHandler<GetUserDetailQuery> {
|
export class GetUserDetailHandler implements IQueryHandler<GetUserDetailQuery> {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(ADMIN_QUERY_REPOSITORY) private readonly adminQueryRepo: IAdminQueryRepository,
|
@Inject(ADMIN_QUERY_REPOSITORY) private readonly adminQueryRepo: IAdminQueryRepository,
|
||||||
|
private readonly logger: LoggerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(query: GetUserDetailQuery): Promise<UserDetail> {
|
async execute(query: GetUserDetailQuery): Promise<UserDetail> {
|
||||||
|
try {
|
||||||
const user = await this.adminQueryRepo.getUserDetail(query.userId);
|
const user = await this.adminQueryRepo.getUserDetail(query.userId);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new NotFoundException('Người dùng không tồn tại');
|
throw new NotFoundException('Người dùng không tồn tại');
|
||||||
}
|
}
|
||||||
return user;
|
return user;
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof DomainException) throw error;
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to get user detail for ${query.userId}: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
error instanceof Error ? error.stack : undefined,
|
||||||
|
'GetUserDetailHandler',
|
||||||
|
);
|
||||||
|
throw new InternalServerErrorException('Lỗi khi lấy thông tin người dùng');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Inject } from '@nestjs/common';
|
import { Inject, InternalServerErrorException } from '@nestjs/common';
|
||||||
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
|
||||||
|
import { DomainException, type LoggerService } from '@modules/shared';
|
||||||
import { ADMIN_QUERY_REPOSITORY, type IAdminQueryRepository, type UserListResult } from '../../../domain/repositories/admin-query.repository';
|
import { ADMIN_QUERY_REPOSITORY, type IAdminQueryRepository, type UserListResult } from '../../../domain/repositories/admin-query.repository';
|
||||||
import { GetUsersQuery } from './get-users.query';
|
import { GetUsersQuery } from './get-users.query';
|
||||||
|
|
||||||
@@ -7,15 +8,26 @@ import { GetUsersQuery } from './get-users.query';
|
|||||||
export class GetUsersHandler implements IQueryHandler<GetUsersQuery> {
|
export class GetUsersHandler implements IQueryHandler<GetUsersQuery> {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(ADMIN_QUERY_REPOSITORY) private readonly adminQueryRepo: IAdminQueryRepository,
|
@Inject(ADMIN_QUERY_REPOSITORY) private readonly adminQueryRepo: IAdminQueryRepository,
|
||||||
|
private readonly logger: LoggerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(query: GetUsersQuery): Promise<UserListResult> {
|
async execute(query: GetUsersQuery): Promise<UserListResult> {
|
||||||
return this.adminQueryRepo.getUsers({
|
try {
|
||||||
|
return await this.adminQueryRepo.getUsers({
|
||||||
page: query.page,
|
page: query.page,
|
||||||
limit: query.limit,
|
limit: query.limit,
|
||||||
role: query.role,
|
role: query.role,
|
||||||
isActive: query.isActive,
|
isActive: query.isActive,
|
||||||
search: query.search,
|
search: query.search,
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof DomainException) throw error;
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to get users: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
error instanceof Error ? error.stack : undefined,
|
||||||
|
'GetUsersHandler',
|
||||||
|
);
|
||||||
|
throw new InternalServerErrorException('Lỗi khi lấy danh sách người dùng');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { createId } from '@paralleldrive/cuid2';
|
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 { InquiryEntity } from '../../../domain/entities/inquiry.entity';
|
||||||
import { INQUIRY_REPOSITORY, type IInquiryRepository } from '../../../domain/repositories/inquiry.repository';
|
import { INQUIRY_REPOSITORY, type IInquiryRepository } from '../../../domain/repositories/inquiry.repository';
|
||||||
import { CreateInquiryCommand } from './create-inquiry.command';
|
import { CreateInquiryCommand } from './create-inquiry.command';
|
||||||
@@ -22,6 +22,7 @@ export class CreateInquiryHandler implements ICommandHandler<CreateInquiryComman
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(command: CreateInquiryCommand): Promise<CreateInquiryResult> {
|
async execute(command: CreateInquiryCommand): Promise<CreateInquiryResult> {
|
||||||
|
try {
|
||||||
// Validate listing exists
|
// Validate listing exists
|
||||||
const listing = await this.prisma.listing.findUnique({
|
const listing = await this.prisma.listing.findUnique({
|
||||||
where: { id: command.listingId },
|
where: { id: command.listingId },
|
||||||
@@ -55,5 +56,14 @@ export class CreateInquiryHandler implements ICommandHandler<CreateInquiryComman
|
|||||||
listingId: command.listingId,
|
listingId: command.listingId,
|
||||||
createdAt: inquiry.createdAt.toISOString(),
|
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');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 { 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 { INQUIRY_REPOSITORY, type IInquiryRepository } from '../../../domain/repositories/inquiry.repository';
|
||||||
import { MarkInquiryReadCommand } from './mark-inquiry-read.command';
|
import { MarkInquiryReadCommand } from './mark-inquiry-read.command';
|
||||||
|
|
||||||
@@ -14,6 +14,7 @@ export class MarkInquiryReadHandler implements ICommandHandler<MarkInquiryReadCo
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(command: MarkInquiryReadCommand): Promise<void> {
|
async execute(command: MarkInquiryReadCommand): Promise<void> {
|
||||||
|
try {
|
||||||
const inquiry = await this.inquiryRepo.findById(command.inquiryId);
|
const inquiry = await this.inquiryRepo.findById(command.inquiryId);
|
||||||
if (!inquiry) {
|
if (!inquiry) {
|
||||||
throw new NotFoundException('Inquiry', command.inquiryId);
|
throw new NotFoundException('Inquiry', command.inquiryId);
|
||||||
@@ -46,5 +47,14 @@ export class MarkInquiryReadHandler implements ICommandHandler<MarkInquiryReadCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.logger.log(`Inquiry ${command.inquiryId} marked as read by agent ${command.agentUserId}`, 'MarkInquiryReadHandler');
|
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');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Inject } from '@nestjs/common';
|
import { Inject, InternalServerErrorException } from '@nestjs/common';
|
||||||
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs';
|
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 { type InquiryReadDto } from '../../../domain/repositories/inquiry-read.dto';
|
||||||
import { INQUIRY_REPOSITORY, type IInquiryRepository, type PaginatedResult } from '../../../domain/repositories/inquiry.repository';
|
import { INQUIRY_REPOSITORY, type IInquiryRepository, type PaginatedResult } from '../../../domain/repositories/inquiry.repository';
|
||||||
import { GetInquiriesByAgentQuery } from './get-inquiries-by-agent.query';
|
import { GetInquiriesByAgentQuery } from './get-inquiries-by-agent.query';
|
||||||
@@ -10,9 +10,11 @@ export class GetInquiriesByAgentHandler implements IQueryHandler<GetInquiriesByA
|
|||||||
constructor(
|
constructor(
|
||||||
@Inject(INQUIRY_REPOSITORY) private readonly inquiryRepo: IInquiryRepository,
|
@Inject(INQUIRY_REPOSITORY) private readonly inquiryRepo: IInquiryRepository,
|
||||||
private readonly prisma: PrismaService,
|
private readonly prisma: PrismaService,
|
||||||
|
private readonly logger: LoggerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(query: GetInquiriesByAgentQuery): Promise<PaginatedResult<InquiryReadDto>> {
|
async execute(query: GetInquiriesByAgentQuery): Promise<PaginatedResult<InquiryReadDto>> {
|
||||||
|
try {
|
||||||
const agent = await this.prisma.agent.findUnique({
|
const agent = await this.prisma.agent.findUnique({
|
||||||
where: { userId: query.agentUserId },
|
where: { userId: query.agentUserId },
|
||||||
select: { id: true },
|
select: { id: true },
|
||||||
@@ -26,5 +28,14 @@ export class GetInquiriesByAgentHandler implements IQueryHandler<GetInquiriesByA
|
|||||||
query.page,
|
query.page,
|
||||||
query.limit,
|
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 { type IQueryHandler, QueryHandler } from '@nestjs/cqrs';
|
||||||
|
import { DomainException, type LoggerService } from '@modules/shared';
|
||||||
import { type InquiryReadDto } from '../../../domain/repositories/inquiry-read.dto';
|
import { type InquiryReadDto } from '../../../domain/repositories/inquiry-read.dto';
|
||||||
import { INQUIRY_REPOSITORY, type IInquiryRepository, type PaginatedResult } from '../../../domain/repositories/inquiry.repository';
|
import { INQUIRY_REPOSITORY, type IInquiryRepository, type PaginatedResult } from '../../../domain/repositories/inquiry.repository';
|
||||||
import { GetInquiriesByListingQuery } from './get-inquiries-by-listing.query';
|
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> {
|
export class GetInquiriesByListingHandler implements IQueryHandler<GetInquiriesByListingQuery> {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(INQUIRY_REPOSITORY) private readonly inquiryRepo: IInquiryRepository,
|
@Inject(INQUIRY_REPOSITORY) private readonly inquiryRepo: IInquiryRepository,
|
||||||
|
private readonly logger: LoggerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(query: GetInquiriesByListingQuery): Promise<PaginatedResult<InquiryReadDto>> {
|
async execute(query: GetInquiriesByListingQuery): Promise<PaginatedResult<InquiryReadDto>> {
|
||||||
return this.inquiryRepo.findByListing(
|
try {
|
||||||
|
return await this.inquiryRepo.findByListing(
|
||||||
query.listingId,
|
query.listingId,
|
||||||
query.page,
|
query.page,
|
||||||
query.limit,
|
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');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { createId } from '@paralleldrive/cuid2';
|
import { createId } from '@paralleldrive/cuid2';
|
||||||
import { NotFoundException, ValidationException, type PrismaService, type LoggerService } from '@modules/shared';
|
import { DomainException, NotFoundException, ValidationException, type PrismaService, type LoggerService } from '@modules/shared';
|
||||||
import { LeadEntity } from '../../../domain/entities/lead.entity';
|
import { LeadEntity } from '../../../domain/entities/lead.entity';
|
||||||
import { LEAD_REPOSITORY, type ILeadRepository } from '../../../domain/repositories/lead.repository';
|
import { LEAD_REPOSITORY, type ILeadRepository } from '../../../domain/repositories/lead.repository';
|
||||||
import { LeadScore } from '../../../domain/value-objects/lead-score.vo';
|
import { LeadScore } from '../../../domain/value-objects/lead-score.vo';
|
||||||
@@ -23,6 +23,7 @@ export class CreateLeadHandler implements ICommandHandler<CreateLeadCommand> {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(command: CreateLeadCommand): Promise<CreateLeadResult> {
|
async execute(command: CreateLeadCommand): Promise<CreateLeadResult> {
|
||||||
|
try {
|
||||||
// Look up agent by userId
|
// Look up agent by userId
|
||||||
const agent = await this.prisma.agent.findUnique({
|
const agent = await this.prisma.agent.findUnique({
|
||||||
where: { userId: command.agentUserId },
|
where: { userId: command.agentUserId },
|
||||||
@@ -68,5 +69,14 @@ export class CreateLeadHandler implements ICommandHandler<CreateLeadCommand> {
|
|||||||
status: lead.status,
|
status: lead.status,
|
||||||
createdAt: lead.createdAt.toISOString(),
|
createdAt: lead.createdAt.toISOString(),
|
||||||
};
|
};
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof DomainException) throw error;
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to create lead: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
error instanceof Error ? error.stack : undefined,
|
||||||
|
'CreateLeadHandler',
|
||||||
|
);
|
||||||
|
throw new InternalServerErrorException('Lỗi khi tạo lead');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 { 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 { LEAD_REPOSITORY, type ILeadRepository } from '../../../domain/repositories/lead.repository';
|
import { LEAD_REPOSITORY, type ILeadRepository } from '../../../domain/repositories/lead.repository';
|
||||||
import { DeleteLeadCommand } from './delete-lead.command';
|
import { DeleteLeadCommand } from './delete-lead.command';
|
||||||
|
|
||||||
@@ -14,6 +14,7 @@ export class DeleteLeadHandler implements ICommandHandler<DeleteLeadCommand> {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(command: DeleteLeadCommand): Promise<void> {
|
async execute(command: DeleteLeadCommand): Promise<void> {
|
||||||
|
try {
|
||||||
// Look up agent by userId
|
// Look up agent by userId
|
||||||
const agent = await this.prisma.agent.findUnique({
|
const agent = await this.prisma.agent.findUnique({
|
||||||
where: { userId: command.agentUserId },
|
where: { userId: command.agentUserId },
|
||||||
@@ -35,5 +36,14 @@ export class DeleteLeadHandler implements ICommandHandler<DeleteLeadCommand> {
|
|||||||
await this.leadRepo.delete(command.leadId);
|
await this.leadRepo.delete(command.leadId);
|
||||||
|
|
||||||
this.logger.log(`Lead ${command.leadId} deleted by agent ${agent.id}`, 'DeleteLeadHandler');
|
this.logger.log(`Lead ${command.leadId} deleted by agent ${agent.id}`, 'DeleteLeadHandler');
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof DomainException) throw error;
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to delete lead: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
error instanceof Error ? error.stack : undefined,
|
||||||
|
'DeleteLeadHandler',
|
||||||
|
);
|
||||||
|
throw new InternalServerErrorException('Lỗi khi xóa lead');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 { 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 { type LeadStatus } from '../../../domain/entities/lead.entity';
|
import { type LeadStatus } from '../../../domain/entities/lead.entity';
|
||||||
import { LEAD_REPOSITORY, type ILeadRepository } from '../../../domain/repositories/lead.repository';
|
import { LEAD_REPOSITORY, type ILeadRepository } from '../../../domain/repositories/lead.repository';
|
||||||
import { UpdateLeadStatusCommand } from './update-lead-status.command';
|
import { UpdateLeadStatusCommand } from './update-lead-status.command';
|
||||||
@@ -15,6 +15,7 @@ export class UpdateLeadStatusHandler implements ICommandHandler<UpdateLeadStatus
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(command: UpdateLeadStatusCommand): Promise<void> {
|
async execute(command: UpdateLeadStatusCommand): Promise<void> {
|
||||||
|
try {
|
||||||
// Look up agent by userId
|
// Look up agent by userId
|
||||||
const agent = await this.prisma.agent.findUnique({
|
const agent = await this.prisma.agent.findUnique({
|
||||||
where: { userId: command.agentUserId },
|
where: { userId: command.agentUserId },
|
||||||
@@ -43,5 +44,14 @@ export class UpdateLeadStatusHandler implements ICommandHandler<UpdateLeadStatus
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.logger.log(`Lead ${command.leadId} status updated to ${command.newStatus} by agent ${agent.id}`, 'UpdateLeadStatusHandler');
|
this.logger.log(`Lead ${command.leadId} status updated to ${command.newStatus} by agent ${agent.id}`, 'UpdateLeadStatusHandler');
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof DomainException) throw error;
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to update lead status: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
error instanceof Error ? error.stack : undefined,
|
||||||
|
'UpdateLeadStatusHandler',
|
||||||
|
);
|
||||||
|
throw new InternalServerErrorException('Lỗi khi cập nhật trạng thái lead');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Inject } from '@nestjs/common';
|
import { Inject, InternalServerErrorException } from '@nestjs/common';
|
||||||
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs';
|
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 { LEAD_REPOSITORY, type ILeadRepository, type LeadStatsData } from '../../../domain/repositories/lead.repository';
|
import { LEAD_REPOSITORY, type ILeadRepository, type LeadStatsData } from '../../../domain/repositories/lead.repository';
|
||||||
import { GetLeadStatsQuery } from './get-lead-stats.query';
|
import { GetLeadStatsQuery } from './get-lead-stats.query';
|
||||||
|
|
||||||
@@ -9,9 +9,11 @@ export class GetLeadStatsHandler implements IQueryHandler<GetLeadStatsQuery> {
|
|||||||
constructor(
|
constructor(
|
||||||
@Inject(LEAD_REPOSITORY) private readonly leadRepo: ILeadRepository,
|
@Inject(LEAD_REPOSITORY) private readonly leadRepo: ILeadRepository,
|
||||||
private readonly prisma: PrismaService,
|
private readonly prisma: PrismaService,
|
||||||
|
private readonly logger: LoggerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(query: GetLeadStatsQuery): Promise<LeadStatsData> {
|
async execute(query: GetLeadStatsQuery): Promise<LeadStatsData> {
|
||||||
|
try {
|
||||||
// Look up agent by userId
|
// Look up agent by userId
|
||||||
const agent = await this.prisma.agent.findUnique({
|
const agent = await this.prisma.agent.findUnique({
|
||||||
where: { userId: query.agentUserId },
|
where: { userId: query.agentUserId },
|
||||||
@@ -21,5 +23,14 @@ export class GetLeadStatsHandler implements IQueryHandler<GetLeadStatsQuery> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return this.leadRepo.getStatsByAgent(agent.id);
|
return this.leadRepo.getStatsByAgent(agent.id);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof DomainException) throw error;
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to get lead stats: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
error instanceof Error ? error.stack : undefined,
|
||||||
|
'GetLeadStatsHandler',
|
||||||
|
);
|
||||||
|
throw new InternalServerErrorException('Lỗi khi lấy thống kê lead');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Inject } from '@nestjs/common';
|
import { Inject, InternalServerErrorException } from '@nestjs/common';
|
||||||
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs';
|
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 LeadReadDto } from '../../../domain/repositories/lead-read.dto';
|
import { type LeadReadDto } from '../../../domain/repositories/lead-read.dto';
|
||||||
import { LEAD_REPOSITORY, type ILeadRepository, type PaginatedResult } from '../../../domain/repositories/lead.repository';
|
import { LEAD_REPOSITORY, type ILeadRepository, type PaginatedResult } from '../../../domain/repositories/lead.repository';
|
||||||
import { GetLeadsByAgentQuery } from './get-leads-by-agent.query';
|
import { GetLeadsByAgentQuery } from './get-leads-by-agent.query';
|
||||||
@@ -10,9 +10,11 @@ export class GetLeadsByAgentHandler implements IQueryHandler<GetLeadsByAgentQuer
|
|||||||
constructor(
|
constructor(
|
||||||
@Inject(LEAD_REPOSITORY) private readonly leadRepo: ILeadRepository,
|
@Inject(LEAD_REPOSITORY) private readonly leadRepo: ILeadRepository,
|
||||||
private readonly prisma: PrismaService,
|
private readonly prisma: PrismaService,
|
||||||
|
private readonly logger: LoggerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(query: GetLeadsByAgentQuery): Promise<PaginatedResult<LeadReadDto>> {
|
async execute(query: GetLeadsByAgentQuery): Promise<PaginatedResult<LeadReadDto>> {
|
||||||
|
try {
|
||||||
// Look up agent by userId
|
// Look up agent by userId
|
||||||
const agent = await this.prisma.agent.findUnique({
|
const agent = await this.prisma.agent.findUnique({
|
||||||
where: { userId: query.agentUserId },
|
where: { userId: query.agentUserId },
|
||||||
@@ -27,5 +29,14 @@ export class GetLeadsByAgentHandler implements IQueryHandler<GetLeadsByAgentQuer
|
|||||||
query.page,
|
query.page,
|
||||||
query.limit,
|
query.limit,
|
||||||
);
|
);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof DomainException) throw error;
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to get leads by agent: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
error instanceof Error ? error.stack : undefined,
|
||||||
|
'GetLeadsByAgentHandler',
|
||||||
|
);
|
||||||
|
throw new InternalServerErrorException('Lỗi khi lấy danh sách lead');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Inject } from '@nestjs/common';
|
import { Inject, InternalServerErrorException } from '@nestjs/common';
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports -- NestJS DI requires value imports for emitDecoratorMetadata
|
// eslint-disable-next-line @typescript-eslint/consistent-type-imports -- NestJS DI requires value imports for emitDecoratorMetadata
|
||||||
import { CommandHandler, EventBus, ICommandHandler } from '@nestjs/cqrs';
|
import { CommandHandler, EventBus, ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { createId } from '@paralleldrive/cuid2';
|
import { createId } from '@paralleldrive/cuid2';
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports -- NestJS DI requires value imports for emitDecoratorMetadata
|
// eslint-disable-next-line @typescript-eslint/consistent-type-imports -- NestJS DI requires value imports for emitDecoratorMetadata
|
||||||
import { ConflictException, ValidationException, LoggerService } from '@modules/shared';
|
import { ConflictException, DomainException, ValidationException, LoggerService } from '@modules/shared';
|
||||||
import { ReviewEntity } from '../../../domain/entities/review.entity';
|
import { ReviewEntity } from '../../../domain/entities/review.entity';
|
||||||
import { REVIEW_REPOSITORY, type IReviewRepository } from '../../../domain/repositories/review.repository';
|
import { REVIEW_REPOSITORY, type IReviewRepository } from '../../../domain/repositories/review.repository';
|
||||||
import { Rating } from '../../../domain/value-objects/rating.vo';
|
import { Rating } from '../../../domain/value-objects/rating.vo';
|
||||||
@@ -26,6 +26,7 @@ export class CreateReviewHandler implements ICommandHandler<CreateReviewCommand>
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(command: CreateReviewCommand): Promise<CreateReviewResult> {
|
async execute(command: CreateReviewCommand): Promise<CreateReviewResult> {
|
||||||
|
try {
|
||||||
// Validate rating value object
|
// Validate rating value object
|
||||||
const ratingResult = Rating.create(command.rating);
|
const ratingResult = Rating.create(command.rating);
|
||||||
if (ratingResult.isErr) {
|
if (ratingResult.isErr) {
|
||||||
@@ -75,5 +76,14 @@ export class CreateReviewHandler implements ICommandHandler<CreateReviewCommand>
|
|||||||
targetId: command.targetId,
|
targetId: command.targetId,
|
||||||
createdAt: review.createdAt.toISOString(),
|
createdAt: review.createdAt.toISOString(),
|
||||||
};
|
};
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof DomainException) throw error;
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to create review: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
error instanceof Error ? error.stack : undefined,
|
||||||
|
'CreateReviewHandler',
|
||||||
|
);
|
||||||
|
throw new InternalServerErrorException('Lỗi khi tạo đánh giá');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Inject } from '@nestjs/common';
|
import { Inject, InternalServerErrorException } from '@nestjs/common';
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports -- NestJS DI requires value imports for emitDecoratorMetadata
|
// eslint-disable-next-line @typescript-eslint/consistent-type-imports -- NestJS DI requires value imports for emitDecoratorMetadata
|
||||||
import { CommandHandler, EventBus, ICommandHandler } from '@nestjs/cqrs';
|
import { CommandHandler, EventBus, ICommandHandler } from '@nestjs/cqrs';
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports -- NestJS DI requires value imports for emitDecoratorMetadata
|
// eslint-disable-next-line @typescript-eslint/consistent-type-imports -- NestJS DI requires value imports for emitDecoratorMetadata
|
||||||
import { ForbiddenException, NotFoundException, LoggerService } from '@modules/shared';
|
import { DomainException, ForbiddenException, NotFoundException, LoggerService } from '@modules/shared';
|
||||||
import { REVIEW_REPOSITORY, type IReviewRepository } from '../../../domain/repositories/review.repository';
|
import { REVIEW_REPOSITORY, type IReviewRepository } from '../../../domain/repositories/review.repository';
|
||||||
import { DeleteReviewCommand } from './delete-review.command';
|
import { DeleteReviewCommand } from './delete-review.command';
|
||||||
|
|
||||||
@@ -15,6 +15,7 @@ export class DeleteReviewHandler implements ICommandHandler<DeleteReviewCommand>
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(command: DeleteReviewCommand): Promise<void> {
|
async execute(command: DeleteReviewCommand): Promise<void> {
|
||||||
|
try {
|
||||||
const review = await this.reviewRepo.findById(command.reviewId);
|
const review = await this.reviewRepo.findById(command.reviewId);
|
||||||
if (!review) {
|
if (!review) {
|
||||||
throw new NotFoundException('Review', command.reviewId);
|
throw new NotFoundException('Review', command.reviewId);
|
||||||
@@ -34,5 +35,14 @@ export class DeleteReviewHandler implements ICommandHandler<DeleteReviewCommand>
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.logger.log(`Review ${command.reviewId} deleted by user ${command.userId}`, 'DeleteReviewHandler');
|
this.logger.log(`Review ${command.reviewId} deleted by user ${command.userId}`, 'DeleteReviewHandler');
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof DomainException) throw error;
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to delete review: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
error instanceof Error ? error.stack : undefined,
|
||||||
|
'DeleteReviewHandler',
|
||||||
|
);
|
||||||
|
throw new InternalServerErrorException('Lỗi khi xóa đánh giá');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Inject } from '@nestjs/common';
|
import { Inject, InternalServerErrorException } from '@nestjs/common';
|
||||||
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs';
|
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs';
|
||||||
|
import { DomainException, type LoggerService } from '@modules/shared';
|
||||||
import { type ReviewStatsData } from '../../../domain/repositories/review-read.dto';
|
import { type ReviewStatsData } from '../../../domain/repositories/review-read.dto';
|
||||||
import { REVIEW_REPOSITORY, type IReviewRepository } from '../../../domain/repositories/review.repository';
|
import { REVIEW_REPOSITORY, type IReviewRepository } from '../../../domain/repositories/review.repository';
|
||||||
import { GetAverageRatingQuery } from './get-average-rating.query';
|
import { GetAverageRatingQuery } from './get-average-rating.query';
|
||||||
@@ -8,9 +9,20 @@ import { GetAverageRatingQuery } from './get-average-rating.query';
|
|||||||
export class GetAverageRatingHandler implements IQueryHandler<GetAverageRatingQuery> {
|
export class GetAverageRatingHandler implements IQueryHandler<GetAverageRatingQuery> {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(REVIEW_REPOSITORY) private readonly reviewRepo: IReviewRepository,
|
@Inject(REVIEW_REPOSITORY) private readonly reviewRepo: IReviewRepository,
|
||||||
|
private readonly logger: LoggerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(query: GetAverageRatingQuery): Promise<ReviewStatsData> {
|
async execute(query: GetAverageRatingQuery): Promise<ReviewStatsData> {
|
||||||
return this.reviewRepo.getStats(query.targetType, query.targetId);
|
try {
|
||||||
|
return await this.reviewRepo.getStats(query.targetType, query.targetId);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof DomainException) throw error;
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to get average rating: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
error instanceof Error ? error.stack : undefined,
|
||||||
|
'GetAverageRatingHandler',
|
||||||
|
);
|
||||||
|
throw new InternalServerErrorException('Lỗi khi lấy đánh giá trung bình');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Inject } from '@nestjs/common';
|
import { Inject, InternalServerErrorException } from '@nestjs/common';
|
||||||
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs';
|
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs';
|
||||||
|
import { DomainException, type LoggerService } from '@modules/shared';
|
||||||
import { type ReviewItemData } from '../../../domain/repositories/review-read.dto';
|
import { type ReviewItemData } from '../../../domain/repositories/review-read.dto';
|
||||||
import { REVIEW_REPOSITORY, type IReviewRepository, type PaginatedResult } from '../../../domain/repositories/review.repository';
|
import { REVIEW_REPOSITORY, type IReviewRepository, type PaginatedResult } from '../../../domain/repositories/review.repository';
|
||||||
import { GetReviewsByTargetQuery } from './get-reviews-by-target.query';
|
import { GetReviewsByTargetQuery } from './get-reviews-by-target.query';
|
||||||
@@ -8,14 +9,25 @@ import { GetReviewsByTargetQuery } from './get-reviews-by-target.query';
|
|||||||
export class GetReviewsByTargetHandler implements IQueryHandler<GetReviewsByTargetQuery> {
|
export class GetReviewsByTargetHandler implements IQueryHandler<GetReviewsByTargetQuery> {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(REVIEW_REPOSITORY) private readonly reviewRepo: IReviewRepository,
|
@Inject(REVIEW_REPOSITORY) private readonly reviewRepo: IReviewRepository,
|
||||||
|
private readonly logger: LoggerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(query: GetReviewsByTargetQuery): Promise<PaginatedResult<ReviewItemData>> {
|
async execute(query: GetReviewsByTargetQuery): Promise<PaginatedResult<ReviewItemData>> {
|
||||||
return this.reviewRepo.findByTarget(
|
try {
|
||||||
|
return await this.reviewRepo.findByTarget(
|
||||||
query.targetType,
|
query.targetType,
|
||||||
query.targetId,
|
query.targetId,
|
||||||
query.page,
|
query.page,
|
||||||
query.limit,
|
query.limit,
|
||||||
);
|
);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof DomainException) throw error;
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to get reviews by target: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
error instanceof Error ? error.stack : undefined,
|
||||||
|
'GetReviewsByTargetHandler',
|
||||||
|
);
|
||||||
|
throw new InternalServerErrorException('Lỗi khi lấy danh sách đánh giá');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Inject } from '@nestjs/common';
|
import { Inject, InternalServerErrorException } from '@nestjs/common';
|
||||||
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs';
|
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs';
|
||||||
|
import { DomainException, type LoggerService } from '@modules/shared';
|
||||||
import { type ReviewItemData } from '../../../domain/repositories/review-read.dto';
|
import { type ReviewItemData } from '../../../domain/repositories/review-read.dto';
|
||||||
import { REVIEW_REPOSITORY, type IReviewRepository, type PaginatedResult } from '../../../domain/repositories/review.repository';
|
import { REVIEW_REPOSITORY, type IReviewRepository, type PaginatedResult } from '../../../domain/repositories/review.repository';
|
||||||
import { GetReviewsByUserQuery } from './get-reviews-by-user.query';
|
import { GetReviewsByUserQuery } from './get-reviews-by-user.query';
|
||||||
@@ -8,9 +9,20 @@ import { GetReviewsByUserQuery } from './get-reviews-by-user.query';
|
|||||||
export class GetReviewsByUserHandler implements IQueryHandler<GetReviewsByUserQuery> {
|
export class GetReviewsByUserHandler implements IQueryHandler<GetReviewsByUserQuery> {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(REVIEW_REPOSITORY) private readonly reviewRepo: IReviewRepository,
|
@Inject(REVIEW_REPOSITORY) private readonly reviewRepo: IReviewRepository,
|
||||||
|
private readonly logger: LoggerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(query: GetReviewsByUserQuery): Promise<PaginatedResult<ReviewItemData>> {
|
async execute(query: GetReviewsByUserQuery): Promise<PaginatedResult<ReviewItemData>> {
|
||||||
return this.reviewRepo.findByUserId(query.userId, query.page, query.limit);
|
try {
|
||||||
|
return await this.reviewRepo.findByUserId(query.userId, query.page, query.limit);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof DomainException) throw error;
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to get reviews by user: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
error instanceof Error ? error.stack : undefined,
|
||||||
|
'GetReviewsByUserHandler',
|
||||||
|
);
|
||||||
|
throw new InternalServerErrorException('Lỗi khi lấy danh sách đánh giá');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user