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/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
- console.log(`Worker internal server for job ${jobId} listening on: ${baseURL}`);
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: R.omit(['content-length'], 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
- console.log(`Worker internal request for job ${jobId} completed with code ${code}`);
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
- console.error(`Worker internal request error for job ${jobId}:`, error.message);
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
- console.log(`Worker internal server for job ${jobId} closed.`);
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
- return { success: true };
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
- console.log(`job ${jobId} > running ${pluginName}:${method} for ${userId}`);
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 id = R.path(['id'], await queue.add({
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
  };