@yellowpanther/shared 1.2.5 → 1.2.7
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/package.json +3 -2
- package/src/queue/articlePublishQueue.js +50 -23
- package/src/queue/imageAlbumPublishQueue.js +49 -23
- package/src/queue/imagePublishQueue.js +46 -23
- package/src/queue/newsPublishQueue.js +46 -23
- package/src/queue/pagePublishQueue.js +46 -23
- package/src/queue/predictionPublishQueue.js +50 -23
- package/src/queue/productPublishQueue.js +50 -23
- package/src/queue/quizPublishQueue.js +46 -23
- package/src/queue/videoPublishQueue.js +46 -23
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
// One-off *publish* scheduler for video items (BullMQ).
|
|
3
3
|
// Strong upsert: purge any prior job for the same video before adding a new delayed job.
|
|
4
4
|
|
|
5
|
-
const { Queue } = require(
|
|
6
|
-
const redisClient = require(
|
|
5
|
+
const { Queue } = require("bullmq");
|
|
6
|
+
const redisClient = require("../redis/redisClient");
|
|
7
7
|
// const logger = require('../logger'); // uncomment if you have a shared logger
|
|
8
8
|
|
|
9
|
-
const DEBUG
|
|
9
|
+
const DEBUG = String(process.env.DEBUG_LOGGER || "").trim() === "1";
|
|
10
10
|
const DRIFT_MS = Math.max(0, Number(process.env.SCHEDULE_DRIFT_MS || 250));
|
|
11
11
|
|
|
12
|
-
const videoPublishQueue = new Queue(
|
|
12
|
+
const videoPublishQueue = new Queue("videoPublishQueue", {
|
|
13
13
|
connection: redisClient,
|
|
14
14
|
defaultJobOptions: {
|
|
15
15
|
attempts: 5,
|
|
16
|
-
backoff: { type:
|
|
16
|
+
backoff: { type: "exponential", delay: 2000 },
|
|
17
17
|
removeOnComplete: 500,
|
|
18
18
|
removeOnFail: 500,
|
|
19
19
|
},
|
|
@@ -21,11 +21,19 @@ const videoPublishQueue = new Queue('videoPublishQueue', {
|
|
|
21
21
|
|
|
22
22
|
// ── ID helpers ─────────────────────────────────────────────────────────────────
|
|
23
23
|
|
|
24
|
+
/** Replace invalid chars for BullMQ IDs */
|
|
25
|
+
function sanitizeId(str) {
|
|
26
|
+
return str.replace(/[:.]/g, "-");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Stable id (latest schedule) */
|
|
24
30
|
function stableJobId(video_id) {
|
|
25
|
-
return `video:publish:${video_id}
|
|
31
|
+
return sanitizeId(`video:publish:${video_id}`);
|
|
26
32
|
}
|
|
33
|
+
|
|
34
|
+
/** Versioned id (keeps history distinct) */
|
|
27
35
|
function versionedJobId(video_id, runAtIso) {
|
|
28
|
-
return `video:publish:${video_id}:${runAtIso}
|
|
36
|
+
return sanitizeId(`video:publish:${video_id}:${runAtIso}`);
|
|
29
37
|
}
|
|
30
38
|
|
|
31
39
|
// ── Time helpers ───────────────────────────────────────────────────────────────
|
|
@@ -33,19 +41,21 @@ function versionedJobId(video_id, runAtIso) {
|
|
|
33
41
|
/** Parse anything into a UTC Date. Strings without timezone are treated as UTC. */
|
|
34
42
|
function normalizeToUtcDate(input) {
|
|
35
43
|
if (input instanceof Date) return new Date(input.getTime());
|
|
36
|
-
if (typeof input ===
|
|
44
|
+
if (typeof input === "number") return new Date(input); // epoch ms
|
|
37
45
|
|
|
38
|
-
if (typeof input ===
|
|
46
|
+
if (typeof input === "string") {
|
|
39
47
|
// Has timezone (Z or ±hh:mm)
|
|
40
48
|
if (/[zZ]|[+\-]\d{2}:\d{2}$/.test(input)) {
|
|
41
49
|
const d = new Date(input);
|
|
42
|
-
if (Number.isNaN(d.getTime()))
|
|
50
|
+
if (Number.isNaN(d.getTime()))
|
|
51
|
+
throw new Error(`Invalid ISO datetime: ${input}`);
|
|
43
52
|
return d;
|
|
44
53
|
}
|
|
45
54
|
// No timezone => treat as UTC
|
|
46
|
-
const s = input.trim().replace(
|
|
55
|
+
const s = input.trim().replace(" ", "T");
|
|
47
56
|
const d = new Date(`${s}Z`);
|
|
48
|
-
if (Number.isNaN(d.getTime()))
|
|
57
|
+
if (Number.isNaN(d.getTime()))
|
|
58
|
+
throw new Error(`Invalid datetime (no tz): ${input}`);
|
|
49
59
|
return d;
|
|
50
60
|
}
|
|
51
61
|
|
|
@@ -55,10 +65,10 @@ function normalizeToUtcDate(input) {
|
|
|
55
65
|
// ── Purge helpers ─────────────────────────────────────────────────────────────
|
|
56
66
|
|
|
57
67
|
async function findPendingVideoJobs(video_id) {
|
|
58
|
-
const states = [
|
|
68
|
+
const states = ["delayed", "waiting", "waiting-children", "active"];
|
|
59
69
|
const jobs = await videoPublishQueue.getJobs(states);
|
|
60
70
|
return jobs.filter(
|
|
61
|
-
(j) => j?.name ===
|
|
71
|
+
(j) => j?.name === "video:publish" && j?.data?.video_id === String(video_id)
|
|
62
72
|
);
|
|
63
73
|
}
|
|
64
74
|
|
|
@@ -72,7 +82,10 @@ async function purgeExistingForVideo(video_id) {
|
|
|
72
82
|
await j.remove();
|
|
73
83
|
removed++;
|
|
74
84
|
} catch (e) {
|
|
75
|
-
if (DEBUG)
|
|
85
|
+
if (DEBUG)
|
|
86
|
+
console.warn(
|
|
87
|
+
`[videoPublishQueue] failed to remove pending job ${j.id}: ${e.message}`
|
|
88
|
+
);
|
|
76
89
|
}
|
|
77
90
|
}
|
|
78
91
|
|
|
@@ -83,12 +96,15 @@ async function purgeExistingForVideo(video_id) {
|
|
|
83
96
|
await last.remove();
|
|
84
97
|
removed++;
|
|
85
98
|
} catch (e) {
|
|
86
|
-
if (DEBUG)
|
|
99
|
+
if (DEBUG)
|
|
100
|
+
console.warn(
|
|
101
|
+
`[videoPublishQueue] failed to remove stable job ${last.id}: ${e.message}`
|
|
102
|
+
);
|
|
87
103
|
}
|
|
88
104
|
}
|
|
89
105
|
|
|
90
106
|
if (DEBUG) {
|
|
91
|
-
console.info(
|
|
107
|
+
console.info("[videoPublishQueue] purgeExistingForVideo", {
|
|
92
108
|
video_id: String(video_id),
|
|
93
109
|
removed,
|
|
94
110
|
});
|
|
@@ -109,9 +125,14 @@ async function purgeExistingForVideo(video_id) {
|
|
|
109
125
|
* @param {object} [params.extra]
|
|
110
126
|
* @param {boolean} [params.useStableId=false] - if true, uses stable jobId
|
|
111
127
|
*/
|
|
112
|
-
async function addVideoPublishJob({
|
|
128
|
+
async function addVideoPublishJob({
|
|
129
|
+
video_id,
|
|
130
|
+
runAtUtc,
|
|
131
|
+
extra = {},
|
|
132
|
+
useStableId = false,
|
|
133
|
+
}) {
|
|
113
134
|
if (!video_id || !runAtUtc) {
|
|
114
|
-
throw new Error(
|
|
135
|
+
throw new Error("addVideoPublishJob: video_id and runAtUtc are required");
|
|
115
136
|
}
|
|
116
137
|
|
|
117
138
|
const runAt = normalizeToUtcDate(runAtUtc);
|
|
@@ -126,16 +147,18 @@ async function addVideoPublishJob({ video_id, runAtUtc, extra = {}, useStableId
|
|
|
126
147
|
await purgeExistingForVideo(video_id);
|
|
127
148
|
|
|
128
149
|
// Choose jobId strategy
|
|
129
|
-
const jobId = useStableId
|
|
150
|
+
const jobId = useStableId
|
|
151
|
+
? stableJobId(video_id)
|
|
152
|
+
: versionedJobId(video_id, runAtIso);
|
|
130
153
|
|
|
131
154
|
const job = await videoPublishQueue.add(
|
|
132
|
-
|
|
155
|
+
"video:publish",
|
|
133
156
|
{ video_id: String(video_id), runAtUtc: runAtIso, extra },
|
|
134
157
|
{ jobId, delay: delayMs }
|
|
135
158
|
);
|
|
136
159
|
|
|
137
160
|
if (DEBUG) {
|
|
138
|
-
console.info(
|
|
161
|
+
console.info("[videoPublishQueue] upsert", {
|
|
139
162
|
video_id: String(video_id),
|
|
140
163
|
jobId,
|
|
141
164
|
runAtIso,
|
|
@@ -161,4 +184,4 @@ module.exports = {
|
|
|
161
184
|
stableJobId,
|
|
162
185
|
versionedJobId,
|
|
163
186
|
normalizeToUtcDate,
|
|
164
|
-
};
|
|
187
|
+
};
|