From c84cd055d41caef8569448696e2371789e1b491f Mon Sep 17 00:00:00 2001 From: Akhil Meka Date: Fri, 2 May 2025 16:27:52 +0530 Subject: [PATCH] updated filtering code --- src/index.ts | 10 +- src/notification/notification.service.ts | 4 +- src/organization/organization.service.ts | 46 +++---- src/pagination.ts | 27 ++-- src/permit/permit.service.ts | 8 +- src/processed/processed.service.ts | 4 +- src/rts/rts.service.ts | 102 +++++++-------- src/task/task.service.ts | 8 +- src/utils/queryParser.ts | 158 ++++++++--------------- src/view/view.service.ts | 4 +- 10 files changed, 160 insertions(+), 211 deletions(-) diff --git a/src/index.ts b/src/index.ts index 9a49859..c0cdddb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,12 +1,12 @@ -import mongoose from "mongoose"; -import app from "./server"; +import mongoose from 'mongoose'; +import app from './server'; (async () => { - const PORT = parseInt(process.env.PORT ?? "8000"); - const DB_URI = process.env.DB_URI ?? ""; + const PORT = parseInt(process.env.PORT ?? '8000'); + const DB_URI = process.env.DB_URI ?? ''; await mongoose.connect(DB_URI); - await app.listen({ port: PORT, host: "0.0.0.0" }); + await app.listen({ port: PORT, host: '0.0.0.0' }); })().catch((err) => { console.log(err); process.exit(1); diff --git a/src/notification/notification.service.ts b/src/notification/notification.service.ts index a347dba..e0e7760 100644 --- a/src/notification/notification.service.ts +++ b/src/notification/notification.service.ts @@ -43,11 +43,11 @@ export async function listNotifications( const page = params.page || 1; const pageSize = params.pageSize || 10; const sortObj = getSortObject(params, notificationFields); - const filterObj = getFilterObject(params, notificationFields); + const filterObj = getFilterObject(params); const pipeline: any = [ { - $match: { $and: [{ tenantId: tenantId }, ...filterObj] }, + $match: { $and: [{ tenantId: tenantId }, { ...filterObj }] }, }, ]; diff --git a/src/organization/organization.service.ts b/src/organization/organization.service.ts index 1382e27..0d85395 100644 --- a/src/organization/organization.service.ts +++ b/src/organization/organization.service.ts @@ -1,12 +1,12 @@ -import { getFilterObject, getSortObject, PageQueryParams } from "../pagination"; -import { ChangeEvent, dbEvents } from "../realtime"; -import { generateId } from "../utils/id"; +import { getFilterObject, getSortObject, PageQueryParams } from '../pagination'; +import { ChangeEvent, dbEvents } from '../realtime'; +import { generateId } from '../utils/id'; import { CreateOrgInput, orgFields, orgModel, UpdateOrgInput, -} from "./organization.schema"; +} from './organization.schema'; export async function createOrg(input: CreateOrgInput, tenantId: string) { const org = await orgModel.create({ @@ -17,13 +17,13 @@ export async function createOrg(input: CreateOrgInput, tenantId: string) { }); dbEvents.emit( - "change", + 'change', { - type: "insert", - collection: "orgs", + type: 'insert', + collection: 'orgs', document: org, } as ChangeEvent, - ["org:read"] + ['org:read'] ); return org; @@ -39,13 +39,13 @@ export async function listOrgs(params: PageQueryParams, tenantId: string) { const page = params.page || 1; const pageSize = params.pageSize || 10; const sortObj = getSortObject(params, orgFields); - const filterObj = getFilterObject(params, orgFields); + const filterObj = getFilterObject(params); const orgs = await orgModel.aggregate([ - { $match: { $and: [{ tenantId: tenantId }, ...filterObj] } }, + { $match: { $and: [{ tenantId: tenantId }, { ...filterObj }] } }, { $facet: { - metadata: [{ $count: "count" }], + metadata: [{ $count: 'count' }], data: [ { $sort: sortObj }, { $skip: (page - 1) * pageSize }, @@ -83,13 +83,13 @@ export async function updateOrg( if (updateOrgResult) { dbEvents.emit( - "change", + 'change', { - type: "update", - collection: "orgs", + type: 'update', + collection: 'orgs', document: updateOrgResult, } as ChangeEvent, - ["org:read"] + ['org:read'] ); } @@ -103,15 +103,15 @@ export async function deleteOrg(orgId: string, tenantId: string) { if (res.deletedCount > 0) { dbEvents.emit( - "change", + 'change', { - type: "delete", - collection: "orgs", + type: 'delete', + collection: 'orgs', document: { pid: orgId, }, } as ChangeEvent, - ["org:read"] + ['org:read'] ); } @@ -122,15 +122,15 @@ export async function searchOrgs(params: PageQueryParams, tenantId: string) { const page = params.page || 1; const pageSize = params.pageSize || 10; const sortObj = getSortObject(params, orgFields); - const filterObj = getFilterObject(params, orgFields); + const filterObj = getFilterObject(params); if (!params.searchToken) return { orgs: [], metadata: { count: 0, page, pageSize } }; - const regex = new RegExp(params.searchToken, "i"); + const regex = new RegExp(params.searchToken, 'i'); const orgs = await orgModel.aggregate([ - { $match: { $and: [{ tenantId: tenantId }, ...filterObj] } }, + { $match: { $and: [{ tenantId: tenantId }, { ...filterObj }] } }, { $match: { $or: [{ name: { $regex: regex } }, { domain: { $regex: regex } }], @@ -138,7 +138,7 @@ export async function searchOrgs(params: PageQueryParams, tenantId: string) { }, { $facet: { - metadata: [{ $count: "count" }], + metadata: [{ $count: 'count' }], data: [ { $sort: sortObj }, { $skip: (page - 1) * pageSize }, diff --git a/src/pagination.ts b/src/pagination.ts index ae65990..c554b9a 100644 --- a/src/pagination.ts +++ b/src/pagination.ts @@ -1,5 +1,5 @@ -import { z } from "zod"; -import { parse } from "./utils/queryParser"; +import { z } from 'zod'; +import { parse } from './utils/queryParser'; export const pageMetadata = z.object({ count: z.number(), @@ -23,30 +23,23 @@ export function getSortObject( ) { const sortObj: Record = {}; - if (params.sort && params.sort != "") { - const sortOptions = params.sort.split(","); + if (params.sort && params.sort != '') { + const sortOptions = params.sort.split(','); sortOptions.forEach((item) => { - const order = item.startsWith("-") ? -1 : 1; - const field = item.replace("-", "").trim(); + const order = item.startsWith('-') ? -1 : 1; + const field = item.replace('-', '').trim(); if (validFields.includes(field)) sortObj[field] = order; }); } - if (Object.keys(sortObj).length == 0) sortObj["createdAt"] = -1; + if (Object.keys(sortObj).length == 0) sortObj['createdAt'] = -1; return sortObj; } -export function getFilterObject( - params: PageQueryParams, - validFields: Array -) { - const filterObj: Array<{}> = []; - - if (params.filter && params.filter != "") { - filterObj.push(...parse(params.filter, validFields)); +export function getFilterObject(params: PageQueryParams) { + if (params.filter && params.filter != '') { + return parse(params.filter.split(',')); } - - return filterObj; } diff --git a/src/permit/permit.service.ts b/src/permit/permit.service.ts index 9cab538..18a3469 100644 --- a/src/permit/permit.service.ts +++ b/src/permit/permit.service.ts @@ -59,11 +59,11 @@ export async function listPermits(params: PageQueryParams, tenantId: string) { const page = params.page || 1; const pageSize = params.pageSize || 10; const sortObj = getSortObject(params, permitFields); - const filterObj = getFilterObject(params, permitFields); + const filterObj = getFilterObject(params); const permitsList = await permitModel.aggregate([ { - $match: { $and: [{ tenantId: tenantId }, ...filterObj] }, + $match: { $and: [{ tenantId: tenantId }, { ...filterObj }] }, }, { $lookup: { @@ -205,7 +205,7 @@ export async function searchPermit(params: PageQueryParams, tenantId: string) { const page = params.page || 1; const pageSize = params.pageSize || 10; const sortObj = getSortObject(params, permitFields); - const filterObj = getFilterObject(params, permitFields); + const filterObj = getFilterObject(params); if (!params.searchToken) return { permits: [], metadata: { count: 0, page, pageSize } }; @@ -214,7 +214,7 @@ export async function searchPermit(params: PageQueryParams, tenantId: string) { const permitsList = await permitModel.aggregate([ { - $match: { $and: [{ tenantId: tenantId }, ...filterObj] }, + $match: { $and: [{ tenantId: tenantId }, { ...filterObj }] }, }, { $match: { diff --git a/src/processed/processed.service.ts b/src/processed/processed.service.ts index c2045fd..2a99019 100644 --- a/src/processed/processed.service.ts +++ b/src/processed/processed.service.ts @@ -40,11 +40,11 @@ export async function listProcessedPermits( const page = params.page || 1; const pageSize = params.pageSize || 10; const sortObj = getSortObject(params, processedFields); - const filterObj = getFilterObject(params, processedFields); + const filterObj = getFilterObject(params); const pipeline: any = [ { - $match: { $and: [{ tenantId: tenantId }, ...filterObj] }, + $match: { $and: [{ tenantId: tenantId }, { ...filterObj }] }, }, ]; diff --git a/src/rts/rts.service.ts b/src/rts/rts.service.ts index 4e46ca9..06e1c22 100644 --- a/src/rts/rts.service.ts +++ b/src/rts/rts.service.ts @@ -4,13 +4,13 @@ import { rtsModel, UpdateRtsInput, UploadRtsInput, -} from "./rts.schema"; -import { AuthenticatedUser } from "../auth"; -import { generateId } from "../utils/id"; -import { getFilterObject, getSortObject, PageQueryParams } from "../pagination"; -import { getUser } from "../user/user.service"; -import { orgModel } from "../organization/organization.schema"; -import { userModel } from "../user/user.schema"; +} from './rts.schema'; +import { AuthenticatedUser } from '../auth'; +import { generateId } from '../utils/id'; +import { getFilterObject, getSortObject, PageQueryParams } from '../pagination'; +import { getUser } from '../user/user.service'; +import { orgModel } from '../organization/organization.schema'; +import { userModel } from '../user/user.schema'; export async function createRts( input: CreateRtsInput, @@ -53,43 +53,43 @@ export async function createRts( export async function getRts(id: string, tenantId: string) { return await rtsModel .findOne({ pid: id, tenantId: tenantId }) - .populate({ path: "county", select: "pid name avatar" }) - .populate({ path: "client", select: "pid name avatar" }) - .populate({ path: "createdBy", select: "pid name avatar" }); + .populate({ path: 'county', select: 'pid name avatar' }) + .populate({ path: 'client', select: 'pid name avatar' }) + .populate({ path: 'createdBy', select: 'pid name avatar' }); } export async function listRts(params: PageQueryParams, tenantId: string) { const page = params.page || 1; const pageSize = params.pageSize || 10; const sortObj = getSortObject(params, rtsFields); - const filterObj = getFilterObject(params, rtsFields); + const filterObj = getFilterObject(params); const rtsList = await rtsModel.aggregate([ { - $match: { $and: [{ tenantId: tenantId }, ...filterObj] }, + $match: { $and: [{ tenantId: tenantId }, { ...filterObj }] }, }, { $lookup: { - from: "organizations", - localField: "county", - foreignField: "_id", - as: "countyRec", + from: 'organizations', + localField: 'county', + foreignField: '_id', + as: 'countyRec', }, }, { $lookup: { - from: "organizations", - localField: "client", - foreignField: "_id", - as: "clientRec", + from: 'organizations', + localField: 'client', + foreignField: '_id', + as: 'clientRec', }, }, { $lookup: { - from: "users", - localField: "createdBy", - foreignField: "_id", - as: "createdRec", + from: 'users', + localField: 'createdBy', + foreignField: '_id', + as: 'createdRec', }, }, { @@ -101,36 +101,36 @@ export async function listRts(params: PageQueryParams, tenantId: string) { createdAt: 1, county: { $let: { - vars: { county: { $arrayElemAt: ["$countyRec", 0] } }, + vars: { county: { $arrayElemAt: ['$countyRec', 0] } }, in: { - _id: "$$county._id", - pid: "$$county.pid", - name: "$$county.name", - type: "$$county.type", - avatar: "$$county.avatar", + _id: '$$county._id', + pid: '$$county.pid', + name: '$$county.name', + type: '$$county.type', + avatar: '$$county.avatar', }, }, }, client: { $let: { - vars: { client: { $arrayElemAt: ["$clientRec", 0] } }, + vars: { client: { $arrayElemAt: ['$clientRec', 0] } }, in: { - _id: "$$client._id", - pid: "$$client.pid", - name: "$$client.name", - type: "$$client.type", - avatar: "$$client.avatar", + _id: '$$client._id', + pid: '$$client.pid', + name: '$$client.name', + type: '$$client.type', + avatar: '$$client.avatar', }, }, }, createdBy: { $let: { - vars: { created: { $arrayElemAt: ["$createdRec", 0] } }, + vars: { created: { $arrayElemAt: ['$createdRec', 0] } }, in: { - _id: "$$created._id", - pid: "$$created.pid", - name: "$$created.name", - avatar: "$$created.avatar", + _id: '$$created._id', + pid: '$$created.pid', + name: '$$created.name', + avatar: '$$created.avatar', }, }, }, @@ -138,7 +138,7 @@ export async function listRts(params: PageQueryParams, tenantId: string) { }, { $facet: { - metadata: [{ $count: "count" }], + metadata: [{ $count: 'count' }], data: [ { $sort: sortObj }, { $skip: (page - 1) * pageSize }, @@ -168,9 +168,9 @@ export async function updateRts( ) { const updatedRts = await rtsModel .findOneAndUpdate({ pid: id, tenantId: tenantId }, input, { new: true }) - .populate({ path: "createdBy", select: "pid name avatar" }) - .populate({ path: "county", select: "pid name avatar" }) - .populate({ path: "client", select: "pid name avatar" }); + .populate({ path: 'createdBy', select: 'pid name avatar' }) + .populate({ path: 'county', select: 'pid name avatar' }) + .populate({ path: 'client', select: 'pid name avatar' }); return updatedRts; } @@ -202,12 +202,12 @@ export async function getUniqueValuesRTS(field: string, tenenatId: string) { let values = await rtsModel.distinct(field, { tenantId: tenenatId }); let matchedValues = []; - if (field === "county") { - matchedValues = await orgModel.find().where("_id").in(values).exec(); - } else if (field === "client") { - matchedValues = await orgModel.find().where("_id").in(values).exec(); - } else if (field === "createdBy") { - matchedValues = await userModel.find().where("_id").in(values).exec(); + if (field === 'county') { + matchedValues = await orgModel.find().where('_id').in(values).exec(); + } else if (field === 'client') { + matchedValues = await orgModel.find().where('_id').in(values).exec(); + } else if (field === 'createdBy') { + matchedValues = await userModel.find().where('_id').in(values).exec(); } if (matchedValues.length > 0) { diff --git a/src/task/task.service.ts b/src/task/task.service.ts index 3d30c8e..135f94a 100644 --- a/src/task/task.service.ts +++ b/src/task/task.service.ts @@ -59,11 +59,11 @@ export async function listTasks(params: PageQueryParams, tenantId: string) { const page = params.page || 1; const pageSize = params.pageSize || 10; const sortObj = getSortObject(params, taskFields); - const filterObj = getFilterObject(params, taskFields); + const filterObj = getFilterObject(params); const taskList = await taskModel.aggregate([ { - $match: { $and: [{ tenantId: tenantId }, ...filterObj] }, + $match: { $and: [{ tenantId: tenantId }, { ...filterObj }] }, }, { $lookup: { @@ -145,13 +145,13 @@ export async function searchTasks(params: PageQueryParams, tenantId: string) { const page = params.page || 1; const pageSize = params.pageSize || 10; const sortObj = getSortObject(params, taskFields); - const filterObj = getFilterObject(params, taskFields); + const filterObj = getFilterObject(params); const regex = new RegExp(params.searchToken, 'i'); const taskList = await taskModel.aggregate([ { - $match: { $and: [{ tenantId: tenantId }, ...filterObj] }, + $match: { $and: [{ tenantId: tenantId }, { ...filterObj }] }, }, { $match: { diff --git a/src/utils/queryParser.ts b/src/utils/queryParser.ts index 4259627..9b96251 100644 --- a/src/utils/queryParser.ts +++ b/src/utils/queryParser.ts @@ -1,111 +1,67 @@ import mongoose from 'mongoose'; -type ParsedQuery = Array< - Record> ->; +type MongoFilter = Record; -function convertValue(value: any) { - if (mongoose.Types.ObjectId.isValid(value)) { - return mongoose.Types.ObjectId.createFromHexString(value); +function formulaToMongoFilter(formula: string): MongoFilter { + // Split the formula into parts + const operatorMatch = formula.match(/([^!<>=]+)([!<>=]+)(.+)/); + + if (!operatorMatch) { + throw new Error(`Invalid formula format: ${formula}`); + } + + const [, field, operator, value] = operatorMatch; + const trimmedField = field.trim(); + const trimmedValue = value.trim(); + + if (trimmedField === 'tenantId') return {}; + + // Convert value to appropriate type (date or number if possible) + let parsedValue: any = trimmedValue; + + if (/^\d{4}-\d{2}-\d{2}$/.test(trimmedValue)) { + // Check if it's a date (ISO format) + parsedValue = new Date(trimmedValue); + } else if (!isNaN(Number(trimmedValue))) { + // Check if it's a number + parsedValue = Number(trimmedValue); } else if ( - !isNaN(parseInt(value)) && - parseInt(value).toString().length == value.toString().length + typeof parsedValue === 'string' && + mongoose.Types.ObjectId.isValid(parsedValue) ) { - return parseInt(value); - } else if (!isNaN(Date.parse(value))) { - return new Date(value); - } else if (value === 'true') { - return true; - } else if (value === 'false') { - return false; - } else { - return value; + parsedValue = new mongoose.Types.ObjectId(parsedValue); + } else if (parsedValue === 'true') { + parsedValue = true; + } else if (parsedValue === 'false') { + parsedValue = false; + } else if (parsedValue === 'null') { + parsedValue = null; + } + + // Convert operator to MongoDB operator + switch (operator) { + case '=': + return { [trimmedField]: { $eq: parsedValue } }; + case '!=': + return { [trimmedField]: { $ne: parsedValue } }; + case '>': + return { [trimmedField]: { $gt: parsedValue } }; + case '<': + return { [trimmedField]: { $lt: parsedValue } }; + case '>=': + return { [trimmedField]: { $gte: parsedValue } }; + case '<=': + return { [trimmedField]: { $lte: parsedValue } }; + default: + throw new Error(`Unsupported operator: ${operator}`); } } -export function parse(query: string, validFields: Array): ParsedQuery { - const result = []; +export function parse(formulas: string[]): MongoFilter { + const parsedQuery: MongoFilter = formulas.reduce((filter, formula) => { + const newFilter = formulaToMongoFilter(formula); + return { ...filter, ...newFilter }; + }, {}); - let currentStage = 'field'; - let token = ''; - let field = ''; - let op = ''; - let value = ''; - let valueArr = []; - for (let i = 0; i < query.length; i++) { - let char = query[i]; - - if (currentStage === 'field') { - if (char === '=' || char === '!') { - field = token; - token = ''; - currentStage = 'value'; - - if (char === '=') { - op = '$eq'; - } else { - op = '$ne'; - i++; - } - - continue; - } - - token += char; - } - - if (currentStage === 'value') { - if (char === '[') { - currentStage = 'valueArr'; - continue; - } - - if (char === ',' || i == query.length - 1) { - if (i == query.length - 1) token += char; - - value = token; - result.push({ [field]: { [op]: convertValue(value) } }); - - token = ''; - field = ''; - op = ''; - - currentStage = 'field'; - - continue; - } - - token += char; - } - - if (currentStage === 'valueArr') { - if (char === ',') { - valueArr.push(convertValue(token)); - token = ''; - continue; - } - - if (char === ']') { - valueArr.push(convertValue(token)); - result.push({ - [field]: { [op === '$eq' ? '$in' : '$nin']: valueArr }, - }); - - token = ''; - field = ''; - op = ''; - valueArr = []; - - currentStage = 'field'; - i++; - - continue; - } - - token += char; - } - } - - console.log(result); - return result; + return parsedQuery; } diff --git a/src/view/view.service.ts b/src/view/view.service.ts index 9d8532d..a8b66e8 100644 --- a/src/view/view.service.ts +++ b/src/view/view.service.ts @@ -33,13 +33,13 @@ export async function listViews( const page = params.page || 1; const pageSize = params.pageSize || 10; const sortObj = getSortObject(params, viewFields); - const filterObj = getFilterObject(params, viewFields); + const filterObj = getFilterObject(params); return await viewModel.find({ $and: [ { tenantId: user.tenantId }, { createdBy: user.userId }, - ...filterObj, + { ...filterObj }, ], }); }