feat(api): add inquiries, leads, and agents modules for Agent Portal
Build three new DDD modules following existing CQRS patterns: - Inquiries: CRUD endpoints for buyer consultation requests with agent notification support - Leads: Full lead lifecycle management with status state machine and conversion tracking - Agents: Quality score calculation (event-driven on review changes) and dashboard stats API All modules include unit tests (14 test files, all 797 tests pass). Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
import type { EventBus } from '@nestjs/cqrs';
|
||||
import type { IInquiryRepository } from '../../domain/repositories/inquiry.repository';
|
||||
import { CreateInquiryCommand } from '../commands/create-inquiry/create-inquiry.command';
|
||||
import { CreateInquiryHandler } from '../commands/create-inquiry/create-inquiry.handler';
|
||||
|
||||
describe('CreateInquiryHandler', () => {
|
||||
let handler: CreateInquiryHandler;
|
||||
let mockInquiryRepo: { [K in keyof IInquiryRepository]: ReturnType<typeof vi.fn> };
|
||||
let mockEventBus: { publish: ReturnType<typeof vi.fn> };
|
||||
let mockPrisma: {
|
||||
listing: { findUnique: ReturnType<typeof vi.fn> };
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockInquiryRepo = {
|
||||
findById: vi.fn(),
|
||||
save: vi.fn(),
|
||||
markAsRead: vi.fn(),
|
||||
findByListing: vi.fn(),
|
||||
findByAgent: vi.fn(),
|
||||
countUnreadByAgent: vi.fn(),
|
||||
};
|
||||
|
||||
mockEventBus = { publish: vi.fn() };
|
||||
|
||||
mockPrisma = {
|
||||
listing: { findUnique: vi.fn() },
|
||||
};
|
||||
|
||||
handler = new CreateInquiryHandler(
|
||||
mockInquiryRepo as any,
|
||||
mockEventBus as unknown as EventBus,
|
||||
mockPrisma as any,
|
||||
);
|
||||
});
|
||||
|
||||
it('creates an inquiry successfully', async () => {
|
||||
mockPrisma.listing.findUnique.mockResolvedValue({ id: 'listing-1' });
|
||||
mockInquiryRepo.save.mockResolvedValue(undefined);
|
||||
|
||||
const command = new CreateInquiryCommand(
|
||||
'user-1',
|
||||
'listing-1',
|
||||
'Tôi muốn xem nhà',
|
||||
'0901234567',
|
||||
);
|
||||
|
||||
const result = await handler.execute(command);
|
||||
|
||||
expect(result.id).toBeDefined();
|
||||
expect(result.listingId).toBe('listing-1');
|
||||
expect(result.createdAt).toBeDefined();
|
||||
expect(mockInquiryRepo.save).toHaveBeenCalledTimes(1);
|
||||
expect(mockEventBus.publish).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('throws NotFoundException when listing not found', async () => {
|
||||
mockPrisma.listing.findUnique.mockResolvedValue(null);
|
||||
|
||||
const command = new CreateInquiryCommand(
|
||||
'user-1',
|
||||
'listing-not-exist',
|
||||
'Tôi muốn xem nhà',
|
||||
null,
|
||||
);
|
||||
|
||||
await expect(handler.execute(command)).rejects.toThrow(
|
||||
"Listing with id 'listing-not-exist' not found",
|
||||
);
|
||||
expect(mockInquiryRepo.save).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('publishes domain events after saving', async () => {
|
||||
mockPrisma.listing.findUnique.mockResolvedValue({ id: 'listing-1' });
|
||||
mockInquiryRepo.save.mockResolvedValue(undefined);
|
||||
|
||||
const command = new CreateInquiryCommand(
|
||||
'user-1',
|
||||
'listing-1',
|
||||
'Cho tôi hỏi giá',
|
||||
null,
|
||||
);
|
||||
|
||||
await handler.execute(command);
|
||||
|
||||
expect(mockEventBus.publish).toHaveBeenCalledTimes(1);
|
||||
expect(mockEventBus.publish).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
eventName: 'inquiry.created',
|
||||
listingId: 'listing-1',
|
||||
userId: 'user-1',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
import type { IInquiryRepository } from '../../domain/repositories/inquiry.repository';
|
||||
import { GetInquiriesByAgentQuery } from '../queries/get-inquiries-by-agent/get-inquiries-by-agent.query';
|
||||
import { GetInquiriesByAgentHandler } from '../queries/get-inquiries-by-agent/get-inquiries-by-agent.handler';
|
||||
|
||||
describe('GetInquiriesByAgentHandler', () => {
|
||||
let handler: GetInquiriesByAgentHandler;
|
||||
let mockInquiryRepo: { [K in keyof IInquiryRepository]: ReturnType<typeof vi.fn> };
|
||||
let mockPrisma: {
|
||||
agent: { findUnique: ReturnType<typeof vi.fn> };
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockInquiryRepo = {
|
||||
findById: vi.fn(),
|
||||
save: vi.fn(),
|
||||
markAsRead: vi.fn(),
|
||||
findByListing: vi.fn(),
|
||||
findByAgent: vi.fn(),
|
||||
countUnreadByAgent: vi.fn(),
|
||||
};
|
||||
|
||||
mockPrisma = {
|
||||
agent: { findUnique: vi.fn() },
|
||||
};
|
||||
|
||||
handler = new GetInquiriesByAgentHandler(
|
||||
mockInquiryRepo as any,
|
||||
mockPrisma as any,
|
||||
);
|
||||
});
|
||||
|
||||
it('returns paginated results', async () => {
|
||||
mockPrisma.agent.findUnique.mockResolvedValue({ id: 'agent-1' });
|
||||
|
||||
const mockResult = {
|
||||
data: [
|
||||
{
|
||||
id: 'inq-1',
|
||||
listingId: 'listing-1',
|
||||
userId: 'user-1',
|
||||
message: 'Tôi muốn xem nhà',
|
||||
phone: '0901234567',
|
||||
isRead: false,
|
||||
createdAt: new Date(),
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
page: 1,
|
||||
limit: 20,
|
||||
totalPages: 1,
|
||||
};
|
||||
mockInquiryRepo.findByAgent.mockResolvedValue(mockResult);
|
||||
|
||||
const query = new GetInquiriesByAgentQuery('agent-user-1', 1, 20);
|
||||
const result = await handler.execute(query);
|
||||
|
||||
expect(result).toEqual(mockResult);
|
||||
expect(result.data).toHaveLength(1);
|
||||
expect(mockPrisma.agent.findUnique).toHaveBeenCalledWith({
|
||||
where: { userId: 'agent-user-1' },
|
||||
select: { id: true },
|
||||
});
|
||||
expect(mockInquiryRepo.findByAgent).toHaveBeenCalledWith('agent-1', 1, 20);
|
||||
});
|
||||
|
||||
it('throws NotFoundException when agent not found for user', async () => {
|
||||
mockPrisma.agent.findUnique.mockResolvedValue(null);
|
||||
|
||||
const query = new GetInquiriesByAgentQuery('not-an-agent', 1, 20);
|
||||
|
||||
await expect(handler.execute(query)).rejects.toThrow(
|
||||
"Agent with id 'not-an-agent' not found",
|
||||
);
|
||||
expect(mockInquiryRepo.findByAgent).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
import type { IInquiryRepository } from '../../domain/repositories/inquiry.repository';
|
||||
import { GetInquiriesByListingQuery } from '../queries/get-inquiries-by-listing/get-inquiries-by-listing.query';
|
||||
import { GetInquiriesByListingHandler } from '../queries/get-inquiries-by-listing/get-inquiries-by-listing.handler';
|
||||
|
||||
describe('GetInquiriesByListingHandler', () => {
|
||||
let handler: GetInquiriesByListingHandler;
|
||||
let mockInquiryRepo: { [K in keyof IInquiryRepository]: ReturnType<typeof vi.fn> };
|
||||
|
||||
beforeEach(() => {
|
||||
mockInquiryRepo = {
|
||||
findById: vi.fn(),
|
||||
save: vi.fn(),
|
||||
markAsRead: vi.fn(),
|
||||
findByListing: vi.fn(),
|
||||
findByAgent: vi.fn(),
|
||||
countUnreadByAgent: vi.fn(),
|
||||
};
|
||||
|
||||
handler = new GetInquiriesByListingHandler(mockInquiryRepo as any);
|
||||
});
|
||||
|
||||
it('returns paginated results', async () => {
|
||||
const mockResult = {
|
||||
data: [
|
||||
{
|
||||
id: 'inq-1',
|
||||
listingId: 'listing-1',
|
||||
userId: 'user-1',
|
||||
message: 'Tôi muốn xem nhà',
|
||||
phone: '0901234567',
|
||||
isRead: false,
|
||||
createdAt: new Date(),
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
page: 1,
|
||||
limit: 20,
|
||||
totalPages: 1,
|
||||
};
|
||||
mockInquiryRepo.findByListing.mockResolvedValue(mockResult);
|
||||
|
||||
const query = new GetInquiriesByListingQuery('listing-1', 1, 20);
|
||||
const result = await handler.execute(query);
|
||||
|
||||
expect(result).toEqual(mockResult);
|
||||
expect(result.data).toHaveLength(1);
|
||||
expect(result.total).toBe(1);
|
||||
expect(result.page).toBe(1);
|
||||
expect(mockInquiryRepo.findByListing).toHaveBeenCalledWith('listing-1', 1, 20);
|
||||
});
|
||||
|
||||
it('returns empty data when no inquiries found', async () => {
|
||||
const mockResult = {
|
||||
data: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
limit: 20,
|
||||
totalPages: 0,
|
||||
};
|
||||
mockInquiryRepo.findByListing.mockResolvedValue(mockResult);
|
||||
|
||||
const query = new GetInquiriesByListingQuery('listing-empty', 1, 20);
|
||||
const result = await handler.execute(query);
|
||||
|
||||
expect(result.data).toHaveLength(0);
|
||||
expect(result.total).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,126 @@
|
||||
import type { EventBus } from '@nestjs/cqrs';
|
||||
import { InquiryEntity } from '../../domain/entities/inquiry.entity';
|
||||
import type { IInquiryRepository } from '../../domain/repositories/inquiry.repository';
|
||||
import { MarkInquiryReadCommand } from '../commands/mark-inquiry-read/mark-inquiry-read.command';
|
||||
import { MarkInquiryReadHandler } from '../commands/mark-inquiry-read/mark-inquiry-read.handler';
|
||||
|
||||
describe('MarkInquiryReadHandler', () => {
|
||||
let handler: MarkInquiryReadHandler;
|
||||
let mockInquiryRepo: { [K in keyof IInquiryRepository]: ReturnType<typeof vi.fn> };
|
||||
let mockEventBus: { publish: ReturnType<typeof vi.fn> };
|
||||
let mockPrisma: {
|
||||
listing: { findUnique: ReturnType<typeof vi.fn> };
|
||||
agent: { findUnique: ReturnType<typeof vi.fn> };
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockInquiryRepo = {
|
||||
findById: vi.fn(),
|
||||
save: vi.fn(),
|
||||
markAsRead: vi.fn(),
|
||||
findByListing: vi.fn(),
|
||||
findByAgent: vi.fn(),
|
||||
countUnreadByAgent: vi.fn(),
|
||||
};
|
||||
|
||||
mockEventBus = { publish: vi.fn() };
|
||||
|
||||
mockPrisma = {
|
||||
listing: { findUnique: vi.fn() },
|
||||
agent: { findUnique: vi.fn() },
|
||||
};
|
||||
|
||||
handler = new MarkInquiryReadHandler(
|
||||
mockInquiryRepo as any,
|
||||
mockEventBus as unknown as EventBus,
|
||||
mockPrisma as any,
|
||||
);
|
||||
});
|
||||
|
||||
it('marks an inquiry as read successfully', async () => {
|
||||
const inquiry = new InquiryEntity('inq-1', {
|
||||
listingId: 'listing-1',
|
||||
userId: 'user-1',
|
||||
message: 'Tôi muốn xem nhà',
|
||||
phone: null,
|
||||
isRead: false,
|
||||
});
|
||||
mockInquiryRepo.findById.mockResolvedValue(inquiry);
|
||||
mockPrisma.listing.findUnique.mockResolvedValue({ agentId: 'agent-1' });
|
||||
mockPrisma.agent.findUnique.mockResolvedValue({ id: 'agent-1' });
|
||||
mockInquiryRepo.markAsRead.mockResolvedValue(undefined);
|
||||
|
||||
const command = new MarkInquiryReadCommand('inq-1', 'agent-user-1');
|
||||
|
||||
await handler.execute(command);
|
||||
|
||||
expect(mockInquiryRepo.markAsRead).toHaveBeenCalledWith('inq-1');
|
||||
expect(mockEventBus.publish).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('throws NotFoundException when inquiry not found', async () => {
|
||||
mockInquiryRepo.findById.mockResolvedValue(null);
|
||||
|
||||
const command = new MarkInquiryReadCommand('inq-not-exist', 'agent-user-1');
|
||||
|
||||
await expect(handler.execute(command)).rejects.toThrow(
|
||||
"Inquiry with id 'inq-not-exist' not found",
|
||||
);
|
||||
});
|
||||
|
||||
it('throws NotFoundException when listing not found', async () => {
|
||||
const inquiry = new InquiryEntity('inq-1', {
|
||||
listingId: 'listing-1',
|
||||
userId: 'user-1',
|
||||
message: 'Test',
|
||||
phone: null,
|
||||
isRead: false,
|
||||
});
|
||||
mockInquiryRepo.findById.mockResolvedValue(inquiry);
|
||||
mockPrisma.listing.findUnique.mockResolvedValue(null);
|
||||
|
||||
const command = new MarkInquiryReadCommand('inq-1', 'agent-user-1');
|
||||
|
||||
await expect(handler.execute(command)).rejects.toThrow(
|
||||
"Listing with id 'listing-1' not found",
|
||||
);
|
||||
});
|
||||
|
||||
it('throws ForbiddenException when user is not the listing agent', async () => {
|
||||
const inquiry = new InquiryEntity('inq-1', {
|
||||
listingId: 'listing-1',
|
||||
userId: 'user-1',
|
||||
message: 'Test',
|
||||
phone: null,
|
||||
isRead: false,
|
||||
});
|
||||
mockInquiryRepo.findById.mockResolvedValue(inquiry);
|
||||
mockPrisma.listing.findUnique.mockResolvedValue({ agentId: 'agent-1' });
|
||||
mockPrisma.agent.findUnique.mockResolvedValue({ id: 'agent-other' });
|
||||
|
||||
const command = new MarkInquiryReadCommand('inq-1', 'agent-user-other');
|
||||
|
||||
await expect(handler.execute(command)).rejects.toThrow(
|
||||
'Bạn không có quyền đánh dấu yêu cầu tư vấn này',
|
||||
);
|
||||
});
|
||||
|
||||
it('throws ForbiddenException when agent not found for user', async () => {
|
||||
const inquiry = new InquiryEntity('inq-1', {
|
||||
listingId: 'listing-1',
|
||||
userId: 'user-1',
|
||||
message: 'Test',
|
||||
phone: null,
|
||||
isRead: false,
|
||||
});
|
||||
mockInquiryRepo.findById.mockResolvedValue(inquiry);
|
||||
mockPrisma.listing.findUnique.mockResolvedValue({ agentId: 'agent-1' });
|
||||
mockPrisma.agent.findUnique.mockResolvedValue(null);
|
||||
|
||||
const command = new MarkInquiryReadCommand('inq-1', 'not-an-agent');
|
||||
|
||||
await expect(handler.execute(command)).rejects.toThrow(
|
||||
'Bạn không có quyền đánh dấu yêu cầu tư vấn này',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
export class CreateInquiryCommand {
|
||||
constructor(
|
||||
public readonly userId: string,
|
||||
public readonly listingId: string,
|
||||
public readonly message: string,
|
||||
public readonly phone: string | null,
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { Inject, Logger } from '@nestjs/common';
|
||||
import { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
||||
import { createId } from '@paralleldrive/cuid2';
|
||||
import { NotFoundException } from '@modules/shared';
|
||||
import { type PrismaService } 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';
|
||||
|
||||
export interface CreateInquiryResult {
|
||||
id: string;
|
||||
listingId: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
@CommandHandler(CreateInquiryCommand)
|
||||
export class CreateInquiryHandler implements ICommandHandler<CreateInquiryCommand> {
|
||||
private readonly logger = new Logger(CreateInquiryHandler.name);
|
||||
|
||||
constructor(
|
||||
@Inject(INQUIRY_REPOSITORY) private readonly inquiryRepo: IInquiryRepository,
|
||||
private readonly eventBus: EventBus,
|
||||
private readonly prisma: PrismaService,
|
||||
) {}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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}`);
|
||||
|
||||
return {
|
||||
id,
|
||||
listingId: command.listingId,
|
||||
createdAt: inquiry.createdAt.toISOString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export class MarkInquiryReadCommand {
|
||||
constructor(
|
||||
public readonly inquiryId: string,
|
||||
public readonly agentUserId: string,
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { Inject, Logger } from '@nestjs/common';
|
||||
import { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs';
|
||||
import { ForbiddenException, NotFoundException } from '@modules/shared';
|
||||
import { type PrismaService } from '@modules/shared';
|
||||
import { INQUIRY_REPOSITORY, type IInquiryRepository } from '../../../domain/repositories/inquiry.repository';
|
||||
import { MarkInquiryReadCommand } from './mark-inquiry-read.command';
|
||||
|
||||
@CommandHandler(MarkInquiryReadCommand)
|
||||
export class MarkInquiryReadHandler implements ICommandHandler<MarkInquiryReadCommand> {
|
||||
private readonly logger = new Logger(MarkInquiryReadHandler.name);
|
||||
|
||||
constructor(
|
||||
@Inject(INQUIRY_REPOSITORY) private readonly inquiryRepo: IInquiryRepository,
|
||||
private readonly eventBus: EventBus,
|
||||
private readonly prisma: PrismaService,
|
||||
) {}
|
||||
|
||||
async execute(command: MarkInquiryReadCommand): Promise<void> {
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs';
|
||||
import { NotFoundException } from '@modules/shared';
|
||||
import { type PrismaService } 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';
|
||||
|
||||
@QueryHandler(GetInquiriesByAgentQuery)
|
||||
export class GetInquiriesByAgentHandler implements IQueryHandler<GetInquiriesByAgentQuery> {
|
||||
constructor(
|
||||
@Inject(INQUIRY_REPOSITORY) private readonly inquiryRepo: IInquiryRepository,
|
||||
private readonly prisma: PrismaService,
|
||||
) {}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return this.inquiryRepo.findByAgent(
|
||||
agent.id,
|
||||
query.page,
|
||||
query.limit,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export class GetInquiriesByAgentQuery {
|
||||
constructor(
|
||||
public readonly agentUserId: string,
|
||||
public readonly page: number,
|
||||
public readonly limit: number,
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs';
|
||||
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';
|
||||
|
||||
@QueryHandler(GetInquiriesByListingQuery)
|
||||
export class GetInquiriesByListingHandler implements IQueryHandler<GetInquiriesByListingQuery> {
|
||||
constructor(
|
||||
@Inject(INQUIRY_REPOSITORY) private readonly inquiryRepo: IInquiryRepository,
|
||||
) {}
|
||||
|
||||
async execute(query: GetInquiriesByListingQuery): Promise<PaginatedResult<InquiryReadDto>> {
|
||||
return this.inquiryRepo.findByListing(
|
||||
query.listingId,
|
||||
query.page,
|
||||
query.limit,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export class GetInquiriesByListingQuery {
|
||||
constructor(
|
||||
public readonly listingId: string,
|
||||
public readonly page: number,
|
||||
public readonly limit: number,
|
||||
) {}
|
||||
}
|
||||
Reference in New Issue
Block a user