@intranefr/superbackend 1.5.3 → 1.6.4

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 (106) hide show
  1. package/cookies.txt +6 -0
  2. package/cookies1.txt +6 -0
  3. package/cookies2.txt +6 -0
  4. package/cookies3.txt +6 -0
  5. package/cookies4.txt +5 -0
  6. package/cookies_old.txt +5 -0
  7. package/cookies_old_test.txt +6 -0
  8. package/cookies_super.txt +5 -0
  9. package/cookies_super_test.txt +6 -0
  10. package/cookies_test.txt +6 -0
  11. package/index.js +7 -0
  12. package/package.json +3 -1
  13. package/plugins/core-waiting-list-migration/README.md +118 -0
  14. package/plugins/core-waiting-list-migration/index.js +438 -0
  15. package/plugins/global-settings-presets/index.js +20 -0
  16. package/plugins/hello-cli/index.js +17 -0
  17. package/plugins/ui-components-seeder/components/suiAlert.js +212 -0
  18. package/plugins/ui-components-seeder/components/suiToast.js +186 -0
  19. package/plugins/ui-components-seeder/index.js +31 -0
  20. package/public/js/admin-ui-components-preview.js +281 -0
  21. package/public/js/admin-ui-components.js +408 -0
  22. package/public/js/llm-provider-model-picker.js +193 -0
  23. package/public/test-iframe-fix.html +63 -0
  24. package/public/test-iframe.html +14 -0
  25. package/src/admin/endpointRegistry.js +68 -0
  26. package/src/controllers/admin.controller.js +25 -5
  27. package/src/controllers/adminDataCleanup.controller.js +45 -0
  28. package/src/controllers/adminLlm.controller.js +0 -8
  29. package/src/controllers/adminLogin.controller.js +269 -0
  30. package/src/controllers/adminPlugins.controller.js +55 -0
  31. package/src/controllers/adminRegistry.controller.js +106 -0
  32. package/src/controllers/adminStats.controller.js +4 -4
  33. package/src/controllers/registry.controller.js +32 -0
  34. package/src/controllers/waitingList.controller.js +52 -74
  35. package/src/middleware/auth.js +71 -1
  36. package/src/middleware/rbac.js +62 -0
  37. package/src/middleware.js +480 -156
  38. package/src/models/GlobalSetting.js +11 -1
  39. package/src/models/UiComponent.js +2 -0
  40. package/src/models/User.js +1 -1
  41. package/src/routes/admin.routes.js +3 -3
  42. package/src/routes/adminAgents.routes.js +2 -2
  43. package/src/routes/adminAssets.routes.js +11 -11
  44. package/src/routes/adminBlog.routes.js +2 -2
  45. package/src/routes/adminBlogAi.routes.js +2 -2
  46. package/src/routes/adminBlogAutomation.routes.js +2 -2
  47. package/src/routes/adminCache.routes.js +2 -2
  48. package/src/routes/adminConsoleManager.routes.js +2 -2
  49. package/src/routes/adminCrons.routes.js +2 -2
  50. package/src/routes/adminDataCleanup.routes.js +26 -0
  51. package/src/routes/adminDbBrowser.routes.js +2 -2
  52. package/src/routes/adminEjsVirtual.routes.js +2 -2
  53. package/src/routes/adminFeatureFlags.routes.js +6 -6
  54. package/src/routes/adminHeadless.routes.js +2 -2
  55. package/src/routes/adminHealthChecks.routes.js +2 -2
  56. package/src/routes/adminI18n.routes.js +2 -2
  57. package/src/routes/adminJsonConfigs.routes.js +8 -8
  58. package/src/routes/adminLlm.routes.js +8 -8
  59. package/src/routes/adminLogin.routes.js +23 -0
  60. package/src/routes/adminMarkdowns.routes.js +3 -9
  61. package/src/routes/adminMigration.routes.js +12 -12
  62. package/src/routes/adminPages.routes.js +2 -2
  63. package/src/routes/adminPlugins.routes.js +15 -0
  64. package/src/routes/adminProxy.routes.js +2 -2
  65. package/src/routes/adminRateLimits.routes.js +8 -8
  66. package/src/routes/adminRbac.routes.js +2 -2
  67. package/src/routes/adminRegistry.routes.js +24 -0
  68. package/src/routes/adminScripts.routes.js +2 -2
  69. package/src/routes/adminSeoConfig.routes.js +10 -10
  70. package/src/routes/adminTelegram.routes.js +2 -2
  71. package/src/routes/adminTerminals.routes.js +2 -2
  72. package/src/routes/adminUiComponents.routes.js +2 -2
  73. package/src/routes/adminUploadNamespaces.routes.js +7 -7
  74. package/src/routes/blogInternal.routes.js +2 -2
  75. package/src/routes/experiments.routes.js +2 -2
  76. package/src/routes/formsAdmin.routes.js +6 -6
  77. package/src/routes/globalSettings.routes.js +8 -8
  78. package/src/routes/internalExperiments.routes.js +2 -2
  79. package/src/routes/notificationAdmin.routes.js +7 -7
  80. package/src/routes/orgAdmin.routes.js +16 -16
  81. package/src/routes/pages.routes.js +3 -3
  82. package/src/routes/registry.routes.js +11 -0
  83. package/src/routes/stripeAdmin.routes.js +12 -12
  84. package/src/routes/userAdmin.routes.js +7 -7
  85. package/src/routes/waitingListAdmin.routes.js +2 -2
  86. package/src/routes/workflows.routes.js +3 -3
  87. package/src/services/dataCleanup.service.js +286 -0
  88. package/src/services/jsonConfigs.service.js +262 -0
  89. package/src/services/plugins.service.js +348 -0
  90. package/src/services/registry.service.js +452 -0
  91. package/src/services/uiComponents.service.js +180 -0
  92. package/src/services/waitingListJson.service.js +401 -0
  93. package/src/utils/rbac/rightsRegistry.js +118 -0
  94. package/test-access.js +63 -0
  95. package/test-iframe-fix.html +63 -0
  96. package/test-iframe.html +14 -0
  97. package/views/admin-403.ejs +92 -0
  98. package/views/admin-dashboard-home.ejs +52 -2
  99. package/views/admin-dashboard.ejs +143 -2
  100. package/views/admin-data-cleanup.ejs +357 -0
  101. package/views/admin-login.ejs +286 -0
  102. package/views/admin-plugins-system.ejs +223 -0
  103. package/views/admin-ui-components.ejs +82 -402
  104. package/views/admin-users.ejs +207 -11
  105. package/views/partials/dashboard/nav-items.ejs +2 -0
  106. package/views/partials/llm-provider-model-picker.ejs +0 -161
@@ -0,0 +1,348 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const crypto = require('crypto');
4
+
5
+ const JsonConfig = require('../models/JsonConfig');
6
+ const { parseJsonOrThrow, clearJsonConfigCache } = require('./jsonConfigs.service');
7
+ const registryService = require('./registry.service');
8
+
9
+ const PLUGINS_STATE_KEY = 'open-registry-plugins-state';
10
+ const DEFAULT_REGISTRY_ID = 'plugins';
11
+
12
+ const exposedServices = {};
13
+ const exposedHelpers = {};
14
+
15
+ function sha256(value) {
16
+ return crypto.createHash('sha256').update(String(value || ''), 'utf8').digest('hex');
17
+ }
18
+
19
+ function nowIso() {
20
+ return new Date().toISOString();
21
+ }
22
+
23
+ function resolvePluginsRoot(customRoot) {
24
+ return customRoot || path.join(process.cwd(), 'plugins');
25
+ }
26
+
27
+ function normalizePlugin(rawModule, pluginId, absoluteDir) {
28
+ const candidate = rawModule && typeof rawModule === 'object' ? rawModule : {};
29
+ const hooks = candidate.hooks && typeof candidate.hooks === 'object' ? candidate.hooks : {};
30
+ const topLevelBootstrap = typeof candidate.bootstrap === 'function' ? candidate.bootstrap : null;
31
+ const topLevelInstall = typeof candidate.install === 'function' ? candidate.install : null;
32
+
33
+ const meta = candidate.meta && typeof candidate.meta === 'object' ? candidate.meta : {};
34
+
35
+ return {
36
+ id: String(meta.id || candidate.id || pluginId || '').trim(),
37
+ name: String(meta.name || candidate.name || pluginId || '').trim(),
38
+ version: String(meta.version || candidate.version || '1.0.0').trim(),
39
+ description: String(meta.description || candidate.description || '').trim(),
40
+ tags: Array.isArray(meta.tags || candidate.tags)
41
+ ? (meta.tags || candidate.tags).map((tag) => String(tag).trim()).filter(Boolean)
42
+ : [],
43
+ bootstrap: typeof hooks.bootstrap === 'function' ? hooks.bootstrap : topLevelBootstrap,
44
+ install: typeof hooks.install === 'function' ? hooks.install : topLevelInstall,
45
+ services: candidate.services && typeof candidate.services === 'object' ? candidate.services : {},
46
+ helpers: candidate.helpers && typeof candidate.helpers === 'object' ? candidate.helpers : {},
47
+ path: absoluteDir,
48
+ };
49
+ }
50
+
51
+ function pluginToRegistryItem(plugin) {
52
+ return {
53
+ id: plugin.id,
54
+ name: plugin.name,
55
+ category: 'plugins',
56
+ version: 1,
57
+ versions: [1],
58
+ description: plugin.description,
59
+ public: true,
60
+ tags: plugin.tags,
61
+ metadata: {
62
+ source: 'local-folder',
63
+ plugin_version: plugin.version,
64
+ local_path: plugin.path,
65
+ hooks: {
66
+ bootstrap: typeof plugin.bootstrap === 'function',
67
+ install: typeof plugin.install === 'function',
68
+ },
69
+ },
70
+ };
71
+ }
72
+
73
+ async function ensurePluginStateDoc() {
74
+ const existing = await JsonConfig.findOne({
75
+ $or: [{ slug: PLUGINS_STATE_KEY }, { alias: PLUGINS_STATE_KEY }],
76
+ });
77
+ if (existing) return existing;
78
+
79
+ const payload = {
80
+ version: 1,
81
+ plugins: {},
82
+ };
83
+
84
+ const doc = await JsonConfig.create({
85
+ title: 'Open Registry Plugins State',
86
+ slug: PLUGINS_STATE_KEY,
87
+ alias: PLUGINS_STATE_KEY,
88
+ publicEnabled: false,
89
+ cacheTtlSeconds: 0,
90
+ jsonRaw: JSON.stringify(payload, null, 2),
91
+ jsonHash: sha256(JSON.stringify(payload)),
92
+ });
93
+
94
+ clearJsonConfigCache(PLUGINS_STATE_KEY);
95
+ return doc;
96
+ }
97
+
98
+ async function getPluginState() {
99
+ const doc = await ensurePluginStateDoc();
100
+ const data = parseJsonOrThrow(String(doc.jsonRaw || '{}'));
101
+ if (!data.plugins || typeof data.plugins !== 'object') {
102
+ data.plugins = {};
103
+ }
104
+ return { doc, data };
105
+ }
106
+
107
+ async function savePluginState(doc, data) {
108
+ doc.jsonRaw = JSON.stringify(data, null, 2);
109
+ doc.jsonHash = sha256(doc.jsonRaw);
110
+ await doc.save();
111
+ clearJsonConfigCache(PLUGINS_STATE_KEY);
112
+ }
113
+
114
+ function clearPluginRequireCache(pluginDir) {
115
+ const entryPath = path.join(pluginDir, 'index.js');
116
+ const resolved = require.resolve(entryPath);
117
+ delete require.cache[resolved];
118
+ }
119
+
120
+ function readPluginModule(pluginDir) {
121
+ const entryPath = path.join(pluginDir, 'index.js');
122
+ if (!fs.existsSync(entryPath)) return null;
123
+
124
+ try {
125
+ clearPluginRequireCache(pluginDir);
126
+ return require(entryPath);
127
+ } catch (error) {
128
+ console.error(`[plugins] Failed loading plugin from ${entryPath}:`, error);
129
+ return null;
130
+ }
131
+ }
132
+
133
+ async function discoverPlugins({ pluginsRoot } = {}) {
134
+ const root = resolvePluginsRoot(pluginsRoot);
135
+ if (!fs.existsSync(root)) return [];
136
+
137
+ const entries = fs.readdirSync(root, { withFileTypes: true });
138
+ const plugins = [];
139
+
140
+ for (const entry of entries) {
141
+ if (!entry.isDirectory()) continue;
142
+ const pluginId = entry.name;
143
+ const absoluteDir = path.join(root, pluginId);
144
+ const loaded = readPluginModule(absoluteDir);
145
+ if (!loaded) continue;
146
+
147
+ const plugin = normalizePlugin(loaded, pluginId, absoluteDir);
148
+ if (!plugin.id) continue;
149
+ plugins.push(plugin);
150
+ }
151
+
152
+ plugins.sort((a, b) => a.id.localeCompare(b.id));
153
+ return plugins;
154
+ }
155
+
156
+ async function ensurePluginsRegistry() {
157
+ return registryService.ensureRegistry({
158
+ id: DEFAULT_REGISTRY_ID,
159
+ name: 'Plugins Registry',
160
+ description: 'Auto-generated registry for local runtime plugins loaded from cwd/plugins',
161
+ public: true,
162
+ categories: ['plugins'],
163
+ version: '1.0.0',
164
+ });
165
+ }
166
+
167
+ async function syncRegistryItemsFromPlugins(plugins) {
168
+ await ensurePluginsRegistry();
169
+
170
+ for (const plugin of plugins) {
171
+ await registryService.upsertItem(DEFAULT_REGISTRY_ID, pluginToRegistryItem(plugin));
172
+ }
173
+ }
174
+
175
+ function applyExposedContracts(plugin) {
176
+ if (plugin.services && typeof plugin.services === 'object') {
177
+ Object.assign(exposedServices, plugin.services);
178
+ }
179
+ if (plugin.helpers && typeof plugin.helpers === 'object') {
180
+ Object.assign(exposedHelpers, plugin.helpers);
181
+ }
182
+ }
183
+
184
+ function createPluginContext(plugin, externalContext = {}) {
185
+ return {
186
+ plugin,
187
+ services: externalContext.services || {},
188
+ helpers: externalContext.helpers || {},
189
+ logger: console,
190
+ cwd: process.cwd(),
191
+ };
192
+ }
193
+
194
+ async function runHook(plugin, hookName, externalContext = {}) {
195
+ const hook = plugin[hookName];
196
+ if (typeof hook !== 'function') return { ok: true, skipped: true };
197
+
198
+ try {
199
+ const context = createPluginContext(plugin, externalContext);
200
+ await Promise.resolve(hook(context));
201
+ return { ok: true };
202
+ } catch (error) {
203
+ console.error(`[plugins] ${hookName} hook failed for plugin ${plugin.id}:`, error);
204
+ return { ok: false, error: error.message || 'hook execution failed' };
205
+ }
206
+ }
207
+
208
+ async function bootstrap({ pluginsRoot, context } = {}) {
209
+ const discovered = await discoverPlugins({ pluginsRoot });
210
+ await ensurePluginsRegistry();
211
+ await syncRegistryItemsFromPlugins(discovered);
212
+
213
+ const { doc, data } = await getPluginState();
214
+
215
+ for (const plugin of discovered) {
216
+ if (!data.plugins[plugin.id]) {
217
+ data.plugins[plugin.id] = {
218
+ enabled: false,
219
+ installedAt: null,
220
+ updatedAt: nowIso(),
221
+ };
222
+ }
223
+ }
224
+
225
+ await savePluginState(doc, data);
226
+
227
+ const results = [];
228
+ for (const plugin of discovered) {
229
+ const state = data.plugins[plugin.id];
230
+ if (!state || state.enabled !== true) continue;
231
+
232
+ applyExposedContracts(plugin);
233
+ const hookResult = await runHook(plugin, 'bootstrap', context || {});
234
+ results.push({ pluginId: plugin.id, hook: 'bootstrap', ...hookResult });
235
+ }
236
+
237
+ return {
238
+ plugins: discovered,
239
+ bootstrapResults: results,
240
+ };
241
+ }
242
+
243
+ async function listPlugins({ pluginsRoot } = {}) {
244
+ const discovered = await discoverPlugins({ pluginsRoot });
245
+ await ensurePluginsRegistry();
246
+ await syncRegistryItemsFromPlugins(discovered);
247
+
248
+ const { data } = await getPluginState();
249
+
250
+ return discovered.map((plugin) => {
251
+ const state = data.plugins?.[plugin.id] || { enabled: false, installedAt: null };
252
+ return {
253
+ id: plugin.id,
254
+ name: plugin.name,
255
+ version: plugin.version,
256
+ description: plugin.description,
257
+ tags: plugin.tags,
258
+ path: plugin.path,
259
+ hooks: {
260
+ bootstrap: typeof plugin.bootstrap === 'function',
261
+ install: typeof plugin.install === 'function',
262
+ },
263
+ enabled: state.enabled === true,
264
+ installedAt: state.installedAt || null,
265
+ updatedAt: state.updatedAt || null,
266
+ };
267
+ });
268
+ }
269
+
270
+ async function enablePlugin(pluginId, { pluginsRoot, context } = {}) {
271
+ const plugins = await discoverPlugins({ pluginsRoot });
272
+ const plugin = plugins.find((item) => item.id === pluginId);
273
+ if (!plugin) {
274
+ const err = new Error('plugin not found');
275
+ err.code = 'NOT_FOUND';
276
+ throw err;
277
+ }
278
+
279
+ const { doc, data } = await getPluginState();
280
+ data.plugins[plugin.id] = data.plugins[plugin.id] || { enabled: false, installedAt: null };
281
+
282
+ const installResult = await runHook(plugin, 'install', context || {});
283
+ const bootstrapResult = await runHook(plugin, 'bootstrap', context || {});
284
+ applyExposedContracts(plugin);
285
+
286
+ data.plugins[plugin.id].enabled = true;
287
+ data.plugins[plugin.id].installedAt = nowIso();
288
+ data.plugins[plugin.id].updatedAt = nowIso();
289
+ await savePluginState(doc, data);
290
+
291
+ return {
292
+ pluginId: plugin.id,
293
+ enabled: true,
294
+ install: installResult,
295
+ bootstrap: bootstrapResult,
296
+ };
297
+ }
298
+
299
+ async function disablePlugin(pluginId) {
300
+ const { doc, data } = await getPluginState();
301
+ if (!data.plugins[pluginId]) {
302
+ const err = new Error('plugin not found');
303
+ err.code = 'NOT_FOUND';
304
+ throw err;
305
+ }
306
+
307
+ data.plugins[pluginId].enabled = false;
308
+ data.plugins[pluginId].updatedAt = nowIso();
309
+ await savePluginState(doc, data);
310
+
311
+ return { pluginId, enabled: false };
312
+ }
313
+
314
+ async function installPlugin(pluginId, { pluginsRoot, context } = {}) {
315
+ const plugins = await discoverPlugins({ pluginsRoot });
316
+ const plugin = plugins.find((item) => item.id === pluginId);
317
+ if (!plugin) {
318
+ const err = new Error('plugin not found');
319
+ err.code = 'NOT_FOUND';
320
+ throw err;
321
+ }
322
+
323
+ const installResult = await runHook(plugin, 'install', context || {});
324
+ applyExposedContracts(plugin);
325
+ return { pluginId: plugin.id, install: installResult };
326
+ }
327
+
328
+ function getExposedServices() {
329
+ return exposedServices;
330
+ }
331
+
332
+ function getExposedHelpers() {
333
+ return exposedHelpers;
334
+ }
335
+
336
+ module.exports = {
337
+ PLUGINS_STATE_KEY,
338
+ DEFAULT_REGISTRY_ID,
339
+ ensurePluginsRegistry,
340
+ discoverPlugins,
341
+ bootstrap,
342
+ listPlugins,
343
+ enablePlugin,
344
+ disablePlugin,
345
+ installPlugin,
346
+ getExposedServices,
347
+ getExposedHelpers,
348
+ };