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:
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user