@infuro/cms-core 1.0.28 → 1.0.30
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/dist/admin.cjs +2571 -1418
- package/dist/admin.cjs.map +1 -1
- package/dist/admin.d.cts +10 -1
- package/dist/admin.d.ts +10 -1
- package/dist/admin.js +2549 -1386
- package/dist/admin.js.map +1 -1
- package/dist/api.cjs +735 -31
- package/dist/api.cjs.map +1 -1
- package/dist/api.d.cts +1 -1
- package/dist/api.d.ts +1 -1
- package/dist/api.js +739 -22
- package/dist/api.js.map +1 -1
- package/dist/cli.cjs +7 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +7 -1
- package/dist/cli.js.map +1 -1
- package/dist/{index-CI6J9dxr.d.cts → index-BPQSXgXF.d.cts} +20 -1
- package/dist/{index-CMJZ5Fpr.d.ts → index-D9SdaDJ0.d.ts} +20 -1
- package/dist/index.cjs +2356 -1375
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +107 -16
- package/dist/index.d.ts +107 -16
- package/dist/index.js +2347 -1364
- package/dist/index.js.map +1 -1
- package/dist/migrations/1775700000000-JobSchedules.ts +70 -0
- package/package.json +11 -2
package/dist/api.js
CHANGED
|
@@ -192,6 +192,81 @@ var init_email_queue = __esm({
|
|
|
192
192
|
}
|
|
193
193
|
});
|
|
194
194
|
|
|
195
|
+
// src/plugins/pg-boss/pg-boss-service.ts
|
|
196
|
+
var pg_boss_service_exports = {};
|
|
197
|
+
__export(pg_boss_service_exports, {
|
|
198
|
+
JOB_RUNNER_QUEUE: () => JOB_RUNNER_QUEUE,
|
|
199
|
+
PgBossService: () => PgBossService
|
|
200
|
+
});
|
|
201
|
+
import PgBoss from "pg-boss";
|
|
202
|
+
var JOB_RUNNER_QUEUE, PgBossService;
|
|
203
|
+
var init_pg_boss_service = __esm({
|
|
204
|
+
"src/plugins/pg-boss/pg-boss-service.ts"() {
|
|
205
|
+
"use strict";
|
|
206
|
+
JOB_RUNNER_QUEUE = "job-runner";
|
|
207
|
+
PgBossService = class {
|
|
208
|
+
boss;
|
|
209
|
+
started = false;
|
|
210
|
+
workRegistered = /* @__PURE__ */ new Set();
|
|
211
|
+
constructor(connectionString, schema) {
|
|
212
|
+
this.boss = new PgBoss({
|
|
213
|
+
connectionString,
|
|
214
|
+
...schema?.trim() ? { schema: schema.trim() } : {},
|
|
215
|
+
schedule: true
|
|
216
|
+
});
|
|
217
|
+
this.boss.on("error", (err) => {
|
|
218
|
+
console.error("[pg-boss]", err);
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
async start() {
|
|
222
|
+
if (this.started) return;
|
|
223
|
+
await this.boss.start();
|
|
224
|
+
await this.ensureQueue(JOB_RUNNER_QUEUE);
|
|
225
|
+
this.started = true;
|
|
226
|
+
}
|
|
227
|
+
async stop() {
|
|
228
|
+
if (!this.started) return;
|
|
229
|
+
await this.boss.stop({ graceful: true, timeout: 1e4 });
|
|
230
|
+
this.started = false;
|
|
231
|
+
this.workRegistered.clear();
|
|
232
|
+
}
|
|
233
|
+
get raw() {
|
|
234
|
+
return this.boss;
|
|
235
|
+
}
|
|
236
|
+
async ensureQueue(name) {
|
|
237
|
+
const existing = await this.boss.getQueue(name);
|
|
238
|
+
if (!existing) {
|
|
239
|
+
await this.boss.createQueue(name);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
async send(queue, data) {
|
|
243
|
+
await this.ensureQueue(queue);
|
|
244
|
+
return this.boss.send(queue, data, { retryLimit: 2 });
|
|
245
|
+
}
|
|
246
|
+
async schedule(name, cron, data, options) {
|
|
247
|
+
await this.ensureQueue(name);
|
|
248
|
+
await this.boss.schedule(name, cron, data, options);
|
|
249
|
+
}
|
|
250
|
+
async unschedule(name) {
|
|
251
|
+
try {
|
|
252
|
+
await this.boss.unschedule(name);
|
|
253
|
+
} catch {
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
async registerWork(queue, handler) {
|
|
257
|
+
if (this.workRegistered.has(queue)) return;
|
|
258
|
+
await this.ensureQueue(queue);
|
|
259
|
+
await this.boss.work(queue, async (jobs) => {
|
|
260
|
+
for (const job of jobs) {
|
|
261
|
+
await handler(job.data);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
this.workRegistered.add(queue);
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
195
270
|
// src/plugins/erp/erp-response-map.ts
|
|
196
271
|
function pickString(o, keys) {
|
|
197
272
|
for (const k of keys) {
|
|
@@ -7324,6 +7399,159 @@ RssFeed = __decorateClass([
|
|
|
7324
7399
|
Entity42("rss_feeds")
|
|
7325
7400
|
], RssFeed);
|
|
7326
7401
|
|
|
7402
|
+
// src/entities/job-schedule.entity.ts
|
|
7403
|
+
import {
|
|
7404
|
+
Entity as Entity44,
|
|
7405
|
+
PrimaryGeneratedColumn as PrimaryGeneratedColumn44,
|
|
7406
|
+
Column as Column44,
|
|
7407
|
+
CreateDateColumn as CreateDateColumn4,
|
|
7408
|
+
UpdateDateColumn as UpdateDateColumn2,
|
|
7409
|
+
OneToMany as OneToMany18
|
|
7410
|
+
} from "typeorm";
|
|
7411
|
+
|
|
7412
|
+
// src/entities/job-schedule-run.entity.ts
|
|
7413
|
+
import {
|
|
7414
|
+
Entity as Entity43,
|
|
7415
|
+
PrimaryGeneratedColumn as PrimaryGeneratedColumn43,
|
|
7416
|
+
Column as Column43,
|
|
7417
|
+
CreateDateColumn as CreateDateColumn3,
|
|
7418
|
+
ManyToOne as ManyToOne29,
|
|
7419
|
+
JoinColumn as JoinColumn29
|
|
7420
|
+
} from "typeorm";
|
|
7421
|
+
var JobScheduleRun = class {
|
|
7422
|
+
id;
|
|
7423
|
+
scheduleId;
|
|
7424
|
+
schedule;
|
|
7425
|
+
startedAt;
|
|
7426
|
+
finishedAt;
|
|
7427
|
+
status;
|
|
7428
|
+
error;
|
|
7429
|
+
result;
|
|
7430
|
+
triggeredBy;
|
|
7431
|
+
createdAt;
|
|
7432
|
+
};
|
|
7433
|
+
__decorateClass([
|
|
7434
|
+
PrimaryGeneratedColumn43("uuid")
|
|
7435
|
+
], JobScheduleRun.prototype, "id", 2);
|
|
7436
|
+
__decorateClass([
|
|
7437
|
+
Column43({ type: "uuid" })
|
|
7438
|
+
], JobScheduleRun.prototype, "scheduleId", 2);
|
|
7439
|
+
__decorateClass([
|
|
7440
|
+
ManyToOne29(() => JobSchedule, (s) => s.runs, { onDelete: "CASCADE" }),
|
|
7441
|
+
JoinColumn29({ name: "scheduleId" })
|
|
7442
|
+
], JobScheduleRun.prototype, "schedule", 2);
|
|
7443
|
+
__decorateClass([
|
|
7444
|
+
Column43({ type: "timestamp" })
|
|
7445
|
+
], JobScheduleRun.prototype, "startedAt", 2);
|
|
7446
|
+
__decorateClass([
|
|
7447
|
+
Column43({ type: "timestamp", nullable: true })
|
|
7448
|
+
], JobScheduleRun.prototype, "finishedAt", 2);
|
|
7449
|
+
__decorateClass([
|
|
7450
|
+
Column43({ type: "varchar", length: 32 })
|
|
7451
|
+
], JobScheduleRun.prototype, "status", 2);
|
|
7452
|
+
__decorateClass([
|
|
7453
|
+
Column43({ type: "text", nullable: true })
|
|
7454
|
+
], JobScheduleRun.prototype, "error", 2);
|
|
7455
|
+
__decorateClass([
|
|
7456
|
+
Column43({ type: "jsonb", nullable: true })
|
|
7457
|
+
], JobScheduleRun.prototype, "result", 2);
|
|
7458
|
+
__decorateClass([
|
|
7459
|
+
Column43({ type: "varchar", length: 16, default: "schedule" })
|
|
7460
|
+
], JobScheduleRun.prototype, "triggeredBy", 2);
|
|
7461
|
+
__decorateClass([
|
|
7462
|
+
CreateDateColumn3()
|
|
7463
|
+
], JobScheduleRun.prototype, "createdAt", 2);
|
|
7464
|
+
JobScheduleRun = __decorateClass([
|
|
7465
|
+
Entity43("job_schedule_runs")
|
|
7466
|
+
], JobScheduleRun);
|
|
7467
|
+
|
|
7468
|
+
// src/entities/job-schedule.entity.ts
|
|
7469
|
+
var JobSchedule = class {
|
|
7470
|
+
id;
|
|
7471
|
+
name;
|
|
7472
|
+
jobType;
|
|
7473
|
+
enabled;
|
|
7474
|
+
scheduleMode;
|
|
7475
|
+
intervalMinutes;
|
|
7476
|
+
runAtTime;
|
|
7477
|
+
runOnDays;
|
|
7478
|
+
timezone;
|
|
7479
|
+
cronExpression;
|
|
7480
|
+
payload;
|
|
7481
|
+
authorId;
|
|
7482
|
+
pgBossScheduleName;
|
|
7483
|
+
lastRunAt;
|
|
7484
|
+
lastRunStatus;
|
|
7485
|
+
lastRunError;
|
|
7486
|
+
nextRunAt;
|
|
7487
|
+
runs;
|
|
7488
|
+
createdAt;
|
|
7489
|
+
updatedAt;
|
|
7490
|
+
};
|
|
7491
|
+
__decorateClass([
|
|
7492
|
+
PrimaryGeneratedColumn44("uuid")
|
|
7493
|
+
], JobSchedule.prototype, "id", 2);
|
|
7494
|
+
__decorateClass([
|
|
7495
|
+
Column44({ type: "text" })
|
|
7496
|
+
], JobSchedule.prototype, "name", 2);
|
|
7497
|
+
__decorateClass([
|
|
7498
|
+
Column44({ type: "varchar", length: 64, default: "blog_generate" })
|
|
7499
|
+
], JobSchedule.prototype, "jobType", 2);
|
|
7500
|
+
__decorateClass([
|
|
7501
|
+
Column44({ type: "boolean", default: false })
|
|
7502
|
+
], JobSchedule.prototype, "enabled", 2);
|
|
7503
|
+
__decorateClass([
|
|
7504
|
+
Column44({ type: "varchar", length: 32, default: "daily" })
|
|
7505
|
+
], JobSchedule.prototype, "scheduleMode", 2);
|
|
7506
|
+
__decorateClass([
|
|
7507
|
+
Column44({ type: "int", nullable: true })
|
|
7508
|
+
], JobSchedule.prototype, "intervalMinutes", 2);
|
|
7509
|
+
__decorateClass([
|
|
7510
|
+
Column44({ type: "varchar", length: 8, nullable: true })
|
|
7511
|
+
], JobSchedule.prototype, "runAtTime", 2);
|
|
7512
|
+
__decorateClass([
|
|
7513
|
+
Column44({ type: "jsonb", nullable: true })
|
|
7514
|
+
], JobSchedule.prototype, "runOnDays", 2);
|
|
7515
|
+
__decorateClass([
|
|
7516
|
+
Column44({ type: "varchar", length: 64, default: "UTC" })
|
|
7517
|
+
], JobSchedule.prototype, "timezone", 2);
|
|
7518
|
+
__decorateClass([
|
|
7519
|
+
Column44({ type: "text", nullable: true })
|
|
7520
|
+
], JobSchedule.prototype, "cronExpression", 2);
|
|
7521
|
+
__decorateClass([
|
|
7522
|
+
Column44({ type: "jsonb", default: {} })
|
|
7523
|
+
], JobSchedule.prototype, "payload", 2);
|
|
7524
|
+
__decorateClass([
|
|
7525
|
+
Column44({ type: "int", nullable: true })
|
|
7526
|
+
], JobSchedule.prototype, "authorId", 2);
|
|
7527
|
+
__decorateClass([
|
|
7528
|
+
Column44({ type: "varchar", length: 128, unique: true })
|
|
7529
|
+
], JobSchedule.prototype, "pgBossScheduleName", 2);
|
|
7530
|
+
__decorateClass([
|
|
7531
|
+
Column44({ type: "timestamp", nullable: true })
|
|
7532
|
+
], JobSchedule.prototype, "lastRunAt", 2);
|
|
7533
|
+
__decorateClass([
|
|
7534
|
+
Column44({ type: "varchar", length: 32, nullable: true })
|
|
7535
|
+
], JobSchedule.prototype, "lastRunStatus", 2);
|
|
7536
|
+
__decorateClass([
|
|
7537
|
+
Column44({ type: "text", nullable: true })
|
|
7538
|
+
], JobSchedule.prototype, "lastRunError", 2);
|
|
7539
|
+
__decorateClass([
|
|
7540
|
+
Column44({ type: "timestamp", nullable: true })
|
|
7541
|
+
], JobSchedule.prototype, "nextRunAt", 2);
|
|
7542
|
+
__decorateClass([
|
|
7543
|
+
OneToMany18(() => JobScheduleRun, (r) => r.schedule)
|
|
7544
|
+
], JobSchedule.prototype, "runs", 2);
|
|
7545
|
+
__decorateClass([
|
|
7546
|
+
CreateDateColumn4()
|
|
7547
|
+
], JobSchedule.prototype, "createdAt", 2);
|
|
7548
|
+
__decorateClass([
|
|
7549
|
+
UpdateDateColumn2()
|
|
7550
|
+
], JobSchedule.prototype, "updatedAt", 2);
|
|
7551
|
+
JobSchedule = __decorateClass([
|
|
7552
|
+
Entity44("job_schedules")
|
|
7553
|
+
], JobSchedule);
|
|
7554
|
+
|
|
7327
7555
|
// src/entities/index.ts
|
|
7328
7556
|
var CMS_ENTITY_MAP = {
|
|
7329
7557
|
users: User,
|
|
@@ -7367,7 +7595,9 @@ var CMS_ENTITY_MAP = {
|
|
|
7367
7595
|
llm_agents: LlmAgent,
|
|
7368
7596
|
llm_agent_knowledge_documents: LlmAgentKnowledgeDocument,
|
|
7369
7597
|
rss_feeds: RssFeed,
|
|
7370
|
-
rss_articles: RssArticle
|
|
7598
|
+
rss_articles: RssArticle,
|
|
7599
|
+
job_schedules: JobSchedule,
|
|
7600
|
+
job_schedule_runs: JobScheduleRun
|
|
7371
7601
|
};
|
|
7372
7602
|
|
|
7373
7603
|
// src/plugins/blog-generator/blog-generator-service.ts
|
|
@@ -7715,6 +7945,13 @@ import path from "path";
|
|
|
7715
7945
|
|
|
7716
7946
|
// src/plugins/social-media/linkedin-client.ts
|
|
7717
7947
|
var RESTLI = "2.0.0";
|
|
7948
|
+
function linkedInHeaders(accessToken, json = false) {
|
|
7949
|
+
return {
|
|
7950
|
+
Authorization: `Bearer ${accessToken}`,
|
|
7951
|
+
"X-Restli-Protocol-Version": RESTLI,
|
|
7952
|
+
...json ? { "Content-Type": "application/json" } : {}
|
|
7953
|
+
};
|
|
7954
|
+
}
|
|
7718
7955
|
async function linkedInGetUserinfo(accessToken) {
|
|
7719
7956
|
const r = await fetch("https://api.linkedin.com/v2/userinfo", {
|
|
7720
7957
|
headers: { Authorization: `Bearer ${accessToken}` }
|
|
@@ -7729,20 +7966,116 @@ async function linkedInGetUserinfo(accessToken) {
|
|
|
7729
7966
|
throw new Error("LinkedIn userinfo: invalid JSON");
|
|
7730
7967
|
}
|
|
7731
7968
|
}
|
|
7969
|
+
async function linkedInFetchOrganizationAcls(accessToken) {
|
|
7970
|
+
const r = await fetch("https://api.linkedin.com/v2/organizationAcls?q=roleAssignee", {
|
|
7971
|
+
headers: linkedInHeaders(accessToken)
|
|
7972
|
+
});
|
|
7973
|
+
const text = await r.text();
|
|
7974
|
+
if (!r.ok) {
|
|
7975
|
+
throw new Error(`LinkedIn organizationAcls failed (${r.status}): ${text.slice(0, 800)}`);
|
|
7976
|
+
}
|
|
7977
|
+
let data;
|
|
7978
|
+
try {
|
|
7979
|
+
data = JSON.parse(text);
|
|
7980
|
+
} catch {
|
|
7981
|
+
throw new Error("LinkedIn organizationAcls: invalid JSON");
|
|
7982
|
+
}
|
|
7983
|
+
return Array.isArray(data.elements) ? data.elements : [];
|
|
7984
|
+
}
|
|
7985
|
+
function organizationDisplayName(org) {
|
|
7986
|
+
if (org.localizedName?.trim()) return org.localizedName.trim();
|
|
7987
|
+
const localized = org.name?.localized;
|
|
7988
|
+
if (localized) {
|
|
7989
|
+
for (const v of Object.values(localized)) {
|
|
7990
|
+
if (v?.trim()) return v.trim();
|
|
7991
|
+
}
|
|
7992
|
+
}
|
|
7993
|
+
if (org.vanityName?.trim()) return org.vanityName.trim();
|
|
7994
|
+
if (org.id != null) return `Organization ${org.id}`;
|
|
7995
|
+
return "Organization";
|
|
7996
|
+
}
|
|
7997
|
+
function organizationWebsite(org) {
|
|
7998
|
+
if (org.localizedWebsite?.trim()) return org.localizedWebsite.trim();
|
|
7999
|
+
const localized = org.website?.localized;
|
|
8000
|
+
if (localized) {
|
|
8001
|
+
for (const v of Object.values(localized)) {
|
|
8002
|
+
if (v?.trim()) return v.trim();
|
|
8003
|
+
}
|
|
8004
|
+
}
|
|
8005
|
+
return void 0;
|
|
8006
|
+
}
|
|
8007
|
+
function linkedInOrganizationUrn(orgIdOrUrn) {
|
|
8008
|
+
const s = String(orgIdOrUrn ?? "").trim();
|
|
8009
|
+
if (s.startsWith("urn:li:organization:")) return s;
|
|
8010
|
+
return `urn:li:organization:${s}`;
|
|
8011
|
+
}
|
|
8012
|
+
function linkedInOrganizationIdFromUrn(organizationUrn) {
|
|
8013
|
+
const m = String(organizationUrn).trim().match(/urn:li:organization:(\d+)/);
|
|
8014
|
+
if (!m?.[1]) throw new Error(`Invalid LinkedIn organization URN: ${organizationUrn}`);
|
|
8015
|
+
return m[1];
|
|
8016
|
+
}
|
|
8017
|
+
async function linkedInGetOrganization(accessToken, orgIdOrUrn) {
|
|
8018
|
+
const orgId = String(orgIdOrUrn).startsWith("urn:") ? linkedInOrganizationIdFromUrn(String(orgIdOrUrn)) : String(orgIdOrUrn).trim();
|
|
8019
|
+
const r = await fetch(`https://api.linkedin.com/v2/organizations/${encodeURIComponent(orgId)}`, {
|
|
8020
|
+
headers: linkedInHeaders(accessToken)
|
|
8021
|
+
});
|
|
8022
|
+
const text = await r.text();
|
|
8023
|
+
if (!r.ok) {
|
|
8024
|
+
throw new Error(`LinkedIn organization ${orgId} failed (${r.status}): ${text.slice(0, 800)}`);
|
|
8025
|
+
}
|
|
8026
|
+
try {
|
|
8027
|
+
return JSON.parse(text);
|
|
8028
|
+
} catch {
|
|
8029
|
+
throw new Error(`LinkedIn organization ${orgId}: invalid JSON`);
|
|
8030
|
+
}
|
|
8031
|
+
}
|
|
8032
|
+
async function linkedInListManagedOrganizations(accessToken) {
|
|
8033
|
+
const acls = await linkedInFetchOrganizationAcls(accessToken);
|
|
8034
|
+
const approved = acls.filter((a) => a.state === "APPROVED" && a.organization?.trim());
|
|
8035
|
+
const byUrn = /* @__PURE__ */ new Map();
|
|
8036
|
+
for (const acl of approved) {
|
|
8037
|
+
const urn = linkedInOrganizationUrn(acl.organization);
|
|
8038
|
+
if (!byUrn.has(urn)) byUrn.set(urn, acl);
|
|
8039
|
+
}
|
|
8040
|
+
const out = [];
|
|
8041
|
+
for (const [urn, acl] of byUrn) {
|
|
8042
|
+
try {
|
|
8043
|
+
const org = await linkedInGetOrganization(accessToken, urn);
|
|
8044
|
+
out.push({
|
|
8045
|
+
urn,
|
|
8046
|
+
id: org.id ?? Number(linkedInOrganizationIdFromUrn(urn)),
|
|
8047
|
+
name: organizationDisplayName(org),
|
|
8048
|
+
vanityName: org.vanityName,
|
|
8049
|
+
website: organizationWebsite(org),
|
|
8050
|
+
role: acl.role
|
|
8051
|
+
});
|
|
8052
|
+
} catch {
|
|
8053
|
+
out.push({
|
|
8054
|
+
urn,
|
|
8055
|
+
id: Number(linkedInOrganizationIdFromUrn(urn)),
|
|
8056
|
+
name: linkedInOrganizationIdFromUrn(urn),
|
|
8057
|
+
role: acl.role
|
|
8058
|
+
});
|
|
8059
|
+
}
|
|
8060
|
+
}
|
|
8061
|
+
out.sort((a, b) => a.name.localeCompare(b.name));
|
|
8062
|
+
return out;
|
|
8063
|
+
}
|
|
7732
8064
|
function personUrn(personSub) {
|
|
7733
8065
|
const s = String(personSub || "").trim();
|
|
7734
8066
|
if (s.startsWith("urn:li:person:")) return s;
|
|
7735
8067
|
return `urn:li:person:${s}`;
|
|
7736
8068
|
}
|
|
7737
|
-
|
|
7738
|
-
const
|
|
8069
|
+
function authorUrnFromParams(authorUrn, personSub) {
|
|
8070
|
+
const direct = String(authorUrn ?? "").trim();
|
|
8071
|
+
if (direct.startsWith("urn:li:")) return direct;
|
|
8072
|
+
return personUrn(String(personSub ?? "").trim());
|
|
8073
|
+
}
|
|
8074
|
+
async function linkedInRegisterImageUpload(accessToken, author) {
|
|
8075
|
+
const owner = authorUrnFromParams(author);
|
|
7739
8076
|
const r = await fetch("https://api.linkedin.com/v2/assets?action=registerUpload", {
|
|
7740
8077
|
method: "POST",
|
|
7741
|
-
headers:
|
|
7742
|
-
Authorization: `Bearer ${accessToken}`,
|
|
7743
|
-
"X-Restli-Protocol-Version": RESTLI,
|
|
7744
|
-
"Content-Type": "application/json"
|
|
7745
|
-
},
|
|
8078
|
+
headers: linkedInHeaders(accessToken, true),
|
|
7746
8079
|
body: JSON.stringify({
|
|
7747
8080
|
registerUploadRequest: {
|
|
7748
8081
|
recipes: ["urn:li:digitalmediaRecipe:feedshare-image"],
|
|
@@ -7790,7 +8123,7 @@ async function linkedInUploadBinary(accessToken, uploadUrl, body, contentType) {
|
|
|
7790
8123
|
}
|
|
7791
8124
|
}
|
|
7792
8125
|
async function linkedInCreateImageShare(params) {
|
|
7793
|
-
const author =
|
|
8126
|
+
const author = authorUrnFromParams(params.authorUrn);
|
|
7794
8127
|
const commentary = params.commentary.slice(0, 2900);
|
|
7795
8128
|
const title = params.title.slice(0, 200);
|
|
7796
8129
|
const description = (params.description ?? title).slice(0, 200);
|
|
@@ -7836,7 +8169,7 @@ async function linkedInCreateImageShare(params) {
|
|
|
7836
8169
|
return { status: r.status, id: data.id };
|
|
7837
8170
|
}
|
|
7838
8171
|
async function linkedInCreateTextShare(params) {
|
|
7839
|
-
const author =
|
|
8172
|
+
const author = authorUrnFromParams(params.authorUrn);
|
|
7840
8173
|
const commentary = params.commentary.slice(0, 2900);
|
|
7841
8174
|
const r = await fetch("https://api.linkedin.com/v2/ugcPosts", {
|
|
7842
8175
|
method: "POST",
|
|
@@ -8170,18 +8503,44 @@ function createSocialMediaHandlers(config) {
|
|
|
8170
8503
|
const enabled = map.enabled !== "false";
|
|
8171
8504
|
const token = (map.linkedin_access_token ?? "").trim();
|
|
8172
8505
|
const sub = (map.linkedin_person_sub ?? "").trim();
|
|
8506
|
+
const orgUrn = (map.linkedin_organization_urn ?? "").trim();
|
|
8173
8507
|
return json({
|
|
8174
8508
|
ok: true,
|
|
8175
|
-
linkedInReady: enabled && Boolean(token) && Boolean(
|
|
8509
|
+
linkedInReady: enabled && Boolean(token) && Boolean(orgUrn),
|
|
8176
8510
|
enabled,
|
|
8177
8511
|
hasToken: Boolean(token),
|
|
8178
|
-
hasPersonSub: Boolean(sub)
|
|
8512
|
+
hasPersonSub: Boolean(sub),
|
|
8513
|
+
hasOrganization: Boolean(orgUrn),
|
|
8514
|
+
organizationName: map.linkedin_organization_name?.trim() || null
|
|
8179
8515
|
});
|
|
8180
8516
|
} catch (e) {
|
|
8181
8517
|
const msg = e instanceof Error ? e.message : "Failed to load status";
|
|
8182
8518
|
return json({ error: msg }, { status: 500 });
|
|
8183
8519
|
}
|
|
8184
8520
|
},
|
|
8521
|
+
async fetchLinkedInOrganizations(req) {
|
|
8522
|
+
const a = await requireAuth(req);
|
|
8523
|
+
if (a) return a;
|
|
8524
|
+
const pe = await requireEntityPermission(req, "settings", "read");
|
|
8525
|
+
if (pe) return pe;
|
|
8526
|
+
let body = {};
|
|
8527
|
+
try {
|
|
8528
|
+
body = await req.json();
|
|
8529
|
+
} catch {
|
|
8530
|
+
}
|
|
8531
|
+
try {
|
|
8532
|
+
const map = await loadGroupMap(dataSource, entityMap, encryptionKey);
|
|
8533
|
+
const token = String(body?.accessToken ?? map.linkedin_access_token ?? "").trim();
|
|
8534
|
+
if (!token) {
|
|
8535
|
+
return json({ error: "LinkedIn access token is required." }, { status: 400 });
|
|
8536
|
+
}
|
|
8537
|
+
const organizations = await linkedInListManagedOrganizations(token);
|
|
8538
|
+
return json({ ok: true, organizations });
|
|
8539
|
+
} catch (e) {
|
|
8540
|
+
const msg = e instanceof Error ? e.message : "LinkedIn organizations request failed";
|
|
8541
|
+
return json({ error: msg }, { status: 502 });
|
|
8542
|
+
}
|
|
8543
|
+
},
|
|
8185
8544
|
async syncLinkedInProfile(req) {
|
|
8186
8545
|
const a = await requireAuth(req);
|
|
8187
8546
|
if (a) return a;
|
|
@@ -8230,10 +8589,12 @@ function createSocialMediaHandlers(config) {
|
|
|
8230
8589
|
return json({ error: "Social media plugin is disabled." }, { status: 400 });
|
|
8231
8590
|
}
|
|
8232
8591
|
const token = (map.linkedin_access_token ?? "").trim();
|
|
8233
|
-
const
|
|
8234
|
-
if (!token || !
|
|
8592
|
+
const authorUrn = (map.linkedin_organization_urn ?? "").trim();
|
|
8593
|
+
if (!token || !authorUrn) {
|
|
8235
8594
|
return json(
|
|
8236
|
-
{
|
|
8595
|
+
{
|
|
8596
|
+
error: "Configure LinkedIn access token and target organization (Plugins \u2192 Social media \u2192 LinkedIn)."
|
|
8597
|
+
},
|
|
8237
8598
|
{ status: 400 }
|
|
8238
8599
|
);
|
|
8239
8600
|
}
|
|
@@ -8274,7 +8635,7 @@ ${excerpt}`).trim().slice(0, 2900);
|
|
|
8274
8635
|
contentType: imagePayload.contentType
|
|
8275
8636
|
});
|
|
8276
8637
|
try {
|
|
8277
|
-
const { uploadUrl, asset } = await linkedInRegisterImageUpload(token,
|
|
8638
|
+
const { uploadUrl, asset } = await linkedInRegisterImageUpload(token, authorUrn);
|
|
8278
8639
|
logLinkedInPublish(publishLog, "image_registered", {
|
|
8279
8640
|
blogId,
|
|
8280
8641
|
asset: truncateForLog(asset, 120),
|
|
@@ -8290,7 +8651,7 @@ ${excerpt}`).trim().slice(0, 2900);
|
|
|
8290
8651
|
logLinkedInPublish(publishLog, "image_uploaded", { blogId, bytes: imagePayload.buffer.length });
|
|
8291
8652
|
const out2 = await linkedInCreateImageShare({
|
|
8292
8653
|
accessToken: token,
|
|
8293
|
-
|
|
8654
|
+
authorUrn,
|
|
8294
8655
|
asset,
|
|
8295
8656
|
commentary,
|
|
8296
8657
|
title,
|
|
@@ -8328,7 +8689,7 @@ ${excerpt}`).trim().slice(0, 2900);
|
|
|
8328
8689
|
});
|
|
8329
8690
|
const out2 = await linkedInCreateTextShare({
|
|
8330
8691
|
accessToken: token,
|
|
8331
|
-
|
|
8692
|
+
authorUrn,
|
|
8332
8693
|
commentary
|
|
8333
8694
|
});
|
|
8334
8695
|
logLinkedInPublish(publishLog, "success", {
|
|
@@ -8367,7 +8728,7 @@ ${excerpt}`).trim().slice(0, 2900);
|
|
|
8367
8728
|
});
|
|
8368
8729
|
const out = await linkedInCreateTextShare({
|
|
8369
8730
|
accessToken: token,
|
|
8370
|
-
|
|
8731
|
+
authorUrn,
|
|
8371
8732
|
commentary
|
|
8372
8733
|
});
|
|
8373
8734
|
logLinkedInPublish(publishLog, "success", {
|
|
@@ -8570,6 +8931,323 @@ ${excerpt}`).trim().slice(0, 2200);
|
|
|
8570
8931
|
};
|
|
8571
8932
|
}
|
|
8572
8933
|
|
|
8934
|
+
// src/api/job-schedule-handlers.ts
|
|
8935
|
+
import { randomUUID } from "crypto";
|
|
8936
|
+
|
|
8937
|
+
// src/plugins/jobs/schedule-cron.ts
|
|
8938
|
+
function pgBossScheduleNameForId(scheduleId) {
|
|
8939
|
+
return `schedule:${scheduleId}`;
|
|
8940
|
+
}
|
|
8941
|
+
function buildCronFromSchedule(schedule) {
|
|
8942
|
+
if (schedule.scheduleMode === "cron" && schedule.cronExpression?.trim()) {
|
|
8943
|
+
return schedule.cronExpression.trim();
|
|
8944
|
+
}
|
|
8945
|
+
const time = parseRunAtTime(schedule.runAtTime);
|
|
8946
|
+
const minute = time?.minute ?? 0;
|
|
8947
|
+
const hour = time?.hour ?? 9;
|
|
8948
|
+
if (schedule.scheduleMode === "interval") {
|
|
8949
|
+
const mins = schedule.intervalMinutes ?? 60;
|
|
8950
|
+
if (mins >= 60 && mins % 60 === 0) {
|
|
8951
|
+
const h = Math.max(1, Math.min(23, Math.floor(mins / 60)));
|
|
8952
|
+
if (h === 1) return `${minute} * * * *`;
|
|
8953
|
+
return `${minute} */${h} * * *`;
|
|
8954
|
+
}
|
|
8955
|
+
if (mins <= 59) {
|
|
8956
|
+
return `*/${Math.max(1, mins)} * * * *`;
|
|
8957
|
+
}
|
|
8958
|
+
return `${minute} * * * *`;
|
|
8959
|
+
}
|
|
8960
|
+
if (schedule.scheduleMode === "weekly") {
|
|
8961
|
+
const days = Array.isArray(schedule.runOnDays) && schedule.runOnDays.length > 0 ? schedule.runOnDays.filter((d) => d >= 0 && d <= 6).join(",") : "1";
|
|
8962
|
+
return `${minute} ${hour} * * ${days}`;
|
|
8963
|
+
}
|
|
8964
|
+
return `${minute} ${hour} * * *`;
|
|
8965
|
+
}
|
|
8966
|
+
function parseRunAtTime(runAtTime) {
|
|
8967
|
+
if (!runAtTime?.trim()) return null;
|
|
8968
|
+
const m = runAtTime.trim().match(/^(\d{1,2}):(\d{2})$/);
|
|
8969
|
+
if (!m) return null;
|
|
8970
|
+
const hour = parseInt(m[1], 10);
|
|
8971
|
+
const minute = parseInt(m[2], 10);
|
|
8972
|
+
if (hour < 0 || hour > 23 || minute < 0 || minute > 59) return null;
|
|
8973
|
+
return { hour, minute };
|
|
8974
|
+
}
|
|
8975
|
+
function validateScheduleInput(schedule) {
|
|
8976
|
+
if (schedule.scheduleMode === "cron") {
|
|
8977
|
+
if (!schedule.cronExpression?.trim()) return "cronExpression is required for cron mode";
|
|
8978
|
+
return null;
|
|
8979
|
+
}
|
|
8980
|
+
if (schedule.scheduleMode === "interval") {
|
|
8981
|
+
const mins = schedule.intervalMinutes;
|
|
8982
|
+
if (mins == null || !Number.isFinite(mins) || mins < 1 || mins > 10080) {
|
|
8983
|
+
return "intervalMinutes must be between 1 and 10080";
|
|
8984
|
+
}
|
|
8985
|
+
return null;
|
|
8986
|
+
}
|
|
8987
|
+
if (schedule.runAtTime?.trim()) {
|
|
8988
|
+
if (!parseRunAtTime(schedule.runAtTime)) return "runAtTime must be HH:mm (24h)";
|
|
8989
|
+
}
|
|
8990
|
+
return null;
|
|
8991
|
+
}
|
|
8992
|
+
|
|
8993
|
+
// src/plugins/jobs/schedule-sync.ts
|
|
8994
|
+
async function syncJobScheduleToPgBoss(cms, schedule, onRegisterQueue) {
|
|
8995
|
+
const boss = cms.getPlugin("pg_boss");
|
|
8996
|
+
if (!boss) return;
|
|
8997
|
+
const name = schedule.pgBossScheduleName;
|
|
8998
|
+
await boss.unschedule(name);
|
|
8999
|
+
if (!schedule.enabled) return;
|
|
9000
|
+
const cron = buildCronFromSchedule(schedule);
|
|
9001
|
+
const data = {
|
|
9002
|
+
scheduleId: schedule.id,
|
|
9003
|
+
triggeredBy: "schedule"
|
|
9004
|
+
};
|
|
9005
|
+
await boss.schedule(name, cron, data, { tz: schedule.timezone || "UTC" });
|
|
9006
|
+
if (onRegisterQueue) {
|
|
9007
|
+
await onRegisterQueue(name);
|
|
9008
|
+
}
|
|
9009
|
+
}
|
|
9010
|
+
async function queueJobScheduleNow(cms, scheduleId) {
|
|
9011
|
+
const boss = cms.getPlugin("pg_boss");
|
|
9012
|
+
if (!boss) {
|
|
9013
|
+
throw new Error("pg-boss is not configured (DATABASE_URL required)");
|
|
9014
|
+
}
|
|
9015
|
+
const { JOB_RUNNER_QUEUE: JOB_RUNNER_QUEUE2 } = await Promise.resolve().then(() => (init_pg_boss_service(), pg_boss_service_exports));
|
|
9016
|
+
await boss.send(JOB_RUNNER_QUEUE2, {
|
|
9017
|
+
scheduleId,
|
|
9018
|
+
triggeredBy: "manual"
|
|
9019
|
+
});
|
|
9020
|
+
}
|
|
9021
|
+
|
|
9022
|
+
// src/plugins/jobs/job-runner.ts
|
|
9023
|
+
init_pg_boss_service();
|
|
9024
|
+
|
|
9025
|
+
// src/plugins/jobs/blog-generate-job.ts
|
|
9026
|
+
import { In as In3 } from "typeorm";
|
|
9027
|
+
|
|
9028
|
+
// src/plugins/jobs/schedule-next-run.ts
|
|
9029
|
+
import { parseExpression } from "cron-parser";
|
|
9030
|
+
function computeNextRunAt(schedule, from = /* @__PURE__ */ new Date()) {
|
|
9031
|
+
if (!schedule.enabled) return null;
|
|
9032
|
+
try {
|
|
9033
|
+
const cron = buildCronFromSchedule(schedule);
|
|
9034
|
+
const tz = schedule.timezone?.trim() || "UTC";
|
|
9035
|
+
const interval = parseExpression(cron, {
|
|
9036
|
+
currentDate: from,
|
|
9037
|
+
tz
|
|
9038
|
+
});
|
|
9039
|
+
return interval.next().toDate();
|
|
9040
|
+
} catch (err) {
|
|
9041
|
+
console.warn("[job-schedules] could not compute next run:", err);
|
|
9042
|
+
return null;
|
|
9043
|
+
}
|
|
9044
|
+
}
|
|
9045
|
+
|
|
9046
|
+
// src/plugins/jobs/job-runner.ts
|
|
9047
|
+
var executeJobRef = null;
|
|
9048
|
+
async function ensureScheduleQueueWorker(cms, queueName) {
|
|
9049
|
+
const boss = cms.getPlugin("pg_boss");
|
|
9050
|
+
if (!boss || !executeJobRef) return;
|
|
9051
|
+
await boss.registerWork(queueName, executeJobRef);
|
|
9052
|
+
}
|
|
9053
|
+
|
|
9054
|
+
// src/api/job-schedule-handlers.ts
|
|
9055
|
+
function serializeSchedule(s) {
|
|
9056
|
+
return {
|
|
9057
|
+
id: s.id,
|
|
9058
|
+
name: s.name,
|
|
9059
|
+
jobType: s.jobType,
|
|
9060
|
+
enabled: s.enabled,
|
|
9061
|
+
scheduleMode: s.scheduleMode,
|
|
9062
|
+
intervalMinutes: s.intervalMinutes,
|
|
9063
|
+
runAtTime: s.runAtTime,
|
|
9064
|
+
runOnDays: s.runOnDays,
|
|
9065
|
+
timezone: s.timezone,
|
|
9066
|
+
cronExpression: s.cronExpression,
|
|
9067
|
+
cronResolved: buildCronFromSchedule(s),
|
|
9068
|
+
payload: s.payload ?? {},
|
|
9069
|
+
authorId: s.authorId,
|
|
9070
|
+
pgBossScheduleName: s.pgBossScheduleName,
|
|
9071
|
+
lastRunAt: s.lastRunAt?.toISOString() ?? null,
|
|
9072
|
+
lastRunStatus: s.lastRunStatus,
|
|
9073
|
+
lastRunError: s.lastRunError,
|
|
9074
|
+
nextRunAt: (computeNextRunAt(s) ?? s.nextRunAt)?.toISOString() ?? null,
|
|
9075
|
+
createdAt: s.createdAt.toISOString(),
|
|
9076
|
+
updatedAt: s.updatedAt.toISOString()
|
|
9077
|
+
};
|
|
9078
|
+
}
|
|
9079
|
+
function serializeRun(r) {
|
|
9080
|
+
return {
|
|
9081
|
+
id: r.id,
|
|
9082
|
+
scheduleId: r.scheduleId,
|
|
9083
|
+
startedAt: r.startedAt.toISOString(),
|
|
9084
|
+
finishedAt: r.finishedAt?.toISOString() ?? null,
|
|
9085
|
+
status: r.status,
|
|
9086
|
+
error: r.error,
|
|
9087
|
+
result: r.result,
|
|
9088
|
+
triggeredBy: r.triggeredBy,
|
|
9089
|
+
createdAt: r.createdAt.toISOString()
|
|
9090
|
+
};
|
|
9091
|
+
}
|
|
9092
|
+
function parseScheduleBody(body, existing) {
|
|
9093
|
+
const scheduleMode = typeof body.scheduleMode === "string" ? body.scheduleMode : existing?.scheduleMode ?? "daily";
|
|
9094
|
+
const patch = {
|
|
9095
|
+
name: typeof body.name === "string" ? body.name.trim().slice(0, 500) : existing?.name,
|
|
9096
|
+
jobType: body.jobType === "blog_generate" ? "blog_generate" : existing?.jobType ?? "blog_generate",
|
|
9097
|
+
enabled: typeof body.enabled === "boolean" ? body.enabled : existing?.enabled ?? false,
|
|
9098
|
+
scheduleMode,
|
|
9099
|
+
intervalMinutes: typeof body.intervalMinutes === "number" ? body.intervalMinutes : body.intervalMinutes != null ? Number(body.intervalMinutes) : existing?.intervalMinutes ?? null,
|
|
9100
|
+
runAtTime: typeof body.runAtTime === "string" ? body.runAtTime.trim() || null : body.runAtTime === null ? null : existing?.runAtTime ?? null,
|
|
9101
|
+
runOnDays: Array.isArray(body.runOnDays) ? body.runOnDays.map((d) => Number(d)).filter((d) => Number.isFinite(d)) : existing?.runOnDays ?? null,
|
|
9102
|
+
timezone: typeof body.timezone === "string" && body.timezone.trim() ? body.timezone.trim().slice(0, 64) : existing?.timezone ?? "UTC",
|
|
9103
|
+
cronExpression: typeof body.cronExpression === "string" ? body.cronExpression.trim() || null : existing?.cronExpression ?? null,
|
|
9104
|
+
payload: body.payload != null && typeof body.payload === "object" && !Array.isArray(body.payload) ? body.payload : existing?.payload ?? {},
|
|
9105
|
+
authorId: typeof body.authorId === "number" ? body.authorId : body.authorId != null ? Number(body.authorId) : existing?.authorId ?? null
|
|
9106
|
+
};
|
|
9107
|
+
return patch;
|
|
9108
|
+
}
|
|
9109
|
+
function createJobScheduleHandlers(apiConfig) {
|
|
9110
|
+
const { dataSource, entityMap, json, requireAuth, requireEntityPermission, getCms } = apiConfig;
|
|
9111
|
+
const ScheduleEntity = entityMap.job_schedules ?? JobSchedule;
|
|
9112
|
+
const RunEntity = entityMap.job_schedule_runs ?? JobScheduleRun;
|
|
9113
|
+
async function perm(req, action) {
|
|
9114
|
+
if (!requireEntityPermission) return null;
|
|
9115
|
+
return requireEntityPermission(req, "blogs", action);
|
|
9116
|
+
}
|
|
9117
|
+
async function syncAfterSave(cms, schedule) {
|
|
9118
|
+
await syncJobScheduleToPgBoss(cms, schedule, async (queueName) => {
|
|
9119
|
+
await ensureScheduleQueueWorker(cms, queueName);
|
|
9120
|
+
});
|
|
9121
|
+
const repo = dataSource.getRepository(ScheduleEntity);
|
|
9122
|
+
schedule.nextRunAt = computeNextRunAt(schedule);
|
|
9123
|
+
await repo.save(schedule);
|
|
9124
|
+
}
|
|
9125
|
+
return {
|
|
9126
|
+
async list(req) {
|
|
9127
|
+
const a = await requireAuth(req);
|
|
9128
|
+
if (a) return a;
|
|
9129
|
+
const pe = await perm(req, "read");
|
|
9130
|
+
if (pe) return pe;
|
|
9131
|
+
const list = await dataSource.getRepository(ScheduleEntity).find({
|
|
9132
|
+
order: { updatedAt: "DESC" }
|
|
9133
|
+
});
|
|
9134
|
+
return json({ schedules: list.map(serializeSchedule) });
|
|
9135
|
+
},
|
|
9136
|
+
async getById(req, id) {
|
|
9137
|
+
const a = await requireAuth(req);
|
|
9138
|
+
if (a) return a;
|
|
9139
|
+
const pe = await perm(req, "read");
|
|
9140
|
+
if (pe) return pe;
|
|
9141
|
+
const row = await dataSource.getRepository(ScheduleEntity).findOne({ where: { id } });
|
|
9142
|
+
if (!row) return json({ error: "Schedule not found" }, { status: 404 });
|
|
9143
|
+
const runs = await dataSource.getRepository(RunEntity).find({
|
|
9144
|
+
where: { scheduleId: id },
|
|
9145
|
+
order: { startedAt: "DESC" },
|
|
9146
|
+
take: 20
|
|
9147
|
+
});
|
|
9148
|
+
return json({ schedule: serializeSchedule(row), runs: runs.map(serializeRun) });
|
|
9149
|
+
},
|
|
9150
|
+
async create(req) {
|
|
9151
|
+
const a = await requireAuth(req);
|
|
9152
|
+
if (a) return a;
|
|
9153
|
+
const pe = await perm(req, "create");
|
|
9154
|
+
if (pe) return pe;
|
|
9155
|
+
if (!getCms) return json({ error: "CMS not configured" }, { status: 503 });
|
|
9156
|
+
let body;
|
|
9157
|
+
try {
|
|
9158
|
+
body = await req.json();
|
|
9159
|
+
} catch {
|
|
9160
|
+
return json({ error: "Invalid JSON body" }, { status: 400 });
|
|
9161
|
+
}
|
|
9162
|
+
const patch = parseScheduleBody(body);
|
|
9163
|
+
if (!patch.name) return json({ error: "name is required" }, { status: 400 });
|
|
9164
|
+
const draft = Object.assign(new JobSchedule(), patch);
|
|
9165
|
+
const validationError = validateScheduleInput(draft);
|
|
9166
|
+
if (validationError) return json({ error: validationError }, { status: 400 });
|
|
9167
|
+
const repo = dataSource.getRepository(ScheduleEntity);
|
|
9168
|
+
let row = repo.create({
|
|
9169
|
+
...patch,
|
|
9170
|
+
pgBossScheduleName: `pending-${randomUUID()}`
|
|
9171
|
+
});
|
|
9172
|
+
row = await repo.save(row);
|
|
9173
|
+
row.pgBossScheduleName = pgBossScheduleNameForId(row.id);
|
|
9174
|
+
row = await repo.save(row);
|
|
9175
|
+
const cms = await getCms();
|
|
9176
|
+
await syncAfterSave(cms, row);
|
|
9177
|
+
return json({ schedule: serializeSchedule(row) }, { status: 201 });
|
|
9178
|
+
},
|
|
9179
|
+
async update(req, id) {
|
|
9180
|
+
const a = await requireAuth(req);
|
|
9181
|
+
if (a) return a;
|
|
9182
|
+
const pe = await perm(req, "update");
|
|
9183
|
+
if (pe) return pe;
|
|
9184
|
+
if (!getCms) return json({ error: "CMS not configured" }, { status: 503 });
|
|
9185
|
+
const repo = dataSource.getRepository(ScheduleEntity);
|
|
9186
|
+
const existing = await repo.findOne({ where: { id } });
|
|
9187
|
+
if (!existing) return json({ error: "Schedule not found" }, { status: 404 });
|
|
9188
|
+
let body;
|
|
9189
|
+
try {
|
|
9190
|
+
body = await req.json();
|
|
9191
|
+
} catch {
|
|
9192
|
+
return json({ error: "Invalid JSON body" }, { status: 400 });
|
|
9193
|
+
}
|
|
9194
|
+
const patch = parseScheduleBody(body, existing);
|
|
9195
|
+
Object.assign(existing, patch);
|
|
9196
|
+
const validationError = validateScheduleInput(existing);
|
|
9197
|
+
if (validationError) return json({ error: validationError }, { status: 400 });
|
|
9198
|
+
const row = await repo.save(existing);
|
|
9199
|
+
const cms = await getCms();
|
|
9200
|
+
await syncAfterSave(cms, row);
|
|
9201
|
+
return json({ schedule: serializeSchedule(row) });
|
|
9202
|
+
},
|
|
9203
|
+
async remove(req, id) {
|
|
9204
|
+
const a = await requireAuth(req);
|
|
9205
|
+
if (a) return a;
|
|
9206
|
+
const pe = await perm(req, "delete");
|
|
9207
|
+
if (pe) return pe;
|
|
9208
|
+
if (!getCms) return json({ error: "CMS not configured" }, { status: 503 });
|
|
9209
|
+
const repo = dataSource.getRepository(ScheduleEntity);
|
|
9210
|
+
const existing = await repo.findOne({ where: { id } });
|
|
9211
|
+
if (!existing) return json({ error: "Schedule not found" }, { status: 404 });
|
|
9212
|
+
const cms = await getCms();
|
|
9213
|
+
existing.enabled = false;
|
|
9214
|
+
await syncJobScheduleToPgBoss(cms, existing);
|
|
9215
|
+
await repo.delete({ id });
|
|
9216
|
+
return json({ ok: true });
|
|
9217
|
+
},
|
|
9218
|
+
async runNow(req, id) {
|
|
9219
|
+
const a = await requireAuth(req);
|
|
9220
|
+
if (a) return a;
|
|
9221
|
+
const pe = await perm(req, "create");
|
|
9222
|
+
if (pe) return pe;
|
|
9223
|
+
if (!getCms) return json({ error: "CMS not configured" }, { status: 503 });
|
|
9224
|
+
const repo = dataSource.getRepository(ScheduleEntity);
|
|
9225
|
+
const existing = await repo.findOne({ where: { id } });
|
|
9226
|
+
if (!existing) return json({ error: "Schedule not found" }, { status: 404 });
|
|
9227
|
+
try {
|
|
9228
|
+
const cms = await getCms();
|
|
9229
|
+
await queueJobScheduleNow(cms, id);
|
|
9230
|
+
return json({ ok: true, message: "Job queued" });
|
|
9231
|
+
} catch (e) {
|
|
9232
|
+
const message = e instanceof Error ? e.message : "Failed to queue job";
|
|
9233
|
+
return json({ error: message }, { status: 503 });
|
|
9234
|
+
}
|
|
9235
|
+
},
|
|
9236
|
+
async listRuns(req, id) {
|
|
9237
|
+
const a = await requireAuth(req);
|
|
9238
|
+
if (a) return a;
|
|
9239
|
+
const pe = await perm(req, "read");
|
|
9240
|
+
if (pe) return pe;
|
|
9241
|
+
const runs = await dataSource.getRepository(RunEntity).find({
|
|
9242
|
+
where: { scheduleId: id },
|
|
9243
|
+
order: { startedAt: "DESC" },
|
|
9244
|
+
take: 50
|
|
9245
|
+
});
|
|
9246
|
+
return json({ runs: runs.map(serializeRun) });
|
|
9247
|
+
}
|
|
9248
|
+
};
|
|
9249
|
+
}
|
|
9250
|
+
|
|
8573
9251
|
// src/api/cms-api-handler.ts
|
|
8574
9252
|
var KNOWLEDGE_SUFFIX = "knowledge";
|
|
8575
9253
|
var CMS_API_LOG = "[cms-api]";
|
|
@@ -8630,7 +9308,9 @@ var DEFAULT_EXCLUDE = /* @__PURE__ */ new Set([
|
|
|
8630
9308
|
"message_templates",
|
|
8631
9309
|
"llm_agent_knowledge_documents",
|
|
8632
9310
|
"rss_feeds",
|
|
8633
|
-
"rss_articles"
|
|
9311
|
+
"rss_articles",
|
|
9312
|
+
"job_schedules",
|
|
9313
|
+
"job_schedule_runs"
|
|
8634
9314
|
]);
|
|
8635
9315
|
function createCmsApiHandler(config) {
|
|
8636
9316
|
const {
|
|
@@ -8793,6 +9473,15 @@ function createCmsApiHandler(config) {
|
|
|
8793
9473
|
...llmAgentKnowledgeMerged,
|
|
8794
9474
|
requireEntityPermission: requireEntityPermissionEffective
|
|
8795
9475
|
}) : null;
|
|
9476
|
+
const jobScheduleHandlers = getCms ? createJobScheduleHandlers({
|
|
9477
|
+
dataSource,
|
|
9478
|
+
entityMap,
|
|
9479
|
+
json: config.json,
|
|
9480
|
+
requireAuth: config.requireAuth,
|
|
9481
|
+
requireEntityPermission: requireEntityPermissionEffective,
|
|
9482
|
+
getCms,
|
|
9483
|
+
config: typeof process !== "undefined" ? process.env : {}
|
|
9484
|
+
}) : null;
|
|
8796
9485
|
function resolveResource(segment) {
|
|
8797
9486
|
const model = pathToModel(segment);
|
|
8798
9487
|
return crudResources.includes(model) ? model : segment;
|
|
@@ -8823,6 +9512,30 @@ function createCmsApiHandler(config) {
|
|
|
8823
9512
|
if (g) return g;
|
|
8824
9513
|
return ecommerceAnalyticsGet(req);
|
|
8825
9514
|
}
|
|
9515
|
+
if (path2[0] === "job-schedules" && jobScheduleHandlers) {
|
|
9516
|
+
if (path2.length === 2 && m === "GET") {
|
|
9517
|
+
return jobScheduleHandlers.getById(req, path2[1]);
|
|
9518
|
+
}
|
|
9519
|
+
if (path2.length === 2 && m === "PATCH") {
|
|
9520
|
+
return jobScheduleHandlers.update(req, path2[1]);
|
|
9521
|
+
}
|
|
9522
|
+
if (path2.length === 2 && m === "DELETE") {
|
|
9523
|
+
return jobScheduleHandlers.remove(req, path2[1]);
|
|
9524
|
+
}
|
|
9525
|
+
if (path2.length === 3 && path2[2] === "run-now" && m === "POST") {
|
|
9526
|
+
return jobScheduleHandlers.runNow(req, path2[1]);
|
|
9527
|
+
}
|
|
9528
|
+
if (path2.length === 3 && path2[2] === "runs" && m === "GET") {
|
|
9529
|
+
return jobScheduleHandlers.listRuns(req, path2[1]);
|
|
9530
|
+
}
|
|
9531
|
+
if (path2.length === 1 && m === "GET") {
|
|
9532
|
+
return jobScheduleHandlers.list(req);
|
|
9533
|
+
}
|
|
9534
|
+
if (path2.length === 1 && m === "POST") {
|
|
9535
|
+
return jobScheduleHandlers.create(req);
|
|
9536
|
+
}
|
|
9537
|
+
return config.json({ error: "Not found" }, { status: 404 });
|
|
9538
|
+
}
|
|
8826
9539
|
if (path2[0] === "blog-generator" && path2[1] === "feeds" && path2.length === 3 && m === "DELETE" && getCms) {
|
|
8827
9540
|
const id = path2[2];
|
|
8828
9541
|
const uuidLooksValid = typeof id === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(id);
|
|
@@ -9288,6 +10001,9 @@ function createCmsApiHandler(config) {
|
|
|
9288
10001
|
if (tail === "sync-profile" && m === "POST") {
|
|
9289
10002
|
return socialMediaHandlers.syncLinkedInProfile(req);
|
|
9290
10003
|
}
|
|
10004
|
+
if (tail === "fetch-organizations" && m === "POST") {
|
|
10005
|
+
return socialMediaHandlers.fetchLinkedInOrganizations(req);
|
|
10006
|
+
}
|
|
9291
10007
|
if (tail === "publish-blog" && m === "POST") {
|
|
9292
10008
|
let body;
|
|
9293
10009
|
try {
|
|
@@ -9468,7 +10184,7 @@ function createCmsApiHandler(config) {
|
|
|
9468
10184
|
}
|
|
9469
10185
|
|
|
9470
10186
|
// src/api/storefront-handlers.ts
|
|
9471
|
-
import { In as
|
|
10187
|
+
import { In as In4, IsNull as IsNull4 } from "typeorm";
|
|
9472
10188
|
|
|
9473
10189
|
// src/lib/is-valid-signup-email.ts
|
|
9474
10190
|
var MAX_EMAIL = 254;
|
|
@@ -11082,7 +11798,7 @@ function createStorefrontApiHandler(config) {
|
|
|
11082
11798
|
const previewByOrder = {};
|
|
11083
11799
|
if (orderIds.length) {
|
|
11084
11800
|
const oItems = await orderItemRepo().find({
|
|
11085
|
-
where: { orderId:
|
|
11801
|
+
where: { orderId: In4(orderIds) },
|
|
11086
11802
|
relations: ["product"],
|
|
11087
11803
|
order: { id: "ASC" }
|
|
11088
11804
|
});
|
|
@@ -11218,6 +11934,7 @@ export {
|
|
|
11218
11934
|
createForgotPasswordHandler,
|
|
11219
11935
|
createFormBySlugHandler,
|
|
11220
11936
|
createInviteAcceptHandler,
|
|
11937
|
+
createJobScheduleHandlers,
|
|
11221
11938
|
createLlmAgentKnowledgeHandlers,
|
|
11222
11939
|
createMediaZipExtractHandler,
|
|
11223
11940
|
createSetPasswordHandler,
|