Enhance utility functions in utils.js for improved reference model handling and filtering logic
All checks were successful
farmcontrol/farmcontrol-api/pipeline/head This commit looks good

This update introduces new functions such as `getRefModelEntryFromPrefix`, `stripPrefixFromOperand`, and `getRefModelEntryForToken` to streamline the processing of reference models and tokens. Additionally, the `resolveRefLeaf` and `resolveRefCondition` functions have been refactored to utilize these enhancements, improving the overall efficiency and clarity of the filtering and reference resolution processes. Minor formatting adjustments were also made in orderitems.js for consistency.
This commit is contained in:
Tom Butcher 2026-08-09 22:52:05 +01:00
parent 069406e4d8
commit 4bd29e2010
2 changed files with 79 additions and 49 deletions

View File

@ -5,31 +5,31 @@ import { getFilter, convertPropertiesString } from '../../utils.js';
const router = express.Router(); const router = express.Router();
const listAllowedFilters = [ const listAllowedFilters = [
'name', 'name',
'itemType', 'itemType',
'item', 'item',
'item._id', 'item._id',
'order', 'order',
'order._id', 'order._id',
'orderType', 'orderType',
'shipment', 'shipment',
'shipment._id', 'shipment._id',
'state', 'state',
'createdAt', 'createdAt',
'updatedAt', 'updatedAt',
'_reference' '_reference',
]; ];
const propertiesAllowedFilters = [ const propertiesAllowedFilters = [
'name', 'name',
'itemType', 'itemType',
'item', 'item',
'item._id', 'item._id',
'order', 'order',
'order._id', 'order._id',
'orderType', 'orderType',
'shipment', 'shipment',
'shipment._id', 'shipment._id',
]; ];
import { import {
listOrderItemsRouteHandler, listOrderItemsRouteHandler,
getOrderItemRouteHandler, getOrderItemRouteHandler,
@ -42,14 +42,13 @@ import {
getOrderItemHistoryRouteHandler, getOrderItemHistoryRouteHandler,
searchOrderItemsRouteHandler, searchOrderItemsRouteHandler,
getOrderItemPropertyValuesRouteHandler, getOrderItemPropertyValuesRouteHandler,
getOrderItemNeighborsRouteHandler,
getOrderItemNeighborsRouteHandler
} from '../../services/inventory/orderitems.js'; } from '../../services/inventory/orderitems.js';
// list of order items // list of order items
router.get('/', isAuthenticated, async (req, res) => { router.get('/', isAuthenticated, async (req, res) => {
const { page, limit, property, search, sort, order } = req.query; const { page, limit, property, search, sort, order } = req.query;
const filter = await getFilter(req.query, listAllowedFilters); const filter = await getFilter(req.query, listAllowedFilters);
listOrderItemsRouteHandler(req, res, page, limit, property, filter, search, sort, order); listOrderItemsRouteHandler(req, res, page, limit, property, filter, search, sort, order);
}); });
@ -72,7 +71,6 @@ router.get('/search', isAuthenticated, async (req, res) => {
searchOrderItemsRouteHandler(req, res, search); searchOrderItemsRouteHandler(req, res, search);
}); });
router.post('/', isAuthenticated, async (req, res) => { router.post('/', isAuthenticated, async (req, res) => {
newOrderItemRouteHandler(req, res); newOrderItemRouteHandler(req, res);
}); });

View File

@ -9,7 +9,7 @@ import log4js from 'log4js';
import config from './config.js'; import config from './config.js';
import crypto from 'crypto'; import crypto from 'crypto';
import canonicalize from 'canonical-json'; import canonicalize from 'canonical-json';
import { getModelByName } from './services/misc/model.js'; import { getModelByName, getModelByPrefix } from './services/misc/model.js';
import { models } from './database/schemas/models.js'; import { models } from './database/schemas/models.js';
import { createEmailRenderAuthCode } from './services/misc/emailRenderAuth.js'; import { createEmailRenderAuthCode } from './services/misc/emailRenderAuth.js';
import { Worker } from 'worker_threads'; import { Worker } from 'worker_threads';
@ -185,6 +185,30 @@ function getRefModelEntryFromSchemaRef(refName) {
}; };
} }
function getRefModelEntryFromPrefix(prefix) {
const entry = getModelByPrefix(prefix);
if (!entry?.model) return null;
return {
model: entry.model,
idField: entry.idField ?? '_id',
referenceField: entry.referenceField ?? '_reference',
};
}
function stripPrefixFromOperand(operand) {
const { suffix, hadPrefix } = parsePrefixedValue(String(operand).trim());
return hadPrefix ? suffix : String(operand).trim();
}
function getRefModelEntryForToken(token, fallbackRefName) {
const { prefix, hadPrefix } = parsePrefixedValue(String(token).trim());
if (hadPrefix) {
const entry = getRefModelEntryFromPrefix(prefix);
if (entry) return entry;
}
return getRefModelEntryFromSchemaRef(fallbackRefName);
}
function parseBooleanOperand(value) { function parseBooleanOperand(value) {
const lower = String(value).trim().toLowerCase(); const lower = String(value).trim().toLowerCase();
if (lower === 'yes') return true; if (lower === 'yes') return true;
@ -615,8 +639,10 @@ function combineAndRefIds(children) {
} }
async function resolveRefOperand(operand, refModelEntry, operator = 'eq') { async function resolveRefOperand(operand, refModelEntry, operator = 'eq') {
const operandText = String(operand).trim(); if (!refModelEntry?.model) return NO_MATCH_CONDITION;
const objectId = extractObjectIdFromOperand(operandText);
const lookupOperand = String(operand).trim();
const objectId = extractObjectIdFromOperand(lookupOperand);
if (objectId) { if (objectId) {
if (operator === 'ne') return { op: { $ne: objectId } }; if (operator === 'ne') return { op: { $ne: objectId } };
@ -624,7 +650,7 @@ async function resolveRefOperand(operand, refModelEntry, operator = 'eq') {
return NO_MATCH_CONDITION; return NO_MATCH_CONDITION;
} }
const expression = buildRefListExpression(operator, operandText); const expression = buildRefListExpression(operator, lookupOperand);
const ids = await listRefModelIds(refModelEntry, expression); const ids = await listRefModelIds(refModelEntry, expression);
if (operator === 'ne') { if (operator === 'ne') {
@ -635,13 +661,18 @@ async function resolveRefOperand(operand, refModelEntry, operator = 'eq') {
return idsToCondition(ids); return idsToCondition(ids);
} }
async function resolveRefLeaf(rawToken, refModelEntry) { async function resolveRefLeaf(rawToken, fallbackRefName) {
const token = rawToken.trim(); const token = rawToken.trim();
if (token === '') return NO_MATCH_CONDITION; if (token === '') return NO_MATCH_CONDITION;
const rangeIdx = token.indexOf('..'); const refModelEntry = getRefModelEntryForToken(token, fallbackRefName);
if (!refModelEntry?.model) return NO_MATCH_CONDITION;
const lookupToken = stripPrefixFromOperand(token);
const rangeIdx = lookupToken.indexOf('..');
if (rangeIdx !== -1) { if (rangeIdx !== -1) {
const ids = await listRefModelIds(refModelEntry, token); const ids = await listRefModelIds(refModelEntry, lookupToken);
return idsToCondition(ids); return idsToCondition(ids);
} }
@ -654,37 +685,33 @@ async function resolveRefLeaf(rawToken, refModelEntry) {
['=', 'eq'], ['=', 'eq'],
]; ];
for (const [symbol, name] of operators) { for (const [symbol, name] of operators) {
if (token.startsWith(symbol)) { if (lookupToken.startsWith(symbol)) {
return resolveRefOperand(token.slice(symbol.length), refModelEntry, name); return resolveRefOperand(lookupToken.slice(symbol.length), refModelEntry, name);
} }
} }
const objectId = extractObjectIdFromOperand(token); const objectId = extractObjectIdFromOperand(lookupToken);
if (objectId) return { value: objectId }; if (objectId) return { value: objectId };
const ids = await listRefModelIds(refModelEntry, token); const ids = await listRefModelIds(refModelEntry, lookupToken);
return idsToCondition(ids); return idsToCondition(ids);
} }
async function resolveRefCondition(node, refModelEntry) { async function resolveRefCondition(node, fallbackRefName) {
if (node.type === 'leaf') { if (node.type === 'leaf') {
return resolveRefLeaf(node.token, refModelEntry); return resolveRefLeaf(node.token, fallbackRefName);
} }
const children = await Promise.all( const children = await Promise.all(
node.items.map((item) => resolveRefCondition(item, refModelEntry)) node.items.map((item) => resolveRefCondition(item, fallbackRefName))
); );
return node.type === 'and' ? combineAndRefIds(children) : combineOrRefIds(children); return node.type === 'and' ? combineAndRefIds(children) : combineOrRefIds(children);
} }
async function resolveObjectRefFilter(filterProperty, expression, refName) { async function resolveObjectRefFilter(filterProperty, expression, fallbackRefName) {
const refModelEntry = getRefModelEntryFromSchemaRef(refName);
if (!refModelEntry?.model) return { [filterProperty]: { $in: [] } };
const tree = parseExpression(expression); const tree = parseExpression(expression);
const condition = await resolveRefCondition(tree, refModelEntry); const condition = await resolveRefCondition(tree, fallbackRefName);
return conditionToQuery(condition, filterProperty); return conditionToQuery(condition, filterProperty);
} }
@ -1524,7 +1551,12 @@ function mergeFilterClauses(clauses) {
// Returns a filter object based on allowed filters and req.query // Returns a filter object based on allowed filters and req.query
async function getFilter(query, allowedFilters, parse = true, model = null) { async function getFilter(query, allowedFilters, parse = true, model = null) {
const clauses = []; const clauses = [];
for (const [key, value] of Object.entries(query)) { const queryClean = { ...query };
for (const key of ['sort', 'order', 'page', 'limit']) {
if (key in queryClean) delete queryClean[key];
}
logger.info('queryExcludingSortAndOrder', queryClean);
for (const [key, value] of Object.entries(queryClean)) {
if (allowedFilters.includes(key)) { if (allowedFilters.includes(key)) {
clauses.push(parse ? await parseFilter(key, value, model) : { [key]: value }); clauses.push(parse ? await parseFilter(key, value, model) : { [key]: value });
} }