@intranefr/superbackend 1.5.1 → 1.5.3

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 (73) hide show
  1. package/.env.example +10 -0
  2. package/index.js +2 -0
  3. package/manage.js +745 -0
  4. package/package.json +5 -2
  5. package/src/controllers/admin.controller.js +79 -6
  6. package/src/controllers/adminAgents.controller.js +37 -0
  7. package/src/controllers/adminExperiments.controller.js +200 -0
  8. package/src/controllers/adminLlm.controller.js +19 -0
  9. package/src/controllers/adminMarkdowns.controller.js +157 -0
  10. package/src/controllers/adminScripts.controller.js +243 -74
  11. package/src/controllers/adminTelegram.controller.js +72 -0
  12. package/src/controllers/experiments.controller.js +85 -0
  13. package/src/controllers/internalExperiments.controller.js +17 -0
  14. package/src/controllers/markdowns.controller.js +42 -0
  15. package/src/helpers/mongooseHelper.js +258 -0
  16. package/src/helpers/scriptBase.js +230 -0
  17. package/src/helpers/scriptRunner.js +335 -0
  18. package/src/middleware.js +195 -34
  19. package/src/models/Agent.js +105 -0
  20. package/src/models/AgentMessage.js +82 -0
  21. package/src/models/CacheEntry.js +1 -1
  22. package/src/models/ConsoleLog.js +1 -1
  23. package/src/models/Experiment.js +75 -0
  24. package/src/models/ExperimentAssignment.js +23 -0
  25. package/src/models/ExperimentEvent.js +26 -0
  26. package/src/models/ExperimentMetricBucket.js +30 -0
  27. package/src/models/GlobalSetting.js +1 -2
  28. package/src/models/Markdown.js +75 -0
  29. package/src/models/RateLimitCounter.js +1 -1
  30. package/src/models/ScriptDefinition.js +1 -0
  31. package/src/models/ScriptRun.js +8 -0
  32. package/src/models/TelegramBot.js +42 -0
  33. package/src/models/Webhook.js +2 -0
  34. package/src/routes/admin.routes.js +2 -0
  35. package/src/routes/adminAgents.routes.js +13 -0
  36. package/src/routes/adminConsoleManager.routes.js +1 -1
  37. package/src/routes/adminExperiments.routes.js +29 -0
  38. package/src/routes/adminLlm.routes.js +1 -0
  39. package/src/routes/adminMarkdowns.routes.js +16 -0
  40. package/src/routes/adminScripts.routes.js +4 -1
  41. package/src/routes/adminTelegram.routes.js +14 -0
  42. package/src/routes/blogInternal.routes.js +2 -2
  43. package/src/routes/experiments.routes.js +30 -0
  44. package/src/routes/internalExperiments.routes.js +15 -0
  45. package/src/routes/markdowns.routes.js +16 -0
  46. package/src/services/agent.service.js +546 -0
  47. package/src/services/agentHistory.service.js +345 -0
  48. package/src/services/agentTools.service.js +578 -0
  49. package/src/services/blogCronsBootstrap.service.js +7 -6
  50. package/src/services/consoleManager.service.js +56 -18
  51. package/src/services/consoleOverride.service.js +1 -0
  52. package/src/services/experiments.service.js +273 -0
  53. package/src/services/experimentsAggregation.service.js +308 -0
  54. package/src/services/experimentsCronsBootstrap.service.js +118 -0
  55. package/src/services/experimentsRetention.service.js +43 -0
  56. package/src/services/experimentsWs.service.js +134 -0
  57. package/src/services/globalSettings.service.js +15 -0
  58. package/src/services/jsonConfigs.service.js +24 -12
  59. package/src/services/llm.service.js +219 -6
  60. package/src/services/markdowns.service.js +522 -0
  61. package/src/services/scriptsRunner.service.js +514 -23
  62. package/src/services/telegram.service.js +130 -0
  63. package/src/utils/rbac/rightsRegistry.js +4 -0
  64. package/views/admin-agents.ejs +273 -0
  65. package/views/admin-coolify-deploy.ejs +8 -8
  66. package/views/admin-dashboard.ejs +63 -12
  67. package/views/admin-experiments.ejs +91 -0
  68. package/views/admin-markdowns.ejs +905 -0
  69. package/views/admin-scripts.ejs +817 -6
  70. package/views/admin-telegram.ejs +269 -0
  71. package/views/partials/dashboard/nav-items.ejs +4 -0
  72. package/views/partials/dashboard/palette.ejs +5 -3
  73. package/src/middleware/internalCronAuth.js +0 -29
@@ -0,0 +1,23 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const experimentAssignmentSchema = new mongoose.Schema(
4
+ {
5
+ experimentId: { type: mongoose.Schema.Types.ObjectId, ref: 'Experiment', required: true, index: true },
6
+ organizationId: { type: mongoose.Schema.Types.ObjectId, ref: 'Organization', default: null, index: true },
7
+
8
+ subjectKey: { type: String, required: true },
9
+
10
+ variantKey: { type: String, required: true },
11
+ assignedAt: { type: Date, default: () => new Date() },
12
+
13
+ context: { type: mongoose.Schema.Types.Mixed, default: {} },
14
+ },
15
+ { timestamps: true, collection: 'experiment_assignments' },
16
+ );
17
+
18
+ experimentAssignmentSchema.index({ experimentId: 1, subjectKey: 1 }, { unique: true });
19
+ experimentAssignmentSchema.index({ organizationId: 1, subjectKey: 1 });
20
+
21
+ module.exports =
22
+ mongoose.models.ExperimentAssignment ||
23
+ mongoose.model('ExperimentAssignment', experimentAssignmentSchema);
@@ -0,0 +1,26 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const experimentEventSchema = new mongoose.Schema(
4
+ {
5
+ experimentId: { type: mongoose.Schema.Types.ObjectId, ref: 'Experiment', required: true, index: true },
6
+ organizationId: { type: mongoose.Schema.Types.ObjectId, ref: 'Organization', default: null, index: true },
7
+
8
+ subjectKey: { type: String, required: true },
9
+
10
+ variantKey: { type: String, required: true, index: true },
11
+
12
+ eventKey: { type: String, required: true, index: true },
13
+ value: { type: Number, default: 1 },
14
+
15
+ ts: { type: Date, required: true, index: true },
16
+
17
+ meta: { type: mongoose.Schema.Types.Mixed, default: {} },
18
+ },
19
+ { timestamps: true, collection: 'experiment_events' },
20
+ );
21
+
22
+ experimentEventSchema.index({ experimentId: 1, ts: 1 });
23
+ experimentEventSchema.index({ organizationId: 1, ts: 1 });
24
+ experimentEventSchema.index({ experimentId: 1, eventKey: 1, ts: 1 });
25
+
26
+ module.exports = mongoose.models.ExperimentEvent || mongoose.model('ExperimentEvent', experimentEventSchema);
@@ -0,0 +1,30 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const experimentMetricBucketSchema = new mongoose.Schema(
4
+ {
5
+ experimentId: { type: mongoose.Schema.Types.ObjectId, ref: 'Experiment', required: true, index: true },
6
+ organizationId: { type: mongoose.Schema.Types.ObjectId, ref: 'Organization', default: null, index: true },
7
+
8
+ variantKey: { type: String, required: true, index: true },
9
+ metricKey: { type: String, required: true, index: true },
10
+
11
+ bucketStart: { type: Date, required: true, index: true },
12
+ bucketMs: { type: Number, required: true },
13
+
14
+ count: { type: Number, default: 0 },
15
+ sum: { type: Number, default: 0 },
16
+ sumSq: { type: Number, default: 0 },
17
+ min: { type: Number, default: null },
18
+ max: { type: Number, default: null },
19
+ },
20
+ { timestamps: true, collection: 'experiment_metric_buckets' },
21
+ );
22
+
23
+ experimentMetricBucketSchema.index(
24
+ { experimentId: 1, variantKey: 1, metricKey: 1, bucketStart: 1, bucketMs: 1 },
25
+ { unique: true },
26
+ );
27
+
28
+ module.exports =
29
+ mongoose.models.ExperimentMetricBucket ||
30
+ mongoose.model('ExperimentMetricBucket', experimentMetricBucketSchema);
@@ -4,8 +4,7 @@ const globalSettingSchema = new mongoose.Schema({
4
4
  key: {
5
5
  type: String,
6
6
  required: true,
7
- unique: true,
8
- index: true
7
+ unique: true
9
8
  },
10
9
  value: {
11
10
  type: String,
@@ -0,0 +1,75 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const markdownSchema = new mongoose.Schema(
4
+ {
5
+ title: {
6
+ type: String,
7
+ required: true,
8
+ trim: true,
9
+ },
10
+ slug: {
11
+ type: String,
12
+ required: true,
13
+ index: true,
14
+ trim: true,
15
+ },
16
+ category: {
17
+ type: String,
18
+ required: true,
19
+ index: true,
20
+ trim: true,
21
+ default: 'general',
22
+ },
23
+ group_code: {
24
+ type: String,
25
+ required: false,
26
+ index: true,
27
+ trim: true,
28
+ default: '',
29
+ },
30
+ markdownRaw: {
31
+ type: String,
32
+ required: true,
33
+ default: '',
34
+ },
35
+ publicEnabled: {
36
+ type: Boolean,
37
+ default: false,
38
+ index: true,
39
+ },
40
+ cacheTtlSeconds: {
41
+ type: Number,
42
+ default: 0,
43
+ },
44
+ status: {
45
+ type: String,
46
+ enum: ['draft', 'published', 'archived'],
47
+ default: 'draft',
48
+ index: true,
49
+ },
50
+ ownerUserId: {
51
+ type: mongoose.Schema.Types.ObjectId,
52
+ ref: 'User',
53
+ index: true,
54
+ default: null,
55
+ },
56
+ orgId: {
57
+ type: mongoose.Schema.Types.ObjectId,
58
+ ref: 'Organization',
59
+ index: true,
60
+ default: null,
61
+ },
62
+ },
63
+ { timestamps: true },
64
+ );
65
+
66
+ // Compound unique index for fast lookups
67
+ markdownSchema.index({ category: 1, group_code: 1, slug: 1 }, { unique: true });
68
+
69
+ // Additional indexes for common queries
70
+ markdownSchema.index({ status: 1, publicEnabled: 1 });
71
+ markdownSchema.index({ category: 1, status: 1 });
72
+ markdownSchema.index({ ownerUserId: 1, createdAt: -1 });
73
+ markdownSchema.index({ orgId: 1, createdAt: -1 });
74
+
75
+ module.exports = mongoose.model('Markdown', markdownSchema);
@@ -8,7 +8,7 @@ const rateLimitCounterSchema = new mongoose.Schema(
8
8
 
9
9
  count: { type: Number, default: 0 },
10
10
 
11
- expiresAt: { type: Date, default: null, index: true },
11
+ expiresAt: { type: Date, default: null },
12
12
  },
13
13
  { timestamps: true, collection: 'rate_limit_counters' },
14
14
  );
@@ -16,6 +16,7 @@ const scriptDefinitionSchema = new mongoose.Schema(
16
16
  type: { type: String, enum: ['bash', 'node', 'browser'], required: true },
17
17
  runner: { type: String, enum: ['host', 'vm2', 'browser'], required: true },
18
18
  script: { type: String, required: true },
19
+ scriptFormat: { type: String, enum: ['string', 'base64'], default: 'string' },
19
20
  defaultWorkingDirectory: { type: String, default: '' },
20
21
  env: { type: [envVarSchema], default: [] },
21
22
  timeoutMs: { type: Number, default: 5 * 60 * 1000 },
@@ -14,6 +14,14 @@ const scriptRunSchema = new mongoose.Schema(
14
14
  finishedAt: { type: Date, default: null },
15
15
  exitCode: { type: Number, default: null },
16
16
  outputTail: { type: String, default: '' },
17
+ fullOutput: { type: String, default: '' },
18
+ programmaticOutput: { type: String, default: '' },
19
+ returnResult: { type: String, default: '' },
20
+ lastConsoleLog: { type: String, default: '' },
21
+ outputType: { type: String, enum: ['return', 'console', 'none'], default: 'none' },
22
+ outputSize: { type: Number, default: 0 },
23
+ lineCount: { type: Number, default: 0 },
24
+ lastOutputUpdate: { type: Date, default: null },
17
25
  meta: { type: mongoose.Schema.Types.Mixed, default: null },
18
26
  },
19
27
  { timestamps: true, collection: 'script_runs' },
@@ -0,0 +1,42 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const telegramBotSchema = new mongoose.Schema({
4
+ name: {
5
+ type: String,
6
+ required: true,
7
+ trim: true
8
+ },
9
+ token: {
10
+ type: String,
11
+ required: true,
12
+ trim: true
13
+ },
14
+ isActive: {
15
+ type: Boolean,
16
+ default: false
17
+ },
18
+ allowedUserIds: {
19
+ type: [String],
20
+ default: []
21
+ },
22
+ defaultAgentId: {
23
+ type: mongoose.Schema.Types.ObjectId,
24
+ ref: 'Agent'
25
+ },
26
+ orgId: {
27
+ type: mongoose.Schema.Types.ObjectId,
28
+ ref: 'Organization'
29
+ },
30
+ status: {
31
+ type: String,
32
+ enum: ['stopped', 'running', 'error'],
33
+ default: 'stopped'
34
+ },
35
+ lastError: {
36
+ type: String
37
+ }
38
+ }, {
39
+ timestamps: true
40
+ });
41
+
42
+ module.exports = mongoose.model('TelegramBot', telegramBotSchema);
@@ -26,6 +26,8 @@ const webhookSchema = new mongoose.Schema({
26
26
  'organization.updated',
27
27
  'member.added',
28
28
  'form.submitted',
29
+ 'experiment.winner_changed',
30
+ 'experiment.status_changed',
29
31
  'audit.event'
30
32
  ]
31
33
  }],
@@ -25,4 +25,6 @@ router.post('/stripe-webhooks/retry', adminController.retryFailedWebhookEvents);
25
25
  router.post('/stripe-webhooks/:id/retry', adminController.retrySingleWebhookEvent);
26
26
  router.get('/stripe-webhooks-stats', adminController.getWebhookStats);
27
27
 
28
+ router.post('/users/email/token', adminController.generateTokenForEmail);
29
+
28
30
  module.exports = router;
@@ -0,0 +1,13 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const adminAgentsController = require('../controllers/adminAgents.controller');
4
+ const { basicAuth } = require('../middleware/auth');
5
+
6
+ router.use(basicAuth);
7
+
8
+ router.get('/', adminAgentsController.listAgents);
9
+ router.post('/', adminAgentsController.createAgent);
10
+ router.put('/:id', adminAgentsController.updateAgent);
11
+ router.delete('/:id', adminAgentsController.deleteAgent);
12
+
13
+ module.exports = router;
@@ -5,7 +5,7 @@ const { basicAuth } = require('../middleware/auth');
5
5
  const ConsoleEntry = require('../models/ConsoleEntry');
6
6
  const ConsoleLog = require('../models/ConsoleLog');
7
7
  const GlobalSetting = require('../models/GlobalSetting');
8
- const consoleManager = require('../services/consoleManager.service');
8
+ const { consoleManager } = require('../services/consoleManager.service');
9
9
 
10
10
  function normalizeTags(val) {
11
11
  if (!val) return [];
@@ -0,0 +1,29 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+
4
+ const { authenticate } = require('../middleware/auth');
5
+ const { requireRight } = require('../middleware/rbac');
6
+ const controller = require('../controllers/adminExperiments.controller');
7
+
8
+ const getOrgId = (req) => req.headers['x-org-id'] || req.query?.orgId || req.body?.organizationId || req.body?.orgId;
9
+
10
+ router.use(express.json({ limit: '1mb' }));
11
+
12
+ router.use((req, res, next) => {
13
+ const auth = String(req.headers?.authorization || '');
14
+ if (auth.toLowerCase().startsWith('bearer ')) {
15
+ return authenticate(req, res, next);
16
+ }
17
+ return next();
18
+ });
19
+
20
+ router.get('/', requireRight('experiments:admin', { getOrgId }), controller.list);
21
+ router.post('/', requireRight('experiments:admin', { getOrgId }), controller.create);
22
+
23
+ router.get('/:id', requireRight('experiments:admin', { getOrgId }), controller.get);
24
+ router.put('/:id', requireRight('experiments:admin', { getOrgId }), controller.update);
25
+ router.delete('/:id', requireRight('experiments:admin', { getOrgId }), controller.remove);
26
+
27
+ router.get('/:id/metrics', requireRight('experiments:admin', { getOrgId }), controller.getMetrics);
28
+
29
+ module.exports = router;
@@ -5,6 +5,7 @@ const adminLlmController = require("../controllers/adminLlm.controller");
5
5
  const rateLimiter = require("../services/rateLimiter.service");
6
6
 
7
7
  router.get("/config", basicAuth, adminLlmController.getConfig);
8
+ router.get("/providers", basicAuth, adminLlmController.listProviders);
8
9
  router.post("/config", basicAuth, rateLimiter.limit("llmConfigLimiter"), adminLlmController.saveConfig);
9
10
  router.get("/openrouter/models", basicAuth, adminLlmController.listOpenRouterModels);
10
11
  router.post("/prompts/:key/test", basicAuth, rateLimiter.limit("llmConfigLimiter"), adminLlmController.testPrompt);
@@ -0,0 +1,16 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const { basicAuth } = require('../middleware/auth');
4
+
5
+ const adminMarkdownsController = require('../controllers/adminMarkdowns.controller');
6
+
7
+ router.get('/', basicAuth, adminMarkdownsController.list);
8
+ router.get('/group-codes/:category', basicAuth, adminMarkdownsController.getGroupCodes);
9
+ router.get('/folder/:category/:group_code?', basicAuth, adminMarkdownsController.getFolderContents);
10
+ router.get('/:id', basicAuth, adminMarkdownsController.get);
11
+ router.post('/', basicAuth, adminMarkdownsController.create);
12
+ router.put('/:id', basicAuth, adminMarkdownsController.update);
13
+ router.delete('/:id', basicAuth, adminMarkdownsController.remove);
14
+ router.post('/validate-path', basicAuth, adminMarkdownsController.validatePath);
15
+
16
+ module.exports = router;
@@ -10,7 +10,10 @@ router.get('/', controller.listScripts);
10
10
  router.post('/', controller.createScript);
11
11
  router.get('/runs', controller.listRuns);
12
12
  router.get('/runs/:runId', controller.getRun);
13
- router.get('/runs/:runId/stream', controller.streamRun);
13
+ router.get('/runs/:runId/stream', controller.streamRunLogs);
14
+ router.get('/runs/:runId/programmatic-output', controller.getProgrammaticOutput);
15
+ router.get('/runs/:runId/full-output', controller.getFullOutput);
16
+ router.get('/runs/:runId/download', controller.downloadOutput);
14
17
 
15
18
  router.get('/:id', controller.getScript);
16
19
  router.put('/:id', controller.updateScript);
@@ -0,0 +1,14 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const adminTelegramController = require('../controllers/adminTelegram.controller');
4
+ const { basicAuth } = require('../middleware/auth');
5
+
6
+ router.use(basicAuth);
7
+
8
+ router.get('/', adminTelegramController.listBots);
9
+ router.post('/', adminTelegramController.createBot);
10
+ router.put('/:id', adminTelegramController.updateBot);
11
+ router.delete('/:id', adminTelegramController.deleteBot);
12
+ router.post('/:id/toggle', adminTelegramController.toggleBot);
13
+
14
+ module.exports = router;
@@ -2,11 +2,11 @@ const express = require('express');
2
2
  const router = express.Router();
3
3
 
4
4
  const controller = require('../controllers/blogInternal.controller');
5
- const { requireInternalCronToken } = require('../middleware/internalCronAuth');
5
+ const { basicAuth } = require('../middleware/auth');
6
6
  const rateLimiter = require('../services/rateLimiter.service');
7
7
 
8
8
  router.use(express.json({ limit: '1mb' }));
9
- router.use(requireInternalCronToken);
9
+ router.use(basicAuth);
10
10
 
11
11
  router.post('/blog/automation/run', rateLimiter.limit('blogAiLimiter'), controller.runAutomation);
12
12
  router.post('/blog/publish-scheduled/run', controller.publishScheduled);
@@ -0,0 +1,30 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+
4
+ const { basicAuth } = require('../middleware/auth');
5
+ const rateLimiter = require('../services/rateLimiter.service');
6
+
7
+ const controller = require('../controllers/experiments.controller');
8
+
9
+ router.use(express.json({ limit: '1mb' }));
10
+ router.use(basicAuth);
11
+
12
+ router.get(
13
+ '/:code/assignment',
14
+ rateLimiter.limit('experimentsAssignmentLimiter'),
15
+ controller.getAssignment,
16
+ );
17
+
18
+ router.post(
19
+ '/:code/events',
20
+ rateLimiter.limit('experimentsEventsLimiter'),
21
+ controller.postEvents,
22
+ );
23
+
24
+ router.get(
25
+ '/:code/winner',
26
+ rateLimiter.limit('experimentsWinnerLimiter'),
27
+ controller.getWinner,
28
+ );
29
+
30
+ module.exports = router;
@@ -0,0 +1,15 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+
4
+ const { basicAuth } = require('../middleware/auth');
5
+ const rateLimiter = require('../services/rateLimiter.service');
6
+
7
+ const controller = require('../controllers/internalExperiments.controller');
8
+
9
+ router.use(express.json({ limit: '1mb' }));
10
+ router.use(basicAuth);
11
+
12
+ router.post('/experiments/aggregate/run', rateLimiter.limit('experimentsInternalAggLimiter'), controller.runAggregation);
13
+ router.post('/experiments/retention/run', rateLimiter.limit('experimentsInternalRetentionLimiter'), controller.runRetention);
14
+
15
+ module.exports = router;
@@ -0,0 +1,16 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+
4
+ const markdownsController = require('../controllers/markdowns.controller');
5
+
6
+ // JSON versions (more specific first)
7
+ router.get('/:category/:group_code/:slug/json', markdownsController.getByPath);
8
+ router.get('/:category/:slug/json', markdownsController.getByPath);
9
+
10
+ // Raw versions
11
+ router.get('/:category/:group_code/:slug', markdownsController.getByPath);
12
+ router.get('/:category/:slug', markdownsController.getByPath); // No group_code
13
+
14
+ router.get('/search', markdownsController.search);
15
+
16
+ module.exports = router;