@intranefr/superbackend 1.6.6 → 1.6.7

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 (51) hide show
  1. package/.env.example +4 -0
  2. package/README.md +18 -0
  3. package/package.json +6 -1
  4. package/public/js/admin-superdemos.js +396 -0
  5. package/public/sdk/superdemos.iife.js +614 -0
  6. package/public/superdemos-qa.html +324 -0
  7. package/sdk/superdemos/browser/src/index.js +719 -0
  8. package/src/cli/agent-chat.js +369 -0
  9. package/src/cli/agent-list.js +42 -0
  10. package/src/controllers/adminAgentsChat.controller.js +172 -0
  11. package/src/controllers/adminSuperDemos.controller.js +382 -0
  12. package/src/controllers/superDemosPublic.controller.js +126 -0
  13. package/src/middleware.js +102 -19
  14. package/src/models/BlogAutomationLock.js +4 -4
  15. package/src/models/BlogPost.js +16 -16
  16. package/src/models/CacheEntry.js +17 -6
  17. package/src/models/JsonConfig.js +2 -4
  18. package/src/models/RateLimitMetricBucket.js +10 -5
  19. package/src/models/SuperDemo.js +38 -0
  20. package/src/models/SuperDemoProject.js +32 -0
  21. package/src/models/SuperDemoStep.js +27 -0
  22. package/src/routes/adminAgents.routes.js +10 -0
  23. package/src/routes/adminMarkdowns.routes.js +3 -0
  24. package/src/routes/adminSuperDemos.routes.js +31 -0
  25. package/src/routes/superDemos.routes.js +9 -0
  26. package/src/services/auditLogger.js +75 -37
  27. package/src/services/email.service.js +18 -3
  28. package/src/services/superDemosAuthoringSessions.service.js +132 -0
  29. package/src/services/superDemosWs.service.js +164 -0
  30. package/src/services/terminalsWs.service.js +35 -3
  31. package/src/utils/rbac/rightsRegistry.js +2 -0
  32. package/views/admin-agents.ejs +261 -11
  33. package/views/admin-dashboard.ejs +78 -8
  34. package/views/admin-superdemos.ejs +335 -0
  35. package/views/admin-terminals.ejs +462 -34
  36. package/views/partials/admin/agents-chat.ejs +80 -0
  37. package/views/partials/dashboard/nav-items.ejs +1 -0
  38. package/views/partials/dashboard/tab-bar.ejs +6 -0
  39. package/cookies.txt +0 -6
  40. package/cookies1.txt +0 -6
  41. package/cookies2.txt +0 -6
  42. package/cookies3.txt +0 -6
  43. package/cookies4.txt +0 -5
  44. package/cookies_old.txt +0 -5
  45. package/cookies_old_test.txt +0 -6
  46. package/cookies_super.txt +0 -5
  47. package/cookies_super_test.txt +0 -6
  48. package/cookies_test.txt +0 -6
  49. package/test-access.js +0 -63
  50. package/test-iframe-fix.html +0 -63
  51. package/test-iframe.html +0 -14
package/src/middleware.js CHANGED
@@ -26,7 +26,7 @@ const ejs = require("ejs");
26
26
  const session = require("express-session");
27
27
  const MongoStore = require("connect-mongo");
28
28
  const { adminSessionAuth } = require("./middleware/auth");
29
- const { requireModuleAccess } = require("./middleware/rbac");
29
+ const { requireModuleAccess, isBasicAuthSuperAdmin } = require("./middleware/rbac");
30
30
  const endpointRegistry = require("./admin/endpointRegistry");
31
31
  const {
32
32
  createFeatureFlagsEjsMiddleware,
@@ -96,6 +96,7 @@ function createMiddleware(options = {}) {
96
96
  const router = express.Router();
97
97
  const adminPath = options.adminPath || "/admin";
98
98
  const pagesPrefix = options.pagesPrefix || "/";
99
+ const isJest = Boolean(process.env.JEST_WORKER_ID);
99
100
 
100
101
  const bootstrapPluginsRuntime = async () => {
101
102
  try {
@@ -209,6 +210,11 @@ function createMiddleware(options = {}) {
209
210
  attachExperimentsWebsocketServer,
210
211
  } = require("./services/experimentsWs.service");
211
212
  attachExperimentsWebsocketServer(server);
213
+
214
+ const {
215
+ attachSuperDemosWebsocketServer,
216
+ } = require("./services/superDemosWs.service");
217
+ attachSuperDemosWebsocketServer(server);
212
218
  };
213
219
 
214
220
  if (!errorCaptureInitialized) {
@@ -243,19 +249,19 @@ function createMiddleware(options = {}) {
243
249
  console.log("✅ Middleware: Connected to MongoDB");
244
250
 
245
251
  // Start cron scheduler after DB connection (only if enabled)
246
- if (options.cron?.enabled !== false) {
252
+ if (!isJest && options.cron?.enabled !== false) {
247
253
  await cronScheduler.start();
248
254
  await healthChecksScheduler.start();
249
255
  await healthChecksBootstrap.bootstrap();
250
256
  await blogCronsBootstrap.bootstrap();
251
257
  await require("./services/experimentsCronsBootstrap.service").bootstrap();
252
258
  } else {
253
- console.log("🔍 Cron scheduler disabled - cron.enabled:", options.cron?.enabled);
259
+ console.log("🔍 Cron scheduler disabled - cron.enabled:", options.cron?.enabled, isJest ? '(jest)' : '');
254
260
  }
255
261
 
256
262
  // Initialize Telegram bots (check telegram config)
257
263
  const telegramEnabled = options.telegram?.enabled !== false;
258
- if (telegramEnabled) {
264
+ if (!isJest && telegramEnabled) {
259
265
  const telegramInitializer =
260
266
  (telegramService && typeof telegramService.initialize === "function"
261
267
  ? telegramService.initialize.bind(telegramService)
@@ -273,7 +279,9 @@ function createMiddleware(options = {}) {
273
279
  console.log("🔍 Telegram bots disabled - telegram.enabled:", options.telegram?.enabled);
274
280
  }
275
281
 
276
- await bootstrapPluginsRuntime();
282
+ if (!isJest) {
283
+ await bootstrapPluginsRuntime();
284
+ }
277
285
 
278
286
  // Console manager is already initialized early in the middleware
279
287
  console.log("[Console Manager] MongoDB connection established");
@@ -387,7 +395,7 @@ function createMiddleware(options = {}) {
387
395
  console.log("✅ Middleware: Using existing MongoDB connection");
388
396
 
389
397
  // Start cron scheduler for existing connection (only if enabled)
390
- if (options.cron?.enabled !== false) {
398
+ if (!isJest && options.cron?.enabled !== false) {
391
399
  cronScheduler.start().catch((err) => {
392
400
  console.error("Failed to start cron scheduler:", err);
393
401
  });
@@ -407,12 +415,12 @@ function createMiddleware(options = {}) {
407
415
  console.error("Failed to bootstrap experiments crons:", err);
408
416
  });
409
417
  } else {
410
- console.log("🔍 Cron scheduler disabled - cron.enabled:", options.cron?.enabled, "(existing connection)");
418
+ console.log("🔍 Cron scheduler disabled - cron.enabled:", options.cron?.enabled, "(existing connection)", isJest ? '(jest)' : '');
411
419
  }
412
420
 
413
421
  // Initialize Telegram bots for existing connection (check telegram config)
414
422
  const telegramEnabled = options.telegram?.enabled !== false;
415
- if (telegramEnabled) {
423
+ if (!isJest && telegramEnabled) {
416
424
  const telegramInitializer =
417
425
  (telegramService && typeof telegramService.initialize === "function"
418
426
  ? telegramService.initialize.bind(telegramService)
@@ -432,9 +440,11 @@ function createMiddleware(options = {}) {
432
440
  console.log("🔍 Telegram bots disabled - telegram.enabled:", options.telegram?.enabled, "(existing connection)");
433
441
  }
434
442
 
435
- bootstrapPluginsRuntime().catch((err) => {
436
- console.error("Failed to bootstrap plugins runtime (existing connection):", err);
437
- });
443
+ if (!isJest) {
444
+ bootstrapPluginsRuntime().catch((err) => {
445
+ console.error("Failed to bootstrap plugins runtime (existing connection):", err);
446
+ });
447
+ }
438
448
 
439
449
  // Initialize console manager AFTER database is already connected
440
450
  if (process.env.NODE_ENV !== "test" && !process.env.JEST_WORKER_ID) {
@@ -530,6 +540,15 @@ function createMiddleware(options = {}) {
530
540
  }
531
541
 
532
542
  // Session middleware for admin authentication
543
+ const sessionMongoUrl = options.mongodbUri || process.env.MONGODB_URI || process.env.MONGO_URI;
544
+ const sessionStore = !isJest && sessionMongoUrl
545
+ ? MongoStore.create({
546
+ mongoUrl: sessionMongoUrl,
547
+ collectionName: 'admin_sessions',
548
+ ttl: 24 * 60 * 60 // 24 hours in seconds
549
+ })
550
+ : undefined;
551
+
533
552
  const sessionMiddleware = session({
534
553
  secret: process.env.SESSION_SECRET || 'superbackend-session-secret-fallback',
535
554
  resave: false,
@@ -540,11 +559,7 @@ function createMiddleware(options = {}) {
540
559
  maxAge: 24 * 60 * 60 * 1000, // 24 hours
541
560
  sameSite: 'lax'
542
561
  },
543
- store: MongoStore.create({
544
- mongoUrl: options.mongodbUri || process.env.MONGODB_URI,
545
- collectionName: 'admin_sessions',
546
- ttl: 24 * 60 * 60 // 24 hours in seconds
547
- }),
562
+ store: sessionStore,
548
563
  name: 'superbackend.admin.session'
549
564
  });
550
565
 
@@ -858,7 +873,8 @@ router.get(`${adminPath}/stats/dashboard-home`, checkIframeAuth, (req, res) => {
858
873
  baseUrl: req.baseUrl,
859
874
  adminPath,
860
875
  endpointRegistry,
861
- isIframe: req.isIframe || false
876
+ isIframe: req.isIframe || false,
877
+ serverPort: process.env.PORT || 3000
862
878
  },
863
879
  {
864
880
  filename: templatePath,
@@ -1110,6 +1126,10 @@ router.get(`${adminPath}/stats/dashboard-home`, checkIframeAuth, (req, res) => {
1110
1126
  "/api/admin/ui-components",
1111
1127
  require("./routes/adminUiComponents.routes"),
1112
1128
  );
1129
+ router.use(
1130
+ "/api/admin/superdemos",
1131
+ require("./routes/adminSuperDemos.routes"),
1132
+ );
1113
1133
  router.use("/api/admin/migration", require("./routes/adminMigration.routes"));
1114
1134
  router.use(
1115
1135
  "/api/admin/errors",
@@ -1151,6 +1171,7 @@ router.get(`${adminPath}/stats/dashboard-home`, checkIframeAuth, (req, res) => {
1151
1171
  router.use("/api/log", require("./routes/log.routes"));
1152
1172
  router.use("/api/error-tracking", require("./routes/errorTracking.routes"));
1153
1173
  router.use("/api/ui-components", require("./routes/uiComponentsPublic.routes"));
1174
+ router.use("/api/superdemos", require("./routes/superDemos.routes"));
1154
1175
  router.use("/api/rbac", require("./routes/rbac.routes"));
1155
1176
  router.use("/registry", require("./routes/registry.routes"));
1156
1177
  router.use("/api/file-manager", require("./routes/fileManager.routes"));
@@ -1181,13 +1202,36 @@ router.get(`${adminPath}/stats/dashboard-home`, checkIframeAuth, (req, res) => {
1181
1202
  }, require("./routes/adminLogin.routes"));
1182
1203
 
1183
1204
  // Admin dashboard (redirect to login if not authenticated)
1184
- router.get(adminPath, adminSessionAuth, (req, res) => {
1205
+ router.get(adminPath, adminSessionAuth, async (req, res) => {
1185
1206
  const templatePath = path.join(
1186
1207
  __dirname,
1187
1208
  "..",
1188
1209
  "views",
1189
1210
  "admin-dashboard.ejs",
1190
1211
  );
1212
+
1213
+ // Get max tabs configuration
1214
+ let maxTabs = 5; // default
1215
+ try {
1216
+ // Environment variable takes priority
1217
+ if (process.env.ADMIN_MAX_TABS) {
1218
+ const envValue = parseInt(process.env.ADMIN_MAX_TABS, 10);
1219
+ if (!isNaN(envValue) && envValue > 0) {
1220
+ maxTabs = envValue;
1221
+ }
1222
+ } else {
1223
+ // Fallback to global settings
1224
+ const settingValue = await globalSettingsService.getSettingValue("ADMIN_MAX_TABS", "5");
1225
+ const parsedValue = parseInt(settingValue, 10);
1226
+ if (!isNaN(parsedValue) && parsedValue > 0) {
1227
+ maxTabs = parsedValue;
1228
+ }
1229
+ }
1230
+ } catch (error) {
1231
+ console.error("Error fetching max tabs configuration:", error);
1232
+ // Use default value
1233
+ }
1234
+
1191
1235
  fs.readFile(templatePath, "utf8", (err, template) => {
1192
1236
  if (err) {
1193
1237
  console.error("Error reading template:", err);
@@ -1196,7 +1240,12 @@ router.get(`${adminPath}/stats/dashboard-home`, checkIframeAuth, (req, res) => {
1196
1240
  try {
1197
1241
  const html = ejs.render(
1198
1242
  template,
1199
- { baseUrl: req.baseUrl, adminPath, isIframe: req.isIframe || false },
1243
+ {
1244
+ baseUrl: req.baseUrl,
1245
+ adminPath,
1246
+ isIframe: req.isIframe || false,
1247
+ maxTabs
1248
+ },
1200
1249
  { filename: templatePath },
1201
1250
  );
1202
1251
  res.send(html);
@@ -1729,6 +1778,40 @@ router.get(`${adminPath}/file-manager`, requireModuleAccessWithIframe('file-mana
1729
1778
  });
1730
1779
  });
1731
1780
 
1781
+ // Admin SuperDemos page
1782
+ router.get(`${adminPath}/superdemos`, requireModuleAccessWithIframe('superdemos', 'read'), (req, res) => {
1783
+ const templatePath = path.join(
1784
+ __dirname,
1785
+ "..",
1786
+ "views",
1787
+ "admin-superdemos.ejs",
1788
+ );
1789
+ fs.readFile(templatePath, "utf8", (err, template) => {
1790
+ if (err) {
1791
+ console.error("Error reading template:", err);
1792
+ return res.status(500).send("Error loading page");
1793
+ }
1794
+ try {
1795
+ const html = ejs.render(
1796
+ template,
1797
+ {
1798
+ baseUrl: req.baseUrl || '',
1799
+ adminPath,
1800
+ endpointRegistry,
1801
+ isIframe: req.isIframe || false,
1802
+ },
1803
+ {
1804
+ filename: templatePath,
1805
+ },
1806
+ );
1807
+ res.send(html);
1808
+ } catch (renderErr) {
1809
+ console.error("Error rendering template:", renderErr);
1810
+ res.status(500).send("Error rendering page");
1811
+ }
1812
+ });
1813
+ });
1814
+
1732
1815
  // Admin JSON configs page (protected by basic auth)
1733
1816
  router.get(`${adminPath}/json-configs`, requireModuleAccessWithIframe('json-configs', 'read'), (req, res) => {
1734
1817
  const templatePath = path.join(
@@ -1,14 +1,14 @@
1
- const mongoose = require('mongoose');
1
+ const mongoose = require("mongoose");
2
2
 
3
3
  const blogAutomationLockSchema = new mongoose.Schema(
4
4
  {
5
- key: { type: String, required: true, unique: true, index: true },
5
+ key: { type: String, required: true, unique: true },
6
6
  lockedUntil: { type: Date, required: true },
7
7
  ownerId: { type: String, required: true },
8
8
  },
9
- { timestamps: true, collection: 'blog_automation_locks' },
9
+ { timestamps: true, collection: "blog_automation_locks" },
10
10
  );
11
11
 
12
12
  module.exports =
13
13
  mongoose.models.BlogAutomationLock ||
14
- mongoose.model('BlogAutomationLock', blogAutomationLockSchema);
14
+ mongoose.model("BlogAutomationLock", blogAutomationLockSchema);
@@ -1,28 +1,27 @@
1
- const mongoose = require('mongoose');
1
+ const mongoose = require("mongoose");
2
2
 
3
3
  const blogPostSchema = new mongoose.Schema(
4
4
  {
5
5
  title: { type: String, required: true },
6
- slug: { type: String, required: true, index: true },
6
+ slug: { type: String, required: true },
7
7
  status: {
8
8
  type: String,
9
- enum: ['draft', 'scheduled', 'published', 'archived'],
10
- default: 'draft',
11
- index: true,
9
+ enum: ["draft", "scheduled", "published", "archived"],
10
+ default: "draft",
12
11
  },
13
- excerpt: { type: String, default: '' },
14
- markdown: { type: String, default: '' },
15
- html: { type: String, default: '' },
16
- coverImageUrl: { type: String, default: '' },
17
- category: { type: String, default: '' },
12
+ excerpt: { type: String, default: "" },
13
+ markdown: { type: String, default: "" },
14
+ html: { type: String, default: "" },
15
+ coverImageUrl: { type: String, default: "" },
16
+ category: { type: String, default: "" },
18
17
  tags: { type: [String], default: [] },
19
- authorName: { type: String, default: '' },
20
- seoTitle: { type: String, default: '' },
21
- seoDescription: { type: String, default: '' },
18
+ authorName: { type: String, default: "" },
19
+ seoTitle: { type: String, default: "" },
20
+ seoDescription: { type: String, default: "" },
22
21
  scheduledAt: { type: Date },
23
22
  publishedAt: { type: Date },
24
23
  },
25
- { timestamps: true, collection: 'blog_posts' },
24
+ { timestamps: true, collection: "blog_posts" },
26
25
  );
27
26
 
28
27
  blogPostSchema.index({ status: 1, publishedAt: -1 });
@@ -34,9 +33,10 @@ blogPostSchema.index(
34
33
  {
35
34
  unique: true,
36
35
  partialFilterExpression: {
37
- status: { $in: ['draft', 'scheduled', 'published'] },
36
+ status: { $in: ["draft", "scheduled", "published"] },
38
37
  },
39
38
  },
40
39
  );
41
40
 
42
- module.exports = mongoose.models.BlogPost || mongoose.model('BlogPost', blogPostSchema);
41
+ module.exports =
42
+ mongoose.models.BlogPost || mongoose.model("BlogPost", blogPostSchema);
@@ -1,12 +1,17 @@
1
- const mongoose = require('mongoose');
1
+ const mongoose = require("mongoose");
2
2
 
3
3
  const cacheEntrySchema = new mongoose.Schema(
4
4
  {
5
5
  namespace: { type: String, required: true, index: true },
6
- key: { type: String, required: true, index: true },
6
+ key: { type: String, required: true },
7
7
 
8
8
  value: { type: String, required: true },
9
- atRestFormat: { type: String, enum: ['string', 'base64'], default: 'string', index: true },
9
+ atRestFormat: {
10
+ type: String,
11
+ enum: ["string", "base64"],
12
+ default: "string",
13
+ index: true,
14
+ },
10
15
 
11
16
  sizeBytes: { type: Number, default: 0 },
12
17
 
@@ -15,12 +20,18 @@ const cacheEntrySchema = new mongoose.Schema(
15
20
  hits: { type: Number, default: 0 },
16
21
  lastAccessAt: { type: Date, default: null },
17
22
 
18
- source: { type: String, enum: ['offloaded', 'manual'], default: 'manual', index: true },
23
+ source: {
24
+ type: String,
25
+ enum: ["offloaded", "manual"],
26
+ default: "manual",
27
+ index: true,
28
+ },
19
29
  },
20
- { timestamps: true, collection: 'cache_entries' },
30
+ { timestamps: true, collection: "cache_entries" },
21
31
  );
22
32
 
23
33
  cacheEntrySchema.index({ namespace: 1, key: 1 }, { unique: true });
24
34
  cacheEntrySchema.index({ expiresAt: 1 }, { expireAfterSeconds: 0 });
25
35
 
26
- module.exports = mongoose.models.CacheEntry || mongoose.model('CacheEntry', cacheEntrySchema);
36
+ module.exports =
37
+ mongoose.models.CacheEntry || mongoose.model("CacheEntry", cacheEntrySchema);
@@ -1,4 +1,4 @@
1
- const mongoose = require('mongoose');
1
+ const mongoose = require("mongoose");
2
2
 
3
3
  const jsonConfigSchema = new mongoose.Schema(
4
4
  {
@@ -10,13 +10,11 @@ const jsonConfigSchema = new mongoose.Schema(
10
10
  type: String,
11
11
  required: true,
12
12
  unique: true,
13
- index: true,
14
13
  },
15
14
  alias: {
16
15
  type: String,
17
16
  unique: true,
18
17
  sparse: true,
19
- index: true,
20
18
  },
21
19
  publicEnabled: {
22
20
  type: Boolean,
@@ -43,4 +41,4 @@ const jsonConfigSchema = new mongoose.Schema(
43
41
  // jsonConfigSchema.index({ slug: 1 }); // Removed duplicate index
44
42
  // jsonConfigSchema.index({ alias: 1 }); // Removed duplicate index
45
43
 
46
- module.exports = mongoose.model('JsonConfig', jsonConfigSchema);
44
+ module.exports = mongoose.model("JsonConfig", jsonConfigSchema);
@@ -1,4 +1,4 @@
1
- const mongoose = require('mongoose');
1
+ const mongoose = require("mongoose");
2
2
 
3
3
  const rateLimitMetricBucketSchema = new mongoose.Schema(
4
4
  {
@@ -9,12 +9,17 @@ const rateLimitMetricBucketSchema = new mongoose.Schema(
9
9
  allowed: { type: Number, default: 0 },
10
10
  blocked: { type: Number, default: 0 },
11
11
 
12
- expiresAt: { type: Date, default: null, index: true },
12
+ expiresAt: { type: Date, default: null },
13
13
  },
14
- { timestamps: true, collection: 'rate_limit_metric_buckets' },
14
+ { timestamps: true, collection: "rate_limit_metric_buckets" },
15
15
  );
16
16
 
17
- rateLimitMetricBucketSchema.index({ limiterId: 1, bucketStart: 1 }, { unique: true });
17
+ rateLimitMetricBucketSchema.index(
18
+ { limiterId: 1, bucketStart: 1 },
19
+ { unique: true },
20
+ );
18
21
  rateLimitMetricBucketSchema.index({ expiresAt: 1 }, { expireAfterSeconds: 0 });
19
22
 
20
- module.exports = mongoose.models.RateLimitMetricBucket || mongoose.model('RateLimitMetricBucket', rateLimitMetricBucketSchema);
23
+ module.exports =
24
+ mongoose.models.RateLimitMetricBucket ||
25
+ mongoose.model("RateLimitMetricBucket", rateLimitMetricBucketSchema);
@@ -0,0 +1,38 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const superDemoSchema = new mongoose.Schema(
4
+ {
5
+ demoId: {
6
+ type: String,
7
+ required: true,
8
+ unique: true,
9
+ index: true,
10
+ trim: true,
11
+ match: [/^demo_[a-z0-9]{8,32}$/, 'Invalid demoId'],
12
+ },
13
+ projectId: { type: String, required: true, index: true, trim: true },
14
+
15
+ name: { type: String, required: true, trim: true },
16
+
17
+ status: {
18
+ type: String,
19
+ enum: ['draft', 'published'],
20
+ default: 'draft',
21
+ index: true,
22
+ },
23
+
24
+ publishedVersion: { type: Number, default: 0 },
25
+ publishedAt: { type: Date, default: null },
26
+
27
+ // Minimal targeting for v1.
28
+ // Interpreted as a substring match against window.location.href unless otherwise specified.
29
+ startUrlPattern: { type: String, default: null },
30
+
31
+ isActive: { type: Boolean, default: true, index: true },
32
+ },
33
+ { timestamps: true, collection: 'super_demos' },
34
+ );
35
+
36
+ superDemoSchema.index({ projectId: 1, status: 1, isActive: 1 });
37
+
38
+ module.exports = mongoose.models.SuperDemo || mongoose.model('SuperDemo', superDemoSchema);
@@ -0,0 +1,32 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const superDemoProjectSchema = new mongoose.Schema(
4
+ {
5
+ projectId: {
6
+ type: String,
7
+ required: true,
8
+ unique: true,
9
+ index: true,
10
+ trim: true,
11
+ match: [/^sdp_[a-z0-9]{8,32}$/, 'Invalid projectId'],
12
+ },
13
+ name: { type: String, required: true, trim: true },
14
+
15
+ isPublic: { type: Boolean, default: true, index: true },
16
+ apiKeyHash: { type: String, default: null },
17
+
18
+ allowedOrigins: { type: [String], default: [] },
19
+ stylePreset: {
20
+ type: String,
21
+ enum: ['default', 'glass-dark', 'high-contrast', 'soft-purple'],
22
+ default: 'default',
23
+ },
24
+ styleOverrides: { type: String, default: '' },
25
+
26
+ isActive: { type: Boolean, default: true, index: true },
27
+ },
28
+ { timestamps: true, collection: 'super_demo_projects' },
29
+ );
30
+
31
+ module.exports = mongoose.models.SuperDemoProject ||
32
+ mongoose.model('SuperDemoProject', superDemoProjectSchema);
@@ -0,0 +1,27 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const superDemoStepSchema = new mongoose.Schema(
4
+ {
5
+ demoId: { type: String, required: true, index: true, trim: true },
6
+ order: { type: Number, required: true, index: true },
7
+
8
+ selector: { type: String, required: true },
9
+ selectorHints: { type: mongoose.Schema.Types.Mixed, default: null },
10
+
11
+ message: { type: String, required: true },
12
+ placement: {
13
+ type: String,
14
+ enum: ['top', 'bottom', 'left', 'right', 'auto'],
15
+ default: 'auto',
16
+ },
17
+
18
+ waitFor: { type: mongoose.Schema.Types.Mixed, default: null },
19
+ advance: { type: mongoose.Schema.Types.Mixed, default: { type: 'manualNext' } },
20
+ },
21
+ { timestamps: true, collection: 'super_demo_steps' },
22
+ );
23
+
24
+ superDemoStepSchema.index({ demoId: 1, order: 1 }, { unique: true });
25
+
26
+ module.exports = mongoose.models.SuperDemoStep ||
27
+ mongoose.model('SuperDemoStep', superDemoStepSchema);
@@ -1,6 +1,7 @@
1
1
  const express = require('express');
2
2
  const router = express.Router();
3
3
  const adminAgentsController = require('../controllers/adminAgents.controller');
4
+ const adminAgentsChatController = require('../controllers/adminAgentsChat.controller');
4
5
  const { adminSessionAuth } = require('../middleware/auth');
5
6
 
6
7
  router.use(adminSessionAuth);
@@ -10,4 +11,13 @@ router.post('/', adminAgentsController.createAgent);
10
11
  router.put('/:id', adminAgentsController.updateAgent);
11
12
  router.delete('/:id', adminAgentsController.deleteAgent);
12
13
 
14
+ router.post('/chat/session/new', adminAgentsChatController.newSession);
15
+ router.get('/chat/sessions', adminAgentsChatController.listSessions);
16
+ router.post('/chat/session/rename', adminAgentsChatController.renameSession);
17
+ router.post('/chat/session/compact', adminAgentsChatController.compactSession);
18
+ router.get('/chat/session/:chatId/messages', adminAgentsChatController.loadSessionMessages);
19
+ router.post('/chat/message', adminAgentsChatController.sendMessage);
20
+ router.post('/chat/stream', adminAgentsChatController.streamMessage);
21
+ router.get('/chat/health', adminAgentsChatController.chatHealth);
22
+
13
23
  module.exports = router;
@@ -5,6 +5,9 @@ const { adminSessionAuth } = require('../middleware/auth');
5
5
  const adminMarkdownsController = require('../controllers/adminMarkdowns.controller');
6
6
 
7
7
  router.use(adminSessionAuth);
8
+ router.get('/', adminMarkdownsController.list);
9
+ router.get('/group-codes/:category', adminMarkdownsController.getGroupCodes);
10
+ router.get('/folder/:category/:group_code', adminMarkdownsController.getFolderContents);
8
11
  router.post('/validate-path', adminSessionAuth, adminMarkdownsController.validatePath);
9
12
 
10
13
  module.exports = router;
@@ -0,0 +1,31 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+
4
+ const { adminSessionAuth } = require('../middleware/auth');
5
+ const controller = require('../controllers/adminSuperDemos.controller');
6
+
7
+ router.use(express.json({ limit: '1mb' }));
8
+ router.use(adminSessionAuth);
9
+
10
+ // Projects
11
+ router.get('/projects', controller.listProjects);
12
+ router.post('/projects', controller.createProject);
13
+ router.put('/projects/:projectId', controller.updateProject);
14
+ router.post('/projects/:projectId/rotate-key', controller.rotateProjectKey);
15
+
16
+ // Demos
17
+ router.get('/projects/:projectId/demos', controller.listProjectDemos);
18
+ router.post('/projects/:projectId/demos', controller.createDemo);
19
+ router.get('/demos/:demoId', controller.getDemo);
20
+ router.put('/demos/:demoId', controller.updateDemo);
21
+ router.post('/demos/:demoId/publish', controller.publishDemo);
22
+
23
+ // Steps
24
+ router.get('/demos/:demoId/steps', controller.listSteps);
25
+ router.put('/demos/:demoId/steps', controller.replaceSteps);
26
+
27
+ // Authoring sessions
28
+ router.post('/authoring-sessions', controller.createAuthoringSession);
29
+ router.delete('/authoring-sessions/:sessionId', controller.deleteAuthoringSession);
30
+
31
+ module.exports = router;
@@ -0,0 +1,9 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+
4
+ const controller = require('../controllers/superDemosPublic.controller');
5
+
6
+ router.get('/projects/:projectId/demos/published', controller.listPublishedDemos);
7
+ router.get('/demos/:demoId/definition', controller.getPublishedDemoDefinition);
8
+
9
+ module.exports = router;