Filtered out felix-fx-i mod
Co-authored-by: felix-fx-top <253056634+felix-fx-top@users.noreply.github.com>
This commit is contained in:
parent
445c56af0c
commit
006673e11c
@ -11,7 +11,10 @@ const Index = () => {
|
||||
|
||||
const { data: userMods, isLoading: loadingUser } = useQuery({
|
||||
queryKey: ["user-projects"],
|
||||
queryFn: () => getUserProjects(),
|
||||
queryFn: async () => {
|
||||
const projects = await getUserProjects();
|
||||
return (projects || []).filter((p: any) => p.slug !== "felix-fx-i" && p.id !== "felix-fx-i");
|
||||
},
|
||||
});
|
||||
|
||||
const { data: discoverData, isLoading: loadingDiscover } = useQuery({
|
||||
|
||||
126
supabase/functions/fetch-curseforge/index.ts
Normal file
126
supabase/functions/fetch-curseforge/index.ts
Normal file
@ -0,0 +1,126 @@
|
||||
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
|
||||
|
||||
const corsHeaders = {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type, x-supabase-client-platform, x-supabase-client-platform-version, x-supabase-client-runtime, x-supabase-client-runtime-version",
|
||||
};
|
||||
|
||||
const CF_BASE = "https://api.curseforge.com/v1";
|
||||
const MINECRAFT_GAME_ID = 432;
|
||||
|
||||
// Rate limiting
|
||||
const rateLimitMap = new Map<string, { count: number; resetAt: number }>();
|
||||
const RATE_LIMIT = 30;
|
||||
const RATE_WINDOW = 60_000;
|
||||
|
||||
function checkRateLimit(ip: string): boolean {
|
||||
const now = Date.now();
|
||||
const entry = rateLimitMap.get(ip);
|
||||
if (!entry || now > entry.resetAt) {
|
||||
rateLimitMap.set(ip, { count: 1, resetAt: now + RATE_WINDOW });
|
||||
return true;
|
||||
}
|
||||
entry.count++;
|
||||
return entry.count <= RATE_LIMIT;
|
||||
}
|
||||
|
||||
function sanitizeString(str: string | null, maxLen = 100): string {
|
||||
if (!str) return "";
|
||||
return str.slice(0, maxLen).replace(/[<>{}]/g, "").trim();
|
||||
}
|
||||
|
||||
serve(async (req) => {
|
||||
if (req.method === "OPTIONS") {
|
||||
return new Response(null, { headers: corsHeaders });
|
||||
}
|
||||
|
||||
if (req.method !== "GET") {
|
||||
return new Response(JSON.stringify({ error: "Method not allowed" }), {
|
||||
status: 405,
|
||||
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
const clientIp = req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || "unknown";
|
||||
if (!checkRateLimit(clientIp)) {
|
||||
return new Response(JSON.stringify({ error: "Too many requests" }), {
|
||||
status: 429,
|
||||
headers: { ...corsHeaders, "Content-Type": "application/json", "Retry-After": "60" },
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const apiKey = Deno.env.get("CURSEFORGE_API_KEY");
|
||||
if (!apiKey) {
|
||||
return new Response(JSON.stringify({ error: "CurseForge API key not configured" }), {
|
||||
status: 500,
|
||||
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
const url = new URL(req.url);
|
||||
const action = sanitizeString(url.searchParams.get("action"), 20);
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
"Accept": "application/json",
|
||||
"x-api-key": apiKey,
|
||||
};
|
||||
|
||||
let cfUrl = "";
|
||||
|
||||
switch (action) {
|
||||
case "search": {
|
||||
const query = sanitizeString(url.searchParams.get("query"), 200);
|
||||
const offset = Math.max(0, Math.min(parseInt(url.searchParams.get("offset") || "0") || 0, 1000));
|
||||
const limit = Math.max(1, Math.min(parseInt(url.searchParams.get("limit") || "20") || 20, 50));
|
||||
const classId = sanitizeString(url.searchParams.get("classId"), 10);
|
||||
|
||||
cfUrl = `${CF_BASE}/mods/search?gameId=${MINECRAFT_GAME_ID}&searchFilter=${encodeURIComponent(query)}&index=${offset}&pageSize=${limit}&sortField=2&sortOrder=desc`;
|
||||
if (classId) {
|
||||
cfUrl += `&classId=${classId}`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "mod": {
|
||||
const id = sanitizeString(url.searchParams.get("id"), 20);
|
||||
if (!id || !/^\d+$/.test(id)) {
|
||||
return new Response(JSON.stringify({ error: "Invalid mod id" }), {
|
||||
status: 400,
|
||||
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
cfUrl = `${CF_BASE}/mods/${id}`;
|
||||
break;
|
||||
}
|
||||
case "files": {
|
||||
const id = sanitizeString(url.searchParams.get("id"), 20);
|
||||
if (!id || !/^\d+$/.test(id)) {
|
||||
return new Response(JSON.stringify({ error: "Invalid mod id" }), {
|
||||
status: 400,
|
||||
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
cfUrl = `${CF_BASE}/mods/${id}/files?pageSize=10`;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return new Response(JSON.stringify({ error: "Invalid action" }), {
|
||||
status: 400,
|
||||
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
const response = await fetch(cfUrl, { headers });
|
||||
const data = await response.json();
|
||||
|
||||
return new Response(JSON.stringify(data), {
|
||||
status: response.status,
|
||||
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
||||
});
|
||||
} catch (error) {
|
||||
return new Response(JSON.stringify({ error: "Internal server error" }), {
|
||||
status: 500,
|
||||
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user