RohanVashisht commited on
Commit
68037ed
·
1 Parent(s): 6fd0abf

Refactor index.ts and types.ts to enhance data structure and loading; remove obsolete database subproject

Browse files
Files changed (3) hide show
  1. database +0 -1
  2. src/index.ts +139 -105
  3. 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
- import { readFile } from "fs/promises";
3
- import path from "path";
4
-
5
- // Type Definition for Items (Packages/Programs)
6
- interface Item {
7
- name?: string;
8
- full_name?: string;
9
- owner?: string;
10
- description?: string;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  topics?: string[];
12
- created_at?: string;
13
- usage_count?: number;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  }
15
 
16
- // --- CONFIGURATION ---
17
 
18
- const DB_PATH = "../database/database";
19
- const PORT = 7860;
20
- const RELOAD_INTERVAL_MS = 3600000; // 1 hour
21
 
22
- const corsHeaders = {
 
23
  "Content-Type": "application/json",
24
  "Access-Control-Allow-Origin": "*",
25
  };
26
 
27
- // --- GLOBAL DATA STORE (in-memory) ---
 
 
28
 
29
- let packages: Item[] = [];
30
- let programs: Item[] = [];
31
- let sorted: {
32
- packages: { latest: Item[]; mostUsed: Item[] };
33
- programs: { latest: Item[]; mostUsed: Item[] };
34
- } = { packages: { latest: [], mostUsed: [] }, programs: { latest: [], mostUsed: [] } };
35
 
36
- // --- UTILITY FUNCTIONS ---
 
 
 
37
 
38
- function getSortedData(packages: Item[], programs: Item[]) {
39
  return {
40
  packages: {
41
- latest: [...packages].sort(sortByDate),
42
- mostUsed: [...packages].sort(sortByUsage),
43
  },
44
  programs: {
45
- latest: [...programs].sort(sortByDate),
46
- mostUsed: [...programs].sort(sortByUsage),
47
  },
48
  };
49
  }
50
 
51
- function sortByDate(a: Item, b: Item): number {
52
- return new Date(b.created_at || "").getTime() - new Date(a.created_at || "").getTime();
53
- }
54
-
55
- function sortByUsage(a: Item, b: Item): number {
56
- return (b.usage_count || 0) - (a.usage_count || 0);
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 (topic && !topics?.some(t => t.toLowerCase() === topic)) return false;
65
- if (!query) return true;
66
- const lowerQ = query.toLowerCase();
67
- return [name, full_name, description, ...(topics || [])]
68
- .some(field => field?.toLowerCase().includes(lowerQ));
 
69
  });
70
  }
71
 
72
- /**
73
- * Returns a paginated slice of items.
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(({ owner: o, name, full_name }) =>
85
- (o?.toLowerCase() === owner && name?.toLowerCase() === repo) ||
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: PORT,
129
- fetch(req: Request): Response | Promise<Response> {
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 preflight CORS requests
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({ error: "Invalid page number" }, { status: 400, headers: corsHeaders });
164
- return Response.json(getPaginated(packages, page), { headers: corsHeaders });
 
 
 
 
 
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({ error: "Invalid page number" }, { status: 400, headers: corsHeaders });
170
- return Response.json(getPaginated(programs, page), { headers: corsHeaders });
 
 
 
 
 
171
  }
172
 
173
- // Item details endpoints (by owner/repo)
174
- const programMatch = pathname.match(/^\/api\/programs\/([^\/]+)\/([^\/]+)$/);
175
  if (programMatch) {
176
- const [_, owner, repo] = programMatch.map(s => s.toLowerCase());
 
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 [_, owner, repo] = packageMatch.map(s => s.toLowerCase());
 
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 (for homepage sections)
194
  if (pathname === "/api/indexDetailsPackages") {
195
  const section = searchParams.get("section");
196
  if (section !== "latestRepos" && section !== "mostUsed") {
197
- return Response.json({ error: "Invalid section" }, { status: 400, headers: corsHeaders });
 
 
 
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({ error: "Invalid section" }, { status: 400, headers: corsHeaders });
 
 
 
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
- // Unknown endpoint
216
  return new Response("Not Found", {
217
  status: 404,
218
  headers: corsHeaders,
@@ -220,4 +254,4 @@ serve({
220
  },
221
  });
222
 
223
- console.log(`Server running on http://localhost:${PORT}`);
 
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
+ }