@intranefr/superbackend 1.5.0 → 1.5.1

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 (171) hide show
  1. package/.env.example +5 -0
  2. package/README.md +11 -0
  3. package/index.js +23 -0
  4. package/package.json +7 -2
  5. package/src/admin/endpointRegistry.js +120 -0
  6. package/src/controllers/admin.controller.js +22 -5
  7. package/src/controllers/adminBlockDefinitions.controller.js +127 -0
  8. package/src/controllers/adminBlockDefinitionsAi.controller.js +54 -0
  9. package/src/controllers/adminCache.controller.js +342 -0
  10. package/src/controllers/adminContextBlockDefinitions.controller.js +141 -0
  11. package/src/controllers/adminCrons.controller.js +388 -0
  12. package/src/controllers/adminDbBrowser.controller.js +124 -0
  13. package/src/controllers/adminEjsVirtual.controller.js +13 -3
  14. package/src/controllers/adminHeadless.controller.js +9 -2
  15. package/src/controllers/adminHealthChecks.controller.js +570 -0
  16. package/src/controllers/adminI18n.controller.js +51 -29
  17. package/src/controllers/adminLlm.controller.js +126 -2
  18. package/src/controllers/adminPages.controller.js +720 -0
  19. package/src/controllers/adminPagesContextBlocksAi.controller.js +54 -0
  20. package/src/controllers/adminProxy.controller.js +113 -0
  21. package/src/controllers/adminRateLimits.controller.js +138 -0
  22. package/src/controllers/adminRbac.controller.js +803 -0
  23. package/src/controllers/adminScripts.controller.js +93 -2
  24. package/src/controllers/adminSeoConfig.controller.js +71 -48
  25. package/src/controllers/blogAdmin.controller.js +279 -0
  26. package/src/controllers/blogAiAdmin.controller.js +224 -0
  27. package/src/controllers/blogAutomationAdmin.controller.js +141 -0
  28. package/src/controllers/blogInternal.controller.js +26 -0
  29. package/src/controllers/blogPublic.controller.js +89 -0
  30. package/src/controllers/fileManager.controller.js +190 -0
  31. package/src/controllers/fileManagerStoragePolicy.controller.js +23 -0
  32. package/src/controllers/healthChecksPublic.controller.js +196 -0
  33. package/src/controllers/metrics.controller.js +64 -4
  34. package/src/controllers/orgAdmin.controller.js +80 -0
  35. package/src/middleware/internalCronAuth.js +29 -0
  36. package/src/middleware/rbac.js +62 -0
  37. package/src/middleware.js +756 -48
  38. package/src/models/BlockDefinition.js +27 -0
  39. package/src/models/BlogAutomationLock.js +14 -0
  40. package/src/models/BlogAutomationRun.js +39 -0
  41. package/src/models/BlogPost.js +42 -0
  42. package/src/models/CacheEntry.js +26 -0
  43. package/src/models/ConsoleEntry.js +32 -0
  44. package/src/models/ConsoleLog.js +23 -0
  45. package/src/models/ContextBlockDefinition.js +33 -0
  46. package/src/models/CronExecution.js +47 -0
  47. package/src/models/CronJob.js +70 -0
  48. package/src/models/ExternalDbConnection.js +49 -0
  49. package/src/models/FileEntry.js +22 -0
  50. package/src/models/HealthAutoHealAttempt.js +57 -0
  51. package/src/models/HealthCheck.js +132 -0
  52. package/src/models/HealthCheckRun.js +51 -0
  53. package/src/models/HealthIncident.js +49 -0
  54. package/src/models/Page.js +95 -0
  55. package/src/models/PageCollection.js +42 -0
  56. package/src/models/ProxyEntry.js +66 -0
  57. package/src/models/RateLimitCounter.js +19 -0
  58. package/src/models/RateLimitMetricBucket.js +20 -0
  59. package/src/models/RbacGrant.js +25 -0
  60. package/src/models/RbacGroup.js +16 -0
  61. package/src/models/RbacGroupMember.js +13 -0
  62. package/src/models/RbacGroupRole.js +13 -0
  63. package/src/models/RbacRole.js +25 -0
  64. package/src/models/RbacUserRole.js +13 -0
  65. package/src/routes/adminBlog.routes.js +21 -0
  66. package/src/routes/adminBlogAi.routes.js +16 -0
  67. package/src/routes/adminBlogAutomation.routes.js +27 -0
  68. package/src/routes/adminCache.routes.js +20 -0
  69. package/src/routes/adminConsoleManager.routes.js +302 -0
  70. package/src/routes/adminCrons.routes.js +25 -0
  71. package/src/routes/adminDbBrowser.routes.js +65 -0
  72. package/src/routes/adminEjsVirtual.routes.js +2 -1
  73. package/src/routes/adminHeadless.routes.js +2 -1
  74. package/src/routes/adminHealthChecks.routes.js +28 -0
  75. package/src/routes/adminI18n.routes.js +4 -3
  76. package/src/routes/adminLlm.routes.js +4 -2
  77. package/src/routes/adminPages.routes.js +55 -0
  78. package/src/routes/adminProxy.routes.js +15 -0
  79. package/src/routes/adminRateLimits.routes.js +17 -0
  80. package/src/routes/adminRbac.routes.js +38 -0
  81. package/src/routes/adminSeoConfig.routes.js +5 -4
  82. package/src/routes/adminUiComponents.routes.js +2 -1
  83. package/src/routes/blogInternal.routes.js +14 -0
  84. package/src/routes/blogPublic.routes.js +9 -0
  85. package/src/routes/fileManager.routes.js +62 -0
  86. package/src/routes/fileManagerStoragePolicy.routes.js +9 -0
  87. package/src/routes/healthChecksPublic.routes.js +9 -0
  88. package/src/routes/log.routes.js +43 -60
  89. package/src/routes/metrics.routes.js +4 -2
  90. package/src/routes/orgAdmin.routes.js +1 -0
  91. package/src/routes/pages.routes.js +123 -0
  92. package/src/routes/proxy.routes.js +46 -0
  93. package/src/routes/rbac.routes.js +47 -0
  94. package/src/routes/webhook.routes.js +2 -1
  95. package/src/routes/workflows.routes.js +4 -0
  96. package/src/services/blockDefinitionsAi.service.js +247 -0
  97. package/src/services/blog.service.js +99 -0
  98. package/src/services/blogAutomation.service.js +978 -0
  99. package/src/services/blogCronsBootstrap.service.js +184 -0
  100. package/src/services/blogPublishing.service.js +58 -0
  101. package/src/services/cacheLayer.service.js +696 -0
  102. package/src/services/consoleManager.service.js +700 -0
  103. package/src/services/consoleOverride.service.js +6 -1
  104. package/src/services/cronScheduler.service.js +350 -0
  105. package/src/services/dbBrowser.service.js +536 -0
  106. package/src/services/ejsVirtual.service.js +102 -32
  107. package/src/services/fileManager.service.js +475 -0
  108. package/src/services/fileManagerStoragePolicy.service.js +285 -0
  109. package/src/services/healthChecks.service.js +650 -0
  110. package/src/services/healthChecksBootstrap.service.js +109 -0
  111. package/src/services/healthChecksScheduler.service.js +106 -0
  112. package/src/services/llmDefaults.service.js +190 -0
  113. package/src/services/migrationAssets/s3.js +2 -2
  114. package/src/services/pages.service.js +602 -0
  115. package/src/services/pagesContext.service.js +331 -0
  116. package/src/services/pagesContextBlocksAi.service.js +349 -0
  117. package/src/services/proxy.service.js +535 -0
  118. package/src/services/rateLimiter.service.js +623 -0
  119. package/src/services/rbac.service.js +212 -0
  120. package/src/services/scriptsRunner.service.js +1 -1
  121. package/src/services/uiComponentsAi.service.js +6 -19
  122. package/src/services/workflow.service.js +23 -8
  123. package/src/utils/orgRoles.js +14 -0
  124. package/src/utils/rbac/engine.js +60 -0
  125. package/src/utils/rbac/rightsRegistry.js +29 -0
  126. package/views/admin-blog-automation.ejs +877 -0
  127. package/views/admin-blog-edit.ejs +542 -0
  128. package/views/admin-blog.ejs +399 -0
  129. package/views/admin-cache.ejs +681 -0
  130. package/views/admin-console-manager.ejs +680 -0
  131. package/views/admin-crons.ejs +645 -0
  132. package/views/admin-db-browser.ejs +445 -0
  133. package/views/admin-ejs-virtual.ejs +16 -10
  134. package/views/admin-file-manager.ejs +942 -0
  135. package/views/admin-health-checks.ejs +725 -0
  136. package/views/admin-i18n.ejs +59 -5
  137. package/views/admin-llm.ejs +99 -1
  138. package/views/admin-organizations.ejs +163 -1
  139. package/views/admin-pages.ejs +2424 -0
  140. package/views/admin-proxy.ejs +491 -0
  141. package/views/admin-rate-limiter.ejs +625 -0
  142. package/views/admin-rbac.ejs +1331 -0
  143. package/views/admin-scripts.ejs +1 -1
  144. package/views/admin-seo-config.ejs +61 -7
  145. package/views/admin-ui-components.ejs +57 -25
  146. package/views/admin-workflows.ejs +7 -7
  147. package/views/file-manager.ejs +866 -0
  148. package/views/pages/blocks/contact.ejs +27 -0
  149. package/views/pages/blocks/cta.ejs +18 -0
  150. package/views/pages/blocks/faq.ejs +20 -0
  151. package/views/pages/blocks/features.ejs +19 -0
  152. package/views/pages/blocks/hero.ejs +13 -0
  153. package/views/pages/blocks/html.ejs +5 -0
  154. package/views/pages/blocks/image.ejs +14 -0
  155. package/views/pages/blocks/testimonials.ejs +26 -0
  156. package/views/pages/blocks/text.ejs +10 -0
  157. package/views/pages/layouts/default.ejs +51 -0
  158. package/views/pages/layouts/minimal.ejs +42 -0
  159. package/views/pages/layouts/sidebar.ejs +54 -0
  160. package/views/pages/partials/footer.ejs +13 -0
  161. package/views/pages/partials/header.ejs +12 -0
  162. package/views/pages/partials/sidebar.ejs +8 -0
  163. package/views/pages/runtime/page.ejs +10 -0
  164. package/views/pages/templates/article.ejs +20 -0
  165. package/views/pages/templates/default.ejs +12 -0
  166. package/views/pages/templates/landing.ejs +14 -0
  167. package/views/pages/templates/listing.ejs +15 -0
  168. package/views/partials/admin-image-upload-modal.ejs +221 -0
  169. package/views/partials/dashboard/nav-items.ejs +11 -0
  170. package/views/partials/llm-provider-model-picker.ejs +183 -0
  171. package/src/routes/llmUi.routes.js +0 -26
package/.env.example CHANGED
@@ -50,3 +50,8 @@ MAX_FILE_SIZE_HARD_CAP=10485760
50
50
  # Encryption key for encrypted settings (new preferred name)
51
51
  # SUPERBACKEND_ENCRYPTION_KEY=your-32-byte-encryption-key
52
52
  # Legacy fallback: SAASBACKEND_ENCRYPTION_KEY=your-32-byte-encryption-key
53
+
54
+ # Console Manager
55
+ # Set to 'false' to disable console manager initialization
56
+ # When disabled, console methods are not overridden and no console entries are tracked
57
+ # CONSOLE_MANAGER_ENABLED=true
package/README.md CHANGED
@@ -23,6 +23,13 @@ Node.js middleware that gives your project backend superpowers. Handles authenti
23
23
  - **Webhooks**: Outgoing webhook system for event-driven integrations
24
24
  - **Metrics & Activity**: Usage tracking and analytics for business insights
25
25
  - **Middleware Mode**: Drop-in Express middleware that preserves your app structure
26
+ - **Workflows System**: Node-based automation with LLM integration, conditionals, and HTTP calls
27
+ - **LLM UI Integration**: AI-powered UI components and conversational interfaces
28
+ - **Admin Scripts & Terminals**: Operational tooling for script execution and terminal management
29
+ - **Migration System**: Database migration and data transfer between environments
30
+ - **Upload Namespaces**: Advanced file organization with customizable storage rules
31
+ - **UI Components**: Project-scoped reusable UI widgets with browser SDK integration
32
+ - **UI Components AI Assistance**: AI-powered generation and iteration of UI component code
26
33
 
27
34
  ---
28
35
 
@@ -85,6 +92,10 @@ See the `docs/features/` directory for detailed guides:
85
92
  - [Core Configuration](docs/features/core-configuration.md)
86
93
  - [Admin API Usage](docs/features/admin-api-usage.md)
87
94
  - [Billing & Subscriptions](docs/features/billing-and-subscriptions.md)
95
+ - [Workflows System](docs/features/workflows-system.md)
96
+ - [LLM UI Integration](docs/features/llm-ui-integration.md)
97
+ - [Migration System](docs/features/migration-system.md)
98
+ - [Upload Namespaces](docs/features/upload-namespaces.md)
88
99
 
89
100
  ---
90
101
 
package/index.js CHANGED
@@ -62,6 +62,8 @@ const saasbackend = {
62
62
  storage: require("./src/services/storage"),
63
63
  i18n: require("./src/services/i18n.service"),
64
64
  audit: require("./src/services/audit.service"),
65
+ cacheLayer: require("./src/services/cacheLayer.service"),
66
+ rbac: require("./src/services/rbac.service"),
65
67
  globalSettings: require("./src/services/globalSettings.service"),
66
68
  jsonConfigs: require("./src/services/jsonConfigs.service"),
67
69
  assets: require("./src/services/assets.service"),
@@ -72,12 +74,24 @@ const saasbackend = {
72
74
  forms: require("./src/services/forms.service"),
73
75
  webhooks: require("./src/services/webhook.service"),
74
76
  workflow: require("./src/services/workflow.service"),
77
+ healthChecks: require("./src/services/healthChecks.service"),
78
+ dbBrowser: require("./src/services/dbBrowser.service"),
79
+ rateLimiter: require("./src/services/rateLimiter.service"),
75
80
  },
76
81
  models: {
77
82
  ActionEvent: require("./src/models/ActionEvent"),
78
83
  ActivityLog: require("./src/models/ActivityLog"),
79
84
  Asset: require("./src/models/Asset"),
80
85
  AuditEvent: require("./src/models/AuditEvent"),
86
+ CacheEntry: require("./src/models/CacheEntry"),
87
+ RateLimitCounter: require("./src/models/RateLimitCounter"),
88
+ RateLimitMetricBucket: require("./src/models/RateLimitMetricBucket"),
89
+ RbacRole: require("./src/models/RbacRole"),
90
+ RbacUserRole: require("./src/models/RbacUserRole"),
91
+ RbacGroup: require("./src/models/RbacGroup"),
92
+ RbacGroupMember: require("./src/models/RbacGroupMember"),
93
+ RbacGroupRole: require("./src/models/RbacGroupRole"),
94
+ RbacGrant: require("./src/models/RbacGrant"),
81
95
  EmailLog: require("./src/models/EmailLog"),
82
96
  ErrorAggregate: require("./src/models/ErrorAggregate"),
83
97
  FormSubmission: require("./src/models/FormSubmission"),
@@ -99,13 +113,22 @@ const saasbackend = {
99
113
  Webhook: require("./src/models/Webhook"),
100
114
  Workflow: require("./src/models/Workflow"),
101
115
  WorkflowExecution: require("./src/models/WorkflowExecution"),
116
+
117
+ HealthCheck: require("./src/models/HealthCheck"),
118
+ HealthCheckRun: require("./src/models/HealthCheckRun"),
119
+ HealthIncident: require("./src/models/HealthIncident"),
120
+ HealthAutoHealAttempt: require("./src/models/HealthAutoHealAttempt"),
121
+
122
+ ExternalDbConnection: require("./src/models/ExternalDbConnection"),
102
123
  },
103
124
  helpers: {
104
125
  auth: require("./src/middleware/auth"),
105
126
  org: require("./src/middleware/org"),
127
+ rbac: require("./src/middleware/rbac"),
106
128
  i18n: require("./src/services/i18n.service"),
107
129
  jsonConfigs: require("./src/services/jsonConfigs.service"),
108
130
  terminals: require("./src/services/terminalsWs.service"),
131
+ rateLimiter: require("./src/services/rateLimiter.service"),
109
132
  },
110
133
  };
111
134
 
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@intranefr/superbackend",
3
- "version": "1.5.0",
3
+ "version": "1.5.1",
4
4
  "description": "Node.js middleware that gives your project backend superpowers",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "start": "node server.js",
8
- "dev": "nodemon server.js",
8
+ "dev": "nodemon --verbose --ignore uploads --ignore '*.log' server.js",
9
9
  "start:minio": "docker compose -f compose.standalone.yml --profile minio-only up -d minio",
10
10
  "minio:envs": "node -e \"console.log(['S3_ENDPOINT=http://localhost:9000','S3_REGION=us-east-1','S3_ACCESS_KEY_ID=minioadmin','S3_SECRET_ACCESS_KEY=minioadmin','S3_BUCKET=saasbackend','S3_FORCE_PATH_STYLE=true'].join('\\n'))\"",
11
11
  "build:sdk:error-tracking:browser": "esbuild sdk/error-tracking/browser/src/embed.js --bundle --format=iife --global-name=saasbackendErrorTrackingEmbed --outfile=sdk/error-tracking/browser/dist/embed.iife.js",
@@ -36,14 +36,19 @@
36
36
  "bcryptjs": "^2.4.3",
37
37
  "cheerio": "^1.0.0-rc.12",
38
38
  "cors": "^2.8.5",
39
+ "cron-parser": "3.5.0",
39
40
  "dotenv": "^16.3.1",
40
41
  "ejs": "^3.1.9",
41
42
  "express": "^4.18.2",
42
43
  "jsonwebtoken": "^9.0.2",
44
+ "marked": "^4.3.0",
43
45
  "mongoose": "^8.0.0",
44
46
  "multer": "^1.4.5-lts.1",
47
+ "mysql2": "^3.16.1",
48
+ "node-cron": "^4.2.1",
45
49
  "node-pty": "^1.1.0",
46
50
  "openai": "^4.0.0",
51
+ "redis": "^4.7.1",
47
52
  "resend": "^6.4.0",
48
53
  "ssh2-sftp-client": "^12.0.1",
49
54
  "stripe": "^14.0.0",
@@ -70,6 +70,75 @@ const endpointRegistry = [
70
70
  },
71
71
  ],
72
72
  },
73
+ {
74
+ id: "admin-pages",
75
+ title: "Pages (Admin)",
76
+ endpoints: [
77
+ {
78
+ id: "pages-context-block-definitions-list",
79
+ method: "GET",
80
+ path: "/api/admin/pages/context-block-definitions",
81
+ auth: "basic",
82
+ },
83
+ {
84
+ id: "pages-context-block-definitions-create",
85
+ method: "POST",
86
+ path: "/api/admin/pages/context-block-definitions",
87
+ auth: "basic",
88
+ bodyExample: {
89
+ code: "blog-post-by-slug",
90
+ label: "Blog post by slug",
91
+ description: "Used by blog post page",
92
+ type: "context.db_query",
93
+ props: {
94
+ assignTo: "post",
95
+ model: "BlogPost",
96
+ op: "findOne",
97
+ filter: { slug: { $ctx: "params.slug" } },
98
+ },
99
+ },
100
+ },
101
+ {
102
+ id: "pages-context-block-definitions-get",
103
+ method: "GET",
104
+ path: "/api/admin/pages/context-block-definitions/:code",
105
+ auth: "basic",
106
+ },
107
+ {
108
+ id: "pages-context-block-definitions-update",
109
+ method: "PUT",
110
+ path: "/api/admin/pages/context-block-definitions/:code",
111
+ auth: "basic",
112
+ bodyExample: {
113
+ label: "Blog post by slug (updated)",
114
+ description: "Used by blog post page",
115
+ type: "context.db_query",
116
+ props: {
117
+ assignTo: "post",
118
+ model: "BlogPost",
119
+ op: "findOne",
120
+ filter: { slug: { $ctx: "params.slug" } },
121
+ },
122
+ },
123
+ },
124
+ {
125
+ id: "pages-context-block-definitions-delete",
126
+ method: "DELETE",
127
+ path: "/api/admin/pages/context-block-definitions/:code",
128
+ auth: "basic",
129
+ },
130
+ {
131
+ id: "pages-context-blocks-ai-generate",
132
+ method: "POST",
133
+ path: "/api/admin/pages/ai/context-blocks/generate",
134
+ auth: "basic",
135
+ bodyExample: {
136
+ prompt: "Load the published BlogPost for params.slug into vars.post. Add cache 30s.",
137
+ blockType: "context.db_query",
138
+ },
139
+ },
140
+ ],
141
+ },
73
142
  {
74
143
  id: "ejs-virtual",
75
144
  title: "EJS Virtual Codebase",
@@ -295,6 +364,57 @@ const endpointRegistry = [
295
364
  },
296
365
  ],
297
366
  },
367
+ {
368
+ id: "rate-limits",
369
+ title: "Rate Limiter",
370
+ endpoints: [
371
+ {
372
+ id: "rate-limits-list",
373
+ method: "GET",
374
+ path: "/api/admin/rate-limits",
375
+ auth: "basic",
376
+ },
377
+ {
378
+ id: "rate-limits-config-get",
379
+ method: "GET",
380
+ path: "/api/admin/rate-limits/config",
381
+ auth: "basic",
382
+ },
383
+ {
384
+ id: "rate-limits-config-update",
385
+ method: "PUT",
386
+ path: "/api/admin/rate-limits/config",
387
+ auth: "basic",
388
+ bodyExample: { jsonRaw: "{\n \"version\": 1,\n \"defaults\": {},\n \"limiters\": {}\n}" },
389
+ },
390
+ {
391
+ id: "rate-limits-metrics",
392
+ method: "GET",
393
+ path: "/api/admin/rate-limits/metrics?start=2026-01-01T00:00:00.000Z&end=2026-01-02T00:00:00.000Z",
394
+ auth: "basic",
395
+ },
396
+ {
397
+ id: "rate-limits-bulk-enabled",
398
+ method: "POST",
399
+ path: "/api/admin/rate-limits/bulk-enabled",
400
+ auth: "basic",
401
+ bodyExample: { enabled: true, all: true },
402
+ },
403
+ {
404
+ id: "rate-limits-limiter-update",
405
+ method: "PUT",
406
+ path: "/api/admin/rate-limits/globalApiLimiter",
407
+ auth: "basic",
408
+ bodyExample: { override: { enabled: true, mode: "reportOnly", limit: { max: 60, windowMs: 60000 } } },
409
+ },
410
+ {
411
+ id: "rate-limits-limiter-reset",
412
+ method: "POST",
413
+ path: "/api/admin/rate-limits/globalApiLimiter/reset",
414
+ auth: "basic",
415
+ },
416
+ ],
417
+ },
298
418
  ];
299
419
 
300
420
  module.exports = endpointRegistry;
@@ -17,12 +17,29 @@ const { auditMiddleware } = require('../services/auditLogger');
17
17
 
18
18
  // Get all users
19
19
  const getUsers = asyncHandler(async (req, res) => {
20
- const users = await User.find()
21
- .select('-passwordHash')
22
- .sort({ createdAt: -1 })
23
- .limit(100);
20
+ const { limit, offset } = req.query;
21
+
22
+ const parsedLimit = Math.min(500, Math.max(1, parseInt(limit, 10) || 50));
23
+ const parsedOffset = Math.max(0, parseInt(offset, 10) || 0);
24
+
25
+ const [users, total] = await Promise.all([
26
+ User.find()
27
+ .select('-passwordHash -passwordResetToken -passwordResetExpiry')
28
+ .sort({ createdAt: -1 })
29
+ .limit(parsedLimit)
30
+ .skip(parsedOffset)
31
+ .lean(),
32
+ User.countDocuments()
33
+ ]);
24
34
 
25
- res.json(users.map(u => u.toJSON()));
35
+ res.json({
36
+ users,
37
+ pagination: {
38
+ total,
39
+ limit: parsedLimit,
40
+ offset: parsedOffset,
41
+ },
42
+ });
26
43
  });
27
44
 
28
45
  // Register new user (admin only)
@@ -0,0 +1,127 @@
1
+ const BlockDefinition = require('../models/BlockDefinition');
2
+
3
+ function parseBool(value, fallback) {
4
+ if (value === undefined) return fallback;
5
+ if (typeof value === 'boolean') return value;
6
+ const v = String(value).trim().toLowerCase();
7
+ if (v === 'true' || v === '1' || v === 'yes') return true;
8
+ if (v === 'false' || v === '0' || v === 'no') return false;
9
+ return fallback;
10
+ }
11
+
12
+ function normalizeCode(code) {
13
+ return String(code || '').trim().toLowerCase();
14
+ }
15
+
16
+ function normalizeFields(fields) {
17
+ if (!fields) return {};
18
+ if (typeof fields !== 'object' || Array.isArray(fields)) return null;
19
+ return fields;
20
+ }
21
+
22
+ exports.list = async (req, res) => {
23
+ try {
24
+ const onlyActive = parseBool(req.query?.active, null);
25
+ const filter = {};
26
+ if (onlyActive !== null) filter.isActive = onlyActive;
27
+
28
+ const items = await BlockDefinition.find(filter).sort({ updatedAt: -1 }).lean();
29
+ return res.json({ items });
30
+ } catch (error) {
31
+ console.error('[adminBlockDefinitions] list error:', error);
32
+ return res.status(500).json({ error: 'Failed to list block definitions' });
33
+ }
34
+ };
35
+
36
+ exports.create = async (req, res) => {
37
+ try {
38
+ const code = normalizeCode(req.body?.code);
39
+ const label = String(req.body?.label || '').trim();
40
+ if (!code) return res.status(400).json({ error: 'code is required' });
41
+ if (!label) return res.status(400).json({ error: 'label is required' });
42
+
43
+ const fields = normalizeFields(req.body?.fields);
44
+ if (fields === null) return res.status(400).json({ error: 'fields must be an object' });
45
+
46
+ const doc = await BlockDefinition.create({
47
+ code,
48
+ label,
49
+ description: String(req.body?.description || ''),
50
+ fields: fields || {},
51
+ version: Number(req.body?.version || 1) || 1,
52
+ isActive: parseBool(req.body?.isActive, true),
53
+ });
54
+
55
+ return res.status(201).json({ item: doc.toObject() });
56
+ } catch (error) {
57
+ console.error('[adminBlockDefinitions] create error:', error);
58
+ if (error?.name === 'ValidationError') return res.status(400).json({ error: error.message });
59
+ if (error?.code === 11000) return res.status(409).json({ error: 'Block already exists' });
60
+ return res.status(500).json({ error: 'Failed to create block definition' });
61
+ }
62
+ };
63
+
64
+ exports.get = async (req, res) => {
65
+ try {
66
+ const code = normalizeCode(req.params?.code);
67
+ const item = await BlockDefinition.findOne({ code }).lean();
68
+ if (!item) return res.status(404).json({ error: 'Block not found' });
69
+ return res.json({ item });
70
+ } catch (error) {
71
+ console.error('[adminBlockDefinitions] get error:', error);
72
+ return res.status(500).json({ error: 'Failed to load block definition' });
73
+ }
74
+ };
75
+
76
+ exports.update = async (req, res) => {
77
+ try {
78
+ const code = normalizeCode(req.params?.code);
79
+ const doc = await BlockDefinition.findOne({ code });
80
+ if (!doc) return res.status(404).json({ error: 'Block not found' });
81
+
82
+ if (req.body?.label !== undefined) {
83
+ const label = String(req.body.label || '').trim();
84
+ if (!label) return res.status(400).json({ error: 'label is required' });
85
+ doc.label = label;
86
+ }
87
+
88
+ if (req.body?.description !== undefined) doc.description = String(req.body.description || '');
89
+
90
+ if (req.body?.fields !== undefined) {
91
+ const fields = normalizeFields(req.body.fields);
92
+ if (fields === null) return res.status(400).json({ error: 'fields must be an object' });
93
+ doc.fields = fields;
94
+ }
95
+
96
+ if (req.body?.version !== undefined) {
97
+ const v = Number(req.body.version);
98
+ if (!Number.isFinite(v) || v < 1) return res.status(400).json({ error: 'version must be a positive number' });
99
+ doc.version = v;
100
+ } else {
101
+ doc.version = Number(doc.version || 1) + 1;
102
+ }
103
+
104
+ if (req.body?.isActive !== undefined) doc.isActive = Boolean(req.body.isActive);
105
+
106
+ await doc.save();
107
+ return res.json({ item: doc.toObject() });
108
+ } catch (error) {
109
+ console.error('[adminBlockDefinitions] update error:', error);
110
+ if (error?.name === 'ValidationError') return res.status(400).json({ error: error.message });
111
+ return res.status(500).json({ error: 'Failed to update block definition' });
112
+ }
113
+ };
114
+
115
+ exports.remove = async (req, res) => {
116
+ try {
117
+ const code = normalizeCode(req.params?.code);
118
+ const doc = await BlockDefinition.findOne({ code });
119
+ if (!doc) return res.status(404).json({ error: 'Block not found' });
120
+
121
+ await BlockDefinition.deleteOne({ _id: doc._id });
122
+ return res.json({ success: true });
123
+ } catch (error) {
124
+ console.error('[adminBlockDefinitions] remove error:', error);
125
+ return res.status(500).json({ error: 'Failed to delete block definition' });
126
+ }
127
+ };
@@ -0,0 +1,54 @@
1
+ const {
2
+ generateBlockDefinition,
3
+ proposeBlockDefinitionEdit,
4
+ } = require('../services/blockDefinitionsAi.service');
5
+
6
+ const { getBasicAuthActor } = require('../services/audit.service');
7
+
8
+ function handleError(res, err) {
9
+ const code = err && err.code;
10
+ if (code === 'VALIDATION') return res.status(400).json({ error: err.message });
11
+ if (code === 'NOT_FOUND') return res.status(404).json({ error: err.message });
12
+ if (code === 'AI_INVALID') return res.status(500).json({ error: err.message });
13
+ return res.status(500).json({ error: err.message || 'Operation failed' });
14
+ }
15
+
16
+ exports.generate = async (req, res) => {
17
+ try {
18
+ const actor = getBasicAuthActor(req);
19
+ const { prompt, providerKey, model } = req.body || {};
20
+
21
+ const result = await generateBlockDefinition({
22
+ prompt,
23
+ providerKey,
24
+ model,
25
+ actor,
26
+ });
27
+
28
+ return res.json(result);
29
+ } catch (err) {
30
+ console.error('[adminBlockDefinitionsAi] generate error', err);
31
+ return handleError(res, err);
32
+ }
33
+ };
34
+
35
+ exports.propose = async (req, res) => {
36
+ try {
37
+ const actor = getBasicAuthActor(req);
38
+ const { code } = req.params;
39
+ const { prompt, providerKey, model } = req.body || {};
40
+
41
+ const result = await proposeBlockDefinitionEdit({
42
+ code,
43
+ prompt,
44
+ providerKey,
45
+ model,
46
+ actor,
47
+ });
48
+
49
+ return res.json(result);
50
+ } catch (err) {
51
+ console.error('[adminBlockDefinitionsAi] propose error', err);
52
+ return handleError(res, err);
53
+ }
54
+ };