Commit
·
68037ed
1
Parent(s):
6fd0abf
Refactor index.ts and types.ts to enhance data structure and loading; remove obsolete database subproject
Browse files- database +0 -1
- src/index.ts +139 -105
- src/types.ts +34 -0
database
DELETED
@@ -1 +0,0 @@
|
|
1 |
-
Subproject commit 456e6be6aacfcec190bee6c48758d7fc9dacb536
|
|
|
|
src/index.ts
CHANGED
@@ -1,138 +1,156 @@
|
|
1 |
import { serve } from "bun";
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
topics?: string[];
|
12 |
-
|
13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
}
|
15 |
|
16 |
-
|
17 |
|
18 |
-
|
19 |
-
|
20 |
-
const RELOAD_INTERVAL_MS = 3600000; // 1 hour
|
21 |
|
22 |
-
|
|
|
23 |
"Content-Type": "application/json",
|
24 |
"Access-Control-Allow-Origin": "*",
|
25 |
};
|
26 |
|
27 |
-
// ---
|
|
|
|
|
28 |
|
29 |
-
|
30 |
-
|
31 |
-
let sorted: {
|
32 |
-
packages: { latest: Item[]; mostUsed: Item[] };
|
33 |
-
programs: { latest: Item[]; mostUsed: Item[] };
|
34 |
-
} = { packages: { latest: [], mostUsed: [] }, programs: { latest: [], mostUsed: [] } };
|
35 |
|
36 |
-
// ---
|
|
|
|
|
|
|
37 |
|
38 |
-
function getSortedData(packages: Item[], programs: Item[]) {
|
39 |
return {
|
40 |
packages: {
|
41 |
-
latest: [...
|
42 |
-
mostUsed: [...
|
43 |
},
|
44 |
programs: {
|
45 |
-
latest: [...
|
46 |
-
mostUsed: [...
|
47 |
},
|
48 |
};
|
49 |
}
|
50 |
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
}
|
58 |
-
|
59 |
-
/**
|
60 |
-
* Filters an array of items by query and topic filter.
|
61 |
-
*/
|
62 |
-
function filterItems(items: Item[], query: string | null, topic: string | null): Item[] {
|
63 |
return items.filter(({ name, full_name, description, topics }) => {
|
64 |
-
if (
|
65 |
-
if (!
|
66 |
-
const lowerQ =
|
67 |
-
return [name, full_name, description, ...(topics
|
68 |
-
|
|
|
69 |
});
|
70 |
}
|
71 |
|
72 |
-
|
73 |
-
|
74 |
-
*/
|
75 |
-
function getPaginated(items: Item[], page: number = 0, size: number = 10): Item[] {
|
76 |
const start = page * size;
|
77 |
return items.slice(start, start + size);
|
78 |
}
|
79 |
|
80 |
-
|
81 |
-
* Finds an item by owner and repo name.
|
82 |
-
*/
|
83 |
function findItem(items: Item[], owner: string, repo: string): Item | undefined {
|
84 |
-
return items.find(
|
85 |
-
(
|
86 |
-
full_name?.toLowerCase() === `${owner}/${repo}`
|
87 |
);
|
88 |
}
|
89 |
|
90 |
-
|
91 |
-
* Parses a range string of the form "start..end" to [start, end].
|
92 |
-
*/
|
93 |
function parseRange(str: string | null, max: number): [number, number] {
|
94 |
const match = str?.match(/^(\d+)\.\.(\d+)$/);
|
95 |
const [start, end] = match
|
96 |
-
? [parseInt(match[1]), parseInt(match[2])]
|
97 |
: [0, 10];
|
98 |
return [Math.max(0, start), Math.min(max, end)];
|
99 |
}
|
100 |
|
101 |
-
|
102 |
-
* Loads data from disk and updates in-memory database and sorted views.
|
103 |
-
*/
|
104 |
-
async function reloadData() {
|
105 |
-
try {
|
106 |
-
const pkgPath = path.resolve(__dirname, `${DB_PATH}/packages.json`);
|
107 |
-
const prgPath = path.resolve(__dirname, `${DB_PATH}/programs.json`);
|
108 |
-
const [pkgData, prgData] = await Promise.all([
|
109 |
-
readFile(pkgPath, "utf8"),
|
110 |
-
readFile(prgPath, "utf8"),
|
111 |
-
]);
|
112 |
-
packages = JSON.parse(pkgData);
|
113 |
-
programs = JSON.parse(prgData);
|
114 |
-
sorted = getSortedData(packages, programs);
|
115 |
-
console.log(`[${new Date().toISOString()}] Database reloaded`);
|
116 |
-
} catch (err) {
|
117 |
-
console.error("Error reloading database files:", err);
|
118 |
-
}
|
119 |
-
}
|
120 |
-
|
121 |
-
// Initial data load and set up periodic reload
|
122 |
-
reloadData();
|
123 |
-
setInterval(reloadData, RELOAD_INTERVAL_MS);
|
124 |
-
|
125 |
-
// --- SERVER DEFINITION ---
|
126 |
-
|
127 |
serve({
|
128 |
-
port:
|
129 |
-
fetch(req
|
130 |
const url = new URL(req.url);
|
131 |
const { pathname, searchParams } = url;
|
132 |
const q = searchParams.get("q")?.trim().toLowerCase() ?? null;
|
133 |
const filter = searchParams.get("filter")?.trim().toLowerCase() ?? null;
|
|
|
134 |
|
135 |
-
// Handle
|
136 |
if (req.method === "OPTIONS") {
|
137 |
return new Response(null, {
|
138 |
status: 204,
|
@@ -144,8 +162,6 @@ serve({
|
|
144 |
});
|
145 |
}
|
146 |
|
147 |
-
// --- API ENDPOINTS ---
|
148 |
-
|
149 |
// Search endpoints
|
150 |
if (pathname === "/api/searchPackages") {
|
151 |
const result = filterItems(packages, q, filter).slice(0, 25);
|
@@ -160,29 +176,41 @@ serve({
|
|
160 |
if (pathname === "/api/infiniteScrollPackages") {
|
161 |
const page = parseInt(searchParams.get("pageNumber") || "0", 10);
|
162 |
if (isNaN(page) || page < 0)
|
163 |
-
return Response.json(
|
164 |
-
|
|
|
|
|
|
|
|
|
|
|
165 |
}
|
166 |
if (pathname === "/api/infiniteScrollPrograms") {
|
167 |
const page = parseInt(searchParams.get("pageNumber") || "0", 10);
|
168 |
if (isNaN(page) || page < 0)
|
169 |
-
return Response.json(
|
170 |
-
|
|
|
|
|
|
|
|
|
|
|
171 |
}
|
172 |
|
173 |
-
//
|
174 |
-
const programMatch = pathname.match(/^\/api\/programs\/([
|
175 |
if (programMatch) {
|
176 |
-
const
|
|
|
177 |
const found = findItem(programs, owner, repo);
|
178 |
return Response.json(found || { error: "Program not found" }, {
|
179 |
status: found ? 200 : 404,
|
180 |
headers: corsHeaders,
|
181 |
});
|
182 |
}
|
183 |
-
const packageMatch = pathname.match(/^\/api\/packages\/([
|
184 |
if (packageMatch) {
|
185 |
-
const
|
|
|
186 |
const found = findItem(packages, owner, repo);
|
187 |
return Response.json(found || { error: "Package not found" }, {
|
188 |
status: found ? 200 : 404,
|
@@ -190,11 +218,14 @@ serve({
|
|
190 |
});
|
191 |
}
|
192 |
|
193 |
-
// Index details endpoints
|
194 |
if (pathname === "/api/indexDetailsPackages") {
|
195 |
const section = searchParams.get("section");
|
196 |
if (section !== "latestRepos" && section !== "mostUsed") {
|
197 |
-
return Response.json(
|
|
|
|
|
|
|
198 |
}
|
199 |
const sortKey = section === "latestRepos" ? "latest" : "mostUsed";
|
200 |
const data = sorted.packages[sortKey as keyof typeof sorted.packages] ?? packages;
|
@@ -204,7 +235,10 @@ serve({
|
|
204 |
if (pathname === "/api/indexDetailsPrograms") {
|
205 |
const section = searchParams.get("section");
|
206 |
if (section !== "latestRepos" && section !== "mostUsed") {
|
207 |
-
return Response.json(
|
|
|
|
|
|
|
208 |
}
|
209 |
const sortKey = section === "latestRepos" ? "latest" : "mostUsed";
|
210 |
const data = sorted.programs[sortKey as keyof typeof sorted.programs] ?? programs;
|
@@ -212,7 +246,7 @@ serve({
|
|
212 |
return Response.json(data.slice(start, end), { headers: corsHeaders });
|
213 |
}
|
214 |
|
215 |
-
//
|
216 |
return new Response("Not Found", {
|
217 |
status: 404,
|
218 |
headers: corsHeaders,
|
@@ -220,4 +254,4 @@ serve({
|
|
220 |
},
|
221 |
});
|
222 |
|
223 |
-
console.log(
|
|
|
1 |
import { serve } from "bun";
|
2 |
+
|
3 |
+
// --- Interfaces ---
|
4 |
+
|
5 |
+
export interface Dependency {
|
6 |
+
name: string;
|
7 |
+
source: "relative" | "remote";
|
8 |
+
location: string;
|
9 |
+
}
|
10 |
+
|
11 |
+
export interface DeepSearchData {
|
12 |
+
[key: string]: string;
|
13 |
+
}
|
14 |
+
|
15 |
+
export interface Item {
|
16 |
+
avatar_url: string;
|
17 |
+
name: string;
|
18 |
+
full_name: string;
|
19 |
+
created_at: string;
|
20 |
+
description?: string | null;
|
21 |
+
default_branch?: string;
|
22 |
+
open_issues: number;
|
23 |
+
stargazers_count: number;
|
24 |
+
forks_count: number;
|
25 |
+
watchers_count: number;
|
26 |
+
contentIsCorrect?: boolean;
|
27 |
+
tags_url: string;
|
28 |
+
license: string;
|
29 |
+
readme_content: string;
|
30 |
+
specials?: string;
|
31 |
topics?: string[];
|
32 |
+
size: number;
|
33 |
+
has_build_zig_zon?: boolean;
|
34 |
+
has_build_zig?: boolean;
|
35 |
+
fork: boolean;
|
36 |
+
updated_at: string;
|
37 |
+
dependencies?: Dependency[];
|
38 |
+
berg?: number;
|
39 |
+
gitlab?: number;
|
40 |
+
archived?: boolean;
|
41 |
+
}
|
42 |
+
|
43 |
+
// --- Constants ---
|
44 |
+
|
45 |
+
const packagesUrls = [
|
46 |
+
"https://raw.githubusercontent.com/Zigistry/database/refs/heads/main/database/games.json",
|
47 |
+
"https://raw.githubusercontent.com/Zigistry/database/refs/heads/main/database/gui.json",
|
48 |
+
"https://raw.githubusercontent.com/Zigistry/database/refs/heads/main/database/packages.json",
|
49 |
+
"https://raw.githubusercontent.com/Zigistry/database/refs/heads/main/database/web.json",
|
50 |
+
];
|
51 |
+
|
52 |
+
const programsUrl =
|
53 |
+
"https://raw.githubusercontent.com/Zigistry/database/refs/heads/main/database/programs.json";
|
54 |
+
|
55 |
+
// --- Data Loading ---
|
56 |
+
|
57 |
+
let packages: Item[] = [];
|
58 |
+
let programs: Item[] = [];
|
59 |
+
|
60 |
+
async function loadData() {
|
61 |
+
const packagesFile = await Promise.all(packagesUrls.map((url) => fetch(url)));
|
62 |
+
packages = (await Promise.all(packagesFile.map((file) => file.json())))
|
63 |
+
.flat() as Item[];
|
64 |
+
|
65 |
+
const programsFile = await fetch(programsUrl);
|
66 |
+
programs = (await programsFile.json()) as Item[];
|
67 |
+
console.log("Data loaded");
|
68 |
}
|
69 |
|
70 |
+
await loadData();
|
71 |
|
72 |
+
// Refresh the data every 10 minutes
|
73 |
+
setInterval(loadData, 10 * 60 * 100);
|
|
|
74 |
|
75 |
+
// --- CORS Headers ---
|
76 |
+
const corsHeaders: Record<string, string> = {
|
77 |
"Content-Type": "application/json",
|
78 |
"Access-Control-Allow-Origin": "*",
|
79 |
};
|
80 |
|
81 |
+
// --- Sorting Helpers ---
|
82 |
+
const sortByDate = (a: Item, b: Item) =>
|
83 |
+
new Date(b.created_at ?? "").getTime() - new Date(a.created_at ?? "").getTime();
|
84 |
|
85 |
+
const sortByUsage = (a: Item, b: Item) =>
|
86 |
+
(b.stargazers_count ?? 0) - (a.stargazers_count ?? 0);
|
|
|
|
|
|
|
|
|
87 |
|
88 |
+
// --- Pre-sorted Data ---
|
89 |
+
function getSorted() {
|
90 |
+
const packagesArray = packages as Item[];
|
91 |
+
const programsArray = programs as Item[];
|
92 |
|
|
|
93 |
return {
|
94 |
packages: {
|
95 |
+
latest: [...packagesArray].sort(sortByDate),
|
96 |
+
mostUsed: [...packagesArray].sort(sortByUsage),
|
97 |
},
|
98 |
programs: {
|
99 |
+
latest: [...programsArray].sort(sortByDate),
|
100 |
+
mostUsed: [...programsArray].sort(sortByUsage),
|
101 |
},
|
102 |
};
|
103 |
}
|
104 |
|
105 |
+
// --- Filtering ---
|
106 |
+
function filterItems(
|
107 |
+
items: Item[],
|
108 |
+
q: string | null,
|
109 |
+
filter: string | null
|
110 |
+
): Item[] {
|
|
|
|
|
|
|
|
|
|
|
|
|
111 |
return items.filter(({ name, full_name, description, topics }) => {
|
112 |
+
if (filter && !topics?.some((t) => t.toLowerCase() === filter)) return false;
|
113 |
+
if (!q) return true;
|
114 |
+
const lowerQ = q.toLowerCase();
|
115 |
+
return [name, full_name, description, ...(topics ?? [])].some((field) =>
|
116 |
+
field?.toLowerCase().includes(lowerQ)
|
117 |
+
);
|
118 |
});
|
119 |
}
|
120 |
|
121 |
+
// --- Pagination ---
|
122 |
+
function getPaginated(items: Item[], page = 0, size = 10): Item[] {
|
|
|
|
|
123 |
const start = page * size;
|
124 |
return items.slice(start, start + size);
|
125 |
}
|
126 |
|
127 |
+
// --- Find by Owner/Repo ---
|
|
|
|
|
128 |
function findItem(items: Item[], owner: string, repo: string): Item | undefined {
|
129 |
+
return items.find(
|
130 |
+
({ full_name }) => full_name?.toLowerCase() === `${owner}/${repo}`
|
|
|
131 |
);
|
132 |
}
|
133 |
|
134 |
+
// --- Parse Range ---
|
|
|
|
|
135 |
function parseRange(str: string | null, max: number): [number, number] {
|
136 |
const match = str?.match(/^(\d+)\.\.(\d+)$/);
|
137 |
const [start, end] = match
|
138 |
+
? [parseInt(match[1] ?? "0", 10), parseInt(match[2] ?? "10", 10)]
|
139 |
: [0, 10];
|
140 |
return [Math.max(0, start), Math.min(max, end)];
|
141 |
}
|
142 |
|
143 |
+
// --- Server ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
144 |
serve({
|
145 |
+
port: 7860,
|
146 |
+
async fetch(req) {
|
147 |
const url = new URL(req.url);
|
148 |
const { pathname, searchParams } = url;
|
149 |
const q = searchParams.get("q")?.trim().toLowerCase() ?? null;
|
150 |
const filter = searchParams.get("filter")?.trim().toLowerCase() ?? null;
|
151 |
+
const sorted = getSorted();
|
152 |
|
153 |
+
// Handle CORS preflight
|
154 |
if (req.method === "OPTIONS") {
|
155 |
return new Response(null, {
|
156 |
status: 204,
|
|
|
162 |
});
|
163 |
}
|
164 |
|
|
|
|
|
165 |
// Search endpoints
|
166 |
if (pathname === "/api/searchPackages") {
|
167 |
const result = filterItems(packages, q, filter).slice(0, 25);
|
|
|
176 |
if (pathname === "/api/infiniteScrollPackages") {
|
177 |
const page = parseInt(searchParams.get("pageNumber") || "0", 10);
|
178 |
if (isNaN(page) || page < 0)
|
179 |
+
return Response.json(
|
180 |
+
{ error: "Invalid page number" },
|
181 |
+
{ status: 400, headers: corsHeaders }
|
182 |
+
);
|
183 |
+
return Response.json(getPaginated(packages, page), {
|
184 |
+
headers: corsHeaders,
|
185 |
+
});
|
186 |
}
|
187 |
if (pathname === "/api/infiniteScrollPrograms") {
|
188 |
const page = parseInt(searchParams.get("pageNumber") || "0", 10);
|
189 |
if (isNaN(page) || page < 0)
|
190 |
+
return Response.json(
|
191 |
+
{ error: "Invalid page number" },
|
192 |
+
{ status: 400, headers: corsHeaders }
|
193 |
+
);
|
194 |
+
return Response.json(getPaginated(programs, page), {
|
195 |
+
headers: corsHeaders,
|
196 |
+
});
|
197 |
}
|
198 |
|
199 |
+
// Get single program/package by owner/repo
|
200 |
+
const programMatch = pathname.match(/^\/api\/programs\/([^/]+)\/([^/]+)$/);
|
201 |
if (programMatch) {
|
202 |
+
const owner = programMatch[1]?.toLowerCase() ?? "";
|
203 |
+
const repo = programMatch[2]?.toLowerCase() ?? "";
|
204 |
const found = findItem(programs, owner, repo);
|
205 |
return Response.json(found || { error: "Program not found" }, {
|
206 |
status: found ? 200 : 404,
|
207 |
headers: corsHeaders,
|
208 |
});
|
209 |
}
|
210 |
+
const packageMatch = pathname.match(/^\/api\/packages\/([^/]+)\/([^/]+)$/);
|
211 |
if (packageMatch) {
|
212 |
+
const owner = packageMatch[1]?.toLowerCase() ?? "";
|
213 |
+
const repo = packageMatch[2]?.toLowerCase() ?? "";
|
214 |
const found = findItem(packages, owner, repo);
|
215 |
return Response.json(found || { error: "Package not found" }, {
|
216 |
status: found ? 200 : 404,
|
|
|
218 |
});
|
219 |
}
|
220 |
|
221 |
+
// Index details endpoints
|
222 |
if (pathname === "/api/indexDetailsPackages") {
|
223 |
const section = searchParams.get("section");
|
224 |
if (section !== "latestRepos" && section !== "mostUsed") {
|
225 |
+
return Response.json(
|
226 |
+
{ error: "Invalid section" },
|
227 |
+
{ status: 400, headers: corsHeaders }
|
228 |
+
);
|
229 |
}
|
230 |
const sortKey = section === "latestRepos" ? "latest" : "mostUsed";
|
231 |
const data = sorted.packages[sortKey as keyof typeof sorted.packages] ?? packages;
|
|
|
235 |
if (pathname === "/api/indexDetailsPrograms") {
|
236 |
const section = searchParams.get("section");
|
237 |
if (section !== "latestRepos" && section !== "mostUsed") {
|
238 |
+
return Response.json(
|
239 |
+
{ error: "Invalid section" },
|
240 |
+
{ status: 400, headers: corsHeaders }
|
241 |
+
);
|
242 |
}
|
243 |
const sortKey = section === "latestRepos" ? "latest" : "mostUsed";
|
244 |
const data = sorted.programs[sortKey as keyof typeof sorted.programs] ?? programs;
|
|
|
246 |
return Response.json(data.slice(start, end), { headers: corsHeaders });
|
247 |
}
|
248 |
|
249 |
+
// Not found
|
250 |
return new Response("Not Found", {
|
251 |
status: 404,
|
252 |
headers: corsHeaders,
|
|
|
254 |
},
|
255 |
});
|
256 |
|
257 |
+
console.log("Server running on http://localhost:7860");
|
src/types.ts
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export interface Dependency {
|
2 |
+
name: string;
|
3 |
+
source: "relative" | "remote";
|
4 |
+
location: string;
|
5 |
+
}
|
6 |
+
|
7 |
+
export interface Item {
|
8 |
+
avatar_url: string;
|
9 |
+
name: string;
|
10 |
+
full_name: string;
|
11 |
+
created_at: string;
|
12 |
+
description?: string | null;
|
13 |
+
default_branch?: string;
|
14 |
+
open_issues: number;
|
15 |
+
stargazers_count: number;
|
16 |
+
forks_count: number;
|
17 |
+
watchers_count: number;
|
18 |
+
contentIsCorrect?: boolean;
|
19 |
+
tags_url: string;
|
20 |
+
license: string;
|
21 |
+
readme_content: string;
|
22 |
+
specials?: string;
|
23 |
+
topics?: string[];
|
24 |
+
size: number;
|
25 |
+
has_build_zig_zon?: boolean;
|
26 |
+
has_build_zig?: boolean;
|
27 |
+
fork: boolean;
|
28 |
+
updated_at: string;
|
29 |
+
dependencies?: Dependency[];
|
30 |
+
berg?: number;
|
31 |
+
gitlab?: number;
|
32 |
+
archived?: boolean;
|
33 |
+
owner?: string; // Added for findItem compatibility
|
34 |
+
}
|