console.log(`Loading nm3clol-express-app search router module...`); import express from 'express'; import { parse, toString } from 'lucene'; import { createClient, Query } from 'solr-client'; import { SearchResponse } from 'solr-client/dist/lib/solr.js'; import { config } from '../config.mjs'; import helpers from '../helpers/functions.mjs'; interface Dictionary { [Key: string]: T; } interface Highlight { text: string[]; } interface WithHighlighting { highlighting: Dictionary; } const router = express.Router(); router.get('/', (req: express.Request, res: express.Response) => { // Extract paging parameters from request query parameters let { q = '', page = 1, pageSize = 10 } = req.query; // Sanitize query, with particular emphasis on one problem area where soft keyboards are creating fancy quotes but we need basic quotes if (typeof q != "undefined") { if (typeof q != "string") { q = (q as string[]).join(' '); } q = q?.replaceAll(/[“”“”„„‟❝❞〝〞〟"❠⹂🙶🙷🙸]/g, '\"').replaceAll(/[‘’‘’'‚‛❛❜❟]/g, '\''); } if (page instanceof String) page = parseInt(page as string); if (pageSize instanceof String) pageSize = parseInt(pageSize as string); // Cap at 100 max per page pageSize = Math.min(pageSize as number, 100); // Calculate start offset for pagination const start = (page as number - 1) * pageSize; if (!q || (typeof q === 'string' && q.trim() == "")) { res.render('search-error', { h: helpers, query: q, error: { code: 400, message: 'Search query is required.'} }); } else { // Parse query let parsedQuery = parse(q); // Construct a Solr q field query string based on the extracted components let qQuery = toString(parsedQuery); // Generate a Solr query based on the query strings and additional parameters let solrQuery = new Query().df('text').q(qQuery).start(start).rows(10).hl({ on: true, q: qQuery, fl: '*', snippets: 5, formatter: 'simple', simplePre: ``, simplePost: ``, highlightMultiTerm: true, usePhraseHighlighter: true, }); // Create a Solr client const solrClient = createClient({ host: config.solrDocsHost, port: config.solrDocsPort, core: config.solrDocsCore }); solrClient.search(solrQuery) .then((solrResponse: SearchResponse|WithHighlighting) => { const solrResponseAsSearchResponse = solrResponse as SearchResponse; const solrResponseWithHighlighting = solrResponse as WithHighlighting; //console.log(require('util').inspect(solrResponse, { showHidden: true, depth: null, colors: true })); // overcome broken hl simplePre/simplePost implementation let overrideHighlighting: Dictionary = {}; Object.keys(solrResponseWithHighlighting.highlighting).forEach((highlight_key: string) => { overrideHighlighting[highlight_key] = solrResponseWithHighlighting.highlighting[highlight_key]; if (overrideHighlighting[highlight_key].text && overrideHighlighting[highlight_key].text.length > 0) { overrideHighlighting[highlight_key].text = overrideHighlighting[highlight_key].text.map( (text) => { return text.replaceAll("", ``).replaceAll("", "") }); } }); solrResponseWithHighlighting.highlighting = overrideHighlighting; // Calculate total number of results (needed for pagination) const totalResults = solrResponseAsSearchResponse.response.numFound; // Calculate total number of pages const totalPages = Math.ceil(totalResults / pageSize); res.render('search-results', { h: helpers, query: qQuery, page, pageSize, totalResults, totalPages, solrQuery: solrQuery, ...solrResponse }); // res.render('search-error', { h: helpers, query: sanitizedQuery, error: { code: 400, message: 'Search query is required.'} }); }) .catch(error => { if (typeof error === 'object' && error instanceof Error) { // check for error from throw new Error(`Request HTTP error ${response.statusCode}: ${text}`) in solr.ts from // solr-node-client dependency const detectRequestHttpErrorRegExLit = /^Request HTTP error (?\d{1,3}): (?\{.*\}$)/s; const detectRequestHttpErrorRegExp = new RegExp(detectRequestHttpErrorRegExLit); const matchRequestHttpErrorRegExpInError = error.message.match(detectRequestHttpErrorRegExp); const statusCode = (matchRequestHttpErrorRegExpInError && matchRequestHttpErrorRegExpInError.groups && matchRequestHttpErrorRegExpInError.groups.statusCode); const text = (matchRequestHttpErrorRegExpInError && matchRequestHttpErrorRegExpInError.groups && matchRequestHttpErrorRegExpInError.groups.text); if (text) { let solrRequestHttpInternalError = JSON.parse(text); error = { message: "Solr Client Request HTTP Error", code: statusCode, innerError: solrRequestHttpInternalError }; } else { error = { message: error }; } } res.render('search-error', { h: helpers, query: qQuery, error }); }); } // // Sanitize search query to prevent code injection // try { // // Validate search query // if (!query) { // //return res.status(400).json({ error: 'q parameter is required' }); // // } // else { // // Send search query to Solr // const response = await axios.get(solrUrl + '/select', { // params: { // q: `text:${sanitizedQuery}`, // Query string with field name // hl: 'true', // 'hl.method': 'unified', // 'hl.fl': '*', // 'hl.snippets': 5, // 'hl.tag.pre': '', // 'hl.tag.post': '', // 'hl.usePhraseHighlighter': true, // start, // Start offset for pagination // rows: 10, // Number of rows to return // wt: 'json', // Response format (JSON) // }, // }); // // // Extract search results from Solr response // const searchResults = response.data.response.docs; // const highlightedSnippets = response.data.highlighting; // // Calculate total number of results (needed for pagination) // const totalResults = response.data.response.numFound; // // Calculate total number of pages // const totalPages = Math.ceil(totalResults / pageSize); // // Send search results as JSON response // //res.json('search-results', { query, searchResults, highlightedSnippets, page, pageSize, totalResults, totalPages }); // res.render('search-results', { h: helpers, query: sanitizedQuery, searchResults, highlightedSnippets, page, pageSize, totalResults, totalPages }); // } // } catch (error) { // // console.error('Error searching Solr:', error.message); // // res.status(500).json({ error: 'Internal server error' }); // res.render('search-error', { h: helpers, query: sanitizedQuery, error }); // } }); export default { router, // solrUrl, // sanitizeQuery, };