@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,184 @@
1
+ const crypto = require('crypto');
2
+
3
+ const GlobalSetting = require('../models/GlobalSetting');
4
+ const CronJob = require('../models/CronJob');
5
+ const uploadNamespacesService = require('./uploadNamespaces.service');
6
+ const globalSettingsService = require('./globalSettings.service');
7
+
8
+ const blogAutomationService = require('./blogAutomation.service');
9
+
10
+ const INTERNAL_CRON_TOKEN_SETTING_KEY = 'blog.internalCronToken';
11
+
12
+ const CRON_NAME_AUTOMATION = 'Blog: Automation (generate drafts)';
13
+ const CRON_NAME_PUBLISH_SCHEDULED = 'Blog: Publish scheduled posts';
14
+
15
+ const AUTOMATION_CRON_PREFIX = 'Blog: Automation';
16
+
17
+ function getAutomationCronNameForConfigId(configId) {
18
+ return `${AUTOMATION_CRON_PREFIX} (${String(configId)})`;
19
+ }
20
+
21
+ function getDefaultInternalCronToken() {
22
+ return crypto.randomBytes(24).toString('hex');
23
+ }
24
+
25
+ async function ensureSettingExists({ key, type, description, defaultValue }) {
26
+ const existing = await GlobalSetting.findOne({ key }).lean();
27
+ if (existing) return existing;
28
+
29
+ const doc = await GlobalSetting.create({
30
+ key,
31
+ type,
32
+ description,
33
+ value: type === 'json' ? JSON.stringify(defaultValue) : String(defaultValue ?? ''),
34
+ templateVariables: [],
35
+ public: false,
36
+ });
37
+
38
+ globalSettingsService.clearSettingsCache();
39
+ return doc.toObject();
40
+ }
41
+
42
+ async function ensureInternalTokenExists() {
43
+ await ensureSettingExists({
44
+ key: INTERNAL_CRON_TOKEN_SETTING_KEY,
45
+ type: 'string',
46
+ description: 'Bearer token used by CronJobs to call internal blog endpoints.',
47
+ defaultValue: getDefaultInternalCronToken(),
48
+ });
49
+
50
+ const raw = await globalSettingsService.getSettingValue(
51
+ INTERNAL_CRON_TOKEN_SETTING_KEY,
52
+ '',
53
+ );
54
+ return String(raw || '').trim();
55
+ }
56
+
57
+ async function ensureBlogImagesNamespace() {
58
+ await uploadNamespacesService.upsertNamespace('blog-images', {
59
+ enabled: true,
60
+ maxFileSizeBytes: 10 * 1024 * 1024,
61
+ allowedContentTypes: ['image/*', 'video/*', 'audio/*'],
62
+ defaultVisibility: 'public',
63
+ enforceVisibility: true,
64
+ });
65
+ }
66
+
67
+ async function ensureCronJobs({ baseUrl, token }) {
68
+ const automationUrl = `${baseUrl}/api/internal/blog/automation/run`;
69
+ const publishUrl = `${baseUrl}/api/internal/blog/publish-scheduled/run`;
70
+
71
+ // Reconcile per-config automation cron jobs
72
+ const configs = await blogAutomationService.getBlogAutomationConfigs();
73
+ const configIds = new Set((configs.items || []).map((c) => String(c.id)));
74
+
75
+ const existingAutomationCrons = await CronJob.find({
76
+ taskType: 'http',
77
+ name: { $regex: `^${AUTOMATION_CRON_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')} \\(` },
78
+ }).lean();
79
+
80
+ for (const cron of existingAutomationCrons) {
81
+ const name = String(cron?.name || '');
82
+ const match = name.match(/^Blog: Automation \((.+)\)$/);
83
+ const id = match ? String(match[1]) : '';
84
+ if (id && !configIds.has(id)) {
85
+ await CronJob.deleteOne({ _id: cron._id }).catch(() => {});
86
+ }
87
+ }
88
+
89
+ for (const cfg of configs.items || []) {
90
+ const id = String(cfg.id);
91
+ const schedule = cfg && typeof cfg.schedule === 'object' ? cfg.schedule : {};
92
+ const managedBy = schedule.managedBy === 'manualOnly' ? 'manualOnly' : 'cronScheduler';
93
+ const cronName = getAutomationCronNameForConfigId(id);
94
+
95
+ if (managedBy !== 'cronScheduler') {
96
+ await CronJob.deleteOne({ name: cronName, taskType: 'http' }).catch(() => {});
97
+ continue;
98
+ }
99
+
100
+ const cronExpression = String(schedule.cronExpression || '').trim() || '0 9 * * 2,4';
101
+ const timezone = String(schedule.timezone || '').trim() || 'UTC';
102
+ const enabled = Boolean(cfg.enabled);
103
+
104
+ const payload = {
105
+ name: cronName,
106
+ description: `Scheduled blog automation run for config: ${String(cfg.name || '').trim() || id}`,
107
+ cronExpression,
108
+ timezone,
109
+ enabled,
110
+ nextRunAt: null,
111
+ taskType: 'http',
112
+ httpMethod: 'POST',
113
+ httpUrl: automationUrl,
114
+ httpHeaders: [],
115
+ httpBody: JSON.stringify({ trigger: 'scheduled', configId: id }),
116
+ httpBodyType: 'json',
117
+ httpAuth: { type: 'bearer', token },
118
+ timeoutMs: 10 * 60 * 1000,
119
+ createdBy: 'system',
120
+ };
121
+
122
+ const existing = await CronJob.findOne({ name: cronName, taskType: 'http' });
123
+ if (!existing) {
124
+ await CronJob.create(payload);
125
+ } else {
126
+ existing.description = payload.description;
127
+ existing.cronExpression = payload.cronExpression;
128
+ existing.timezone = payload.timezone;
129
+ existing.enabled = payload.enabled;
130
+ existing.httpUrl = payload.httpUrl;
131
+ existing.httpBody = payload.httpBody;
132
+ existing.httpBodyType = payload.httpBodyType;
133
+ existing.httpAuth = payload.httpAuth;
134
+ existing.timeoutMs = payload.timeoutMs;
135
+ await existing.save();
136
+ }
137
+ }
138
+
139
+ const existingPublisher = await CronJob.findOne({ name: CRON_NAME_PUBLISH_SCHEDULED, taskType: 'http' }).lean();
140
+ if (!existingPublisher) {
141
+ await CronJob.create({
142
+ name: CRON_NAME_PUBLISH_SCHEDULED,
143
+ description: 'Publishes due scheduled posts (disabled by default).',
144
+ cronExpression: '*/5 * * * *',
145
+ timezone: 'UTC',
146
+ enabled: false,
147
+ nextRunAt: null,
148
+ taskType: 'http',
149
+ httpMethod: 'POST',
150
+ httpUrl: publishUrl,
151
+ httpHeaders: [],
152
+ httpBody: JSON.stringify({}),
153
+ httpBodyType: 'json',
154
+ httpAuth: { type: 'bearer', token },
155
+ timeoutMs: 2 * 60 * 1000,
156
+ createdBy: 'system',
157
+ });
158
+ }
159
+ }
160
+
161
+ async function bootstrap() {
162
+ // Ensure blog automation settings exist (config + style guide)
163
+ await blogAutomationService.getBlogAutomationConfigs();
164
+ await blogAutomationService.getBlogAutomationStyleGuide();
165
+
166
+ await ensureBlogImagesNamespace();
167
+
168
+ const token = await ensureInternalTokenExists();
169
+
170
+ // CronScheduler HTTP jobs need an absolute base URL.
171
+ const baseUrl =
172
+ String(process.env.SUPERBACKEND_BASE_URL || process.env.PUBLIC_URL || '').trim() ||
173
+ 'http://localhost:3000';
174
+
175
+ await ensureCronJobs({ baseUrl: baseUrl.replace(/\/+$/, ''), token });
176
+ }
177
+
178
+ module.exports = {
179
+ bootstrap,
180
+ INTERNAL_CRON_TOKEN_SETTING_KEY,
181
+ CRON_NAME_AUTOMATION,
182
+ CRON_NAME_PUBLISH_SCHEDULED,
183
+ getAutomationCronNameForConfigId,
184
+ };
@@ -0,0 +1,58 @@
1
+ const BlogPost = require('../models/BlogPost');
2
+ const { generateUniqueBlogSlug } = require('./blog.service');
3
+
4
+ async function publishScheduledDue({ limit = 20 } = {}) {
5
+ const l = Math.min(200, Math.max(1, Number(limit) || 20));
6
+ const now = new Date();
7
+
8
+ const due = await BlogPost.find({
9
+ status: 'scheduled',
10
+ scheduledAt: { $lte: now },
11
+ })
12
+ .sort({ scheduledAt: 1, createdAt: 1 })
13
+ .limit(l);
14
+
15
+ const results = {
16
+ processed: 0,
17
+ published: 0,
18
+ errors: [],
19
+ };
20
+
21
+ for (const post of due) {
22
+ results.processed += 1;
23
+ try {
24
+ post.status = 'published';
25
+ post.scheduledAt = null;
26
+ if (!post.publishedAt) post.publishedAt = new Date();
27
+
28
+ await post.save();
29
+ results.published += 1;
30
+ } catch (err) {
31
+ // If slug is no longer available (archived posts free slugs; scheduled could conflict with a newer draft),
32
+ // regenerate a unique slug and retry once.
33
+ const msg = String(err?.message || err || 'Unknown error');
34
+ if (/duplicate key/i.test(msg) && /slug/i.test(msg)) {
35
+ try {
36
+ post.slug = await generateUniqueBlogSlug(post.title, { excludeId: post._id });
37
+ post.status = 'published';
38
+ post.scheduledAt = null;
39
+ if (!post.publishedAt) post.publishedAt = new Date();
40
+ await post.save();
41
+ results.published += 1;
42
+ continue;
43
+ } catch (err2) {
44
+ results.errors.push({ postId: String(post._id), error: String(err2?.message || err2 || '') });
45
+ continue;
46
+ }
47
+ }
48
+
49
+ results.errors.push({ postId: String(post._id), error: msg });
50
+ }
51
+ }
52
+
53
+ return results;
54
+ }
55
+
56
+ module.exports = {
57
+ publishScheduledDue,
58
+ };