import type { Field, TabAsField } from 'payload' import mongoose from 'mongoose' import { createArrayFromCommaDelineated } from 'payload' type SanitizeQueryValueArgs = { field: Field | TabAsField hasCustomID: boolean operator: string path: string val: any } export const sanitizeQueryValue = ({ field, hasCustomID, operator, path, val, }: SanitizeQueryValueArgs): { operator?: string rawQuery?: unknown val?: unknown } => { let formattedValue = val let formattedOperator = operator // 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 { operator: formattedOperator, val: undefined } } } if (field.type === 'number') { const parsedNumber = parseFloat(val) if (Number.isNaN(parsedNumber)) { return { operator: formattedOperator, val: 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', 'in', 'not_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' && operator !== 'exists') { formattedValue = new Date(val) if (Number.isNaN(Date.parse(formattedValue))) { return undefined } } if (['relationship', 'upload'].includes(field.type)) { if (val === 'null') { formattedValue = null } // Object equality requires the value to be the first key in the object that is being queried. if ( operator === 'equals' && formattedValue && typeof formattedValue === 'object' && formattedValue.value && formattedValue.relationTo ) { return { rawQuery: { $and: [ { [`${path}.value`]: { $eq: formattedValue.value } }, { [`${path}.relationTo`]: { $eq: formattedValue.relationTo } }, ], }, } } 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 == null || lat == null || (maxDistance == null && minDistance == null)) { 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 (operator === 'within' || operator === 'intersects') { formattedValue = { $geometry: formattedValue, } } if (path !== '_id' || (path === '_id' && hasCustomID && field.type === 'text')) { if (operator === 'contains') { formattedValue = { $options: 'i', $regex: formattedValue.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&'), } } } if ( (path === '_id' || path === 'parent') && operator === 'like' && formattedValue.length === 24 && !hasCustomID ) { formattedOperator = 'equals' } if (operator === 'exists') { formattedValue = formattedValue === 'true' || formattedValue === true // Clearable fields if (['relationship', 'select', 'upload'].includes(field.type)) { if (formattedValue) { return { rawQuery: { $and: [{ [path]: { $exists: true } }, { [path]: { $ne: null } }], }, } } else { return { rawQuery: { $or: [{ [path]: { $exists: false } }, { [path]: { $eq: null } }], }, } } } } return { operator: formattedOperator, val: formattedValue } }