@superdangerous/app-framework 4.9.0

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.
Files changed (239) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +652 -0
  3. package/dist/api/logsRouter.d.ts +20 -0
  4. package/dist/api/logsRouter.d.ts.map +1 -0
  5. package/dist/api/logsRouter.js +515 -0
  6. package/dist/api/logsRouter.js.map +1 -0
  7. package/dist/cli/dev-server.d.ts +7 -0
  8. package/dist/cli/dev-server.d.ts.map +1 -0
  9. package/dist/cli/dev-server.js +640 -0
  10. package/dist/cli/dev-server.js.map +1 -0
  11. package/dist/cli/index.d.ts +7 -0
  12. package/dist/cli/index.d.ts.map +1 -0
  13. package/dist/cli/index.js +26 -0
  14. package/dist/cli/index.js.map +1 -0
  15. package/dist/core/StandardServer.d.ts +129 -0
  16. package/dist/core/StandardServer.d.ts.map +1 -0
  17. package/dist/core/StandardServer.js +453 -0
  18. package/dist/core/StandardServer.js.map +1 -0
  19. package/dist/core/apiResponse.d.ts +69 -0
  20. package/dist/core/apiResponse.d.ts.map +1 -0
  21. package/dist/core/apiResponse.js +127 -0
  22. package/dist/core/apiResponse.js.map +1 -0
  23. package/dist/core/healthCheck.d.ts +160 -0
  24. package/dist/core/healthCheck.d.ts.map +1 -0
  25. package/dist/core/healthCheck.js +398 -0
  26. package/dist/core/healthCheck.js.map +1 -0
  27. package/dist/core/index.d.ts +40 -0
  28. package/dist/core/index.d.ts.map +1 -0
  29. package/dist/core/index.js +40 -0
  30. package/dist/core/index.js.map +1 -0
  31. package/dist/core/logger.d.ts +117 -0
  32. package/dist/core/logger.d.ts.map +1 -0
  33. package/dist/core/logger.js +826 -0
  34. package/dist/core/logger.js.map +1 -0
  35. package/dist/core/portUtils.d.ts +71 -0
  36. package/dist/core/portUtils.d.ts.map +1 -0
  37. package/dist/core/portUtils.js +240 -0
  38. package/dist/core/portUtils.js.map +1 -0
  39. package/dist/core/storageService.d.ts +119 -0
  40. package/dist/core/storageService.d.ts.map +1 -0
  41. package/dist/core/storageService.js +405 -0
  42. package/dist/core/storageService.js.map +1 -0
  43. package/dist/desktop/bundler.d.ts +40 -0
  44. package/dist/desktop/bundler.d.ts.map +1 -0
  45. package/dist/desktop/bundler.js +176 -0
  46. package/dist/desktop/bundler.js.map +1 -0
  47. package/dist/desktop/index.d.ts +25 -0
  48. package/dist/desktop/index.d.ts.map +1 -0
  49. package/dist/desktop/index.js +15 -0
  50. package/dist/desktop/index.js.map +1 -0
  51. package/dist/desktop/native-modules.d.ts +66 -0
  52. package/dist/desktop/native-modules.d.ts.map +1 -0
  53. package/dist/desktop/native-modules.js +200 -0
  54. package/dist/desktop/native-modules.js.map +1 -0
  55. package/dist/index.d.ts +29 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +39 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/logging/LogCategories.d.ts +87 -0
  60. package/dist/logging/LogCategories.d.ts.map +1 -0
  61. package/dist/logging/LogCategories.js +205 -0
  62. package/dist/logging/LogCategories.js.map +1 -0
  63. package/dist/middleware/aiErrorHandler.d.ts +31 -0
  64. package/dist/middleware/aiErrorHandler.d.ts.map +1 -0
  65. package/dist/middleware/aiErrorHandler.js +181 -0
  66. package/dist/middleware/aiErrorHandler.js.map +1 -0
  67. package/dist/middleware/auth.d.ts +101 -0
  68. package/dist/middleware/auth.d.ts.map +1 -0
  69. package/dist/middleware/auth.js +230 -0
  70. package/dist/middleware/auth.js.map +1 -0
  71. package/dist/middleware/cors.d.ts +56 -0
  72. package/dist/middleware/cors.d.ts.map +1 -0
  73. package/dist/middleware/cors.js +123 -0
  74. package/dist/middleware/cors.js.map +1 -0
  75. package/dist/middleware/errorHandler.d.ts +13 -0
  76. package/dist/middleware/errorHandler.d.ts.map +1 -0
  77. package/dist/middleware/errorHandler.js +85 -0
  78. package/dist/middleware/errorHandler.js.map +1 -0
  79. package/dist/middleware/fileUpload.d.ts +62 -0
  80. package/dist/middleware/fileUpload.d.ts.map +1 -0
  81. package/dist/middleware/fileUpload.js +175 -0
  82. package/dist/middleware/fileUpload.js.map +1 -0
  83. package/dist/middleware/health.d.ts +48 -0
  84. package/dist/middleware/health.d.ts.map +1 -0
  85. package/dist/middleware/health.js +143 -0
  86. package/dist/middleware/health.js.map +1 -0
  87. package/dist/middleware/index.d.ts +20 -0
  88. package/dist/middleware/index.d.ts.map +1 -0
  89. package/dist/middleware/index.js +18 -0
  90. package/dist/middleware/index.js.map +1 -0
  91. package/dist/middleware/openapi.d.ts +64 -0
  92. package/dist/middleware/openapi.d.ts.map +1 -0
  93. package/dist/middleware/openapi.js +258 -0
  94. package/dist/middleware/openapi.js.map +1 -0
  95. package/dist/middleware/requestLogging.d.ts +22 -0
  96. package/dist/middleware/requestLogging.d.ts.map +1 -0
  97. package/dist/middleware/requestLogging.js +61 -0
  98. package/dist/middleware/requestLogging.js.map +1 -0
  99. package/dist/middleware/session.d.ts +84 -0
  100. package/dist/middleware/session.d.ts.map +1 -0
  101. package/dist/middleware/session.js +189 -0
  102. package/dist/middleware/session.js.map +1 -0
  103. package/dist/middleware/validation.d.ts +1337 -0
  104. package/dist/middleware/validation.d.ts.map +1 -0
  105. package/dist/middleware/validation.js +483 -0
  106. package/dist/middleware/validation.js.map +1 -0
  107. package/dist/services/aiService.d.ts +180 -0
  108. package/dist/services/aiService.d.ts.map +1 -0
  109. package/dist/services/aiService.js +547 -0
  110. package/dist/services/aiService.js.map +1 -0
  111. package/dist/services/conversationStorage.d.ts +38 -0
  112. package/dist/services/conversationStorage.d.ts.map +1 -0
  113. package/dist/services/conversationStorage.js +158 -0
  114. package/dist/services/conversationStorage.js.map +1 -0
  115. package/dist/services/crossPlatformBuffer.d.ts +84 -0
  116. package/dist/services/crossPlatformBuffer.d.ts.map +1 -0
  117. package/dist/services/crossPlatformBuffer.js +246 -0
  118. package/dist/services/crossPlatformBuffer.js.map +1 -0
  119. package/dist/services/index.d.ts +17 -0
  120. package/dist/services/index.d.ts.map +1 -0
  121. package/dist/services/index.js +18 -0
  122. package/dist/services/index.js.map +1 -0
  123. package/dist/services/networkService.d.ts +81 -0
  124. package/dist/services/networkService.d.ts.map +1 -0
  125. package/dist/services/networkService.js +268 -0
  126. package/dist/services/networkService.js.map +1 -0
  127. package/dist/services/queueService.d.ts +112 -0
  128. package/dist/services/queueService.d.ts.map +1 -0
  129. package/dist/services/queueService.js +338 -0
  130. package/dist/services/queueService.js.map +1 -0
  131. package/dist/services/settingsService.d.ts +135 -0
  132. package/dist/services/settingsService.d.ts.map +1 -0
  133. package/dist/services/settingsService.js +425 -0
  134. package/dist/services/settingsService.js.map +1 -0
  135. package/dist/services/systemMonitor.d.ts +208 -0
  136. package/dist/services/systemMonitor.d.ts.map +1 -0
  137. package/dist/services/systemMonitor.js +693 -0
  138. package/dist/services/systemMonitor.js.map +1 -0
  139. package/dist/services/updateService.d.ts +78 -0
  140. package/dist/services/updateService.d.ts.map +1 -0
  141. package/dist/services/updateService.js +252 -0
  142. package/dist/services/updateService.js.map +1 -0
  143. package/dist/services/websocketEvents.d.ts +372 -0
  144. package/dist/services/websocketEvents.d.ts.map +1 -0
  145. package/dist/services/websocketEvents.js +338 -0
  146. package/dist/services/websocketEvents.js.map +1 -0
  147. package/dist/services/websocketServer.d.ts +80 -0
  148. package/dist/services/websocketServer.d.ts.map +1 -0
  149. package/dist/services/websocketServer.js +299 -0
  150. package/dist/services/websocketServer.js.map +1 -0
  151. package/dist/settings/SettingsSchema.d.ts +151 -0
  152. package/dist/settings/SettingsSchema.d.ts.map +1 -0
  153. package/dist/settings/SettingsSchema.js +424 -0
  154. package/dist/settings/SettingsSchema.js.map +1 -0
  155. package/dist/testing/TestServer.d.ts +69 -0
  156. package/dist/testing/TestServer.d.ts.map +1 -0
  157. package/dist/testing/TestServer.js +250 -0
  158. package/dist/testing/TestServer.js.map +1 -0
  159. package/dist/types/index.d.ts +137 -0
  160. package/dist/types/index.d.ts.map +1 -0
  161. package/dist/types/index.js +5 -0
  162. package/dist/types/index.js.map +1 -0
  163. package/dist/utils/appPaths.d.ts +74 -0
  164. package/dist/utils/appPaths.d.ts.map +1 -0
  165. package/dist/utils/appPaths.js +162 -0
  166. package/dist/utils/appPaths.js.map +1 -0
  167. package/dist/utils/fs-utils.d.ts +50 -0
  168. package/dist/utils/fs-utils.d.ts.map +1 -0
  169. package/dist/utils/fs-utils.js +114 -0
  170. package/dist/utils/fs-utils.js.map +1 -0
  171. package/dist/utils/index.d.ts +12 -0
  172. package/dist/utils/index.d.ts.map +1 -0
  173. package/dist/utils/index.js +10 -0
  174. package/dist/utils/index.js.map +1 -0
  175. package/dist/utils/standardConfig.d.ts +61 -0
  176. package/dist/utils/standardConfig.d.ts.map +1 -0
  177. package/dist/utils/standardConfig.js +109 -0
  178. package/dist/utils/standardConfig.js.map +1 -0
  179. package/dist/utils/startupBanner.d.ts +34 -0
  180. package/dist/utils/startupBanner.d.ts.map +1 -0
  181. package/dist/utils/startupBanner.js +169 -0
  182. package/dist/utils/startupBanner.js.map +1 -0
  183. package/dist/utils/startupLogger.d.ts +45 -0
  184. package/dist/utils/startupLogger.d.ts.map +1 -0
  185. package/dist/utils/startupLogger.js +200 -0
  186. package/dist/utils/startupLogger.js.map +1 -0
  187. package/package.json +151 -0
  188. package/src/api/logsRouter.ts +600 -0
  189. package/src/cli/dev-server.ts +803 -0
  190. package/src/cli/index.ts +31 -0
  191. package/src/core/StandardServer.ts +587 -0
  192. package/src/core/apiResponse.ts +202 -0
  193. package/src/core/healthCheck.ts +565 -0
  194. package/src/core/index.ts +80 -0
  195. package/src/core/logger.ts +1092 -0
  196. package/src/core/portUtils.ts +319 -0
  197. package/src/core/storageService.ts +595 -0
  198. package/src/desktop/bundler.ts +271 -0
  199. package/src/desktop/index.ts +18 -0
  200. package/src/desktop/native-modules.ts +289 -0
  201. package/src/index.ts +142 -0
  202. package/src/logging/LogCategories.ts +302 -0
  203. package/src/middleware/aiErrorHandler.ts +278 -0
  204. package/src/middleware/auth.ts +329 -0
  205. package/src/middleware/cors.ts +187 -0
  206. package/src/middleware/errorHandler.ts +103 -0
  207. package/src/middleware/fileUpload.ts +252 -0
  208. package/src/middleware/health.ts +206 -0
  209. package/src/middleware/index.ts +71 -0
  210. package/src/middleware/openapi.ts +305 -0
  211. package/src/middleware/requestLogging.ts +92 -0
  212. package/src/middleware/session.ts +238 -0
  213. package/src/middleware/validation.ts +603 -0
  214. package/src/services/aiService.ts +789 -0
  215. package/src/services/conversationStorage.ts +232 -0
  216. package/src/services/crossPlatformBuffer.ts +341 -0
  217. package/src/services/index.ts +47 -0
  218. package/src/services/networkService.ts +351 -0
  219. package/src/services/queueService.ts +446 -0
  220. package/src/services/settingsService.ts +549 -0
  221. package/src/services/systemMonitor.ts +936 -0
  222. package/src/services/updateService.ts +334 -0
  223. package/src/services/websocketEvents.ts +409 -0
  224. package/src/services/websocketServer.ts +394 -0
  225. package/src/settings/SettingsSchema.ts +664 -0
  226. package/src/testing/TestServer.ts +312 -0
  227. package/src/types/index.ts +154 -0
  228. package/src/utils/appPaths.ts +196 -0
  229. package/src/utils/fs-utils.ts +130 -0
  230. package/src/utils/index.ts +15 -0
  231. package/src/utils/standardConfig.ts +178 -0
  232. package/src/utils/startupBanner.ts +287 -0
  233. package/src/utils/startupLogger.ts +268 -0
  234. package/ui/dist/index.d.mts +1221 -0
  235. package/ui/dist/index.d.ts +1221 -0
  236. package/ui/dist/index.js +73 -0
  237. package/ui/dist/index.js.map +1 -0
  238. package/ui/dist/index.mjs +73 -0
  239. package/ui/dist/index.mjs.map +1 -0
@@ -0,0 +1,600 @@
1
+ /**
2
+ * Standardized Logging API Router
3
+ * Provides consistent logging endpoints for all SuperDangerous applications
4
+ */
5
+
6
+ import express, { Request, Response } from "express";
7
+ import { getLogger } from "../core/logger.js";
8
+ import path from "path";
9
+ import fs from "fs/promises";
10
+ import { existsSync, createReadStream } from "fs";
11
+
12
+ const router = express.Router();
13
+
14
+ export interface LogEntry {
15
+ timestamp: string;
16
+ level: string;
17
+ message: string;
18
+ source?: string;
19
+ metadata?: any;
20
+ }
21
+
22
+ export interface LogFile {
23
+ name: string;
24
+ size: number;
25
+ modified: string;
26
+ path?: string;
27
+ }
28
+
29
+ /**
30
+ * Get recent log entries
31
+ * GET /api/logs/entries?limit=100&level=info
32
+ */
33
+ router.get("/entries", async (req: Request, res: Response) => {
34
+ try {
35
+ const limit = parseInt(req.query.limit as string) || 100;
36
+ const level = (req.query.level as string) || "all";
37
+
38
+ const logger = getLogger;
39
+ const logs = await logger.getRecentLogs(limit, level);
40
+
41
+ res.json({
42
+ success: true,
43
+ logs: logs || [],
44
+ });
45
+ } catch (_error) {
46
+ console.error("Failed to fetch log entries:", _error);
47
+ res.status(500).json({
48
+ success: false,
49
+ error: "Failed to fetch log entries",
50
+ logs: [],
51
+ });
52
+ }
53
+ });
54
+
55
+ /**
56
+ * Get log files list
57
+ * GET /api/logs/files
58
+ */
59
+ router.get("/files", async (_req: Request, res: Response) => {
60
+ try {
61
+ const logsDir = path.join(process.cwd(), "data", "logs");
62
+
63
+ if (!existsSync(logsDir)) {
64
+ res.json({
65
+ success: true,
66
+ files: [],
67
+ });
68
+ return;
69
+ }
70
+
71
+ const files = await fs.readdir(logsDir);
72
+ const logFiles: LogFile[] = [];
73
+
74
+ for (const file of files) {
75
+ if (file.endsWith(".log") || file.endsWith(".txt")) {
76
+ const filePath = path.join(logsDir, file);
77
+ const stats = await fs.stat(filePath);
78
+
79
+ logFiles.push({
80
+ name: file,
81
+ size: stats.size,
82
+ modified: stats.mtime.toISOString(),
83
+ path: filePath,
84
+ });
85
+ }
86
+ }
87
+
88
+ // Sort by modified date, newest first
89
+ logFiles.sort(
90
+ (a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime(),
91
+ );
92
+
93
+ res.json({
94
+ success: true,
95
+ files: logFiles,
96
+ });
97
+ } catch (_error) {
98
+ console.error("Failed to fetch log files:", _error);
99
+ res.status(500).json({
100
+ success: false,
101
+ error: "Failed to fetch log files",
102
+ files: [],
103
+ });
104
+ }
105
+ });
106
+
107
+ /**
108
+ * Download a specific log file
109
+ * GET /api/logs/download/:filename
110
+ */
111
+ router.get("/download/:filename", async (req: Request, res: Response) => {
112
+ try {
113
+ const { filename } = req.params;
114
+ const logsDir = path.join(process.cwd(), "data", "logs");
115
+ const filePath = path.join(logsDir, filename);
116
+
117
+ // Security check - prevent directory traversal
118
+ if (!filePath.startsWith(logsDir)) {
119
+ res.status(403).json({
120
+ success: false,
121
+ error: "Access denied",
122
+ });
123
+ return;
124
+ }
125
+
126
+ if (!existsSync(filePath)) {
127
+ res.status(404).json({
128
+ success: false,
129
+ error: "File not found",
130
+ });
131
+ return;
132
+ }
133
+
134
+ res.download(filePath);
135
+ } catch (_error) {
136
+ console.error("Failed to download log file:", _error);
137
+ res.status(500).json({
138
+ success: false,
139
+ error: "Failed to download log file",
140
+ });
141
+ }
142
+ });
143
+
144
+ /**
145
+ * Stream log file content
146
+ * GET /api/logs/stream/:filename
147
+ */
148
+ router.get("/stream/:filename", async (req: Request, res: Response) => {
149
+ try {
150
+ const { filename } = req.params;
151
+ const logsDir = path.join(process.cwd(), "data", "logs");
152
+ const filePath = path.join(logsDir, filename);
153
+
154
+ // Security check
155
+ if (!filePath.startsWith(logsDir)) {
156
+ res.status(403).json({
157
+ success: false,
158
+ error: "Access denied",
159
+ });
160
+ return;
161
+ }
162
+
163
+ if (!existsSync(filePath)) {
164
+ res.status(404).json({
165
+ success: false,
166
+ error: "File not found",
167
+ });
168
+ return;
169
+ }
170
+
171
+ res.setHeader("Content-Type", "text/plain");
172
+ const stream = createReadStream(filePath);
173
+ stream.pipe(res);
174
+ } catch (_error) {
175
+ console.error("Failed to stream log file:", _error);
176
+ res.status(500).json({
177
+ success: false,
178
+ error: "Failed to stream log file",
179
+ });
180
+ }
181
+ });
182
+
183
+ /**
184
+ * Clear log files
185
+ * POST /api/logs/clear
186
+ */
187
+ router.post("/clear", async (_req: Request, res: Response) => {
188
+ try {
189
+ const logger = getLogger;
190
+ await logger.clearLogs();
191
+
192
+ res.json({
193
+ success: true,
194
+ message: "Logs cleared successfully",
195
+ });
196
+ } catch (_error) {
197
+ console.error("Failed to clear logs:", _error);
198
+ res.status(500).json({
199
+ success: false,
200
+ error: "Failed to clear logs",
201
+ });
202
+ }
203
+ });
204
+
205
+ /**
206
+ * Get archived log files
207
+ * GET /api/logs/archives
208
+ */
209
+ router.get("/archives", async (_req: Request, res: Response) => {
210
+ try {
211
+ const logsDir = path.join(process.cwd(), "data", "logs");
212
+
213
+ if (!existsSync(logsDir)) {
214
+ return res.json({
215
+ success: true,
216
+ archives: [],
217
+ });
218
+ }
219
+
220
+ const files = await fs.readdir(logsDir);
221
+ const archives: LogFile[] = [];
222
+
223
+ for (const file of files) {
224
+ if (file.endsWith(".log") || file.endsWith(".txt")) {
225
+ const filePath = path.join(logsDir, file);
226
+ const stats = await fs.stat(filePath);
227
+ archives.push({
228
+ name: file,
229
+ size: stats.size,
230
+ modified: stats.mtime.toISOString(),
231
+ });
232
+ }
233
+ }
234
+
235
+ // Sort by modified date, newest first
236
+ archives.sort(
237
+ (a, b) => new Date(b.modified).getTime() - new Date(a.modified).getTime(),
238
+ );
239
+
240
+ return res.json({
241
+ success: true,
242
+ archives,
243
+ });
244
+ } catch (_error) {
245
+ console.error("Failed to fetch archives:", _error);
246
+ return res.status(500).json({
247
+ success: false,
248
+ error: "Failed to fetch archives",
249
+ archives: [],
250
+ });
251
+ }
252
+ });
253
+
254
+ /**
255
+ * Delete specific archive
256
+ * DELETE /api/logs/archive/:filename
257
+ */
258
+ router.delete("/archive/:filename", async (req: Request, res: Response) => {
259
+ try {
260
+ const { filename } = req.params;
261
+ const logsDir = path.join(process.cwd(), "data", "logs");
262
+ const filePath = path.join(logsDir, filename);
263
+
264
+ // Security check
265
+ if (!filePath.startsWith(logsDir) || filename.includes("..")) {
266
+ return res.status(403).json({
267
+ success: false,
268
+ error: "Access denied",
269
+ });
270
+ }
271
+
272
+ if (!existsSync(filePath)) {
273
+ return res.status(404).json({
274
+ success: false,
275
+ error: "Archive not found",
276
+ });
277
+ }
278
+
279
+ await fs.unlink(filePath);
280
+
281
+ return res.json({
282
+ success: true,
283
+ message: "Archive deleted successfully",
284
+ });
285
+ } catch (_error) {
286
+ console.error("Failed to delete archive:", _error);
287
+ return res.status(500).json({
288
+ success: false,
289
+ error: "Failed to delete archive",
290
+ });
291
+ }
292
+ });
293
+
294
+ /**
295
+ * Export logs as text (always)
296
+ * GET /api/logs/export?level=all
297
+ */
298
+ router.get("/export", async (req: Request, res: Response) => {
299
+ try {
300
+ const level = (req.query.level as string) || "all";
301
+
302
+ const logger = getLogger;
303
+ const logs = await logger.getRecentLogs(10000, level); // Get up to 10k entries for export
304
+
305
+ // Sort newest first
306
+ const sortedLogs = logs.sort(
307
+ (a: LogEntry, b: LogEntry) =>
308
+ new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(),
309
+ );
310
+
311
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
312
+ const filename = `logs-${timestamp}.txt`;
313
+
314
+ // Always export as text
315
+ res.setHeader("Content-Type", "text/plain");
316
+ res.setHeader("Content-Disposition", `attachment; filename="${filename}"`);
317
+
318
+ const textLogs = sortedLogs
319
+ .map(
320
+ (log: LogEntry) =>
321
+ `[${log.timestamp}] [${log.level.toUpperCase()}]${log.source ? ` [${log.source}]` : ""} ${log.message}`,
322
+ )
323
+ .join("\n");
324
+
325
+ res.send(textLogs);
326
+ } catch (_error) {
327
+ console.error("Failed to export logs:", _error);
328
+ res.status(500).json({
329
+ success: false,
330
+ error: "Failed to export logs",
331
+ });
332
+ }
333
+ });
334
+
335
+ /**
336
+ * Get log statistics
337
+ * GET /api/logs/stats
338
+ */
339
+ router.get("/stats", async (_req: Request, res: Response) => {
340
+ try {
341
+ const logsDir = path.join(process.cwd(), "data", "logs");
342
+
343
+ if (!existsSync(logsDir)) {
344
+ res.json({
345
+ success: true,
346
+ stats: {
347
+ totalSize: 0,
348
+ fileCount: 0,
349
+ oldestLog: null,
350
+ newestLog: null,
351
+ },
352
+ });
353
+ return;
354
+ }
355
+
356
+ const files = await fs.readdir(logsDir);
357
+ let totalSize = 0;
358
+ let oldestTime: Date | null = null;
359
+ let newestTime: Date | null = null;
360
+ let fileCount = 0;
361
+
362
+ for (const file of files) {
363
+ if (file.endsWith(".log") || file.endsWith(".txt")) {
364
+ const filePath = path.join(logsDir, file);
365
+ const stats = await fs.stat(filePath);
366
+
367
+ totalSize += stats.size;
368
+ fileCount++;
369
+
370
+ if (!oldestTime || stats.birthtime < oldestTime) {
371
+ oldestTime = stats.birthtime;
372
+ }
373
+ if (!newestTime || stats.mtime > newestTime) {
374
+ newestTime = stats.mtime;
375
+ }
376
+ }
377
+ }
378
+
379
+ res.json({
380
+ success: true,
381
+ stats: {
382
+ totalSize,
383
+ totalSizeFormatted: formatBytes(totalSize),
384
+ fileCount,
385
+ oldestLog: oldestTime?.toISOString() || null,
386
+ newestLog: newestTime?.toISOString() || null,
387
+ },
388
+ });
389
+ } catch (_error) {
390
+ console.error("Failed to get log stats:", _error);
391
+ res.status(500).json({
392
+ success: false,
393
+ error: "Failed to get log statistics",
394
+ });
395
+ }
396
+ });
397
+
398
+ /**
399
+ * Compact logs - Archive old logs
400
+ * POST /api/logs/compact
401
+ */
402
+ router.post("/compact", async (req: Request, res: Response) => {
403
+ try {
404
+ const { days = 7 } = req.body;
405
+ const logger = getLogger;
406
+
407
+ const stats = await logger.compactLogs(days);
408
+
409
+ res.json({
410
+ success: true,
411
+ stats,
412
+ message: `Logs older than ${days} days have been archived`,
413
+ });
414
+ } catch (_error) {
415
+ res.status(500).json({
416
+ success: false,
417
+ error:
418
+ _error instanceof Error
419
+ ? _error.message
420
+ : String(_error) || "Failed to compact logs",
421
+ });
422
+ }
423
+ });
424
+
425
+ /**
426
+ * Clean up zero-length files
427
+ * POST /api/logs/cleanup-zero
428
+ */
429
+ router.post("/cleanup-zero", async (_req: Request, res: Response) => {
430
+ try {
431
+ const logger = getLogger;
432
+ const result = await logger.cleanupZeroFiles();
433
+
434
+ res.json({
435
+ success: true,
436
+ ...result,
437
+ message: `Removed ${result.removed} zero-length files`,
438
+ });
439
+ } catch (_error) {
440
+ res.status(500).json({
441
+ success: false,
442
+ error:
443
+ _error instanceof Error
444
+ ? _error.message
445
+ : String(_error) || "Failed to cleanup logs",
446
+ });
447
+ }
448
+ });
449
+
450
+ /**
451
+ * Purge all logs (dangerous)
452
+ * POST /api/logs/purge-all
453
+ */
454
+ router.post("/purge-all", async (_req: Request, res: Response) => {
455
+ try {
456
+ const logger = getLogger;
457
+ await logger.purgeAllLogs();
458
+
459
+ res.json({
460
+ success: true,
461
+ message: "All logs have been purged",
462
+ });
463
+ } catch (_error) {
464
+ res.status(500).json({
465
+ success: false,
466
+ error:
467
+ _error instanceof Error
468
+ ? _error.message
469
+ : String(_error) || "Failed to purge logs",
470
+ });
471
+ }
472
+ });
473
+
474
+ /**
475
+ * Get all log files including archives
476
+ * GET /api/logs/all-files
477
+ */
478
+ router.get("/all-files", async (_req: Request, res: Response) => {
479
+ try {
480
+ const logger = getLogger;
481
+ const files = await logger.getAllLogFiles();
482
+
483
+ res.json({
484
+ success: true,
485
+ files,
486
+ count: files.length,
487
+ });
488
+ } catch (_error) {
489
+ res.status(500).json({
490
+ success: false,
491
+ error:
492
+ _error instanceof Error
493
+ ? _error.message
494
+ : String(_error) || "Failed to list log files",
495
+ files: [],
496
+ });
497
+ }
498
+ });
499
+
500
+ /**
501
+ * Download any log file (including archives)
502
+ * GET /api/logs/download-any/:filename
503
+ */
504
+ router.get(
505
+ "/download-any/:filename(*)",
506
+ async (req: Request, res: Response) => {
507
+ try {
508
+ const { filename } = req.params;
509
+
510
+ // Security: prevent directory traversal
511
+ const safeName = path.basename(filename);
512
+
513
+ // Check in logs directory first
514
+ const logsDir = path.join(process.cwd(), "data", "logs");
515
+ let filePath = path.join(logsDir, safeName);
516
+
517
+ // If not found, check in archive directory
518
+ if (!existsSync(filePath)) {
519
+ filePath = path.join(logsDir, "archive", safeName);
520
+ }
521
+
522
+ // If still not found and filename includes 'archive/', handle that case
523
+ if (!existsSync(filePath) && filename.startsWith("archive/")) {
524
+ const archiveName = filename.replace("archive/", "");
525
+ filePath = path.join(logsDir, "archive", archiveName);
526
+ }
527
+
528
+ if (!existsSync(filePath)) {
529
+ return res.status(404).json({
530
+ success: false,
531
+ error: "File not found",
532
+ });
533
+ }
534
+
535
+ // Set appropriate headers for download
536
+ const isCompressed = filePath.endsWith(".gz");
537
+ if (isCompressed) {
538
+ res.setHeader("Content-Type", "application/gzip");
539
+ } else {
540
+ res.setHeader("Content-Type", "text/plain");
541
+ }
542
+
543
+ return res.download(filePath, safeName);
544
+ } catch (_error) {
545
+ return res.status(500).json({
546
+ success: false,
547
+ error:
548
+ _error instanceof Error
549
+ ? _error.message
550
+ : String(_error) || "Failed to download file",
551
+ });
552
+ }
553
+ },
554
+ );
555
+
556
+ /**
557
+ * Force log rotation
558
+ * POST /api/logs/rotate
559
+ */
560
+ router.post("/rotate", async (_req: Request, res: Response) => {
561
+ try {
562
+ const logger = getLogger;
563
+
564
+ // Get all logger instances and force rotation
565
+ const loggers = logger.getLoggers();
566
+ for (const [, winstonLogger] of loggers) {
567
+ // Find rotate transport and trigger rotation
568
+ winstonLogger.transports.forEach((transport: any) => {
569
+ if (transport.rotate && typeof transport.rotate === "function") {
570
+ transport.rotate();
571
+ }
572
+ });
573
+ }
574
+
575
+ res.json({
576
+ success: true,
577
+ message: "Log rotation triggered",
578
+ });
579
+ } catch (_error) {
580
+ res.status(500).json({
581
+ success: false,
582
+ error:
583
+ _error instanceof Error
584
+ ? _error.message
585
+ : String(_error) || "Failed to rotate logs",
586
+ });
587
+ }
588
+ });
589
+
590
+ // Helper function to format bytes
591
+ function formatBytes(bytes: number, decimals = 2): string {
592
+ if (bytes === 0) return "0 Bytes";
593
+ const k = 1024;
594
+ const dm = decimals < 0 ? 0 : decimals;
595
+ const sizes = ["Bytes", "KB", "MB", "GB"];
596
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
597
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
598
+ }
599
+
600
+ export default router;