@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.
- package/LICENSE +21 -0
- package/README.md +652 -0
- package/dist/api/logsRouter.d.ts +20 -0
- package/dist/api/logsRouter.d.ts.map +1 -0
- package/dist/api/logsRouter.js +515 -0
- package/dist/api/logsRouter.js.map +1 -0
- package/dist/cli/dev-server.d.ts +7 -0
- package/dist/cli/dev-server.d.ts.map +1 -0
- package/dist/cli/dev-server.js +640 -0
- package/dist/cli/dev-server.js.map +1 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +26 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/StandardServer.d.ts +129 -0
- package/dist/core/StandardServer.d.ts.map +1 -0
- package/dist/core/StandardServer.js +453 -0
- package/dist/core/StandardServer.js.map +1 -0
- package/dist/core/apiResponse.d.ts +69 -0
- package/dist/core/apiResponse.d.ts.map +1 -0
- package/dist/core/apiResponse.js +127 -0
- package/dist/core/apiResponse.js.map +1 -0
- package/dist/core/healthCheck.d.ts +160 -0
- package/dist/core/healthCheck.d.ts.map +1 -0
- package/dist/core/healthCheck.js +398 -0
- package/dist/core/healthCheck.js.map +1 -0
- package/dist/core/index.d.ts +40 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +40 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/logger.d.ts +117 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +826 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/portUtils.d.ts +71 -0
- package/dist/core/portUtils.d.ts.map +1 -0
- package/dist/core/portUtils.js +240 -0
- package/dist/core/portUtils.js.map +1 -0
- package/dist/core/storageService.d.ts +119 -0
- package/dist/core/storageService.d.ts.map +1 -0
- package/dist/core/storageService.js +405 -0
- package/dist/core/storageService.js.map +1 -0
- package/dist/desktop/bundler.d.ts +40 -0
- package/dist/desktop/bundler.d.ts.map +1 -0
- package/dist/desktop/bundler.js +176 -0
- package/dist/desktop/bundler.js.map +1 -0
- package/dist/desktop/index.d.ts +25 -0
- package/dist/desktop/index.d.ts.map +1 -0
- package/dist/desktop/index.js +15 -0
- package/dist/desktop/index.js.map +1 -0
- package/dist/desktop/native-modules.d.ts +66 -0
- package/dist/desktop/native-modules.d.ts.map +1 -0
- package/dist/desktop/native-modules.js +200 -0
- package/dist/desktop/native-modules.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/logging/LogCategories.d.ts +87 -0
- package/dist/logging/LogCategories.d.ts.map +1 -0
- package/dist/logging/LogCategories.js +205 -0
- package/dist/logging/LogCategories.js.map +1 -0
- package/dist/middleware/aiErrorHandler.d.ts +31 -0
- package/dist/middleware/aiErrorHandler.d.ts.map +1 -0
- package/dist/middleware/aiErrorHandler.js +181 -0
- package/dist/middleware/aiErrorHandler.js.map +1 -0
- package/dist/middleware/auth.d.ts +101 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +230 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/cors.d.ts +56 -0
- package/dist/middleware/cors.d.ts.map +1 -0
- package/dist/middleware/cors.js +123 -0
- package/dist/middleware/cors.js.map +1 -0
- package/dist/middleware/errorHandler.d.ts +13 -0
- package/dist/middleware/errorHandler.d.ts.map +1 -0
- package/dist/middleware/errorHandler.js +85 -0
- package/dist/middleware/errorHandler.js.map +1 -0
- package/dist/middleware/fileUpload.d.ts +62 -0
- package/dist/middleware/fileUpload.d.ts.map +1 -0
- package/dist/middleware/fileUpload.js +175 -0
- package/dist/middleware/fileUpload.js.map +1 -0
- package/dist/middleware/health.d.ts +48 -0
- package/dist/middleware/health.d.ts.map +1 -0
- package/dist/middleware/health.js +143 -0
- package/dist/middleware/health.js.map +1 -0
- package/dist/middleware/index.d.ts +20 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +18 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/openapi.d.ts +64 -0
- package/dist/middleware/openapi.d.ts.map +1 -0
- package/dist/middleware/openapi.js +258 -0
- package/dist/middleware/openapi.js.map +1 -0
- package/dist/middleware/requestLogging.d.ts +22 -0
- package/dist/middleware/requestLogging.d.ts.map +1 -0
- package/dist/middleware/requestLogging.js +61 -0
- package/dist/middleware/requestLogging.js.map +1 -0
- package/dist/middleware/session.d.ts +84 -0
- package/dist/middleware/session.d.ts.map +1 -0
- package/dist/middleware/session.js +189 -0
- package/dist/middleware/session.js.map +1 -0
- package/dist/middleware/validation.d.ts +1337 -0
- package/dist/middleware/validation.d.ts.map +1 -0
- package/dist/middleware/validation.js +483 -0
- package/dist/middleware/validation.js.map +1 -0
- package/dist/services/aiService.d.ts +180 -0
- package/dist/services/aiService.d.ts.map +1 -0
- package/dist/services/aiService.js +547 -0
- package/dist/services/aiService.js.map +1 -0
- package/dist/services/conversationStorage.d.ts +38 -0
- package/dist/services/conversationStorage.d.ts.map +1 -0
- package/dist/services/conversationStorage.js +158 -0
- package/dist/services/conversationStorage.js.map +1 -0
- package/dist/services/crossPlatformBuffer.d.ts +84 -0
- package/dist/services/crossPlatformBuffer.d.ts.map +1 -0
- package/dist/services/crossPlatformBuffer.js +246 -0
- package/dist/services/crossPlatformBuffer.js.map +1 -0
- package/dist/services/index.d.ts +17 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +18 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/networkService.d.ts +81 -0
- package/dist/services/networkService.d.ts.map +1 -0
- package/dist/services/networkService.js +268 -0
- package/dist/services/networkService.js.map +1 -0
- package/dist/services/queueService.d.ts +112 -0
- package/dist/services/queueService.d.ts.map +1 -0
- package/dist/services/queueService.js +338 -0
- package/dist/services/queueService.js.map +1 -0
- package/dist/services/settingsService.d.ts +135 -0
- package/dist/services/settingsService.d.ts.map +1 -0
- package/dist/services/settingsService.js +425 -0
- package/dist/services/settingsService.js.map +1 -0
- package/dist/services/systemMonitor.d.ts +208 -0
- package/dist/services/systemMonitor.d.ts.map +1 -0
- package/dist/services/systemMonitor.js +693 -0
- package/dist/services/systemMonitor.js.map +1 -0
- package/dist/services/updateService.d.ts +78 -0
- package/dist/services/updateService.d.ts.map +1 -0
- package/dist/services/updateService.js +252 -0
- package/dist/services/updateService.js.map +1 -0
- package/dist/services/websocketEvents.d.ts +372 -0
- package/dist/services/websocketEvents.d.ts.map +1 -0
- package/dist/services/websocketEvents.js +338 -0
- package/dist/services/websocketEvents.js.map +1 -0
- package/dist/services/websocketServer.d.ts +80 -0
- package/dist/services/websocketServer.d.ts.map +1 -0
- package/dist/services/websocketServer.js +299 -0
- package/dist/services/websocketServer.js.map +1 -0
- package/dist/settings/SettingsSchema.d.ts +151 -0
- package/dist/settings/SettingsSchema.d.ts.map +1 -0
- package/dist/settings/SettingsSchema.js +424 -0
- package/dist/settings/SettingsSchema.js.map +1 -0
- package/dist/testing/TestServer.d.ts +69 -0
- package/dist/testing/TestServer.d.ts.map +1 -0
- package/dist/testing/TestServer.js +250 -0
- package/dist/testing/TestServer.js.map +1 -0
- package/dist/types/index.d.ts +137 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/appPaths.d.ts +74 -0
- package/dist/utils/appPaths.d.ts.map +1 -0
- package/dist/utils/appPaths.js +162 -0
- package/dist/utils/appPaths.js.map +1 -0
- package/dist/utils/fs-utils.d.ts +50 -0
- package/dist/utils/fs-utils.d.ts.map +1 -0
- package/dist/utils/fs-utils.js +114 -0
- package/dist/utils/fs-utils.js.map +1 -0
- package/dist/utils/index.d.ts +12 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +10 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/standardConfig.d.ts +61 -0
- package/dist/utils/standardConfig.d.ts.map +1 -0
- package/dist/utils/standardConfig.js +109 -0
- package/dist/utils/standardConfig.js.map +1 -0
- package/dist/utils/startupBanner.d.ts +34 -0
- package/dist/utils/startupBanner.d.ts.map +1 -0
- package/dist/utils/startupBanner.js +169 -0
- package/dist/utils/startupBanner.js.map +1 -0
- package/dist/utils/startupLogger.d.ts +45 -0
- package/dist/utils/startupLogger.d.ts.map +1 -0
- package/dist/utils/startupLogger.js +200 -0
- package/dist/utils/startupLogger.js.map +1 -0
- package/package.json +151 -0
- package/src/api/logsRouter.ts +600 -0
- package/src/cli/dev-server.ts +803 -0
- package/src/cli/index.ts +31 -0
- package/src/core/StandardServer.ts +587 -0
- package/src/core/apiResponse.ts +202 -0
- package/src/core/healthCheck.ts +565 -0
- package/src/core/index.ts +80 -0
- package/src/core/logger.ts +1092 -0
- package/src/core/portUtils.ts +319 -0
- package/src/core/storageService.ts +595 -0
- package/src/desktop/bundler.ts +271 -0
- package/src/desktop/index.ts +18 -0
- package/src/desktop/native-modules.ts +289 -0
- package/src/index.ts +142 -0
- package/src/logging/LogCategories.ts +302 -0
- package/src/middleware/aiErrorHandler.ts +278 -0
- package/src/middleware/auth.ts +329 -0
- package/src/middleware/cors.ts +187 -0
- package/src/middleware/errorHandler.ts +103 -0
- package/src/middleware/fileUpload.ts +252 -0
- package/src/middleware/health.ts +206 -0
- package/src/middleware/index.ts +71 -0
- package/src/middleware/openapi.ts +305 -0
- package/src/middleware/requestLogging.ts +92 -0
- package/src/middleware/session.ts +238 -0
- package/src/middleware/validation.ts +603 -0
- package/src/services/aiService.ts +789 -0
- package/src/services/conversationStorage.ts +232 -0
- package/src/services/crossPlatformBuffer.ts +341 -0
- package/src/services/index.ts +47 -0
- package/src/services/networkService.ts +351 -0
- package/src/services/queueService.ts +446 -0
- package/src/services/settingsService.ts +549 -0
- package/src/services/systemMonitor.ts +936 -0
- package/src/services/updateService.ts +334 -0
- package/src/services/websocketEvents.ts +409 -0
- package/src/services/websocketServer.ts +394 -0
- package/src/settings/SettingsSchema.ts +664 -0
- package/src/testing/TestServer.ts +312 -0
- package/src/types/index.ts +154 -0
- package/src/utils/appPaths.ts +196 -0
- package/src/utils/fs-utils.ts +130 -0
- package/src/utils/index.ts +15 -0
- package/src/utils/standardConfig.ts +178 -0
- package/src/utils/startupBanner.ts +287 -0
- package/src/utils/startupLogger.ts +268 -0
- package/ui/dist/index.d.mts +1221 -0
- package/ui/dist/index.d.ts +1221 -0
- package/ui/dist/index.js +73 -0
- package/ui/dist/index.js.map +1 -0
- package/ui/dist/index.mjs +73 -0
- package/ui/dist/index.mjs.map +1 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAPI Documentation Middleware
|
|
3
|
+
* Provides automatic API documentation generation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import swaggerJsdoc from "swagger-jsdoc";
|
|
7
|
+
import swaggerUi from "swagger-ui-express";
|
|
8
|
+
import { Express } from "express";
|
|
9
|
+
|
|
10
|
+
export interface OpenAPIConfig {
|
|
11
|
+
title: string;
|
|
12
|
+
version: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
servers?: Array<{
|
|
15
|
+
url: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
}>;
|
|
18
|
+
contact?: {
|
|
19
|
+
name?: string;
|
|
20
|
+
email?: string;
|
|
21
|
+
url?: string;
|
|
22
|
+
};
|
|
23
|
+
license?: {
|
|
24
|
+
name: string;
|
|
25
|
+
url?: string;
|
|
26
|
+
};
|
|
27
|
+
tags?: Array<{
|
|
28
|
+
name: string;
|
|
29
|
+
description?: string;
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Generate OpenAPI specification
|
|
35
|
+
* @param config OpenAPI configuration
|
|
36
|
+
* @param apis Array of file patterns for API routes
|
|
37
|
+
*/
|
|
38
|
+
export function generateOpenAPISpec(
|
|
39
|
+
config: OpenAPIConfig,
|
|
40
|
+
apis: string[],
|
|
41
|
+
): object {
|
|
42
|
+
const options: swaggerJsdoc.Options = {
|
|
43
|
+
definition: {
|
|
44
|
+
openapi: "3.0.0",
|
|
45
|
+
info: {
|
|
46
|
+
title: config.title,
|
|
47
|
+
version: config.version,
|
|
48
|
+
description: config.description,
|
|
49
|
+
contact: config.contact,
|
|
50
|
+
license: config.license,
|
|
51
|
+
},
|
|
52
|
+
servers: config.servers || [
|
|
53
|
+
{
|
|
54
|
+
url: "http://localhost:3000",
|
|
55
|
+
description: "Development server",
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
tags: config.tags,
|
|
59
|
+
components: {
|
|
60
|
+
schemas: {
|
|
61
|
+
ApiResponse: {
|
|
62
|
+
type: "object",
|
|
63
|
+
properties: {
|
|
64
|
+
success: {
|
|
65
|
+
type: "boolean",
|
|
66
|
+
description: "Indicates if the request was successful",
|
|
67
|
+
},
|
|
68
|
+
data: {
|
|
69
|
+
type: "object",
|
|
70
|
+
description: "Response data payload",
|
|
71
|
+
},
|
|
72
|
+
error: {
|
|
73
|
+
type: "object",
|
|
74
|
+
properties: {
|
|
75
|
+
code: {
|
|
76
|
+
type: "string",
|
|
77
|
+
description: "Error code",
|
|
78
|
+
},
|
|
79
|
+
message: {
|
|
80
|
+
type: "string",
|
|
81
|
+
description: "Error message",
|
|
82
|
+
},
|
|
83
|
+
details: {
|
|
84
|
+
type: "object",
|
|
85
|
+
description: "Additional error details",
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
message: {
|
|
90
|
+
type: "string",
|
|
91
|
+
description: "Optional success message",
|
|
92
|
+
},
|
|
93
|
+
metadata: {
|
|
94
|
+
type: "object",
|
|
95
|
+
properties: {
|
|
96
|
+
timestamp: {
|
|
97
|
+
type: "string",
|
|
98
|
+
format: "date-time",
|
|
99
|
+
description: "Response timestamp",
|
|
100
|
+
},
|
|
101
|
+
version: {
|
|
102
|
+
type: "string",
|
|
103
|
+
description: "API version",
|
|
104
|
+
},
|
|
105
|
+
pagination: {
|
|
106
|
+
type: "object",
|
|
107
|
+
properties: {
|
|
108
|
+
page: { type: "integer" },
|
|
109
|
+
limit: { type: "integer" },
|
|
110
|
+
total: { type: "integer" },
|
|
111
|
+
hasMore: { type: "boolean" },
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
ValidationError: {
|
|
119
|
+
type: "object",
|
|
120
|
+
properties: {
|
|
121
|
+
field: {
|
|
122
|
+
type: "string",
|
|
123
|
+
description: "Field that failed validation",
|
|
124
|
+
},
|
|
125
|
+
message: {
|
|
126
|
+
type: "string",
|
|
127
|
+
description: "Validation error message",
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
ErrorResponse: {
|
|
132
|
+
type: "object",
|
|
133
|
+
properties: {
|
|
134
|
+
success: {
|
|
135
|
+
type: "boolean",
|
|
136
|
+
enum: [false],
|
|
137
|
+
},
|
|
138
|
+
error: {
|
|
139
|
+
type: "object",
|
|
140
|
+
properties: {
|
|
141
|
+
code: { type: "string" },
|
|
142
|
+
message: { type: "string" },
|
|
143
|
+
details: { type: "object" },
|
|
144
|
+
},
|
|
145
|
+
required: ["code", "message"],
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
required: ["success", "error"],
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
securitySchemes: {
|
|
152
|
+
sessionAuth: {
|
|
153
|
+
type: "apiKey",
|
|
154
|
+
in: "cookie",
|
|
155
|
+
name: "connect.sid",
|
|
156
|
+
description: "Session-based authentication",
|
|
157
|
+
},
|
|
158
|
+
bearerAuth: {
|
|
159
|
+
type: "http",
|
|
160
|
+
scheme: "bearer",
|
|
161
|
+
bearerFormat: "JWT",
|
|
162
|
+
description: "JWT bearer token authentication",
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
responses: {
|
|
166
|
+
BadRequest: {
|
|
167
|
+
description: "Bad request",
|
|
168
|
+
content: {
|
|
169
|
+
"application/json": {
|
|
170
|
+
schema: {
|
|
171
|
+
$ref: "#/components/schemas/ErrorResponse",
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
Unauthorized: {
|
|
177
|
+
description: "Authentication required",
|
|
178
|
+
content: {
|
|
179
|
+
"application/json": {
|
|
180
|
+
schema: {
|
|
181
|
+
$ref: "#/components/schemas/ErrorResponse",
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
Forbidden: {
|
|
187
|
+
description: "Access denied",
|
|
188
|
+
content: {
|
|
189
|
+
"application/json": {
|
|
190
|
+
schema: {
|
|
191
|
+
$ref: "#/components/schemas/ErrorResponse",
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
NotFound: {
|
|
197
|
+
description: "Resource not found",
|
|
198
|
+
content: {
|
|
199
|
+
"application/json": {
|
|
200
|
+
schema: {
|
|
201
|
+
$ref: "#/components/schemas/ErrorResponse",
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
InternalError: {
|
|
207
|
+
description: "Internal server error",
|
|
208
|
+
content: {
|
|
209
|
+
"application/json": {
|
|
210
|
+
schema: {
|
|
211
|
+
$ref: "#/components/schemas/ErrorResponse",
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
apis,
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
return swaggerJsdoc(options);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Setup OpenAPI documentation UI
|
|
227
|
+
* @param app Express application
|
|
228
|
+
* @param config OpenAPI configuration
|
|
229
|
+
* @param apis Array of file patterns for API routes
|
|
230
|
+
* @param path Path to serve documentation (default: /api-docs)
|
|
231
|
+
*/
|
|
232
|
+
export function setupOpenAPIDocumentation(
|
|
233
|
+
app: Express,
|
|
234
|
+
config: OpenAPIConfig,
|
|
235
|
+
apis: string[],
|
|
236
|
+
path: string = "/api-docs",
|
|
237
|
+
): void {
|
|
238
|
+
const swaggerSpec = generateOpenAPISpec(config, apis);
|
|
239
|
+
|
|
240
|
+
// Serve OpenAPI spec as JSON
|
|
241
|
+
app.get(`${path}/spec.json`, (_req, res) => {
|
|
242
|
+
res.json(swaggerSpec);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// Serve Swagger UI
|
|
246
|
+
app.use(
|
|
247
|
+
path,
|
|
248
|
+
swaggerUi.serve,
|
|
249
|
+
swaggerUi.setup(swaggerSpec, {
|
|
250
|
+
customSiteTitle: `${config.title} - API Documentation`,
|
|
251
|
+
customfavIcon: "/favicon.ico",
|
|
252
|
+
customCss: `
|
|
253
|
+
.swagger-ui .topbar { display: none }
|
|
254
|
+
.swagger-ui .info { margin-bottom: 40px }
|
|
255
|
+
.swagger-ui .scheme-container { display: none }
|
|
256
|
+
`,
|
|
257
|
+
swaggerOptions: {
|
|
258
|
+
persistAuthorization: true,
|
|
259
|
+
displayRequestDuration: true,
|
|
260
|
+
docExpansion: "none",
|
|
261
|
+
filter: true,
|
|
262
|
+
showExtensions: true,
|
|
263
|
+
showCommonExtensions: true,
|
|
264
|
+
displayOperationId: false,
|
|
265
|
+
},
|
|
266
|
+
}),
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Example JSDoc annotation for OpenAPI
|
|
272
|
+
*
|
|
273
|
+
* @swagger
|
|
274
|
+
* /api/resource:
|
|
275
|
+
* get:
|
|
276
|
+
* summary: Get all resources
|
|
277
|
+
* tags: [Resources]
|
|
278
|
+
* responses:
|
|
279
|
+
* 200:
|
|
280
|
+
* description: Successful response
|
|
281
|
+
* content:
|
|
282
|
+
* application/json:
|
|
283
|
+
* schema:
|
|
284
|
+
* $ref: '#/components/schemas/ApiResponse'
|
|
285
|
+
* 500:
|
|
286
|
+
* $ref: '#/components/responses/InternalError'
|
|
287
|
+
*/
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* OpenAPI operation decorator (for future TypeScript decorator support)
|
|
291
|
+
*/
|
|
292
|
+
export function ApiOperation(operation: any) {
|
|
293
|
+
return function (
|
|
294
|
+
target: any,
|
|
295
|
+
propertyKey: string,
|
|
296
|
+
descriptor: PropertyDescriptor,
|
|
297
|
+
) {
|
|
298
|
+
// Store OpenAPI metadata for later processing
|
|
299
|
+
if (!target._openapi) {
|
|
300
|
+
target._openapi = {};
|
|
301
|
+
}
|
|
302
|
+
target._openapi[propertyKey] = operation;
|
|
303
|
+
return descriptor;
|
|
304
|
+
};
|
|
305
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { NextFunction, Request, Response } from "express";
|
|
2
|
+
import { randomUUID } from "crypto";
|
|
3
|
+
import { createLogger } from "../core/logger.js";
|
|
4
|
+
|
|
5
|
+
export interface RequestLoggingOptions {
|
|
6
|
+
logger?: any;
|
|
7
|
+
/**
|
|
8
|
+
* Paths to skip logging for (commonly health checks or metrics)
|
|
9
|
+
*/
|
|
10
|
+
skipPaths?: Array<string | RegExp>;
|
|
11
|
+
/**
|
|
12
|
+
* Whether to log the request payload (truncated)
|
|
13
|
+
*/
|
|
14
|
+
logPayload?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Maximum number of characters from the payload to log
|
|
17
|
+
*/
|
|
18
|
+
maxPayloadLength?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const defaultSkips = [/^\/health/, /^\/api\/health/];
|
|
22
|
+
|
|
23
|
+
function shouldSkip(path: string, patterns: Array<string | RegExp>) {
|
|
24
|
+
return patterns.some((pattern) => {
|
|
25
|
+
if (pattern instanceof RegExp) {
|
|
26
|
+
return pattern.test(path);
|
|
27
|
+
}
|
|
28
|
+
return path.startsWith(pattern);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function safePayload(body: unknown, maxLength: number): string | undefined {
|
|
33
|
+
if (!body || typeof body !== "object") return undefined;
|
|
34
|
+
const serialized = JSON.stringify(body);
|
|
35
|
+
if (serialized.length <= maxLength) return serialized;
|
|
36
|
+
return `${serialized.slice(0, maxLength)}…`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Express middleware that assigns a request ID and logs completion.
|
|
41
|
+
* Uses the framework logger so request logs land in the same files as the app.
|
|
42
|
+
*/
|
|
43
|
+
export function createRequestLoggingMiddleware(
|
|
44
|
+
options: RequestLoggingOptions = {},
|
|
45
|
+
) {
|
|
46
|
+
const logger =
|
|
47
|
+
options.logger ||
|
|
48
|
+
// Avoid forcing the consumer to initialize logging before use
|
|
49
|
+
createLogger("http");
|
|
50
|
+
const skipPatterns = options.skipPaths || defaultSkips;
|
|
51
|
+
const logPayload = options.logPayload ?? false;
|
|
52
|
+
const maxPayloadLength = options.maxPayloadLength ?? 2000;
|
|
53
|
+
|
|
54
|
+
return function requestLogging(
|
|
55
|
+
req: Request,
|
|
56
|
+
res: Response,
|
|
57
|
+
next: NextFunction,
|
|
58
|
+
) {
|
|
59
|
+
const startedAt = process.hrtime.bigint();
|
|
60
|
+
const requestId = (req as any).requestId || randomUUID();
|
|
61
|
+
(req as any).requestId = requestId;
|
|
62
|
+
res.locals.requestId = requestId;
|
|
63
|
+
res.setHeader("x-request-id", requestId);
|
|
64
|
+
|
|
65
|
+
res.on("finish", () => {
|
|
66
|
+
if (shouldSkip(req.path, skipPatterns)) return;
|
|
67
|
+
|
|
68
|
+
const durationMs =
|
|
69
|
+
Number(process.hrtime.bigint() - startedAt) / 1_000_000;
|
|
70
|
+
|
|
71
|
+
// Prefer the richer logRequest helper if available
|
|
72
|
+
if (typeof logger.logRequest === "function") {
|
|
73
|
+
logger.logRequest(req, res, Number(durationMs.toFixed(2)));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
logger.info("Request completed", {
|
|
78
|
+
method: req.method,
|
|
79
|
+
path: req.originalUrl || req.url,
|
|
80
|
+
status: res.statusCode,
|
|
81
|
+
durationMs: Number(durationMs.toFixed(2)),
|
|
82
|
+
requestId,
|
|
83
|
+
userAgent: req.get("user-agent"),
|
|
84
|
+
payload: logPayload
|
|
85
|
+
? safePayload(req.body, maxPayloadLength)
|
|
86
|
+
: undefined,
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
next();
|
|
91
|
+
};
|
|
92
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Configuration Middleware
|
|
3
|
+
* Standardized session management for Express applications
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import session from "express-session";
|
|
7
|
+
import { Express } from "express";
|
|
8
|
+
import { createLogger } from "../core/index.js";
|
|
9
|
+
|
|
10
|
+
let logger: any; // Will be initialized when needed
|
|
11
|
+
|
|
12
|
+
function ensureLogger() {
|
|
13
|
+
if (!logger) {
|
|
14
|
+
logger = createLogger("SessionMiddleware");
|
|
15
|
+
}
|
|
16
|
+
return logger;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface SessionConfig {
|
|
20
|
+
secret?: string;
|
|
21
|
+
name?: string;
|
|
22
|
+
maxAge?: number;
|
|
23
|
+
secure?: boolean;
|
|
24
|
+
sameSite?: "lax" | "strict" | "none";
|
|
25
|
+
trustProxy?: boolean;
|
|
26
|
+
resave?: boolean;
|
|
27
|
+
saveUninitialized?: boolean;
|
|
28
|
+
rolling?: boolean;
|
|
29
|
+
store?: "memory" | session.Store;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Configure session middleware with sensible defaults
|
|
34
|
+
*/
|
|
35
|
+
export function configureSession(app: Express, config: SessionConfig = {}) {
|
|
36
|
+
const {
|
|
37
|
+
secret = process.env.SESSION_SECRET || "change-this-secret-in-production",
|
|
38
|
+
name = "app.sid",
|
|
39
|
+
maxAge = 3600000, // 1 hour
|
|
40
|
+
secure = process.env.NODE_ENV === "production",
|
|
41
|
+
sameSite = "lax",
|
|
42
|
+
trustProxy = true,
|
|
43
|
+
resave = false,
|
|
44
|
+
saveUninitialized = false,
|
|
45
|
+
rolling = true,
|
|
46
|
+
store = "memory",
|
|
47
|
+
} = config;
|
|
48
|
+
|
|
49
|
+
// Warn about insecure settings in production
|
|
50
|
+
if (process.env.NODE_ENV === "production") {
|
|
51
|
+
if (secret === "change-this-secret-in-production") {
|
|
52
|
+
ensureLogger().warn(
|
|
53
|
+
"Using default session secret in production! Set SESSION_SECRET environment variable.",
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
if (store === "memory") {
|
|
57
|
+
ensureLogger().warn(
|
|
58
|
+
"Using memory session store in production! Consider using Redis or another persistent store.",
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
if (!secure) {
|
|
62
|
+
ensureLogger().warn(
|
|
63
|
+
"Session cookies are not secure in production! Consider enabling secure cookies.",
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Trust proxy if configured
|
|
69
|
+
if (trustProxy) {
|
|
70
|
+
app.set("trust proxy", 1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Suppress MemoryStore warning in production
|
|
74
|
+
let originalWarn: typeof console.warn | undefined;
|
|
75
|
+
if (process.env.NODE_ENV === "production" && store === "memory") {
|
|
76
|
+
originalWarn = console.warn;
|
|
77
|
+
console.warn = (msg: string, ...args: any[]) => {
|
|
78
|
+
if (typeof msg === "string" && !msg.includes("MemoryStore")) {
|
|
79
|
+
originalWarn!(msg, ...args);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const sessionConfig: session.SessionOptions = {
|
|
85
|
+
secret,
|
|
86
|
+
name,
|
|
87
|
+
resave,
|
|
88
|
+
saveUninitialized,
|
|
89
|
+
rolling,
|
|
90
|
+
cookie: {
|
|
91
|
+
secure,
|
|
92
|
+
httpOnly: true,
|
|
93
|
+
maxAge,
|
|
94
|
+
sameSite,
|
|
95
|
+
path: "/",
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// Add store if provided
|
|
100
|
+
if (store !== "memory") {
|
|
101
|
+
sessionConfig.store = store;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Apply middleware
|
|
105
|
+
app.use(session(sessionConfig));
|
|
106
|
+
|
|
107
|
+
// Restore console.warn if it was modified
|
|
108
|
+
if (originalWarn) {
|
|
109
|
+
// Restore after a brief delay to ensure session is initialized
|
|
110
|
+
setTimeout(() => {
|
|
111
|
+
console.warn = originalWarn!;
|
|
112
|
+
}, 100);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (
|
|
116
|
+
process.env.NODE_ENV === "development" &&
|
|
117
|
+
process.env.LOG_LEVEL?.toLowerCase() === "debug"
|
|
118
|
+
) {
|
|
119
|
+
ensureLogger().debug("Session middleware configured", {
|
|
120
|
+
name,
|
|
121
|
+
secure,
|
|
122
|
+
sameSite,
|
|
123
|
+
maxAge: `${maxAge / 1000}s`,
|
|
124
|
+
store: store === "memory" ? "memory" : "custom",
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Create a Redis session store (OPTIONAL)
|
|
131
|
+
*
|
|
132
|
+
* Redis is an optional dependency for persistent session storage in production.
|
|
133
|
+
* If Redis packages are not installed, the framework gracefully falls back to memory store.
|
|
134
|
+
*
|
|
135
|
+
* To enable Redis sessions:
|
|
136
|
+
* 1. Install optional dependencies: `npm install connect-redis redis`
|
|
137
|
+
* 2. Set REDIS_URL environment variable (optional, defaults to redis://localhost:6379)
|
|
138
|
+
* 3. Use createRedisStore() in your session configuration
|
|
139
|
+
*
|
|
140
|
+
* For desktop applications with single users, memory store is usually sufficient.
|
|
141
|
+
*
|
|
142
|
+
* @param redisUrl - Optional Redis connection URL (defaults to REDIS_URL env var or redis://localhost:6379)
|
|
143
|
+
* @returns Redis store instance or 'memory' string on failure
|
|
144
|
+
*/
|
|
145
|
+
export async function createRedisStore(redisUrl?: string) {
|
|
146
|
+
try {
|
|
147
|
+
// @ts-expect-error - Optional dependency
|
|
148
|
+
const RedisStore = (await import(/* @vite-ignore */ "connect-redis"))
|
|
149
|
+
.default;
|
|
150
|
+
// @ts-expect-error - Optional dependency
|
|
151
|
+
const { createClient } = await import(/* @vite-ignore */ "redis");
|
|
152
|
+
|
|
153
|
+
const redisClient = createClient({
|
|
154
|
+
url: redisUrl || process.env.REDIS_URL || "redis://localhost:6379",
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
await redisClient.connect();
|
|
158
|
+
|
|
159
|
+
ensureLogger().info("Redis session store connected");
|
|
160
|
+
|
|
161
|
+
return new RedisStore({
|
|
162
|
+
client: redisClient,
|
|
163
|
+
prefix: "session:",
|
|
164
|
+
});
|
|
165
|
+
} catch (_error: any) {
|
|
166
|
+
ensureLogger().error("Failed to create Redis store:", _error.message);
|
|
167
|
+
ensureLogger().warn("Falling back to memory store");
|
|
168
|
+
return "memory" as const;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Session utilities
|
|
174
|
+
*/
|
|
175
|
+
export const sessionUtils = {
|
|
176
|
+
/**
|
|
177
|
+
* Regenerate session ID (for security after login)
|
|
178
|
+
*/
|
|
179
|
+
regenerateSession(req: any): Promise<void> {
|
|
180
|
+
return new Promise((resolve, reject) => {
|
|
181
|
+
req.session.regenerate((err: any) => {
|
|
182
|
+
if (err) {
|
|
183
|
+
ensureLogger().error("Failed to regenerate session:", err);
|
|
184
|
+
reject(err);
|
|
185
|
+
} else {
|
|
186
|
+
resolve();
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Save session explicitly
|
|
194
|
+
*/
|
|
195
|
+
saveSession(req: any): Promise<void> {
|
|
196
|
+
return new Promise((resolve, reject) => {
|
|
197
|
+
req.session.save((err: any) => {
|
|
198
|
+
if (err) {
|
|
199
|
+
ensureLogger().error("Failed to save session:", err);
|
|
200
|
+
reject(err);
|
|
201
|
+
} else {
|
|
202
|
+
resolve();
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Destroy session
|
|
210
|
+
*/
|
|
211
|
+
destroySession(req: any): Promise<void> {
|
|
212
|
+
return new Promise((resolve, reject) => {
|
|
213
|
+
req.session.destroy((err: any) => {
|
|
214
|
+
if (err) {
|
|
215
|
+
ensureLogger().error("Failed to destroy session:", err);
|
|
216
|
+
reject(err);
|
|
217
|
+
} else {
|
|
218
|
+
resolve();
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Touch session to extend expiry
|
|
226
|
+
*/
|
|
227
|
+
touchSession(req: any): void {
|
|
228
|
+
if (req.session) {
|
|
229
|
+
req.session.touch();
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
export default {
|
|
235
|
+
configureSession,
|
|
236
|
+
createRedisStore,
|
|
237
|
+
sessionUtils,
|
|
238
|
+
};
|