Skip to content
2 changes: 1 addition & 1 deletion src/adapters/postgres/cohort-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -858,7 +858,7 @@ export class PostgresCohortService {
if (Object.keys(searchCustomFields).length > 0) {
const context = "COHORT";
getCohortIdUsingCustomFields =
await this.fieldsService.filterUserUsingCustomFields(
await this.fieldsService.filterUserUsingCustomFieldsOptimized(
context,
searchCustomFields
);
Expand Down
65 changes: 65 additions & 0 deletions src/adapters/postgres/fields-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1538,6 +1538,71 @@ export class PostgresFieldsService implements IServicelocatorfields {
return result;
}

// OPTIMIZED VERSION - Much faster alternative to avoid JSON aggregation
async filterUserUsingCustomFieldsOptimized(context: string, stateDistBlockData: any) {
let joinCond = "";
let targetTable = "";

if (context === "COHORT") {
joinCond = `JOIN "Cohort" u ON fv."itemId" = u."cohortId"`;
targetTable = "Cohort";
} else if (context === "USERS") {
joinCond = `JOIN "Users" u ON fv."itemId" = u."userId"`;
targetTable = "Users";
} else {
// Generic case - no specific table join
targetTable = "FieldValues";
}

// Build EXISTS conditions for each field filter
const conditions = [];
let paramIndex = 1;
const queryParams = [];

for (const [fieldName, fieldValues] of Object.entries(stateDistBlockData)) {
const values = Array.isArray(fieldValues) ? fieldValues : [fieldValues];

// Create placeholders for parameterized query
const valuePlaceholders = values.map(() => `$${paramIndex++}`);
queryParams.push(...values);

const condition = `
EXISTS (
SELECT 1
FROM "FieldValues" fv_inner
JOIN "Fields" f_inner ON fv_inner."fieldId" = f_inner."fieldId"
WHERE fv_inner."itemId" = ${context === 'COHORT' ? 'c."cohortId"' : 'u."userId"'}
AND f_inner."name" = $${paramIndex}
AND (f_inner.context IN($${paramIndex + 1}, 'NULL', 'null', '') OR f_inner.context IS NULL)
AND fv_inner."value" && ARRAY[${valuePlaceholders.join(',')}]
)`;

queryParams.push(fieldName, context);
paramIndex += 2;
conditions.push(condition);
}

let query;
if (context === "COHORT") {
query = `
SELECT DISTINCT c."cohortId" as "itemId"
FROM "Cohort" c
WHERE ${conditions.join(' AND ')}`;
} else if (context === "USERS") {
query = `
SELECT DISTINCT u."userId" as "itemId"
FROM "Users" u
WHERE ${conditions.join(' AND ')}`;
} else {
// Fallback to original logic for unknown context
return this.filterUserUsingCustomFields(context, stateDistBlockData);
}

const queryData = await this.fieldsValuesRepository.query(query, queryParams);
const result = queryData.length > 0 ? queryData.map((item) => item.itemId) : null;
return result;
}

async filterUserUsingCustomFields(context: string, stateDistBlockData: any) {
const searchKey = [];
let whereCondition = ` WHERE `;
Expand Down
20 changes: 17 additions & 3 deletions src/adapters/postgres/user-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ export class PostgresUserService implements IServicelocator {
) {
const apiId = APIID.USER_LIST;
try {
const findData = await this.findAllUserDetails(userSearchDto);
const findData = await this.findAllUserDetails(userSearchDto, tenantId);

if (findData === false) {
LoggerUtil.error(
Expand Down Expand Up @@ -401,7 +401,7 @@ export class PostgresUserService implements IServicelocator {
}


async findAllUserDetails(userSearchDto) {
async findAllUserDetails(userSearchDto, tenantId?: string) {
let { limit, offset, filters, exclude, sort } = userSearchDto;
let excludeCohortIdes;
let excludeUserIdes;
Expand Down Expand Up @@ -513,7 +513,7 @@ export class PostgresUserService implements IServicelocator {

const context = "USERS";
getUserIdUsingCustomFields =
await this.fieldsService.filterUserUsingCustomFields(
await this.fieldsService.filterUserUsingCustomFieldsOptimized(
context,
searchCustomFields
);
Expand Down Expand Up @@ -554,6 +554,18 @@ export class PostgresUserService implements IServicelocator {
whereCondition = "";
}

// Apply tenant filtering conditionally if tenantId is provided from headers
if (tenantId && tenantId.trim() !== '') {
if (index === 0 && whereCondition === "") {
whereCondition = `WHERE UTM."tenantId" = '${tenantId}'`;
} else {
whereCondition += ` AND UTM."tenantId" = '${tenantId}'`;
}
LoggerUtil.log(`Applying tenant filter for tenantId: ${tenantId}`, APIID.USER_LIST);
} else {
LoggerUtil.warn(`No tenantId provided - returning users from all tenants`, APIID.USER_LIST);
}

//Get user core fields data
const query = `SELECT U."userId",U."enrollmentId", U."username",U."email", U."firstName",UTM."tenantId", U."middleName", U."lastName", U."gender", U."dob", R."name" AS role, U."mobile", U."createdBy",U."updatedBy", U."createdAt", U."updatedAt", U."status", COUNT(*) OVER() AS total_count
FROM public."Users" U
Expand Down Expand Up @@ -791,6 +803,7 @@ export class PostgresUserService implements IServicelocator {
T."channelId",
T.name AS tenantName,
T.params,
T."type",
UTM."Id" AS userTenantMappingId
FROM
public."UserTenantMapping" UTM
Expand Down Expand Up @@ -832,6 +845,7 @@ export class PostgresUserService implements IServicelocator {
params: data.params,
roleId: roleId,
roleName: roleName,
tenantType: data.type,
// privileges: privileges,
});
}
Expand Down