fix(suggest): route sous /api ET /proxy/api + tri prefix-first style YouTube

This commit is contained in:
2026-09-26 13:56:55 -04:00
parent d9c8a2e14e
commit 4a7cb901b2
3 changed files with 24 additions and 1 deletions
+5 -1
View File
@@ -2584,7 +2584,11 @@ const suggestLimiter = rateLimit({
standardHeaders: true,
legacyHeaders: false,
});
app.get('/api/search/suggest', suggestLimiter, async (req, res) => {
// NOTE: montée sur le router `r` (et non `app`) pour être servie sous les
// DEUX préfixes `/api` et `/proxy/api` (cf. apiBase() côté front : en prod le
// front appelle `/proxy/api`, sinon fallback SPA -> index.html -> parse error
// et seules les suggestions locales s'affichaient). Même pattern que transcript.
r.get('/search/suggest', suggestLimiter, async (req, res) => {
try {
const rawQ = typeof req.query.q === 'string' ? req.query.q : '';
const q = rawQ.trim();
+9
View File
@@ -155,6 +155,15 @@ try {
expect(JSON.stringify(withoutWeb) === JSON.stringify(['dm', 'od', 'pt', 'ru', 'tw', 'yt']), `full registry fallback (got ${keys})`);
});
await scenario('served under both /api and /proxy/api (front apiBase)', async () => {
for (const prefix of ['/api', '/proxy/api']) {
const res = await fetch(`${baseUrl}${prefix}/search/suggest?q=tutoriel&providers=yt&limit=3`);
expect(res.status === 200, `expected 200 on ${prefix}, got ${res.status}`);
const body = await res.json();
expect(body.q === 'tutoriel' && Array.isArray(body.groups?.yt), `${prefix} returns suggest JSON`);
}
});
await scenario('no duplicate occurrence across groups (web enrichment included)', async () => {
const res = await fetch(`${baseUrl}/api/search/suggest?q=tutoriel&limit=10`);
expect(res.status === 200, `expected 200, got ${res.status}`);
@@ -98,6 +98,16 @@ export class SearchBoxComponent {
seen.add(k);
return true;
});
// Tri « style YouTube/Google » : les complétions qui commencent par la
// frappe d'abord, puis le reste (tri stable : l'ordre providers est gardé).
const nq = normSuggest(q);
if (nq) {
merged.sort((a, b) => {
const pa = normSuggest(a.text).startsWith(nq) ? 0 : 1;
const pb = normSuggest(b.text).startsWith(nq) ? 0 : 1;
return pa - pb;
});
}
const out: SuggestFlatItem[] = [...recents, ...merged];
// Tier 3 — suggestions locales générées à la frappe (sans réseau) : la
// frappe propose toujours quelque chose, même sans provider suggest