feat: Implement a general location search for agents, querying both legacy city/state/country fields and dynamic profile field values.

This commit is contained in:
pradeepkumar
2026-03-04 20:49:37 +05:30
parent 7b8d6e3db8
commit 0ad73b1636
2 changed files with 93 additions and 0 deletions

View File

@@ -181,6 +181,7 @@ export class AgentsService {
async searchAgents(dto: SearchAgentsDto) { async searchAgents(dto: SearchAgentsDto) {
const { const {
search, search,
location,
city, city,
state, state,
country, country,
@@ -208,6 +209,90 @@ export class AgentsService {
]; ];
} }
// General location search - searches across legacy columns AND dynamic field values
if (location) {
// Find agent IDs that have matching city/state in dynamic field values
// City and State are CHECKBOX_GROUP fields stored as JSON arrays in jsonValue
const locationFields = await this.prisma.profileField.findMany({
where: { slug: { in: ['city', 'state', 'service_areas'] } },
select: { id: true },
});
const fieldIds = locationFields.map((f) => f.id);
let matchingAgentIds: string[] = [];
if (fieldIds.length > 0) {
// Search jsonValue (JSON arrays) and textValue using raw SQL for case-insensitive JSON content matching
const locationLower = location.toLowerCase();
// Also match snake_case version (e.g., "New York" -> "new_york")
const locationSnakeCase = locationLower.replace(/\s+/g, '_');
const matchingFieldValues =
await this.prisma.agentProfileFieldValue.findMany({
where: {
fieldId: { in: fieldIds },
OR: [
{
textValue: { contains: location, mode: 'insensitive' },
},
],
},
select: { agentProfileId: true, jsonValue: true },
});
// Also fetch all field values with jsonValue for these fields to search within JSON arrays
const jsonFieldValues =
await this.prisma.agentProfileFieldValue.findMany({
where: {
fieldId: { in: fieldIds },
jsonValue: { not: Prisma.JsonNull },
},
select: { agentProfileId: true, jsonValue: true },
});
// Filter JSON array values that contain the location string
const jsonMatchIds = jsonFieldValues
.filter((fv) => {
const val = fv.jsonValue;
if (Array.isArray(val)) {
return val.some((v: unknown) => {
if (typeof v !== 'string') return false;
const vLower = v.toLowerCase();
return (
vLower.includes(locationLower) ||
vLower.includes(locationSnakeCase) ||
locationLower.includes(vLower) ||
locationSnakeCase.includes(vLower)
);
});
}
if (typeof val === 'string') {
return val.toLowerCase().includes(locationLower);
}
return false;
})
.map((fv) => fv.agentProfileId);
const textMatchIds = matchingFieldValues.map(
(fv) => fv.agentProfileId,
);
matchingAgentIds = [
...new Set([...textMatchIds, ...jsonMatchIds]),
];
}
// Search legacy columns OR dynamic field values
where.OR = [
...(where.OR || []),
{ city: { contains: location, mode: 'insensitive' } },
{ state: { contains: location, mode: 'insensitive' } },
{ country: { contains: location, mode: 'insensitive' } },
...(matchingAgentIds.length > 0
? [{ id: { in: matchingAgentIds } }]
: []),
];
}
if (city) { if (city) {
where.city = { contains: city, mode: 'insensitive' }; where.city = { contains: city, mode: 'insensitive' };
} }

View File

@@ -16,6 +16,14 @@ export class SearchAgentsDto {
@IsString() @IsString()
search?: string; search?: string;
@ApiPropertyOptional({
example: 'New York',
description: 'General location search across city, state, country, and dynamic field values',
})
@IsOptional()
@IsString()
location?: string;
@ApiPropertyOptional({ example: 'Los Angeles' }) @ApiPropertyOptional({ example: 'Los Angeles' })
@IsOptional() @IsOptional()
@IsString() @IsString()