
Refactor index.ts and types.ts to enhance data structure and loading; remove obsolete database subproject
68037ed
import { serve } from "bun"; | |
// --- Interfaces --- | |
export interface Dependency { | |
name: string; | |
source: "relative" | "remote"; | |
location: string; | |
} | |
export interface DeepSearchData { | |
[key: string]: string; | |
} | |
export interface Item { | |
avatar_url: string; | |
name: string; | |
full_name: string; | |
created_at: string; | |
description?: string | null; | |
default_branch?: string; | |
open_issues: number; | |
stargazers_count: number; | |
forks_count: number; | |
watchers_count: number; | |
contentIsCorrect?: boolean; | |
tags_url: string; | |
license: string; | |
readme_content: string; | |
specials?: string; | |
topics?: string[]; | |
size: number; | |
has_build_zig_zon?: boolean; | |
has_build_zig?: boolean; | |
fork: boolean; | |
updated_at: string; | |
dependencies?: Dependency[]; | |
berg?: number; | |
gitlab?: number; | |
archived?: boolean; | |
} | |
// --- Constants --- | |
const packagesUrls = [ | |
"https://raw.githubusercontent.com/Zigistry/database/refs/heads/main/database/games.json", | |
"https://raw.githubusercontent.com/Zigistry/database/refs/heads/main/database/gui.json", | |
"https://raw.githubusercontent.com/Zigistry/database/refs/heads/main/database/packages.json", | |
"https://raw.githubusercontent.com/Zigistry/database/refs/heads/main/database/web.json", | |
]; | |
const programsUrl = | |
"https://raw.githubusercontent.com/Zigistry/database/refs/heads/main/database/programs.json"; | |
// --- Data Loading --- | |
let packages: Item[] = []; | |
let programs: Item[] = []; | |
async function loadData() { | |
const packagesFile = await Promise.all(packagesUrls.map((url) => fetch(url))); | |
packages = (await Promise.all(packagesFile.map((file) => file.json()))) | |
.flat() as Item[]; | |
const programsFile = await fetch(programsUrl); | |
programs = (await programsFile.json()) as Item[]; | |
console.log("Data loaded"); | |
} | |
await loadData(); | |
// Refresh the data every 10 minutes | |
setInterval(loadData, 10 * 60 * 100); | |
// --- CORS Headers --- | |
const corsHeaders: Record<string, string> = { | |
"Content-Type": "application/json", | |
"Access-Control-Allow-Origin": "*", | |
}; | |
// --- Sorting Helpers --- | |
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.stargazers_count ?? 0) - (a.stargazers_count ?? 0); | |
// --- Pre-sorted Data --- | |
function getSorted() { | |
const packagesArray = packages as Item[]; | |
const programsArray = programs as Item[]; | |
return { | |
packages: { | |
latest: [...packagesArray].sort(sortByDate), | |
mostUsed: [...packagesArray].sort(sortByUsage), | |
}, | |
programs: { | |
latest: [...programsArray].sort(sortByDate), | |
mostUsed: [...programsArray].sort(sortByUsage), | |
}, | |
}; | |
} | |
// --- Filtering --- | |
function filterItems( | |
items: Item[], | |
q: string | null, | |
filter: string | null | |
): Item[] { | |
return items.filter(({ name, full_name, description, topics }) => { | |
if (filter && !topics?.some((t) => t.toLowerCase() === filter)) return false; | |
if (!q) return true; | |
const lowerQ = q.toLowerCase(); | |
return [name, full_name, description, ...(topics ?? [])].some((field) => | |
field?.toLowerCase().includes(lowerQ) | |
); | |
}); | |
} | |
// --- Pagination --- | |
function getPaginated(items: Item[], page = 0, size = 10): Item[] { | |
const start = page * size; | |
return items.slice(start, start + size); | |
} | |
// --- Find by Owner/Repo --- | |
function findItem(items: Item[], owner: string, repo: string): Item | undefined { | |
return items.find( | |
({ full_name }) => full_name?.toLowerCase() === `${owner}/${repo}` | |
); | |
} | |
// --- Parse Range --- | |
function parseRange(str: string | null, max: number): [number, number] { | |
const match = str?.match(/^(\d+)\.\.(\d+)$/); | |
const [start, end] = match | |
? [parseInt(match[1] ?? "0", 10), parseInt(match[2] ?? "10", 10)] | |
: [0, 10]; | |
return [Math.max(0, start), Math.min(max, end)]; | |
} | |
// --- Server --- | |
serve({ | |
port: 7860, | |
async 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; | |
const sorted = getSorted(); | |
// Handle CORS preflight | |
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", | |
}, | |
}); | |
} | |
// 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, | |
}); | |
} | |
// Get single program/package by owner/repo | |
const programMatch = pathname.match(/^\/api\/programs\/([^/]+)\/([^/]+)$/); | |
if (programMatch) { | |
const owner = programMatch[1]?.toLowerCase() ?? ""; | |
const repo = programMatch[2]?.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 = packageMatch[1]?.toLowerCase() ?? ""; | |
const repo = packageMatch[2]?.toLowerCase() ?? ""; | |
const found = findItem(packages, owner, repo); | |
return Response.json(found || { error: "Package not found" }, { | |
status: found ? 200 : 404, | |
headers: corsHeaders, | |
}); | |
} | |
// Index details endpoints | |
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 }); | |
} | |
// Not found | |
return new Response("Not Found", { | |
status: 404, | |
headers: corsHeaders, | |
}); | |
}, | |
}); | |
console.log("Server running on http://localhost:7860"); |