diff --git a/server.js b/server.js index ef5d474..08fc04f 100644 --- a/server.js +++ b/server.js @@ -2,41 +2,75 @@ const express = require('express'); const cors = require('cors'); const cinecheck = require('./aggregators/cinecheck-adapter'); const commonsense = require('./aggregators/commonsense-adapter'); -const filmages = require('./aggregators/filmages-adapter'); // New +const filmages = require('./aggregators/filmages-adapter'); const { mergeResults } = require('./merge'); const app = express(); app.use(cors()); +// Helper to normalize text and get words for matching +function getWords(text) { + if (!text || typeof text !== 'string') return []; + return text + .toLowerCase() + // Remove punctuation, keep letters, numbers, and whitespace. Handles Unicode. + .replace(/[^\p{L}\p{N}\s]/gu, '') + .replace(/\s+/g, ' ') // Normalize multiple spaces to single + .trim() + .split(' ') + .filter(Boolean); // Remove empty strings from split +} + app.get('/search', async (req, res) => { const q = req.query.q; if (!q) { - // Gilfoyle: "A search query without a query. Bold." - return res.status(400).json({ error: "Missing query. Astounding." }); + return res.status(400).json({ error: "Missing query. Predictable." }); } try { - // Mike: "Run 'em all. See what sticks." const [cine, cs, fa] = await Promise.all([ cinecheck.searchAndEnrich(q).catch(e => { console.error('Cinecheck failed:', e.message); return []; }), commonsense.searchAndEnrich(q).catch(e => { console.error('Commonsense failed:', e.message); return []; }), filmages.searchAndEnrich(q).catch(e => { console.error('Filmages failed:', e.message); return []; }) ]); - if (!cine.length && !cs.length && !fa.length) { - // Gilfoyle: "Zero results. Is this a surprise to anyone?" + let merged = mergeResults([cine, cs, fa]); + + // Sort merged results based on query relevance + const queryWords = getWords(q); + if (queryWords.length > 0) { + merged.forEach(item => { + const titleWords = getWords(item.title); + const uniqueQueryWords = [...new Set(queryWords)]; + const uniqueTitleWords = [...new Set(titleWords)]; + + let commonWordCount = 0; + for (const qw of uniqueQueryWords) { + if (uniqueTitleWords.includes(qw)) { + commonWordCount++; + } + } + + item.matchScore1 = uniqueQueryWords.length > 0 ? commonWordCount / uniqueQueryWords.length : 0; + + const unionLength = new Set([...uniqueQueryWords, ...uniqueTitleWords]).size; + item.matchScore2 = unionLength > 0 ? commonWordCount / unionLength : 0; + }); + + merged.sort((a, b) => { + if (b.matchScore1 !== a.matchScore1) return b.matchScore1 - a.matchScore1; + if (b.matchScore2 !== a.matchScore2) return b.matchScore2 - a.matchScore2; + return getWords(a.title).length - getWords(b.title).length; // Shorter titles preferred as tertiary sort + }); } - const merged = mergeResults([cine, cs, fa]); res.json(merged); } catch (e) { - // Mike: "Something went sideways. Happens." console.error('General search error:', e); - res.status(500).json({ error: e.message || "Server had a moment." }); + res.status(500).json({ error: e.message || "Server's taking a nap." }); } }); const PORT = 3000; app.listen(PORT, () => { - // Gilfoyle: "It's listening. Don't get excited." - console.log(`Backend multi-agrégateurs opérationnel sur http://localhost:${PORT}. Ne me remerciez pas.`); + console.log(`Backend sorting your life out on http://localhost:${PORT}. You're welcome.`); });