ti2 1.0.94 → 1.0.95
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/.github/workflows/tests.yml +7 -3
- package/api.yml +159 -0
- package/cli.js +106 -0
- package/controllers/__tests__/admin.js +1 -1
- package/controllers/__tests__/allotments.js +1 -2
- package/controllers/__tests__/app.js +1 -1
- package/controllers/__tests__/bookings.js +13 -3
- package/controllers/__tests__/cronjobs.js +394 -0
- package/controllers/__tests__/ping.js +1 -1
- package/controllers/__tests__/productsSearch-background.js +1 -1
- package/controllers/__tests__/user.js +1 -1
- package/controllers/app.js +269 -6
- package/index.js +2 -3
- package/migrations/20240130000000-add-token-to-cronjobs.js +12 -0
- package/migrations/20250528_create_apiCronjobs.js +61 -0
- package/models/apiCronJobs.js +96 -0
- package/package.json +2 -2
- package/test/utils.js +37 -10
- package/worker/__tests__/job-callback.js +0 -22
- package/worker/index.js +41 -30
- package/worker/queue.js +69 -5
package/worker/index.js
CHANGED
|
@@ -24,14 +24,16 @@ const worker = ({ plugins: pluginsParam }) => (id, disconnect) => {
|
|
|
24
24
|
data: {
|
|
25
25
|
type = 'plugin',
|
|
26
26
|
pluginName,
|
|
27
|
-
method,
|
|
28
|
-
userId,
|
|
29
27
|
inTesting,
|
|
30
28
|
payload = {}
|
|
31
29
|
},
|
|
32
30
|
id: jobId,
|
|
33
31
|
data: params,
|
|
34
32
|
} = job;
|
|
33
|
+
let resultValue;
|
|
34
|
+
const jobLog = msg => console.log(`job ${jobId} > `, msg)
|
|
35
|
+
const jobErr = msg => console.error(`job ${jobId} > `, msg)
|
|
36
|
+
jobLog(`of type ${type} started`);
|
|
35
37
|
if (type === 'api') {
|
|
36
38
|
const {
|
|
37
39
|
method,
|
|
@@ -39,6 +41,7 @@ const worker = ({ plugins: pluginsParam }) => (id, disconnect) => {
|
|
|
39
41
|
payload,
|
|
40
42
|
headers,
|
|
41
43
|
} = params;
|
|
44
|
+
jobLog(`running method ${method} ${url}`)
|
|
42
45
|
|
|
43
46
|
let code, result, server;
|
|
44
47
|
const http = require('http');
|
|
@@ -55,23 +58,30 @@ const worker = ({ plugins: pluginsParam }) => (id, disconnect) => {
|
|
|
55
58
|
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
|
|
56
59
|
const { address, port } = server.address();
|
|
57
60
|
const baseURL = `http://${address}:${port}`;
|
|
58
|
-
|
|
61
|
+
jobLog(`Worker internal server for job ${jobId} listening on: ${baseURL}`);
|
|
62
|
+
|
|
63
|
+
// Extract token and body from payload
|
|
64
|
+
const { token, body } = payload || {};
|
|
65
|
+
const requestHeaders = {
|
|
66
|
+
...R.omit(['content-length'], headers),
|
|
67
|
+
...(token ? { Authorization: `Bearer ${token}` } : {})
|
|
68
|
+
};
|
|
59
69
|
|
|
60
70
|
// Use axios to make the request
|
|
61
71
|
const response = await axios({
|
|
62
72
|
method: method.toLowerCase(),
|
|
63
73
|
url: `${baseURL}${url}`,
|
|
64
74
|
data: R.omit(['backgroundJob'], payload),
|
|
65
|
-
headers:
|
|
75
|
+
headers: requestHeaders,
|
|
66
76
|
validateStatus: () => true, // Prevent axios from throwing on non-2xx status
|
|
67
77
|
});
|
|
68
78
|
|
|
69
79
|
code = response.status;
|
|
70
80
|
result = response.data;
|
|
71
|
-
|
|
81
|
+
jobLog(`Worker internal request for job ${jobId} completed with code ${code}`);
|
|
72
82
|
} catch (error) {
|
|
73
83
|
// Handle errors during app init or axios request
|
|
74
|
-
|
|
84
|
+
jobErr(`Worker internal request error for job ${jobId}:`, error.message);
|
|
75
85
|
// If axios error has response, use its status
|
|
76
86
|
code = R.pathOr(500, ['response', 'status'], error);
|
|
77
87
|
result = R.pathOr({ message: error.message }, ['response', 'data'], error);
|
|
@@ -79,47 +89,48 @@ const worker = ({ plugins: pluginsParam }) => (id, disconnect) => {
|
|
|
79
89
|
// Ensure server is closed if it was created
|
|
80
90
|
if (server) {
|
|
81
91
|
await new Promise(resolve => server.close(resolve));
|
|
82
|
-
|
|
92
|
+
jobLog(`Worker internal server for job ${jobId} closed.`);
|
|
83
93
|
}
|
|
84
94
|
}
|
|
85
|
-
|
|
86
|
-
return {
|
|
95
|
+
resultValue = {
|
|
87
96
|
code,
|
|
88
97
|
result,
|
|
89
98
|
success: code >= 200 && code < 300,
|
|
90
99
|
};
|
|
91
|
-
}
|
|
92
|
-
if (type === 'callback') {
|
|
100
|
+
} else if (type === 'callback') {
|
|
93
101
|
const {
|
|
94
102
|
callbackUrl,
|
|
95
103
|
request,
|
|
96
104
|
result,
|
|
97
105
|
} = payload;
|
|
98
|
-
|
|
99
|
-
console.log(`job ${jobId} > sending callback to ${callbackUrl}`);
|
|
106
|
+
jobLog(`job ${jobId} > sending callback to ${callbackUrl}`);
|
|
100
107
|
await require('../lib/callback').sendCallback({
|
|
101
108
|
callbackUrl,
|
|
102
109
|
request,
|
|
103
110
|
result,
|
|
104
111
|
});
|
|
105
|
-
|
|
112
|
+
resultValue = { success: true };
|
|
113
|
+
} else if (type === 'plugin') {
|
|
114
|
+
const {
|
|
115
|
+
method,
|
|
116
|
+
payload,
|
|
117
|
+
} = params;
|
|
118
|
+
const plugins = await (async () => {
|
|
119
|
+
if (inTesting) {
|
|
120
|
+
const fakePlugin = require('../test/plugin');
|
|
121
|
+
return [...pluginsParam, await new fakePlugin({ name: pluginName })];
|
|
122
|
+
}
|
|
123
|
+
return pluginsParam;
|
|
124
|
+
})();
|
|
125
|
+
const thePlugin = plugins.find(({ name }) => name === pluginName);
|
|
126
|
+
resultValue = await thePlugin[method]({
|
|
127
|
+
...payload,
|
|
128
|
+
token: params.token,
|
|
129
|
+
plugins,
|
|
130
|
+
typeDefsAndQueries: require('../controllers/bookings').typeDefsAndQueries,
|
|
131
|
+
});
|
|
106
132
|
}
|
|
107
|
-
|
|
108
|
-
const plugins = await (async () => {
|
|
109
|
-
if (inTesting) {
|
|
110
|
-
const fakePlugin = require('../test/plugin');
|
|
111
|
-
return [...pluginsParam, await new fakePlugin({ name: pluginName })];
|
|
112
|
-
}
|
|
113
|
-
return pluginsParam;
|
|
114
|
-
})();
|
|
115
|
-
const thePlugin = plugins.find(({ name }) => name === pluginName);
|
|
116
|
-
const resultValue = await thePlugin[method]({
|
|
117
|
-
...params.payload,
|
|
118
|
-
token: params.token,
|
|
119
|
-
plugins,
|
|
120
|
-
typeDefsAndQueries: require('../controllers/bookings').typeDefsAndQueries,
|
|
121
|
-
});
|
|
122
|
-
console.log(`job ${jobId} > completed`);
|
|
133
|
+
jobLog(`job ${jobId} > completed`);
|
|
123
134
|
await saveResult({ id: jobId, resultValue });
|
|
124
135
|
});
|
|
125
136
|
};
|
package/worker/queue.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
const Queue = require('bull');
|
|
2
|
-
const Redis = require('ioredis');
|
|
3
2
|
const R = require('ramda');
|
|
4
|
-
|
|
3
|
+
const Redis = require('ioredis');
|
|
4
|
+
const sqldb = require('../models');
|
|
5
|
+
const { env: { redisHost, redisPort } } = process;
|
|
5
6
|
const REDIS_URL = process.env.REDIS_URL || 'redis://redis:6379';
|
|
6
7
|
const itemsTTL = 3 * 60 * 60; // 3 hours
|
|
7
8
|
|
|
@@ -32,13 +33,15 @@ queue.on('failed', (job, err) => {
|
|
|
32
33
|
const addJob = async (payload, paramsParam) => {
|
|
33
34
|
const params = paramsParam || {};
|
|
34
35
|
const inTesting = Boolean(process.env.JEST_WORKER_ID);
|
|
35
|
-
const
|
|
36
|
+
const job = await queue.add({
|
|
36
37
|
...payload,
|
|
37
38
|
inTesting,
|
|
38
39
|
}, {
|
|
39
40
|
removeOnComplete: true,
|
|
40
41
|
...params,
|
|
41
|
-
})
|
|
42
|
+
});
|
|
43
|
+
// For repeat jobs, we need to store the full repeat key
|
|
44
|
+
const id = job.opts.repeat ? job.opts.jobId : job.id;
|
|
42
45
|
return id;
|
|
43
46
|
};
|
|
44
47
|
|
|
@@ -46,6 +49,67 @@ const saveResult = async ({ id, resultValue }) => {
|
|
|
46
49
|
await redisResults.set(id, JSON.stringify(resultValue), 'EX', itemsTTL);
|
|
47
50
|
};
|
|
48
51
|
|
|
52
|
+
const removeJob = async (jobId) => {
|
|
53
|
+
// For repeat jobs, the ID is in the format 'repeat:jobId:timestamp'
|
|
54
|
+
const jobIdParts = jobId.split(':');
|
|
55
|
+
const isRepeatJob = jobIdParts[0] === 'repeat';
|
|
56
|
+
|
|
57
|
+
if (isRepeatJob) {
|
|
58
|
+
// Get all repeatable jobs
|
|
59
|
+
const repeatableJobs = await queue.getRepeatableJobs();
|
|
60
|
+
|
|
61
|
+
// Find the job with matching cron pattern
|
|
62
|
+
const cronJob = await sqldb.CronJobs.findOne({
|
|
63
|
+
where: {
|
|
64
|
+
bullJobId: jobId,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (cronJob) {
|
|
69
|
+
// First remove all repeatable jobs with matching cron pattern
|
|
70
|
+
for (const repeatableJob of repeatableJobs) {
|
|
71
|
+
if (repeatableJob.cron === cronJob.cron) {
|
|
72
|
+
await queue.removeRepeatableByKey(repeatableJob.key);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Then remove all jobs with this pattern
|
|
77
|
+
const jobs = await queue.getJobs(['active', 'wait', 'delayed']);
|
|
78
|
+
for (const job of jobs) {
|
|
79
|
+
if (job.opts.repeat && job.opts.jobId === jobId) {
|
|
80
|
+
await job.remove();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Wait for the queue to process the removals
|
|
85
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
86
|
+
|
|
87
|
+
// Clean up any remaining jobs
|
|
88
|
+
const remainingJobs = await queue.getJobs(['active', 'wait', 'delayed']);
|
|
89
|
+
for (const job of remainingJobs) {
|
|
90
|
+
if (job.opts.repeat && job.opts.jobId === jobId) {
|
|
91
|
+
await job.remove();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
await redisResults.del(jobId);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// If we couldn't find the repeatable job, just delete from the database
|
|
100
|
+
// This can happen if the job was already removed from Bull but still exists in our database
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// If not a repeatable job, try to remove as a regular job
|
|
105
|
+
const job = await queue.getJob(jobId);
|
|
106
|
+
if (!job) {
|
|
107
|
+
throw new Error('Job not found');
|
|
108
|
+
}
|
|
109
|
+
await job.remove();
|
|
110
|
+
await redisResults.del(jobId);
|
|
111
|
+
};
|
|
112
|
+
|
|
49
113
|
const jobStatus = async ({ jobId }) => {
|
|
50
114
|
try {
|
|
51
115
|
const job = await queue.getJob(jobId);
|
|
@@ -71,7 +135,6 @@ const jobStatus = async ({ jobId }) => {
|
|
|
71
135
|
status: state.toLowerCase(),
|
|
72
136
|
};
|
|
73
137
|
} catch (err) {
|
|
74
|
-
console.log(`could not get job status for ${jobId}`, err);
|
|
75
138
|
return { // 500
|
|
76
139
|
jobId,
|
|
77
140
|
status: 'failed',
|
|
@@ -85,4 +148,5 @@ module.exports = {
|
|
|
85
148
|
queue,
|
|
86
149
|
saveResult,
|
|
87
150
|
redisResults,
|
|
151
|
+
removeJob,
|
|
88
152
|
};
|