@intranefr/superbackend 1.5.0 → 1.5.2
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 +15 -0
- package/README.md +11 -0
- package/analysis-only.skill +0 -0
- package/index.js +23 -0
- package/package.json +8 -2
- package/src/admin/endpointRegistry.js +120 -0
- package/src/controllers/admin.controller.js +90 -6
- 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/adminExperiments.controller.js +200 -0
- package/src/controllers/adminHeadless.controller.js +9 -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 +126 -4
- package/src/controllers/adminSeoConfig.controller.js +71 -48
- 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/experiments.controller.js +85 -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/internalExperiments.controller.js +17 -0
- package/src/controllers/metrics.controller.js +64 -4
- package/src/controllers/orgAdmin.controller.js +80 -0
- package/src/helpers/mongooseHelper.js +258 -0
- package/src/helpers/scriptBase.js +230 -0
- package/src/helpers/scriptRunner.js +335 -0
- package/src/middleware/rbac.js +62 -0
- package/src/middleware.js +810 -48
- 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/Experiment.js +75 -0
- package/src/models/ExperimentAssignment.js +23 -0
- package/src/models/ExperimentEvent.js +26 -0
- package/src/models/ExperimentMetricBucket.js +30 -0
- package/src/models/ExternalDbConnection.js +49 -0
- package/src/models/FileEntry.js +22 -0
- package/src/models/GlobalSetting.js +1 -2
- 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 +1 -0
- package/src/models/Webhook.js +2 -0
- package/src/routes/admin.routes.js +2 -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/adminExperiments.routes.js +29 -0
- package/src/routes/adminHeadless.routes.js +2 -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/adminSeoConfig.routes.js +5 -4
- package/src/routes/adminUiComponents.routes.js +2 -1
- package/src/routes/blogInternal.routes.js +14 -0
- package/src/routes/blogPublic.routes.js +9 -0
- package/src/routes/experiments.routes.js +30 -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/internalExperiments.routes.js +15 -0
- package/src/routes/log.routes.js +43 -60
- package/src/routes/metrics.routes.js +4 -2
- package/src/routes/orgAdmin.routes.js +1 -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/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 +185 -0
- package/src/services/blogPublishing.service.js +58 -0
- package/src/services/cacheLayer.service.js +696 -0
- package/src/services/consoleManager.service.js +738 -0
- package/src/services/consoleOverride.service.js +7 -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/experiments.service.js +273 -0
- package/src/services/experimentsAggregation.service.js +308 -0
- package/src/services/experimentsCronsBootstrap.service.js +118 -0
- package/src/services/experimentsRetention.service.js +43 -0
- package/src/services/experimentsWs.service.js +134 -0
- package/src/services/fileManager.service.js +475 -0
- package/src/services/fileManagerStoragePolicy.service.js +285 -0
- package/src/services/globalSettings.service.js +15 -0
- 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/jsonConfigs.service.js +2 -2
- 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 +215 -15
- package/src/services/uiComponentsAi.service.js +6 -19
- 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 +33 -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-dashboard.ejs +28 -8
- package/views/admin-db-browser.ejs +445 -0
- package/views/admin-ejs-virtual.ejs +16 -10
- package/views/admin-experiments.ejs +91 -0
- package/views/admin-file-manager.ejs +942 -0
- 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 +163 -1
- 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 +597 -3
- package/views/admin-seo-config.ejs +61 -7
- package/views/admin-ui-components.ejs +57 -25
- 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 +12 -0
- package/views/partials/dashboard/palette.ejs +5 -3
- package/views/partials/llm-provider-model-picker.ejs +183 -0
- package/src/routes/llmUi.routes.js +0 -26
package/.env.example
CHANGED
|
@@ -27,6 +27,16 @@ STRIPE_WEBHOOK_SECRET=whsec_...
|
|
|
27
27
|
ADMIN_USERNAME=admin
|
|
28
28
|
ADMIN_PASSWORD=change-me-in-production
|
|
29
29
|
|
|
30
|
+
# Internal Cron Basic Auth (for internal APIs)
|
|
31
|
+
INTERNAL_CRON_USERNAME=admin
|
|
32
|
+
INTERNAL_CRON_PASSWORD=change-me-in-production
|
|
33
|
+
|
|
34
|
+
# Alternative Basic Auth variables (fallbacks)
|
|
35
|
+
BASIC_AUTH_USERNAME=admin
|
|
36
|
+
BASIC_AUTH_PASSWORD=change-me-in-production
|
|
37
|
+
BASIC_AUTH_USER=admin
|
|
38
|
+
BASIC_AUTH_PASS=change-me-in-production
|
|
39
|
+
|
|
30
40
|
# Emailing
|
|
31
41
|
RESEND_API_KEY=
|
|
32
42
|
|
|
@@ -50,3 +60,8 @@ MAX_FILE_SIZE_HARD_CAP=10485760
|
|
|
50
60
|
# Encryption key for encrypted settings (new preferred name)
|
|
51
61
|
# SUPERBACKEND_ENCRYPTION_KEY=your-32-byte-encryption-key
|
|
52
62
|
# Legacy fallback: SAASBACKEND_ENCRYPTION_KEY=your-32-byte-encryption-key
|
|
63
|
+
|
|
64
|
+
# Console Manager
|
|
65
|
+
# Set to 'false' to disable console manager initialization
|
|
66
|
+
# When disabled, console methods are not overridden and no console entries are tracked
|
|
67
|
+
# CONSOLE_MANAGER_ENABLED=true
|
package/README.md
CHANGED
|
@@ -23,6 +23,13 @@ Node.js middleware that gives your project backend superpowers. Handles authenti
|
|
|
23
23
|
- **Webhooks**: Outgoing webhook system for event-driven integrations
|
|
24
24
|
- **Metrics & Activity**: Usage tracking and analytics for business insights
|
|
25
25
|
- **Middleware Mode**: Drop-in Express middleware that preserves your app structure
|
|
26
|
+
- **Workflows System**: Node-based automation with LLM integration, conditionals, and HTTP calls
|
|
27
|
+
- **LLM UI Integration**: AI-powered UI components and conversational interfaces
|
|
28
|
+
- **Admin Scripts & Terminals**: Operational tooling for script execution and terminal management
|
|
29
|
+
- **Migration System**: Database migration and data transfer between environments
|
|
30
|
+
- **Upload Namespaces**: Advanced file organization with customizable storage rules
|
|
31
|
+
- **UI Components**: Project-scoped reusable UI widgets with browser SDK integration
|
|
32
|
+
- **UI Components AI Assistance**: AI-powered generation and iteration of UI component code
|
|
26
33
|
|
|
27
34
|
---
|
|
28
35
|
|
|
@@ -85,6 +92,10 @@ See the `docs/features/` directory for detailed guides:
|
|
|
85
92
|
- [Core Configuration](docs/features/core-configuration.md)
|
|
86
93
|
- [Admin API Usage](docs/features/admin-api-usage.md)
|
|
87
94
|
- [Billing & Subscriptions](docs/features/billing-and-subscriptions.md)
|
|
95
|
+
- [Workflows System](docs/features/workflows-system.md)
|
|
96
|
+
- [LLM UI Integration](docs/features/llm-ui-integration.md)
|
|
97
|
+
- [Migration System](docs/features/migration-system.md)
|
|
98
|
+
- [Upload Namespaces](docs/features/upload-namespaces.md)
|
|
88
99
|
|
|
89
100
|
---
|
|
90
101
|
|
|
Binary file
|
package/index.js
CHANGED
|
@@ -62,6 +62,8 @@ const saasbackend = {
|
|
|
62
62
|
storage: require("./src/services/storage"),
|
|
63
63
|
i18n: require("./src/services/i18n.service"),
|
|
64
64
|
audit: require("./src/services/audit.service"),
|
|
65
|
+
cacheLayer: require("./src/services/cacheLayer.service"),
|
|
66
|
+
rbac: require("./src/services/rbac.service"),
|
|
65
67
|
globalSettings: require("./src/services/globalSettings.service"),
|
|
66
68
|
jsonConfigs: require("./src/services/jsonConfigs.service"),
|
|
67
69
|
assets: require("./src/services/assets.service"),
|
|
@@ -72,12 +74,24 @@ const saasbackend = {
|
|
|
72
74
|
forms: require("./src/services/forms.service"),
|
|
73
75
|
webhooks: require("./src/services/webhook.service"),
|
|
74
76
|
workflow: require("./src/services/workflow.service"),
|
|
77
|
+
healthChecks: require("./src/services/healthChecks.service"),
|
|
78
|
+
dbBrowser: require("./src/services/dbBrowser.service"),
|
|
79
|
+
rateLimiter: require("./src/services/rateLimiter.service"),
|
|
75
80
|
},
|
|
76
81
|
models: {
|
|
77
82
|
ActionEvent: require("./src/models/ActionEvent"),
|
|
78
83
|
ActivityLog: require("./src/models/ActivityLog"),
|
|
79
84
|
Asset: require("./src/models/Asset"),
|
|
80
85
|
AuditEvent: require("./src/models/AuditEvent"),
|
|
86
|
+
CacheEntry: require("./src/models/CacheEntry"),
|
|
87
|
+
RateLimitCounter: require("./src/models/RateLimitCounter"),
|
|
88
|
+
RateLimitMetricBucket: require("./src/models/RateLimitMetricBucket"),
|
|
89
|
+
RbacRole: require("./src/models/RbacRole"),
|
|
90
|
+
RbacUserRole: require("./src/models/RbacUserRole"),
|
|
91
|
+
RbacGroup: require("./src/models/RbacGroup"),
|
|
92
|
+
RbacGroupMember: require("./src/models/RbacGroupMember"),
|
|
93
|
+
RbacGroupRole: require("./src/models/RbacGroupRole"),
|
|
94
|
+
RbacGrant: require("./src/models/RbacGrant"),
|
|
81
95
|
EmailLog: require("./src/models/EmailLog"),
|
|
82
96
|
ErrorAggregate: require("./src/models/ErrorAggregate"),
|
|
83
97
|
FormSubmission: require("./src/models/FormSubmission"),
|
|
@@ -99,13 +113,22 @@ const saasbackend = {
|
|
|
99
113
|
Webhook: require("./src/models/Webhook"),
|
|
100
114
|
Workflow: require("./src/models/Workflow"),
|
|
101
115
|
WorkflowExecution: require("./src/models/WorkflowExecution"),
|
|
116
|
+
|
|
117
|
+
HealthCheck: require("./src/models/HealthCheck"),
|
|
118
|
+
HealthCheckRun: require("./src/models/HealthCheckRun"),
|
|
119
|
+
HealthIncident: require("./src/models/HealthIncident"),
|
|
120
|
+
HealthAutoHealAttempt: require("./src/models/HealthAutoHealAttempt"),
|
|
121
|
+
|
|
122
|
+
ExternalDbConnection: require("./src/models/ExternalDbConnection"),
|
|
102
123
|
},
|
|
103
124
|
helpers: {
|
|
104
125
|
auth: require("./src/middleware/auth"),
|
|
105
126
|
org: require("./src/middleware/org"),
|
|
127
|
+
rbac: require("./src/middleware/rbac"),
|
|
106
128
|
i18n: require("./src/services/i18n.service"),
|
|
107
129
|
jsonConfigs: require("./src/services/jsonConfigs.service"),
|
|
108
130
|
terminals: require("./src/services/terminalsWs.service"),
|
|
131
|
+
rateLimiter: require("./src/services/rateLimiter.service"),
|
|
109
132
|
},
|
|
110
133
|
};
|
|
111
134
|
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intranefr/superbackend",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.2",
|
|
4
4
|
"description": "Node.js middleware that gives your project backend superpowers",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"start": "node server.js",
|
|
8
|
-
"dev": "nodemon server.js",
|
|
8
|
+
"dev": "nodemon --verbose --ignore uploads --ignore '*.log' server.js",
|
|
9
9
|
"start:minio": "docker compose -f compose.standalone.yml --profile minio-only up -d minio",
|
|
10
10
|
"minio:envs": "node -e \"console.log(['S3_ENDPOINT=http://localhost:9000','S3_REGION=us-east-1','S3_ACCESS_KEY_ID=minioadmin','S3_SECRET_ACCESS_KEY=minioadmin','S3_BUCKET=saasbackend','S3_FORCE_PATH_STYLE=true'].join('\\n'))\"",
|
|
11
11
|
"build:sdk:error-tracking:browser": "esbuild sdk/error-tracking/browser/src/embed.js --bundle --format=iife --global-name=saasbackendErrorTrackingEmbed --outfile=sdk/error-tracking/browser/dist/embed.iife.js",
|
|
@@ -36,14 +36,19 @@
|
|
|
36
36
|
"bcryptjs": "^2.4.3",
|
|
37
37
|
"cheerio": "^1.0.0-rc.12",
|
|
38
38
|
"cors": "^2.8.5",
|
|
39
|
+
"cron-parser": "3.5.0",
|
|
39
40
|
"dotenv": "^16.3.1",
|
|
40
41
|
"ejs": "^3.1.9",
|
|
41
42
|
"express": "^4.18.2",
|
|
42
43
|
"jsonwebtoken": "^9.0.2",
|
|
44
|
+
"marked": "^4.3.0",
|
|
43
45
|
"mongoose": "^8.0.0",
|
|
44
46
|
"multer": "^1.4.5-lts.1",
|
|
47
|
+
"mysql2": "^3.16.1",
|
|
48
|
+
"node-cron": "^4.2.1",
|
|
45
49
|
"node-pty": "^1.1.0",
|
|
46
50
|
"openai": "^4.0.0",
|
|
51
|
+
"redis": "^4.7.1",
|
|
47
52
|
"resend": "^6.4.0",
|
|
48
53
|
"ssh2-sftp-client": "^12.0.1",
|
|
49
54
|
"stripe": "^14.0.0",
|
|
@@ -58,6 +63,7 @@
|
|
|
58
63
|
},
|
|
59
64
|
"jest": {
|
|
60
65
|
"testEnvironment": "node",
|
|
66
|
+
"testTimeout": 15000,
|
|
61
67
|
"collectCoverageFrom": [
|
|
62
68
|
"src/**/*.js",
|
|
63
69
|
"!src/**/*.test.js"
|
|
@@ -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;
|
|
@@ -10,6 +10,7 @@ const FormSubmission = require('../models/FormSubmission');
|
|
|
10
10
|
const asyncHandler = require('../utils/asyncHandler');
|
|
11
11
|
const fs = require('fs');
|
|
12
12
|
const path = require('path');
|
|
13
|
+
const crypto = require('crypto');
|
|
13
14
|
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
|
|
14
15
|
const { generateAccessToken, generateRefreshToken } = require('../utils/jwt');
|
|
15
16
|
const { retryFailedWebhooks, processWebhookEvent } = require('../utils/webhookRetry');
|
|
@@ -17,12 +18,29 @@ const { auditMiddleware } = require('../services/auditLogger');
|
|
|
17
18
|
|
|
18
19
|
// Get all users
|
|
19
20
|
const getUsers = asyncHandler(async (req, res) => {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
const { limit, offset } = req.query;
|
|
22
|
+
|
|
23
|
+
const parsedLimit = Math.min(500, Math.max(1, parseInt(limit, 10) || 50));
|
|
24
|
+
const parsedOffset = Math.max(0, parseInt(offset, 10) || 0);
|
|
25
|
+
|
|
26
|
+
const [users, total] = await Promise.all([
|
|
27
|
+
User.find()
|
|
28
|
+
.select('-passwordHash -passwordResetToken -passwordResetExpiry')
|
|
29
|
+
.sort({ createdAt: -1 })
|
|
30
|
+
.limit(parsedLimit)
|
|
31
|
+
.skip(parsedOffset)
|
|
32
|
+
.lean(),
|
|
33
|
+
User.countDocuments()
|
|
34
|
+
]);
|
|
24
35
|
|
|
25
|
-
res.json(
|
|
36
|
+
res.json({
|
|
37
|
+
users,
|
|
38
|
+
pagination: {
|
|
39
|
+
total,
|
|
40
|
+
limit: parsedLimit,
|
|
41
|
+
offset: parsedOffset,
|
|
42
|
+
},
|
|
43
|
+
});
|
|
26
44
|
});
|
|
27
45
|
|
|
28
46
|
// Register new user (admin only)
|
|
@@ -441,6 +459,71 @@ async function cleanupUserData(userId) {
|
|
|
441
459
|
}
|
|
442
460
|
}
|
|
443
461
|
|
|
462
|
+
const generateTokenForEmail = asyncHandler(async (req, res) => {
|
|
463
|
+
const { email } = req.body;
|
|
464
|
+
|
|
465
|
+
if (!email) {
|
|
466
|
+
return res.status(400).json({ error: 'Email is required' });
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Find or create user
|
|
470
|
+
let user = await User.findOne({ email: email.toLowerCase() });
|
|
471
|
+
|
|
472
|
+
if (!user) {
|
|
473
|
+
// Generate random password
|
|
474
|
+
const randomPassword = crypto.randomBytes(16).toString('hex');
|
|
475
|
+
|
|
476
|
+
// Create user with admin role
|
|
477
|
+
user = new User({
|
|
478
|
+
email: email.toLowerCase(),
|
|
479
|
+
passwordHash: randomPassword,
|
|
480
|
+
name: email,
|
|
481
|
+
role: 'admin' // All auto-created users get admin role
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
await user.save();
|
|
485
|
+
|
|
486
|
+
// Create default organization automatically
|
|
487
|
+
const defaultOrgSlug = process.env.POLYBOT_DEFAULT_ORG_SLUG || 'polybot';
|
|
488
|
+
const defaultOrgName = process.env.POLYBOT_DEFAULT_ORG_NAME || 'Polybot';
|
|
489
|
+
|
|
490
|
+
let org = await Organization.findOne({ slug: defaultOrgSlug });
|
|
491
|
+
if (!org) {
|
|
492
|
+
org = new Organization({
|
|
493
|
+
name: defaultOrgName,
|
|
494
|
+
slug: defaultOrgSlug,
|
|
495
|
+
ownerUserId: user._id
|
|
496
|
+
});
|
|
497
|
+
await org.save();
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Add user to organization
|
|
501
|
+
const existingMember = await OrganizationMember.findOne({
|
|
502
|
+
userId: user._id,
|
|
503
|
+
orgId: org._id
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
if (!existingMember) {
|
|
507
|
+
const member = new OrganizationMember({
|
|
508
|
+
userId: user._id,
|
|
509
|
+
orgId: org._id,
|
|
510
|
+
role: 'admin'
|
|
511
|
+
});
|
|
512
|
+
await member.save();
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Generate tokens with 1 second expiry for access, 2 hours for refresh
|
|
517
|
+
const token = generateAccessToken(user._id, user.role);
|
|
518
|
+
const refreshToken = generateRefreshToken(user._id);
|
|
519
|
+
|
|
520
|
+
res.json({
|
|
521
|
+
token,
|
|
522
|
+
refreshToken,
|
|
523
|
+
user: user.toJSON()
|
|
524
|
+
});
|
|
525
|
+
});
|
|
526
|
+
|
|
444
527
|
module.exports = {
|
|
445
528
|
getUsers,
|
|
446
529
|
registerUser,
|
|
@@ -450,10 +533,11 @@ module.exports = {
|
|
|
450
533
|
deleteUser,
|
|
451
534
|
reconcileUser,
|
|
452
535
|
generateToken,
|
|
536
|
+
generateTokenForEmail,
|
|
453
537
|
getWebhookEvents,
|
|
454
538
|
getWebhookEvent,
|
|
455
539
|
retryFailedWebhookEvents,
|
|
456
540
|
retrySingleWebhookEvent,
|
|
457
541
|
getWebhookStats,
|
|
458
|
-
provisionCoolifyDeploy
|
|
542
|
+
provisionCoolifyDeploy,
|
|
459
543
|
};
|
|
@@ -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
|
+
};
|