135 lines
4.5 KiB
JavaScript
135 lines
4.5 KiB
JavaScript
import { getSubsiteByHost } from '../../utils/subsites.js';
|
|
import {
|
|
checkRateLimit,
|
|
recordRequest,
|
|
getCookieId,
|
|
generateNonce,
|
|
createNonceCookie,
|
|
getClientIp,
|
|
} from '../../utils/rateLimit.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 });
|
|
}
|
|
|
|
// Rate limit check (5 requests per second, flood protection at 30/10s)
|
|
const rateCheck = checkRateLimit(request, {
|
|
limit: 5,
|
|
windowMs: 1000,
|
|
burstLimit: 30,
|
|
burstWindowMs: 10_000,
|
|
});
|
|
|
|
let cookieId = getCookieId(request);
|
|
const hadCookie = !!cookieId;
|
|
if (!cookieId) {
|
|
cookieId = generateNonce();
|
|
}
|
|
|
|
if (!rateCheck.allowed) {
|
|
const errorMsg = rateCheck.isFlooding
|
|
? { error: 'Too many requests - please slow down' }
|
|
: { error: 'Rate limit exceeded' };
|
|
const response = new Response(JSON.stringify(errorMsg), {
|
|
status: 429,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Retry-After': '1',
|
|
},
|
|
});
|
|
if (!hadCookie) {
|
|
response.headers.set('Set-Cookie', createNonceCookie(cookieId));
|
|
}
|
|
console.log(`[search] rate limited: ip=${rateCheck.ip} flooding=${rateCheck.isFlooding}`);
|
|
return response;
|
|
}
|
|
|
|
// Record the request for rate limiting
|
|
recordRequest(request, 1000);
|
|
|
|
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', createNonceCookie(cookieId));
|
|
}
|
|
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', createNonceCookie(cookieId));
|
|
}
|
|
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', createNonceCookie(cookieId));
|
|
}
|
|
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', createNonceCookie(cookieId));
|
|
}
|
|
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', createNonceCookie(cookieId));
|
|
}
|
|
return response;
|
|
}
|
|
} |