agent and category Dto creation
This commit is contained in:
176
src/agents/agents.controller.ts
Normal file
176
src/agents/agents.controller.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Put,
|
||||
Delete,
|
||||
Body,
|
||||
Param,
|
||||
Query,
|
||||
UseGuards,
|
||||
ParseUUIDPipe,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
ApiTags,
|
||||
ApiOperation,
|
||||
ApiResponse,
|
||||
ApiBearerAuth,
|
||||
} from '@nestjs/swagger';
|
||||
import { AgentsService } from './agents.service';
|
||||
import {
|
||||
CreateAgentProfileDto,
|
||||
UpdateAgentProfileDto,
|
||||
SearchAgentsDto,
|
||||
} from './dto';
|
||||
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
||||
import { RolesGuard } from '../auth/guards/roles.guard';
|
||||
import { Roles } from '../auth/decorators/roles.decorator';
|
||||
import { CurrentUser } from '../auth/decorators/current-user.decorator';
|
||||
import { UserRole } from '@prisma/client';
|
||||
|
||||
@ApiTags('agents')
|
||||
@Controller('agents')
|
||||
export class AgentsController {
|
||||
constructor(private readonly agentsService: AgentsService) {}
|
||||
|
||||
// Public endpoints
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: 'Search and list agents' })
|
||||
@ApiResponse({ status: 200, description: 'Agents retrieved successfully' })
|
||||
async searchAgents(@Query() dto: SearchAgentsDto) {
|
||||
return this.agentsService.searchAgents(dto);
|
||||
}
|
||||
|
||||
@Get('featured')
|
||||
@ApiOperation({ summary: 'Get featured agents' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Featured agents retrieved successfully',
|
||||
})
|
||||
async getFeaturedAgents(@Query('limit') limit?: number) {
|
||||
return this.agentsService.getFeaturedAgents(limit || 10);
|
||||
}
|
||||
|
||||
@Get('top-rated')
|
||||
@ApiOperation({ summary: 'Get top rated agents' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Top rated agents retrieved successfully',
|
||||
})
|
||||
async getTopRatedAgents(@Query('limit') limit?: number) {
|
||||
return this.agentsService.getTopRatedAgents(limit || 10);
|
||||
}
|
||||
|
||||
// Authenticated endpoints - must come before :id route
|
||||
|
||||
@Get('profile/me')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@ApiOperation({ summary: 'Get current user agent profile' })
|
||||
@ApiResponse({ status: 200, description: 'Profile retrieved successfully' })
|
||||
@ApiResponse({ status: 404, description: 'Profile not found' })
|
||||
async getMyProfile(@CurrentUser('id') userId: string) {
|
||||
return this.agentsService.getProfile(userId);
|
||||
}
|
||||
|
||||
// Parameterized route - must come after specific routes
|
||||
@Get(':id')
|
||||
@ApiOperation({ summary: 'Get agent profile by ID' })
|
||||
@ApiResponse({ status: 200, description: 'Agent profile retrieved successfully' })
|
||||
@ApiResponse({ status: 404, description: 'Agent profile not found' })
|
||||
async getAgentById(@Param('id', ParseUUIDPipe) id: string) {
|
||||
return this.agentsService.getProfileById(id);
|
||||
}
|
||||
|
||||
@Post('profile')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@ApiOperation({ summary: 'Create agent profile' })
|
||||
@ApiResponse({ status: 201, description: 'Profile created successfully' })
|
||||
@ApiResponse({ status: 409, description: 'Profile already exists' })
|
||||
async createProfile(
|
||||
@CurrentUser('id') userId: string,
|
||||
@Body() dto: CreateAgentProfileDto,
|
||||
) {
|
||||
return this.agentsService.createProfile(userId, dto);
|
||||
}
|
||||
|
||||
@Put('profile')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@ApiOperation({ summary: 'Update current user agent profile' })
|
||||
@ApiResponse({ status: 200, description: 'Profile updated successfully' })
|
||||
@ApiResponse({ status: 404, description: 'Profile not found' })
|
||||
async updateProfile(
|
||||
@CurrentUser('id') userId: string,
|
||||
@Body() dto: UpdateAgentProfileDto,
|
||||
) {
|
||||
return this.agentsService.updateProfile(userId, dto);
|
||||
}
|
||||
|
||||
// Admin endpoints
|
||||
|
||||
@Get('admin/list')
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles(UserRole.ADMIN)
|
||||
@ApiBearerAuth()
|
||||
@ApiOperation({ summary: 'Admin: List all agents' })
|
||||
@ApiResponse({ status: 200, description: 'Agents retrieved successfully' })
|
||||
async adminListAgents(@Query() dto: SearchAgentsDto) {
|
||||
return this.agentsService.getAllAgentsAdmin(dto);
|
||||
}
|
||||
|
||||
@Put('admin/:id')
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles(UserRole.ADMIN)
|
||||
@ApiBearerAuth()
|
||||
@ApiOperation({ summary: 'Admin: Update agent profile' })
|
||||
@ApiResponse({ status: 200, description: 'Profile updated successfully' })
|
||||
@ApiResponse({ status: 404, description: 'Profile not found' })
|
||||
async adminUpdateProfile(
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
@Body() dto: UpdateAgentProfileDto,
|
||||
) {
|
||||
return this.agentsService.adminUpdateProfile(id, dto);
|
||||
}
|
||||
|
||||
@Delete('admin/:id')
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles(UserRole.ADMIN)
|
||||
@ApiBearerAuth()
|
||||
@ApiOperation({ summary: 'Admin: Delete agent profile' })
|
||||
@ApiResponse({ status: 200, description: 'Profile deleted successfully' })
|
||||
@ApiResponse({ status: 404, description: 'Profile not found' })
|
||||
async adminDeleteProfile(@Param('id', ParseUUIDPipe) id: string) {
|
||||
return this.agentsService.adminDeleteProfile(id);
|
||||
}
|
||||
|
||||
@Put('admin/:id/verify')
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles(UserRole.ADMIN)
|
||||
@ApiBearerAuth()
|
||||
@ApiOperation({ summary: 'Admin: Verify/unverify agent' })
|
||||
@ApiResponse({ status: 200, description: 'Verification status updated' })
|
||||
@ApiResponse({ status: 404, description: 'Profile not found' })
|
||||
async adminVerifyAgent(
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
@Body('isVerified') isVerified: boolean,
|
||||
) {
|
||||
return this.agentsService.adminVerifyAgent(id, isVerified);
|
||||
}
|
||||
|
||||
@Put('admin/:id/featured')
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles(UserRole.ADMIN)
|
||||
@ApiBearerAuth()
|
||||
@ApiOperation({ summary: 'Admin: Set agent as featured' })
|
||||
@ApiResponse({ status: 200, description: 'Featured status updated' })
|
||||
@ApiResponse({ status: 404, description: 'Profile not found' })
|
||||
async adminSetFeatured(
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
@Body('isFeatured') isFeatured: boolean,
|
||||
) {
|
||||
return this.agentsService.adminSetFeatured(id, isFeatured);
|
||||
}
|
||||
}
|
||||
12
src/agents/agents.module.ts
Normal file
12
src/agents/agents.module.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AgentsController } from './agents.controller';
|
||||
import { AgentsService } from './agents.service';
|
||||
import { PrismaModule } from '../prisma/prisma.module';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
controllers: [AgentsController],
|
||||
providers: [AgentsService],
|
||||
exports: [AgentsService],
|
||||
})
|
||||
export class AgentsModule {}
|
||||
509
src/agents/agents.service.ts
Normal file
509
src/agents/agents.service.ts
Normal file
@@ -0,0 +1,509 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
128
src/agents/dto/create-agent-profile.dto.ts
Normal file
128
src/agents/dto/create-agent-profile.dto.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import {
|
||||
IsString,
|
||||
IsOptional,
|
||||
IsArray,
|
||||
IsInt,
|
||||
IsUrl,
|
||||
IsUUID,
|
||||
MinLength,
|
||||
MaxLength,
|
||||
Min,
|
||||
Max,
|
||||
} from 'class-validator';
|
||||
|
||||
export class CreateAgentProfileDto {
|
||||
@ApiProperty({ example: 'John' })
|
||||
@IsString()
|
||||
@MinLength(1)
|
||||
@MaxLength(100)
|
||||
firstName: string;
|
||||
|
||||
@ApiProperty({ example: 'Doe' })
|
||||
@IsString()
|
||||
@MinLength(1)
|
||||
@MaxLength(100)
|
||||
lastName: string;
|
||||
|
||||
@ApiPropertyOptional({ example: '+1234567890' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(20)
|
||||
phone?: string;
|
||||
|
||||
@ApiPropertyOptional({ example: 'Experienced real estate agent...' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(2000)
|
||||
bio?: string;
|
||||
|
||||
@ApiPropertyOptional({ example: 'Top Real Estate Agent in California' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(200)
|
||||
headline?: string;
|
||||
|
||||
@ApiPropertyOptional({ example: 'Los Angeles' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(100)
|
||||
city?: string;
|
||||
|
||||
@ApiPropertyOptional({ example: 'California' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(100)
|
||||
state?: string;
|
||||
|
||||
@ApiPropertyOptional({ example: 'USA' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(100)
|
||||
country?: string;
|
||||
|
||||
@ApiPropertyOptional({ example: '123 Main St' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(200)
|
||||
address?: string;
|
||||
|
||||
@ApiPropertyOptional({ example: '90001' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(20)
|
||||
zipCode?: string;
|
||||
|
||||
@ApiPropertyOptional({ example: 10 })
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
@Max(100)
|
||||
yearsOfExperience?: number;
|
||||
|
||||
@ApiPropertyOptional({ example: 'LIC-12345' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(50)
|
||||
licenseNumber?: string;
|
||||
|
||||
@ApiPropertyOptional({ example: 'ABC Realty' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(200)
|
||||
companyName?: string;
|
||||
|
||||
@ApiPropertyOptional({ example: 'https://mywebsite.com' })
|
||||
@IsOptional()
|
||||
@IsUrl()
|
||||
website?: string;
|
||||
|
||||
@ApiPropertyOptional({ example: 'https://facebook.com/johndoe' })
|
||||
@IsOptional()
|
||||
@IsUrl()
|
||||
facebookUrl?: string;
|
||||
|
||||
@ApiPropertyOptional({ example: 'https://twitter.com/johndoe' })
|
||||
@IsOptional()
|
||||
@IsUrl()
|
||||
twitterUrl?: string;
|
||||
|
||||
@ApiPropertyOptional({ example: 'https://linkedin.com/in/johndoe' })
|
||||
@IsOptional()
|
||||
@IsUrl()
|
||||
linkedinUrl?: string;
|
||||
|
||||
@ApiPropertyOptional({ example: 'https://instagram.com/johndoe' })
|
||||
@IsOptional()
|
||||
@IsUrl()
|
||||
instagramUrl?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: ['uuid-1', 'uuid-2'],
|
||||
description: 'Array of specialization IDs',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@IsUUID('4', { each: true })
|
||||
specializationIds?: string[];
|
||||
}
|
||||
3
src/agents/dto/index.ts
Normal file
3
src/agents/dto/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './create-agent-profile.dto';
|
||||
export * from './update-agent-profile.dto';
|
||||
export * from './search-agents.dto';
|
||||
83
src/agents/dto/search-agents.dto.ts
Normal file
83
src/agents/dto/search-agents.dto.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import {
|
||||
IsString,
|
||||
IsOptional,
|
||||
IsBoolean,
|
||||
IsInt,
|
||||
IsUUID,
|
||||
IsArray,
|
||||
Min,
|
||||
Max,
|
||||
} from 'class-validator';
|
||||
import { Transform, Type } from 'class-transformer';
|
||||
|
||||
export class SearchAgentsDto {
|
||||
@ApiPropertyOptional({ example: 'John' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
search?: string;
|
||||
|
||||
@ApiPropertyOptional({ example: 'Los Angeles' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
city?: string;
|
||||
|
||||
@ApiPropertyOptional({ example: 'California' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
state?: string;
|
||||
|
||||
@ApiPropertyOptional({ example: 'USA' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
country?: string;
|
||||
|
||||
@ApiPropertyOptional({ example: 'uuid-of-category' })
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
categoryId?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: ['uuid-1', 'uuid-2'],
|
||||
description: 'Filter by specialization IDs',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@IsUUID('4', { each: true })
|
||||
@Transform(({ value }) => (typeof value === 'string' ? [value] : value))
|
||||
specializationIds?: string[];
|
||||
|
||||
@ApiPropertyOptional({ example: true })
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => value === 'true' || value === true)
|
||||
@IsBoolean()
|
||||
isVerified?: boolean;
|
||||
|
||||
@ApiPropertyOptional({ example: 1, default: 1 })
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
page?: number = 1;
|
||||
|
||||
@ApiPropertyOptional({ example: 10, default: 10 })
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Max(100)
|
||||
limit?: number = 10;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: 'createdAt',
|
||||
enum: ['createdAt', 'averageRating', 'totalReviews', 'firstName'],
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
sortBy?: string = 'createdAt';
|
||||
|
||||
@ApiPropertyOptional({ example: 'desc', enum: ['asc', 'desc'] })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
sortOrder?: 'asc' | 'desc' = 'desc';
|
||||
}
|
||||
4
src/agents/dto/update-agent-profile.dto.ts
Normal file
4
src/agents/dto/update-agent-profile.dto.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreateAgentProfileDto } from './create-agent-profile.dto';
|
||||
|
||||
export class UpdateAgentProfileDto extends PartialType(CreateAgentProfileDto) {}
|
||||
4
src/agents/index.ts
Normal file
4
src/agents/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './agents.module';
|
||||
export * from './agents.service';
|
||||
export * from './agents.controller';
|
||||
export * from './dto';
|
||||
Reference in New Issue
Block a user