@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
|
@@ -54,7 +54,7 @@ const consoleOverride = {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
// Wait a bit for stream to fully close, then truncate
|
|
57
|
-
setTimeout(() => {
|
|
57
|
+
const truncateTimer = setTimeout(() => {
|
|
58
58
|
// Truncate log file on initialization (start with empty file)
|
|
59
59
|
if (fs.existsSync(logPath)) {
|
|
60
60
|
fs.truncateSync(logPath, 0);
|
|
@@ -87,6 +87,11 @@ const consoleOverride = {
|
|
|
87
87
|
originalConsole.log(initMsg);
|
|
88
88
|
this._writeToFile(initMsg);
|
|
89
89
|
}, 10);
|
|
90
|
+
|
|
91
|
+
// Avoid keeping the event loop alive in tests / short-lived processes.
|
|
92
|
+
if (typeof truncateTimer.unref === "function") {
|
|
93
|
+
truncateTimer.unref();
|
|
94
|
+
}
|
|
90
95
|
|
|
91
96
|
} catch (error) {
|
|
92
97
|
// Fallback to console-only logging
|
|
@@ -289,3 +294,4 @@ process.on('SIGTERM', () => {
|
|
|
289
294
|
});
|
|
290
295
|
|
|
291
296
|
module.exports = consoleOverride;
|
|
297
|
+
module.exports.TRULY_ORIGINAL_CONSOLE = TRULY_ORIGINAL_CONSOLE;
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
const cron = require('node-cron');
|
|
2
|
+
const parser = require('cron-parser');
|
|
3
|
+
const { startRun } = require('./scriptsRunner.service');
|
|
4
|
+
const CronJob = require('../models/CronJob');
|
|
5
|
+
const CronExecution = require('../models/CronExecution');
|
|
6
|
+
const ScriptDefinition = require('../models/ScriptDefinition');
|
|
7
|
+
const ScriptRun = require('../models/ScriptRun');
|
|
8
|
+
|
|
9
|
+
const console = global.console
|
|
10
|
+
|
|
11
|
+
class CronScheduler {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.scheduledJobs = new Map(); // Map<jobId, cron.ScheduledTask>
|
|
14
|
+
this.isRunning = false;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async start() {
|
|
18
|
+
if (this.isRunning) return;
|
|
19
|
+
this.isRunning = true;
|
|
20
|
+
|
|
21
|
+
// Load all enabled jobs from database
|
|
22
|
+
const enabledJobs = await CronJob.find({ enabled: true }).lean();
|
|
23
|
+
|
|
24
|
+
for (const job of enabledJobs) {
|
|
25
|
+
try {
|
|
26
|
+
await this.scheduleJob(job);
|
|
27
|
+
} catch (err) {
|
|
28
|
+
console.error(`Failed to schedule cron job ${job.name}:`, err);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
console.log(`Cron scheduler started with ${enabledJobs.length} jobs`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async stop() {
|
|
36
|
+
// Unschedule all jobs
|
|
37
|
+
for (const [jobId, task] of this.scheduledJobs) {
|
|
38
|
+
task.stop();
|
|
39
|
+
}
|
|
40
|
+
this.scheduledJobs.clear();
|
|
41
|
+
this.isRunning = false;
|
|
42
|
+
console.log('Cron scheduler stopped');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async scheduleJob(cronJob) {
|
|
46
|
+
const jobId = String(cronJob._id);
|
|
47
|
+
|
|
48
|
+
// Unschedule if already scheduled
|
|
49
|
+
if (this.scheduledJobs.has(jobId)) {
|
|
50
|
+
this.unscheduleJob(jobId);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Validate cron expression
|
|
54
|
+
if (!cron.validate(cronJob.cronExpression)) {
|
|
55
|
+
throw new Error(`Invalid cron expression: ${cronJob.cronExpression}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Create the scheduled task
|
|
59
|
+
const task = cron.schedule(cronJob.cronExpression, async () => {
|
|
60
|
+
await this.executeJob(cronJob);
|
|
61
|
+
}, {
|
|
62
|
+
scheduled: false, // Don't start immediately
|
|
63
|
+
timezone: cronJob.timezone || 'UTC',
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Start the task
|
|
67
|
+
task.start();
|
|
68
|
+
this.scheduledJobs.set(jobId, task);
|
|
69
|
+
|
|
70
|
+
// Update next run time
|
|
71
|
+
const nextRunAt = this.calculateNextRun(cronJob.cronExpression, cronJob.timezone);
|
|
72
|
+
await CronJob.updateOne(
|
|
73
|
+
{ _id: jobId },
|
|
74
|
+
{ $set: { nextRunAt } }
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
console.log(`Scheduled cron job: ${cronJob.name} (${cronJob.cronExpression})`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async unscheduleJob(jobId) {
|
|
81
|
+
const task = this.scheduledJobs.get(String(jobId));
|
|
82
|
+
if (task) {
|
|
83
|
+
task.stop();
|
|
84
|
+
this.scheduledJobs.delete(String(jobId));
|
|
85
|
+
|
|
86
|
+
// Clear next run time
|
|
87
|
+
await CronJob.updateOne(
|
|
88
|
+
{ _id: jobId },
|
|
89
|
+
{ $set: { nextRunAt: null } }
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
console.log(`Unscheduled cron job: ${jobId}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async executeJob(cronJob) {
|
|
97
|
+
const execution = await CronExecution.create({
|
|
98
|
+
cronJobId: cronJob._id,
|
|
99
|
+
status: 'running',
|
|
100
|
+
startedAt: new Date(),
|
|
101
|
+
triggeredAt: new Date(),
|
|
102
|
+
actualRunAt: new Date(),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
console.log(`Executing cron job: ${cronJob.name} (execution: ${execution._id})`);
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
if (cronJob.taskType === 'script') {
|
|
109
|
+
await this.executeScriptJob(cronJob, execution);
|
|
110
|
+
} else if (cronJob.taskType === 'http') {
|
|
111
|
+
await this.executeHttpJob(cronJob, execution);
|
|
112
|
+
} else {
|
|
113
|
+
throw new Error(`Unknown task type: ${cronJob.taskType}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Update execution as succeeded
|
|
117
|
+
await CronExecution.updateOne(
|
|
118
|
+
{ _id: execution._id },
|
|
119
|
+
{
|
|
120
|
+
$set: {
|
|
121
|
+
status: 'succeeded',
|
|
122
|
+
finishedAt: new Date(),
|
|
123
|
+
},
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
} catch (err) {
|
|
127
|
+
// Update execution as failed
|
|
128
|
+
await CronExecution.updateOne(
|
|
129
|
+
{ _id: execution._id },
|
|
130
|
+
{
|
|
131
|
+
$set: {
|
|
132
|
+
status: 'failed',
|
|
133
|
+
finishedAt: new Date(),
|
|
134
|
+
error: err.message,
|
|
135
|
+
},
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
console.error(`Cron job failed: ${cronJob.name}`, err);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Update next run time for the job
|
|
142
|
+
if (cronJob.enabled) {
|
|
143
|
+
const nextRunAt = this.calculateNextRun(cronJob.cronExpression, cronJob.timezone);
|
|
144
|
+
await CronJob.updateOne(
|
|
145
|
+
{ _id: cronJob._id },
|
|
146
|
+
{ $set: { nextRunAt } }
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return execution;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async executeScriptJob(cronJob, execution) {
|
|
154
|
+
console.log(`[CronScheduler] Executing script job: ${cronJob.name} (scriptId: ${cronJob.scriptId})`);
|
|
155
|
+
|
|
156
|
+
// Get the script definition
|
|
157
|
+
const scriptDef = await ScriptDefinition.findById(cronJob.scriptId);
|
|
158
|
+
if (!scriptDef) {
|
|
159
|
+
throw new Error(`Script not found: ${cronJob.scriptId}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!scriptDef.enabled) {
|
|
163
|
+
throw new Error(`Script is disabled: ${scriptDef.name}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Merge environment variables
|
|
167
|
+
const env = [...scriptDef.env];
|
|
168
|
+
if (cronJob.scriptEnv && cronJob.scriptEnv.length > 0) {
|
|
169
|
+
// Override with cron-specific env vars
|
|
170
|
+
for (const cronEnv of cronJob.scriptEnv) {
|
|
171
|
+
const existingIndex = env.findIndex(e => e.key === cronEnv.key);
|
|
172
|
+
if (existingIndex >= 0) {
|
|
173
|
+
env[existingIndex] = cronEnv;
|
|
174
|
+
} else {
|
|
175
|
+
env.push(cronEnv);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Create a modified script definition for execution
|
|
181
|
+
const modifiedScript = {
|
|
182
|
+
...scriptDef.toObject(),
|
|
183
|
+
env,
|
|
184
|
+
timeoutMs: cronJob.timeoutMs || scriptDef.timeoutMs,
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
console.log(`[CronScheduler] Starting script execution for: ${scriptDef.name}`);
|
|
188
|
+
// Start the script execution
|
|
189
|
+
const run = await startRun(modifiedScript, {
|
|
190
|
+
trigger: 'schedule',
|
|
191
|
+
meta: { cronJobId: cronJob._id, cronExecutionId: execution._id },
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
console.log(`[CronScheduler] Script run created with ID: ${run._id}`);
|
|
195
|
+
// Wait for completion and capture output
|
|
196
|
+
const output = await this.waitForScriptCompletion(run._id);
|
|
197
|
+
|
|
198
|
+
console.log(`[CronScheduler] Script execution completed for: ${scriptDef.name}`);
|
|
199
|
+
// Update execution with output
|
|
200
|
+
await CronExecution.updateOne(
|
|
201
|
+
{ _id: execution._id },
|
|
202
|
+
{ $set: { output } }
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async executeHttpJob(cronJob, execution) {
|
|
207
|
+
const { httpMethod, httpUrl, httpHeaders, httpBody, httpBodyType, httpAuth } = cronJob;
|
|
208
|
+
|
|
209
|
+
// Prepare headers
|
|
210
|
+
const headers = {};
|
|
211
|
+
if (httpHeaders) {
|
|
212
|
+
for (const header of httpHeaders) {
|
|
213
|
+
headers[header.key] = header.value;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Add authentication
|
|
218
|
+
if (httpAuth && httpAuth.type === 'bearer' && httpAuth.token) {
|
|
219
|
+
headers['Authorization'] = `Bearer ${httpAuth.token}`;
|
|
220
|
+
} else if (httpAuth && httpAuth.type === 'basic' && httpAuth.username && httpAuth.password) {
|
|
221
|
+
const encoded = Buffer.from(`${httpAuth.username}:${httpAuth.password}`).toString('base64');
|
|
222
|
+
headers['Authorization'] = `Basic ${encoded}`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Prepare body
|
|
226
|
+
let body = null;
|
|
227
|
+
if (httpBody && ['POST', 'PUT', 'PATCH'].includes(httpMethod)) {
|
|
228
|
+
if (httpBodyType === 'json') {
|
|
229
|
+
headers['Content-Type'] = 'application/json';
|
|
230
|
+
body = httpBody;
|
|
231
|
+
} else if (httpBodyType === 'form') {
|
|
232
|
+
headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
|
233
|
+
body = httpBody;
|
|
234
|
+
} else {
|
|
235
|
+
headers['Content-Type'] = 'text/plain';
|
|
236
|
+
body = httpBody;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Make the HTTP request
|
|
241
|
+
const response = await fetch(httpUrl, {
|
|
242
|
+
method: httpMethod,
|
|
243
|
+
headers,
|
|
244
|
+
body,
|
|
245
|
+
timeout: cronJob.timeoutMs || 300000,
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// Get response text
|
|
249
|
+
const output = await response.text();
|
|
250
|
+
|
|
251
|
+
// Update execution with HTTP response details
|
|
252
|
+
await CronExecution.updateOne(
|
|
253
|
+
{ _id: execution._id },
|
|
254
|
+
{
|
|
255
|
+
$set: {
|
|
256
|
+
output,
|
|
257
|
+
httpStatusCode: response.status,
|
|
258
|
+
httpResponseHeaders: Object.fromEntries(response.headers.entries()),
|
|
259
|
+
},
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
// Throw error if response is not successful
|
|
264
|
+
if (!response.ok) {
|
|
265
|
+
throw new Error(`HTTP request failed: ${response.status} ${response.statusText}`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async waitForScriptCompletion(runId, timeout = 300000) {
|
|
270
|
+
console.log(`[CronScheduler] Waiting for script completion: ${runId}`);
|
|
271
|
+
return new Promise((resolve, reject) => {
|
|
272
|
+
let output = '';
|
|
273
|
+
const startTime = Date.now();
|
|
274
|
+
const timeoutId = setTimeout(() => {
|
|
275
|
+
console.log(`[CronScheduler] Script execution timeout: ${runId}`);
|
|
276
|
+
reject(new Error('Script execution timeout'));
|
|
277
|
+
}, timeout);
|
|
278
|
+
|
|
279
|
+
// Check for completion periodically
|
|
280
|
+
const checkInterval = setInterval(async () => {
|
|
281
|
+
try {
|
|
282
|
+
const run = await ScriptRun.findById(runId);
|
|
283
|
+
if (!run) {
|
|
284
|
+
console.log(`[CronScheduler] Script run not found: ${runId}`);
|
|
285
|
+
clearInterval(checkInterval);
|
|
286
|
+
clearTimeout(timeoutId);
|
|
287
|
+
reject(new Error('Script run not found'));
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Wait for the script to start running (it starts as 'queued')
|
|
292
|
+
if (run.status === 'queued') {
|
|
293
|
+
// Still waiting to start
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
console.log(`[CronScheduler] Script status: ${run.status} for run: ${runId}`);
|
|
298
|
+
|
|
299
|
+
if (run.status === 'succeeded') {
|
|
300
|
+
clearInterval(checkInterval);
|
|
301
|
+
clearTimeout(timeoutId);
|
|
302
|
+
resolve(run.outputTail || '');
|
|
303
|
+
} else if (run.status === 'failed' || run.status === 'timed_out') {
|
|
304
|
+
clearInterval(checkInterval);
|
|
305
|
+
clearTimeout(timeoutId);
|
|
306
|
+
const errorMsg = run.error || 'Script execution failed';
|
|
307
|
+
reject(new Error(errorMsg));
|
|
308
|
+
} else if (run.status === 'running') {
|
|
309
|
+
// Still running, continue waiting
|
|
310
|
+
return;
|
|
311
|
+
} else {
|
|
312
|
+
clearInterval(checkInterval);
|
|
313
|
+
clearTimeout(timeoutId);
|
|
314
|
+
reject(new Error(`Unexpected script status: ${run.status}`));
|
|
315
|
+
}
|
|
316
|
+
} catch (err) {
|
|
317
|
+
console.log(`[CronScheduler] Error checking script status: ${err.message}`);
|
|
318
|
+
clearInterval(checkInterval);
|
|
319
|
+
clearTimeout(timeoutId);
|
|
320
|
+
reject(err);
|
|
321
|
+
}
|
|
322
|
+
}, 1000);
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
calculateNextRun(cronExpression, timezone = 'UTC') {
|
|
327
|
+
try {
|
|
328
|
+
const interval = parser.parseExpression(cronExpression, {
|
|
329
|
+
tz: timezone,
|
|
330
|
+
});
|
|
331
|
+
return interval.next().toDate();
|
|
332
|
+
} catch (err) {
|
|
333
|
+
console.error('Failed to calculate next run:', err);
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
getScheduledJobs() {
|
|
339
|
+
return Array.from(this.scheduledJobs.keys());
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
isJobScheduled(jobId) {
|
|
343
|
+
return this.scheduledJobs.has(String(jobId));
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Create singleton instance
|
|
348
|
+
const cronScheduler = new CronScheduler();
|
|
349
|
+
|
|
350
|
+
module.exports = cronScheduler;
|