import { serve } from "bun"; import { readFile } from "fs/promises"; import path from "path"; // Type Definition for Items (Packages/Programs) interface Item { name?: string; full_name?: string; owner?: string; description?: string; topics?: string[]; created_at?: string; usage_count?: number; } // --- CONFIGURATION --- const DB_PATH = "../database/database"; const PORT = 7860; const RELOAD_INTERVAL_MS = 3600000; // 1 hour const corsHeaders = { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*", }; // --- GLOBAL DATA STORE (in-memory) --- let packages: Item[] = []; let programs: Item[] = []; let sorted: { packages: { latest: Item[]; mostUsed: Item[] }; programs: { latest: Item[]; mostUsed: Item[] }; } = { packages: { latest: [], mostUsed: [] }, programs: { latest: [], mostUsed: [] } }; // --- UTILITY FUNCTIONS --- function getSortedData(packages: Item[], programs: Item[]) { return { packages: { latest: [...packages].sort(sortByDate), mostUsed: [...packages].sort(sortByUsage), }, programs: { latest: [...programs].sort(sortByDate), mostUsed: [...programs].sort(sortByUsage), }, }; } function sortByDate(a: Item, b: Item): number { return new Date(b.created_at || "").getTime() - new Date(a.created_at || "").getTime(); } function sortByUsage(a: Item, b: Item): number { return (b.usage_count || 0) - (a.usage_count || 0); } /** * Filters an array of items by query and topic filter. */ function filterItems(items: Item[], query: string | null, topic: string | null): Item[] { return items.filter(({ name, full_name, description, topics }) => { if (topic && !topics?.some(t => t.toLowerCase() === topic)) return false; if (!query) return true; const lowerQ = query.toLowerCase(); return [name, full_name, description, ...(topics || [])] .some(field => field?.toLowerCase().includes(lowerQ)); }); } /** * Returns a paginated slice of items. */ function getPaginated(items: Item[], page: number = 0, size: number = 10): Item[] { const start = page * size; return items.slice(start, start + size); } /** * Finds an item by owner and repo name. */ function findItem(items: Item[], owner: string, repo: string): Item | undefined { return items.find(({ owner: o, name, full_name }) => (o?.toLowerCase() === owner && name?.toLowerCase() === repo) || full_name?.toLowerCase() === `${owner}/${repo}` ); } /** * Parses a range string of the form "start..end" to [start, end]. */ function parseRange(str: string | null, max: number): [number, number] { const match = str?.match(/^(\d+)\.\.(\d+)$/); const [start, end] = match ? [parseInt(match[1]), parseInt(match[2])] : [0, 10]; return [Math.max(0, start), Math.min(max, end)]; } /** * Loads data from disk and updates in-memory database and sorted views. */ async function reloadData() { try { const pkgPath = path.resolve(__dirname, `${DB_PATH}/packages.json`); const prgPath = path.resolve(__dirname, `${DB_PATH}/programs.json`); const [pkgData, prgData] = await Promise.all([ readFile(pkgPath, "utf8"), readFile(prgPath, "utf8"), ]); packages = JSON.parse(pkgData); programs = JSON.parse(prgData); sorted = getSortedData(packages, programs); console.log(`[${new Date().toISOString()}] Database reloaded`); } catch (err) { console.error("Error reloading database files:", err); } } // Initial data load and set up periodic reload reloadData(); setInterval(reloadData, RELOAD_INTERVAL_MS); // --- SERVER DEFINITION --- serve({ port: PORT, fetch(req: Request): Response | Promise { const url = new URL(req.url); const { pathname, searchParams } = url; const q = searchParams.get("q")?.trim().toLowerCase() ?? null; const filter = searchParams.get("filter")?.trim().toLowerCase() ?? null; // Handle preflight CORS requests if (req.method === "OPTIONS") { return new Response(null, { status: 204, headers: { ...corsHeaders, "Access-Control-Allow-Methods": "GET, POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type", }, }); } // --- API ENDPOINTS --- // Search endpoints if (pathname === "/api/searchPackages") { const result = filterItems(packages, q, filter).slice(0, 25); return Response.json(result, { headers: corsHeaders }); } if (pathname === "/api/searchPrograms") { const result = filterItems(programs, q, filter).slice(0, 25); return Response.json(result, { headers: corsHeaders }); } // Infinite scroll endpoints if (pathname === "/api/infiniteScrollPackages") { const page = parseInt(searchParams.get("pageNumber") || "0", 10); if (isNaN(page) || page < 0) return Response.json({ error: "Invalid page number" }, { status: 400, headers: corsHeaders }); return Response.json(getPaginated(packages, page), { headers: corsHeaders }); } if (pathname === "/api/infiniteScrollPrograms") { const page = parseInt(searchParams.get("pageNumber") || "0", 10); if (isNaN(page) || page < 0) return Response.json({ error: "Invalid page number" }, { status: 400, headers: corsHeaders }); return Response.json(getPaginated(programs, page), { headers: corsHeaders }); } // Item details endpoints (by owner/repo) const programMatch = pathname.match(/^\/api\/programs\/([^\/]+)\/([^\/]+)$/); if (programMatch) { const [_, owner, repo] = programMatch.map(s => s.toLowerCase()); const found = findItem(programs, owner, repo); return Response.json(found || { error: "Program not found" }, { status: found ? 200 : 404, headers: corsHeaders, }); } const packageMatch = pathname.match(/^\/api\/packages\/([^\/]+)\/([^\/]+)$/); if (packageMatch) { const [_, owner, repo] = packageMatch.map(s => s.toLowerCase()); const found = findItem(packages, owner, repo); return Response.json(found || { error: "Package not found" }, { status: found ? 200 : 404, headers: corsHeaders, }); } // Index details endpoints (for homepage sections) if (pathname === "/api/indexDetailsPackages") { const section = searchParams.get("section"); if (section !== "latestRepos" && section !== "mostUsed") { return Response.json({ error: "Invalid section" }, { status: 400, headers: corsHeaders }); } const sortKey = section === "latestRepos" ? "latest" : "mostUsed"; const data = sorted.packages[sortKey as keyof typeof sorted.packages] ?? packages; const [start, end] = parseRange(searchParams.get("range"), data.length); return Response.json(data.slice(start, end), { headers: corsHeaders }); } if (pathname === "/api/indexDetailsPrograms") { const section = searchParams.get("section"); if (section !== "latestRepos" && section !== "mostUsed") { return Response.json({ error: "Invalid section" }, { status: 400, headers: corsHeaders }); } const sortKey = section === "latestRepos" ? "latest" : "mostUsed"; const data = sorted.programs[sortKey as keyof typeof sorted.programs] ?? programs; const [start, end] = parseRange(searchParams.get("range"), data.length); return Response.json(data.slice(start, end), { headers: corsHeaders }); } // Unknown endpoint return new Response("Not Found", { status: 404, headers: corsHeaders, }); }, }); console.log(`Server running on http://localhost:${PORT}`);