@mahidsec/nest 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/nest.js +6 -1
- package/dist/server.js +57 -26
- package/frontend/dist/assets/index-BdWhHv6t.js +19 -0
- package/frontend/dist/assets/index-Dn2D1MJH.css +1 -0
- package/frontend/dist/index.html +2 -2
- package/package.json +2 -2
- package/src/server.ts +66 -31
- package/src/types.ts +1 -0
- package/frontend/dist/assets/index-B_vuW2lv.js +0 -19
- package/frontend/dist/assets/index-CcqSIahz.css +0 -1
package/bin/nest.js
CHANGED
|
@@ -314,7 +314,12 @@ async function handleSelect() {
|
|
|
314
314
|
console.log(" \x1b[90mServer: http://localhost:" + PORT + "\x1b[0m");
|
|
315
315
|
console.log();
|
|
316
316
|
|
|
317
|
-
|
|
317
|
+
// Redirect server stdout/stderr so logs don't leak to terminal
|
|
318
|
+
if (serverProcess) {
|
|
319
|
+
serverProcess.stdout?.destroy();
|
|
320
|
+
serverProcess.stderr?.destroy();
|
|
321
|
+
serverProcess.unref();
|
|
322
|
+
}
|
|
318
323
|
|
|
319
324
|
spawn("node", [__filename, "--tray"], {
|
|
320
325
|
stdio: "ignore",
|
package/dist/server.js
CHANGED
|
@@ -141,11 +141,14 @@ var countVideoFiles = async (dirPath) => {
|
|
|
141
141
|
const videoExts = [".mp4", ".mkv", ".avi", ".mov", ".webm", ".m4v"];
|
|
142
142
|
try {
|
|
143
143
|
const entries = await readdir(dirPath, { withFileTypes: true });
|
|
144
|
+
const promises = [];
|
|
144
145
|
for (const entry of entries) {
|
|
145
146
|
if (entry.name.startsWith(".")) continue;
|
|
146
|
-
if (entry.isDirectory())
|
|
147
|
+
if (entry.isDirectory()) promises.push(countVideoFiles(path.join(dirPath, entry.name)));
|
|
147
148
|
else if (videoExts.includes(path.extname(entry.name).toLowerCase())) count++;
|
|
148
149
|
}
|
|
150
|
+
const results = await Promise.all(promises);
|
|
151
|
+
count += results.reduce((a, b) => a + b, 0);
|
|
149
152
|
} catch {
|
|
150
153
|
}
|
|
151
154
|
return count;
|
|
@@ -153,16 +156,47 @@ var countVideoFiles = async (dirPath) => {
|
|
|
153
156
|
var videoCountCache = /* @__PURE__ */ new Map();
|
|
154
157
|
var CACHE_TTL = 3e4;
|
|
155
158
|
var CACHE_MAX = 200;
|
|
156
|
-
var
|
|
159
|
+
var triggerVideoCountUpdate = async (course) => {
|
|
160
|
+
const dirPath = course.localPath;
|
|
161
|
+
let cached = videoCountCache.get(dirPath);
|
|
162
|
+
if (!cached) {
|
|
163
|
+
cached = { count: course.totalVideos || 0, ts: 0, updating: true };
|
|
164
|
+
if (videoCountCache.size >= CACHE_MAX) {
|
|
165
|
+
const oldest = videoCountCache.keys().next().value;
|
|
166
|
+
if (oldest) videoCountCache.delete(oldest);
|
|
167
|
+
}
|
|
168
|
+
videoCountCache.set(dirPath, cached);
|
|
169
|
+
} else {
|
|
170
|
+
cached.updating = true;
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
const count = await countVideoFiles(dirPath);
|
|
174
|
+
videoCountCache.set(dirPath, { count, ts: Date.now(), updating: false });
|
|
175
|
+
if (course.totalVideos !== count) {
|
|
176
|
+
const allCourses = await getCourses();
|
|
177
|
+
const target = allCourses.find((c) => c.id === course.id);
|
|
178
|
+
if (target && target.totalVideos !== count) {
|
|
179
|
+
target.totalVideos = count;
|
|
180
|
+
await saveCourses(allCourses);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
} catch (err) {
|
|
184
|
+
if (cached) cached.updating = false;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
var getCachedVideoCount = (course) => {
|
|
188
|
+
const dirPath = course.localPath;
|
|
157
189
|
const cached = videoCountCache.get(dirPath);
|
|
158
|
-
if (cached
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
190
|
+
if (cached) {
|
|
191
|
+
if (Date.now() - cached.ts > CACHE_TTL && !cached.updating) {
|
|
192
|
+
triggerVideoCountUpdate(course).catch(() => {
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
return cached.count;
|
|
163
196
|
}
|
|
164
|
-
|
|
165
|
-
|
|
197
|
+
triggerVideoCountUpdate(course).catch(() => {
|
|
198
|
+
});
|
|
199
|
+
return course.totalVideos || 0;
|
|
166
200
|
};
|
|
167
201
|
var invalidateVideoCount = (dirPath) => {
|
|
168
202
|
videoCountCache.delete(dirPath);
|
|
@@ -198,15 +232,12 @@ function findCloudflared() {
|
|
|
198
232
|
} catch {
|
|
199
233
|
}
|
|
200
234
|
if (fs.existsSync(CLOUDFLARED_PATH)) return CLOUDFLARED_PATH;
|
|
201
|
-
console.log("[Tunnel] cloudflared not found \u2014 downloading...");
|
|
202
235
|
try {
|
|
203
236
|
if (!fs.existsSync(NEST_BIN_DIR)) fs.mkdirSync(NEST_BIN_DIR, { recursive: true });
|
|
204
237
|
execSync(`curl -fSL -o "${CLOUDFLARED_PATH}" "${getCloudflaredDownloadUrl()}"`, { stdio: "inherit" });
|
|
205
238
|
fs.chmodSync(CLOUDFLARED_PATH, 493);
|
|
206
|
-
console.log("[Tunnel] cloudflared installed");
|
|
207
239
|
return CLOUDFLARED_PATH;
|
|
208
240
|
} catch {
|
|
209
|
-
console.error("[Tunnel] Failed to download cloudflared");
|
|
210
241
|
return null;
|
|
211
242
|
}
|
|
212
243
|
}
|
|
@@ -281,13 +312,12 @@ app.post("/api/tunnel/stop", (_req, res) => {
|
|
|
281
312
|
app.get("/api/courses", async (_req, res) => {
|
|
282
313
|
try {
|
|
283
314
|
const courses = await getCourses();
|
|
284
|
-
const enriched =
|
|
315
|
+
const enriched = courses.map((c) => ({
|
|
285
316
|
...c,
|
|
286
|
-
totalVideos:
|
|
287
|
-
}))
|
|
317
|
+
totalVideos: getCachedVideoCount(c)
|
|
318
|
+
}));
|
|
288
319
|
res.json(enriched);
|
|
289
|
-
} catch
|
|
290
|
-
console.error("[Courses] List error:", err);
|
|
320
|
+
} catch {
|
|
291
321
|
res.status(500).json({ error: "Failed to load courses" });
|
|
292
322
|
}
|
|
293
323
|
});
|
|
@@ -317,7 +347,6 @@ app.post("/api/courses", async (req, res) => {
|
|
|
317
347
|
courses.push(course);
|
|
318
348
|
await saveCourses(courses);
|
|
319
349
|
invalidateVideoCount(resolved);
|
|
320
|
-
console.log(`[Courses] Added "${name}" \u2192 ${resolved}`);
|
|
321
350
|
res.json({ success: true, course });
|
|
322
351
|
});
|
|
323
352
|
app.delete("/api/courses/:id", async (req, res) => {
|
|
@@ -326,9 +355,16 @@ app.delete("/api/courses/:id", async (req, res) => {
|
|
|
326
355
|
if (!target) return res.status(404).json({ error: "Course not found" });
|
|
327
356
|
invalidateVideoCount(target.localPath);
|
|
328
357
|
await saveCourses(courses.filter((c) => c.id !== req.params.id));
|
|
329
|
-
console.log(`[Courses] Removed course ${req.params.id}`);
|
|
330
358
|
res.json({ success: true });
|
|
331
359
|
});
|
|
360
|
+
app.get("/api/courses/progress", async (_req, res) => {
|
|
361
|
+
const all = await getCourseProgressData();
|
|
362
|
+
const result = {};
|
|
363
|
+
for (const [courseId, files] of Object.entries(all)) {
|
|
364
|
+
result[courseId] = Object.keys(files).length;
|
|
365
|
+
}
|
|
366
|
+
res.json(result);
|
|
367
|
+
});
|
|
332
368
|
app.get("/api/courses/:id/browse", async (req, res) => {
|
|
333
369
|
const courses = await getCourses();
|
|
334
370
|
const course = courses.find((c) => c.id === req.params.id);
|
|
@@ -342,8 +378,7 @@ app.get("/api/courses/:id/browse", async (req, res) => {
|
|
|
342
378
|
const result = await scanDirectory(course.localPath, course.localPath);
|
|
343
379
|
invalidateVideoCount(course.localPath);
|
|
344
380
|
res.json({ ...course, ...result });
|
|
345
|
-
} catch
|
|
346
|
-
console.error("[Courses] Browse error:", err);
|
|
381
|
+
} catch {
|
|
347
382
|
res.status(500).json({ error: "Failed to scan directory" });
|
|
348
383
|
}
|
|
349
384
|
});
|
|
@@ -442,8 +477,7 @@ app.get("/api/courses/:id/file", async (req, res) => {
|
|
|
442
477
|
"Cache-Control": "public, max-age=3600"
|
|
443
478
|
});
|
|
444
479
|
safePipe(fs.createReadStream(realResolved), res);
|
|
445
|
-
} catch
|
|
446
|
-
console.error("[Courses] File error:", err);
|
|
480
|
+
} catch {
|
|
447
481
|
res.status(500).json({ error: "Failed to serve file" });
|
|
448
482
|
}
|
|
449
483
|
});
|
|
@@ -472,9 +506,6 @@ app.get("*", (_req, res) => {
|
|
|
472
506
|
}
|
|
473
507
|
});
|
|
474
508
|
httpServer.listen(PORT, "0.0.0.0", () => {
|
|
475
|
-
console.log(`[Nest] Server running on http://localhost:${PORT}`);
|
|
476
|
-
console.log(`[Nest] Data dir: ${DATA_DIR}`);
|
|
477
|
-
if (IS_TUNNEL) console.log(`[Nest] Tunnel mode: enabled`);
|
|
478
509
|
});
|
|
479
510
|
var shuttingDown = false;
|
|
480
511
|
var shutdown = (signal) => {
|