From bc830a020855a74ce374ed248dd746f78d5f62d5 Mon Sep 17 00:00:00 2001 From: pradeepkumar Date: Sun, 1 Feb 2026 18:30:14 +0530 Subject: [PATCH] feat: Implement dynamic filtering for agent search based on profile fields. --- src/agents/agents.service.ts | 77 +++++++++++++++++++++++++++++ src/agents/dto/search-agents.dto.ts | 8 +++ 2 files changed, 85 insertions(+) diff --git a/src/agents/agents.service.ts b/src/agents/agents.service.ts index ddce2b5..63bc13f 100644 --- a/src/agents/agents.service.ts +++ b/src/agents/agents.service.ts @@ -141,6 +141,7 @@ export class AgentsService { limit = 10, sortBy = 'createdAt', sortOrder = 'desc', + filters, } = dto; const skip = (page - 1) * limit; @@ -179,6 +180,82 @@ export class AgentsService { where.agentTypeId = agentTypeId; } + // Process dynamic profile field filters + if (filters) { + try { + const parsedFilters: Record = JSON.parse(filters); + const filterSlugs = Object.keys(parsedFilters); + + if (filterSlugs.length > 0) { + // Get field IDs for the filter slugs + const fields = await this.prisma.profileField.findMany({ + where: { slug: { in: filterSlugs } }, + select: { id: true, slug: true }, + }); + + const slugToFieldId = new Map(fields.map((f) => [f.slug, f.id])); + + // For each filter, find agents that have ANY of the selected values + // Use AND logic between different fields + const filterConditions: string[][] = []; + + for (const [slug, selectedValues] of Object.entries(parsedFilters)) { + const fieldId = slugToFieldId.get(slug); + if (!fieldId || !selectedValues || selectedValues.length === 0) continue; + + // Find agentProfileIds where jsonValue contains ANY of the selected values + const matchingValues = await this.prisma.agentProfileFieldValue.findMany({ + where: { + fieldId, + // jsonValue contains any of the selected values (for MULTI_SELECT, CHECKBOX_GROUP) + // We need to check if the jsonValue array contains any of the selected values + }, + select: { + agentProfileId: true, + jsonValue: true, + }, + }); + + // Filter in memory - check if jsonValue array contains any selected value + const matchingAgentIds = matchingValues + .filter((fv) => { + const jsonVal = fv.jsonValue as string[] | null; + if (!jsonVal || !Array.isArray(jsonVal)) return false; + return selectedValues.some((sv) => jsonVal.includes(sv)); + }) + .map((fv) => fv.agentProfileId); + + filterConditions.push(matchingAgentIds); + } + + // AND all filter conditions - find intersection of all matching agent IDs + if (filterConditions.length > 0) { + const intersection = filterConditions.reduce((acc, curr) => { + const currSet = new Set(curr); + return acc.filter((id) => currSet.has(id)); + }); + + // If no agents match all filters, return empty result + if (intersection.length === 0) { + return { + data: [], + meta: { + total: 0, + page, + limit, + totalPages: 0, + }, + }; + } + + where.id = { in: intersection }; + } + } + } catch { + // Invalid JSON, ignore filters + } + } + // Build orderBy const orderBy: Prisma.AgentProfileOrderByWithRelationInput = {}; if (sortBy === 'averageRating') { diff --git a/src/agents/dto/search-agents.dto.ts b/src/agents/dto/search-agents.dto.ts index c62fb76..2910a93 100644 --- a/src/agents/dto/search-agents.dto.ts +++ b/src/agents/dto/search-agents.dto.ts @@ -72,4 +72,12 @@ export class SearchAgentsDto { @IsOptional() @IsString() sortOrder?: 'asc' | 'desc' = 'desc'; + + @ApiPropertyOptional({ + description: 'Dynamic profile field filters as JSON string (fieldSlug: [selectedValues])', + example: '{"client_specialization":["first_time_buyers","investors"]}', + }) + @IsOptional() + @IsString() + filters?: string; }