import { Injectable, NotFoundException, ForbiddenException, ConflictException, } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; import { CreateAgentProfileDto, UpdateAgentProfileDto, SearchAgentsDto, } from './dto'; import { Prisma } from '@prisma/client'; function generateSlug(firstName: string, lastName: string): string { const base = `${firstName}-${lastName}` .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-|-$/g, ''); const uniqueSuffix = Math.random().toString(36).substring(2, 8); return `${base}-${uniqueSuffix}`; } @Injectable() export class AgentsService { constructor(private prisma: PrismaService) {} async createProfile(userId: string, dto: CreateAgentProfileDto) { // Check if user already has an agent profile const existingProfile = await this.prisma.agentProfile.findUnique({ where: { userId }, }); if (existingProfile) { throw new ConflictException('User already has an agent profile'); } const { specializationIds, ...profileData } = dto; const slug = generateSlug(dto.firstName, dto.lastName); const profile = await this.prisma.agentProfile.create({ data: { ...profileData, userId, slug, specializations: specializationIds?.length ? { create: specializationIds.map((specId) => ({ specializationId: specId, })), } : undefined, }, include: { user: { select: { id: true, email: true, avatar: true, }, }, specializations: { include: { specialization: { include: { category: true, }, }, }, }, }, }); return profile; } async getProfile(userId: string) { const profile = await this.prisma.agentProfile.findUnique({ where: { userId }, include: { user: { select: { id: true, email: true, avatar: true, }, }, specializations: { include: { specialization: { include: { category: true, }, }, }, }, verificationRequests: { orderBy: { createdAt: 'desc' }, take: 5, }, }, }); if (!profile) { throw new NotFoundException('Agent profile not found'); } return profile; } async getProfileById(profileId: string) { const profile = await this.prisma.agentProfile.findUnique({ where: { id: profileId }, include: { user: { select: { id: true, email: true, avatar: true, }, }, specializations: { include: { specialization: { include: { category: true, }, }, }, }, }, }); if (!profile) { throw new NotFoundException('Agent profile not found'); } return profile; } async updateProfile(userId: string, dto: UpdateAgentProfileDto) { const profile = await this.prisma.agentProfile.findUnique({ where: { userId }, }); if (!profile) { throw new NotFoundException('Agent profile not found'); } const { specializationIds, ...profileData } = dto; // If specializationIds is provided, update the specializations if (specializationIds !== undefined) { // Delete existing specializations await this.prisma.agentSpecialization.deleteMany({ where: { agentProfileId: profile.id }, }); // Create new specializations if (specializationIds.length > 0) { await this.prisma.agentSpecialization.createMany({ data: specializationIds.map((specId) => ({ agentProfileId: profile.id, specializationId: specId, })), }); } } const updatedProfile = await this.prisma.agentProfile.update({ where: { userId }, data: profileData, include: { user: { select: { id: true, email: true, avatar: true, }, }, specializations: { include: { specialization: { include: { category: true, }, }, }, }, }, }); return updatedProfile; } async searchAgents(dto: SearchAgentsDto) { const { search, city, state, country, categoryId, specializationIds, isVerified, page = 1, limit = 10, sortBy = 'createdAt', sortOrder = 'desc', } = dto; const skip = (page - 1) * limit; // Build where clause const where: Prisma.AgentProfileWhereInput = {}; if (search) { where.OR = [ { firstName: { contains: search, mode: 'insensitive' } }, { lastName: { contains: search, mode: 'insensitive' } }, { bio: { contains: search, mode: 'insensitive' } }, { headline: { contains: search, mode: 'insensitive' } }, { companyName: { contains: search, mode: 'insensitive' } }, ]; } if (city) { where.city = { contains: city, mode: 'insensitive' }; } if (state) { where.state = { contains: state, mode: 'insensitive' }; } if (country) { where.country = { contains: country, mode: 'insensitive' }; } if (isVerified !== undefined) { where.isVerified = isVerified; } // Filter by category if (categoryId) { where.specializations = { some: { specialization: { categoryId, }, }, }; } // Filter by specializations if (specializationIds?.length) { where.specializations = { some: { specializationId: { in: specializationIds }, }, }; } // Build orderBy const orderBy: Prisma.AgentProfileOrderByWithRelationInput = {}; if (sortBy === 'averageRating') { orderBy.averageRating = sortOrder; } else if (sortBy === 'totalReviews') { orderBy.totalReviews = sortOrder; } else if (sortBy === 'firstName') { orderBy.firstName = sortOrder; } else { orderBy.createdAt = sortOrder; } const [agents, total] = await Promise.all([ this.prisma.agentProfile.findMany({ where, skip, take: limit, orderBy, include: { user: { select: { id: true, email: true, avatar: true, }, }, specializations: { include: { specialization: { include: { category: true, }, }, }, }, }, }), this.prisma.agentProfile.count({ where }), ]); return { data: agents, meta: { total, page, limit, totalPages: Math.ceil(total / limit), }, }; } async getFeaturedAgents(limit = 10) { const agents = await this.prisma.agentProfile.findMany({ where: { isVerified: true, isFeatured: true, }, take: limit, orderBy: [{ averageRating: 'desc' }, { totalReviews: 'desc' }], include: { user: { select: { id: true, email: true, avatar: true, }, }, specializations: { include: { specialization: { include: { category: true, }, }, }, }, }, }); return agents; } async getTopRatedAgents(limit = 10) { const agents = await this.prisma.agentProfile.findMany({ where: { isVerified: true, totalReviews: { gte: 5 }, }, take: limit, orderBy: [{ averageRating: 'desc' }, { totalReviews: 'desc' }], include: { user: { select: { id: true, email: true, avatar: true, }, }, specializations: { include: { specialization: { include: { category: true, }, }, }, }, }, }); return agents; } // Admin methods async adminUpdateProfile(profileId: string, dto: UpdateAgentProfileDto) { const profile = await this.prisma.agentProfile.findUnique({ where: { id: profileId }, }); if (!profile) { throw new NotFoundException('Agent profile not found'); } const { specializationIds, ...profileData } = dto; if (specializationIds !== undefined) { await this.prisma.agentSpecialization.deleteMany({ where: { agentProfileId: profileId }, }); if (specializationIds.length > 0) { await this.prisma.agentSpecialization.createMany({ data: specializationIds.map((specId) => ({ agentProfileId: profileId, specializationId: specId, })), }); } } const updatedProfile = await this.prisma.agentProfile.update({ where: { id: profileId }, data: profileData, include: { user: { select: { id: true, email: true, avatar: true, }, }, specializations: { include: { specialization: { include: { category: true, }, }, }, }, }, }); return updatedProfile; } async adminDeleteProfile(profileId: string) { const profile = await this.prisma.agentProfile.findUnique({ where: { id: profileId }, }); if (!profile) { throw new NotFoundException('Agent profile not found'); } // Delete related records first await this.prisma.agentSpecialization.deleteMany({ where: { agentProfileId: profileId }, }); await this.prisma.verificationRequest.deleteMany({ where: { agentProfileId: profileId }, }); await this.prisma.agentProfile.delete({ where: { id: profileId }, }); return { message: 'Agent profile deleted successfully' }; } async adminVerifyAgent(profileId: string, isVerified: boolean) { const profile = await this.prisma.agentProfile.findUnique({ where: { id: profileId }, }); if (!profile) { throw new NotFoundException('Agent profile not found'); } const updatedProfile = await this.prisma.agentProfile.update({ where: { id: profileId }, data: { isVerified }, include: { user: { select: { id: true, email: true, avatar: true, }, }, }, }); return updatedProfile; } async adminSetFeatured(profileId: string, isFeatured: boolean) { const profile = await this.prisma.agentProfile.findUnique({ where: { id: profileId }, }); if (!profile) { throw new NotFoundException('Agent profile not found'); } const updatedProfile = await this.prisma.agentProfile.update({ where: { id: profileId }, data: { isFeatured }, include: { user: { select: { id: true, email: true, avatar: true, }, }, }, }); return updatedProfile; } async getAllAgentsAdmin(dto: SearchAgentsDto) { // Same as searchAgents but returns all data for admin return this.searchAgents(dto); } }