@manojkmfsi/monodog 1.0.23 → 1.0.25

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 (74) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +12 -0
  3. package/dist/controllers/{commitController.js → commit-controller.js} +5 -4
  4. package/dist/controllers/{configController.js → config-controller.js} +3 -3
  5. package/dist/controllers/{healthController.js → health-controller.js} +3 -3
  6. package/dist/controllers/{packageController.js → package-controller.js} +6 -6
  7. package/dist/index.js +11 -236
  8. package/dist/middleware/dashboard-startup.js +124 -0
  9. package/dist/middleware/error-handler.js +36 -0
  10. package/dist/middleware/index.js +23 -0
  11. package/dist/middleware/logger.js +63 -0
  12. package/dist/middleware/security.js +78 -0
  13. package/dist/middleware/server-startup.js +117 -0
  14. package/dist/repositories/commit-repository.js +97 -0
  15. package/dist/repositories/dependency-repository.js +97 -0
  16. package/dist/repositories/index.js +18 -0
  17. package/dist/repositories/package-health-repository.js +65 -0
  18. package/dist/repositories/package-repository.js +126 -0
  19. package/dist/repositories/prisma-client.js +57 -0
  20. package/dist/routes/{commitRoutes.js → commit-routes.js} +2 -2
  21. package/dist/routes/{configRoutes.js → config-routes.js} +3 -3
  22. package/dist/routes/{healthRoutes.js → health-routes.js} +3 -3
  23. package/dist/routes/{packageRoutes.js → package-routes.js} +5 -5
  24. package/dist/serve.js +15 -2
  25. package/dist/services/{commitService.js → commit-service.js} +2 -2
  26. package/dist/services/{configService.js → config-service.js} +2 -40
  27. package/dist/services/{healthService.js → health-service.js} +11 -63
  28. package/dist/services/{packageService.js → package-service.js} +80 -54
  29. package/dist/types/git.js +11 -0
  30. package/dist/types/index.js +1 -0
  31. package/package.json +10 -3
  32. package/prisma/schema/commit.prisma +11 -0
  33. package/prisma/schema/dependency-info.prisma +12 -0
  34. package/prisma/schema/health-status.prisma +14 -0
  35. package/prisma/schema/package-health.prisma +15 -0
  36. package/prisma/schema/package.prisma +21 -0
  37. package/prisma/schema/schema.prisma +15 -0
  38. package/src/controllers/{commitController.ts → commit-controller.ts} +7 -5
  39. package/src/controllers/{configController.ts → config-controller.ts} +4 -3
  40. package/src/controllers/{healthController.ts → health-controller.ts} +4 -3
  41. package/src/controllers/{packageController.ts → package-controller.ts} +7 -6
  42. package/src/index.ts +9 -281
  43. package/src/middleware/dashboard-startup.ts +149 -0
  44. package/src/middleware/error-handler.ts +50 -0
  45. package/src/middleware/index.ts +20 -0
  46. package/src/middleware/logger.ts +58 -0
  47. package/src/middleware/security.ts +81 -0
  48. package/src/middleware/server-startup.ts +142 -0
  49. package/src/repositories/commit-repository.ts +107 -0
  50. package/src/repositories/dependency-repository.ts +109 -0
  51. package/src/repositories/index.ts +10 -0
  52. package/src/repositories/package-health-repository.ts +75 -0
  53. package/src/repositories/package-repository.ts +142 -0
  54. package/src/repositories/prisma-client.ts +25 -0
  55. package/src/routes/{commitRoutes.ts → commit-routes.ts} +1 -1
  56. package/src/routes/{configRoutes.ts → config-routes.ts} +1 -1
  57. package/src/routes/{healthRoutes.ts → health-routes.ts} +1 -1
  58. package/src/routes/{packageRoutes.ts → package-routes.ts} +1 -1
  59. package/src/serve.ts +19 -3
  60. package/src/services/{commitService.ts → commit-service.ts} +1 -1
  61. package/src/services/{configService.ts → config-service.ts} +22 -9
  62. package/src/services/{gitService.ts → git-service.ts} +4 -4
  63. package/src/services/{healthService.ts → health-service.ts} +17 -35
  64. package/src/services/package-service.ts +201 -0
  65. package/src/types/database.ts +57 -1
  66. package/src/types/git.ts +8 -8
  67. package/src/types/index.ts +1 -1
  68. package/dist/utils/db-utils.js +0 -227
  69. package/prisma/schema.prisma +0 -116
  70. package/src/services/packageService.ts +0 -115
  71. package/src/types/monorepo-scanner.d.ts +0 -32
  72. package/src/utils/db-utils.ts +0 -220
  73. /package/dist/services/{gitService.js → git-service.js} +0 -0
  74. /package/prisma/migrations/{20251219074511_create_unique_composite_key_for_commits → 20251219090102_composite_key_for_table_commits}/migration.sql +0 -0
@@ -1,7 +1,8 @@
1
- import { updatePackageConfigurationService } from '../services/configService';
2
- import { getPackageDetailService, getPackagesService, refreshPackagesService } from '../services/packageService';
1
+ import { Request, Response } from 'express';
2
+ import { updatePackageConfigurationService } from '../services/config-service';
3
+ import { getPackageDetailService, getPackagesService, refreshPackagesService } from '../services/package-service';
3
4
 
4
- export const getPackages = async (_req: any, res: any) => {
5
+ export const getPackages = async (_req: Request, res: Response) => {
5
6
  try {
6
7
  const transformedPackages = await getPackagesService(_req.app.locals.rootPath);
7
8
  res.json(transformedPackages);
@@ -10,7 +11,7 @@ export const getPackages = async (_req: any, res: any) => {
10
11
  }
11
12
  }
12
13
 
13
- export const refreshPackages = async (_req: any, res: any) => {
14
+ export const refreshPackages = async (_req: Request, res: Response) => {
14
15
  console.log('Refreshing packages from source...'+ _req.app.locals.rootPath);
15
16
 
16
17
  try {
@@ -21,7 +22,7 @@ export const refreshPackages = async (_req: any, res: any) => {
21
22
  }
22
23
  }
23
24
 
24
- export const getPackageDetail = async (_req: any, res: any) => {
25
+ export const getPackageDetail = async (_req: Request, res: Response) => {
25
26
  const { name } = _req.params;
26
27
  try {
27
28
  const packageDetail = await getPackageDetailService(name)
@@ -31,7 +32,7 @@ export const getPackageDetail = async (_req: any, res: any) => {
31
32
  }
32
33
  }
33
34
 
34
- export const updatePackageConfig = async (req: any, res: any) => {
35
+ export const updatePackageConfig = async (req: Request, res: Response) => {
35
36
  try {
36
37
  const { packageName, config, packagePath } = req.body;
37
38
 
package/src/index.ts CHANGED
@@ -1,281 +1,9 @@
1
- import express, {
2
- type Request,
3
- type Response,
4
- type NextFunction,
5
- type ErrorRequestHandler,
6
- } from 'express';
7
-
8
- import cors from 'cors';
9
- import path from 'path';
10
- import { json } from 'body-parser';
11
- import helmet from 'helmet';
12
-
13
- import { appConfig } from './config-loader';
14
-
15
- import packageRouter from './routes/packageRoutes';
16
- import commitRouter from './routes/commitRoutes';
17
- import healthRouter from './routes/healthRoutes';
18
- import configRouter from './routes/configRoutes';
19
-
20
- // Security constants
21
- const PORT_MIN = 1024;
22
- const PORT_MAX = 65535;
23
-
24
- // Validate port number
25
- function validatePort(port: string | number): number {
26
- const portNum = typeof port === 'string' ? parseInt(port, 10) : port;
27
-
28
- if (isNaN(portNum) || portNum < PORT_MIN || portNum > PORT_MAX) {
29
- throw new Error(`Port must be between ${PORT_MIN} and ${PORT_MAX}`);
30
- }
31
-
32
- return portNum;
33
- }
34
-
35
- // Global error handler
36
- const errorHandler: ErrorRequestHandler = (err: any, req: Request, res: Response, _next: NextFunction) => {
37
- const status = err.status || err.statusCode || 500;
38
-
39
- console.error('[ERROR]', {
40
- status,
41
- method: req.method,
42
- path: req.path,
43
- message: err.message,
44
- });
45
-
46
- res.status(status).json({
47
- error: 'Internal server error'
48
- });
49
- };
50
-
51
- // The main function exported and called by the CLI
52
- export function startServer(
53
- rootPath: string
54
- ): void {
55
-
56
- try {
57
- const port = appConfig.server.port;
58
- const host = appConfig.server.host;
59
- const validatedPort = validatePort(port);
60
- const app = express();
61
-
62
- // Set request timeout (30 seconds)
63
- app.use((req, res, next) => {
64
- req.setTimeout(30000);
65
- res.setTimeout(30000);
66
- next();
67
- });
68
-
69
- app.locals.rootPath = rootPath;
70
-
71
- // Security middleware with CSP allowing API calls
72
- const apiHost = host === '0.0.0.0' ? 'localhost' : host;
73
- const apiUrl = process.env.API_URL || `http://${apiHost}:${validatedPort}`;
74
- const dashboardHost = appConfig.dashboard.host === '0.0.0.0' ? 'localhost' : appConfig.dashboard.host;
75
- const dashboardUrl = `http://${dashboardHost}:${appConfig.dashboard.port}`;
76
-
77
- app.use(helmet({
78
- contentSecurityPolicy: {
79
- directives: {
80
- defaultSrc: ["'self'"],
81
- connectSrc: ["'self'", apiUrl, 'http://localhost:*', 'http://127.0.0.1:*'],
82
- scriptSrc: ["'self'"],
83
- imgSrc: ["'self'", 'data:', 'https:'],
84
- },
85
- },
86
- }));
87
- app.use(cors({
88
- origin: process.env.CORS_ORIGIN || dashboardUrl,
89
- credentials: true,
90
- methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
91
- allowedHeaders: ['Content-Type', 'Authorization'],
92
- }));
93
-
94
- app.use(json({ limit: '1mb' }));
95
-
96
- // Request logging middleware (safe version)
97
- app.use((_req: Request, _res: Response, next: NextFunction) => {
98
- console.log(`[${new Date().toISOString()}] ${_req.method} ${_req.path}`);
99
- next();
100
- });
101
-
102
- app.use('/api/packages', packageRouter);
103
-
104
- // Get commit details
105
- app.use('/api/commits/', commitRouter);
106
-
107
- // Health check endpoint
108
- app.use('/api/health/', healthRouter);
109
-
110
- // Configuration endpoint
111
- app.use('/api/config/', configRouter);
112
-
113
- // 404 handler
114
- app.use('*', (_, res) => {
115
- res.status(404).json({
116
- error: 'Endpoint not found',
117
- timestamp: Date.now(),
118
- });
119
- });
120
-
121
- // Global error handler (must be last)
122
- app.use(errorHandler);
123
-
124
- const server = app.listen(validatedPort, host, () => {
125
- console.log(`Backend server running on http://${host}:${validatedPort}`);
126
- console.log(`API endpoints available:`);
127
- console.log(` - GET /api/health`);
128
- console.log(` - GET /api/packages/refresh`);
129
- console.log(` - GET /api/packages`);
130
- console.log(` - GET /api/packages/:name`);
131
- console.log(` - PUT /api/packages/update-config`);
132
- console.log(` - GET /api/commits/:packagePath`);
133
- console.log(` - GET /api/health/packages`);
134
- console.log(` - PUT /api/config/files/:id`);
135
- console.log(` - GET /api/config/files`);
136
- });
137
-
138
- server.on('error', (err: any) => {
139
- // Handle common errors like EADDRINUSE (port already in use)
140
- if (err.code === 'EADDRINUSE') {
141
- console.error(
142
- `Error: Port ${validatedPort} is already in use. Please specify a different port.`
143
- );
144
- process.exit(1);
145
- } else if (err.code === 'EACCES') {
146
- console.error(
147
- `Error: Permission denied to listen on port ${validatedPort}. Use a port above 1024.`
148
- );
149
- process.exit(1);
150
- } else {
151
- console.error('Server failed to start:', err.message);
152
- process.exit(1);
153
- }
154
- });
155
-
156
- // Graceful shutdown
157
- process.on('SIGTERM', () => {
158
- console.log('SIGTERM signal received: closing HTTP server');
159
- server.close(() => {
160
- console.log('HTTP server closed');
161
- process.exit(0);
162
- });
163
- });
164
- } catch (error: any) {
165
- console.error('Failed to start server:', error.message);
166
- process.exit(1);
167
- }
168
- }
169
-
170
- export function serveDashboard(
171
- rootPath: string
172
- ): void {
173
- try {
174
- const port = appConfig.dashboard.port;
175
- const host = appConfig.dashboard.host;
176
- const validatedPort = validatePort(port);
177
- const app = express();
178
-
179
- // Security middleware
180
- const serverHost = appConfig.server.host === '0.0.0.0' ? 'localhost' : appConfig.server.host;
181
- const apiUrl = process.env.API_URL || `http://${serverHost}:${appConfig.server.port}`;
182
- app.use(helmet({
183
- contentSecurityPolicy: {
184
- directives: {
185
- defaultSrc: ["'self'"],
186
- connectSrc: ["'self'", apiUrl, 'http://localhost:*', 'http://127.0.0.1:*'],
187
- scriptSrc: ["'self'"],
188
- imgSrc: ["'self'", 'data:', 'https:'],
189
- },
190
- },
191
- }));
192
-
193
- // Strict CORS for dashboard
194
- app.use(cors({
195
- origin: false, // Don't allow any origin for static assets
196
- }));
197
-
198
- // Set request timeout
199
- app.use((req, res, next) => {
200
- req.setTimeout(30000);
201
- res.setTimeout(30000);
202
- next();
203
- });
204
-
205
- app.get('/env-config.js', (req, res) => {
206
- res.setHeader('Content-Type', 'application/javascript');
207
- res.setHeader('Cache-Control', 'private, no-cache, no-store, must-revalidate');
208
-
209
- const serverHost = appConfig.server.host === '0.0.0.0' ? 'localhost' : appConfig.server.host;
210
- const apiUrl = process.env.API_URL || `http://${serverHost}:${appConfig.server.port}`;
211
- res.send(
212
- `window.ENV = { API_URL: "${apiUrl}" };`
213
- );
214
- });
215
-
216
- // This code makes sure that any request that does not matches a static file
217
- // in the build folder, will just serve index.html. Client side routing is
218
- // going to make sure that the correct content will be loaded.
219
- app.use((req, res, next) => {
220
- if (/(.ico|.js|.css|.jpg|.png|.map|.woff|.woff2|.ttf)$/i.test(req.path)) {
221
- next();
222
- } else {
223
- res.header(
224
- 'Cache-Control',
225
- 'private, no-cache, no-store, must-revalidate'
226
- );
227
- res.header('Expires', '-1');
228
- res.header('Pragma', 'no-cache');
229
- res.sendFile('index.html', {
230
- root: path.resolve(__dirname, '..', 'monodog-dashboard', 'dist'),
231
- }, (err) => {
232
- if (err) {
233
- console.error('Error serving index.html:', err.message);
234
- res.status(500).json({ error: 'Internal server error' });
235
- }
236
- });
237
- }
238
- });
239
-
240
- const staticPath = path.join(__dirname, '..', 'monodog-dashboard', 'dist');
241
- console.log('Serving static files from:', staticPath);
242
- app.use(express.static(staticPath, {
243
- maxAge: '1d',
244
- etag: false,
245
- dotfiles: 'deny', // Don't serve dot files
246
- }));
247
-
248
- // Global error handler
249
- app.use(errorHandler);
250
-
251
- const server = app.listen(validatedPort, host, () => {
252
- console.log(`Dashboard listening on http://${host}:${validatedPort}`);
253
- console.log('Press Ctrl+C to quit.');
254
- });
255
-
256
- server.on('error', (err: any) => {
257
- if (err.code === 'EADDRINUSE') {
258
- console.error(`Error: Port ${validatedPort} is already in use.`);
259
- process.exit(1);
260
- } else if (err.code === 'EACCES') {
261
- console.error(`Error: Permission denied to listen on port ${validatedPort}.`);
262
- process.exit(1);
263
- } else {
264
- console.error('Server failed to start:', err.message);
265
- process.exit(1);
266
- }
267
- });
268
-
269
- // Graceful shutdown
270
- process.on('SIGTERM', () => {
271
- console.log('SIGTERM signal received: closing dashboard server');
272
- server.close(() => {
273
- console.log('Dashboard server closed');
274
- process.exit(0);
275
- });
276
- });
277
- } catch (error: any) {
278
- console.error('Failed to start dashboard:', error.message);
279
- process.exit(1);
280
- }
281
- }
1
+ /**
2
+ * Monodog Application Entry Point
3
+ *
4
+ * This file exports the main server and dashboard startup functions
5
+ * All middleware, security, and error handling logic has been moved to separate files
6
+ */
7
+
8
+ export { startServer } from './middleware/server-startup';
9
+ export { serveDashboard } from './middleware/dashboard-startup';
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Dashboard server startup logic
3
+ */
4
+
5
+ import express from 'express';
6
+ import path from 'path';
7
+ import type { Express } from 'express';
8
+ import { httpLogger, AppLogger } from './logger';
9
+
10
+ import { appConfig } from '../config-loader';
11
+ import {
12
+ errorHandler,
13
+ } from './error-handler';
14
+ import {
15
+ createHelmetMiddleware,
16
+ createDashboardCorsMiddleware,
17
+ createTimeoutMiddleware,
18
+ buildApiUrl,
19
+ } from './security';
20
+
21
+ // Security constants
22
+ const PORT_MIN = 1024;
23
+ const PORT_MAX = 65535;
24
+
25
+ /**
26
+ * Validate port number
27
+ */
28
+ function validatePort(port: string | number): number {
29
+ const portNum = typeof port === 'string' ? parseInt(port, 10) : port;
30
+
31
+ if (isNaN(portNum) || portNum < PORT_MIN || portNum > PORT_MAX) {
32
+ throw new Error(`Port must be between ${PORT_MIN} and ${PORT_MAX}`);
33
+ }
34
+
35
+ return portNum;
36
+ }
37
+
38
+ /**
39
+ * Create Express app for dashboard with middleware
40
+ */
41
+ function createDashboardApp(): Express {
42
+ const app = express();
43
+
44
+ // Timeout middleware
45
+ app.use(createTimeoutMiddleware());
46
+
47
+ // Security setup
48
+ const serverHost = appConfig.server.host === '0.0.0.0'
49
+ ? 'localhost'
50
+ : appConfig.server.host;
51
+ const apiUrl = buildApiUrl(serverHost, appConfig.server.port);
52
+
53
+ app.use(createHelmetMiddleware(apiUrl));
54
+ app.use(createDashboardCorsMiddleware());
55
+
56
+ // Environment config endpoint
57
+ app.get('/env-config.js', (_req, res) => {
58
+ res.setHeader('Content-Type', 'application/javascript');
59
+ res.setHeader('Cache-Control', 'private, no-cache, no-store, must-revalidate');
60
+
61
+ res.send(
62
+ `window.ENV = { API_URL: "${apiUrl}" };`
63
+ );
64
+ });
65
+
66
+ // Request logging
67
+ app.use(httpLogger);
68
+ // app.use(requestLogger);
69
+
70
+ // SPA routing: serve index.html for non-static routes
71
+ app.use((_req, _res, next) => {
72
+ if (/(.ico|.js|.css|.jpg|.png|.map|.woff|.woff2|.ttf)$/i.test(_req.path)) {
73
+ next();
74
+ } else {
75
+ _res.header(
76
+ 'Cache-Control',
77
+ 'private, no-cache, no-store, must-revalidate'
78
+ );
79
+ _res.header('Expires', '-1');
80
+ _res.header('Pragma', 'no-cache');
81
+ _res.sendFile('index.html', {
82
+ root: path.resolve(__dirname, '..', '..', 'monodog-dashboard', 'dist'),
83
+ }, (err: Error | null) => {
84
+ if (err) {
85
+ AppLogger.error('Error serving index.html:', err);
86
+ _res.status(500).json({ error: 'Internal server error' });
87
+ }
88
+ });
89
+ }
90
+ });
91
+
92
+ // Static files
93
+ const staticPath = path.join(__dirname, '..', '..', 'monodog-dashboard', 'dist');
94
+ AppLogger.debug('Serving static files from:', { path: staticPath });
95
+ app.use(express.static(staticPath, {
96
+ maxAge: '1d',
97
+ etag: false,
98
+ dotfiles: 'deny',
99
+ }));
100
+
101
+ // Global error handler (must be last)
102
+ app.use(errorHandler);
103
+
104
+ return app;
105
+ }
106
+
107
+ /**
108
+ * Start the dashboard server
109
+ */
110
+ export function serveDashboard(rootPath: string): void {
111
+ try {
112
+ const port = appConfig.dashboard.port;
113
+ const host = appConfig.dashboard.host;
114
+ const validatedPort = validatePort(port);
115
+
116
+ const app = createDashboardApp();
117
+
118
+ const server = app.listen(validatedPort, host, () => {
119
+ console.log(`Dashboard listening on http://${host}:${validatedPort}`);
120
+ console.log('Press Ctrl+C to quit.');
121
+ });
122
+
123
+ server.on('error', (err: NodeJS.ErrnoException) => {
124
+ if (err.code === 'EADDRINUSE') {
125
+ AppLogger.error(`Port ${validatedPort} is already in use.`, err);
126
+ process.exit(1);
127
+ } else if (err.code === 'EACCES') {
128
+ AppLogger.error(`Permission denied to listen on port ${validatedPort}.`, err);
129
+ process.exit(1);
130
+ } else {
131
+ AppLogger.error('Server failed to start:', err);
132
+ process.exit(1);
133
+ }
134
+ });
135
+
136
+ // Graceful shutdown
137
+ process.on('SIGTERM', () => {
138
+ AppLogger.info('SIGTERM signal received: closing dashboard server');
139
+ server.close(() => {
140
+ AppLogger.info('Dashboard server closed');
141
+ process.exit(0);
142
+ });
143
+ });
144
+ } catch (error: unknown) {
145
+ const err = error as Error & { message?: string };
146
+ AppLogger.error('Failed to start dashboard:', err);
147
+ process.exit(1);
148
+ }
149
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Error handling middleware for Express
3
+ */
4
+
5
+ import { Request, Response, NextFunction, ErrorRequestHandler } from 'express';
6
+ import { AppLogger } from './logger';
7
+
8
+ /**
9
+ * Custom error interface extending Error
10
+ */
11
+ export interface CustomError extends Error {
12
+ status?: number;
13
+ statusCode?: number;
14
+ }
15
+
16
+ /**
17
+ * Global error handler middleware
18
+ * Must be registered last in the middleware chain
19
+ */
20
+ export const errorHandler: ErrorRequestHandler = (
21
+ err: CustomError,
22
+ req: Request,
23
+ res: Response,
24
+ _next: NextFunction
25
+ ): void => {
26
+ const status = err.status || err.statusCode || 500;
27
+
28
+ AppLogger.error('Request error occurred', {
29
+ status,
30
+ method: req.method,
31
+ path: req.path,
32
+ message: err.message,
33
+ timestamp: new Date().toISOString(),
34
+ });
35
+
36
+ res.status(status).json({
37
+ error: 'Internal server error',
38
+ timestamp: Date.now(),
39
+ });
40
+ };
41
+
42
+ /**
43
+ * 404 Not Found handler
44
+ */
45
+ export const notFoundHandler = (_req: Request, res: Response): void => {
46
+ res.status(404).json({
47
+ error: 'Endpoint not found',
48
+ timestamp: Date.now(),
49
+ });
50
+ };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Middleware exports
3
+ */
4
+
5
+ export { errorHandler, notFoundHandler } from './error-handler';
6
+ export type { CustomError } from './error-handler';
7
+
8
+ export {
9
+ createHelmetMiddleware,
10
+ createApiCorsMiddleware,
11
+ createDashboardCorsMiddleware,
12
+ createTimeoutMiddleware,
13
+ buildApiUrl,
14
+ buildDashboardUrl,
15
+ } from './security';
16
+
17
+ export { startServer } from './server-startup';
18
+ export { serveDashboard } from './dashboard-startup';
19
+
20
+ export { httpLogger, AppLogger } from './logger';
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Logger configuration using Morgan
3
+ */
4
+
5
+ import morgan from 'morgan';
6
+
7
+ /**
8
+ * HTTP request logger middleware using Morgan
9
+ */
10
+ export const httpLogger = morgan('dev');
11
+
12
+ /**
13
+ * Application logger for non-HTTP events
14
+ */
15
+ export class AppLogger {
16
+ private static readonly prefix = '[APP]';
17
+
18
+ static info(message: string, data?: Record<string, unknown>): void {
19
+ if (process.env.LOG_LEVEL == 'info' || process.env.LOG_LEVEL == 'debug') {
20
+ if (data) {
21
+ console.log(`${this.prefix} [INFO]`, message, JSON.stringify(data, null, 2));
22
+ } else {
23
+ console.log(`${this.prefix} [INFO]`, message);
24
+ }
25
+ }
26
+ }
27
+
28
+ static error(message: string, error?: Error | Record<string, unknown>): void {
29
+ if (error instanceof Error) {
30
+ console.error(`${this.prefix} [ERROR]`, message, {
31
+ message: error.message,
32
+ stack: error.stack,
33
+ });
34
+ } else if (error) {
35
+ console.error(`${this.prefix} [ERROR]`, message, error);
36
+ } else {
37
+ console.error(`${this.prefix} [ERROR]`, message);
38
+ }
39
+ }
40
+
41
+ static warn(message: string, data?: Record<string, unknown>): void {
42
+ if (data) {
43
+ console.warn(`${this.prefix} [WARN]`, message, JSON.stringify(data, null, 2));
44
+ } else {
45
+ console.warn(`${this.prefix} [WARN]`, message);
46
+ }
47
+ }
48
+
49
+ static debug(message: string, data?: Record<string, unknown>): void {
50
+ if (process.env.LOG_LEVEL == 'debug') {
51
+ if (data) {
52
+ console.log(`${this.prefix} [DEBUG]`, message, JSON.stringify(data, null, 2));
53
+ } else {
54
+ console.log(`${this.prefix} [DEBUG]`, message);
55
+ }
56
+ }
57
+ }
58
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Security middleware and configuration
3
+ */
4
+
5
+ import { Request, Response, NextFunction } from 'express';
6
+ import helmet from 'helmet';
7
+ import cors, { CorsOptions } from 'cors';
8
+ import type { MonodogConfig } from '../types/config';
9
+
10
+ /**
11
+ * Create Helmet security middleware with Content Security Policy
12
+ */
13
+ export function createHelmetMiddleware(apiUrl: string) {
14
+ return helmet({
15
+ contentSecurityPolicy: {
16
+ directives: {
17
+ defaultSrc: ["'self'"],
18
+ connectSrc: ["'self'", apiUrl, 'http://localhost:*', 'http://127.0.0.1:*'],
19
+ scriptSrc: ["'self'"],
20
+ imgSrc: ["'self'", 'data:', 'https:'],
21
+ },
22
+ },
23
+ });
24
+ }
25
+
26
+ /**
27
+ * Create CORS middleware for API server
28
+ */
29
+ export function createApiCorsMiddleware(dashboardUrl: string) {
30
+ const corsOptions: CorsOptions = {
31
+ origin: dashboardUrl,
32
+ credentials: true,
33
+ methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
34
+ allowedHeaders: ['Content-Type', 'Authorization'],
35
+ };
36
+
37
+ return cors(corsOptions);
38
+ }
39
+
40
+ /**
41
+ * Create CORS middleware for dashboard (no cross-origin)
42
+ */
43
+ export function createDashboardCorsMiddleware() {
44
+ const corsOptions: CorsOptions = {
45
+ origin: false, // Don't allow any origin for static assets
46
+ };
47
+
48
+ return cors(corsOptions);
49
+ }
50
+
51
+ /**
52
+ * Request timeout middleware (30 seconds)
53
+ */
54
+ export function createTimeoutMiddleware() {
55
+ return (req: Request, res: Response, next: NextFunction): void => {
56
+ req.setTimeout(30000);
57
+ res.setTimeout(30000);
58
+ next();
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Build API URL based on config
64
+ */
65
+ export function buildApiUrl(
66
+ host: string,
67
+ port: number
68
+ ): string {
69
+ const apiHost = host === '0.0.0.0' ? 'localhost' : host;
70
+ return `http://${apiHost}:${port}`;
71
+ }
72
+
73
+ /**
74
+ * Build dashboard URL based on config
75
+ */
76
+ export function buildDashboardUrl(config: MonodogConfig): string {
77
+ const dashboardHost = config.dashboard.host === '0.0.0.0'
78
+ ? 'localhost'
79
+ : config.dashboard.host;
80
+ return `http://${dashboardHost}:${config.dashboard.port}`;
81
+ }