diff --git a/apps/web/app/api/docs/search/route.ts b/apps/web/app/api/docs/search/route.ts index 886d8d88..10b4d081 100644 --- a/apps/web/app/api/docs/search/route.ts +++ b/apps/web/app/api/docs/search/route.ts @@ -3,6 +3,7 @@ import fs from 'fs' import path from 'path' import matter from 'gray-matter' import { docSections } from '@/lib/docroutes' +import { checkWebhookCooldown, clientIp } from '@/lib/demo-limits' export type SearchResult = { title: string @@ -100,6 +101,15 @@ function getCorpus(): IndexedDoc[] { const MAX_QUERY_LENGTH = 128 export async function GET(request: NextRequest) { + const ip = clientIp(request) + const cooldown = checkWebhookCooldown(ip) + if (!cooldown.ok) { + return NextResponse.json(cooldown.body, { + status: 429, + headers: { 'Retry-After': String(Math.ceil(cooldown.body.retryAfterMs / 1000)) }, + }) + } + const query = request.nextUrl.searchParams.get('q')?.trim().slice(0, MAX_QUERY_LENGTH) ?? '' if (query.length < 2) { diff --git a/apps/web/components/docs/SearchDialog.tsx b/apps/web/components/docs/SearchDialog.tsx index acdeb0bd..3d9ea195 100644 --- a/apps/web/components/docs/SearchDialog.tsx +++ b/apps/web/components/docs/SearchDialog.tsx @@ -66,6 +66,11 @@ export default function SearchDialog({ open, onClose }: Props) { debounceRef.current = setTimeout(async () => { try { const res = await fetch(`/api/docs/search?q=${encodeURIComponent(query)}`) + if (!res.ok) { + setResults([]) + setSelected(0) + return + } const data: SearchResult[] = await res.json() setResults(data) setSelected(0)