
Refactor index.ts to streamline data sorting and filtering for packages and programs; implement indexDetails endpoints
33ffc9c
import { serve } from "bun"; | |
import packages from "../database/database/packages.json" assert { type: "json" }; | |
import programs from "../database/database/programs.json" assert { type: "json" }; | |
const typedPackages: Item[] = packages as Item[]; | |
const typedPrograms: Item[] = programs as Item[]; | |
type Item = { | |
name?: string; | |
full_name?: string; | |
owner?: string; | |
description?: string | null; | |
topics?: string[]; | |
created_at?: string; | |
usage_count?: number; | |
}; | |
const corsHeaders = { | |
"Content-Type": "application/json", | |
"Access-Control-Allow-Origin": "*", | |
}; | |
const sortByDate = (a: Item, b: Item) => | |
new Date(b.created_at ?? "").getTime() - new Date(a.created_at ?? "").getTime(); | |
const sortByUsage = (a: Item, b: Item) => | |
(b.usage_count ?? 0) - (a.usage_count ?? 0); | |
const sorted = { | |
packages: { | |
latest: [...typedPackages].sort(sortByDate), | |
mostUsed: [...typedPackages].sort(sortByUsage), | |
}, | |
programs: { | |
latest: [...typedPrograms].sort(sortByDate), | |
mostUsed: [...typedPrograms].sort(sortByUsage), | |
}, | |
}; | |
mostUsed: [...programs].sort(sortByUsage), | |
}, | |
}; | |
function filterItems(items: Item[], q: string | null, filter: string | null) { | |
return items.filter(({ name, full_name, description, topics }) => { | |
if (filter && !topics?.some(t => t.toLowerCase() === filter)) return false; | |
if (!q) return true; | |
const qlc = q.toLowerCase(); | |
return [name, full_name, description, ...(topics ?? [])].some( | |
field => field?.toLowerCase().includes(qlc) | |
); | |
}); | |
} | |
function getPaginated(items: Item[], page = 0, size = 10) { | |
return items.slice(page * size, page * size + size); | |
} | |
function findItem(items: Item[], owner: string, repo: string) { | |
owner = owner.toLowerCase(); | |
repo = repo.toLowerCase(); | |
return items.find( | |
({ owner: o, name, full_name }) => | |
(o?.toLowerCase() === owner && name?.toLowerCase() === repo) || | |
full_name?.toLowerCase() === `${owner}/${repo}` | |
); | |
} | |
function parseRange(str: string | null, max: number): [number, number] { | |
const m = str?.match(/^(\d+)\.\.(\d+)$/); | |
const [start, end] = m ? [parseInt(m[1]), parseInt(m[2])] : [0, 10]; | |
return [Math.max(0, start), Math.min(max, end)]; | |
} | |
serve({ | |
port: 7860, | |
fetch(req) { | |
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; | |
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", | |
}, | |
}); | |
if (pathname === "/api/searchPackages") | |
return Response.json(filterItems(packages, q, filter).slice(0, 25), { headers: corsHeaders }); | |
if (pathname === "/api/searchPrograms") | |
return Response.json(filterItems(programs, q, filter).slice(0, 25), { headers: corsHeaders }); | |
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 }); | |
} | |
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, | |
}); | |
} | |
if (pathname === "/api/indexDetailsPackages" || pathname === "/api/indexDetailsPrograms") { | |
const section = searchParams.get("section"); | |
if (section !== "latestRepos" && section !== "mostUsed") | |
return Response.json({ error: "Invalid section" }, { status: 400, headers: corsHeaders }); | |
const key = section === "latestRepos" ? "latest" : "mostUsed"; | |
const which = pathname.includes("Packages") ? "packages" : "programs"; | |
const data = sorted[which as "packages" | "programs"][key as "latest" | "mostUsed"] ?? (which === "packages" ? packages : programs); | |
const [start, end] = parseRange(searchParams.get("range"), data.length); | |
return Response.json(data.slice(start, end), { headers: corsHeaders }); | |
} | |
return new Response("Not Found", { status: 404, headers: corsHeaders }); | |
}, | |
}); | |
console.log("Server running on http://localhost:7860"); |