@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/api.cjs CHANGED
@@ -213,6 +213,81 @@ var init_email_queue = __esm({
213
213
  }
214
214
  });
215
215
 
216
+ // src/plugins/pg-boss/pg-boss-service.ts
217
+ var pg_boss_service_exports = {};
218
+ __export(pg_boss_service_exports, {
219
+ JOB_RUNNER_QUEUE: () => JOB_RUNNER_QUEUE,
220
+ PgBossService: () => PgBossService
221
+ });
222
+ var import_pg_boss, JOB_RUNNER_QUEUE, PgBossService;
223
+ var init_pg_boss_service = __esm({
224
+ "src/plugins/pg-boss/pg-boss-service.ts"() {
225
+ "use strict";
226
+ import_pg_boss = __toESM(require("pg-boss"), 1);
227
+ JOB_RUNNER_QUEUE = "job-runner";
228
+ PgBossService = class {
229
+ boss;
230
+ started = false;
231
+ workRegistered = /* @__PURE__ */ new Set();
232
+ constructor(connectionString, schema) {
233
+ this.boss = new import_pg_boss.default({
234
+ connectionString,
235
+ ...schema?.trim() ? { schema: schema.trim() } : {},
236
+ schedule: true
237
+ });
238
+ this.boss.on("error", (err) => {
239
+ console.error("[pg-boss]", err);
240
+ });
241
+ }
242
+ async start() {
243
+ if (this.started) return;
244
+ await this.boss.start();
245
+ await this.ensureQueue(JOB_RUNNER_QUEUE);
246
+ this.started = true;
247
+ }
248
+ async stop() {
249
+ if (!this.started) return;
250
+ await this.boss.stop({ graceful: true, timeout: 1e4 });
251
+ this.started = false;
252
+ this.workRegistered.clear();
253
+ }
254
+ get raw() {
255
+ return this.boss;
256
+ }
257
+ async ensureQueue(name) {
258
+ const existing = await this.boss.getQueue(name);
259
+ if (!existing) {
260
+ await this.boss.createQueue(name);
261
+ }
262
+ }
263
+ async send(queue, data) {
264
+ await this.ensureQueue(queue);
265
+ return this.boss.send(queue, data, { retryLimit: 2 });
266
+ }
267
+ async schedule(name, cron, data, options) {
268
+ await this.ensureQueue(name);
269
+ await this.boss.schedule(name, cron, data, options);
270
+ }
271
+ async unschedule(name) {
272
+ try {
273
+ await this.boss.unschedule(name);
274
+ } catch {
275
+ }
276
+ }
277
+ async registerWork(queue, handler) {
278
+ if (this.workRegistered.has(queue)) return;
279
+ await this.ensureQueue(queue);
280
+ await this.boss.work(queue, async (jobs) => {
281
+ for (const job of jobs) {
282
+ await handler(job.data);
283
+ }
284
+ });
285
+ this.workRegistered.add(queue);
286
+ }
287
+ };
288
+ }
289
+ });
290
+
216
291
  // src/plugins/erp/erp-response-map.ts
217
292
  function pickString(o, keys) {
218
293
  for (const k of keys) {
@@ -508,6 +583,7 @@ __export(api_exports, {
508
583
  createForgotPasswordHandler: () => createForgotPasswordHandler,
509
584
  createFormBySlugHandler: () => createFormBySlugHandler,
510
585
  createInviteAcceptHandler: () => createInviteAcceptHandler,
586
+ createJobScheduleHandlers: () => createJobScheduleHandlers,
511
587
  createLlmAgentKnowledgeHandlers: () => createLlmAgentKnowledgeHandlers,
512
588
  createMediaZipExtractHandler: () => createMediaZipExtractHandler,
513
589
  createSetPasswordHandler: () => createSetPasswordHandler,
@@ -7355,6 +7431,145 @@ RssFeed = __decorateClass([
7355
7431
  (0, import_typeorm47.Entity)("rss_feeds")
7356
7432
  ], RssFeed);
7357
7433
 
7434
+ // src/entities/job-schedule.entity.ts
7435
+ var import_typeorm49 = require("typeorm");
7436
+
7437
+ // src/entities/job-schedule-run.entity.ts
7438
+ var import_typeorm48 = require("typeorm");
7439
+ var JobScheduleRun = class {
7440
+ id;
7441
+ scheduleId;
7442
+ schedule;
7443
+ startedAt;
7444
+ finishedAt;
7445
+ status;
7446
+ error;
7447
+ result;
7448
+ triggeredBy;
7449
+ createdAt;
7450
+ };
7451
+ __decorateClass([
7452
+ (0, import_typeorm48.PrimaryGeneratedColumn)("uuid")
7453
+ ], JobScheduleRun.prototype, "id", 2);
7454
+ __decorateClass([
7455
+ (0, import_typeorm48.Column)({ type: "uuid" })
7456
+ ], JobScheduleRun.prototype, "scheduleId", 2);
7457
+ __decorateClass([
7458
+ (0, import_typeorm48.ManyToOne)(() => JobSchedule, (s) => s.runs, { onDelete: "CASCADE" }),
7459
+ (0, import_typeorm48.JoinColumn)({ name: "scheduleId" })
7460
+ ], JobScheduleRun.prototype, "schedule", 2);
7461
+ __decorateClass([
7462
+ (0, import_typeorm48.Column)({ type: "timestamp" })
7463
+ ], JobScheduleRun.prototype, "startedAt", 2);
7464
+ __decorateClass([
7465
+ (0, import_typeorm48.Column)({ type: "timestamp", nullable: true })
7466
+ ], JobScheduleRun.prototype, "finishedAt", 2);
7467
+ __decorateClass([
7468
+ (0, import_typeorm48.Column)({ type: "varchar", length: 32 })
7469
+ ], JobScheduleRun.prototype, "status", 2);
7470
+ __decorateClass([
7471
+ (0, import_typeorm48.Column)({ type: "text", nullable: true })
7472
+ ], JobScheduleRun.prototype, "error", 2);
7473
+ __decorateClass([
7474
+ (0, import_typeorm48.Column)({ type: "jsonb", nullable: true })
7475
+ ], JobScheduleRun.prototype, "result", 2);
7476
+ __decorateClass([
7477
+ (0, import_typeorm48.Column)({ type: "varchar", length: 16, default: "schedule" })
7478
+ ], JobScheduleRun.prototype, "triggeredBy", 2);
7479
+ __decorateClass([
7480
+ (0, import_typeorm48.CreateDateColumn)()
7481
+ ], JobScheduleRun.prototype, "createdAt", 2);
7482
+ JobScheduleRun = __decorateClass([
7483
+ (0, import_typeorm48.Entity)("job_schedule_runs")
7484
+ ], JobScheduleRun);
7485
+
7486
+ // src/entities/job-schedule.entity.ts
7487
+ var JobSchedule = class {
7488
+ id;
7489
+ name;
7490
+ jobType;
7491
+ enabled;
7492
+ scheduleMode;
7493
+ intervalMinutes;
7494
+ runAtTime;
7495
+ runOnDays;
7496
+ timezone;
7497
+ cronExpression;
7498
+ payload;
7499
+ authorId;
7500
+ pgBossScheduleName;
7501
+ lastRunAt;
7502
+ lastRunStatus;
7503
+ lastRunError;
7504
+ nextRunAt;
7505
+ runs;
7506
+ createdAt;
7507
+ updatedAt;
7508
+ };
7509
+ __decorateClass([
7510
+ (0, import_typeorm49.PrimaryGeneratedColumn)("uuid")
7511
+ ], JobSchedule.prototype, "id", 2);
7512
+ __decorateClass([
7513
+ (0, import_typeorm49.Column)({ type: "text" })
7514
+ ], JobSchedule.prototype, "name", 2);
7515
+ __decorateClass([
7516
+ (0, import_typeorm49.Column)({ type: "varchar", length: 64, default: "blog_generate" })
7517
+ ], JobSchedule.prototype, "jobType", 2);
7518
+ __decorateClass([
7519
+ (0, import_typeorm49.Column)({ type: "boolean", default: false })
7520
+ ], JobSchedule.prototype, "enabled", 2);
7521
+ __decorateClass([
7522
+ (0, import_typeorm49.Column)({ type: "varchar", length: 32, default: "daily" })
7523
+ ], JobSchedule.prototype, "scheduleMode", 2);
7524
+ __decorateClass([
7525
+ (0, import_typeorm49.Column)({ type: "int", nullable: true })
7526
+ ], JobSchedule.prototype, "intervalMinutes", 2);
7527
+ __decorateClass([
7528
+ (0, import_typeorm49.Column)({ type: "varchar", length: 8, nullable: true })
7529
+ ], JobSchedule.prototype, "runAtTime", 2);
7530
+ __decorateClass([
7531
+ (0, import_typeorm49.Column)({ type: "jsonb", nullable: true })
7532
+ ], JobSchedule.prototype, "runOnDays", 2);
7533
+ __decorateClass([
7534
+ (0, import_typeorm49.Column)({ type: "varchar", length: 64, default: "UTC" })
7535
+ ], JobSchedule.prototype, "timezone", 2);
7536
+ __decorateClass([
7537
+ (0, import_typeorm49.Column)({ type: "text", nullable: true })
7538
+ ], JobSchedule.prototype, "cronExpression", 2);
7539
+ __decorateClass([
7540
+ (0, import_typeorm49.Column)({ type: "jsonb", default: {} })
7541
+ ], JobSchedule.prototype, "payload", 2);
7542
+ __decorateClass([
7543
+ (0, import_typeorm49.Column)({ type: "int", nullable: true })
7544
+ ], JobSchedule.prototype, "authorId", 2);
7545
+ __decorateClass([
7546
+ (0, import_typeorm49.Column)({ type: "varchar", length: 128, unique: true })
7547
+ ], JobSchedule.prototype, "pgBossScheduleName", 2);
7548
+ __decorateClass([
7549
+ (0, import_typeorm49.Column)({ type: "timestamp", nullable: true })
7550
+ ], JobSchedule.prototype, "lastRunAt", 2);
7551
+ __decorateClass([
7552
+ (0, import_typeorm49.Column)({ type: "varchar", length: 32, nullable: true })
7553
+ ], JobSchedule.prototype, "lastRunStatus", 2);
7554
+ __decorateClass([
7555
+ (0, import_typeorm49.Column)({ type: "text", nullable: true })
7556
+ ], JobSchedule.prototype, "lastRunError", 2);
7557
+ __decorateClass([
7558
+ (0, import_typeorm49.Column)({ type: "timestamp", nullable: true })
7559
+ ], JobSchedule.prototype, "nextRunAt", 2);
7560
+ __decorateClass([
7561
+ (0, import_typeorm49.OneToMany)(() => JobScheduleRun, (r) => r.schedule)
7562
+ ], JobSchedule.prototype, "runs", 2);
7563
+ __decorateClass([
7564
+ (0, import_typeorm49.CreateDateColumn)()
7565
+ ], JobSchedule.prototype, "createdAt", 2);
7566
+ __decorateClass([
7567
+ (0, import_typeorm49.UpdateDateColumn)()
7568
+ ], JobSchedule.prototype, "updatedAt", 2);
7569
+ JobSchedule = __decorateClass([
7570
+ (0, import_typeorm49.Entity)("job_schedules")
7571
+ ], JobSchedule);
7572
+
7358
7573
  // src/entities/index.ts
7359
7574
  var CMS_ENTITY_MAP = {
7360
7575
  users: User,
@@ -7398,7 +7613,9 @@ var CMS_ENTITY_MAP = {
7398
7613
  llm_agents: LlmAgent,
7399
7614
  llm_agent_knowledge_documents: LlmAgentKnowledgeDocument,
7400
7615
  rss_feeds: RssFeed,
7401
- rss_articles: RssArticle
7616
+ rss_articles: RssArticle,
7617
+ job_schedules: JobSchedule,
7618
+ job_schedule_runs: JobScheduleRun
7402
7619
  };
7403
7620
 
7404
7621
  // src/plugins/blog-generator/blog-generator-service.ts
@@ -7746,6 +7963,13 @@ var import_node_path = __toESM(require("path"), 1);
7746
7963
 
7747
7964
  // src/plugins/social-media/linkedin-client.ts
7748
7965
  var RESTLI = "2.0.0";
7966
+ function linkedInHeaders(accessToken, json = false) {
7967
+ return {
7968
+ Authorization: `Bearer ${accessToken}`,
7969
+ "X-Restli-Protocol-Version": RESTLI,
7970
+ ...json ? { "Content-Type": "application/json" } : {}
7971
+ };
7972
+ }
7749
7973
  async function linkedInGetUserinfo(accessToken) {
7750
7974
  const r = await fetch("https://api.linkedin.com/v2/userinfo", {
7751
7975
  headers: { Authorization: `Bearer ${accessToken}` }
@@ -7760,20 +7984,116 @@ async function linkedInGetUserinfo(accessToken) {
7760
7984
  throw new Error("LinkedIn userinfo: invalid JSON");
7761
7985
  }
7762
7986
  }
7987
+ async function linkedInFetchOrganizationAcls(accessToken) {
7988
+ const r = await fetch("https://api.linkedin.com/v2/organizationAcls?q=roleAssignee", {
7989
+ headers: linkedInHeaders(accessToken)
7990
+ });
7991
+ const text = await r.text();
7992
+ if (!r.ok) {
7993
+ throw new Error(`LinkedIn organizationAcls failed (${r.status}): ${text.slice(0, 800)}`);
7994
+ }
7995
+ let data;
7996
+ try {
7997
+ data = JSON.parse(text);
7998
+ } catch {
7999
+ throw new Error("LinkedIn organizationAcls: invalid JSON");
8000
+ }
8001
+ return Array.isArray(data.elements) ? data.elements : [];
8002
+ }
8003
+ function organizationDisplayName(org) {
8004
+ if (org.localizedName?.trim()) return org.localizedName.trim();
8005
+ const localized = org.name?.localized;
8006
+ if (localized) {
8007
+ for (const v of Object.values(localized)) {
8008
+ if (v?.trim()) return v.trim();
8009
+ }
8010
+ }
8011
+ if (org.vanityName?.trim()) return org.vanityName.trim();
8012
+ if (org.id != null) return `Organization ${org.id}`;
8013
+ return "Organization";
8014
+ }
8015
+ function organizationWebsite(org) {
8016
+ if (org.localizedWebsite?.trim()) return org.localizedWebsite.trim();
8017
+ const localized = org.website?.localized;
8018
+ if (localized) {
8019
+ for (const v of Object.values(localized)) {
8020
+ if (v?.trim()) return v.trim();
8021
+ }
8022
+ }
8023
+ return void 0;
8024
+ }
8025
+ function linkedInOrganizationUrn(orgIdOrUrn) {
8026
+ const s = String(orgIdOrUrn ?? "").trim();
8027
+ if (s.startsWith("urn:li:organization:")) return s;
8028
+ return `urn:li:organization:${s}`;
8029
+ }
8030
+ function linkedInOrganizationIdFromUrn(organizationUrn) {
8031
+ const m = String(organizationUrn).trim().match(/urn:li:organization:(\d+)/);
8032
+ if (!m?.[1]) throw new Error(`Invalid LinkedIn organization URN: ${organizationUrn}`);
8033
+ return m[1];
8034
+ }
8035
+ async function linkedInGetOrganization(accessToken, orgIdOrUrn) {
8036
+ const orgId = String(orgIdOrUrn).startsWith("urn:") ? linkedInOrganizationIdFromUrn(String(orgIdOrUrn)) : String(orgIdOrUrn).trim();
8037
+ const r = await fetch(`https://api.linkedin.com/v2/organizations/${encodeURIComponent(orgId)}`, {
8038
+ headers: linkedInHeaders(accessToken)
8039
+ });
8040
+ const text = await r.text();
8041
+ if (!r.ok) {
8042
+ throw new Error(`LinkedIn organization ${orgId} failed (${r.status}): ${text.slice(0, 800)}`);
8043
+ }
8044
+ try {
8045
+ return JSON.parse(text);
8046
+ } catch {
8047
+ throw new Error(`LinkedIn organization ${orgId}: invalid JSON`);
8048
+ }
8049
+ }
8050
+ async function linkedInListManagedOrganizations(accessToken) {
8051
+ const acls = await linkedInFetchOrganizationAcls(accessToken);
8052
+ const approved = acls.filter((a) => a.state === "APPROVED" && a.organization?.trim());
8053
+ const byUrn = /* @__PURE__ */ new Map();
8054
+ for (const acl of approved) {
8055
+ const urn = linkedInOrganizationUrn(acl.organization);
8056
+ if (!byUrn.has(urn)) byUrn.set(urn, acl);
8057
+ }
8058
+ const out = [];
8059
+ for (const [urn, acl] of byUrn) {
8060
+ try {
8061
+ const org = await linkedInGetOrganization(accessToken, urn);
8062
+ out.push({
8063
+ urn,
8064
+ id: org.id ?? Number(linkedInOrganizationIdFromUrn(urn)),
8065
+ name: organizationDisplayName(org),
8066
+ vanityName: org.vanityName,
8067
+ website: organizationWebsite(org),
8068
+ role: acl.role
8069
+ });
8070
+ } catch {
8071
+ out.push({
8072
+ urn,
8073
+ id: Number(linkedInOrganizationIdFromUrn(urn)),
8074
+ name: linkedInOrganizationIdFromUrn(urn),
8075
+ role: acl.role
8076
+ });
8077
+ }
8078
+ }
8079
+ out.sort((a, b) => a.name.localeCompare(b.name));
8080
+ return out;
8081
+ }
7763
8082
  function personUrn(personSub) {
7764
8083
  const s = String(personSub || "").trim();
7765
8084
  if (s.startsWith("urn:li:person:")) return s;
7766
8085
  return `urn:li:person:${s}`;
7767
8086
  }
7768
- async function linkedInRegisterImageUpload(accessToken, personSub) {
7769
- const owner = personUrn(personSub);
8087
+ function authorUrnFromParams(authorUrn, personSub) {
8088
+ const direct = String(authorUrn ?? "").trim();
8089
+ if (direct.startsWith("urn:li:")) return direct;
8090
+ return personUrn(String(personSub ?? "").trim());
8091
+ }
8092
+ async function linkedInRegisterImageUpload(accessToken, author) {
8093
+ const owner = authorUrnFromParams(author);
7770
8094
  const r = await fetch("https://api.linkedin.com/v2/assets?action=registerUpload", {
7771
8095
  method: "POST",
7772
- headers: {
7773
- Authorization: `Bearer ${accessToken}`,
7774
- "X-Restli-Protocol-Version": RESTLI,
7775
- "Content-Type": "application/json"
7776
- },
8096
+ headers: linkedInHeaders(accessToken, true),
7777
8097
  body: JSON.stringify({
7778
8098
  registerUploadRequest: {
7779
8099
  recipes: ["urn:li:digitalmediaRecipe:feedshare-image"],
@@ -7821,7 +8141,7 @@ async function linkedInUploadBinary(accessToken, uploadUrl, body, contentType) {
7821
8141
  }
7822
8142
  }
7823
8143
  async function linkedInCreateImageShare(params) {
7824
- const author = personUrn(params.personSub);
8144
+ const author = authorUrnFromParams(params.authorUrn);
7825
8145
  const commentary = params.commentary.slice(0, 2900);
7826
8146
  const title = params.title.slice(0, 200);
7827
8147
  const description = (params.description ?? title).slice(0, 200);
@@ -7867,7 +8187,7 @@ async function linkedInCreateImageShare(params) {
7867
8187
  return { status: r.status, id: data.id };
7868
8188
  }
7869
8189
  async function linkedInCreateTextShare(params) {
7870
- const author = personUrn(params.personSub);
8190
+ const author = authorUrnFromParams(params.authorUrn);
7871
8191
  const commentary = params.commentary.slice(0, 2900);
7872
8192
  const r = await fetch("https://api.linkedin.com/v2/ugcPosts", {
7873
8193
  method: "POST",
@@ -8201,18 +8521,44 @@ function createSocialMediaHandlers(config) {
8201
8521
  const enabled = map.enabled !== "false";
8202
8522
  const token = (map.linkedin_access_token ?? "").trim();
8203
8523
  const sub = (map.linkedin_person_sub ?? "").trim();
8524
+ const orgUrn = (map.linkedin_organization_urn ?? "").trim();
8204
8525
  return json({
8205
8526
  ok: true,
8206
- linkedInReady: enabled && Boolean(token) && Boolean(sub),
8527
+ linkedInReady: enabled && Boolean(token) && Boolean(orgUrn),
8207
8528
  enabled,
8208
8529
  hasToken: Boolean(token),
8209
- hasPersonSub: Boolean(sub)
8530
+ hasPersonSub: Boolean(sub),
8531
+ hasOrganization: Boolean(orgUrn),
8532
+ organizationName: map.linkedin_organization_name?.trim() || null
8210
8533
  });
8211
8534
  } catch (e) {
8212
8535
  const msg = e instanceof Error ? e.message : "Failed to load status";
8213
8536
  return json({ error: msg }, { status: 500 });
8214
8537
  }
8215
8538
  },
8539
+ async fetchLinkedInOrganizations(req) {
8540
+ const a = await requireAuth(req);
8541
+ if (a) return a;
8542
+ const pe = await requireEntityPermission(req, "settings", "read");
8543
+ if (pe) return pe;
8544
+ let body = {};
8545
+ try {
8546
+ body = await req.json();
8547
+ } catch {
8548
+ }
8549
+ try {
8550
+ const map = await loadGroupMap(dataSource, entityMap, encryptionKey);
8551
+ const token = String(body?.accessToken ?? map.linkedin_access_token ?? "").trim();
8552
+ if (!token) {
8553
+ return json({ error: "LinkedIn access token is required." }, { status: 400 });
8554
+ }
8555
+ const organizations = await linkedInListManagedOrganizations(token);
8556
+ return json({ ok: true, organizations });
8557
+ } catch (e) {
8558
+ const msg = e instanceof Error ? e.message : "LinkedIn organizations request failed";
8559
+ return json({ error: msg }, { status: 502 });
8560
+ }
8561
+ },
8216
8562
  async syncLinkedInProfile(req) {
8217
8563
  const a = await requireAuth(req);
8218
8564
  if (a) return a;
@@ -8261,10 +8607,12 @@ function createSocialMediaHandlers(config) {
8261
8607
  return json({ error: "Social media plugin is disabled." }, { status: 400 });
8262
8608
  }
8263
8609
  const token = (map.linkedin_access_token ?? "").trim();
8264
- const personSub = (map.linkedin_person_sub ?? "").trim();
8265
- if (!token || !personSub) {
8610
+ const authorUrn = (map.linkedin_organization_urn ?? "").trim();
8611
+ if (!token || !authorUrn) {
8266
8612
  return json(
8267
- { error: "Configure LinkedIn access token and sync profile (Plugins \u2192 Social media)." },
8613
+ {
8614
+ error: "Configure LinkedIn access token and target organization (Plugins \u2192 Social media \u2192 LinkedIn)."
8615
+ },
8268
8616
  { status: 400 }
8269
8617
  );
8270
8618
  }
@@ -8305,7 +8653,7 @@ ${excerpt}`).trim().slice(0, 2900);
8305
8653
  contentType: imagePayload.contentType
8306
8654
  });
8307
8655
  try {
8308
- const { uploadUrl, asset } = await linkedInRegisterImageUpload(token, personSub);
8656
+ const { uploadUrl, asset } = await linkedInRegisterImageUpload(token, authorUrn);
8309
8657
  logLinkedInPublish(publishLog, "image_registered", {
8310
8658
  blogId,
8311
8659
  asset: truncateForLog(asset, 120),
@@ -8321,7 +8669,7 @@ ${excerpt}`).trim().slice(0, 2900);
8321
8669
  logLinkedInPublish(publishLog, "image_uploaded", { blogId, bytes: imagePayload.buffer.length });
8322
8670
  const out2 = await linkedInCreateImageShare({
8323
8671
  accessToken: token,
8324
- personSub,
8672
+ authorUrn,
8325
8673
  asset,
8326
8674
  commentary,
8327
8675
  title,
@@ -8359,7 +8707,7 @@ ${excerpt}`).trim().slice(0, 2900);
8359
8707
  });
8360
8708
  const out2 = await linkedInCreateTextShare({
8361
8709
  accessToken: token,
8362
- personSub,
8710
+ authorUrn,
8363
8711
  commentary
8364
8712
  });
8365
8713
  logLinkedInPublish(publishLog, "success", {
@@ -8398,7 +8746,7 @@ ${excerpt}`).trim().slice(0, 2900);
8398
8746
  });
8399
8747
  const out = await linkedInCreateTextShare({
8400
8748
  accessToken: token,
8401
- personSub,
8749
+ authorUrn,
8402
8750
  commentary
8403
8751
  });
8404
8752
  logLinkedInPublish(publishLog, "success", {
@@ -8601,6 +8949,323 @@ ${excerpt}`).trim().slice(0, 2200);
8601
8949
  };
8602
8950
  }
8603
8951
 
8952
+ // src/api/job-schedule-handlers.ts
8953
+ var import_crypto = require("crypto");
8954
+
8955
+ // src/plugins/jobs/schedule-cron.ts
8956
+ function pgBossScheduleNameForId(scheduleId) {
8957
+ return `schedule:${scheduleId}`;
8958
+ }
8959
+ function buildCronFromSchedule(schedule) {
8960
+ if (schedule.scheduleMode === "cron" && schedule.cronExpression?.trim()) {
8961
+ return schedule.cronExpression.trim();
8962
+ }
8963
+ const time = parseRunAtTime(schedule.runAtTime);
8964
+ const minute = time?.minute ?? 0;
8965
+ const hour = time?.hour ?? 9;
8966
+ if (schedule.scheduleMode === "interval") {
8967
+ const mins = schedule.intervalMinutes ?? 60;
8968
+ if (mins >= 60 && mins % 60 === 0) {
8969
+ const h = Math.max(1, Math.min(23, Math.floor(mins / 60)));
8970
+ if (h === 1) return `${minute} * * * *`;
8971
+ return `${minute} */${h} * * *`;
8972
+ }
8973
+ if (mins <= 59) {
8974
+ return `*/${Math.max(1, mins)} * * * *`;
8975
+ }
8976
+ return `${minute} * * * *`;
8977
+ }
8978
+ if (schedule.scheduleMode === "weekly") {
8979
+ const days = Array.isArray(schedule.runOnDays) && schedule.runOnDays.length > 0 ? schedule.runOnDays.filter((d) => d >= 0 && d <= 6).join(",") : "1";
8980
+ return `${minute} ${hour} * * ${days}`;
8981
+ }
8982
+ return `${minute} ${hour} * * *`;
8983
+ }
8984
+ function parseRunAtTime(runAtTime) {
8985
+ if (!runAtTime?.trim()) return null;
8986
+ const m = runAtTime.trim().match(/^(\d{1,2}):(\d{2})$/);
8987
+ if (!m) return null;
8988
+ const hour = parseInt(m[1], 10);
8989
+ const minute = parseInt(m[2], 10);
8990
+ if (hour < 0 || hour > 23 || minute < 0 || minute > 59) return null;
8991
+ return { hour, minute };
8992
+ }
8993
+ function validateScheduleInput(schedule) {
8994
+ if (schedule.scheduleMode === "cron") {
8995
+ if (!schedule.cronExpression?.trim()) return "cronExpression is required for cron mode";
8996
+ return null;
8997
+ }
8998
+ if (schedule.scheduleMode === "interval") {
8999
+ const mins = schedule.intervalMinutes;
9000
+ if (mins == null || !Number.isFinite(mins) || mins < 1 || mins > 10080) {
9001
+ return "intervalMinutes must be between 1 and 10080";
9002
+ }
9003
+ return null;
9004
+ }
9005
+ if (schedule.runAtTime?.trim()) {
9006
+ if (!parseRunAtTime(schedule.runAtTime)) return "runAtTime must be HH:mm (24h)";
9007
+ }
9008
+ return null;
9009
+ }
9010
+
9011
+ // src/plugins/jobs/schedule-sync.ts
9012
+ async function syncJobScheduleToPgBoss(cms, schedule, onRegisterQueue) {
9013
+ const boss = cms.getPlugin("pg_boss");
9014
+ if (!boss) return;
9015
+ const name = schedule.pgBossScheduleName;
9016
+ await boss.unschedule(name);
9017
+ if (!schedule.enabled) return;
9018
+ const cron = buildCronFromSchedule(schedule);
9019
+ const data = {
9020
+ scheduleId: schedule.id,
9021
+ triggeredBy: "schedule"
9022
+ };
9023
+ await boss.schedule(name, cron, data, { tz: schedule.timezone || "UTC" });
9024
+ if (onRegisterQueue) {
9025
+ await onRegisterQueue(name);
9026
+ }
9027
+ }
9028
+ async function queueJobScheduleNow(cms, scheduleId) {
9029
+ const boss = cms.getPlugin("pg_boss");
9030
+ if (!boss) {
9031
+ throw new Error("pg-boss is not configured (DATABASE_URL required)");
9032
+ }
9033
+ const { JOB_RUNNER_QUEUE: JOB_RUNNER_QUEUE2 } = await Promise.resolve().then(() => (init_pg_boss_service(), pg_boss_service_exports));
9034
+ await boss.send(JOB_RUNNER_QUEUE2, {
9035
+ scheduleId,
9036
+ triggeredBy: "manual"
9037
+ });
9038
+ }
9039
+
9040
+ // src/plugins/jobs/job-runner.ts
9041
+ init_pg_boss_service();
9042
+
9043
+ // src/plugins/jobs/blog-generate-job.ts
9044
+ var import_typeorm50 = require("typeorm");
9045
+
9046
+ // src/plugins/jobs/schedule-next-run.ts
9047
+ var import_cron_parser = require("cron-parser");
9048
+ function computeNextRunAt(schedule, from = /* @__PURE__ */ new Date()) {
9049
+ if (!schedule.enabled) return null;
9050
+ try {
9051
+ const cron = buildCronFromSchedule(schedule);
9052
+ const tz = schedule.timezone?.trim() || "UTC";
9053
+ const interval = (0, import_cron_parser.parseExpression)(cron, {
9054
+ currentDate: from,
9055
+ tz
9056
+ });
9057
+ return interval.next().toDate();
9058
+ } catch (err) {
9059
+ console.warn("[job-schedules] could not compute next run:", err);
9060
+ return null;
9061
+ }
9062
+ }
9063
+
9064
+ // src/plugins/jobs/job-runner.ts
9065
+ var executeJobRef = null;
9066
+ async function ensureScheduleQueueWorker(cms, queueName) {
9067
+ const boss = cms.getPlugin("pg_boss");
9068
+ if (!boss || !executeJobRef) return;
9069
+ await boss.registerWork(queueName, executeJobRef);
9070
+ }
9071
+
9072
+ // src/api/job-schedule-handlers.ts
9073
+ function serializeSchedule(s) {
9074
+ return {
9075
+ id: s.id,
9076
+ name: s.name,
9077
+ jobType: s.jobType,
9078
+ enabled: s.enabled,
9079
+ scheduleMode: s.scheduleMode,
9080
+ intervalMinutes: s.intervalMinutes,
9081
+ runAtTime: s.runAtTime,
9082
+ runOnDays: s.runOnDays,
9083
+ timezone: s.timezone,
9084
+ cronExpression: s.cronExpression,
9085
+ cronResolved: buildCronFromSchedule(s),
9086
+ payload: s.payload ?? {},
9087
+ authorId: s.authorId,
9088
+ pgBossScheduleName: s.pgBossScheduleName,
9089
+ lastRunAt: s.lastRunAt?.toISOString() ?? null,
9090
+ lastRunStatus: s.lastRunStatus,
9091
+ lastRunError: s.lastRunError,
9092
+ nextRunAt: (computeNextRunAt(s) ?? s.nextRunAt)?.toISOString() ?? null,
9093
+ createdAt: s.createdAt.toISOString(),
9094
+ updatedAt: s.updatedAt.toISOString()
9095
+ };
9096
+ }
9097
+ function serializeRun(r) {
9098
+ return {
9099
+ id: r.id,
9100
+ scheduleId: r.scheduleId,
9101
+ startedAt: r.startedAt.toISOString(),
9102
+ finishedAt: r.finishedAt?.toISOString() ?? null,
9103
+ status: r.status,
9104
+ error: r.error,
9105
+ result: r.result,
9106
+ triggeredBy: r.triggeredBy,
9107
+ createdAt: r.createdAt.toISOString()
9108
+ };
9109
+ }
9110
+ function parseScheduleBody(body, existing) {
9111
+ const scheduleMode = typeof body.scheduleMode === "string" ? body.scheduleMode : existing?.scheduleMode ?? "daily";
9112
+ const patch = {
9113
+ name: typeof body.name === "string" ? body.name.trim().slice(0, 500) : existing?.name,
9114
+ jobType: body.jobType === "blog_generate" ? "blog_generate" : existing?.jobType ?? "blog_generate",
9115
+ enabled: typeof body.enabled === "boolean" ? body.enabled : existing?.enabled ?? false,
9116
+ scheduleMode,
9117
+ intervalMinutes: typeof body.intervalMinutes === "number" ? body.intervalMinutes : body.intervalMinutes != null ? Number(body.intervalMinutes) : existing?.intervalMinutes ?? null,
9118
+ runAtTime: typeof body.runAtTime === "string" ? body.runAtTime.trim() || null : body.runAtTime === null ? null : existing?.runAtTime ?? null,
9119
+ runOnDays: Array.isArray(body.runOnDays) ? body.runOnDays.map((d) => Number(d)).filter((d) => Number.isFinite(d)) : existing?.runOnDays ?? null,
9120
+ timezone: typeof body.timezone === "string" && body.timezone.trim() ? body.timezone.trim().slice(0, 64) : existing?.timezone ?? "UTC",
9121
+ cronExpression: typeof body.cronExpression === "string" ? body.cronExpression.trim() || null : existing?.cronExpression ?? null,
9122
+ payload: body.payload != null && typeof body.payload === "object" && !Array.isArray(body.payload) ? body.payload : existing?.payload ?? {},
9123
+ authorId: typeof body.authorId === "number" ? body.authorId : body.authorId != null ? Number(body.authorId) : existing?.authorId ?? null
9124
+ };
9125
+ return patch;
9126
+ }
9127
+ function createJobScheduleHandlers(apiConfig) {
9128
+ const { dataSource, entityMap, json, requireAuth, requireEntityPermission, getCms } = apiConfig;
9129
+ const ScheduleEntity = entityMap.job_schedules ?? JobSchedule;
9130
+ const RunEntity = entityMap.job_schedule_runs ?? JobScheduleRun;
9131
+ async function perm(req, action) {
9132
+ if (!requireEntityPermission) return null;
9133
+ return requireEntityPermission(req, "blogs", action);
9134
+ }
9135
+ async function syncAfterSave(cms, schedule) {
9136
+ await syncJobScheduleToPgBoss(cms, schedule, async (queueName) => {
9137
+ await ensureScheduleQueueWorker(cms, queueName);
9138
+ });
9139
+ const repo = dataSource.getRepository(ScheduleEntity);
9140
+ schedule.nextRunAt = computeNextRunAt(schedule);
9141
+ await repo.save(schedule);
9142
+ }
9143
+ return {
9144
+ async list(req) {
9145
+ const a = await requireAuth(req);
9146
+ if (a) return a;
9147
+ const pe = await perm(req, "read");
9148
+ if (pe) return pe;
9149
+ const list = await dataSource.getRepository(ScheduleEntity).find({
9150
+ order: { updatedAt: "DESC" }
9151
+ });
9152
+ return json({ schedules: list.map(serializeSchedule) });
9153
+ },
9154
+ async getById(req, id) {
9155
+ const a = await requireAuth(req);
9156
+ if (a) return a;
9157
+ const pe = await perm(req, "read");
9158
+ if (pe) return pe;
9159
+ const row = await dataSource.getRepository(ScheduleEntity).findOne({ where: { id } });
9160
+ if (!row) return json({ error: "Schedule not found" }, { status: 404 });
9161
+ const runs = await dataSource.getRepository(RunEntity).find({
9162
+ where: { scheduleId: id },
9163
+ order: { startedAt: "DESC" },
9164
+ take: 20
9165
+ });
9166
+ return json({ schedule: serializeSchedule(row), runs: runs.map(serializeRun) });
9167
+ },
9168
+ async create(req) {
9169
+ const a = await requireAuth(req);
9170
+ if (a) return a;
9171
+ const pe = await perm(req, "create");
9172
+ if (pe) return pe;
9173
+ if (!getCms) return json({ error: "CMS not configured" }, { status: 503 });
9174
+ let body;
9175
+ try {
9176
+ body = await req.json();
9177
+ } catch {
9178
+ return json({ error: "Invalid JSON body" }, { status: 400 });
9179
+ }
9180
+ const patch = parseScheduleBody(body);
9181
+ if (!patch.name) return json({ error: "name is required" }, { status: 400 });
9182
+ const draft = Object.assign(new JobSchedule(), patch);
9183
+ const validationError = validateScheduleInput(draft);
9184
+ if (validationError) return json({ error: validationError }, { status: 400 });
9185
+ const repo = dataSource.getRepository(ScheduleEntity);
9186
+ let row = repo.create({
9187
+ ...patch,
9188
+ pgBossScheduleName: `pending-${(0, import_crypto.randomUUID)()}`
9189
+ });
9190
+ row = await repo.save(row);
9191
+ row.pgBossScheduleName = pgBossScheduleNameForId(row.id);
9192
+ row = await repo.save(row);
9193
+ const cms = await getCms();
9194
+ await syncAfterSave(cms, row);
9195
+ return json({ schedule: serializeSchedule(row) }, { status: 201 });
9196
+ },
9197
+ async update(req, id) {
9198
+ const a = await requireAuth(req);
9199
+ if (a) return a;
9200
+ const pe = await perm(req, "update");
9201
+ if (pe) return pe;
9202
+ if (!getCms) return json({ error: "CMS not configured" }, { status: 503 });
9203
+ const repo = dataSource.getRepository(ScheduleEntity);
9204
+ const existing = await repo.findOne({ where: { id } });
9205
+ if (!existing) return json({ error: "Schedule not found" }, { status: 404 });
9206
+ let body;
9207
+ try {
9208
+ body = await req.json();
9209
+ } catch {
9210
+ return json({ error: "Invalid JSON body" }, { status: 400 });
9211
+ }
9212
+ const patch = parseScheduleBody(body, existing);
9213
+ Object.assign(existing, patch);
9214
+ const validationError = validateScheduleInput(existing);
9215
+ if (validationError) return json({ error: validationError }, { status: 400 });
9216
+ const row = await repo.save(existing);
9217
+ const cms = await getCms();
9218
+ await syncAfterSave(cms, row);
9219
+ return json({ schedule: serializeSchedule(row) });
9220
+ },
9221
+ async remove(req, id) {
9222
+ const a = await requireAuth(req);
9223
+ if (a) return a;
9224
+ const pe = await perm(req, "delete");
9225
+ if (pe) return pe;
9226
+ if (!getCms) return json({ error: "CMS not configured" }, { status: 503 });
9227
+ const repo = dataSource.getRepository(ScheduleEntity);
9228
+ const existing = await repo.findOne({ where: { id } });
9229
+ if (!existing) return json({ error: "Schedule not found" }, { status: 404 });
9230
+ const cms = await getCms();
9231
+ existing.enabled = false;
9232
+ await syncJobScheduleToPgBoss(cms, existing);
9233
+ await repo.delete({ id });
9234
+ return json({ ok: true });
9235
+ },
9236
+ async runNow(req, id) {
9237
+ const a = await requireAuth(req);
9238
+ if (a) return a;
9239
+ const pe = await perm(req, "create");
9240
+ if (pe) return pe;
9241
+ if (!getCms) return json({ error: "CMS not configured" }, { status: 503 });
9242
+ const repo = dataSource.getRepository(ScheduleEntity);
9243
+ const existing = await repo.findOne({ where: { id } });
9244
+ if (!existing) return json({ error: "Schedule not found" }, { status: 404 });
9245
+ try {
9246
+ const cms = await getCms();
9247
+ await queueJobScheduleNow(cms, id);
9248
+ return json({ ok: true, message: "Job queued" });
9249
+ } catch (e) {
9250
+ const message = e instanceof Error ? e.message : "Failed to queue job";
9251
+ return json({ error: message }, { status: 503 });
9252
+ }
9253
+ },
9254
+ async listRuns(req, id) {
9255
+ const a = await requireAuth(req);
9256
+ if (a) return a;
9257
+ const pe = await perm(req, "read");
9258
+ if (pe) return pe;
9259
+ const runs = await dataSource.getRepository(RunEntity).find({
9260
+ where: { scheduleId: id },
9261
+ order: { startedAt: "DESC" },
9262
+ take: 50
9263
+ });
9264
+ return json({ runs: runs.map(serializeRun) });
9265
+ }
9266
+ };
9267
+ }
9268
+
8604
9269
  // src/api/cms-api-handler.ts
8605
9270
  var KNOWLEDGE_SUFFIX = "knowledge";
8606
9271
  var CMS_API_LOG = "[cms-api]";
@@ -8661,7 +9326,9 @@ var DEFAULT_EXCLUDE = /* @__PURE__ */ new Set([
8661
9326
  "message_templates",
8662
9327
  "llm_agent_knowledge_documents",
8663
9328
  "rss_feeds",
8664
- "rss_articles"
9329
+ "rss_articles",
9330
+ "job_schedules",
9331
+ "job_schedule_runs"
8665
9332
  ]);
8666
9333
  function createCmsApiHandler(config) {
8667
9334
  const {
@@ -8824,6 +9491,15 @@ function createCmsApiHandler(config) {
8824
9491
  ...llmAgentKnowledgeMerged,
8825
9492
  requireEntityPermission: requireEntityPermissionEffective
8826
9493
  }) : null;
9494
+ const jobScheduleHandlers = getCms ? createJobScheduleHandlers({
9495
+ dataSource,
9496
+ entityMap,
9497
+ json: config.json,
9498
+ requireAuth: config.requireAuth,
9499
+ requireEntityPermission: requireEntityPermissionEffective,
9500
+ getCms,
9501
+ config: typeof process !== "undefined" ? process.env : {}
9502
+ }) : null;
8827
9503
  function resolveResource(segment) {
8828
9504
  const model = pathToModel(segment);
8829
9505
  return crudResources.includes(model) ? model : segment;
@@ -8854,6 +9530,30 @@ function createCmsApiHandler(config) {
8854
9530
  if (g) return g;
8855
9531
  return ecommerceAnalyticsGet(req);
8856
9532
  }
9533
+ if (path2[0] === "job-schedules" && jobScheduleHandlers) {
9534
+ if (path2.length === 2 && m === "GET") {
9535
+ return jobScheduleHandlers.getById(req, path2[1]);
9536
+ }
9537
+ if (path2.length === 2 && m === "PATCH") {
9538
+ return jobScheduleHandlers.update(req, path2[1]);
9539
+ }
9540
+ if (path2.length === 2 && m === "DELETE") {
9541
+ return jobScheduleHandlers.remove(req, path2[1]);
9542
+ }
9543
+ if (path2.length === 3 && path2[2] === "run-now" && m === "POST") {
9544
+ return jobScheduleHandlers.runNow(req, path2[1]);
9545
+ }
9546
+ if (path2.length === 3 && path2[2] === "runs" && m === "GET") {
9547
+ return jobScheduleHandlers.listRuns(req, path2[1]);
9548
+ }
9549
+ if (path2.length === 1 && m === "GET") {
9550
+ return jobScheduleHandlers.list(req);
9551
+ }
9552
+ if (path2.length === 1 && m === "POST") {
9553
+ return jobScheduleHandlers.create(req);
9554
+ }
9555
+ return config.json({ error: "Not found" }, { status: 404 });
9556
+ }
8857
9557
  if (path2[0] === "blog-generator" && path2[1] === "feeds" && path2.length === 3 && m === "DELETE" && getCms) {
8858
9558
  const id = path2[2];
8859
9559
  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);
@@ -9319,6 +10019,9 @@ function createCmsApiHandler(config) {
9319
10019
  if (tail === "sync-profile" && m === "POST") {
9320
10020
  return socialMediaHandlers.syncLinkedInProfile(req);
9321
10021
  }
10022
+ if (tail === "fetch-organizations" && m === "POST") {
10023
+ return socialMediaHandlers.fetchLinkedInOrganizations(req);
10024
+ }
9322
10025
  if (tail === "publish-blog" && m === "POST") {
9323
10026
  let body;
9324
10027
  try {
@@ -9499,7 +10202,7 @@ function createCmsApiHandler(config) {
9499
10202
  }
9500
10203
 
9501
10204
  // src/api/storefront-handlers.ts
9502
- var import_typeorm49 = require("typeorm");
10205
+ var import_typeorm52 = require("typeorm");
9503
10206
 
9504
10207
  // src/lib/is-valid-signup-email.ts
9505
10208
  var MAX_EMAIL = 254;
@@ -9758,8 +10461,8 @@ async function queueSms(cms, payload) {
9758
10461
  }
9759
10462
 
9760
10463
  // src/lib/otp-challenge.ts
9761
- var import_crypto = require("crypto");
9762
- var import_typeorm48 = require("typeorm");
10464
+ var import_crypto2 = require("crypto");
10465
+ var import_typeorm51 = require("typeorm");
9763
10466
  var OTP_TTL_MS = 10 * 60 * 1e3;
9764
10467
  var MAX_SENDS_PER_HOUR = 5;
9765
10468
  var MAX_VERIFY_ATTEMPTS = 8;
@@ -9767,19 +10470,19 @@ function getPepper(explicit) {
9767
10470
  return (explicit || process.env.OTP_PEPPER || process.env.NEXTAUTH_SECRET || "dev-otp-pepper").trim();
9768
10471
  }
9769
10472
  function hashOtpCode(code, purpose, identifier, pepper) {
9770
- return (0, import_crypto.createHmac)("sha256", getPepper(pepper)).update(`${purpose}|${identifier}|${code}`).digest("hex");
10473
+ return (0, import_crypto2.createHmac)("sha256", getPepper(pepper)).update(`${purpose}|${identifier}|${code}`).digest("hex");
9771
10474
  }
9772
10475
  function verifyOtpCodeHash(code, storedHash, purpose, identifier, pepper) {
9773
10476
  const h = hashOtpCode(code, purpose, identifier, pepper);
9774
10477
  try {
9775
- return (0, import_crypto.timingSafeEqual)(Buffer.from(h, "utf8"), Buffer.from(storedHash, "utf8"));
10478
+ return (0, import_crypto2.timingSafeEqual)(Buffer.from(h, "utf8"), Buffer.from(storedHash, "utf8"));
9776
10479
  } catch {
9777
10480
  return false;
9778
10481
  }
9779
10482
  }
9780
10483
  function generateNumericOtp(length = 6) {
9781
10484
  const max = 10 ** length;
9782
- return (0, import_crypto.randomInt)(0, max).toString().padStart(length, "0");
10485
+ return (0, import_crypto2.randomInt)(0, max).toString().padStart(length, "0");
9783
10486
  }
9784
10487
  function normalizePhoneE164(raw, defaultCountryCode) {
9785
10488
  const t = raw.trim();
@@ -9793,7 +10496,7 @@ function normalizePhoneE164(raw, defaultCountryCode) {
9793
10496
  async function countRecentOtpSends(dataSource, entityMap, purpose, identifier, since) {
9794
10497
  const repo = dataSource.getRepository(entityMap.otp_challenges);
9795
10498
  return repo.count({
9796
- where: { purpose, identifier, createdAt: (0, import_typeorm48.MoreThan)(since) }
10499
+ where: { purpose, identifier, createdAt: (0, import_typeorm51.MoreThan)(since) }
9797
10500
  });
9798
10501
  }
9799
10502
  async function createOtpChallenge(dataSource, entityMap, input) {
@@ -9807,7 +10510,7 @@ async function createOtpChallenge(dataSource, entityMap, input) {
9807
10510
  await repo.delete({
9808
10511
  purpose,
9809
10512
  identifier,
9810
- consumedAt: (0, import_typeorm48.IsNull)()
10513
+ consumedAt: (0, import_typeorm51.IsNull)()
9811
10514
  });
9812
10515
  const expiresAt = new Date(Date.now() + OTP_TTL_MS);
9813
10516
  const codeHash = hashOtpCode(code, purpose, identifier, pepper);
@@ -9828,7 +10531,7 @@ async function verifyAndConsumeOtpChallenge(dataSource, entityMap, input) {
9828
10531
  const { purpose, identifier, code, pepper } = input;
9829
10532
  const repo = dataSource.getRepository(entityMap.otp_challenges);
9830
10533
  const row = await repo.findOne({
9831
- where: { purpose, identifier, consumedAt: (0, import_typeorm48.IsNull)() },
10534
+ where: { purpose, identifier, consumedAt: (0, import_typeorm51.IsNull)() },
9832
10535
  order: { id: "DESC" }
9833
10536
  });
9834
10537
  if (!row) {
@@ -10062,7 +10765,7 @@ function createStorefrontApiHandler(config) {
10062
10765
  const u = await userRepo().findOne({ where: { id: userId } });
10063
10766
  if (!u) return null;
10064
10767
  const unclaimed = await contactRepo().findOne({
10065
- where: { email: u.email, userId: (0, import_typeorm49.IsNull)(), deleted: false }
10768
+ where: { email: u.email, userId: (0, import_typeorm52.IsNull)(), deleted: false }
10066
10769
  });
10067
10770
  if (unclaimed) {
10068
10771
  await contactRepo().update(unclaimed.id, { userId });
@@ -11113,7 +11816,7 @@ function createStorefrontApiHandler(config) {
11113
11816
  const previewByOrder = {};
11114
11817
  if (orderIds.length) {
11115
11818
  const oItems = await orderItemRepo().find({
11116
- where: { orderId: (0, import_typeorm49.In)(orderIds) },
11819
+ where: { orderId: (0, import_typeorm52.In)(orderIds) },
11117
11820
  relations: ["product"],
11118
11821
  order: { id: "ASC" }
11119
11822
  });
@@ -11250,6 +11953,7 @@ function createStorefrontApiHandler(config) {
11250
11953
  createForgotPasswordHandler,
11251
11954
  createFormBySlugHandler,
11252
11955
  createInviteAcceptHandler,
11956
+ createJobScheduleHandlers,
11253
11957
  createLlmAgentKnowledgeHandlers,
11254
11958
  createMediaZipExtractHandler,
11255
11959
  createSetPasswordHandler,