RohanVashisht commited on
Commit
7f60362
·
verified ·
1 Parent(s): c345f16

Update src/index.ts

Browse files
Files changed (1) hide show
  1. src/index.ts +91 -34
src/index.ts CHANGED
@@ -1,7 +1,8 @@
1
  import { serve } from "bun";
2
- import packages from "../database/database/packages.json";
3
- import programs from "../database/database/programs.json";
4
 
 
5
  interface Item {
6
  name?: string;
7
  full_name?: string;
@@ -12,43 +13,70 @@ interface Item {
12
  usage_count?: number;
13
  }
14
 
 
 
 
 
 
 
15
  const corsHeaders = {
16
  "Content-Type": "application/json",
17
  "Access-Control-Allow-Origin": "*",
18
  };
19
 
20
- const sortByDate = (a: Item, b: Item): number =>
21
- new Date(b.created_at || "").getTime() - new Date(a.created_at || "").getTime();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
- const sortByUsage = (a: Item, b: Item): number =>
24
- (b.usage_count || 0) - (a.usage_count || 0);
 
25
 
26
- const sorted = {
27
- packages: {
28
- latest: [...packages].sort(sortByDate),
29
- mostUsed: [...packages].sort(sortByUsage),
30
- },
31
- programs: {
32
- latest: [...programs].sort(sortByDate),
33
- mostUsed: [...programs].sort(sortByUsage),
34
- },
35
- };
36
 
37
- function filterItems(items: Item[], q: string | null, filter: string | null): Item[] {
 
 
 
38
  return items.filter(({ name, full_name, description, topics }) => {
39
- if (filter && !topics?.some(t => t.toLowerCase() === filter)) return false;
40
- if (!q) return true;
41
- const lowerQ = q.toLowerCase();
42
  return [name, full_name, description, ...(topics || [])]
43
  .some(field => field?.toLowerCase().includes(lowerQ));
44
  });
45
  }
46
 
 
 
 
47
  function getPaginated(items: Item[], page: number = 0, size: number = 10): Item[] {
48
  const start = page * size;
49
  return items.slice(start, start + size);
50
  }
51
 
 
 
 
52
  function findItem(items: Item[], owner: string, repo: string): Item | undefined {
53
  return items.find(({ owner: o, name, full_name }) =>
54
  (o?.toLowerCase() === owner && name?.toLowerCase() === repo) ||
@@ -56,6 +84,9 @@ function findItem(items: Item[], owner: string, repo: string): Item | undefined
56
  );
57
  }
58
 
 
 
 
59
  function parseRange(str: string | null, max: number): [number, number] {
60
  const match = str?.match(/^(\d+)\.\.(\d+)$/);
61
  const [start, end] = match
@@ -64,14 +95,41 @@ function parseRange(str: string | null, max: number): [number, number] {
64
  return [Math.max(0, start), Math.min(max, end)];
65
  }
66
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  serve({
68
- port: 7860,
69
  fetch(req: Request): Response | Promise<Response> {
70
  const url = new URL(req.url);
71
  const { pathname, searchParams } = url;
72
  const q = searchParams.get("q")?.trim().toLowerCase() ?? null;
73
  const filter = searchParams.get("filter")?.trim().toLowerCase() ?? null;
74
 
 
75
  if (req.method === "OPTIONS") {
76
  return new Response(null, {
77
  status: 204,
@@ -83,32 +141,33 @@ serve({
83
  });
84
  }
85
 
 
 
 
86
  if (pathname === "/api/searchPackages") {
87
  const result = filterItems(packages, q, filter).slice(0, 25);
88
  return Response.json(result, { headers: corsHeaders });
89
  }
90
-
91
  if (pathname === "/api/searchPrograms") {
92
  const result = filterItems(programs, q, filter).slice(0, 25);
93
  return Response.json(result, { headers: corsHeaders });
94
  }
95
 
 
96
  if (pathname === "/api/infiniteScrollPackages") {
97
  const page = parseInt(searchParams.get("pageNumber") || "0", 10);
98
  if (isNaN(page) || page < 0)
99
  return Response.json({ error: "Invalid page number" }, { status: 400, headers: corsHeaders });
100
-
101
  return Response.json(getPaginated(packages, page), { headers: corsHeaders });
102
  }
103
-
104
  if (pathname === "/api/infiniteScrollPrograms") {
105
  const page = parseInt(searchParams.get("pageNumber") || "0", 10);
106
  if (isNaN(page) || page < 0)
107
  return Response.json({ error: "Invalid page number" }, { status: 400, headers: corsHeaders });
108
-
109
  return Response.json(getPaginated(programs, page), { headers: corsHeaders });
110
  }
111
 
 
112
  const programMatch = pathname.match(/^\/api\/programs\/([^\/]+)\/([^\/]+)$/);
113
  if (programMatch) {
114
  const [_, owner, repo] = programMatch.map(s => s.toLowerCase());
@@ -118,7 +177,6 @@ serve({
118
  headers: corsHeaders,
119
  });
120
  }
121
-
122
  const packageMatch = pathname.match(/^\/api\/packages\/([^\/]+)\/([^\/]+)$/);
123
  if (packageMatch) {
124
  const [_, owner, repo] = packageMatch.map(s => s.toLowerCase());
@@ -129,30 +187,29 @@ serve({
129
  });
130
  }
131
 
 
132
  if (pathname === "/api/indexDetailsPackages") {
133
- let section = searchParams.get("section");
134
  if (section !== "latestRepos" && section !== "mostUsed") {
135
  return Response.json({ error: "Invalid section" }, { status: 400, headers: corsHeaders });
136
  }
137
- // Map 'latestRepos' to 'latest' for sorting
138
  const sortKey = section === "latestRepos" ? "latest" : "mostUsed";
139
- let data = sorted.packages[sortKey as keyof typeof sorted.packages] ?? packages;
140
  const [start, end] = parseRange(searchParams.get("range"), data.length);
141
  return Response.json(data.slice(start, end), { headers: corsHeaders });
142
  }
143
-
144
  if (pathname === "/api/indexDetailsPrograms") {
145
- let section = searchParams.get("section");
146
  if (section !== "latestRepos" && section !== "mostUsed") {
147
  return Response.json({ error: "Invalid section" }, { status: 400, headers: corsHeaders });
148
  }
149
- // Map 'latestRepos' to 'latest' for sorting
150
  const sortKey = section === "latestRepos" ? "latest" : "mostUsed";
151
- let data = sorted.programs[sortKey as keyof typeof sorted.programs] ?? programs;
152
  const [start, end] = parseRange(searchParams.get("range"), data.length);
153
  return Response.json(data.slice(start, end), { headers: corsHeaders });
154
  }
155
 
 
156
  return new Response("Not Found", {
157
  status: 404,
158
  headers: corsHeaders,
@@ -160,4 +217,4 @@ serve({
160
  },
161
  });
162
 
163
- console.log("Server running on http://localhost:7860");
 
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;
 
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[] = require(`${DB_PATH}/packages.json`);
30
+ let programs: Item[] = require(`${DB_PATH}/programs.json`);
31
+ let sorted = getSortedData(packages, programs);
32
+
33
+ // --- UTILITY FUNCTIONS ---
34
+
35
+ function getSortedData(packages: Item[], programs: Item[]) {
36
+ return {
37
+ packages: {
38
+ latest: [...packages].sort(sortByDate),
39
+ mostUsed: [...packages].sort(sortByUsage),
40
+ },
41
+ programs: {
42
+ latest: [...programs].sort(sortByDate),
43
+ mostUsed: [...programs].sort(sortByUsage),
44
+ },
45
+ };
46
+ }
47
 
48
+ function sortByDate(a: Item, b: Item): number {
49
+ return new Date(b.created_at || "").getTime() - new Date(a.created_at || "").getTime();
50
+ }
51
 
52
+ function sortByUsage(a: Item, b: Item): number {
53
+ return (b.usage_count || 0) - (a.usage_count || 0);
54
+ }
 
 
 
 
 
 
 
55
 
56
+ /**
57
+ * Filters an array of items by query and topic filter.
58
+ */
59
+ function filterItems(items: Item[], query: string | null, topic: string | null): Item[] {
60
  return items.filter(({ name, full_name, description, topics }) => {
61
+ if (topic && !topics?.some(t => t.toLowerCase() === topic)) return false;
62
+ if (!query) return true;
63
+ const lowerQ = query.toLowerCase();
64
  return [name, full_name, description, ...(topics || [])]
65
  .some(field => field?.toLowerCase().includes(lowerQ));
66
  });
67
  }
68
 
69
+ /**
70
+ * Returns a paginated slice of items.
71
+ */
72
  function getPaginated(items: Item[], page: number = 0, size: number = 10): Item[] {
73
  const start = page * size;
74
  return items.slice(start, start + size);
75
  }
76
 
77
+ /**
78
+ * Finds an item by owner and repo name.
79
+ */
80
  function findItem(items: Item[], owner: string, repo: string): Item | undefined {
81
  return items.find(({ owner: o, name, full_name }) =>
82
  (o?.toLowerCase() === owner && name?.toLowerCase() === repo) ||
 
84
  );
85
  }
86
 
87
+ /**
88
+ * Parses a range string of the form "start..end" to [start, end].
89
+ */
90
  function parseRange(str: string | null, max: number): [number, number] {
91
  const match = str?.match(/^(\d+)\.\.(\d+)$/);
92
  const [start, end] = match
 
95
  return [Math.max(0, start), Math.min(max, end)];
96
  }
97
 
98
+ /**
99
+ * Loads data from disk and updates in-memory database and sorted views.
100
+ */
101
+ async function reloadData() {
102
+ try {
103
+ const pkgPath = path.resolve(__dirname, `${DB_PATH}/packages.json`);
104
+ const prgPath = path.resolve(__dirname, `${DB_PATH}/programs.json`);
105
+ const [pkgData, prgData] = await Promise.all([
106
+ readFile(pkgPath, "utf8"),
107
+ readFile(prgPath, "utf8"),
108
+ ]);
109
+ packages = JSON.parse(pkgData);
110
+ programs = JSON.parse(prgData);
111
+ sorted = getSortedData(packages, programs);
112
+ console.log(`[${new Date().toISOString()}] Database reloaded`);
113
+ } catch (err) {
114
+ console.error("Error reloading database files:", err);
115
+ }
116
+ }
117
+
118
+ // Initial data load and set up periodic reload
119
+ reloadData();
120
+ setInterval(reloadData, RELOAD_INTERVAL_MS);
121
+
122
+ // --- SERVER DEFINITION ---
123
+
124
  serve({
125
+ port: PORT,
126
  fetch(req: Request): Response | Promise<Response> {
127
  const url = new URL(req.url);
128
  const { pathname, searchParams } = url;
129
  const q = searchParams.get("q")?.trim().toLowerCase() ?? null;
130
  const filter = searchParams.get("filter")?.trim().toLowerCase() ?? null;
131
 
132
+ // Handle preflight CORS requests
133
  if (req.method === "OPTIONS") {
134
  return new Response(null, {
135
  status: 204,
 
141
  });
142
  }
143
 
144
+ // --- API ENDPOINTS ---
145
+
146
+ // Search endpoints
147
  if (pathname === "/api/searchPackages") {
148
  const result = filterItems(packages, q, filter).slice(0, 25);
149
  return Response.json(result, { headers: corsHeaders });
150
  }
 
151
  if (pathname === "/api/searchPrograms") {
152
  const result = filterItems(programs, q, filter).slice(0, 25);
153
  return Response.json(result, { headers: corsHeaders });
154
  }
155
 
156
+ // Infinite scroll endpoints
157
  if (pathname === "/api/infiniteScrollPackages") {
158
  const page = parseInt(searchParams.get("pageNumber") || "0", 10);
159
  if (isNaN(page) || page < 0)
160
  return Response.json({ error: "Invalid page number" }, { status: 400, headers: corsHeaders });
 
161
  return Response.json(getPaginated(packages, page), { headers: corsHeaders });
162
  }
 
163
  if (pathname === "/api/infiniteScrollPrograms") {
164
  const page = parseInt(searchParams.get("pageNumber") || "0", 10);
165
  if (isNaN(page) || page < 0)
166
  return Response.json({ error: "Invalid page number" }, { status: 400, headers: corsHeaders });
 
167
  return Response.json(getPaginated(programs, page), { headers: corsHeaders });
168
  }
169
 
170
+ // Item details endpoints (by owner/repo)
171
  const programMatch = pathname.match(/^\/api\/programs\/([^\/]+)\/([^\/]+)$/);
172
  if (programMatch) {
173
  const [_, owner, repo] = programMatch.map(s => s.toLowerCase());
 
177
  headers: corsHeaders,
178
  });
179
  }
 
180
  const packageMatch = pathname.match(/^\/api\/packages\/([^\/]+)\/([^\/]+)$/);
181
  if (packageMatch) {
182
  const [_, owner, repo] = packageMatch.map(s => s.toLowerCase());
 
187
  });
188
  }
189
 
190
+ // Index details endpoints (for homepage sections)
191
  if (pathname === "/api/indexDetailsPackages") {
192
+ const section = searchParams.get("section");
193
  if (section !== "latestRepos" && section !== "mostUsed") {
194
  return Response.json({ error: "Invalid section" }, { status: 400, headers: corsHeaders });
195
  }
 
196
  const sortKey = section === "latestRepos" ? "latest" : "mostUsed";
197
+ const data = sorted.packages[sortKey as keyof typeof sorted.packages] ?? packages;
198
  const [start, end] = parseRange(searchParams.get("range"), data.length);
199
  return Response.json(data.slice(start, end), { headers: corsHeaders });
200
  }
 
201
  if (pathname === "/api/indexDetailsPrograms") {
202
+ const section = searchParams.get("section");
203
  if (section !== "latestRepos" && section !== "mostUsed") {
204
  return Response.json({ error: "Invalid section" }, { status: 400, headers: corsHeaders });
205
  }
 
206
  const sortKey = section === "latestRepos" ? "latest" : "mostUsed";
207
+ const data = sorted.programs[sortKey as keyof typeof sorted.programs] ?? programs;
208
  const [start, end] = parseRange(searchParams.get("range"), data.length);
209
  return Response.json(data.slice(start, end), { headers: corsHeaders });
210
  }
211
 
212
+ // Unknown endpoint
213
  return new Response("Not Found", {
214
  status: 404,
215
  headers: corsHeaders,
 
217
  },
218
  });
219
 
220
+ console.log(`Server running on http://localhost:${PORT}`);