import dotenv from 'dotenv'; import log4js from 'log4js'; import mongoose from 'mongoose'; import { jobModel } from '../../schemas/production/job.schema.js'; import { subJobModel } from '../../schemas/production/subjob.schema.js'; import { printerModel } from '../../schemas/production/printer.schema.js'; import { filamentModel } from '../../schemas/management/filament.schema.js'; import { gcodeFileModel } from '../../schemas/production/gcodefile.schema.js'; import { partModel } from '../../schemas/management/part.schema.js'; import { productModel } from '../../schemas/management/product.schema.js'; import { vendorModel } from '../../schemas/management/vendor.schema.js'; import { filamentStockModel } from '../../schemas/inventory/filamentstock.schema.js'; import { stockEventModel } from '../../schemas/inventory/stockevent.schema.js'; import { stockAuditModel } from '../../schemas/inventory/stockaudit.schema.js'; import { partStockModel } from '../../schemas/inventory/partstock.schema.js'; import { auditLogModel } from '../../schemas/management/auditlog.schema.js'; import { userModel } from '../../schemas/management/user.schema.js'; import { noteTypeModel } from '../../schemas/management/notetype.schema.js'; import { noteModel } from '../../schemas/misc/note.schema.js'; dotenv.config(); const logger = log4js.getLogger('Jobs'); logger.level = process.env.LOG_LEVEL; // Map prefixes to models and id fields const PREFIX_MODEL_MAP = { PRN: { model: printerModel, idField: '_id', type: 'printer' }, FIL: { model: filamentModel, idField: '_id', type: 'filament' }, SPL: { model: null, idField: '_id', type: 'spool' }, // No spool model found GCF: { model: gcodeFileModel, idField: '_id', type: 'gcodefile' }, JOB: { model: jobModel, idField: '_id', type: 'job' }, PRT: { model: partModel, idField: '_id', type: 'part' }, PRD: { model: productModel, idField: '_id', type: 'product' }, VEN: { model: vendorModel, idField: '_id', type: 'vendor' }, SJB: { model: subJobModel, idField: '_id', type: 'subjob' }, FLS: { model: filamentStockModel, idField: '_id', type: 'filamentstock' }, SEV: { model: stockEventModel, idField: '_id', type: 'stockevent' }, SAU: { model: stockAuditModel, idField: '_id', type: 'stockaudit' }, PTS: { model: partStockModel, idField: '_id', type: 'partstock' }, PDS: { model: null, idField: '_id', type: 'productstock' }, // No productStockModel found ADL: { model: auditLogModel, idField: '_id', type: 'auditlog' }, USR: { model: userModel, idField: '_id', type: 'user' }, NTY: { model: noteTypeModel, idField: '_id', type: 'notetype' }, NTE: { model: noteModel, idField: '_id', type: 'note' }, }; // Helper function to build search filter from query parameters const buildSearchFilter = (params) => { const filter = {}; for (const [key, value] of Object.entries(params)) { // Skip pagination and limit parameters as they're not search filters if (key === 'limit' || key === 'page') continue; // Handle different field types if (key === 'name') { filter.name = { $regex: value, $options: 'i' }; // Case-insensitive search } else if (key === 'id' || key === '_id') { if (mongoose.Types.ObjectId.isValid(value)) { filter._id = value; } } else if (key === 'tags') { filter.tags = { $in: [new RegExp(value, 'i')] }; } else if (key === 'state') { filter['state.type'] = value; } else if (key.includes('.')) { // Handle nested fields like 'state.type', 'address.city', etc. filter[key] = { $regex: value, $options: 'i' }; } else { // For all other fields, do a case-insensitive search filter[key] = { $regex: value, $options: 'i' }; } } return filter; }; const trimSpotlightObject = (object) => { return { _id: object._id, name: object.name || undefined, state: object.state && object?.state.type ? { type: object.state.type } : undefined, tags: object.tags || undefined, email: object.email || undefined, color: object.color || undefined, updatedAt: object.updatedAt || undefined, }; }; export const getSpotlightRouteHandler = async (req, res) => { try { const query = req.params.query; const queryParams = req.query; if (query.length < 3) { res.status(200).send([]); return; } const prefix = query.substring(0, 3); const delimiter = query.substring(3, 4); const suffix = query.substring(4); if (delimiter == ':') { const prefixEntry = PREFIX_MODEL_MAP[prefix]; if (!prefixEntry || !prefixEntry.model) { res.status(400).send({ error: 'Invalid or unsupported prefix' }); return; } const { model, idField } = prefixEntry; // Validate ObjectId if the idField is '_id' if (idField === '_id' && !mongoose.Types.ObjectId.isValid(suffix)) { res.status(404).send({ error: `${prefix} not found` }); return; } // Find the object by the correct field const queryObj = {}; queryObj[idField] = suffix.toLowerCase(); let doc = await model.findOne(queryObj).lean(); if (!doc) { res.status(404).send({ error: `${prefix} not found` }); return; } // Build the response with only the required fields const response = trimSpotlightObject(doc); res.status(200).send(response); return; } console.log(queryParams); if (Object.keys(queryParams).length > 0) { const prefixEntry = PREFIX_MODEL_MAP[prefix]; console.log(prefixEntry); if (!prefixEntry || !prefixEntry.model) { res.status(400).send({ error: 'Invalid or unsupported prefix' }); return; } const { model } = prefixEntry; // Use req.query for search parameters if (Object.keys(queryParams).length === 0) { res.status(400).send({ error: 'No search parameters provided' }); return; } // Build search filter const searchFilter = buildSearchFilter(queryParams); // Perform search with limit const limit = parseInt(req.query.limit) || 10; const docs = await model.find(searchFilter).limit(limit).sort({ updatedAt: -1 }).lean(); // Format response const response = docs.map((doc) => trimSpotlightObject(doc)); res.status(200).send(response); return; } } catch (error) { logger.error('Error in spotlight lookup:', error); res.status(500).send({ error: error }); } };