api / src /index.ts
RohanVashisht's picture
Refactor index.ts and types.ts to enhance data structure and loading; remove obsolete database subproject
68037ed
raw
history blame
8.06 kB
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");