@intranefr/superbackend 1.4.4 → 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 (195) hide show
  1. package/.env.example +5 -0
  2. package/README.md +11 -0
  3. package/index.js +39 -1
  4. package/package.json +11 -3
  5. package/public/sdk/ui-components.iife.js +191 -0
  6. package/sdk/ui-components/browser/src/index.js +228 -0
  7. package/src/admin/endpointRegistry.js +120 -0
  8. package/src/controllers/admin.controller.js +111 -5
  9. package/src/controllers/adminBlockDefinitions.controller.js +127 -0
  10. package/src/controllers/adminBlockDefinitionsAi.controller.js +54 -0
  11. package/src/controllers/adminCache.controller.js +342 -0
  12. package/src/controllers/adminContextBlockDefinitions.controller.js +141 -0
  13. package/src/controllers/adminCrons.controller.js +388 -0
  14. package/src/controllers/adminDbBrowser.controller.js +124 -0
  15. package/src/controllers/adminEjsVirtual.controller.js +13 -3
  16. package/src/controllers/adminHeadless.controller.js +91 -2
  17. package/src/controllers/adminHealthChecks.controller.js +570 -0
  18. package/src/controllers/adminI18n.controller.js +51 -29
  19. package/src/controllers/adminLlm.controller.js +126 -2
  20. package/src/controllers/adminPages.controller.js +720 -0
  21. package/src/controllers/adminPagesContextBlocksAi.controller.js +54 -0
  22. package/src/controllers/adminProxy.controller.js +113 -0
  23. package/src/controllers/adminRateLimits.controller.js +138 -0
  24. package/src/controllers/adminRbac.controller.js +803 -0
  25. package/src/controllers/adminScripts.controller.js +320 -0
  26. package/src/controllers/adminSeoConfig.controller.js +71 -48
  27. package/src/controllers/adminTerminals.controller.js +39 -0
  28. package/src/controllers/adminUiComponents.controller.js +315 -0
  29. package/src/controllers/adminUiComponentsAi.controller.js +34 -0
  30. package/src/controllers/blogAdmin.controller.js +279 -0
  31. package/src/controllers/blogAiAdmin.controller.js +224 -0
  32. package/src/controllers/blogAutomationAdmin.controller.js +141 -0
  33. package/src/controllers/blogInternal.controller.js +26 -0
  34. package/src/controllers/blogPublic.controller.js +89 -0
  35. package/src/controllers/fileManager.controller.js +190 -0
  36. package/src/controllers/fileManagerStoragePolicy.controller.js +23 -0
  37. package/src/controllers/healthChecksPublic.controller.js +196 -0
  38. package/src/controllers/metrics.controller.js +64 -4
  39. package/src/controllers/orgAdmin.controller.js +366 -0
  40. package/src/controllers/uiComponentsPublic.controller.js +118 -0
  41. package/src/middleware/auth.js +7 -0
  42. package/src/middleware/internalCronAuth.js +29 -0
  43. package/src/middleware/rbac.js +62 -0
  44. package/src/middleware.js +879 -56
  45. package/src/models/BlockDefinition.js +27 -0
  46. package/src/models/BlogAutomationLock.js +14 -0
  47. package/src/models/BlogAutomationRun.js +39 -0
  48. package/src/models/BlogPost.js +42 -0
  49. package/src/models/CacheEntry.js +26 -0
  50. package/src/models/ConsoleEntry.js +32 -0
  51. package/src/models/ConsoleLog.js +23 -0
  52. package/src/models/ContextBlockDefinition.js +33 -0
  53. package/src/models/CronExecution.js +47 -0
  54. package/src/models/CronJob.js +70 -0
  55. package/src/models/ExternalDbConnection.js +49 -0
  56. package/src/models/FileEntry.js +22 -0
  57. package/src/models/HeadlessModelDefinition.js +10 -0
  58. package/src/models/HealthAutoHealAttempt.js +57 -0
  59. package/src/models/HealthCheck.js +132 -0
  60. package/src/models/HealthCheckRun.js +51 -0
  61. package/src/models/HealthIncident.js +49 -0
  62. package/src/models/Page.js +95 -0
  63. package/src/models/PageCollection.js +42 -0
  64. package/src/models/ProxyEntry.js +66 -0
  65. package/src/models/RateLimitCounter.js +19 -0
  66. package/src/models/RateLimitMetricBucket.js +20 -0
  67. package/src/models/RbacGrant.js +25 -0
  68. package/src/models/RbacGroup.js +16 -0
  69. package/src/models/RbacGroupMember.js +13 -0
  70. package/src/models/RbacGroupRole.js +13 -0
  71. package/src/models/RbacRole.js +25 -0
  72. package/src/models/RbacUserRole.js +13 -0
  73. package/src/models/ScriptDefinition.js +42 -0
  74. package/src/models/ScriptRun.js +22 -0
  75. package/src/models/UiComponent.js +29 -0
  76. package/src/models/UiComponentProject.js +26 -0
  77. package/src/models/UiComponentProjectComponent.js +18 -0
  78. package/src/routes/admin.routes.js +1 -0
  79. package/src/routes/adminBlog.routes.js +21 -0
  80. package/src/routes/adminBlogAi.routes.js +16 -0
  81. package/src/routes/adminBlogAutomation.routes.js +27 -0
  82. package/src/routes/adminCache.routes.js +20 -0
  83. package/src/routes/adminConsoleManager.routes.js +302 -0
  84. package/src/routes/adminCrons.routes.js +25 -0
  85. package/src/routes/adminDbBrowser.routes.js +65 -0
  86. package/src/routes/adminEjsVirtual.routes.js +2 -1
  87. package/src/routes/adminHeadless.routes.js +8 -1
  88. package/src/routes/adminHealthChecks.routes.js +28 -0
  89. package/src/routes/adminI18n.routes.js +4 -3
  90. package/src/routes/adminLlm.routes.js +4 -2
  91. package/src/routes/adminPages.routes.js +55 -0
  92. package/src/routes/adminProxy.routes.js +15 -0
  93. package/src/routes/adminRateLimits.routes.js +17 -0
  94. package/src/routes/adminRbac.routes.js +38 -0
  95. package/src/routes/adminScripts.routes.js +21 -0
  96. package/src/routes/adminSeoConfig.routes.js +5 -4
  97. package/src/routes/adminTerminals.routes.js +13 -0
  98. package/src/routes/adminUiComponents.routes.js +30 -0
  99. package/src/routes/blogInternal.routes.js +14 -0
  100. package/src/routes/blogPublic.routes.js +9 -0
  101. package/src/routes/fileManager.routes.js +62 -0
  102. package/src/routes/fileManagerStoragePolicy.routes.js +9 -0
  103. package/src/routes/healthChecksPublic.routes.js +9 -0
  104. package/src/routes/log.routes.js +43 -60
  105. package/src/routes/metrics.routes.js +4 -2
  106. package/src/routes/orgAdmin.routes.js +6 -0
  107. package/src/routes/pages.routes.js +123 -0
  108. package/src/routes/proxy.routes.js +46 -0
  109. package/src/routes/rbac.routes.js +47 -0
  110. package/src/routes/uiComponentsPublic.routes.js +9 -0
  111. package/src/routes/webhook.routes.js +2 -1
  112. package/src/routes/workflows.routes.js +4 -0
  113. package/src/services/blockDefinitionsAi.service.js +247 -0
  114. package/src/services/blog.service.js +99 -0
  115. package/src/services/blogAutomation.service.js +978 -0
  116. package/src/services/blogCronsBootstrap.service.js +184 -0
  117. package/src/services/blogPublishing.service.js +58 -0
  118. package/src/services/cacheLayer.service.js +696 -0
  119. package/src/services/consoleManager.service.js +700 -0
  120. package/src/services/consoleOverride.service.js +6 -1
  121. package/src/services/cronScheduler.service.js +350 -0
  122. package/src/services/dbBrowser.service.js +536 -0
  123. package/src/services/ejsVirtual.service.js +102 -32
  124. package/src/services/fileManager.service.js +475 -0
  125. package/src/services/fileManagerStoragePolicy.service.js +285 -0
  126. package/src/services/headlessExternalModels.service.js +292 -0
  127. package/src/services/headlessModels.service.js +26 -6
  128. package/src/services/healthChecks.service.js +650 -0
  129. package/src/services/healthChecksBootstrap.service.js +109 -0
  130. package/src/services/healthChecksScheduler.service.js +106 -0
  131. package/src/services/llmDefaults.service.js +190 -0
  132. package/src/services/migrationAssets/s3.js +2 -2
  133. package/src/services/pages.service.js +602 -0
  134. package/src/services/pagesContext.service.js +331 -0
  135. package/src/services/pagesContextBlocksAi.service.js +349 -0
  136. package/src/services/proxy.service.js +535 -0
  137. package/src/services/rateLimiter.service.js +623 -0
  138. package/src/services/rbac.service.js +212 -0
  139. package/src/services/scriptsRunner.service.js +259 -0
  140. package/src/services/terminals.service.js +152 -0
  141. package/src/services/terminalsWs.service.js +100 -0
  142. package/src/services/uiComponentsAi.service.js +299 -0
  143. package/src/services/uiComponentsCrypto.service.js +39 -0
  144. package/src/services/workflow.service.js +23 -8
  145. package/src/utils/orgRoles.js +14 -0
  146. package/src/utils/rbac/engine.js +60 -0
  147. package/src/utils/rbac/rightsRegistry.js +29 -0
  148. package/views/admin-blog-automation.ejs +877 -0
  149. package/views/admin-blog-edit.ejs +542 -0
  150. package/views/admin-blog.ejs +399 -0
  151. package/views/admin-cache.ejs +681 -0
  152. package/views/admin-console-manager.ejs +680 -0
  153. package/views/admin-crons.ejs +645 -0
  154. package/views/admin-db-browser.ejs +445 -0
  155. package/views/admin-ejs-virtual.ejs +16 -10
  156. package/views/admin-file-manager.ejs +942 -0
  157. package/views/admin-headless.ejs +294 -24
  158. package/views/admin-health-checks.ejs +725 -0
  159. package/views/admin-i18n.ejs +59 -5
  160. package/views/admin-llm.ejs +99 -1
  161. package/views/admin-organizations.ejs +528 -10
  162. package/views/admin-pages.ejs +2424 -0
  163. package/views/admin-proxy.ejs +491 -0
  164. package/views/admin-rate-limiter.ejs +625 -0
  165. package/views/admin-rbac.ejs +1331 -0
  166. package/views/admin-scripts.ejs +497 -0
  167. package/views/admin-seo-config.ejs +61 -7
  168. package/views/admin-terminals.ejs +328 -0
  169. package/views/admin-ui-components.ejs +741 -0
  170. package/views/admin-users.ejs +261 -4
  171. package/views/admin-workflows.ejs +7 -7
  172. package/views/file-manager.ejs +866 -0
  173. package/views/pages/blocks/contact.ejs +27 -0
  174. package/views/pages/blocks/cta.ejs +18 -0
  175. package/views/pages/blocks/faq.ejs +20 -0
  176. package/views/pages/blocks/features.ejs +19 -0
  177. package/views/pages/blocks/hero.ejs +13 -0
  178. package/views/pages/blocks/html.ejs +5 -0
  179. package/views/pages/blocks/image.ejs +14 -0
  180. package/views/pages/blocks/testimonials.ejs +26 -0
  181. package/views/pages/blocks/text.ejs +10 -0
  182. package/views/pages/layouts/default.ejs +51 -0
  183. package/views/pages/layouts/minimal.ejs +42 -0
  184. package/views/pages/layouts/sidebar.ejs +54 -0
  185. package/views/pages/partials/footer.ejs +13 -0
  186. package/views/pages/partials/header.ejs +12 -0
  187. package/views/pages/partials/sidebar.ejs +8 -0
  188. package/views/pages/runtime/page.ejs +10 -0
  189. package/views/pages/templates/article.ejs +20 -0
  190. package/views/pages/templates/default.ejs +12 -0
  191. package/views/pages/templates/landing.ejs +14 -0
  192. package/views/pages/templates/listing.ejs +15 -0
  193. package/views/partials/admin-image-upload-modal.ejs +221 -0
  194. package/views/partials/dashboard/nav-items.ejs +14 -0
  195. package/views/partials/llm-provider-model-picker.ejs +183 -0
@@ -0,0 +1,51 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const healthCheckRunSchema = new mongoose.Schema(
4
+ {
5
+ healthCheckId: {
6
+ type: mongoose.Schema.Types.ObjectId,
7
+ ref: 'HealthCheck',
8
+ required: true,
9
+ index: true,
10
+ },
11
+
12
+ status: {
13
+ type: String,
14
+ enum: ['running', 'healthy', 'unhealthy', 'timed_out', 'error'],
15
+ default: 'running',
16
+ index: true,
17
+ },
18
+
19
+ attempt: { type: Number, default: 0 },
20
+
21
+ startedAt: { type: Date, default: Date.now, index: true },
22
+ finishedAt: { type: Date },
23
+ durationMs: { type: Number },
24
+
25
+ latencyMs: { type: Number },
26
+
27
+ httpStatusCode: { type: Number },
28
+ httpResponseHeaders: { type: mongoose.Schema.Types.Mixed },
29
+ responseBodySnippet: { type: String },
30
+
31
+ reason: { type: String },
32
+ errorMessage: { type: String },
33
+
34
+ incidentId: { type: mongoose.Schema.Types.ObjectId, ref: 'HealthIncident' },
35
+ },
36
+ { timestamps: true, collection: 'health_check_runs' },
37
+ );
38
+
39
+ healthCheckRunSchema.index({ healthCheckId: 1, startedAt: -1 });
40
+ healthCheckRunSchema.index({ status: 1, startedAt: -1 });
41
+
42
+ healthCheckRunSchema.pre('save', function preSave(next) {
43
+ if (this.isModified('finishedAt') && this.finishedAt && this.startedAt) {
44
+ this.durationMs = this.finishedAt.getTime() - this.startedAt.getTime();
45
+ }
46
+ next();
47
+ });
48
+
49
+ module.exports =
50
+ mongoose.models.HealthCheckRun ||
51
+ mongoose.model('HealthCheckRun', healthCheckRunSchema);
@@ -0,0 +1,49 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const healthIncidentSchema = new mongoose.Schema(
4
+ {
5
+ healthCheckId: {
6
+ type: mongoose.Schema.Types.ObjectId,
7
+ ref: 'HealthCheck',
8
+ required: true,
9
+ index: true,
10
+ },
11
+
12
+ status: {
13
+ type: String,
14
+ enum: ['open', 'acknowledged', 'resolved'],
15
+ default: 'open',
16
+ index: true,
17
+ },
18
+
19
+ severity: {
20
+ type: String,
21
+ enum: ['warning', 'critical'],
22
+ default: 'warning',
23
+ },
24
+
25
+ openedAt: { type: Date, default: Date.now, index: true },
26
+ acknowledgedAt: { type: Date },
27
+ resolvedAt: { type: Date },
28
+ lastSeenAt: { type: Date, default: Date.now },
29
+
30
+ consecutiveFailureCount: { type: Number, default: 0 },
31
+ consecutiveSuccessCount: { type: Number, default: 0 },
32
+
33
+ lastRunId: { type: mongoose.Schema.Types.ObjectId, ref: 'HealthCheckRun' },
34
+
35
+ summary: { type: String, default: '' },
36
+ lastError: { type: String, default: '' },
37
+
38
+ autoHealAttemptCount: { type: Number, default: 0 },
39
+ lastAutoHealAttemptAt: { type: Date },
40
+ },
41
+ { timestamps: true, collection: 'health_incidents' },
42
+ );
43
+
44
+ healthIncidentSchema.index({ healthCheckId: 1, openedAt: -1 });
45
+ healthIncidentSchema.index({ status: 1, openedAt: -1 });
46
+
47
+ module.exports =
48
+ mongoose.models.HealthIncident ||
49
+ mongoose.model('HealthIncident', healthIncidentSchema);
@@ -0,0 +1,95 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const seoMetaSchema = new mongoose.Schema(
4
+ {
5
+ title: { type: String, default: '' },
6
+ description: { type: String, default: '' },
7
+ keywords: { type: String, default: '' },
8
+ ogImage: { type: String, default: '' },
9
+ canonicalUrl: { type: String, default: '' },
10
+ },
11
+ { _id: false },
12
+ );
13
+
14
+ const pageSchema = new mongoose.Schema(
15
+ {
16
+ slug: {
17
+ type: String,
18
+ required: true,
19
+ index: true,
20
+ },
21
+ collectionId: {
22
+ type: mongoose.Schema.Types.ObjectId,
23
+ ref: 'PageCollection',
24
+ default: null,
25
+ index: true,
26
+ },
27
+ title: {
28
+ type: String,
29
+ required: true,
30
+ },
31
+ templateKey: {
32
+ type: String,
33
+ default: 'default',
34
+ },
35
+ layoutKey: {
36
+ type: String,
37
+ default: 'default',
38
+ },
39
+ blocks: {
40
+ type: mongoose.Schema.Types.Mixed,
41
+ default: [],
42
+ },
43
+ repeat: {
44
+ type: mongoose.Schema.Types.Mixed,
45
+ default: null,
46
+ },
47
+ customCss: {
48
+ type: String,
49
+ default: '',
50
+ },
51
+ customJs: {
52
+ type: String,
53
+ default: '',
54
+ },
55
+ seoMeta: {
56
+ type: seoMetaSchema,
57
+ default: () => ({}),
58
+ },
59
+ tenantId: {
60
+ type: mongoose.Schema.Types.ObjectId,
61
+ ref: 'Organization',
62
+ default: null,
63
+ index: true,
64
+ },
65
+ isGlobal: {
66
+ type: Boolean,
67
+ default: true,
68
+ index: true,
69
+ },
70
+ status: {
71
+ type: String,
72
+ enum: ['draft', 'published', 'archived'],
73
+ default: 'draft',
74
+ index: true,
75
+ },
76
+ publishedAt: {
77
+ type: Date,
78
+ default: null,
79
+ },
80
+ },
81
+ { timestamps: true },
82
+ );
83
+
84
+ pageSchema.index({ slug: 1, collectionId: 1, tenantId: 1 }, { unique: true });
85
+ pageSchema.index({ status: 1, isGlobal: 1 });
86
+ pageSchema.index({ collectionId: 1, status: 1 });
87
+
88
+ pageSchema.virtual('routePath').get(function () {
89
+ return this._routePath || null;
90
+ });
91
+
92
+ pageSchema.set('toJSON', { virtuals: true });
93
+ pageSchema.set('toObject', { virtuals: true });
94
+
95
+ module.exports = mongoose.model('Page', pageSchema);
@@ -0,0 +1,42 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const pageCollectionSchema = new mongoose.Schema(
4
+ {
5
+ slug: {
6
+ type: String,
7
+ required: true,
8
+ index: true,
9
+ },
10
+ name: {
11
+ type: String,
12
+ required: true,
13
+ },
14
+ description: {
15
+ type: String,
16
+ default: '',
17
+ },
18
+ tenantId: {
19
+ type: mongoose.Schema.Types.ObjectId,
20
+ ref: 'Organization',
21
+ default: null,
22
+ index: true,
23
+ },
24
+ isGlobal: {
25
+ type: Boolean,
26
+ default: true,
27
+ index: true,
28
+ },
29
+ status: {
30
+ type: String,
31
+ enum: ['active', 'archived'],
32
+ default: 'active',
33
+ index: true,
34
+ },
35
+ },
36
+ { timestamps: true },
37
+ );
38
+
39
+ pageCollectionSchema.index({ slug: 1, tenantId: 1 }, { unique: true });
40
+ pageCollectionSchema.index({ isGlobal: 1, status: 1 });
41
+
42
+ module.exports = mongoose.model('PageCollection', pageCollectionSchema);
@@ -0,0 +1,66 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const proxyRuleSchema = new mongoose.Schema(
4
+ {
5
+ enabled: { type: Boolean, default: true },
6
+ type: { type: String, enum: ['contains', 'regexp'], required: true },
7
+ value: { type: String, required: true },
8
+ applyTo: { type: String, enum: ['targetUrl', 'host', 'path'], default: 'targetUrl' },
9
+ flags: { type: String, default: 'i' },
10
+ },
11
+ { _id: false },
12
+ );
13
+
14
+ const proxyEntrySchema = new mongoose.Schema(
15
+ {
16
+ name: { type: String, default: '' },
17
+ enabled: { type: Boolean, default: false, index: true },
18
+ match: {
19
+ type: {
20
+ type: String,
21
+ enum: ['exact', 'contains', 'regexp'],
22
+ default: 'contains',
23
+ },
24
+ value: { type: String, required: true },
25
+ applyTo: { type: String, enum: ['targetUrl', 'host', 'path'], default: 'host' },
26
+ flags: { type: String, default: 'i' },
27
+ },
28
+ policy: {
29
+ mode: { type: String, enum: ['blacklist', 'whitelist', 'allowAll', 'denyAll'], default: 'whitelist' },
30
+ rules: { type: [proxyRuleSchema], default: [] },
31
+ },
32
+ rateLimit: {
33
+ enabled: { type: Boolean, default: false },
34
+ limiterId: { type: String, default: null },
35
+ },
36
+ cache: {
37
+ enabled: { type: Boolean, default: false },
38
+ ttlSeconds: { type: Number, default: 60 },
39
+ namespace: { type: String, default: 'proxy' },
40
+ methods: { type: [String], default: ['GET', 'HEAD'] },
41
+ keyParts: {
42
+ url: { type: Boolean, default: true },
43
+ query: { type: Boolean, default: true },
44
+ bodyHash: { type: Boolean, default: true },
45
+ headersHash: { type: Boolean, default: true },
46
+ },
47
+ keyHeaderAllowList: { type: [String], default: [] },
48
+ },
49
+ headers: {
50
+ forwardAuthorization: { type: Boolean, default: true },
51
+ forwardCookie: { type: Boolean, default: true },
52
+ allowList: { type: [String], default: [] },
53
+ denyList: { type: [String], default: [] },
54
+ },
55
+ transform: {
56
+ enabled: { type: Boolean, default: false },
57
+ timeoutMs: { type: Number, default: 200 },
58
+ code: { type: String, default: '' },
59
+ },
60
+ },
61
+ { timestamps: true, collection: 'proxy_entries' },
62
+ );
63
+
64
+ proxyEntrySchema.index({ 'match.type': 1, 'match.applyTo': 1 });
65
+
66
+ module.exports = mongoose.models.ProxyEntry || mongoose.model('ProxyEntry', proxyEntrySchema);
@@ -0,0 +1,19 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const rateLimitCounterSchema = new mongoose.Schema(
4
+ {
5
+ limiterId: { type: String, required: true, index: true },
6
+ identityKey: { type: String, required: true, index: true },
7
+ windowStart: { type: Date, required: true, index: true },
8
+
9
+ count: { type: Number, default: 0 },
10
+
11
+ expiresAt: { type: Date, default: null, index: true },
12
+ },
13
+ { timestamps: true, collection: 'rate_limit_counters' },
14
+ );
15
+
16
+ rateLimitCounterSchema.index({ limiterId: 1, identityKey: 1, windowStart: 1 }, { unique: true });
17
+ rateLimitCounterSchema.index({ expiresAt: 1 }, { expireAfterSeconds: 0 });
18
+
19
+ module.exports = mongoose.models.RateLimitCounter || mongoose.model('RateLimitCounter', rateLimitCounterSchema);
@@ -0,0 +1,20 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const rateLimitMetricBucketSchema = new mongoose.Schema(
4
+ {
5
+ bucketStart: { type: Date, required: true, index: true },
6
+ limiterId: { type: String, required: true, index: true },
7
+
8
+ checked: { type: Number, default: 0 },
9
+ allowed: { type: Number, default: 0 },
10
+ blocked: { type: Number, default: 0 },
11
+
12
+ expiresAt: { type: Date, default: null, index: true },
13
+ },
14
+ { timestamps: true, collection: 'rate_limit_metric_buckets' },
15
+ );
16
+
17
+ rateLimitMetricBucketSchema.index({ limiterId: 1, bucketStart: 1 }, { unique: true });
18
+ rateLimitMetricBucketSchema.index({ expiresAt: 1 }, { expireAfterSeconds: 0 });
19
+
20
+ module.exports = mongoose.models.RateLimitMetricBucket || mongoose.model('RateLimitMetricBucket', rateLimitMetricBucketSchema);
@@ -0,0 +1,25 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const rbacGrantSchema = new mongoose.Schema(
4
+ {
5
+ subjectType: { type: String, enum: ['user', 'role', 'group', 'org'], required: true, index: true },
6
+ subjectId: { type: mongoose.Schema.Types.ObjectId, required: true, index: true },
7
+
8
+ scopeType: { type: String, enum: ['global', 'org'], required: true, index: true },
9
+ scopeId: { type: mongoose.Schema.Types.ObjectId, ref: 'Organization', default: null, index: true },
10
+
11
+ right: { type: String, required: true, trim: true, index: true },
12
+ effect: { type: String, enum: ['allow', 'deny'], default: 'allow', index: true },
13
+
14
+ createdByActorType: { type: String, default: null },
15
+ createdByActorId: { type: String, default: null },
16
+ },
17
+ { timestamps: true, collection: 'rbac_grants' },
18
+ );
19
+
20
+ rbacGrantSchema.index(
21
+ { subjectType: 1, subjectId: 1, scopeType: 1, scopeId: 1, right: 1 },
22
+ { unique: true },
23
+ );
24
+
25
+ module.exports = mongoose.models.RbacGrant || mongoose.model('RbacGrant', rbacGrantSchema);
@@ -0,0 +1,16 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const rbacGroupSchema = new mongoose.Schema(
4
+ {
5
+ name: { type: String, required: true, trim: true },
6
+ description: { type: String, default: '', trim: true },
7
+ status: { type: String, enum: ['active', 'disabled'], default: 'active', index: true },
8
+ isGlobal: { type: Boolean, default: true, index: true },
9
+ orgId: { type: mongoose.Schema.Types.ObjectId, ref: 'Organization', default: null, index: true },
10
+ },
11
+ { timestamps: true, collection: 'rbac_groups' },
12
+ );
13
+
14
+ rbacGroupSchema.index({ isGlobal: 1, orgId: 1, name: 1 });
15
+
16
+ module.exports = mongoose.models.RbacGroup || mongoose.model('RbacGroup', rbacGroupSchema);
@@ -0,0 +1,13 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const rbacGroupMemberSchema = new mongoose.Schema(
4
+ {
5
+ groupId: { type: mongoose.Schema.Types.ObjectId, ref: 'RbacGroup', required: true, index: true },
6
+ userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true, index: true },
7
+ },
8
+ { timestamps: true, collection: 'rbac_group_members' },
9
+ );
10
+
11
+ rbacGroupMemberSchema.index({ groupId: 1, userId: 1 }, { unique: true });
12
+
13
+ module.exports = mongoose.models.RbacGroupMember || mongoose.model('RbacGroupMember', rbacGroupMemberSchema);
@@ -0,0 +1,13 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const rbacGroupRoleSchema = new mongoose.Schema(
4
+ {
5
+ groupId: { type: mongoose.Schema.Types.ObjectId, ref: 'RbacGroup', required: true, index: true },
6
+ roleId: { type: mongoose.Schema.Types.ObjectId, ref: 'RbacRole', required: true, index: true },
7
+ },
8
+ { timestamps: true, collection: 'rbac_group_roles' },
9
+ );
10
+
11
+ rbacGroupRoleSchema.index({ groupId: 1, roleId: 1 }, { unique: true });
12
+
13
+ module.exports = mongoose.models.RbacGroupRole || mongoose.model('RbacGroupRole', rbacGroupRoleSchema);
@@ -0,0 +1,25 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const rbacRoleSchema = new mongoose.Schema(
4
+ {
5
+ key: { type: String, required: true, trim: true, lowercase: true, index: true },
6
+ name: { type: String, required: true, trim: true },
7
+ description: { type: String, default: '', trim: true },
8
+ status: { type: String, enum: ['active', 'disabled'], default: 'active', index: true },
9
+ isGlobal: { type: Boolean, default: true, index: true },
10
+ orgId: { type: mongoose.Schema.Types.ObjectId, ref: 'Organization', default: null, index: true },
11
+ },
12
+ { timestamps: true, collection: 'rbac_roles' },
13
+ );
14
+
15
+ rbacRoleSchema.index({ isGlobal: 1, orgId: 1, key: 1 });
16
+ rbacRoleSchema.index(
17
+ { key: 1 },
18
+ { unique: true, partialFilterExpression: { isGlobal: true } },
19
+ );
20
+ rbacRoleSchema.index(
21
+ { orgId: 1, key: 1 },
22
+ { unique: true, partialFilterExpression: { isGlobal: false } },
23
+ );
24
+
25
+ module.exports = mongoose.models.RbacRole || mongoose.model('RbacRole', rbacRoleSchema);
@@ -0,0 +1,13 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const rbacUserRoleSchema = new mongoose.Schema(
4
+ {
5
+ userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true, index: true },
6
+ roleId: { type: mongoose.Schema.Types.ObjectId, ref: 'RbacRole', required: true, index: true },
7
+ },
8
+ { timestamps: true, collection: 'rbac_user_roles' },
9
+ );
10
+
11
+ rbacUserRoleSchema.index({ userId: 1, roleId: 1 }, { unique: true });
12
+
13
+ module.exports = mongoose.models.RbacUserRole || mongoose.model('RbacUserRole', rbacUserRoleSchema);
@@ -0,0 +1,42 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const envVarSchema = new mongoose.Schema(
4
+ {
5
+ key: { type: String, required: true },
6
+ value: { type: String, required: true },
7
+ },
8
+ { _id: false },
9
+ );
10
+
11
+ const scriptDefinitionSchema = new mongoose.Schema(
12
+ {
13
+ name: { type: String, required: true },
14
+ codeIdentifier: { type: String, required: true, unique: true, index: true },
15
+ description: { type: String, default: '' },
16
+ type: { type: String, enum: ['bash', 'node', 'browser'], required: true },
17
+ runner: { type: String, enum: ['host', 'vm2', 'browser'], required: true },
18
+ script: { type: String, required: true },
19
+ defaultWorkingDirectory: { type: String, default: '' },
20
+ env: { type: [envVarSchema], default: [] },
21
+ timeoutMs: { type: Number, default: 5 * 60 * 1000 },
22
+ enabled: { type: Boolean, default: true, index: true },
23
+ },
24
+ { timestamps: true, collection: 'script_definitions' },
25
+ );
26
+
27
+ function normalizeCodeIdentifier(codeIdentifier) {
28
+ return String(codeIdentifier || '')
29
+ .trim()
30
+ .toLowerCase()
31
+ .replace(/\s+/g, '-')
32
+ .replace(/[^a-z0-9_-]/g, '');
33
+ }
34
+
35
+ scriptDefinitionSchema.pre('validate', function preValidate(next) {
36
+ this.codeIdentifier = normalizeCodeIdentifier(this.codeIdentifier);
37
+ next();
38
+ });
39
+
40
+ module.exports =
41
+ mongoose.models.ScriptDefinition ||
42
+ mongoose.model('ScriptDefinition', scriptDefinitionSchema);
@@ -0,0 +1,22 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const scriptRunSchema = new mongoose.Schema(
4
+ {
5
+ scriptId: { type: mongoose.Schema.Types.ObjectId, ref: 'ScriptDefinition', required: true, index: true },
6
+ status: {
7
+ type: String,
8
+ enum: ['queued', 'running', 'succeeded', 'failed', 'canceled', 'timed_out'],
9
+ default: 'queued',
10
+ index: true,
11
+ },
12
+ trigger: { type: String, enum: ['manual', 'schedule', 'api'], default: 'manual', index: true },
13
+ startedAt: { type: Date, default: null },
14
+ finishedAt: { type: Date, default: null },
15
+ exitCode: { type: Number, default: null },
16
+ outputTail: { type: String, default: '' },
17
+ meta: { type: mongoose.Schema.Types.Mixed, default: null },
18
+ },
19
+ { timestamps: true, collection: 'script_runs' },
20
+ );
21
+
22
+ module.exports = mongoose.models.ScriptRun || mongoose.model('ScriptRun', scriptRunSchema);
@@ -0,0 +1,29 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const uiComponentSchema = new mongoose.Schema(
4
+ {
5
+ code: {
6
+ type: String,
7
+ required: true,
8
+ unique: true,
9
+ index: true,
10
+ trim: true,
11
+ lowercase: true,
12
+ match: [/^[a-z][a-z0-9_-]{1,63}$/, 'Invalid component code'],
13
+ },
14
+ name: { type: String, required: true, trim: true },
15
+
16
+ html: { type: String, default: '' },
17
+ js: { type: String, default: '' },
18
+ css: { type: String, default: '' },
19
+
20
+ api: { type: mongoose.Schema.Types.Mixed, default: null },
21
+ usageMarkdown: { type: String, default: '' },
22
+
23
+ version: { type: Number, default: 1 },
24
+ isActive: { type: Boolean, default: true, index: true },
25
+ },
26
+ { timestamps: true, collection: 'ui_components' },
27
+ );
28
+
29
+ module.exports = mongoose.models.UiComponent || mongoose.model('UiComponent', uiComponentSchema);
@@ -0,0 +1,26 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const uiComponentProjectSchema = new mongoose.Schema(
4
+ {
5
+ projectId: {
6
+ type: String,
7
+ required: true,
8
+ unique: true,
9
+ index: true,
10
+ trim: true,
11
+ match: [/^prj_[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
+
20
+ isActive: { type: Boolean, default: true, index: true },
21
+ },
22
+ { timestamps: true, collection: 'ui_component_projects' },
23
+ );
24
+
25
+ module.exports = mongoose.models.UiComponentProject ||
26
+ mongoose.model('UiComponentProject', uiComponentProjectSchema);
@@ -0,0 +1,18 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const uiComponentProjectComponentSchema = new mongoose.Schema(
4
+ {
5
+ projectId: { type: String, required: true, index: true },
6
+ componentCode: { type: String, required: true, index: true },
7
+ enabled: { type: Boolean, default: true, index: true },
8
+ },
9
+ { timestamps: true, collection: 'ui_component_project_components' },
10
+ );
11
+
12
+ uiComponentProjectComponentSchema.index(
13
+ { projectId: 1, componentCode: 1 },
14
+ { unique: true },
15
+ );
16
+
17
+ module.exports = mongoose.models.UiComponentProjectComponent ||
18
+ mongoose.model('UiComponentProjectComponent', uiComponentProjectComponentSchema);
@@ -11,6 +11,7 @@ router.post('/users/register', adminController.registerUser);
11
11
  router.get('/users/:id', adminController.getUser);
12
12
  router.put('/users/:id/subscription', adminController.updateUserSubscription);
13
13
  router.patch('/users/:id', adminController.updateUserPassword);
14
+ router.delete('/users/:id', adminController.deleteUser);
14
15
  router.post('/users/:id/reconcile', adminController.reconcileUser);
15
16
  router.post('/generate-token', adminController.generateToken);
16
17
 
@@ -0,0 +1,21 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+
4
+ const { basicAuth } = require('../middleware/auth');
5
+ const controller = require('../controllers/blogAdmin.controller');
6
+
7
+ router.use(basicAuth);
8
+ router.use(express.json({ limit: '2mb' }));
9
+
10
+ router.get('/blog-posts', controller.list);
11
+ router.get('/blog-posts/suggestions', controller.suggestions);
12
+ router.post('/blog-posts', controller.create);
13
+ router.get('/blog-posts/:id', controller.get);
14
+ router.put('/blog-posts/:id', controller.update);
15
+ router.put('/blog-posts/:id/publish', controller.publish);
16
+ router.put('/blog-posts/:id/unpublish', controller.unpublish);
17
+ router.put('/blog-posts/:id/schedule', controller.schedule);
18
+ router.put('/blog-posts/:id/archive', controller.archive);
19
+ router.delete('/blog-posts/:id', controller.remove);
20
+
21
+ module.exports = router;
@@ -0,0 +1,16 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+
4
+ const { basicAuth } = require('../middleware/auth');
5
+ const controller = require('../controllers/blogAiAdmin.controller');
6
+ const rateLimiter = require('../services/rateLimiter.service');
7
+
8
+ router.use(basicAuth);
9
+ router.use(express.json({ limit: '2mb' }));
10
+
11
+ router.post('/blog-ai/generate-field', rateLimiter.limit('blogAiLimiter'), controller.generateField);
12
+ router.post('/blog-ai/generate-all', rateLimiter.limit('blogAiLimiter'), controller.generateAll);
13
+ router.post('/blog-ai/format-markdown', rateLimiter.limit('blogAiLimiter'), controller.formatMarkdown);
14
+ router.post('/blog-ai/refine-markdown', rateLimiter.limit('blogAiLimiter'), controller.refineMarkdown);
15
+
16
+ module.exports = router;