@intranefr/superbackend 1.5.2 → 1.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) 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 +9 -0
  12. package/manage.js +745 -0
  13. package/package.json +6 -2
  14. package/plugins/core-waiting-list-migration/README.md +118 -0
  15. package/plugins/core-waiting-list-migration/index.js +438 -0
  16. package/plugins/global-settings-presets/index.js +20 -0
  17. package/plugins/hello-cli/index.js +17 -0
  18. package/plugins/ui-components-seeder/components/suiAlert.js +212 -0
  19. package/plugins/ui-components-seeder/components/suiToast.js +186 -0
  20. package/plugins/ui-components-seeder/index.js +31 -0
  21. package/public/js/admin-ui-components-preview.js +281 -0
  22. package/public/js/admin-ui-components.js +408 -0
  23. package/public/js/llm-provider-model-picker.js +193 -0
  24. package/public/test-iframe-fix.html +63 -0
  25. package/public/test-iframe.html +14 -0
  26. package/src/admin/endpointRegistry.js +68 -0
  27. package/src/controllers/admin.controller.js +36 -10
  28. package/src/controllers/adminAgents.controller.js +37 -0
  29. package/src/controllers/adminDataCleanup.controller.js +45 -0
  30. package/src/controllers/adminLlm.controller.js +19 -8
  31. package/src/controllers/adminLogin.controller.js +269 -0
  32. package/src/controllers/adminMarkdowns.controller.js +157 -0
  33. package/src/controllers/adminPlugins.controller.js +55 -0
  34. package/src/controllers/adminRegistry.controller.js +106 -0
  35. package/src/controllers/adminScripts.controller.js +138 -0
  36. package/src/controllers/adminStats.controller.js +4 -4
  37. package/src/controllers/adminTelegram.controller.js +72 -0
  38. package/src/controllers/markdowns.controller.js +42 -0
  39. package/src/controllers/registry.controller.js +32 -0
  40. package/src/controllers/waitingList.controller.js +52 -74
  41. package/src/helpers/mongooseHelper.js +6 -6
  42. package/src/helpers/scriptBase.js +2 -2
  43. package/src/middleware/auth.js +71 -1
  44. package/src/middleware/rbac.js +62 -0
  45. package/src/middleware.js +584 -176
  46. package/src/models/Agent.js +105 -0
  47. package/src/models/AgentMessage.js +82 -0
  48. package/src/models/GlobalSetting.js +11 -1
  49. package/src/models/Markdown.js +75 -0
  50. package/src/models/ScriptRun.js +8 -0
  51. package/src/models/TelegramBot.js +42 -0
  52. package/src/models/UiComponent.js +2 -0
  53. package/src/models/User.js +1 -1
  54. package/src/routes/admin.routes.js +3 -3
  55. package/src/routes/adminAgents.routes.js +13 -0
  56. package/src/routes/adminAssets.routes.js +11 -11
  57. package/src/routes/adminBlog.routes.js +2 -2
  58. package/src/routes/adminBlogAi.routes.js +2 -2
  59. package/src/routes/adminBlogAutomation.routes.js +2 -2
  60. package/src/routes/adminCache.routes.js +2 -2
  61. package/src/routes/adminConsoleManager.routes.js +2 -2
  62. package/src/routes/adminCrons.routes.js +2 -2
  63. package/src/routes/adminDataCleanup.routes.js +26 -0
  64. package/src/routes/adminDbBrowser.routes.js +2 -2
  65. package/src/routes/adminEjsVirtual.routes.js +2 -2
  66. package/src/routes/adminFeatureFlags.routes.js +6 -6
  67. package/src/routes/adminHeadless.routes.js +2 -2
  68. package/src/routes/adminHealthChecks.routes.js +2 -2
  69. package/src/routes/adminI18n.routes.js +2 -2
  70. package/src/routes/adminJsonConfigs.routes.js +8 -8
  71. package/src/routes/adminLlm.routes.js +8 -7
  72. package/src/routes/adminLogin.routes.js +23 -0
  73. package/src/routes/adminMarkdowns.routes.js +10 -0
  74. package/src/routes/adminMigration.routes.js +12 -12
  75. package/src/routes/adminPages.routes.js +2 -2
  76. package/src/routes/adminPlugins.routes.js +15 -0
  77. package/src/routes/adminProxy.routes.js +2 -2
  78. package/src/routes/adminRateLimits.routes.js +8 -8
  79. package/src/routes/adminRbac.routes.js +2 -2
  80. package/src/routes/adminRegistry.routes.js +24 -0
  81. package/src/routes/adminScripts.routes.js +6 -3
  82. package/src/routes/adminSeoConfig.routes.js +10 -10
  83. package/src/routes/adminTelegram.routes.js +14 -0
  84. package/src/routes/adminTerminals.routes.js +2 -2
  85. package/src/routes/adminUiComponents.routes.js +2 -2
  86. package/src/routes/adminUploadNamespaces.routes.js +7 -7
  87. package/src/routes/blogInternal.routes.js +2 -2
  88. package/src/routes/experiments.routes.js +2 -2
  89. package/src/routes/formsAdmin.routes.js +6 -6
  90. package/src/routes/globalSettings.routes.js +8 -8
  91. package/src/routes/internalExperiments.routes.js +2 -2
  92. package/src/routes/markdowns.routes.js +16 -0
  93. package/src/routes/notificationAdmin.routes.js +7 -7
  94. package/src/routes/orgAdmin.routes.js +16 -16
  95. package/src/routes/pages.routes.js +3 -3
  96. package/src/routes/registry.routes.js +11 -0
  97. package/src/routes/stripeAdmin.routes.js +12 -12
  98. package/src/routes/userAdmin.routes.js +7 -7
  99. package/src/routes/waitingListAdmin.routes.js +2 -2
  100. package/src/routes/workflows.routes.js +3 -3
  101. package/src/services/agent.service.js +546 -0
  102. package/src/services/agentHistory.service.js +345 -0
  103. package/src/services/agentTools.service.js +578 -0
  104. package/src/services/dataCleanup.service.js +286 -0
  105. package/src/services/jsonConfigs.service.js +284 -10
  106. package/src/services/llm.service.js +219 -6
  107. package/src/services/markdowns.service.js +522 -0
  108. package/src/services/plugins.service.js +348 -0
  109. package/src/services/registry.service.js +452 -0
  110. package/src/services/scriptsRunner.service.js +328 -37
  111. package/src/services/telegram.service.js +130 -0
  112. package/src/services/uiComponents.service.js +180 -0
  113. package/src/services/waitingListJson.service.js +401 -0
  114. package/src/utils/rbac/rightsRegistry.js +118 -0
  115. package/test-access.js +63 -0
  116. package/test-iframe-fix.html +63 -0
  117. package/test-iframe.html +14 -0
  118. package/views/admin-403.ejs +92 -0
  119. package/views/admin-agents.ejs +273 -0
  120. package/views/admin-coolify-deploy.ejs +8 -8
  121. package/views/admin-dashboard-home.ejs +52 -2
  122. package/views/admin-dashboard.ejs +179 -7
  123. package/views/admin-data-cleanup.ejs +357 -0
  124. package/views/admin-experiments.ejs +1 -1
  125. package/views/admin-login.ejs +286 -0
  126. package/views/admin-markdowns.ejs +905 -0
  127. package/views/admin-plugins-system.ejs +223 -0
  128. package/views/admin-scripts.ejs +221 -4
  129. package/views/admin-telegram.ejs +269 -0
  130. package/views/admin-ui-components.ejs +82 -402
  131. package/views/admin-users.ejs +207 -11
  132. package/views/partials/dashboard/nav-items.ejs +5 -0
  133. package/views/partials/llm-provider-model-picker.ejs +0 -161
  134. package/analysis-only.skill +0 -0
@@ -0,0 +1,180 @@
1
+ const UiComponent = require('../models/UiComponent');
2
+
3
+ /**
4
+ * UI Components Service
5
+ * Provides service layer for UI Components operations
6
+ */
7
+
8
+ class UiComponentsService {
9
+ /**
10
+ * Upsert a UI component (create or update)
11
+ * @param {Object} componentData - Component data
12
+ * @param {string} componentData.code - Unique component code
13
+ * @param {string} componentData.name - Component display name
14
+ * @param {string} componentData.html - HTML template
15
+ * @param {string} componentData.css - CSS styles
16
+ * @param {string} componentData.js - JavaScript code
17
+ * @param {string} componentData.usageMarkdown - Usage documentation
18
+ * @param {string} componentData.api - API summary
19
+ * @param {number} componentData.version - Component version
20
+ * @param {boolean} componentData.isActive - Whether component is active
21
+ * @returns {Promise<Object>} The created/updated component
22
+ */
23
+ async upsertComponent(componentData) {
24
+ try {
25
+ const {
26
+ code,
27
+ name,
28
+ html = '',
29
+ css = '',
30
+ js = '',
31
+ usageMarkdown = '',
32
+ api = null,
33
+ version = 1,
34
+ isActive = true,
35
+ previewExample = null,
36
+ } = componentData;
37
+
38
+ // Validate required fields
39
+ if (!code || typeof code !== 'string') {
40
+ throw new Error('code is required and must be a string');
41
+ }
42
+ if (!name || typeof name !== 'string') {
43
+ throw new Error('name is required and must be a string');
44
+ }
45
+
46
+ // Normalize code to lowercase
47
+ const normalizedCode = String(code).trim().toLowerCase();
48
+
49
+ // Check if component exists
50
+ const existing = await UiComponent.findOne({ code: normalizedCode });
51
+
52
+ if (existing) {
53
+ // Update existing component
54
+ const updateData = {
55
+ name: String(name).trim(),
56
+ html: String(html),
57
+ css: String(css),
58
+ js: String(js),
59
+ usageMarkdown: String(usageMarkdown),
60
+ api,
61
+ version: Number(version) || 1,
62
+ isActive: Boolean(isActive),
63
+ updatedAt: new Date(),
64
+ previewExample,
65
+ };
66
+
67
+ const updated = await UiComponent.findOneAndUpdate(
68
+ { code: normalizedCode },
69
+ updateData,
70
+ { new: true, runValidators: true }
71
+ );
72
+
73
+ console.log(`[uiComponents] Updated component: ${normalizedCode}`);
74
+ return updated.toObject();
75
+ } else {
76
+ // Create new component
77
+ const createData = {
78
+ code: normalizedCode,
79
+ name: String(name).trim(),
80
+ html: String(html),
81
+ css: String(css),
82
+ js: String(js),
83
+ usageMarkdown: String(usageMarkdown),
84
+ api,
85
+ version: Number(version) || 1,
86
+ isActive: Boolean(isActive),
87
+ previewExample,
88
+ };
89
+
90
+ const created = await UiComponent.create(createData);
91
+ console.log(`[uiComponents] Created component: ${normalizedCode}`);
92
+ return created.toObject();
93
+ }
94
+ } catch (error) {
95
+ console.error(`[uiComponents] Failed to upsert component ${componentData.code}:`, error);
96
+ throw error;
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Get a component by code
102
+ * @param {string} code - Component code
103
+ * @returns {Promise<Object|null>} Component data or null if not found
104
+ */
105
+ async getComponent(code) {
106
+ try {
107
+ const component = await UiComponent.findOne({
108
+ code: String(code).trim().toLowerCase()
109
+ }).lean();
110
+ return component;
111
+ } catch (error) {
112
+ console.error(`[uiComponents] Failed to get component ${code}:`, error);
113
+ throw error;
114
+ }
115
+ }
116
+
117
+ /**
118
+ * List all components
119
+ * @param {Object} options - Query options
120
+ * @param {boolean} options.activeOnly - Only return active components
121
+ * @returns {Promise<Array>} Array of components
122
+ */
123
+ async listComponents(options = {}) {
124
+ try {
125
+ const { activeOnly = false } = options;
126
+ const query = activeOnly ? { isActive: true } : {};
127
+
128
+ const components = await UiComponent.find(query)
129
+ .sort({ updatedAt: -1 })
130
+ .lean();
131
+
132
+ return components;
133
+ } catch (error) {
134
+ console.error('[uiComponents] Failed to list components:', error);
135
+ throw error;
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Delete a component by code
141
+ * @param {string} code - Component code
142
+ * @returns {Promise<boolean>} True if deleted, false if not found
143
+ */
144
+ async deleteComponent(code) {
145
+ try {
146
+ const result = await UiComponent.deleteOne({
147
+ code: String(code).trim().toLowerCase()
148
+ });
149
+
150
+ if (result.deletedCount > 0) {
151
+ console.log(`[uiComponents] Deleted component: ${code}`);
152
+ return true;
153
+ }
154
+
155
+ return false;
156
+ } catch (error) {
157
+ console.error(`[uiComponents] Failed to delete component ${code}:`, error);
158
+ throw error;
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Check if component exists
164
+ * @param {string} code - Component code
165
+ * @returns {Promise<boolean>} True if component exists
166
+ */
167
+ async componentExists(code) {
168
+ try {
169
+ const count = await UiComponent.countDocuments({
170
+ code: String(code).trim().toLowerCase()
171
+ });
172
+ return count > 0;
173
+ } catch (error) {
174
+ console.error(`[uiComponents] Failed to check component existence ${code}:`, error);
175
+ throw error;
176
+ }
177
+ }
178
+ }
179
+
180
+ module.exports = new UiComponentsService();
@@ -0,0 +1,401 @@
1
+ const crypto = require('crypto');
2
+ const {
3
+ getJsonConfigValueBySlug,
4
+ updateJsonConfigValueBySlug,
5
+ clearJsonConfigCacheByPattern,
6
+ isJsonConfigCached,
7
+ getJsonConfigCacheInfo
8
+ } = require('./jsonConfigs.service');
9
+
10
+ const WAITING_LIST_ENTRIES_KEY = 'waiting-list-entries';
11
+ const WAITING_LIST_STATS_KEY = 'waiting-list-stats';
12
+
13
+ /**
14
+ * Waiting List JSON Service
15
+ * Manages waiting list data using JSON Configs system with enhanced caching
16
+ */
17
+
18
+ function generateId() {
19
+ return crypto.randomBytes(16).toString('hex');
20
+ }
21
+
22
+ function normalizeEmail(email) {
23
+ return String(email || '').trim().toLowerCase();
24
+ }
25
+
26
+ function validateEntry(entry) {
27
+ if (!entry || typeof entry !== 'object') {
28
+ const err = new Error('Entry must be an object');
29
+ err.code = 'VALIDATION';
30
+ throw err;
31
+ }
32
+
33
+ if (!entry.email || typeof entry.email !== 'string') {
34
+ const err = new Error('Email is required');
35
+ err.code = 'VALIDATION';
36
+ throw err;
37
+ }
38
+
39
+ const normalizedEmail = normalizeEmail(entry.email);
40
+ if (!normalizedEmail.includes('@')) {
41
+ const err = new Error('Invalid email format');
42
+ err.code = 'VALIDATION';
43
+ throw err;
44
+ }
45
+
46
+ if (!entry.type || typeof entry.type !== 'string') {
47
+ const err = new Error('Type is required');
48
+ err.code = 'VALIDATION';
49
+ throw err;
50
+ }
51
+
52
+ return {
53
+ id: entry.id || generateId(),
54
+ email: normalizedEmail,
55
+ type: String(entry.type).trim(),
56
+ status: entry.status || 'active',
57
+ referralSource: entry.referralSource || 'website',
58
+ createdAt: entry.createdAt || new Date().toISOString(),
59
+ updatedAt: entry.updatedAt || new Date().toISOString()
60
+ };
61
+ }
62
+
63
+ /**
64
+ * Get waiting list entries with caching
65
+ */
66
+ async function getWaitingListEntries(options = {}) {
67
+ try {
68
+ const data = await getJsonConfigValueBySlug(WAITING_LIST_ENTRIES_KEY, {
69
+ bypassCache: options.bypassCache
70
+ });
71
+
72
+ return {
73
+ entries: Array.isArray(data.entries) ? data.entries : [],
74
+ lastUpdated: data.lastUpdated || null
75
+ };
76
+ } catch (error) {
77
+ if (error.code === 'NOT_FOUND') {
78
+ return { entries: [], lastUpdated: null };
79
+ }
80
+ throw error;
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Add new waiting list entry with automatic cache invalidation
86
+ */
87
+ async function addWaitingListEntry(entryData) {
88
+ const validatedEntry = validateEntry(entryData);
89
+
90
+ try {
91
+ const result = await updateJsonConfigValueBySlug(WAITING_LIST_ENTRIES_KEY, (currentData) => {
92
+ const data = currentData || { entries: [] };
93
+ const entries = Array.isArray(data.entries) ? data.entries : [];
94
+
95
+ // Check for duplicate email
96
+ const existingEntry = entries.find(e =>
97
+ normalizeEmail(e.email) === normalizeEmail(validatedEntry.email)
98
+ );
99
+
100
+ if (existingEntry) {
101
+ const err = new Error('This email is already on our waiting list');
102
+ err.code = 'DUPLICATE_EMAIL';
103
+ throw err;
104
+ }
105
+
106
+ // Add new entry
107
+ entries.push(validatedEntry);
108
+
109
+ return {
110
+ ...data,
111
+ entries,
112
+ lastUpdated: new Date().toISOString()
113
+ };
114
+ }, { invalidateCache: true });
115
+
116
+ // Clear stats cache since data changed
117
+ clearWaitingListCache();
118
+
119
+ return validatedEntry;
120
+ } catch (error) {
121
+ // If the config doesn't exist, initialize it first
122
+ if (error.code === 'NOT_FOUND') {
123
+ await initializeWaitingListData();
124
+
125
+ // Retry the operation after initialization (only once)
126
+ try {
127
+ return await addWaitingListEntry(entryData);
128
+ } catch (retryError) {
129
+ // If we still get NOT_FOUND, something went wrong with initialization
130
+ if (retryError.code === 'NOT_FOUND') {
131
+ const err = new Error('Failed to initialize waiting list data structure');
132
+ err.code = 'INITIALIZATION_FAILED';
133
+ throw err;
134
+ }
135
+ throw retryError;
136
+ }
137
+ }
138
+ throw error;
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Update waiting list entry
144
+ */
145
+ async function updateWaitingListEntry(entryId, updates) {
146
+ if (!entryId) {
147
+ const err = new Error('Entry ID is required');
148
+ err.code = 'VALIDATION';
149
+ throw err;
150
+ }
151
+
152
+ const result = await updateJsonConfigValueBySlug(WAITING_LIST_ENTRIES_KEY, (currentData) => {
153
+ const data = currentData || { entries: [] };
154
+ const entries = Array.isArray(data.entries) ? data.entries : [];
155
+
156
+ const entryIndex = entries.findIndex(e => e.id === entryId);
157
+ if (entryIndex === -1) {
158
+ const err = new Error('Waiting list entry not found');
159
+ err.code = 'NOT_FOUND';
160
+ throw err;
161
+ }
162
+
163
+ // Update entry with validation
164
+ const updatedEntry = validateEntry({
165
+ ...entries[entryIndex],
166
+ ...updates,
167
+ id: entryId, // Preserve original ID
168
+ updatedAt: new Date().toISOString()
169
+ });
170
+
171
+ entries[entryIndex] = updatedEntry;
172
+
173
+ return {
174
+ ...data,
175
+ entries,
176
+ lastUpdated: new Date().toISOString()
177
+ };
178
+ }, { invalidateCache: true });
179
+
180
+ // Clear stats cache since data changed
181
+ clearWaitingListCache();
182
+
183
+ return result;
184
+ }
185
+
186
+ /**
187
+ * Remove waiting list entry
188
+ */
189
+ async function removeWaitingListEntry(entryId) {
190
+ if (!entryId) {
191
+ const err = new Error('Entry ID is required');
192
+ err.code = 'VALIDATION';
193
+ throw err;
194
+ }
195
+
196
+ const result = await updateJsonConfigValueBySlug(WAITING_LIST_ENTRIES_KEY, (currentData) => {
197
+ const data = currentData || { entries: [] };
198
+ const entries = Array.isArray(data.entries) ? data.entries : [];
199
+
200
+ const entryIndex = entries.findIndex(e => e.id === entryId);
201
+ if (entryIndex === -1) {
202
+ const err = new Error('Waiting list entry not found');
203
+ err.code = 'NOT_FOUND';
204
+ throw err;
205
+ }
206
+
207
+ // Remove entry
208
+ entries.splice(entryIndex, 1);
209
+
210
+ return {
211
+ ...data,
212
+ entries,
213
+ lastUpdated: new Date().toISOString()
214
+ };
215
+ }, { invalidateCache: true });
216
+
217
+ // Clear stats cache since data changed
218
+ clearWaitingListCache();
219
+
220
+ return result;
221
+ }
222
+
223
+ /**
224
+ * Get waiting list statistics with 5-minute TTL
225
+ */
226
+ async function getWaitingListStats(options = {}) {
227
+ const ttlSeconds = options.ttlSeconds || 300; // 5 minutes default
228
+
229
+ try {
230
+ // Try to get cached stats first
231
+ if (!options.bypassCache && isJsonConfigCached(WAITING_LIST_STATS_KEY)) {
232
+ const cacheInfo = getJsonConfigCacheInfo(WAITING_LIST_STATS_KEY);
233
+ if (cacheInfo.exists && cacheInfo.ttlRemaining > 0) {
234
+ return await getJsonConfigValueBySlug(WAITING_LIST_STATS_KEY);
235
+ }
236
+ }
237
+
238
+ // Generate fresh stats
239
+ const { entries } = await getWaitingListEntries();
240
+
241
+ const activeEntries = entries.filter(e => e.status === 'active');
242
+ const totalSubscribers = activeEntries.length;
243
+
244
+ // Type aggregation
245
+ const typeAgg = activeEntries.reduce((acc, entry) => {
246
+ const type = String(entry.type || 'unknown').trim();
247
+ acc[type] = (acc[type] || 0) + 1;
248
+ return acc;
249
+ }, {});
250
+
251
+ // Backward compatibility fields
252
+ const buyerCount = (typeAgg.buyer || 0) + (typeAgg.both || 0);
253
+ const sellerCount = (typeAgg.seller || 0) + (typeAgg.both || 0);
254
+
255
+ // Mock growth data (same as original)
256
+ const growthThisWeek = Math.floor(totalSubscribers * 0.05);
257
+
258
+ const stats = {
259
+ totalSubscribers,
260
+ buyerCount,
261
+ sellerCount,
262
+ typeCounts: typeAgg,
263
+ growthThisWeek,
264
+ lastUpdated: new Date().toISOString()
265
+ };
266
+
267
+ // Cache the stats
268
+ await updateJsonConfigValueBySlug(WAITING_LIST_STATS_KEY, () => stats, {
269
+ invalidateCache: false
270
+ });
271
+
272
+ return stats;
273
+ } catch (error) {
274
+ if (error.code === 'NOT_FOUND') {
275
+ // Return default stats if no data exists
276
+ return {
277
+ totalSubscribers: 0,
278
+ buyerCount: 0,
279
+ sellerCount: 0,
280
+ typeCounts: {},
281
+ growthThisWeek: 0,
282
+ lastUpdated: new Date().toISOString()
283
+ };
284
+ }
285
+ throw error;
286
+ }
287
+ }
288
+
289
+ /**
290
+ * Get paginated waiting list entries for admin
291
+ */
292
+ async function getWaitingListEntriesAdmin(filters = {}) {
293
+ const { status, type, email, limit = 50, offset = 0 } = filters;
294
+
295
+ const { entries } = await getWaitingListEntries();
296
+
297
+ // Apply filters
298
+ let filteredEntries = entries;
299
+
300
+ if (status) {
301
+ filteredEntries = filteredEntries.filter(e => e.status === status);
302
+ }
303
+
304
+ if (type) {
305
+ filteredEntries = filteredEntries.filter(e => e.type === type);
306
+ }
307
+
308
+ if (email) {
309
+ const searchEmail = normalizeEmail(email);
310
+ filteredEntries = filteredEntries.filter(e =>
311
+ normalizeEmail(e.email) === searchEmail
312
+ );
313
+ }
314
+
315
+ // Sort by creation date (newest first)
316
+ filteredEntries.sort((a, b) =>
317
+ new Date(b.createdAt || 0).getTime() - new Date(a.createdAt || 0).getTime()
318
+ );
319
+
320
+ // Apply pagination
321
+ const parsedLimit = Math.min(500, Math.max(1, parseInt(limit, 10) || 50));
322
+ const parsedOffset = Math.max(0, parseInt(offset, 10) || 0);
323
+
324
+ const paginatedEntries = filteredEntries.slice(parsedOffset, parsedOffset + parsedLimit);
325
+
326
+ return {
327
+ entries: paginatedEntries,
328
+ pagination: {
329
+ total: filteredEntries.length,
330
+ limit: parsedLimit,
331
+ offset: parsedOffset
332
+ }
333
+ };
334
+ }
335
+
336
+ /**
337
+ * Clear all waiting list related caches
338
+ */
339
+ function clearWaitingListCache() {
340
+ return clearJsonConfigCacheByPattern('waiting-list-*');
341
+ }
342
+
343
+ /**
344
+ * Get waiting list cache information
345
+ */
346
+ function getWaitingListCacheInfo() {
347
+ return {
348
+ entries: getJsonConfigCacheInfo(WAITING_LIST_ENTRIES_KEY),
349
+ stats: getJsonConfigCacheInfo(WAITING_LIST_STATS_KEY)
350
+ };
351
+ }
352
+
353
+ /**
354
+ * Initialize waiting list data structure if it doesn't exist
355
+ */
356
+ async function initializeWaitingListData() {
357
+ try {
358
+ await getJsonConfigValueBySlug(WAITING_LIST_ENTRIES_KEY, { bypassCache: true });
359
+ } catch (error) {
360
+ if (error.code === 'NOT_FOUND') {
361
+ // Create initial data structure
362
+ const { createJsonConfig } = require('./jsonConfigs.service');
363
+ await createJsonConfig({
364
+ title: 'Waiting List Entries',
365
+ alias: WAITING_LIST_ENTRIES_KEY,
366
+ jsonRaw: JSON.stringify({
367
+ entries: [],
368
+ lastUpdated: new Date().toISOString()
369
+ }),
370
+ publicEnabled: false,
371
+ cacheTtlSeconds: 300
372
+ });
373
+ } else {
374
+ throw error;
375
+ }
376
+ }
377
+ }
378
+
379
+ module.exports = {
380
+ // Core operations
381
+ getWaitingListEntries,
382
+ addWaitingListEntry,
383
+ updateWaitingListEntry,
384
+ removeWaitingListEntry,
385
+ getWaitingListStats,
386
+ getWaitingListEntriesAdmin,
387
+
388
+ // Cache management
389
+ clearWaitingListCache,
390
+ getWaitingListCacheInfo,
391
+
392
+ // Utilities
393
+ initializeWaitingListData,
394
+ validateEntry,
395
+ normalizeEmail,
396
+ generateId,
397
+
398
+ // Constants
399
+ WAITING_LIST_ENTRIES_KEY,
400
+ WAITING_LIST_STATS_KEY
401
+ };
@@ -1,4 +1,5 @@
1
1
  const DEFAULT_RIGHTS = [
2
+ // Existing RBAC rights
2
3
  'rbac:roles:read',
3
4
  'rbac:roles:write',
4
5
  'rbac:groups:read',
@@ -6,6 +7,8 @@ const DEFAULT_RIGHTS = [
6
7
  'rbac:grants:read',
7
8
  'rbac:grants:write',
8
9
  'rbac:test',
10
+
11
+ // Existing experiment and file manager rights
9
12
  'experiments:*',
10
13
  'experiments:read',
11
14
  'experiments:events:write',
@@ -21,6 +24,121 @@ const DEFAULT_RIGHTS = [
21
24
  'file_manager:files:share',
22
25
  'backoffice:*',
23
26
  'backoffice:dashboard:access',
27
+
28
+ // Admin panel login rights
29
+ 'admin_panel__login',
30
+ 'admin_panel__dashboard',
31
+
32
+ // Dashboard section
33
+ 'admin_panel__overview:read',
34
+
35
+ // User Management section
36
+ 'admin_panel__users:read',
37
+ 'admin_panel__users:write',
38
+ 'admin_panel__organizations:read',
39
+ 'admin_panel__organizations:write',
40
+ 'admin_panel__rbac:read',
41
+ 'admin_panel__rbac:write',
42
+ 'admin_panel__notifications:read',
43
+ 'admin_panel__notifications:write',
44
+ 'admin_panel__waiting-list:read',
45
+ 'admin_panel__waiting-list:write',
46
+
47
+ // Content & Config section
48
+ 'admin_panel__i18n:read',
49
+ 'admin_panel__i18n:write',
50
+ 'admin_panel__i18n-locales:read',
51
+ 'admin_panel__i18n-locales:write',
52
+ 'admin_panel__json-configs:read',
53
+ 'admin_panel__json-configs:write',
54
+ 'admin_panel__markdowns:read',
55
+ 'admin_panel__markdowns:write',
56
+ 'admin_panel__seo-config:read',
57
+ 'admin_panel__seo-config:write',
58
+ 'admin_panel__assets:read',
59
+ 'admin_panel__assets:write',
60
+ 'admin_panel__file-manager:read',
61
+ 'admin_panel__file-manager:write',
62
+ 'admin_panel__ui-components:read',
63
+ 'admin_panel__ui-components:write',
64
+ 'admin_panel__headless:read',
65
+ 'admin_panel__headless:write',
66
+ 'admin_panel__pages:read',
67
+ 'admin_panel__pages:write',
68
+ 'admin_panel__blog:read',
69
+ 'admin_panel__blog:write',
70
+
71
+ // System & DevOps section
72
+ 'admin_panel__global-settings:read',
73
+ 'admin_panel__global-settings:write',
74
+ 'admin_panel__plugins-system:read',
75
+ 'admin_panel__plugins-system:write',
76
+ 'admin_panel__feature-flags:read',
77
+ 'admin_panel__feature-flags:write',
78
+ 'admin_panel__ejs-virtual:read',
79
+ 'admin_panel__ejs-virtual:write',
80
+ 'admin_panel__rate-limiter:read',
81
+ 'admin_panel__rate-limiter:write',
82
+ 'admin_panel__proxy:read',
83
+ 'admin_panel__proxy:write',
84
+ 'admin_panel__cache:read',
85
+ 'admin_panel__cache:write',
86
+ 'admin_panel__db-browser:read',
87
+ 'admin_panel__db-browser:write',
88
+ 'admin_panel__data-cleanup:read',
89
+ 'admin_panel__data-cleanup:write',
90
+ 'admin_panel__migration:read',
91
+ 'admin_panel__migration:write',
92
+ 'admin_panel__webhooks:read',
93
+ 'admin_panel__webhooks:write',
94
+ 'admin_panel__coolify-deploy:read',
95
+ 'admin_panel__coolify-deploy:write',
96
+
97
+ // Monitoring & AI section
98
+ 'admin_panel__audit:read',
99
+ 'admin_panel__audit:write',
100
+ 'admin_panel__errors:read',
101
+ 'admin_panel__errors:write',
102
+ 'admin_panel__experiments:read',
103
+ 'admin_panel__experiments:write',
104
+ 'admin_panel__console-manager:read',
105
+ 'admin_panel__console-manager:write',
106
+ 'admin_panel__health-checks:read',
107
+ 'admin_panel__health-checks:write',
108
+ 'admin_panel__metrics:read',
109
+ 'admin_panel__metrics:write',
110
+ 'admin_panel__llm:read',
111
+ 'admin_panel__llm:write',
112
+
113
+ // Billing & Forms section
114
+ 'admin_panel__stripe-pricing:read',
115
+ 'admin_panel__stripe-pricing:write',
116
+ 'admin_panel__forms:read',
117
+ 'admin_panel__forms:write',
118
+
119
+ // Automation section
120
+ 'admin_panel__agents:read',
121
+ 'admin_panel__agents:write',
122
+ 'admin_panel__telegram:read',
123
+ 'admin_panel__telegram:write',
124
+ 'admin_panel__workflows:read',
125
+ 'admin_panel__workflows:write',
126
+ 'admin_panel__scripts:read',
127
+ 'admin_panel__scripts:write',
128
+ 'admin_panel__crons:read',
129
+ 'admin_panel__crons:write',
130
+ 'admin_panel__terminals:read',
131
+ 'admin_panel__terminals:write',
132
+
133
+ // Section wildcards for easier role management
134
+ 'admin_panel__user-management:*',
135
+ 'admin_panel__content-config:*',
136
+ 'admin_panel__system-devops:*',
137
+ 'admin_panel__monitoring-ai:*',
138
+ 'admin_panel__billing-forms:*',
139
+ 'admin_panel__automation:*',
140
+
141
+ // Superuser wildcard
24
142
  '*',
25
143
  ];
26
144