figranium 0.12.0 → 0.12.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.
@@ -1,171 +1,171 @@
1
- const express = require('express');
2
- const { requireAuth } = require('../middleware');
3
- const { loadTasks, saveTasks, getTaskById } = require('../storage');
4
- const { taskMutex } = require('../state');
5
- const { refreshSchedule, removeSchedule, getSchedulerStatus, resolveCron } = require('../scheduler');
6
- const { isValidCron, describeCron, getNextRun } = require('../cron-parser');
7
-
8
- const router = express.Router();
9
-
10
- /**
11
- * GET /api/schedules
12
- * List all tasks that have schedules (enabled or not), with status info.
13
- */
14
- router.get('/', requireAuth, async (req, res) => {
15
- const tasks = await loadTasks();
16
- const schedules = tasks
17
- .filter(t => t.schedule)
18
- .map(t => ({
19
- taskId: t.id,
20
- taskName: t.name,
21
- mode: t.mode,
22
- schedule: t.schedule
23
- }));
24
- res.json({ schedules });
25
- });
26
-
27
- /**
28
- * POST /api/schedules/:taskId
29
- * Create or update a schedule on a task.
30
- * Body: { enabled, frequency?, intervalMinutes?, hour?, minute?, daysOfWeek?, dayOfMonth?, cron? }
31
- */
32
- router.post('/:taskId', requireAuth, async (req, res) => {
33
- await taskMutex.lock();
34
- try {
35
- const tasks = await loadTasks();
36
- const task = getTaskById(req.params.taskId);
37
- if (!task) return res.status(404).json({ error: 'TASK_NOT_FOUND' });
38
-
39
- const body = req.body || {};
40
- const schedule = {
41
- ...(task.schedule || {}),
42
- ...body,
43
- };
44
-
45
- // If body explicitly provides one mode, clear the other to avoid mode-switching bugs
46
- if (body.cron && !body.frequency) {
47
- delete schedule.frequency;
48
- delete schedule.intervalMinutes;
49
- delete schedule.hour;
50
- delete schedule.minute;
51
- delete schedule.daysOfWeek;
52
- delete schedule.dayOfMonth;
53
- } else if (body.frequency && !body.cron) {
54
- delete schedule.cron;
55
- }
56
-
57
- // Handle explicit nulls (JSON doesn't support undefined, so null is common)
58
- if (body.cron === null) delete schedule.cron;
59
- if (body.frequency === null) delete schedule.frequency;
60
-
61
- // Validate the resulting cron
62
- const cron = resolveCron(schedule);
63
- if (schedule.enabled && !cron) {
64
- return res.status(400).json({ error: 'INVALID_SCHEDULE', message: 'Cannot resolve a valid cron expression from the provided schedule config.' });
65
- }
66
-
67
- // Compute metadata
68
- if (cron) {
69
- schedule.cron = cron;
70
- try {
71
- const nextRun = getNextRun(cron);
72
- schedule.nextRun = nextRun.getTime();
73
- } catch {
74
- schedule.nextRun = null;
75
- }
76
- }
77
-
78
- task.schedule = schedule;
79
- await saveTasks(tasks);
80
-
81
- // Notify scheduler
82
- await refreshSchedule(task.id);
83
-
84
- res.json({
85
- schedule: task.schedule,
86
- description: cron ? describeCron(cron) : null,
87
- nextRun: cron ? getNextRun(cron).getTime() : null
88
- });
89
- } finally {
90
- taskMutex.unlock();
91
- }
92
- });
93
-
94
- /**
95
- * DELETE /api/schedules/:taskId
96
- * Remove/disable schedule from a task.
97
- */
98
- router.delete('/:taskId', requireAuth, async (req, res) => {
99
- await taskMutex.lock();
100
- try {
101
- const tasks = await loadTasks();
102
- const task = getTaskById(req.params.taskId);
103
- if (!task) return res.status(404).json({ error: 'TASK_NOT_FOUND' });
104
-
105
- if (task.schedule) {
106
- task.schedule.enabled = false;
107
- }
108
-
109
- await saveTasks(tasks);
110
- removeSchedule(task.id);
111
-
112
- res.json({ success: true });
113
- } finally {
114
- taskMutex.unlock();
115
- }
116
- });
117
-
118
- /**
119
- * GET /api/schedules/:taskId/status
120
- * Get schedule status for a specific task.
121
- */
122
- router.get('/:taskId/status', requireAuth, async (req, res) => {
123
- await loadTasks();
124
- const task = getTaskById(req.params.taskId);
125
- if (!task) return res.status(404).json({ error: 'TASK_NOT_FOUND' });
126
-
127
- const schedule = task.schedule || {};
128
- const cron = resolveCron(schedule);
129
-
130
- res.json({
131
- schedule,
132
- cron,
133
- description: cron ? describeCron(cron) : null,
134
- isValid: cron ? isValidCron(cron) : false
135
- });
136
- });
137
-
138
- /**
139
- * POST /api/schedules/:taskId/describe
140
- * Validate and describe a schedule config without saving it.
141
- */
142
- router.post('/:taskId/describe', requireAuth, async (req, res) => {
143
- const body = req.body || {};
144
- const cron = resolveCron(body);
145
-
146
- if (!cron) {
147
- return res.json({ valid: false, description: null, cron: null, nextRun: null });
148
- }
149
-
150
- let nextRun = null;
151
- try {
152
- nextRun = getNextRun(cron).getTime();
153
- } catch { }
154
-
155
- res.json({
156
- valid: true,
157
- cron,
158
- description: describeCron(cron),
159
- nextRun
160
- });
161
- });
162
-
163
- /**
164
- * GET /api/schedules/status/all
165
- * Get overall scheduler status.
166
- */
167
- router.get('/status/all', requireAuth, async (_req, res) => {
168
- res.json(getSchedulerStatus());
169
- });
170
-
171
- module.exports = router;
1
+ const express = require('express');
2
+ const { requireAuth } = require('../middleware');
3
+ const { loadTasks, saveTasks, getTaskById } = require('../storage');
4
+ const { taskMutex } = require('../state');
5
+ const { refreshSchedule, removeSchedule, getSchedulerStatus, resolveCron } = require('../scheduler');
6
+ const { isValidCron, describeCron, getNextRun } = require('../cron-parser');
7
+
8
+ const router = express.Router();
9
+
10
+ /**
11
+ * GET /api/schedules
12
+ * List all tasks that have schedules (enabled or not), with status info.
13
+ */
14
+ router.get('/', requireAuth, async (req, res) => {
15
+ const tasks = await loadTasks();
16
+ const schedules = tasks
17
+ .filter(t => t.schedule)
18
+ .map(t => ({
19
+ taskId: t.id,
20
+ taskName: t.name,
21
+ mode: t.mode,
22
+ schedule: t.schedule
23
+ }));
24
+ res.json({ schedules });
25
+ });
26
+
27
+ /**
28
+ * POST /api/schedules/:taskId
29
+ * Create or update a schedule on a task.
30
+ * Body: { enabled, frequency?, intervalMinutes?, hour?, minute?, daysOfWeek?, dayOfMonth?, cron? }
31
+ */
32
+ router.post('/:taskId', requireAuth, async (req, res) => {
33
+ await taskMutex.lock();
34
+ try {
35
+ const tasks = await loadTasks();
36
+ const task = getTaskById(req.params.taskId);
37
+ if (!task) return res.status(404).json({ error: 'TASK_NOT_FOUND' });
38
+
39
+ const body = req.body || {};
40
+ const schedule = {
41
+ ...(task.schedule || {}),
42
+ ...body,
43
+ };
44
+
45
+ // If body explicitly provides one mode, clear the other to avoid mode-switching bugs
46
+ if (body.cron && !body.frequency) {
47
+ delete schedule.frequency;
48
+ delete schedule.intervalMinutes;
49
+ delete schedule.hour;
50
+ delete schedule.minute;
51
+ delete schedule.daysOfWeek;
52
+ delete schedule.dayOfMonth;
53
+ } else if (body.frequency && !body.cron) {
54
+ delete schedule.cron;
55
+ }
56
+
57
+ // Handle explicit nulls (JSON doesn't support undefined, so null is common)
58
+ if (body.cron === null) delete schedule.cron;
59
+ if (body.frequency === null) delete schedule.frequency;
60
+
61
+ // Validate the resulting cron
62
+ const cron = resolveCron(schedule);
63
+ if (schedule.enabled && !cron) {
64
+ return res.status(400).json({ error: 'INVALID_SCHEDULE', message: 'Cannot resolve a valid cron expression from the provided schedule config.' });
65
+ }
66
+
67
+ // Compute metadata
68
+ if (cron) {
69
+ schedule.cron = cron;
70
+ try {
71
+ const nextRun = getNextRun(cron);
72
+ schedule.nextRun = nextRun.getTime();
73
+ } catch {
74
+ schedule.nextRun = null;
75
+ }
76
+ }
77
+
78
+ task.schedule = schedule;
79
+ await saveTasks(tasks);
80
+
81
+ // Notify scheduler
82
+ await refreshSchedule(task.id);
83
+
84
+ res.json({
85
+ schedule: task.schedule,
86
+ description: cron ? describeCron(cron) : null,
87
+ nextRun: cron ? getNextRun(cron).getTime() : null
88
+ });
89
+ } finally {
90
+ taskMutex.unlock();
91
+ }
92
+ });
93
+
94
+ /**
95
+ * DELETE /api/schedules/:taskId
96
+ * Remove/disable schedule from a task.
97
+ */
98
+ router.delete('/:taskId', requireAuth, async (req, res) => {
99
+ await taskMutex.lock();
100
+ try {
101
+ const tasks = await loadTasks();
102
+ const task = getTaskById(req.params.taskId);
103
+ if (!task) return res.status(404).json({ error: 'TASK_NOT_FOUND' });
104
+
105
+ if (task.schedule) {
106
+ task.schedule.enabled = false;
107
+ }
108
+
109
+ await saveTasks(tasks);
110
+ removeSchedule(task.id);
111
+
112
+ res.json({ success: true });
113
+ } finally {
114
+ taskMutex.unlock();
115
+ }
116
+ });
117
+
118
+ /**
119
+ * GET /api/schedules/:taskId/status
120
+ * Get schedule status for a specific task.
121
+ */
122
+ router.get('/:taskId/status', requireAuth, async (req, res) => {
123
+ await loadTasks();
124
+ const task = getTaskById(req.params.taskId);
125
+ if (!task) return res.status(404).json({ error: 'TASK_NOT_FOUND' });
126
+
127
+ const schedule = task.schedule || {};
128
+ const cron = resolveCron(schedule);
129
+
130
+ res.json({
131
+ schedule,
132
+ cron,
133
+ description: cron ? describeCron(cron) : null,
134
+ isValid: cron ? isValidCron(cron) : false
135
+ });
136
+ });
137
+
138
+ /**
139
+ * POST /api/schedules/:taskId/describe
140
+ * Validate and describe a schedule config without saving it.
141
+ */
142
+ router.post('/:taskId/describe', requireAuth, async (req, res) => {
143
+ const body = req.body || {};
144
+ const cron = resolveCron(body);
145
+
146
+ if (!cron) {
147
+ return res.json({ valid: false, description: null, cron: null, nextRun: null });
148
+ }
149
+
150
+ let nextRun = null;
151
+ try {
152
+ nextRun = getNextRun(cron).getTime();
153
+ } catch { }
154
+
155
+ res.json({
156
+ valid: true,
157
+ cron,
158
+ description: describeCron(cron),
159
+ nextRun
160
+ });
161
+ });
162
+
163
+ /**
164
+ * GET /api/schedules/status/all
165
+ * Get overall scheduler status.
166
+ */
167
+ router.get('/status/all', requireAuth, async (_req, res) => {
168
+ res.json(getSchedulerStatus());
169
+ });
170
+
171
+ module.exports = router;