@intranefr/superbackend 1.5.0 → 1.5.2

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 (198) hide show
  1. package/.env.example +15 -0
  2. package/README.md +11 -0
  3. package/analysis-only.skill +0 -0
  4. package/index.js +23 -0
  5. package/package.json +8 -2
  6. package/src/admin/endpointRegistry.js +120 -0
  7. package/src/controllers/admin.controller.js +90 -6
  8. package/src/controllers/adminBlockDefinitions.controller.js +127 -0
  9. package/src/controllers/adminBlockDefinitionsAi.controller.js +54 -0
  10. package/src/controllers/adminCache.controller.js +342 -0
  11. package/src/controllers/adminContextBlockDefinitions.controller.js +141 -0
  12. package/src/controllers/adminCrons.controller.js +388 -0
  13. package/src/controllers/adminDbBrowser.controller.js +124 -0
  14. package/src/controllers/adminEjsVirtual.controller.js +13 -3
  15. package/src/controllers/adminExperiments.controller.js +200 -0
  16. package/src/controllers/adminHeadless.controller.js +9 -2
  17. package/src/controllers/adminHealthChecks.controller.js +570 -0
  18. package/src/controllers/adminI18n.controller.js +51 -29
  19. package/src/controllers/adminLlm.controller.js +126 -2
  20. package/src/controllers/adminPages.controller.js +720 -0
  21. package/src/controllers/adminPagesContextBlocksAi.controller.js +54 -0
  22. package/src/controllers/adminProxy.controller.js +113 -0
  23. package/src/controllers/adminRateLimits.controller.js +138 -0
  24. package/src/controllers/adminRbac.controller.js +803 -0
  25. package/src/controllers/adminScripts.controller.js +126 -4
  26. package/src/controllers/adminSeoConfig.controller.js +71 -48
  27. package/src/controllers/blogAdmin.controller.js +279 -0
  28. package/src/controllers/blogAiAdmin.controller.js +224 -0
  29. package/src/controllers/blogAutomationAdmin.controller.js +141 -0
  30. package/src/controllers/blogInternal.controller.js +26 -0
  31. package/src/controllers/blogPublic.controller.js +89 -0
  32. package/src/controllers/experiments.controller.js +85 -0
  33. package/src/controllers/fileManager.controller.js +190 -0
  34. package/src/controllers/fileManagerStoragePolicy.controller.js +23 -0
  35. package/src/controllers/healthChecksPublic.controller.js +196 -0
  36. package/src/controllers/internalExperiments.controller.js +17 -0
  37. package/src/controllers/metrics.controller.js +64 -4
  38. package/src/controllers/orgAdmin.controller.js +80 -0
  39. package/src/helpers/mongooseHelper.js +258 -0
  40. package/src/helpers/scriptBase.js +230 -0
  41. package/src/helpers/scriptRunner.js +335 -0
  42. package/src/middleware/rbac.js +62 -0
  43. package/src/middleware.js +810 -48
  44. package/src/models/BlockDefinition.js +27 -0
  45. package/src/models/BlogAutomationLock.js +14 -0
  46. package/src/models/BlogAutomationRun.js +39 -0
  47. package/src/models/BlogPost.js +42 -0
  48. package/src/models/CacheEntry.js +26 -0
  49. package/src/models/ConsoleEntry.js +32 -0
  50. package/src/models/ConsoleLog.js +23 -0
  51. package/src/models/ContextBlockDefinition.js +33 -0
  52. package/src/models/CronExecution.js +47 -0
  53. package/src/models/CronJob.js +70 -0
  54. package/src/models/Experiment.js +75 -0
  55. package/src/models/ExperimentAssignment.js +23 -0
  56. package/src/models/ExperimentEvent.js +26 -0
  57. package/src/models/ExperimentMetricBucket.js +30 -0
  58. package/src/models/ExternalDbConnection.js +49 -0
  59. package/src/models/FileEntry.js +22 -0
  60. package/src/models/GlobalSetting.js +1 -2
  61. package/src/models/HealthAutoHealAttempt.js +57 -0
  62. package/src/models/HealthCheck.js +132 -0
  63. package/src/models/HealthCheckRun.js +51 -0
  64. package/src/models/HealthIncident.js +49 -0
  65. package/src/models/Page.js +95 -0
  66. package/src/models/PageCollection.js +42 -0
  67. package/src/models/ProxyEntry.js +66 -0
  68. package/src/models/RateLimitCounter.js +19 -0
  69. package/src/models/RateLimitMetricBucket.js +20 -0
  70. package/src/models/RbacGrant.js +25 -0
  71. package/src/models/RbacGroup.js +16 -0
  72. package/src/models/RbacGroupMember.js +13 -0
  73. package/src/models/RbacGroupRole.js +13 -0
  74. package/src/models/RbacRole.js +25 -0
  75. package/src/models/RbacUserRole.js +13 -0
  76. package/src/models/ScriptDefinition.js +1 -0
  77. package/src/models/Webhook.js +2 -0
  78. package/src/routes/admin.routes.js +2 -0
  79. package/src/routes/adminBlog.routes.js +21 -0
  80. package/src/routes/adminBlogAi.routes.js +16 -0
  81. package/src/routes/adminBlogAutomation.routes.js +27 -0
  82. package/src/routes/adminCache.routes.js +20 -0
  83. package/src/routes/adminConsoleManager.routes.js +302 -0
  84. package/src/routes/adminCrons.routes.js +25 -0
  85. package/src/routes/adminDbBrowser.routes.js +65 -0
  86. package/src/routes/adminEjsVirtual.routes.js +2 -1
  87. package/src/routes/adminExperiments.routes.js +29 -0
  88. package/src/routes/adminHeadless.routes.js +2 -1
  89. package/src/routes/adminHealthChecks.routes.js +28 -0
  90. package/src/routes/adminI18n.routes.js +4 -3
  91. package/src/routes/adminLlm.routes.js +4 -2
  92. package/src/routes/adminPages.routes.js +55 -0
  93. package/src/routes/adminProxy.routes.js +15 -0
  94. package/src/routes/adminRateLimits.routes.js +17 -0
  95. package/src/routes/adminRbac.routes.js +38 -0
  96. package/src/routes/adminSeoConfig.routes.js +5 -4
  97. package/src/routes/adminUiComponents.routes.js +2 -1
  98. package/src/routes/blogInternal.routes.js +14 -0
  99. package/src/routes/blogPublic.routes.js +9 -0
  100. package/src/routes/experiments.routes.js +30 -0
  101. package/src/routes/fileManager.routes.js +62 -0
  102. package/src/routes/fileManagerStoragePolicy.routes.js +9 -0
  103. package/src/routes/healthChecksPublic.routes.js +9 -0
  104. package/src/routes/internalExperiments.routes.js +15 -0
  105. package/src/routes/log.routes.js +43 -60
  106. package/src/routes/metrics.routes.js +4 -2
  107. package/src/routes/orgAdmin.routes.js +1 -0
  108. package/src/routes/pages.routes.js +123 -0
  109. package/src/routes/proxy.routes.js +46 -0
  110. package/src/routes/rbac.routes.js +47 -0
  111. package/src/routes/webhook.routes.js +2 -1
  112. package/src/routes/workflows.routes.js +4 -0
  113. package/src/services/blockDefinitionsAi.service.js +247 -0
  114. package/src/services/blog.service.js +99 -0
  115. package/src/services/blogAutomation.service.js +978 -0
  116. package/src/services/blogCronsBootstrap.service.js +185 -0
  117. package/src/services/blogPublishing.service.js +58 -0
  118. package/src/services/cacheLayer.service.js +696 -0
  119. package/src/services/consoleManager.service.js +738 -0
  120. package/src/services/consoleOverride.service.js +7 -1
  121. package/src/services/cronScheduler.service.js +350 -0
  122. package/src/services/dbBrowser.service.js +536 -0
  123. package/src/services/ejsVirtual.service.js +102 -32
  124. package/src/services/experiments.service.js +273 -0
  125. package/src/services/experimentsAggregation.service.js +308 -0
  126. package/src/services/experimentsCronsBootstrap.service.js +118 -0
  127. package/src/services/experimentsRetention.service.js +43 -0
  128. package/src/services/experimentsWs.service.js +134 -0
  129. package/src/services/fileManager.service.js +475 -0
  130. package/src/services/fileManagerStoragePolicy.service.js +285 -0
  131. package/src/services/globalSettings.service.js +15 -0
  132. package/src/services/healthChecks.service.js +650 -0
  133. package/src/services/healthChecksBootstrap.service.js +109 -0
  134. package/src/services/healthChecksScheduler.service.js +106 -0
  135. package/src/services/jsonConfigs.service.js +2 -2
  136. package/src/services/llmDefaults.service.js +190 -0
  137. package/src/services/migrationAssets/s3.js +2 -2
  138. package/src/services/pages.service.js +602 -0
  139. package/src/services/pagesContext.service.js +331 -0
  140. package/src/services/pagesContextBlocksAi.service.js +349 -0
  141. package/src/services/proxy.service.js +535 -0
  142. package/src/services/rateLimiter.service.js +623 -0
  143. package/src/services/rbac.service.js +212 -0
  144. package/src/services/scriptsRunner.service.js +215 -15
  145. package/src/services/uiComponentsAi.service.js +6 -19
  146. package/src/services/workflow.service.js +23 -8
  147. package/src/utils/orgRoles.js +14 -0
  148. package/src/utils/rbac/engine.js +60 -0
  149. package/src/utils/rbac/rightsRegistry.js +33 -0
  150. package/views/admin-blog-automation.ejs +877 -0
  151. package/views/admin-blog-edit.ejs +542 -0
  152. package/views/admin-blog.ejs +399 -0
  153. package/views/admin-cache.ejs +681 -0
  154. package/views/admin-console-manager.ejs +680 -0
  155. package/views/admin-crons.ejs +645 -0
  156. package/views/admin-dashboard.ejs +28 -8
  157. package/views/admin-db-browser.ejs +445 -0
  158. package/views/admin-ejs-virtual.ejs +16 -10
  159. package/views/admin-experiments.ejs +91 -0
  160. package/views/admin-file-manager.ejs +942 -0
  161. package/views/admin-health-checks.ejs +725 -0
  162. package/views/admin-i18n.ejs +59 -5
  163. package/views/admin-llm.ejs +99 -1
  164. package/views/admin-organizations.ejs +163 -1
  165. package/views/admin-pages.ejs +2424 -0
  166. package/views/admin-proxy.ejs +491 -0
  167. package/views/admin-rate-limiter.ejs +625 -0
  168. package/views/admin-rbac.ejs +1331 -0
  169. package/views/admin-scripts.ejs +597 -3
  170. package/views/admin-seo-config.ejs +61 -7
  171. package/views/admin-ui-components.ejs +57 -25
  172. package/views/admin-workflows.ejs +7 -7
  173. package/views/file-manager.ejs +866 -0
  174. package/views/pages/blocks/contact.ejs +27 -0
  175. package/views/pages/blocks/cta.ejs +18 -0
  176. package/views/pages/blocks/faq.ejs +20 -0
  177. package/views/pages/blocks/features.ejs +19 -0
  178. package/views/pages/blocks/hero.ejs +13 -0
  179. package/views/pages/blocks/html.ejs +5 -0
  180. package/views/pages/blocks/image.ejs +14 -0
  181. package/views/pages/blocks/testimonials.ejs +26 -0
  182. package/views/pages/blocks/text.ejs +10 -0
  183. package/views/pages/layouts/default.ejs +51 -0
  184. package/views/pages/layouts/minimal.ejs +42 -0
  185. package/views/pages/layouts/sidebar.ejs +54 -0
  186. package/views/pages/partials/footer.ejs +13 -0
  187. package/views/pages/partials/header.ejs +12 -0
  188. package/views/pages/partials/sidebar.ejs +8 -0
  189. package/views/pages/runtime/page.ejs +10 -0
  190. package/views/pages/templates/article.ejs +20 -0
  191. package/views/pages/templates/default.ejs +12 -0
  192. package/views/pages/templates/landing.ejs +14 -0
  193. package/views/pages/templates/listing.ejs +15 -0
  194. package/views/partials/admin-image-upload-modal.ejs +221 -0
  195. package/views/partials/dashboard/nav-items.ejs +12 -0
  196. package/views/partials/dashboard/palette.ejs +5 -3
  197. package/views/partials/llm-provider-model-picker.ejs +183 -0
  198. package/src/routes/llmUi.routes.js +0 -26
package/src/middleware.js CHANGED
@@ -1,3 +1,22 @@
1
+ const consoleOverride = require("./services/consoleOverride.service");
2
+ const { consoleManager } = require("./services/consoleManager.service");
3
+
4
+ // Initialize console override service early to capture all logs
5
+ // Avoid keeping timers/streams alive during Jest runs.
6
+ if (process.env.NODE_ENV !== "test" && !process.env.JEST_WORKER_ID) {
7
+ consoleOverride.init()
8
+
9
+ // Initialize console manager after a short delay to ensure consoleOverride is fully set up
10
+ setTimeout(() => {
11
+ // Set module prefix for this middleware
12
+ consoleManager.setModulePrefix('middleware');
13
+
14
+ // Initialize console manager early to enable prefixing for all subsequent logs
15
+ consoleManager.init();
16
+ console.log("[Console Manager] Initialized - prefixing enabled");
17
+ }, 20);
18
+ }
19
+
1
20
  const express = require("express");
2
21
  const path = require("path");
3
22
  const mongoose = require("mongoose");
@@ -6,17 +25,54 @@ const fs = require("fs");
6
25
  const ejs = require("ejs");
7
26
  const { basicAuth } = require("./middleware/auth");
8
27
  const endpointRegistry = require("./admin/endpointRegistry");
9
- const { createFeatureFlagsEjsMiddleware } = require("./services/featureFlags.service");
10
- const consoleOverride = require("./services/consoleOverride.service");
28
+ const {
29
+ createFeatureFlagsEjsMiddleware,
30
+ } = require("./services/featureFlags.service");
31
+ const globalSettingsService = require("./services/globalSettings.service");
32
+ const cronScheduler = require("./services/cronScheduler.service");
33
+ const healthChecksScheduler = require("./services/healthChecksScheduler.service");
34
+ const healthChecksBootstrap = require("./services/healthChecksBootstrap.service");
35
+ const blogCronsBootstrap = require("./services/blogCronsBootstrap.service");
11
36
  const {
12
37
  hookConsoleError,
13
38
  setupProcessHandlers,
14
39
  expressErrorMiddleware,
15
40
  requestIdMiddleware,
16
41
  } = require("./middleware/errorCapture");
42
+ const rateLimiter = require("./services/rateLimiter.service");
17
43
 
18
44
  let errorCaptureInitialized = false;
19
45
 
46
+ /**
47
+ * Check if console manager should be enabled based on environment variable and global settings
48
+ * Priority: Environment Variable > Global Settings > Default (true)
49
+ * @returns {Promise<boolean>} Whether console manager should be enabled
50
+ */
51
+ async function isConsoleManagerEnabled() {
52
+ // Environment variable takes highest priority
53
+ const envEnabled = process.env.CONSOLE_MANAGER_ENABLED;
54
+ if (envEnabled !== undefined) {
55
+ const enabled = String(envEnabled).toLowerCase() !== 'false';
56
+ console.log(`[Console Manager] Environment variable CONSOLE_MANAGER_ENABLED=${envEnabled}, ${enabled ? 'enabled' : 'disabled'}`);
57
+ return enabled;
58
+ }
59
+
60
+ // Check global settings if environment variable not set
61
+ try {
62
+ const enabledRaw = await globalSettingsService.getSettingValue(
63
+ "CONSOLE_MANAGER_ENABLED",
64
+ "true"
65
+ );
66
+ const enabled = String(enabledRaw) === "true";
67
+ console.log(`[Console Manager] Global setting CONSOLE_MANAGER_ENABLED=${enabledRaw}, ${enabled ? 'enabled' : 'disabled'}`);
68
+ return enabled;
69
+ } catch (error) {
70
+ console.error("[Console Manager] Error loading global setting:", error);
71
+ console.log("[Console Manager] Fallback to enabled due to error");
72
+ return true; // Fallback to enabled on error
73
+ }
74
+ }
75
+
20
76
  /**
21
77
  * Creates and configures the SaaS backend middleware
22
78
  * @param {Object} options - Configuration options
@@ -30,16 +86,55 @@ let errorCaptureInitialized = false;
30
86
  function createMiddleware(options = {}) {
31
87
  const router = express.Router();
32
88
  const adminPath = options.adminPath || "/admin";
89
+ const pagesPrefix = options.pagesPrefix || "/";
90
+
91
+ const normalizeBasePath = (value) => {
92
+ const v = String(value || "").trim();
93
+ if (!v) return "/files";
94
+ return v.startsWith("/") ? v : `/${v}`;
95
+ };
96
+
97
+ const fileManagerPublicConfig = {
98
+ enabled: false,
99
+ basePath: "/files",
100
+ loaded: false,
101
+ };
102
+
103
+ // Restart-required behavior: we load settings once and keep the values in memory.
104
+ (async () => {
105
+ try {
106
+ const enabledRaw = await globalSettingsService.getSettingValue(
107
+ "FILE_MANAGER_ENABLED",
108
+ "false",
109
+ );
110
+ const basePathRaw = await globalSettingsService.getSettingValue(
111
+ "FILE_MANAGER_BASE_PATH",
112
+ "/files",
113
+ );
114
+
115
+ fileManagerPublicConfig.enabled = String(enabledRaw) === "true";
116
+ fileManagerPublicConfig.basePath = normalizeBasePath(basePathRaw);
117
+ fileManagerPublicConfig.loaded = true;
118
+ } catch (error) {
119
+ console.error("Error loading File Manager public config:", error);
120
+ fileManagerPublicConfig.loaded = true;
121
+ }
122
+ })();
33
123
 
34
- // Expose adminPath and WS attachment helper
124
+ // Expose adminPath, pagesPrefix and WS attachment helper
35
125
  router.adminPath = adminPath;
126
+ router.pagesPrefix = pagesPrefix;
36
127
  router.attachWs = (server) => {
37
- const { attachTerminalWebsocketServer } = require('./services/terminalsWs.service');
128
+ const {
129
+ attachTerminalWebsocketServer,
130
+ } = require("./services/terminalsWs.service");
38
131
  attachTerminalWebsocketServer(server, { basePathPrefix: adminPath });
39
- };
40
132
 
41
- // Initialize console override service early to capture all logs
42
- consoleOverride.init();
133
+ const {
134
+ attachExperimentsWebsocketServer,
135
+ } = require("./services/experimentsWs.service");
136
+ attachExperimentsWebsocketServer(server);
137
+ };
43
138
 
44
139
  if (!errorCaptureInitialized) {
45
140
  errorCaptureInitialized = true;
@@ -47,9 +142,14 @@ function createMiddleware(options = {}) {
47
142
  setupProcessHandlers();
48
143
  }
49
144
 
145
+ // Console manager will be initialized after database connection
146
+
50
147
  // Database connection
51
148
  const mongoUri =
52
- options.mongodbUri || options.dbConnection || process.env.MONGODB_URI || process.env.MONGO_URI;
149
+ options.mongodbUri ||
150
+ options.dbConnection ||
151
+ process.env.MONGODB_URI ||
152
+ process.env.MONGO_URI;
53
153
 
54
154
  if (!mongoUri && mongoose.connection.readyState !== 1) {
55
155
  console.warn(
@@ -60,23 +160,135 @@ function createMiddleware(options = {}) {
60
160
  serverSelectionTimeoutMS: 5000,
61
161
  maxPoolSize: 10,
62
162
  };
63
-
163
+
64
164
  // Return a promise that resolves when connection is established
65
165
  const connectionPromise = mongoose
66
166
  .connect(mongoUri, connectionOptions)
67
- .then(() => {
167
+ .then(async () => {
68
168
  console.log("✅ Middleware: Connected to MongoDB");
169
+ // Start cron scheduler after DB connection
170
+ await cronScheduler.start();
171
+ await healthChecksScheduler.start();
172
+ await healthChecksBootstrap.bootstrap();
173
+ await blogCronsBootstrap.bootstrap();
174
+ await require("./services/experimentsCronsBootstrap.service").bootstrap();
175
+
176
+ // Console manager is already initialized early in the middleware
177
+ console.log("[Console Manager] MongoDB connection established");
178
+
69
179
  return true;
70
180
  })
71
181
  .catch((err) => {
72
182
  console.error("❌ Middleware: MongoDB connection error:", err);
73
183
  return false;
74
184
  });
75
-
185
+
186
+ router.get(`${adminPath}/health-checks`, basicAuth, (req, res) => {
187
+ const templatePath = path.join(
188
+ __dirname,
189
+ "..",
190
+ "views",
191
+ "admin-health-checks.ejs",
192
+ );
193
+ fs.readFile(templatePath, "utf8", (err, template) => {
194
+ if (err) {
195
+ console.error("Error reading template:", err);
196
+ return res.status(500).send("Error loading page");
197
+ }
198
+ try {
199
+ const html = ejs.render(
200
+ template,
201
+ {
202
+ baseUrl: req.baseUrl,
203
+ adminPath,
204
+ },
205
+ {
206
+ filename: templatePath,
207
+ },
208
+ );
209
+ res.send(html);
210
+ } catch (renderErr) {
211
+ console.error("Error rendering template:", renderErr);
212
+ res.status(500).send("Error rendering page");
213
+ }
214
+ });
215
+ });
216
+
217
+ router.get(`${adminPath}/console-manager`, basicAuth, (req, res) => {
218
+ const templatePath = path.join(
219
+ __dirname,
220
+ "..",
221
+ "views",
222
+ "admin-console-manager.ejs",
223
+ );
224
+ fs.readFile(templatePath, "utf8", (err, template) => {
225
+ if (err) {
226
+ console.error("Error reading template:", err);
227
+ return res.status(500).send("Error loading page");
228
+ }
229
+ try {
230
+ const html = ejs.render(
231
+ template,
232
+ {
233
+ baseUrl: req.baseUrl,
234
+ adminPath,
235
+ },
236
+ {
237
+ filename: templatePath,
238
+ },
239
+ );
240
+ res.send(html);
241
+ } catch (renderErr) {
242
+ console.error("Error rendering template:", renderErr);
243
+ res.status(500).send("Error rendering page");
244
+ }
245
+ });
246
+ });
247
+
76
248
  // Store the promise so it can be awaited if needed
77
249
  router.connectionPromise = connectionPromise;
78
250
  } else if (mongoose.connection.readyState === 1) {
79
251
  console.log("✅ Middleware: Using existing MongoDB connection");
252
+ // Start cron scheduler for existing connection
253
+ cronScheduler.start().catch((err) => {
254
+ console.error("Failed to start cron scheduler:", err);
255
+ });
256
+ healthChecksScheduler.start().catch((err) => {
257
+ console.error("Failed to start health checks scheduler:", err);
258
+ });
259
+ healthChecksBootstrap.bootstrap().catch((err) => {
260
+ console.error("Failed to bootstrap health checks:", err);
261
+ });
262
+ blogCronsBootstrap.bootstrap().catch((err) => {
263
+ console.error("Failed to bootstrap blog crons:", err);
264
+ });
265
+
266
+ require("./services/experimentsCronsBootstrap.service")
267
+ .bootstrap()
268
+ .catch((err) => {
269
+ console.error("Failed to bootstrap experiments crons:", err);
270
+ });
271
+
272
+ // Initialize console manager AFTER database is already connected
273
+ if (process.env.NODE_ENV !== "test" && !process.env.JEST_WORKER_ID) {
274
+ isConsoleManagerEnabled().then(consoleManagerEnabled => {
275
+ if (consoleManagerEnabled) {
276
+ consoleManager.init();
277
+ // Set module prefix after initialization
278
+ consoleManager.setModulePrefix('middleware');
279
+ console.log("[Console Manager] Initialized");
280
+ } else {
281
+ console.log("[Console Manager] Disabled - console methods not overridden");
282
+ }
283
+ }).catch(error => {
284
+ console.error("[Console Manager] Error checking enabled status:", error);
285
+ console.log("[Console Manager] Fallback to enabled due to error");
286
+ consoleManager.init();
287
+ // Set module prefix after initialization
288
+ consoleManager.setModulePrefix('middleware');
289
+ console.log("[Console Manager] Initialized (fallback)");
290
+ });
291
+ }
80
292
  }
81
293
 
82
294
  // CORS configuration
@@ -138,6 +350,12 @@ function createMiddleware(options = {}) {
138
350
  webhookHandler,
139
351
  );
140
352
 
353
+ router.use(
354
+ "/proxy",
355
+ express.raw({ type: "*/*", limit: "10mb" }),
356
+ require("./routes/proxy.routes"),
357
+ );
358
+
141
359
  // Regular JSON parsing for other routes (skip if parent app already handles it)
142
360
  if (!options.skipBodyParser) {
143
361
  router.use(express.json());
@@ -146,6 +364,8 @@ function createMiddleware(options = {}) {
146
364
 
147
365
  router.use(requestIdMiddleware);
148
366
 
367
+ router.use("/api", rateLimiter.limit("globalApiLimiter"));
368
+
149
369
  // Serve public static files (e.g. /og/og-default.png)
150
370
  router.use(express.static(path.join(__dirname, "..", "public")));
151
371
 
@@ -164,6 +384,53 @@ function createMiddleware(options = {}) {
164
384
  // EJS locals: feature flags for server-rendered pages
165
385
  router.use(createFeatureFlagsEjsMiddleware());
166
386
 
387
+ // Public File Manager SPA (gated by global settings; restart required)
388
+ router.get("*", (req, res, next) => {
389
+ try {
390
+ if (!fileManagerPublicConfig.enabled) return next();
391
+
392
+ const basePath = fileManagerPublicConfig.basePath || "/files";
393
+ const reqPath = req.path;
394
+ const matches =
395
+ reqPath === basePath ||
396
+ reqPath === `${basePath}/` ||
397
+ reqPath.startsWith(`${basePath}/`);
398
+
399
+ if (!matches) return next();
400
+ if (req.method !== "GET") return next();
401
+
402
+ const templatePath = path.join(
403
+ __dirname,
404
+ "..",
405
+ "views",
406
+ "file-manager.ejs",
407
+ );
408
+ fs.readFile(templatePath, "utf8", (err, template) => {
409
+ if (err) {
410
+ console.error("Error reading template:", err);
411
+ return res.status(500).send("Error loading page");
412
+ }
413
+ try {
414
+ const html = ejs.render(
415
+ template,
416
+ {
417
+ baseUrl: req.baseUrl,
418
+ fileManagerBasePath: basePath,
419
+ },
420
+ { filename: templatePath },
421
+ );
422
+ res.send(html);
423
+ } catch (renderErr) {
424
+ console.error("Error rendering template:", renderErr);
425
+ res.status(500).send("Error rendering page");
426
+ }
427
+ });
428
+ } catch (error) {
429
+ console.error("Error serving File Manager SPA:", error);
430
+ next();
431
+ }
432
+ });
433
+
167
434
  // API Routes
168
435
  router.use("/api/auth", require("./routes/auth.routes"));
169
436
  router.use("/api/billing", require("./routes/billing.routes"));
@@ -177,22 +444,96 @@ function createMiddleware(options = {}) {
177
444
  );
178
445
  router.use("/api/admin/orgs", require("./routes/orgAdmin.routes"));
179
446
  router.use("/api/admin/users", require("./routes/userAdmin.routes"));
180
- router.use("/api/admin/notifications", require("./routes/notificationAdmin.routes"));
447
+ router.use("/api/admin/rbac", require("./routes/adminRbac.routes"));
448
+ router.use(
449
+ "/api/admin/notifications",
450
+ require("./routes/notificationAdmin.routes"),
451
+ );
181
452
  router.use("/api/admin/stripe", require("./routes/stripeAdmin.routes"));
182
-
453
+
183
454
  // Stats Routes
184
455
  const adminStatsController = require("./controllers/adminStats.controller");
185
- router.get("/api/admin/stats/overview", basicAuth, adminStatsController.getOverviewStats);
186
-
456
+ router.get(
457
+ "/api/admin/stats/overview",
458
+ basicAuth,
459
+ adminStatsController.getOverviewStats,
460
+ );
461
+
187
462
  router.get(`${adminPath}/stats/dashboard-home`, basicAuth, (req, res) => {
188
- const templatePath = path.join(__dirname, "..", "views", "admin-dashboard-home.ejs");
463
+ const templatePath = path.join(
464
+ __dirname,
465
+ "..",
466
+ "views",
467
+ "admin-dashboard-home.ejs",
468
+ );
189
469
  fs.readFile(templatePath, "utf8", (err, template) => {
190
470
  if (err) {
191
471
  console.error("Error reading template:", err);
192
472
  return res.status(500).send("Error loading page");
193
473
  }
194
474
  try {
195
- const html = ejs.render(template, { baseUrl: req.baseUrl, adminPath }, { filename: templatePath });
475
+ const html = ejs.render(
476
+ template,
477
+ { baseUrl: req.baseUrl, adminPath },
478
+ { filename: templatePath },
479
+ );
480
+ res.send(html);
481
+ } catch (renderErr) {
482
+ console.error("Error rendering template:", renderErr);
483
+ res.status(500).send("Error rendering page");
484
+ }
485
+ });
486
+ });
487
+
488
+ router.get(`${adminPath}/experiments`, basicAuth, (req, res) => {
489
+ const templatePath = path.join(
490
+ __dirname,
491
+ "..",
492
+ "views",
493
+ "admin-experiments.ejs",
494
+ );
495
+ fs.readFile(templatePath, "utf8", (err, template) => {
496
+ if (err) {
497
+ console.error("Error reading template:", err);
498
+ return res.status(500).send("Error loading page");
499
+ }
500
+ try {
501
+ const html = ejs.render(
502
+ template,
503
+ {
504
+ baseUrl: req.baseUrl,
505
+ adminPath,
506
+ },
507
+ {
508
+ filename: templatePath,
509
+ },
510
+ );
511
+ res.send(html);
512
+ } catch (renderErr) {
513
+ console.error("Error rendering template:", renderErr);
514
+ res.status(500).send("Error rendering page");
515
+ }
516
+ });
517
+ });
518
+
519
+ router.get(`${adminPath}/rbac`, basicAuth, (req, res) => {
520
+ const templatePath = path.join(__dirname, "..", "views", "admin-rbac.ejs");
521
+ fs.readFile(templatePath, "utf8", (err, template) => {
522
+ if (err) {
523
+ console.error("Error reading template:", err);
524
+ return res.status(500).send("Error loading page");
525
+ }
526
+ try {
527
+ const html = ejs.render(
528
+ template,
529
+ {
530
+ baseUrl: req.baseUrl,
531
+ adminPath,
532
+ },
533
+ {
534
+ filename: templatePath,
535
+ },
536
+ );
196
537
  res.send(html);
197
538
  } catch (renderErr) {
198
539
  console.error("Error rendering template:", renderErr);
@@ -265,6 +606,89 @@ function createMiddleware(options = {}) {
265
606
  });
266
607
  });
267
608
 
609
+ router.get(`${adminPath}/crons`, basicAuth, (req, res) => {
610
+ const templatePath = path.join(__dirname, "..", "views", "admin-crons.ejs");
611
+ fs.readFile(templatePath, "utf8", (err, template) => {
612
+ if (err) {
613
+ console.error("Error reading template:", err);
614
+ return res.status(500).send("Error loading page");
615
+ }
616
+ try {
617
+ const html = ejs.render(
618
+ template,
619
+ {
620
+ baseUrl: req.baseUrl,
621
+ adminPath,
622
+ },
623
+ {
624
+ filename: templatePath,
625
+ },
626
+ );
627
+ res.send(html);
628
+ } catch (renderErr) {
629
+ console.error("Error rendering template:", renderErr);
630
+ res.status(500).send("Error rendering page");
631
+ }
632
+ });
633
+ });
634
+
635
+ router.get(`${adminPath}/cache`, basicAuth, (req, res) => {
636
+ const templatePath = path.join(__dirname, "..", "views", "admin-cache.ejs");
637
+ fs.readFile(templatePath, "utf8", (err, template) => {
638
+ if (err) {
639
+ console.error("Error reading template:", err);
640
+ return res.status(500).send("Error loading page");
641
+ }
642
+ try {
643
+ const html = ejs.render(
644
+ template,
645
+ {
646
+ baseUrl: req.baseUrl,
647
+ adminPath,
648
+ },
649
+ {
650
+ filename: templatePath,
651
+ },
652
+ );
653
+ res.send(html);
654
+ } catch (renderErr) {
655
+ console.error("Error rendering template:", renderErr);
656
+ res.status(500).send("Error rendering page");
657
+ }
658
+ });
659
+ });
660
+
661
+ router.get(`${adminPath}/db-browser`, basicAuth, (req, res) => {
662
+ const templatePath = path.join(
663
+ __dirname,
664
+ "..",
665
+ "views",
666
+ "admin-db-browser.ejs",
667
+ );
668
+ fs.readFile(templatePath, "utf8", (err, template) => {
669
+ if (err) {
670
+ console.error("Error reading template:", err);
671
+ return res.status(500).send("Error loading page");
672
+ }
673
+ try {
674
+ const html = ejs.render(
675
+ template,
676
+ {
677
+ baseUrl: req.baseUrl,
678
+ adminPath,
679
+ },
680
+ {
681
+ filename: templatePath,
682
+ },
683
+ );
684
+ res.send(html);
685
+ } catch (renderErr) {
686
+ console.error("Error rendering template:", renderErr);
687
+ res.status(500).send("Error rendering page");
688
+ }
689
+ });
690
+ });
691
+
268
692
  router.use("/api/admin", require("./routes/admin.routes"));
269
693
  router.use("/api/admin/settings", require("./routes/globalSettings.routes"));
270
694
  router.use(
@@ -275,6 +699,11 @@ function createMiddleware(options = {}) {
275
699
  "/api/admin/json-configs",
276
700
  require("./routes/adminJsonConfigs.routes"),
277
701
  );
702
+ router.use(
703
+ "/api/admin/rate-limits",
704
+ require("./routes/adminRateLimits.routes"),
705
+ );
706
+ router.use("/api/admin/proxy", require("./routes/adminProxy.routes"));
278
707
  router.use(
279
708
  "/api/admin/seo-config",
280
709
  require("./routes/adminSeoConfig.routes"),
@@ -282,19 +711,52 @@ function createMiddleware(options = {}) {
282
711
  router.use("/api/admin/i18n", require("./routes/adminI18n.routes"));
283
712
  router.use("/api/admin/headless", require("./routes/adminHeadless.routes"));
284
713
  router.use("/api/admin/scripts", require("./routes/adminScripts.routes"));
714
+ router.use("/api/admin/crons", require("./routes/adminCrons.routes"));
715
+ router.use(
716
+ "/api/admin/health-checks",
717
+ require("./routes/adminHealthChecks.routes"),
718
+ );
719
+ router.use("/api/admin/cache", require("./routes/adminCache.routes"));
720
+ router.use(
721
+ "/api/admin/console-manager",
722
+ require("./routes/adminConsoleManager.routes"),
723
+ );
724
+ router.use(
725
+ "/api/admin/db-browser",
726
+ require("./routes/adminDbBrowser.routes"),
727
+ );
285
728
  router.use("/api/admin/terminals", require("./routes/adminTerminals.routes"));
729
+ router.use("/api/admin/experiments", require("./routes/adminExperiments.routes"));
286
730
  router.use("/api/admin/assets", require("./routes/adminAssets.routes"));
287
731
  router.use(
288
732
  "/api/admin/upload-namespaces",
289
733
  require("./routes/adminUploadNamespaces.routes"),
290
734
  );
291
- router.use("/api/admin/ui-components", require("./routes/adminUiComponents.routes"));
735
+ router.use(
736
+ "/api/admin/ui-components",
737
+ require("./routes/adminUiComponents.routes"),
738
+ );
292
739
  router.use("/api/admin/migration", require("./routes/adminMigration.routes"));
293
- router.use("/api/admin/errors", basicAuth, require("./routes/adminErrors.routes"));
294
- router.use("/api/admin/audit", basicAuth, require("./routes/adminAudit.routes"));
740
+ router.use(
741
+ "/api/admin/errors",
742
+ basicAuth,
743
+ require("./routes/adminErrors.routes"),
744
+ );
745
+ router.use(
746
+ "/api/admin/audit",
747
+ basicAuth,
748
+ require("./routes/adminAudit.routes"),
749
+ );
295
750
  router.use("/api/admin/llm", require("./routes/adminLlm.routes"));
296
- router.use("/api/admin/ejs-virtual", require("./routes/adminEjsVirtual.routes"));
297
- router.use("/api/workflows", basicAuth, require("./routes/workflows.routes"));
751
+ router.use(
752
+ "/api/admin/ejs-virtual",
753
+ require("./routes/adminEjsVirtual.routes"),
754
+ );
755
+ router.use("/api/admin/pages", require("./routes/adminPages.routes"));
756
+ router.use("/api/admin", require("./routes/adminBlog.routes"));
757
+ router.use("/api/admin", require("./routes/adminBlogAi.routes"));
758
+ router.use("/api/admin", require("./routes/adminBlogAutomation.routes"));
759
+ router.use("/api/admin/workflows", basicAuth, require("./routes/workflows.routes"));
298
760
  router.use("/w", require("./routes/workflowWebhook.routes"));
299
761
  router.use("/api/webhooks", require("./routes/webhook.routes"));
300
762
  router.use("/api/settings", require("./routes/globalSettings.routes"));
@@ -310,21 +772,47 @@ function createMiddleware(options = {}) {
310
772
  router.use("/api/log", require("./routes/log.routes"));
311
773
  router.use("/api/error-tracking", require("./routes/errorTracking.routes"));
312
774
  router.use("/api/ui-components", require("./routes/uiComponentsPublic.routes"));
313
- router.use("/api/llm/ui", require("./routes/llmUi.routes"));
775
+ router.use("/api/rbac", require("./routes/rbac.routes"));
776
+ router.use("/api/file-manager", require("./routes/fileManager.routes"));
777
+ router.use("/api/experiments", require("./routes/experiments.routes"));
778
+
779
+ // Public blog APIs (headless)
780
+ router.use("/api", require("./routes/blogPublic.routes"));
781
+
782
+ // Internal blog endpoints (used by HTTP CronJobs)
783
+ router.use("/api/internal", require("./routes/blogInternal.routes"));
784
+
785
+ // Internal experiments endpoints (used by HTTP CronJobs)
786
+ router.use("/api/internal", require("./routes/internalExperiments.routes"));
787
+
788
+ // Public health checks status (gated by global setting)
789
+ router.use(
790
+ "/api/health-checks",
791
+ require("./routes/healthChecksPublic.routes"),
792
+ );
314
793
 
315
794
  // Public assets proxy
316
795
  router.use("/public/assets", require("./routes/publicAssets.routes"));
317
796
 
318
797
  // Admin dashboard (polished view)
319
798
  router.get(adminPath, basicAuth, (req, res) => {
320
- const templatePath = path.join(__dirname, "..", "views", "admin-dashboard.ejs");
799
+ const templatePath = path.join(
800
+ __dirname,
801
+ "..",
802
+ "views",
803
+ "admin-dashboard.ejs",
804
+ );
321
805
  fs.readFile(templatePath, "utf8", (err, template) => {
322
806
  if (err) {
323
807
  console.error("Error reading template:", err);
324
808
  return res.status(500).send("Error loading page");
325
809
  }
326
810
  try {
327
- const html = ejs.render(template, { baseUrl: req.baseUrl, adminPath }, { filename: templatePath });
811
+ const html = ejs.render(
812
+ template,
813
+ { baseUrl: req.baseUrl, adminPath },
814
+ { filename: templatePath },
815
+ );
328
816
  res.send(html);
329
817
  } catch (renderErr) {
330
818
  console.error("Error rendering template:", renderErr);
@@ -362,7 +850,12 @@ function createMiddleware(options = {}) {
362
850
  });
363
851
 
364
852
  router.get(`${adminPath}/migration`, basicAuth, (req, res) => {
365
- const templatePath = path.join(__dirname, "..", "views", "admin-migration.ejs");
853
+ const templatePath = path.join(
854
+ __dirname,
855
+ "..",
856
+ "views",
857
+ "admin-migration.ejs",
858
+ );
366
859
  fs.readFile(templatePath, "utf8", (err, template) => {
367
860
  if (err) {
368
861
  console.error("Error reading template:", err);
@@ -390,12 +883,59 @@ function createMiddleware(options = {}) {
390
883
 
391
884
  // Admin LLM/AI page (protected by basic auth)
392
885
  router.get(`${adminPath}/admin-llm`, basicAuth, (req, res) => {
886
+ const templatePath = path.join(__dirname, "..", "views", "admin-llm.ejs");
887
+ fs.readFile(templatePath, "utf8", (err, template) => {
888
+ if (err) {
889
+ console.error("Error reading template:", err);
890
+ return res.status(500).send("Error loading page");
891
+ }
892
+ try {
893
+ const html = ejs.render(
894
+ template,
895
+ {
896
+ baseUrl: req.baseUrl,
897
+ adminPath,
898
+ },
899
+ {
900
+ filename: templatePath,
901
+ },
902
+ );
903
+ res.send(html);
904
+ } catch (renderErr) {
905
+ console.error("Error rendering template:", renderErr);
906
+ res.status(500).send("Error rendering page");
907
+ }
908
+ });
909
+ });
910
+
911
+ router.get(`${adminPath}/workflows/:id`, basicAuth, (req, res) => {
393
912
  const templatePath = path.join(
394
913
  __dirname,
395
914
  "..",
396
915
  "views",
397
- "admin-llm.ejs",
916
+ "admin-workflows.ejs",
398
917
  );
918
+ fs.readFile(templatePath, "utf8", (err, template) => {
919
+ if (err) {
920
+ console.error("Error reading template:", err);
921
+ return res.status(500).send("Error loading page");
922
+ }
923
+ try {
924
+ const html = ejs.render(
925
+ template,
926
+ { baseUrl: req.baseUrl, adminPath },
927
+ { filename: templatePath },
928
+ );
929
+ res.send(html);
930
+ } catch (renderErr) {
931
+ console.error("Error rendering template:", renderErr);
932
+ res.status(500).send("Error rendering page");
933
+ }
934
+ });
935
+ });
936
+
937
+ router.get(`${adminPath}/pages`, basicAuth, (req, res) => {
938
+ const templatePath = path.join(__dirname, "..", "views", "admin-pages.ejs");
399
939
  fs.readFile(templatePath, "utf8", (err, template) => {
400
940
  if (err) {
401
941
  console.error("Error reading template:", err);
@@ -420,15 +960,133 @@ function createMiddleware(options = {}) {
420
960
  });
421
961
  });
422
962
 
423
- router.get(`${adminPath}/workflows/:id`, basicAuth, (req, res) => {
424
- const templatePath = path.join(__dirname, "..", "views", "admin-workflows.ejs");
963
+ router.get(`${adminPath}/blog`, basicAuth, (req, res) => {
964
+ const templatePath = path.join(__dirname, "..", "views", "admin-blog.ejs");
425
965
  fs.readFile(templatePath, "utf8", (err, template) => {
426
966
  if (err) {
427
967
  console.error("Error reading template:", err);
428
968
  return res.status(500).send("Error loading page");
429
969
  }
430
970
  try {
431
- const html = ejs.render(template, { baseUrl: req.baseUrl, adminPath }, { filename: templatePath });
971
+ const html = ejs.render(
972
+ template,
973
+ { baseUrl: req.baseUrl, adminPath },
974
+ { filename: templatePath },
975
+ );
976
+ res.send(html);
977
+ } catch (renderErr) {
978
+ console.error("Error rendering template:", renderErr);
979
+ res.status(500).send("Error rendering page");
980
+ }
981
+ });
982
+ });
983
+
984
+ router.get(`${adminPath}/blog-automation`, basicAuth, (req, res) => {
985
+ const templatePath = path.join(
986
+ __dirname,
987
+ "..",
988
+ "views",
989
+ "admin-blog-automation.ejs",
990
+ );
991
+ fs.readFile(templatePath, "utf8", (err, template) => {
992
+ if (err) {
993
+ console.error("Error reading template:", err);
994
+ return res.status(500).send("Error loading page");
995
+ }
996
+ try {
997
+ const html = ejs.render(
998
+ template,
999
+ { baseUrl: req.baseUrl, adminPath },
1000
+ { filename: templatePath },
1001
+ );
1002
+ res.send(html);
1003
+ } catch (renderErr) {
1004
+ console.error("Error rendering template:", renderErr);
1005
+ res.status(500).send("Error rendering page");
1006
+ }
1007
+ });
1008
+ });
1009
+
1010
+ router.get(`${adminPath}/blog/new`, basicAuth, (req, res) => {
1011
+ const templatePath = path.join(
1012
+ __dirname,
1013
+ "..",
1014
+ "views",
1015
+ "admin-blog-edit.ejs",
1016
+ );
1017
+ fs.readFile(templatePath, "utf8", (err, template) => {
1018
+ if (err) {
1019
+ console.error("Error reading template:", err);
1020
+ return res.status(500).send("Error loading page");
1021
+ }
1022
+ try {
1023
+ const html = ejs.render(
1024
+ template,
1025
+ { baseUrl: req.baseUrl, adminPath, postId: "", mode: "new" },
1026
+ { filename: templatePath },
1027
+ );
1028
+ res.send(html);
1029
+ } catch (renderErr) {
1030
+ console.error("Error rendering template:", renderErr);
1031
+ res.status(500).send("Error rendering page");
1032
+ }
1033
+ });
1034
+ });
1035
+
1036
+ router.get(`${adminPath}/blog/edit/:id`, basicAuth, (req, res) => {
1037
+ const templatePath = path.join(
1038
+ __dirname,
1039
+ "..",
1040
+ "views",
1041
+ "admin-blog-edit.ejs",
1042
+ );
1043
+ fs.readFile(templatePath, "utf8", (err, template) => {
1044
+ if (err) {
1045
+ console.error("Error reading template:", err);
1046
+ return res.status(500).send("Error loading page");
1047
+ }
1048
+ try {
1049
+ const html = ejs.render(
1050
+ template,
1051
+ {
1052
+ baseUrl: req.baseUrl,
1053
+ adminPath,
1054
+ postId: String(req.params.id || ""),
1055
+ mode: "edit",
1056
+ },
1057
+ { filename: templatePath },
1058
+ );
1059
+ res.send(html);
1060
+ } catch (renderErr) {
1061
+ console.error("Error rendering template:", renderErr);
1062
+ res.status(500).send("Error rendering page");
1063
+ }
1064
+ });
1065
+ });
1066
+
1067
+ router.get(`${adminPath}/file-manager`, basicAuth, (req, res) => {
1068
+ const templatePath = path.join(
1069
+ __dirname,
1070
+ "..",
1071
+ "views",
1072
+ "admin-file-manager.ejs",
1073
+ );
1074
+ fs.readFile(templatePath, "utf8", (err, template) => {
1075
+ if (err) {
1076
+ console.error("Error reading template:", err);
1077
+ return res.status(500).send("Error loading page");
1078
+ }
1079
+ try {
1080
+ const html = ejs.render(
1081
+ template,
1082
+ {
1083
+ baseUrl: req.baseUrl,
1084
+ adminPath,
1085
+ },
1086
+ {
1087
+ filename: templatePath,
1088
+ },
1089
+ );
432
1090
  res.send(html);
433
1091
  } catch (renderErr) {
434
1092
  console.error("Error rendering template:", renderErr);
@@ -438,7 +1096,12 @@ function createMiddleware(options = {}) {
438
1096
  });
439
1097
 
440
1098
  router.get(`${adminPath}/ejs-virtual`, basicAuth, (req, res) => {
441
- const templatePath = path.join(__dirname, "..", "views", "admin-ejs-virtual.ejs");
1099
+ const templatePath = path.join(
1100
+ __dirname,
1101
+ "..",
1102
+ "views",
1103
+ "admin-ejs-virtual.ejs",
1104
+ );
442
1105
  fs.readFile(templatePath, "utf8", (err, template) => {
443
1106
  if (err) {
444
1107
  console.error("Error reading template:", err);
@@ -464,7 +1127,12 @@ function createMiddleware(options = {}) {
464
1127
  });
465
1128
 
466
1129
  router.get(`${adminPath}/seo-config`, basicAuth, (req, res) => {
467
- const templatePath = path.join(__dirname, "..", "views", "admin-seo-config.ejs");
1130
+ const templatePath = path.join(
1131
+ __dirname,
1132
+ "..",
1133
+ "views",
1134
+ "admin-seo-config.ejs",
1135
+ );
468
1136
  fs.readFile(templatePath, "utf8", (err, template) => {
469
1137
  if (err) {
470
1138
  console.error("Error reading template:", err);
@@ -498,7 +1166,11 @@ function createMiddleware(options = {}) {
498
1166
  return res.status(500).send("Error loading page");
499
1167
  }
500
1168
  try {
501
- const html = ejs.render(template, { baseUrl: req.baseUrl, adminPath }, { filename: templatePath });
1169
+ const html = ejs.render(
1170
+ template,
1171
+ { baseUrl: req.baseUrl, adminPath },
1172
+ { filename: templatePath },
1173
+ );
502
1174
  res.send(html);
503
1175
  } catch (renderErr) {
504
1176
  console.error("Error rendering template:", renderErr);
@@ -520,7 +1192,11 @@ function createMiddleware(options = {}) {
520
1192
  return res.status(500).send("Error loading page");
521
1193
  }
522
1194
  try {
523
- const html = ejs.render(template, { baseUrl: req.baseUrl, adminPath }, { filename: templatePath });
1195
+ const html = ejs.render(
1196
+ template,
1197
+ { baseUrl: req.baseUrl, adminPath },
1198
+ { filename: templatePath },
1199
+ );
524
1200
  res.send(html);
525
1201
  } catch (renderErr) {
526
1202
  console.error("Error rendering template:", renderErr);
@@ -784,12 +1460,7 @@ function createMiddleware(options = {}) {
784
1460
 
785
1461
  // Admin users page (protected by basic auth)
786
1462
  router.get(`${adminPath}/users`, basicAuth, (req, res) => {
787
- const templatePath = path.join(
788
- __dirname,
789
- "..",
790
- "views",
791
- "admin-users.ejs",
792
- );
1463
+ const templatePath = path.join(__dirname, "..", "views", "admin-users.ejs");
793
1464
  fs.readFile(templatePath, "utf8", (err, template) => {
794
1465
  if (err) {
795
1466
  console.error("Error reading template:", err);
@@ -908,6 +1579,31 @@ function createMiddleware(options = {}) {
908
1579
  });
909
1580
  });
910
1581
 
1582
+ router.get(`${adminPath}/rate-limiter`, basicAuth, (req, res) => {
1583
+ const templatePath = path.join(
1584
+ __dirname,
1585
+ "..",
1586
+ "views",
1587
+ "admin-rate-limiter.ejs",
1588
+ );
1589
+ fs.readFile(templatePath, "utf8", (err, template) => {
1590
+ if (err) {
1591
+ console.error("Error reading template:", err);
1592
+ return res.status(500).send("Error loading page");
1593
+ }
1594
+ try {
1595
+ const html = ejs.render(template, {
1596
+ baseUrl: req.baseUrl,
1597
+ adminPath,
1598
+ });
1599
+ res.send(html);
1600
+ } catch (renderErr) {
1601
+ console.error("Error rendering template:", renderErr);
1602
+ res.status(500).send("Error rendering page");
1603
+ }
1604
+ });
1605
+ });
1606
+
911
1607
  // Admin global settings page (protected by basic auth) - render manually
912
1608
  router.get(`${adminPath}/global-settings`, basicAuth, (req, res) => {
913
1609
  const templatePath = path.join(
@@ -932,14 +1628,23 @@ function createMiddleware(options = {}) {
932
1628
  });
933
1629
 
934
1630
  router.get(`${adminPath}/errors`, basicAuth, (req, res) => {
935
- const templatePath = path.join(__dirname, "..", "views", "admin-errors.ejs");
1631
+ const templatePath = path.join(
1632
+ __dirname,
1633
+ "..",
1634
+ "views",
1635
+ "admin-errors.ejs",
1636
+ );
936
1637
  fs.readFile(templatePath, "utf8", (err, template) => {
937
1638
  if (err) {
938
1639
  console.error("Error reading template:", err);
939
1640
  return res.status(500).send("Error loading page");
940
1641
  }
941
1642
  try {
942
- const html = ejs.render(template, { baseUrl: req.baseUrl, adminPath }, { filename: templatePath });
1643
+ const html = ejs.render(
1644
+ template,
1645
+ { baseUrl: req.baseUrl, adminPath },
1646
+ { filename: templatePath },
1647
+ );
943
1648
  res.send(html);
944
1649
  } catch (renderErr) {
945
1650
  console.error("Error rendering template:", renderErr);
@@ -956,7 +1661,11 @@ function createMiddleware(options = {}) {
956
1661
  return res.status(500).send("Error loading page");
957
1662
  }
958
1663
  try {
959
- const html = ejs.render(template, { baseUrl: req.baseUrl, adminPath }, { filename: templatePath });
1664
+ const html = ejs.render(
1665
+ template,
1666
+ { baseUrl: req.baseUrl, adminPath },
1667
+ { filename: templatePath },
1668
+ );
960
1669
  res.send(html);
961
1670
  } catch (renderErr) {
962
1671
  console.error("Error rendering template:", renderErr);
@@ -966,14 +1675,44 @@ function createMiddleware(options = {}) {
966
1675
  });
967
1676
 
968
1677
  router.get(`${adminPath}/coolify-deploy`, basicAuth, (req, res) => {
969
- const templatePath = path.join(__dirname, "..", "views", "admin-coolify-deploy.ejs");
1678
+ const templatePath = path.join(
1679
+ __dirname,
1680
+ "..",
1681
+ "views",
1682
+ "admin-coolify-deploy.ejs",
1683
+ );
970
1684
  fs.readFile(templatePath, "utf8", (err, template) => {
971
1685
  if (err) {
972
1686
  console.error("Error reading template:", err);
973
1687
  return res.status(500).send("Error loading page");
974
1688
  }
975
1689
  try {
976
- const html = ejs.render(template, { baseUrl: req.baseUrl, adminPath }, { filename: templatePath });
1690
+ const html = ejs.render(
1691
+ template,
1692
+ { baseUrl: req.baseUrl, adminPath },
1693
+ { filename: templatePath },
1694
+ );
1695
+ res.send(html);
1696
+ } catch (renderErr) {
1697
+ console.error("Error rendering template:", renderErr);
1698
+ res.status(500).send("Error rendering page");
1699
+ }
1700
+ });
1701
+ });
1702
+
1703
+ router.get(`${adminPath}/proxy`, basicAuth, (req, res) => {
1704
+ const templatePath = path.join(__dirname, "..", "views", "admin-proxy.ejs");
1705
+ fs.readFile(templatePath, "utf8", (err, template) => {
1706
+ if (err) {
1707
+ console.error("Error reading template:", err);
1708
+ return res.status(500).send("Error loading page");
1709
+ }
1710
+ try {
1711
+ const html = ejs.render(
1712
+ template,
1713
+ { baseUrl: req.baseUrl, adminPath },
1714
+ { filename: templatePath },
1715
+ );
977
1716
  res.send(html);
978
1717
  } catch (renderErr) {
979
1718
  console.error("Error rendering template:", renderErr);
@@ -983,14 +1722,23 @@ function createMiddleware(options = {}) {
983
1722
  });
984
1723
 
985
1724
  router.get(`${adminPath}/webhooks`, basicAuth, (req, res) => {
986
- const templatePath = path.join(__dirname, "..", "views", "admin-webhooks.ejs");
1725
+ const templatePath = path.join(
1726
+ __dirname,
1727
+ "..",
1728
+ "views",
1729
+ "admin-webhooks.ejs",
1730
+ );
987
1731
  fs.readFile(templatePath, "utf8", (err, template) => {
988
1732
  if (err) {
989
1733
  console.error("Error reading template:", err);
990
1734
  return res.status(500).send("Error loading page");
991
1735
  }
992
1736
  try {
993
- const html = ejs.render(template, { baseUrl: req.baseUrl, adminPath }, { filename: templatePath });
1737
+ const html = ejs.render(
1738
+ template,
1739
+ { baseUrl: req.baseUrl, adminPath },
1740
+ { filename: templatePath },
1741
+ );
994
1742
  res.send(html);
995
1743
  } catch (renderErr) {
996
1744
  console.error("Error rendering template:", renderErr);
@@ -999,7 +1747,7 @@ function createMiddleware(options = {}) {
999
1747
  });
1000
1748
  });
1001
1749
 
1002
- router.get("/health", (req, res) => {
1750
+ router.get("/health", rateLimiter.limit("healthRateLimiter"), (req, res) => {
1003
1751
  res.json({
1004
1752
  status: "ok",
1005
1753
  mode: "middleware",
@@ -1011,6 +1759,20 @@ function createMiddleware(options = {}) {
1011
1759
  router.use("/api/ejs-virtual", require("./routes/adminEjsVirtual.routes"));
1012
1760
  router.use("/api/webhooks", require("./routes/webhook.routes"));
1013
1761
 
1762
+ // Store pagesPrefix and adminPath on app for pages router
1763
+ router.use((req, res, next) => {
1764
+ if (!req.app.get("pagesPrefix")) {
1765
+ req.app.set("pagesPrefix", pagesPrefix);
1766
+ }
1767
+ if (!req.app.get("adminPath")) {
1768
+ req.app.set("adminPath", adminPath);
1769
+ }
1770
+ next();
1771
+ });
1772
+
1773
+ // Public pages router (catch-all, must be last before error handler)
1774
+ router.use(require("./routes/pages.routes"));
1775
+
1014
1776
  // Error handling middleware
1015
1777
  router.use(expressErrorMiddleware);
1016
1778