@intranefr/superbackend 1.6.5 → 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 (53) 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 +108 -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/llm.service.js +1 -0
  29. package/src/services/plugins.service.js +50 -16
  30. package/src/services/superDemosAuthoringSessions.service.js +132 -0
  31. package/src/services/superDemosWs.service.js +164 -0
  32. package/src/services/terminalsWs.service.js +35 -3
  33. package/src/utils/rbac/rightsRegistry.js +2 -0
  34. package/views/admin-agents.ejs +261 -11
  35. package/views/admin-dashboard.ejs +78 -8
  36. package/views/admin-superdemos.ejs +335 -0
  37. package/views/admin-terminals.ejs +462 -34
  38. package/views/partials/admin/agents-chat.ejs +80 -0
  39. package/views/partials/dashboard/nav-items.ejs +1 -0
  40. package/views/partials/dashboard/tab-bar.ejs +6 -0
  41. package/cookies.txt +0 -6
  42. package/cookies1.txt +0 -6
  43. package/cookies2.txt +0 -6
  44. package/cookies3.txt +0 -6
  45. package/cookies4.txt +0 -5
  46. package/cookies_old.txt +0 -5
  47. package/cookies_old_test.txt +0 -6
  48. package/cookies_super.txt +0 -5
  49. package/cookies_super_test.txt +0 -6
  50. package/cookies_test.txt +0 -6
  51. package/test-access.js +0 -63
  52. package/test-iframe-fix.html +0 -63
  53. 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,9 +96,16 @@ 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 {
103
+ if (options.plugins?.extraRoots) {
104
+ const roots = Array.isArray(options.plugins.extraRoots) ? options.plugins.extraRoots : [];
105
+ for (const root of roots) {
106
+ await pluginsService.loadAllPluginsFromFolder(root, { context: {} });
107
+ }
108
+ }
102
109
  const superbackend = globalThis.superbackend || globalThis.saasbackend || {};
103
110
  await pluginsService.bootstrap({
104
111
  context: {
@@ -203,6 +210,11 @@ function createMiddleware(options = {}) {
203
210
  attachExperimentsWebsocketServer,
204
211
  } = require("./services/experimentsWs.service");
205
212
  attachExperimentsWebsocketServer(server);
213
+
214
+ const {
215
+ attachSuperDemosWebsocketServer,
216
+ } = require("./services/superDemosWs.service");
217
+ attachSuperDemosWebsocketServer(server);
206
218
  };
207
219
 
208
220
  if (!errorCaptureInitialized) {
@@ -237,19 +249,19 @@ function createMiddleware(options = {}) {
237
249
  console.log("✅ Middleware: Connected to MongoDB");
238
250
 
239
251
  // Start cron scheduler after DB connection (only if enabled)
240
- if (options.cron?.enabled !== false) {
252
+ if (!isJest && options.cron?.enabled !== false) {
241
253
  await cronScheduler.start();
242
254
  await healthChecksScheduler.start();
243
255
  await healthChecksBootstrap.bootstrap();
244
256
  await blogCronsBootstrap.bootstrap();
245
257
  await require("./services/experimentsCronsBootstrap.service").bootstrap();
246
258
  } else {
247
- console.log("🔍 Cron scheduler disabled - cron.enabled:", options.cron?.enabled);
259
+ console.log("🔍 Cron scheduler disabled - cron.enabled:", options.cron?.enabled, isJest ? '(jest)' : '');
248
260
  }
249
261
 
250
262
  // Initialize Telegram bots (check telegram config)
251
263
  const telegramEnabled = options.telegram?.enabled !== false;
252
- if (telegramEnabled) {
264
+ if (!isJest && telegramEnabled) {
253
265
  const telegramInitializer =
254
266
  (telegramService && typeof telegramService.initialize === "function"
255
267
  ? telegramService.initialize.bind(telegramService)
@@ -267,7 +279,9 @@ function createMiddleware(options = {}) {
267
279
  console.log("🔍 Telegram bots disabled - telegram.enabled:", options.telegram?.enabled);
268
280
  }
269
281
 
270
- await bootstrapPluginsRuntime();
282
+ if (!isJest) {
283
+ await bootstrapPluginsRuntime();
284
+ }
271
285
 
272
286
  // Console manager is already initialized early in the middleware
273
287
  console.log("[Console Manager] MongoDB connection established");
@@ -381,7 +395,7 @@ function createMiddleware(options = {}) {
381
395
  console.log("✅ Middleware: Using existing MongoDB connection");
382
396
 
383
397
  // Start cron scheduler for existing connection (only if enabled)
384
- if (options.cron?.enabled !== false) {
398
+ if (!isJest && options.cron?.enabled !== false) {
385
399
  cronScheduler.start().catch((err) => {
386
400
  console.error("Failed to start cron scheduler:", err);
387
401
  });
@@ -401,12 +415,12 @@ function createMiddleware(options = {}) {
401
415
  console.error("Failed to bootstrap experiments crons:", err);
402
416
  });
403
417
  } else {
404
- 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)' : '');
405
419
  }
406
420
 
407
421
  // Initialize Telegram bots for existing connection (check telegram config)
408
422
  const telegramEnabled = options.telegram?.enabled !== false;
409
- if (telegramEnabled) {
423
+ if (!isJest && telegramEnabled) {
410
424
  const telegramInitializer =
411
425
  (telegramService && typeof telegramService.initialize === "function"
412
426
  ? telegramService.initialize.bind(telegramService)
@@ -426,9 +440,11 @@ function createMiddleware(options = {}) {
426
440
  console.log("🔍 Telegram bots disabled - telegram.enabled:", options.telegram?.enabled, "(existing connection)");
427
441
  }
428
442
 
429
- bootstrapPluginsRuntime().catch((err) => {
430
- console.error("Failed to bootstrap plugins runtime (existing connection):", err);
431
- });
443
+ if (!isJest) {
444
+ bootstrapPluginsRuntime().catch((err) => {
445
+ console.error("Failed to bootstrap plugins runtime (existing connection):", err);
446
+ });
447
+ }
432
448
 
433
449
  // Initialize console manager AFTER database is already connected
434
450
  if (process.env.NODE_ENV !== "test" && !process.env.JEST_WORKER_ID) {
@@ -524,6 +540,15 @@ function createMiddleware(options = {}) {
524
540
  }
525
541
 
526
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
+
527
552
  const sessionMiddleware = session({
528
553
  secret: process.env.SESSION_SECRET || 'superbackend-session-secret-fallback',
529
554
  resave: false,
@@ -534,11 +559,7 @@ function createMiddleware(options = {}) {
534
559
  maxAge: 24 * 60 * 60 * 1000, // 24 hours
535
560
  sameSite: 'lax'
536
561
  },
537
- store: MongoStore.create({
538
- mongoUrl: options.mongodbUri || process.env.MONGODB_URI,
539
- collectionName: 'admin_sessions',
540
- ttl: 24 * 60 * 60 // 24 hours in seconds
541
- }),
562
+ store: sessionStore,
542
563
  name: 'superbackend.admin.session'
543
564
  });
544
565
 
@@ -852,7 +873,8 @@ router.get(`${adminPath}/stats/dashboard-home`, checkIframeAuth, (req, res) => {
852
873
  baseUrl: req.baseUrl,
853
874
  adminPath,
854
875
  endpointRegistry,
855
- isIframe: req.isIframe || false
876
+ isIframe: req.isIframe || false,
877
+ serverPort: process.env.PORT || 3000
856
878
  },
857
879
  {
858
880
  filename: templatePath,
@@ -1104,6 +1126,10 @@ router.get(`${adminPath}/stats/dashboard-home`, checkIframeAuth, (req, res) => {
1104
1126
  "/api/admin/ui-components",
1105
1127
  require("./routes/adminUiComponents.routes"),
1106
1128
  );
1129
+ router.use(
1130
+ "/api/admin/superdemos",
1131
+ require("./routes/adminSuperDemos.routes"),
1132
+ );
1107
1133
  router.use("/api/admin/migration", require("./routes/adminMigration.routes"));
1108
1134
  router.use(
1109
1135
  "/api/admin/errors",
@@ -1145,6 +1171,7 @@ router.get(`${adminPath}/stats/dashboard-home`, checkIframeAuth, (req, res) => {
1145
1171
  router.use("/api/log", require("./routes/log.routes"));
1146
1172
  router.use("/api/error-tracking", require("./routes/errorTracking.routes"));
1147
1173
  router.use("/api/ui-components", require("./routes/uiComponentsPublic.routes"));
1174
+ router.use("/api/superdemos", require("./routes/superDemos.routes"));
1148
1175
  router.use("/api/rbac", require("./routes/rbac.routes"));
1149
1176
  router.use("/registry", require("./routes/registry.routes"));
1150
1177
  router.use("/api/file-manager", require("./routes/fileManager.routes"));
@@ -1175,13 +1202,36 @@ router.get(`${adminPath}/stats/dashboard-home`, checkIframeAuth, (req, res) => {
1175
1202
  }, require("./routes/adminLogin.routes"));
1176
1203
 
1177
1204
  // Admin dashboard (redirect to login if not authenticated)
1178
- router.get(adminPath, adminSessionAuth, (req, res) => {
1205
+ router.get(adminPath, adminSessionAuth, async (req, res) => {
1179
1206
  const templatePath = path.join(
1180
1207
  __dirname,
1181
1208
  "..",
1182
1209
  "views",
1183
1210
  "admin-dashboard.ejs",
1184
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
+
1185
1235
  fs.readFile(templatePath, "utf8", (err, template) => {
1186
1236
  if (err) {
1187
1237
  console.error("Error reading template:", err);
@@ -1190,7 +1240,12 @@ router.get(`${adminPath}/stats/dashboard-home`, checkIframeAuth, (req, res) => {
1190
1240
  try {
1191
1241
  const html = ejs.render(
1192
1242
  template,
1193
- { baseUrl: req.baseUrl, adminPath, isIframe: req.isIframe || false },
1243
+ {
1244
+ baseUrl: req.baseUrl,
1245
+ adminPath,
1246
+ isIframe: req.isIframe || false,
1247
+ maxTabs
1248
+ },
1194
1249
  { filename: templatePath },
1195
1250
  );
1196
1251
  res.send(html);
@@ -1723,6 +1778,40 @@ router.get(`${adminPath}/file-manager`, requireModuleAccessWithIframe('file-mana
1723
1778
  });
1724
1779
  });
1725
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
+
1726
1815
  // Admin JSON configs page (protected by basic auth)
1727
1816
  router.get(`${adminPath}/json-configs`, requireModuleAccessWithIframe('json-configs', 'read'), (req, res) => {
1728
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;