@infuro/cms-core 1.0.29 → 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 +1711 -1133
- package/dist/admin.cjs.map +1 -1
- package/dist/admin.js +1712 -1123
- package/dist/admin.js.map +1 -1
- package/dist/api.cjs +174 -19
- package/dist/api.cjs.map +1 -1
- package/dist/api.js +174 -19
- package/dist/api.js.map +1 -1
- package/dist/cli.cjs +5 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +5 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +175 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +175 -19
- package/dist/index.js.map +1 -1
- package/package.json +8 -1
package/dist/index.cjs
CHANGED
|
@@ -4421,6 +4421,24 @@ async function queueJobScheduleNow(cms, scheduleId) {
|
|
|
4421
4421
|
});
|
|
4422
4422
|
}
|
|
4423
4423
|
|
|
4424
|
+
// src/plugins/jobs/schedule-next-run.ts
|
|
4425
|
+
var import_cron_parser = require("cron-parser");
|
|
4426
|
+
function computeNextRunAt(schedule, from = /* @__PURE__ */ new Date()) {
|
|
4427
|
+
if (!schedule.enabled) return null;
|
|
4428
|
+
try {
|
|
4429
|
+
const cron = buildCronFromSchedule(schedule);
|
|
4430
|
+
const tz = schedule.timezone?.trim() || "UTC";
|
|
4431
|
+
const interval = (0, import_cron_parser.parseExpression)(cron, {
|
|
4432
|
+
currentDate: from,
|
|
4433
|
+
tz
|
|
4434
|
+
});
|
|
4435
|
+
return interval.next().toDate();
|
|
4436
|
+
} catch (err) {
|
|
4437
|
+
console.warn("[job-schedules] could not compute next run:", err);
|
|
4438
|
+
return null;
|
|
4439
|
+
}
|
|
4440
|
+
}
|
|
4441
|
+
|
|
4424
4442
|
// src/plugins/jobs/job-runner.ts
|
|
4425
4443
|
var executeJobRef = null;
|
|
4426
4444
|
function registerJobRunnerWorker(cms, deps) {
|
|
@@ -4475,6 +4493,7 @@ function registerJobRunnerWorker(cms, deps) {
|
|
|
4475
4493
|
schedule.lastRunAt = finishedAt;
|
|
4476
4494
|
schedule.lastRunStatus = "success";
|
|
4477
4495
|
schedule.lastRunError = null;
|
|
4496
|
+
schedule.nextRunAt = computeNextRunAt(schedule, finishedAt);
|
|
4478
4497
|
await scheduleRepo.save(schedule);
|
|
4479
4498
|
} catch (e) {
|
|
4480
4499
|
const message = e instanceof Error ? e.message : String(e);
|
|
@@ -4535,7 +4554,7 @@ function serializeSchedule(s) {
|
|
|
4535
4554
|
lastRunAt: s.lastRunAt?.toISOString() ?? null,
|
|
4536
4555
|
lastRunStatus: s.lastRunStatus,
|
|
4537
4556
|
lastRunError: s.lastRunError,
|
|
4538
|
-
nextRunAt: s.nextRunAt?.toISOString() ?? null,
|
|
4557
|
+
nextRunAt: (computeNextRunAt(s) ?? s.nextRunAt)?.toISOString() ?? null,
|
|
4539
4558
|
createdAt: s.createdAt.toISOString(),
|
|
4540
4559
|
updatedAt: s.updatedAt.toISOString()
|
|
4541
4560
|
};
|
|
@@ -4582,6 +4601,9 @@ function createJobScheduleHandlers(apiConfig) {
|
|
|
4582
4601
|
await syncJobScheduleToPgBoss(cms, schedule, async (queueName) => {
|
|
4583
4602
|
await ensureScheduleQueueWorker(cms, queueName);
|
|
4584
4603
|
});
|
|
4604
|
+
const repo = dataSource.getRepository(ScheduleEntity);
|
|
4605
|
+
schedule.nextRunAt = computeNextRunAt(schedule);
|
|
4606
|
+
await repo.save(schedule);
|
|
4585
4607
|
}
|
|
4586
4608
|
return {
|
|
4587
4609
|
async list(req) {
|
|
@@ -7242,6 +7264,13 @@ var import_node_path = __toESM(require("path"), 1);
|
|
|
7242
7264
|
|
|
7243
7265
|
// src/plugins/social-media/linkedin-client.ts
|
|
7244
7266
|
var RESTLI = "2.0.0";
|
|
7267
|
+
function linkedInHeaders(accessToken, json = false) {
|
|
7268
|
+
return {
|
|
7269
|
+
Authorization: `Bearer ${accessToken}`,
|
|
7270
|
+
"X-Restli-Protocol-Version": RESTLI,
|
|
7271
|
+
...json ? { "Content-Type": "application/json" } : {}
|
|
7272
|
+
};
|
|
7273
|
+
}
|
|
7245
7274
|
async function linkedInGetUserinfo(accessToken) {
|
|
7246
7275
|
const r = await fetch("https://api.linkedin.com/v2/userinfo", {
|
|
7247
7276
|
headers: { Authorization: `Bearer ${accessToken}` }
|
|
@@ -7256,20 +7285,116 @@ async function linkedInGetUserinfo(accessToken) {
|
|
|
7256
7285
|
throw new Error("LinkedIn userinfo: invalid JSON");
|
|
7257
7286
|
}
|
|
7258
7287
|
}
|
|
7288
|
+
async function linkedInFetchOrganizationAcls(accessToken) {
|
|
7289
|
+
const r = await fetch("https://api.linkedin.com/v2/organizationAcls?q=roleAssignee", {
|
|
7290
|
+
headers: linkedInHeaders(accessToken)
|
|
7291
|
+
});
|
|
7292
|
+
const text = await r.text();
|
|
7293
|
+
if (!r.ok) {
|
|
7294
|
+
throw new Error(`LinkedIn organizationAcls failed (${r.status}): ${text.slice(0, 800)}`);
|
|
7295
|
+
}
|
|
7296
|
+
let data;
|
|
7297
|
+
try {
|
|
7298
|
+
data = JSON.parse(text);
|
|
7299
|
+
} catch {
|
|
7300
|
+
throw new Error("LinkedIn organizationAcls: invalid JSON");
|
|
7301
|
+
}
|
|
7302
|
+
return Array.isArray(data.elements) ? data.elements : [];
|
|
7303
|
+
}
|
|
7304
|
+
function organizationDisplayName(org) {
|
|
7305
|
+
if (org.localizedName?.trim()) return org.localizedName.trim();
|
|
7306
|
+
const localized = org.name?.localized;
|
|
7307
|
+
if (localized) {
|
|
7308
|
+
for (const v of Object.values(localized)) {
|
|
7309
|
+
if (v?.trim()) return v.trim();
|
|
7310
|
+
}
|
|
7311
|
+
}
|
|
7312
|
+
if (org.vanityName?.trim()) return org.vanityName.trim();
|
|
7313
|
+
if (org.id != null) return `Organization ${org.id}`;
|
|
7314
|
+
return "Organization";
|
|
7315
|
+
}
|
|
7316
|
+
function organizationWebsite(org) {
|
|
7317
|
+
if (org.localizedWebsite?.trim()) return org.localizedWebsite.trim();
|
|
7318
|
+
const localized = org.website?.localized;
|
|
7319
|
+
if (localized) {
|
|
7320
|
+
for (const v of Object.values(localized)) {
|
|
7321
|
+
if (v?.trim()) return v.trim();
|
|
7322
|
+
}
|
|
7323
|
+
}
|
|
7324
|
+
return void 0;
|
|
7325
|
+
}
|
|
7326
|
+
function linkedInOrganizationUrn(orgIdOrUrn) {
|
|
7327
|
+
const s = String(orgIdOrUrn ?? "").trim();
|
|
7328
|
+
if (s.startsWith("urn:li:organization:")) return s;
|
|
7329
|
+
return `urn:li:organization:${s}`;
|
|
7330
|
+
}
|
|
7331
|
+
function linkedInOrganizationIdFromUrn(organizationUrn) {
|
|
7332
|
+
const m = String(organizationUrn).trim().match(/urn:li:organization:(\d+)/);
|
|
7333
|
+
if (!m?.[1]) throw new Error(`Invalid LinkedIn organization URN: ${organizationUrn}`);
|
|
7334
|
+
return m[1];
|
|
7335
|
+
}
|
|
7336
|
+
async function linkedInGetOrganization(accessToken, orgIdOrUrn) {
|
|
7337
|
+
const orgId = String(orgIdOrUrn).startsWith("urn:") ? linkedInOrganizationIdFromUrn(String(orgIdOrUrn)) : String(orgIdOrUrn).trim();
|
|
7338
|
+
const r = await fetch(`https://api.linkedin.com/v2/organizations/${encodeURIComponent(orgId)}`, {
|
|
7339
|
+
headers: linkedInHeaders(accessToken)
|
|
7340
|
+
});
|
|
7341
|
+
const text = await r.text();
|
|
7342
|
+
if (!r.ok) {
|
|
7343
|
+
throw new Error(`LinkedIn organization ${orgId} failed (${r.status}): ${text.slice(0, 800)}`);
|
|
7344
|
+
}
|
|
7345
|
+
try {
|
|
7346
|
+
return JSON.parse(text);
|
|
7347
|
+
} catch {
|
|
7348
|
+
throw new Error(`LinkedIn organization ${orgId}: invalid JSON`);
|
|
7349
|
+
}
|
|
7350
|
+
}
|
|
7351
|
+
async function linkedInListManagedOrganizations(accessToken) {
|
|
7352
|
+
const acls = await linkedInFetchOrganizationAcls(accessToken);
|
|
7353
|
+
const approved = acls.filter((a) => a.state === "APPROVED" && a.organization?.trim());
|
|
7354
|
+
const byUrn = /* @__PURE__ */ new Map();
|
|
7355
|
+
for (const acl of approved) {
|
|
7356
|
+
const urn = linkedInOrganizationUrn(acl.organization);
|
|
7357
|
+
if (!byUrn.has(urn)) byUrn.set(urn, acl);
|
|
7358
|
+
}
|
|
7359
|
+
const out = [];
|
|
7360
|
+
for (const [urn, acl] of byUrn) {
|
|
7361
|
+
try {
|
|
7362
|
+
const org = await linkedInGetOrganization(accessToken, urn);
|
|
7363
|
+
out.push({
|
|
7364
|
+
urn,
|
|
7365
|
+
id: org.id ?? Number(linkedInOrganizationIdFromUrn(urn)),
|
|
7366
|
+
name: organizationDisplayName(org),
|
|
7367
|
+
vanityName: org.vanityName,
|
|
7368
|
+
website: organizationWebsite(org),
|
|
7369
|
+
role: acl.role
|
|
7370
|
+
});
|
|
7371
|
+
} catch {
|
|
7372
|
+
out.push({
|
|
7373
|
+
urn,
|
|
7374
|
+
id: Number(linkedInOrganizationIdFromUrn(urn)),
|
|
7375
|
+
name: linkedInOrganizationIdFromUrn(urn),
|
|
7376
|
+
role: acl.role
|
|
7377
|
+
});
|
|
7378
|
+
}
|
|
7379
|
+
}
|
|
7380
|
+
out.sort((a, b) => a.name.localeCompare(b.name));
|
|
7381
|
+
return out;
|
|
7382
|
+
}
|
|
7259
7383
|
function personUrn(personSub) {
|
|
7260
7384
|
const s = String(personSub || "").trim();
|
|
7261
7385
|
if (s.startsWith("urn:li:person:")) return s;
|
|
7262
7386
|
return `urn:li:person:${s}`;
|
|
7263
7387
|
}
|
|
7264
|
-
|
|
7265
|
-
const
|
|
7388
|
+
function authorUrnFromParams(authorUrn, personSub) {
|
|
7389
|
+
const direct = String(authorUrn ?? "").trim();
|
|
7390
|
+
if (direct.startsWith("urn:li:")) return direct;
|
|
7391
|
+
return personUrn(String(personSub ?? "").trim());
|
|
7392
|
+
}
|
|
7393
|
+
async function linkedInRegisterImageUpload(accessToken, author) {
|
|
7394
|
+
const owner = authorUrnFromParams(author);
|
|
7266
7395
|
const r = await fetch("https://api.linkedin.com/v2/assets?action=registerUpload", {
|
|
7267
7396
|
method: "POST",
|
|
7268
|
-
headers:
|
|
7269
|
-
Authorization: `Bearer ${accessToken}`,
|
|
7270
|
-
"X-Restli-Protocol-Version": RESTLI,
|
|
7271
|
-
"Content-Type": "application/json"
|
|
7272
|
-
},
|
|
7397
|
+
headers: linkedInHeaders(accessToken, true),
|
|
7273
7398
|
body: JSON.stringify({
|
|
7274
7399
|
registerUploadRequest: {
|
|
7275
7400
|
recipes: ["urn:li:digitalmediaRecipe:feedshare-image"],
|
|
@@ -7317,7 +7442,7 @@ async function linkedInUploadBinary(accessToken, uploadUrl, body, contentType) {
|
|
|
7317
7442
|
}
|
|
7318
7443
|
}
|
|
7319
7444
|
async function linkedInCreateImageShare(params) {
|
|
7320
|
-
const author =
|
|
7445
|
+
const author = authorUrnFromParams(params.authorUrn);
|
|
7321
7446
|
const commentary = params.commentary.slice(0, 2900);
|
|
7322
7447
|
const title = params.title.slice(0, 200);
|
|
7323
7448
|
const description = (params.description ?? title).slice(0, 200);
|
|
@@ -7363,7 +7488,7 @@ async function linkedInCreateImageShare(params) {
|
|
|
7363
7488
|
return { status: r.status, id: data.id };
|
|
7364
7489
|
}
|
|
7365
7490
|
async function linkedInCreateTextShare(params) {
|
|
7366
|
-
const author =
|
|
7491
|
+
const author = authorUrnFromParams(params.authorUrn);
|
|
7367
7492
|
const commentary = params.commentary.slice(0, 2900);
|
|
7368
7493
|
const r = await fetch("https://api.linkedin.com/v2/ugcPosts", {
|
|
7369
7494
|
method: "POST",
|
|
@@ -7697,18 +7822,44 @@ function createSocialMediaHandlers(config) {
|
|
|
7697
7822
|
const enabled = map.enabled !== "false";
|
|
7698
7823
|
const token = (map.linkedin_access_token ?? "").trim();
|
|
7699
7824
|
const sub = (map.linkedin_person_sub ?? "").trim();
|
|
7825
|
+
const orgUrn = (map.linkedin_organization_urn ?? "").trim();
|
|
7700
7826
|
return json({
|
|
7701
7827
|
ok: true,
|
|
7702
|
-
linkedInReady: enabled && Boolean(token) && Boolean(
|
|
7828
|
+
linkedInReady: enabled && Boolean(token) && Boolean(orgUrn),
|
|
7703
7829
|
enabled,
|
|
7704
7830
|
hasToken: Boolean(token),
|
|
7705
|
-
hasPersonSub: Boolean(sub)
|
|
7831
|
+
hasPersonSub: Boolean(sub),
|
|
7832
|
+
hasOrganization: Boolean(orgUrn),
|
|
7833
|
+
organizationName: map.linkedin_organization_name?.trim() || null
|
|
7706
7834
|
});
|
|
7707
7835
|
} catch (e) {
|
|
7708
7836
|
const msg = e instanceof Error ? e.message : "Failed to load status";
|
|
7709
7837
|
return json({ error: msg }, { status: 500 });
|
|
7710
7838
|
}
|
|
7711
7839
|
},
|
|
7840
|
+
async fetchLinkedInOrganizations(req) {
|
|
7841
|
+
const a = await requireAuth(req);
|
|
7842
|
+
if (a) return a;
|
|
7843
|
+
const pe = await requireEntityPermission(req, "settings", "read");
|
|
7844
|
+
if (pe) return pe;
|
|
7845
|
+
let body = {};
|
|
7846
|
+
try {
|
|
7847
|
+
body = await req.json();
|
|
7848
|
+
} catch {
|
|
7849
|
+
}
|
|
7850
|
+
try {
|
|
7851
|
+
const map = await loadGroupMap(dataSource, entityMap, encryptionKey);
|
|
7852
|
+
const token = String(body?.accessToken ?? map.linkedin_access_token ?? "").trim();
|
|
7853
|
+
if (!token) {
|
|
7854
|
+
return json({ error: "LinkedIn access token is required." }, { status: 400 });
|
|
7855
|
+
}
|
|
7856
|
+
const organizations = await linkedInListManagedOrganizations(token);
|
|
7857
|
+
return json({ ok: true, organizations });
|
|
7858
|
+
} catch (e) {
|
|
7859
|
+
const msg = e instanceof Error ? e.message : "LinkedIn organizations request failed";
|
|
7860
|
+
return json({ error: msg }, { status: 502 });
|
|
7861
|
+
}
|
|
7862
|
+
},
|
|
7712
7863
|
async syncLinkedInProfile(req) {
|
|
7713
7864
|
const a = await requireAuth(req);
|
|
7714
7865
|
if (a) return a;
|
|
@@ -7757,10 +7908,12 @@ function createSocialMediaHandlers(config) {
|
|
|
7757
7908
|
return json({ error: "Social media plugin is disabled." }, { status: 400 });
|
|
7758
7909
|
}
|
|
7759
7910
|
const token = (map.linkedin_access_token ?? "").trim();
|
|
7760
|
-
const
|
|
7761
|
-
if (!token || !
|
|
7911
|
+
const authorUrn = (map.linkedin_organization_urn ?? "").trim();
|
|
7912
|
+
if (!token || !authorUrn) {
|
|
7762
7913
|
return json(
|
|
7763
|
-
{
|
|
7914
|
+
{
|
|
7915
|
+
error: "Configure LinkedIn access token and target organization (Plugins \u2192 Social media \u2192 LinkedIn)."
|
|
7916
|
+
},
|
|
7764
7917
|
{ status: 400 }
|
|
7765
7918
|
);
|
|
7766
7919
|
}
|
|
@@ -7801,7 +7954,7 @@ ${excerpt}`).trim().slice(0, 2900);
|
|
|
7801
7954
|
contentType: imagePayload.contentType
|
|
7802
7955
|
});
|
|
7803
7956
|
try {
|
|
7804
|
-
const { uploadUrl, asset } = await linkedInRegisterImageUpload(token,
|
|
7957
|
+
const { uploadUrl, asset } = await linkedInRegisterImageUpload(token, authorUrn);
|
|
7805
7958
|
logLinkedInPublish(publishLog, "image_registered", {
|
|
7806
7959
|
blogId,
|
|
7807
7960
|
asset: truncateForLog(asset, 120),
|
|
@@ -7817,7 +7970,7 @@ ${excerpt}`).trim().slice(0, 2900);
|
|
|
7817
7970
|
logLinkedInPublish(publishLog, "image_uploaded", { blogId, bytes: imagePayload.buffer.length });
|
|
7818
7971
|
const out2 = await linkedInCreateImageShare({
|
|
7819
7972
|
accessToken: token,
|
|
7820
|
-
|
|
7973
|
+
authorUrn,
|
|
7821
7974
|
asset,
|
|
7822
7975
|
commentary,
|
|
7823
7976
|
title,
|
|
@@ -7855,7 +8008,7 @@ ${excerpt}`).trim().slice(0, 2900);
|
|
|
7855
8008
|
});
|
|
7856
8009
|
const out2 = await linkedInCreateTextShare({
|
|
7857
8010
|
accessToken: token,
|
|
7858
|
-
|
|
8011
|
+
authorUrn,
|
|
7859
8012
|
commentary
|
|
7860
8013
|
});
|
|
7861
8014
|
logLinkedInPublish(publishLog, "success", {
|
|
@@ -7894,7 +8047,7 @@ ${excerpt}`).trim().slice(0, 2900);
|
|
|
7894
8047
|
});
|
|
7895
8048
|
const out = await linkedInCreateTextShare({
|
|
7896
8049
|
accessToken: token,
|
|
7897
|
-
|
|
8050
|
+
authorUrn,
|
|
7898
8051
|
commentary
|
|
7899
8052
|
});
|
|
7900
8053
|
logLinkedInPublish(publishLog, "success", {
|
|
@@ -14662,6 +14815,9 @@ function createCmsApiHandler(config) {
|
|
|
14662
14815
|
if (tail === "sync-profile" && m === "POST") {
|
|
14663
14816
|
return socialMediaHandlers.syncLinkedInProfile(req);
|
|
14664
14817
|
}
|
|
14818
|
+
if (tail === "fetch-organizations" && m === "POST") {
|
|
14819
|
+
return socialMediaHandlers.fetchLinkedInOrganizations(req);
|
|
14820
|
+
}
|
|
14665
14821
|
if (tail === "publish-blog" && m === "POST") {
|
|
14666
14822
|
let body;
|
|
14667
14823
|
try {
|