import mongoose from 'mongoose'; import { createArrayFromCommaDelineated } from './createArrayFromCommaDelineated'; import { Field, TabAsField } from '../fields/config/types'; type SanitizeQueryValueArgs = { field: Field | TabAsField path: string operator: string, val: any hasCustomID: boolean } export const sanitizeQueryValue = ({ field, path, operator, val, hasCustomID }: SanitizeQueryValueArgs): unknown => { let formattedValue = val; // Disregard invalid _ids if (path === '_id' && typeof val === 'string' && val.split(',').length === 1) { if (!hasCustomID) { const isValid = mongoose.Types.ObjectId.isValid(val); if (!isValid) { return undefined; } } if (field.type === 'number') { const parsedNumber = parseFloat(val); if (Number.isNaN(parsedNumber)) { return undefined; } } } // Cast incoming values as proper searchable types if (field.type === 'checkbox' && typeof val === 'string') { if (val.toLowerCase() === 'true') formattedValue = true; if (val.toLowerCase() === 'false') formattedValue = false; } if (['all', 'not_in', 'in'].includes(operator) && typeof formattedValue === 'string') { formattedValue = createArrayFromCommaDelineated(formattedValue); if (field.type === 'number') { formattedValue = formattedValue.map((arrayVal) => parseFloat(arrayVal)); } } if (field.type === 'number' && typeof formattedValue === 'string') { formattedValue = Number(val); } if (field.type === 'date' && typeof val === 'string') { formattedValue = new Date(val); if (Number.isNaN(Date.parse(formattedValue))) { return undefined; } } if (['relationship', 'upload'].includes(field.type)) { if (val === 'null') { formattedValue = null; } if (operator === 'in' && Array.isArray(formattedValue)) { formattedValue = formattedValue.reduce((formattedValues, inVal) => { const newValues = [inVal]; if (mongoose.Types.ObjectId.isValid(inVal)) newValues.push(new mongoose.Types.ObjectId(inVal)); const parsedNumber = parseFloat(inVal); if (!Number.isNaN(parsedNumber)) newValues.push(parsedNumber); return [ ...formattedValues, ...newValues, ]; }, []); } } // Set up specific formatting necessary by operators if (operator === 'near') { let lng; let lat; let maxDistance; let minDistance; if (Array.isArray(formattedValue)) { [lng, lat, maxDistance, minDistance] = formattedValue; } if (typeof formattedValue === 'string') { [lng, lat, maxDistance, minDistance] = createArrayFromCommaDelineated(formattedValue); } if (!lng || !lat || (!maxDistance && !minDistance)) { formattedValue = undefined; } else { formattedValue = { $geometry: { type: 'Point', coordinates: [parseFloat(lng), parseFloat(lat)] }, }; if (maxDistance) formattedValue.$maxDistance = parseFloat(maxDistance); if (minDistance) formattedValue.$minDistance = parseFloat(minDistance); } } if (path !== '_id' || (path === '_id' && hasCustomID && field.type === 'text')) { if (operator === 'contains') { formattedValue = { $regex: formattedValue, $options: 'i' }; } } if (operator === 'exists') { formattedValue = (formattedValue === 'true' || formattedValue === true); } return formattedValue; };