feat: Implement section access validation for agent filtering and refine filterable field retrieval to include only fields from global or assigned sections.

This commit is contained in:
pradeepkumar
2026-02-24 21:45:39 +05:30
parent 49bd08c5fe
commit d30358755e
2 changed files with 47 additions and 12 deletions

View File

@@ -236,41 +236,67 @@ export class AgentsService {
const filterSlugs = Object.keys(parsedFilters); const filterSlugs = Object.keys(parsedFilters);
if (filterSlugs.length > 0) { if (filterSlugs.length > 0) {
// Get field IDs for the filter slugs // Get field IDs for the filter slugs, including section info for assignment validation
const fields = await this.prisma.profileField.findMany({ const fields = await this.prisma.profileField.findMany({
where: { slug: { in: filterSlugs } }, where: { slug: { in: filterSlugs } },
select: { id: true, slug: true }, select: {
id: true,
slug: true,
sectionId: true,
section: {
select: {
isGlobal: true,
agentTypeSections: {
select: { agentTypeId: true },
},
},
},
},
}); });
const slugToFieldId = new Map(fields.map((f) => [f.slug, f.id])); const slugToField = new Map(fields.map((f) => [f.slug, f]));
// For each filter, find agents that have ANY of the selected values // For each filter, find agents that have ANY of the selected values
// Use AND logic between different fields // Use AND logic between different fields
const filterConditions: string[][] = []; const filterConditions: string[][] = [];
for (const [slug, selectedValues] of Object.entries(parsedFilters)) { for (const [slug, selectedValues] of Object.entries(parsedFilters)) {
const fieldId = slugToFieldId.get(slug); const field = slugToField.get(slug);
if (!fieldId || !selectedValues || selectedValues.length === 0) continue; if (!field || !selectedValues || selectedValues.length === 0) continue;
// Build set of agent type IDs that have access to this field's section
const isGlobal = field.section.isGlobal;
const allowedAgentTypeIds = new Set(
field.section.agentTypeSections.map((ats) => ats.agentTypeId),
);
// Find agentProfileIds where jsonValue contains ANY of the selected values // Find agentProfileIds where jsonValue contains ANY of the selected values
const matchingValues = await this.prisma.agentProfileFieldValue.findMany({ const matchingValues = await this.prisma.agentProfileFieldValue.findMany({
where: { where: { fieldId: field.id },
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: { select: {
agentProfileId: true, agentProfileId: true,
jsonValue: true, jsonValue: true,
agentProfile: {
select: { agentTypeId: true },
},
}, },
}); });
// Filter in memory - check if jsonValue array contains any selected value // Filter in memory:
// 1. Check if jsonValue array contains any selected value
// 2. Check if the agent's type has access to this field's section
const matchingAgentIds = matchingValues const matchingAgentIds = matchingValues
.filter((fv) => { .filter((fv) => {
const jsonVal = fv.jsonValue as string[] | null; const jsonVal = fv.jsonValue as string[] | null;
if (!jsonVal || !Array.isArray(jsonVal)) return false; if (!jsonVal || !Array.isArray(jsonVal)) return false;
return selectedValues.some((sv) => jsonVal.includes(sv)); if (!selectedValues.some((sv) => jsonVal.includes(sv))) return false;
// Validate that this agent's type has access to the field's section
const agentTypeId = fv.agentProfile.agentTypeId;
if (!isGlobal && (!agentTypeId || !allowedAgentTypeIds.has(agentTypeId))) {
return false;
}
return true;
}) })
.map((fv) => fv.agentProfileId); .map((fv) => fv.agentProfileId);

View File

@@ -235,6 +235,7 @@ export class ProfileFieldsService {
/** /**
* Get all filterable fields (public endpoint for search filters) * Get all filterable fields (public endpoint for search filters)
* Returns fields with CHECKBOX_GROUP, SELECT, MULTI_SELECT types that have options * Returns fields with CHECKBOX_GROUP, SELECT, MULTI_SELECT types that have options
* Only includes fields from sections that are global or assigned to at least one agent type
* De-duplicates by both slug AND name to handle duplicate fields with different slugs * De-duplicates by both slug AND name to handle duplicate fields with different slugs
*/ */
async getFilterableFields() { async getFilterableFields() {
@@ -245,6 +246,14 @@ export class ProfileFieldsService {
isActive: true, isActive: true,
fieldType: { in: filterableTypes }, fieldType: { in: filterableTypes },
options: { not: Prisma.JsonNull }, options: { not: Prisma.JsonNull },
// Only include fields from sections that are global or assigned to at least one agent type
section: {
isActive: true,
OR: [
{ isGlobal: true },
{ agentTypeSections: { some: {} } },
],
},
}, },
select: { select: {
id: true, id: true,