const rateLimitMap = new Map(); function checkRateLimit(userId, limit = 5, windowMs = 1000) { const now = Date.now(); const key = userId; const entry = rateLimitMap.get(key); if (!entry || now > entry.resetTime) { rateLimitMap.set(key, { count: 1, resetTime: now + windowMs }); return true; } if (entry.count >= limit) { return false; } entry.count++; return true; } import { getSubsiteByHost } from '../../utils/subsites.js'; export async function GET({ request }) { const host = request.headers.get('host'); const subsite = getSubsiteByHost(host); if (!subsite || subsite.short !== 'req') { return new Response('Not found', { status: 404 }); } let userId = request.headers.get('cookie')?.split(';').find(c => c.trim().startsWith('nonce='))?.split('=')[1]; const hadCookie = !!userId; if (!userId) { userId = crypto.randomUUID(); } if (!checkRateLimit(userId)) { const response = new Response(JSON.stringify({ error: 'Rate limit exceeded' }), { status: 429, headers: { 'Content-Type': 'application/json' } }); if (!hadCookie) { response.headers.set('Set-Cookie', `nonce=${userId}; HttpOnly; Path=/; Max-Age=31536000`); } return response; } const TMDB_API_KEY = import.meta.env.TMDB_API_KEY; if (!TMDB_API_KEY) { console.error('TMDB_API_KEY not set'); const response = new Response(JSON.stringify([]), { status: 500, headers: { 'Content-Type': 'application/json' } }); if (!hadCookie) { response.headers.set('Set-Cookie', `nonce=${userId}; HttpOnly; Path=/; Max-Age=31536000`); } return response; } const url = new URL(request.url); const q = url.searchParams.get('q'); if (!q || typeof q !== 'string' || !q.trim()) { const response = new Response(JSON.stringify([]), { status: 200, headers: { 'Content-Type': 'application/json' } }); if (!hadCookie) { response.headers.set('Set-Cookie', `nonce=${userId}; HttpOnly; Path=/; Max-Age=31536000`); } return response; } if (q.length > 100) { const response = new Response(JSON.stringify([]), { status: 200, headers: { 'Content-Type': 'application/json' } }); if (!hadCookie) { response.headers.set('Set-Cookie', `nonce=${userId}; HttpOnly; Path=/; Max-Age=31536000`); } return response; } try { const apiResponse = await fetch(`https://api.themoviedb.org/3/search/multi?api_key=${TMDB_API_KEY}&query=${encodeURIComponent(q)}`); if (!apiResponse.ok) { throw new Error(`TMDB API error: ${apiResponse.status}`); } const data = await apiResponse.json(); const seen = new Set(); const filtered = data.results .filter(item => { if (item.media_type !== 'movie' && item.media_type !== 'tv') return false; const key = `${item.media_type}-${item.title || item.name}-${item.release_date?.split('-')[0] || item.first_air_date?.split('-')[0] || ''}`; if (seen.has(key)) return false; seen.add(key); return true; }) .slice(0, 10) // Limit to 10 suggestions .map(item => ({ label: item.title || item.name, value: item.title || item.name, year: item.release_date?.split('-')[0] || item.first_air_date?.split('-')[0], mediaType: item.media_type, overview: item.overview, poster_path: item.poster_path, })); const response = new Response(JSON.stringify(filtered), { headers: { 'Content-Type': 'application/json' } }); if (!hadCookie) { response.headers.set('Set-Cookie', `nonce=${userId}; HttpOnly; Path=/; Max-Age=31536000`); } return response; } catch (error) { console.error('Error fetching suggestions:', error); const response = new Response(JSON.stringify([]), { status: 500, headers: { 'Content-Type': 'application/json' } }); if (!hadCookie) { response.headers.set('Set-Cookie', `nonce=${userId}; HttpOnly; Path=/; Max-Age=31536000`); } return response; } }