Add boolean field handling to filtering logic in utils.js
All checks were successful
farmcontrol/farmcontrol-api/pipeline/head This commit looks good
All checks were successful
farmcontrol/farmcontrol-api/pipeline/head This commit looks good
This update introduces functions to manage boolean fields in the filtering process, including parsing boolean operands and building equality/comparison conditions. The `buildCondition`, `parseLeafCondition`, and `parseFilter` functions have been modified to accommodate boolean field checks, enhancing the overall filtering capabilities across the application.
This commit is contained in:
parent
c043ad7a86
commit
6c694d7d48
92
src/utils.js
92
src/utils.js
@ -10,6 +10,7 @@ import config from './config.js';
|
||||
import crypto from 'crypto';
|
||||
import canonicalize from 'canonical-json';
|
||||
import { getModelByName } from './services/misc/model.js';
|
||||
import { models } from './database/schemas/models.js';
|
||||
import { createEmailRenderAuthCode } from './services/misc/emailRenderAuth.js';
|
||||
import { Worker } from 'worker_threads';
|
||||
import path from 'path';
|
||||
@ -63,6 +64,50 @@ function looksLikeDateField(property) {
|
||||
return /[a-z](?:At|Date|Time)$/.test(last) || /^(?:date|time|datetime)$/i.test(last);
|
||||
}
|
||||
|
||||
const NO_MATCH_CONDITION = { op: { $in: [] } };
|
||||
|
||||
function isBooleanField(property, model = null) {
|
||||
const schema = model?.schema;
|
||||
if (schema) {
|
||||
const path = schema.path(property);
|
||||
return path?.instance === 'Boolean';
|
||||
}
|
||||
|
||||
let found = false;
|
||||
for (const entry of Object.values(models)) {
|
||||
const path = entry.model?.schema?.path(property);
|
||||
if (!path) continue;
|
||||
found = true;
|
||||
if (path.instance !== 'Boolean') return false;
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
function parseBooleanOperand(value) {
|
||||
const lower = String(value).trim().toLowerCase();
|
||||
if (lower === 'yes') return true;
|
||||
if (lower === 'no') return false;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function buildBooleanEquality(value) {
|
||||
const bool = parseBooleanOperand(value);
|
||||
if (bool === undefined) return NO_MATCH_CONDITION;
|
||||
return { value: bool };
|
||||
}
|
||||
|
||||
function buildBooleanComparison(name, value) {
|
||||
if (name === 'eq') return buildBooleanEquality(value);
|
||||
|
||||
if (name === 'ne') {
|
||||
const bool = parseBooleanOperand(value);
|
||||
if (bool === undefined) return NO_MATCH_CONDITION;
|
||||
return { op: { $ne: bool } };
|
||||
}
|
||||
|
||||
return NO_MATCH_CONDITION;
|
||||
}
|
||||
|
||||
function isObjectIdString(value) {
|
||||
return /^[a-f\d]{24}$/i.test(value);
|
||||
}
|
||||
@ -197,7 +242,8 @@ function stripIgnoreCase(str) {
|
||||
}
|
||||
|
||||
// Builds an equality condition (value, date range, or wildcard regex).
|
||||
function buildEquality(value, isDateField) {
|
||||
function buildEquality(value, isDateField, isBooleanField = false) {
|
||||
if (isBooleanField) return buildBooleanEquality(value);
|
||||
if (isDateField) {
|
||||
const start = parseDateOperand(value, 'start');
|
||||
const end = parseDateOperand(value, 'end');
|
||||
@ -212,7 +258,8 @@ function buildEquality(value, isDateField) {
|
||||
}
|
||||
|
||||
// Builds a comparison condition for a single operator.
|
||||
function buildComparison(name, value, isDateField) {
|
||||
function buildComparison(name, value, isDateField, isBooleanField = false) {
|
||||
if (isBooleanField) return buildBooleanComparison(name, value);
|
||||
if (name === 'eq') return buildEquality(value, isDateField);
|
||||
|
||||
if (name === 'ne') {
|
||||
@ -233,15 +280,21 @@ function buildComparison(name, value, isDateField) {
|
||||
}
|
||||
|
||||
// Parses a leaf token (range, comparison, or plain value) into a condition.
|
||||
function parseLeafCondition(rawToken, isDateField) {
|
||||
function parseLeafCondition(rawToken, isDateField, isBooleanField = false) {
|
||||
const token = rawToken.trim();
|
||||
if (token === '') {
|
||||
if (isBooleanField) return NO_MATCH_CONDITION;
|
||||
return { op: { $regex: '^$', $options: 'i' } };
|
||||
}
|
||||
|
||||
// Interval: a..b, ..b, a..
|
||||
const rangeIdx = token.indexOf('..');
|
||||
if (rangeIdx !== -1) {
|
||||
if (isBooleanField) {
|
||||
const lo = stripIgnoreCase(token.slice(0, rangeIdx).trim());
|
||||
const hi = stripIgnoreCase(token.slice(rangeIdx + 2).trim());
|
||||
if (lo !== '' || hi !== '') return NO_MATCH_CONDITION;
|
||||
}
|
||||
const lo = stripIgnoreCase(token.slice(0, rangeIdx).trim());
|
||||
const hi = stripIgnoreCase(token.slice(rangeIdx + 2).trim());
|
||||
const op = {};
|
||||
@ -263,11 +316,16 @@ function parseLeafCondition(rawToken, isDateField) {
|
||||
];
|
||||
for (const [symbol, name] of operators) {
|
||||
if (token.startsWith(symbol)) {
|
||||
return buildComparison(name, stripIgnoreCase(token.slice(symbol.length).trim()), isDateField);
|
||||
return buildComparison(
|
||||
name,
|
||||
stripIgnoreCase(token.slice(symbol.length).trim()),
|
||||
isDateField,
|
||||
isBooleanField
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return buildEquality(stripIgnoreCase(token), isDateField);
|
||||
return buildEquality(stripIgnoreCase(token), isDateField, isBooleanField);
|
||||
}
|
||||
|
||||
// Converts a condition descriptor into a MongoDB query object for `property`.
|
||||
@ -309,26 +367,33 @@ function combineOr(children, property) {
|
||||
return { query: { $or: children.map((child) => conditionToQuery(child, property)) } };
|
||||
}
|
||||
|
||||
function buildCondition(node, property, isDateField) {
|
||||
function buildCondition(node, property, isDateField, isBooleanField = false) {
|
||||
if (node.type === 'leaf') {
|
||||
return parseLeafCondition(node.token, isDateField);
|
||||
return parseLeafCondition(node.token, isDateField, isBooleanField);
|
||||
}
|
||||
const children = node.items.map((item) => buildCondition(item, property, isDateField));
|
||||
const children = node.items.map((item) =>
|
||||
buildCondition(item, property, isDateField, isBooleanField)
|
||||
);
|
||||
return node.type === 'and' ? combineAnd(children, property) : combineOr(children, property);
|
||||
}
|
||||
|
||||
function parseFilter(property, value) {
|
||||
function parseFilter(property, value, model = null) {
|
||||
// Normalize state filter to state.type for schemas with state: { type }
|
||||
if (property === 'state') {
|
||||
property = 'state.type';
|
||||
}
|
||||
|
||||
const isBoolField = isBooleanField(property, model);
|
||||
|
||||
if (value?._id !== undefined && value?._id !== null) {
|
||||
return { [property]: { _id: new mongoose.Types.ObjectId(value._id) } };
|
||||
}
|
||||
|
||||
// Non-string values (actual booleans, numbers, objects, etc.) pass through.
|
||||
// Non-string values pass through; boolean fields only accept actual booleans.
|
||||
if (typeof value !== 'string') {
|
||||
if (isBoolField && typeof value !== 'boolean') {
|
||||
return { [property]: { $in: [] } };
|
||||
}
|
||||
return { [property]: value };
|
||||
}
|
||||
|
||||
@ -340,7 +405,7 @@ function parseFilter(property, value) {
|
||||
|
||||
const isDateField = looksLikeDateField(property);
|
||||
const tree = parseExpression(trimmed);
|
||||
const condition = buildCondition(tree, property, isDateField);
|
||||
const condition = buildCondition(tree, property, isDateField, isBoolField);
|
||||
return conditionToQuery(condition, property);
|
||||
}
|
||||
|
||||
@ -1136,14 +1201,13 @@ function mergeFilterClauses(clauses) {
|
||||
}
|
||||
|
||||
// Returns a filter object based on allowed filters and req.query
|
||||
function getFilter(query, allowedFilters, parse = true) {
|
||||
function getFilter(query, allowedFilters, parse = true, model = null) {
|
||||
const clauses = [];
|
||||
for (const [key, value] of Object.entries(query)) {
|
||||
if (allowedFilters.includes(key)) {
|
||||
clauses.push(parse ? parseFilter(key, value) : { [key]: value });
|
||||
clauses.push(parse ? parseFilter(key, value, model) : { [key]: value });
|
||||
}
|
||||
}
|
||||
console.log('clauses', clauses);
|
||||
return mergeFilterClauses(clauses);
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user