feedsearch.ts 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. import type { APIRoute } from "astro";
  2. const cache = new Map<
  3. string,
  4. { data: string; timestamp: number; headers: Record<string, string> }
  5. >();
  6. const CACHE_TTL = 5 * 60 * 1000;
  7. export const GET: APIRoute = async ({ request }) => {
  8. const url = new URL(request.url);
  9. const target = url.searchParams.get("url");
  10. if (!target) {
  11. return new Response(JSON.stringify({ error: "Missing url parameter" }), {
  12. status: 400,
  13. headers: {
  14. "Content-Type": "application/json",
  15. "Access-Control-Allow-Origin": "*",
  16. },
  17. });
  18. }
  19. // Validate URL
  20. try {
  21. new URL(target);
  22. } catch {
  23. return new Response(JSON.stringify({ error: "Invalid URL provided" }), {
  24. status: 400,
  25. headers: {
  26. "Content-Type": "application/json",
  27. "Access-Control-Allow-Origin": "*",
  28. },
  29. });
  30. }
  31. try {
  32. const cached = cache.get(target);
  33. const now = Date.now();
  34. if (cached && now - cached.timestamp < CACHE_TTL) {
  35. return new Response(cached.data, {
  36. status: 200,
  37. headers: {
  38. ...cached.headers,
  39. "X-Cache": "HIT",
  40. "Access-Control-Allow-Origin": "*",
  41. },
  42. });
  43. }
  44. const upstream = `https://feedsearch.dev/api/v1/search?url=${encodeURIComponent(
  45. target,
  46. )}`;
  47. const response = await fetch(upstream, {
  48. headers: {
  49. "User-Agent": "Mozilla/5.0 (compatible; MikroFeedSearch/1.0)",
  50. },
  51. });
  52. if (!response.ok) {
  53. return new Response(
  54. JSON.stringify({
  55. error: `Failed to fetch feedsearch: ${response.status} ${response.statusText}`,
  56. }),
  57. {
  58. status: response.status,
  59. headers: {
  60. "Content-Type": "application/json",
  61. "Access-Control-Allow-Origin": "*",
  62. },
  63. },
  64. );
  65. }
  66. const data = await response.text();
  67. const contentType = response.headers.get("content-type") ?? "application/json";
  68. const headersToCache: Record<string, string> = {
  69. "Content-Type": contentType,
  70. };
  71. cache.set(target, {
  72. data,
  73. timestamp: now,
  74. headers: headersToCache,
  75. });
  76. return new Response(data, {
  77. status: 200,
  78. headers: {
  79. ...headersToCache,
  80. "X-Cache": "MISS",
  81. "Access-Control-Allow-Origin": "*",
  82. "Cache-Control": "public, max-age=300",
  83. },
  84. });
  85. } catch (error) {
  86. return new Response(
  87. JSON.stringify({
  88. error: "Failed to fetch feedsearch",
  89. details: error instanceof Error ? error.message : "Unknown error",
  90. }),
  91. {
  92. status: 500,
  93. headers: {
  94. "Content-Type": "application/json",
  95. "Access-Control-Allow-Origin": "*",
  96. },
  97. },
  98. );
  99. }
  100. };
  101. export const OPTIONS: APIRoute = async () => {
  102. return new Response(null, {
  103. status: 204,
  104. headers: {
  105. "Access-Control-Allow-Origin": "*",
  106. "Access-Control-Allow-Methods": "GET, OPTIONS",
  107. "Access-Control-Allow-Headers": "Content-Type",
  108. },
  109. });
  110. };