@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.
- package/.env.example +5 -0
- package/README.md +11 -0
- package/index.js +39 -1
- package/package.json +11 -3
- package/public/sdk/ui-components.iife.js +191 -0
- package/sdk/ui-components/browser/src/index.js +228 -0
- package/src/admin/endpointRegistry.js +120 -0
- package/src/controllers/admin.controller.js +111 -5
- package/src/controllers/adminBlockDefinitions.controller.js +127 -0
- package/src/controllers/adminBlockDefinitionsAi.controller.js +54 -0
- package/src/controllers/adminCache.controller.js +342 -0
- package/src/controllers/adminContextBlockDefinitions.controller.js +141 -0
- package/src/controllers/adminCrons.controller.js +388 -0
- package/src/controllers/adminDbBrowser.controller.js +124 -0
- package/src/controllers/adminEjsVirtual.controller.js +13 -3
- package/src/controllers/adminHeadless.controller.js +91 -2
- package/src/controllers/adminHealthChecks.controller.js +570 -0
- package/src/controllers/adminI18n.controller.js +51 -29
- package/src/controllers/adminLlm.controller.js +126 -2
- package/src/controllers/adminPages.controller.js +720 -0
- package/src/controllers/adminPagesContextBlocksAi.controller.js +54 -0
- package/src/controllers/adminProxy.controller.js +113 -0
- package/src/controllers/adminRateLimits.controller.js +138 -0
- package/src/controllers/adminRbac.controller.js +803 -0
- package/src/controllers/adminScripts.controller.js +320 -0
- package/src/controllers/adminSeoConfig.controller.js +71 -48
- package/src/controllers/adminTerminals.controller.js +39 -0
- package/src/controllers/adminUiComponents.controller.js +315 -0
- package/src/controllers/adminUiComponentsAi.controller.js +34 -0
- package/src/controllers/blogAdmin.controller.js +279 -0
- package/src/controllers/blogAiAdmin.controller.js +224 -0
- package/src/controllers/blogAutomationAdmin.controller.js +141 -0
- package/src/controllers/blogInternal.controller.js +26 -0
- package/src/controllers/blogPublic.controller.js +89 -0
- package/src/controllers/fileManager.controller.js +190 -0
- package/src/controllers/fileManagerStoragePolicy.controller.js +23 -0
- package/src/controllers/healthChecksPublic.controller.js +196 -0
- package/src/controllers/metrics.controller.js +64 -4
- package/src/controllers/orgAdmin.controller.js +366 -0
- package/src/controllers/uiComponentsPublic.controller.js +118 -0
- package/src/middleware/auth.js +7 -0
- package/src/middleware/internalCronAuth.js +29 -0
- package/src/middleware/rbac.js +62 -0
- package/src/middleware.js +879 -56
- package/src/models/BlockDefinition.js +27 -0
- package/src/models/BlogAutomationLock.js +14 -0
- package/src/models/BlogAutomationRun.js +39 -0
- package/src/models/BlogPost.js +42 -0
- package/src/models/CacheEntry.js +26 -0
- package/src/models/ConsoleEntry.js +32 -0
- package/src/models/ConsoleLog.js +23 -0
- package/src/models/ContextBlockDefinition.js +33 -0
- package/src/models/CronExecution.js +47 -0
- package/src/models/CronJob.js +70 -0
- package/src/models/ExternalDbConnection.js +49 -0
- package/src/models/FileEntry.js +22 -0
- package/src/models/HeadlessModelDefinition.js +10 -0
- package/src/models/HealthAutoHealAttempt.js +57 -0
- package/src/models/HealthCheck.js +132 -0
- package/src/models/HealthCheckRun.js +51 -0
- package/src/models/HealthIncident.js +49 -0
- package/src/models/Page.js +95 -0
- package/src/models/PageCollection.js +42 -0
- package/src/models/ProxyEntry.js +66 -0
- package/src/models/RateLimitCounter.js +19 -0
- package/src/models/RateLimitMetricBucket.js +20 -0
- package/src/models/RbacGrant.js +25 -0
- package/src/models/RbacGroup.js +16 -0
- package/src/models/RbacGroupMember.js +13 -0
- package/src/models/RbacGroupRole.js +13 -0
- package/src/models/RbacRole.js +25 -0
- package/src/models/RbacUserRole.js +13 -0
- package/src/models/ScriptDefinition.js +42 -0
- package/src/models/ScriptRun.js +22 -0
- package/src/models/UiComponent.js +29 -0
- package/src/models/UiComponentProject.js +26 -0
- package/src/models/UiComponentProjectComponent.js +18 -0
- package/src/routes/admin.routes.js +1 -0
- package/src/routes/adminBlog.routes.js +21 -0
- package/src/routes/adminBlogAi.routes.js +16 -0
- package/src/routes/adminBlogAutomation.routes.js +27 -0
- package/src/routes/adminCache.routes.js +20 -0
- package/src/routes/adminConsoleManager.routes.js +302 -0
- package/src/routes/adminCrons.routes.js +25 -0
- package/src/routes/adminDbBrowser.routes.js +65 -0
- package/src/routes/adminEjsVirtual.routes.js +2 -1
- package/src/routes/adminHeadless.routes.js +8 -1
- package/src/routes/adminHealthChecks.routes.js +28 -0
- package/src/routes/adminI18n.routes.js +4 -3
- package/src/routes/adminLlm.routes.js +4 -2
- package/src/routes/adminPages.routes.js +55 -0
- package/src/routes/adminProxy.routes.js +15 -0
- package/src/routes/adminRateLimits.routes.js +17 -0
- package/src/routes/adminRbac.routes.js +38 -0
- package/src/routes/adminScripts.routes.js +21 -0
- package/src/routes/adminSeoConfig.routes.js +5 -4
- package/src/routes/adminTerminals.routes.js +13 -0
- package/src/routes/adminUiComponents.routes.js +30 -0
- package/src/routes/blogInternal.routes.js +14 -0
- package/src/routes/blogPublic.routes.js +9 -0
- package/src/routes/fileManager.routes.js +62 -0
- package/src/routes/fileManagerStoragePolicy.routes.js +9 -0
- package/src/routes/healthChecksPublic.routes.js +9 -0
- package/src/routes/log.routes.js +43 -60
- package/src/routes/metrics.routes.js +4 -2
- package/src/routes/orgAdmin.routes.js +6 -0
- package/src/routes/pages.routes.js +123 -0
- package/src/routes/proxy.routes.js +46 -0
- package/src/routes/rbac.routes.js +47 -0
- package/src/routes/uiComponentsPublic.routes.js +9 -0
- package/src/routes/webhook.routes.js +2 -1
- package/src/routes/workflows.routes.js +4 -0
- package/src/services/blockDefinitionsAi.service.js +247 -0
- package/src/services/blog.service.js +99 -0
- package/src/services/blogAutomation.service.js +978 -0
- package/src/services/blogCronsBootstrap.service.js +184 -0
- package/src/services/blogPublishing.service.js +58 -0
- package/src/services/cacheLayer.service.js +696 -0
- package/src/services/consoleManager.service.js +700 -0
- package/src/services/consoleOverride.service.js +6 -1
- package/src/services/cronScheduler.service.js +350 -0
- package/src/services/dbBrowser.service.js +536 -0
- package/src/services/ejsVirtual.service.js +102 -32
- package/src/services/fileManager.service.js +475 -0
- package/src/services/fileManagerStoragePolicy.service.js +285 -0
- package/src/services/headlessExternalModels.service.js +292 -0
- package/src/services/headlessModels.service.js +26 -6
- package/src/services/healthChecks.service.js +650 -0
- package/src/services/healthChecksBootstrap.service.js +109 -0
- package/src/services/healthChecksScheduler.service.js +106 -0
- package/src/services/llmDefaults.service.js +190 -0
- package/src/services/migrationAssets/s3.js +2 -2
- package/src/services/pages.service.js +602 -0
- package/src/services/pagesContext.service.js +331 -0
- package/src/services/pagesContextBlocksAi.service.js +349 -0
- package/src/services/proxy.service.js +535 -0
- package/src/services/rateLimiter.service.js +623 -0
- package/src/services/rbac.service.js +212 -0
- package/src/services/scriptsRunner.service.js +259 -0
- package/src/services/terminals.service.js +152 -0
- package/src/services/terminalsWs.service.js +100 -0
- package/src/services/uiComponentsAi.service.js +299 -0
- package/src/services/uiComponentsCrypto.service.js +39 -0
- package/src/services/workflow.service.js +23 -8
- package/src/utils/orgRoles.js +14 -0
- package/src/utils/rbac/engine.js +60 -0
- package/src/utils/rbac/rightsRegistry.js +29 -0
- package/views/admin-blog-automation.ejs +877 -0
- package/views/admin-blog-edit.ejs +542 -0
- package/views/admin-blog.ejs +399 -0
- package/views/admin-cache.ejs +681 -0
- package/views/admin-console-manager.ejs +680 -0
- package/views/admin-crons.ejs +645 -0
- package/views/admin-db-browser.ejs +445 -0
- package/views/admin-ejs-virtual.ejs +16 -10
- package/views/admin-file-manager.ejs +942 -0
- package/views/admin-headless.ejs +294 -24
- package/views/admin-health-checks.ejs +725 -0
- package/views/admin-i18n.ejs +59 -5
- package/views/admin-llm.ejs +99 -1
- package/views/admin-organizations.ejs +528 -10
- package/views/admin-pages.ejs +2424 -0
- package/views/admin-proxy.ejs +491 -0
- package/views/admin-rate-limiter.ejs +625 -0
- package/views/admin-rbac.ejs +1331 -0
- package/views/admin-scripts.ejs +497 -0
- package/views/admin-seo-config.ejs +61 -7
- package/views/admin-terminals.ejs +328 -0
- package/views/admin-ui-components.ejs +741 -0
- package/views/admin-users.ejs +261 -4
- package/views/admin-workflows.ejs +7 -7
- package/views/file-manager.ejs +866 -0
- package/views/pages/blocks/contact.ejs +27 -0
- package/views/pages/blocks/cta.ejs +18 -0
- package/views/pages/blocks/faq.ejs +20 -0
- package/views/pages/blocks/features.ejs +19 -0
- package/views/pages/blocks/hero.ejs +13 -0
- package/views/pages/blocks/html.ejs +5 -0
- package/views/pages/blocks/image.ejs +14 -0
- package/views/pages/blocks/testimonials.ejs +26 -0
- package/views/pages/blocks/text.ejs +10 -0
- package/views/pages/layouts/default.ejs +51 -0
- package/views/pages/layouts/minimal.ejs +42 -0
- package/views/pages/layouts/sidebar.ejs +54 -0
- package/views/pages/partials/footer.ejs +13 -0
- package/views/pages/partials/header.ejs +12 -0
- package/views/pages/partials/sidebar.ejs +8 -0
- package/views/pages/runtime/page.ejs +10 -0
- package/views/pages/templates/article.ejs +20 -0
- package/views/pages/templates/default.ejs +12 -0
- package/views/pages/templates/landing.ejs +14 -0
- package/views/pages/templates/listing.ejs +15 -0
- package/views/partials/admin-image-upload-modal.ejs +221 -0
- package/views/partials/dashboard/nav-items.ejs +14 -0
- package/views/partials/llm-provider-model-picker.ejs +183 -0
|
@@ -70,6 +70,75 @@ const endpointRegistry = [
|
|
|
70
70
|
},
|
|
71
71
|
],
|
|
72
72
|
},
|
|
73
|
+
{
|
|
74
|
+
id: "admin-pages",
|
|
75
|
+
title: "Pages (Admin)",
|
|
76
|
+
endpoints: [
|
|
77
|
+
{
|
|
78
|
+
id: "pages-context-block-definitions-list",
|
|
79
|
+
method: "GET",
|
|
80
|
+
path: "/api/admin/pages/context-block-definitions",
|
|
81
|
+
auth: "basic",
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: "pages-context-block-definitions-create",
|
|
85
|
+
method: "POST",
|
|
86
|
+
path: "/api/admin/pages/context-block-definitions",
|
|
87
|
+
auth: "basic",
|
|
88
|
+
bodyExample: {
|
|
89
|
+
code: "blog-post-by-slug",
|
|
90
|
+
label: "Blog post by slug",
|
|
91
|
+
description: "Used by blog post page",
|
|
92
|
+
type: "context.db_query",
|
|
93
|
+
props: {
|
|
94
|
+
assignTo: "post",
|
|
95
|
+
model: "BlogPost",
|
|
96
|
+
op: "findOne",
|
|
97
|
+
filter: { slug: { $ctx: "params.slug" } },
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: "pages-context-block-definitions-get",
|
|
103
|
+
method: "GET",
|
|
104
|
+
path: "/api/admin/pages/context-block-definitions/:code",
|
|
105
|
+
auth: "basic",
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: "pages-context-block-definitions-update",
|
|
109
|
+
method: "PUT",
|
|
110
|
+
path: "/api/admin/pages/context-block-definitions/:code",
|
|
111
|
+
auth: "basic",
|
|
112
|
+
bodyExample: {
|
|
113
|
+
label: "Blog post by slug (updated)",
|
|
114
|
+
description: "Used by blog post page",
|
|
115
|
+
type: "context.db_query",
|
|
116
|
+
props: {
|
|
117
|
+
assignTo: "post",
|
|
118
|
+
model: "BlogPost",
|
|
119
|
+
op: "findOne",
|
|
120
|
+
filter: { slug: { $ctx: "params.slug" } },
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
id: "pages-context-block-definitions-delete",
|
|
126
|
+
method: "DELETE",
|
|
127
|
+
path: "/api/admin/pages/context-block-definitions/:code",
|
|
128
|
+
auth: "basic",
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
id: "pages-context-blocks-ai-generate",
|
|
132
|
+
method: "POST",
|
|
133
|
+
path: "/api/admin/pages/ai/context-blocks/generate",
|
|
134
|
+
auth: "basic",
|
|
135
|
+
bodyExample: {
|
|
136
|
+
prompt: "Load the published BlogPost for params.slug into vars.post. Add cache 30s.",
|
|
137
|
+
blockType: "context.db_query",
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
},
|
|
73
142
|
{
|
|
74
143
|
id: "ejs-virtual",
|
|
75
144
|
title: "EJS Virtual Codebase",
|
|
@@ -295,6 +364,57 @@ const endpointRegistry = [
|
|
|
295
364
|
},
|
|
296
365
|
],
|
|
297
366
|
},
|
|
367
|
+
{
|
|
368
|
+
id: "rate-limits",
|
|
369
|
+
title: "Rate Limiter",
|
|
370
|
+
endpoints: [
|
|
371
|
+
{
|
|
372
|
+
id: "rate-limits-list",
|
|
373
|
+
method: "GET",
|
|
374
|
+
path: "/api/admin/rate-limits",
|
|
375
|
+
auth: "basic",
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
id: "rate-limits-config-get",
|
|
379
|
+
method: "GET",
|
|
380
|
+
path: "/api/admin/rate-limits/config",
|
|
381
|
+
auth: "basic",
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
id: "rate-limits-config-update",
|
|
385
|
+
method: "PUT",
|
|
386
|
+
path: "/api/admin/rate-limits/config",
|
|
387
|
+
auth: "basic",
|
|
388
|
+
bodyExample: { jsonRaw: "{\n \"version\": 1,\n \"defaults\": {},\n \"limiters\": {}\n}" },
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
id: "rate-limits-metrics",
|
|
392
|
+
method: "GET",
|
|
393
|
+
path: "/api/admin/rate-limits/metrics?start=2026-01-01T00:00:00.000Z&end=2026-01-02T00:00:00.000Z",
|
|
394
|
+
auth: "basic",
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
id: "rate-limits-bulk-enabled",
|
|
398
|
+
method: "POST",
|
|
399
|
+
path: "/api/admin/rate-limits/bulk-enabled",
|
|
400
|
+
auth: "basic",
|
|
401
|
+
bodyExample: { enabled: true, all: true },
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
id: "rate-limits-limiter-update",
|
|
405
|
+
method: "PUT",
|
|
406
|
+
path: "/api/admin/rate-limits/globalApiLimiter",
|
|
407
|
+
auth: "basic",
|
|
408
|
+
bodyExample: { override: { enabled: true, mode: "reportOnly", limit: { max: 60, windowMs: 60000 } } },
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
id: "rate-limits-limiter-reset",
|
|
412
|
+
method: "POST",
|
|
413
|
+
path: "/api/admin/rate-limits/globalApiLimiter/reset",
|
|
414
|
+
auth: "basic",
|
|
415
|
+
},
|
|
416
|
+
],
|
|
417
|
+
},
|
|
298
418
|
];
|
|
299
419
|
|
|
300
420
|
module.exports = endpointRegistry;
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
const User = require('../models/User');
|
|
2
2
|
const StripeWebhookEvent = require('../models/StripeWebhookEvent');
|
|
3
|
+
const Organization = require('../models/Organization');
|
|
4
|
+
const OrganizationMember = require('../models/OrganizationMember');
|
|
5
|
+
const Asset = require('../models/Asset');
|
|
6
|
+
const Notification = require('../models/Notification');
|
|
7
|
+
const Invite = require('../models/Invite');
|
|
8
|
+
const EmailLog = require('../models/EmailLog');
|
|
9
|
+
const FormSubmission = require('../models/FormSubmission');
|
|
3
10
|
const asyncHandler = require('../utils/asyncHandler');
|
|
4
11
|
const fs = require('fs');
|
|
5
12
|
const path = require('path');
|
|
@@ -10,12 +17,29 @@ const { auditMiddleware } = require('../services/auditLogger');
|
|
|
10
17
|
|
|
11
18
|
// Get all users
|
|
12
19
|
const getUsers = asyncHandler(async (req, res) => {
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
const { limit, offset } = req.query;
|
|
21
|
+
|
|
22
|
+
const parsedLimit = Math.min(500, Math.max(1, parseInt(limit, 10) || 50));
|
|
23
|
+
const parsedOffset = Math.max(0, parseInt(offset, 10) || 0);
|
|
24
|
+
|
|
25
|
+
const [users, total] = await Promise.all([
|
|
26
|
+
User.find()
|
|
27
|
+
.select('-passwordHash -passwordResetToken -passwordResetExpiry')
|
|
28
|
+
.sort({ createdAt: -1 })
|
|
29
|
+
.limit(parsedLimit)
|
|
30
|
+
.skip(parsedOffset)
|
|
31
|
+
.lean(),
|
|
32
|
+
User.countDocuments()
|
|
33
|
+
]);
|
|
17
34
|
|
|
18
|
-
res.json(
|
|
35
|
+
res.json({
|
|
36
|
+
users,
|
|
37
|
+
pagination: {
|
|
38
|
+
total,
|
|
39
|
+
limit: parsedLimit,
|
|
40
|
+
offset: parsedOffset,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
19
43
|
});
|
|
20
44
|
|
|
21
45
|
// Register new user (admin only)
|
|
@@ -353,12 +377,94 @@ const provisionCoolifyDeploy = asyncHandler(async (req, res) => {
|
|
|
353
377
|
}
|
|
354
378
|
});
|
|
355
379
|
|
|
380
|
+
// Delete user (admin only)
|
|
381
|
+
const deleteUser = asyncHandler(async (req, res) => {
|
|
382
|
+
|
|
383
|
+
const userId = req.params.id;
|
|
384
|
+
|
|
385
|
+
// 1. Validate user exists
|
|
386
|
+
const user = await User.findById(userId);
|
|
387
|
+
if (!user) {
|
|
388
|
+
return res.status(404).json({ error: 'User not found' });
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// 2. Prevent self-deletion
|
|
392
|
+
// Note: In a real implementation, you'd get the admin ID from req.admin or similar
|
|
393
|
+
// For now, we'll skip this check as the basic auth doesn't provide user identity
|
|
394
|
+
|
|
395
|
+
// 3. Check if this is the last admin
|
|
396
|
+
const adminCount = await User.countDocuments({ role: 'admin' });
|
|
397
|
+
if (user.role === 'admin' && adminCount <= 1) {
|
|
398
|
+
return res.status(400).json({ error: 'Cannot delete the last admin user' });
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// 4. Cleanup dependencies
|
|
402
|
+
await cleanupUserData(userId);
|
|
403
|
+
|
|
404
|
+
// 5. Delete user
|
|
405
|
+
await User.findByIdAndDelete(userId);
|
|
406
|
+
|
|
407
|
+
// 6. Log action
|
|
408
|
+
console.log(`Admin deleted user: ${user.email} (${userId})`);
|
|
409
|
+
|
|
410
|
+
res.json({ message: 'User deleted successfully' });
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
// Helper function to clean up user data
|
|
414
|
+
async function cleanupUserData(userId) {
|
|
415
|
+
|
|
416
|
+
try {
|
|
417
|
+
// Handle organizations owned by user
|
|
418
|
+
const ownedOrgs = await Organization.find({ ownerUserId: userId });
|
|
419
|
+
for (const org of ownedOrgs) {
|
|
420
|
+
// Check if organization has other members
|
|
421
|
+
const memberCount = await OrganizationMember.countDocuments({
|
|
422
|
+
orgId: org._id,
|
|
423
|
+
userId: { $ne: userId }
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
if (memberCount === 0) {
|
|
427
|
+
// Delete organization if no other members
|
|
428
|
+
await Organization.findByIdAndDelete(org._id);
|
|
429
|
+
console.log(`Deleted organization ${org.name} (${org._id}) - no other members`);
|
|
430
|
+
} else {
|
|
431
|
+
// Remove owner but keep organization
|
|
432
|
+
org.ownerUserId = null;
|
|
433
|
+
await org.save();
|
|
434
|
+
console.log(`Removed owner from organization ${org.name} (${org._id}) - has other members`);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Remove from all organization memberships
|
|
439
|
+
await OrganizationMember.deleteMany({ userId: userId });
|
|
440
|
+
|
|
441
|
+
// Delete user's assets
|
|
442
|
+
await Asset.deleteMany({ ownerUserId: userId });
|
|
443
|
+
|
|
444
|
+
// Delete notifications
|
|
445
|
+
await Notification.deleteMany({ userId: userId });
|
|
446
|
+
|
|
447
|
+
// Clean up other references
|
|
448
|
+
await Invite.deleteMany({ createdByUserId: userId });
|
|
449
|
+
await EmailLog.deleteMany({ userId: userId });
|
|
450
|
+
await FormSubmission.deleteMany({ userId: userId });
|
|
451
|
+
|
|
452
|
+
// Note: We keep ActivityLog and AuditEvent for audit purposes
|
|
453
|
+
|
|
454
|
+
console.log(`Completed cleanup for user ${userId}`);
|
|
455
|
+
} catch (error) {
|
|
456
|
+
console.error('Error during user cleanup:', error);
|
|
457
|
+
throw error;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
356
461
|
module.exports = {
|
|
357
462
|
getUsers,
|
|
358
463
|
registerUser,
|
|
359
464
|
getUser,
|
|
360
465
|
updateUserSubscription,
|
|
361
466
|
updateUserPassword,
|
|
467
|
+
deleteUser,
|
|
362
468
|
reconcileUser,
|
|
363
469
|
generateToken,
|
|
364
470
|
getWebhookEvents,
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
const BlockDefinition = require('../models/BlockDefinition');
|
|
2
|
+
|
|
3
|
+
function parseBool(value, fallback) {
|
|
4
|
+
if (value === undefined) return fallback;
|
|
5
|
+
if (typeof value === 'boolean') return value;
|
|
6
|
+
const v = String(value).trim().toLowerCase();
|
|
7
|
+
if (v === 'true' || v === '1' || v === 'yes') return true;
|
|
8
|
+
if (v === 'false' || v === '0' || v === 'no') return false;
|
|
9
|
+
return fallback;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function normalizeCode(code) {
|
|
13
|
+
return String(code || '').trim().toLowerCase();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function normalizeFields(fields) {
|
|
17
|
+
if (!fields) return {};
|
|
18
|
+
if (typeof fields !== 'object' || Array.isArray(fields)) return null;
|
|
19
|
+
return fields;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
exports.list = async (req, res) => {
|
|
23
|
+
try {
|
|
24
|
+
const onlyActive = parseBool(req.query?.active, null);
|
|
25
|
+
const filter = {};
|
|
26
|
+
if (onlyActive !== null) filter.isActive = onlyActive;
|
|
27
|
+
|
|
28
|
+
const items = await BlockDefinition.find(filter).sort({ updatedAt: -1 }).lean();
|
|
29
|
+
return res.json({ items });
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error('[adminBlockDefinitions] list error:', error);
|
|
32
|
+
return res.status(500).json({ error: 'Failed to list block definitions' });
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
exports.create = async (req, res) => {
|
|
37
|
+
try {
|
|
38
|
+
const code = normalizeCode(req.body?.code);
|
|
39
|
+
const label = String(req.body?.label || '').trim();
|
|
40
|
+
if (!code) return res.status(400).json({ error: 'code is required' });
|
|
41
|
+
if (!label) return res.status(400).json({ error: 'label is required' });
|
|
42
|
+
|
|
43
|
+
const fields = normalizeFields(req.body?.fields);
|
|
44
|
+
if (fields === null) return res.status(400).json({ error: 'fields must be an object' });
|
|
45
|
+
|
|
46
|
+
const doc = await BlockDefinition.create({
|
|
47
|
+
code,
|
|
48
|
+
label,
|
|
49
|
+
description: String(req.body?.description || ''),
|
|
50
|
+
fields: fields || {},
|
|
51
|
+
version: Number(req.body?.version || 1) || 1,
|
|
52
|
+
isActive: parseBool(req.body?.isActive, true),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return res.status(201).json({ item: doc.toObject() });
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error('[adminBlockDefinitions] create error:', error);
|
|
58
|
+
if (error?.name === 'ValidationError') return res.status(400).json({ error: error.message });
|
|
59
|
+
if (error?.code === 11000) return res.status(409).json({ error: 'Block already exists' });
|
|
60
|
+
return res.status(500).json({ error: 'Failed to create block definition' });
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
exports.get = async (req, res) => {
|
|
65
|
+
try {
|
|
66
|
+
const code = normalizeCode(req.params?.code);
|
|
67
|
+
const item = await BlockDefinition.findOne({ code }).lean();
|
|
68
|
+
if (!item) return res.status(404).json({ error: 'Block not found' });
|
|
69
|
+
return res.json({ item });
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error('[adminBlockDefinitions] get error:', error);
|
|
72
|
+
return res.status(500).json({ error: 'Failed to load block definition' });
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
exports.update = async (req, res) => {
|
|
77
|
+
try {
|
|
78
|
+
const code = normalizeCode(req.params?.code);
|
|
79
|
+
const doc = await BlockDefinition.findOne({ code });
|
|
80
|
+
if (!doc) return res.status(404).json({ error: 'Block not found' });
|
|
81
|
+
|
|
82
|
+
if (req.body?.label !== undefined) {
|
|
83
|
+
const label = String(req.body.label || '').trim();
|
|
84
|
+
if (!label) return res.status(400).json({ error: 'label is required' });
|
|
85
|
+
doc.label = label;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (req.body?.description !== undefined) doc.description = String(req.body.description || '');
|
|
89
|
+
|
|
90
|
+
if (req.body?.fields !== undefined) {
|
|
91
|
+
const fields = normalizeFields(req.body.fields);
|
|
92
|
+
if (fields === null) return res.status(400).json({ error: 'fields must be an object' });
|
|
93
|
+
doc.fields = fields;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (req.body?.version !== undefined) {
|
|
97
|
+
const v = Number(req.body.version);
|
|
98
|
+
if (!Number.isFinite(v) || v < 1) return res.status(400).json({ error: 'version must be a positive number' });
|
|
99
|
+
doc.version = v;
|
|
100
|
+
} else {
|
|
101
|
+
doc.version = Number(doc.version || 1) + 1;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (req.body?.isActive !== undefined) doc.isActive = Boolean(req.body.isActive);
|
|
105
|
+
|
|
106
|
+
await doc.save();
|
|
107
|
+
return res.json({ item: doc.toObject() });
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error('[adminBlockDefinitions] update error:', error);
|
|
110
|
+
if (error?.name === 'ValidationError') return res.status(400).json({ error: error.message });
|
|
111
|
+
return res.status(500).json({ error: 'Failed to update block definition' });
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
exports.remove = async (req, res) => {
|
|
116
|
+
try {
|
|
117
|
+
const code = normalizeCode(req.params?.code);
|
|
118
|
+
const doc = await BlockDefinition.findOne({ code });
|
|
119
|
+
if (!doc) return res.status(404).json({ error: 'Block not found' });
|
|
120
|
+
|
|
121
|
+
await BlockDefinition.deleteOne({ _id: doc._id });
|
|
122
|
+
return res.json({ success: true });
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.error('[adminBlockDefinitions] remove error:', error);
|
|
125
|
+
return res.status(500).json({ error: 'Failed to delete block definition' });
|
|
126
|
+
}
|
|
127
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const {
|
|
2
|
+
generateBlockDefinition,
|
|
3
|
+
proposeBlockDefinitionEdit,
|
|
4
|
+
} = require('../services/blockDefinitionsAi.service');
|
|
5
|
+
|
|
6
|
+
const { getBasicAuthActor } = require('../services/audit.service');
|
|
7
|
+
|
|
8
|
+
function handleError(res, err) {
|
|
9
|
+
const code = err && err.code;
|
|
10
|
+
if (code === 'VALIDATION') return res.status(400).json({ error: err.message });
|
|
11
|
+
if (code === 'NOT_FOUND') return res.status(404).json({ error: err.message });
|
|
12
|
+
if (code === 'AI_INVALID') return res.status(500).json({ error: err.message });
|
|
13
|
+
return res.status(500).json({ error: err.message || 'Operation failed' });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
exports.generate = async (req, res) => {
|
|
17
|
+
try {
|
|
18
|
+
const actor = getBasicAuthActor(req);
|
|
19
|
+
const { prompt, providerKey, model } = req.body || {};
|
|
20
|
+
|
|
21
|
+
const result = await generateBlockDefinition({
|
|
22
|
+
prompt,
|
|
23
|
+
providerKey,
|
|
24
|
+
model,
|
|
25
|
+
actor,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return res.json(result);
|
|
29
|
+
} catch (err) {
|
|
30
|
+
console.error('[adminBlockDefinitionsAi] generate error', err);
|
|
31
|
+
return handleError(res, err);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
exports.propose = async (req, res) => {
|
|
36
|
+
try {
|
|
37
|
+
const actor = getBasicAuthActor(req);
|
|
38
|
+
const { code } = req.params;
|
|
39
|
+
const { prompt, providerKey, model } = req.body || {};
|
|
40
|
+
|
|
41
|
+
const result = await proposeBlockDefinitionEdit({
|
|
42
|
+
code,
|
|
43
|
+
prompt,
|
|
44
|
+
providerKey,
|
|
45
|
+
model,
|
|
46
|
+
actor,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return res.json(result);
|
|
50
|
+
} catch (err) {
|
|
51
|
+
console.error('[adminBlockDefinitionsAi] propose error', err);
|
|
52
|
+
return handleError(res, err);
|
|
53
|
+
}
|
|
54
|
+
};
|