@infuro/cms-core 1.0.27 → 1.0.29

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.d.cts CHANGED
@@ -1,3 +1,3 @@
1
- export { A as AnalyticsHandlerConfig, c as AuthHandlersConfig, B as BlogBySlugConfig, d as ChangePasswordConfig, e as ChatPublicConfig, f as CmsApiHandlerConfig, g as CmsGetter, h as CrudHandlerOptions, D as DashboardStatsConfig, i as EcommerceAnalyticsConfig, b as EntityMap, F as ForgotPasswordConfig, j as FormBySlugConfig, G as GetPublicSettingsGroupConfig, k as GetPublicSettingsGroupDataSource, I as InviteAcceptConfig, L as LlmAgentKnowledgeApiConfig, l as LlmAgentValidationRulesJson, P as ParsedLlmAgentValidation, m as SetPasswordConfig, n as SettingsApiConfig, p as StorefrontApiConfig, q as StorefrontOtpFlags, U as UploadHandlerConfig, r as UserAuthApiConfig, s as UserAvatarConfig, t as UserProfileConfig, u as UsersApiConfig, v as createAnalyticsHandlers, w as createBlogBySlugHandler, x as createChangePasswordHandler, y as createCmsApiHandler, z as createCrudByIdHandler, H as createCrudHandler, J as createDashboardStatsHandler, K as createEcommerceAnalyticsHandler, M as createForgotPasswordHandler, N as createFormBySlugHandler, Q as createInviteAcceptHandler, R as createLlmAgentKnowledgeHandlers, V as createMediaZipExtractHandler, W as createSetPasswordHandler, X as createSettingsApiHandlers, Y as createStorefrontApiHandler, Z as createUploadHandler, _ as createUserAuthApiRouter, $ as createUserAvatarHandler, a0 as createUserProfileHandler, a1 as createUsersApiHandlers, a3 as getPublicSettingsGroup, a5 as mergeGuardrailsIntoSystemPrompt, a6 as parseLlmAgentValidationRules, a7 as simpleDecrypt, a8 as simpleEncrypt, a9 as validateUserMessageAgainstAgentRules, aa as validateUserMessageAgainstStructuredRules } from './index-CI6J9dxr.cjs';
1
+ export { A as AnalyticsHandlerConfig, c as AuthHandlersConfig, B as BlogBySlugConfig, d as ChangePasswordConfig, e as ChatPublicConfig, f as CmsApiHandlerConfig, g as CmsGetter, h as CrudHandlerOptions, D as DashboardStatsConfig, i as EcommerceAnalyticsConfig, b as EntityMap, F as ForgotPasswordConfig, j as FormBySlugConfig, G as GetPublicSettingsGroupConfig, k as GetPublicSettingsGroupDataSource, I as InviteAcceptConfig, J as JobScheduleApiConfig, L as LlmAgentKnowledgeApiConfig, l as LlmAgentValidationRulesJson, P as ParsedLlmAgentValidation, m as SetPasswordConfig, n as SettingsApiConfig, p as StorefrontApiConfig, q as StorefrontOtpFlags, U as UploadHandlerConfig, r as UserAuthApiConfig, s as UserAvatarConfig, t as UserProfileConfig, u as UsersApiConfig, v as createAnalyticsHandlers, w as createBlogBySlugHandler, x as createChangePasswordHandler, y as createCmsApiHandler, z as createCrudByIdHandler, H as createCrudHandler, K as createDashboardStatsHandler, M as createEcommerceAnalyticsHandler, N as createForgotPasswordHandler, Q as createFormBySlugHandler, R as createInviteAcceptHandler, V as createJobScheduleHandlers, W as createLlmAgentKnowledgeHandlers, X as createMediaZipExtractHandler, Y as createSetPasswordHandler, Z as createSettingsApiHandlers, _ as createStorefrontApiHandler, $ as createUploadHandler, a0 as createUserAuthApiRouter, a1 as createUserAvatarHandler, a2 as createUserProfileHandler, a3 as createUsersApiHandlers, a5 as getPublicSettingsGroup, a7 as mergeGuardrailsIntoSystemPrompt, a8 as parseLlmAgentValidationRules, a9 as simpleDecrypt, aa as simpleEncrypt, ab as validateUserMessageAgainstAgentRules, ac as validateUserMessageAgainstStructuredRules } from './index-BPQSXgXF.cjs';
2
2
  import 'typeorm';
3
3
  import './helpers-dlrF_49e.cjs';
package/dist/api.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { A as AnalyticsHandlerConfig, c as AuthHandlersConfig, B as BlogBySlugConfig, d as ChangePasswordConfig, e as ChatPublicConfig, f as CmsApiHandlerConfig, g as CmsGetter, h as CrudHandlerOptions, D as DashboardStatsConfig, i as EcommerceAnalyticsConfig, b as EntityMap, F as ForgotPasswordConfig, j as FormBySlugConfig, G as GetPublicSettingsGroupConfig, k as GetPublicSettingsGroupDataSource, I as InviteAcceptConfig, L as LlmAgentKnowledgeApiConfig, l as LlmAgentValidationRulesJson, P as ParsedLlmAgentValidation, m as SetPasswordConfig, n as SettingsApiConfig, p as StorefrontApiConfig, q as StorefrontOtpFlags, U as UploadHandlerConfig, r as UserAuthApiConfig, s as UserAvatarConfig, t as UserProfileConfig, u as UsersApiConfig, v as createAnalyticsHandlers, w as createBlogBySlugHandler, x as createChangePasswordHandler, y as createCmsApiHandler, z as createCrudByIdHandler, H as createCrudHandler, J as createDashboardStatsHandler, K as createEcommerceAnalyticsHandler, M as createForgotPasswordHandler, N as createFormBySlugHandler, Q as createInviteAcceptHandler, R as createLlmAgentKnowledgeHandlers, V as createMediaZipExtractHandler, W as createSetPasswordHandler, X as createSettingsApiHandlers, Y as createStorefrontApiHandler, Z as createUploadHandler, _ as createUserAuthApiRouter, $ as createUserAvatarHandler, a0 as createUserProfileHandler, a1 as createUsersApiHandlers, a3 as getPublicSettingsGroup, a5 as mergeGuardrailsIntoSystemPrompt, a6 as parseLlmAgentValidationRules, a7 as simpleDecrypt, a8 as simpleEncrypt, a9 as validateUserMessageAgainstAgentRules, aa as validateUserMessageAgainstStructuredRules } from './index-CMJZ5Fpr.js';
1
+ export { A as AnalyticsHandlerConfig, c as AuthHandlersConfig, B as BlogBySlugConfig, d as ChangePasswordConfig, e as ChatPublicConfig, f as CmsApiHandlerConfig, g as CmsGetter, h as CrudHandlerOptions, D as DashboardStatsConfig, i as EcommerceAnalyticsConfig, b as EntityMap, F as ForgotPasswordConfig, j as FormBySlugConfig, G as GetPublicSettingsGroupConfig, k as GetPublicSettingsGroupDataSource, I as InviteAcceptConfig, J as JobScheduleApiConfig, L as LlmAgentKnowledgeApiConfig, l as LlmAgentValidationRulesJson, P as ParsedLlmAgentValidation, m as SetPasswordConfig, n as SettingsApiConfig, p as StorefrontApiConfig, q as StorefrontOtpFlags, U as UploadHandlerConfig, r as UserAuthApiConfig, s as UserAvatarConfig, t as UserProfileConfig, u as UsersApiConfig, v as createAnalyticsHandlers, w as createBlogBySlugHandler, x as createChangePasswordHandler, y as createCmsApiHandler, z as createCrudByIdHandler, H as createCrudHandler, K as createDashboardStatsHandler, M as createEcommerceAnalyticsHandler, N as createForgotPasswordHandler, Q as createFormBySlugHandler, R as createInviteAcceptHandler, V as createJobScheduleHandlers, W as createLlmAgentKnowledgeHandlers, X as createMediaZipExtractHandler, Y as createSetPasswordHandler, Z as createSettingsApiHandlers, _ as createStorefrontApiHandler, $ as createUploadHandler, a0 as createUserAuthApiRouter, a1 as createUserAvatarHandler, a2 as createUserProfileHandler, a3 as createUsersApiHandlers, a5 as getPublicSettingsGroup, a7 as mergeGuardrailsIntoSystemPrompt, a8 as parseLlmAgentValidationRules, a9 as simpleDecrypt, aa as simpleEncrypt, ab as validateUserMessageAgainstAgentRules, ac as validateUserMessageAgainstStructuredRules } from './index-D9SdaDJ0.js';
2
2
  import 'typeorm';
3
3
  import './helpers-dlrF_49e.js';
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
@@ -8570,6 +8800,302 @@ ${excerpt}`).trim().slice(0, 2200);
8570
8800
  };
8571
8801
  }
8572
8802
 
8803
+ // src/api/job-schedule-handlers.ts
8804
+ import { randomUUID } from "crypto";
8805
+
8806
+ // src/plugins/jobs/schedule-cron.ts
8807
+ function pgBossScheduleNameForId(scheduleId) {
8808
+ return `schedule:${scheduleId}`;
8809
+ }
8810
+ function buildCronFromSchedule(schedule) {
8811
+ if (schedule.scheduleMode === "cron" && schedule.cronExpression?.trim()) {
8812
+ return schedule.cronExpression.trim();
8813
+ }
8814
+ const time = parseRunAtTime(schedule.runAtTime);
8815
+ const minute = time?.minute ?? 0;
8816
+ const hour = time?.hour ?? 9;
8817
+ if (schedule.scheduleMode === "interval") {
8818
+ const mins = schedule.intervalMinutes ?? 60;
8819
+ if (mins >= 60 && mins % 60 === 0) {
8820
+ const h = Math.max(1, Math.min(23, Math.floor(mins / 60)));
8821
+ if (h === 1) return `${minute} * * * *`;
8822
+ return `${minute} */${h} * * *`;
8823
+ }
8824
+ if (mins <= 59) {
8825
+ return `*/${Math.max(1, mins)} * * * *`;
8826
+ }
8827
+ return `${minute} * * * *`;
8828
+ }
8829
+ if (schedule.scheduleMode === "weekly") {
8830
+ const days = Array.isArray(schedule.runOnDays) && schedule.runOnDays.length > 0 ? schedule.runOnDays.filter((d) => d >= 0 && d <= 6).join(",") : "1";
8831
+ return `${minute} ${hour} * * ${days}`;
8832
+ }
8833
+ return `${minute} ${hour} * * *`;
8834
+ }
8835
+ function parseRunAtTime(runAtTime) {
8836
+ if (!runAtTime?.trim()) return null;
8837
+ const m = runAtTime.trim().match(/^(\d{1,2}):(\d{2})$/);
8838
+ if (!m) return null;
8839
+ const hour = parseInt(m[1], 10);
8840
+ const minute = parseInt(m[2], 10);
8841
+ if (hour < 0 || hour > 23 || minute < 0 || minute > 59) return null;
8842
+ return { hour, minute };
8843
+ }
8844
+ function validateScheduleInput(schedule) {
8845
+ if (schedule.scheduleMode === "cron") {
8846
+ if (!schedule.cronExpression?.trim()) return "cronExpression is required for cron mode";
8847
+ return null;
8848
+ }
8849
+ if (schedule.scheduleMode === "interval") {
8850
+ const mins = schedule.intervalMinutes;
8851
+ if (mins == null || !Number.isFinite(mins) || mins < 1 || mins > 10080) {
8852
+ return "intervalMinutes must be between 1 and 10080";
8853
+ }
8854
+ return null;
8855
+ }
8856
+ if (schedule.runAtTime?.trim()) {
8857
+ if (!parseRunAtTime(schedule.runAtTime)) return "runAtTime must be HH:mm (24h)";
8858
+ }
8859
+ return null;
8860
+ }
8861
+
8862
+ // src/plugins/jobs/schedule-sync.ts
8863
+ async function syncJobScheduleToPgBoss(cms, schedule, onRegisterQueue) {
8864
+ const boss = cms.getPlugin("pg_boss");
8865
+ if (!boss) return;
8866
+ const name = schedule.pgBossScheduleName;
8867
+ await boss.unschedule(name);
8868
+ if (!schedule.enabled) return;
8869
+ const cron = buildCronFromSchedule(schedule);
8870
+ const data = {
8871
+ scheduleId: schedule.id,
8872
+ triggeredBy: "schedule"
8873
+ };
8874
+ await boss.schedule(name, cron, data, { tz: schedule.timezone || "UTC" });
8875
+ if (onRegisterQueue) {
8876
+ await onRegisterQueue(name);
8877
+ }
8878
+ }
8879
+ async function queueJobScheduleNow(cms, scheduleId) {
8880
+ const boss = cms.getPlugin("pg_boss");
8881
+ if (!boss) {
8882
+ throw new Error("pg-boss is not configured (DATABASE_URL required)");
8883
+ }
8884
+ const { JOB_RUNNER_QUEUE: JOB_RUNNER_QUEUE2 } = await Promise.resolve().then(() => (init_pg_boss_service(), pg_boss_service_exports));
8885
+ await boss.send(JOB_RUNNER_QUEUE2, {
8886
+ scheduleId,
8887
+ triggeredBy: "manual"
8888
+ });
8889
+ }
8890
+
8891
+ // src/plugins/jobs/job-runner.ts
8892
+ init_pg_boss_service();
8893
+
8894
+ // src/plugins/jobs/blog-generate-job.ts
8895
+ import { In as In3 } from "typeorm";
8896
+
8897
+ // src/plugins/jobs/job-runner.ts
8898
+ var executeJobRef = null;
8899
+ async function ensureScheduleQueueWorker(cms, queueName) {
8900
+ const boss = cms.getPlugin("pg_boss");
8901
+ if (!boss || !executeJobRef) return;
8902
+ await boss.registerWork(queueName, executeJobRef);
8903
+ }
8904
+
8905
+ // src/api/job-schedule-handlers.ts
8906
+ function serializeSchedule(s) {
8907
+ return {
8908
+ id: s.id,
8909
+ name: s.name,
8910
+ jobType: s.jobType,
8911
+ enabled: s.enabled,
8912
+ scheduleMode: s.scheduleMode,
8913
+ intervalMinutes: s.intervalMinutes,
8914
+ runAtTime: s.runAtTime,
8915
+ runOnDays: s.runOnDays,
8916
+ timezone: s.timezone,
8917
+ cronExpression: s.cronExpression,
8918
+ cronResolved: buildCronFromSchedule(s),
8919
+ payload: s.payload ?? {},
8920
+ authorId: s.authorId,
8921
+ pgBossScheduleName: s.pgBossScheduleName,
8922
+ lastRunAt: s.lastRunAt?.toISOString() ?? null,
8923
+ lastRunStatus: s.lastRunStatus,
8924
+ lastRunError: s.lastRunError,
8925
+ nextRunAt: s.nextRunAt?.toISOString() ?? null,
8926
+ createdAt: s.createdAt.toISOString(),
8927
+ updatedAt: s.updatedAt.toISOString()
8928
+ };
8929
+ }
8930
+ function serializeRun(r) {
8931
+ return {
8932
+ id: r.id,
8933
+ scheduleId: r.scheduleId,
8934
+ startedAt: r.startedAt.toISOString(),
8935
+ finishedAt: r.finishedAt?.toISOString() ?? null,
8936
+ status: r.status,
8937
+ error: r.error,
8938
+ result: r.result,
8939
+ triggeredBy: r.triggeredBy,
8940
+ createdAt: r.createdAt.toISOString()
8941
+ };
8942
+ }
8943
+ function parseScheduleBody(body, existing) {
8944
+ const scheduleMode = typeof body.scheduleMode === "string" ? body.scheduleMode : existing?.scheduleMode ?? "daily";
8945
+ const patch = {
8946
+ name: typeof body.name === "string" ? body.name.trim().slice(0, 500) : existing?.name,
8947
+ jobType: body.jobType === "blog_generate" ? "blog_generate" : existing?.jobType ?? "blog_generate",
8948
+ enabled: typeof body.enabled === "boolean" ? body.enabled : existing?.enabled ?? false,
8949
+ scheduleMode,
8950
+ intervalMinutes: typeof body.intervalMinutes === "number" ? body.intervalMinutes : body.intervalMinutes != null ? Number(body.intervalMinutes) : existing?.intervalMinutes ?? null,
8951
+ runAtTime: typeof body.runAtTime === "string" ? body.runAtTime.trim() || null : body.runAtTime === null ? null : existing?.runAtTime ?? null,
8952
+ runOnDays: Array.isArray(body.runOnDays) ? body.runOnDays.map((d) => Number(d)).filter((d) => Number.isFinite(d)) : existing?.runOnDays ?? null,
8953
+ timezone: typeof body.timezone === "string" && body.timezone.trim() ? body.timezone.trim().slice(0, 64) : existing?.timezone ?? "UTC",
8954
+ cronExpression: typeof body.cronExpression === "string" ? body.cronExpression.trim() || null : existing?.cronExpression ?? null,
8955
+ payload: body.payload != null && typeof body.payload === "object" && !Array.isArray(body.payload) ? body.payload : existing?.payload ?? {},
8956
+ authorId: typeof body.authorId === "number" ? body.authorId : body.authorId != null ? Number(body.authorId) : existing?.authorId ?? null
8957
+ };
8958
+ return patch;
8959
+ }
8960
+ function createJobScheduleHandlers(apiConfig) {
8961
+ const { dataSource, entityMap, json, requireAuth, requireEntityPermission, getCms } = apiConfig;
8962
+ const ScheduleEntity = entityMap.job_schedules ?? JobSchedule;
8963
+ const RunEntity = entityMap.job_schedule_runs ?? JobScheduleRun;
8964
+ async function perm(req, action) {
8965
+ if (!requireEntityPermission) return null;
8966
+ return requireEntityPermission(req, "blogs", action);
8967
+ }
8968
+ async function syncAfterSave(cms, schedule) {
8969
+ await syncJobScheduleToPgBoss(cms, schedule, async (queueName) => {
8970
+ await ensureScheduleQueueWorker(cms, queueName);
8971
+ });
8972
+ }
8973
+ return {
8974
+ async list(req) {
8975
+ const a = await requireAuth(req);
8976
+ if (a) return a;
8977
+ const pe = await perm(req, "read");
8978
+ if (pe) return pe;
8979
+ const list = await dataSource.getRepository(ScheduleEntity).find({
8980
+ order: { updatedAt: "DESC" }
8981
+ });
8982
+ return json({ schedules: list.map(serializeSchedule) });
8983
+ },
8984
+ async getById(req, id) {
8985
+ const a = await requireAuth(req);
8986
+ if (a) return a;
8987
+ const pe = await perm(req, "read");
8988
+ if (pe) return pe;
8989
+ const row = await dataSource.getRepository(ScheduleEntity).findOne({ where: { id } });
8990
+ if (!row) return json({ error: "Schedule not found" }, { status: 404 });
8991
+ const runs = await dataSource.getRepository(RunEntity).find({
8992
+ where: { scheduleId: id },
8993
+ order: { startedAt: "DESC" },
8994
+ take: 20
8995
+ });
8996
+ return json({ schedule: serializeSchedule(row), runs: runs.map(serializeRun) });
8997
+ },
8998
+ async create(req) {
8999
+ const a = await requireAuth(req);
9000
+ if (a) return a;
9001
+ const pe = await perm(req, "create");
9002
+ if (pe) return pe;
9003
+ if (!getCms) return json({ error: "CMS not configured" }, { status: 503 });
9004
+ let body;
9005
+ try {
9006
+ body = await req.json();
9007
+ } catch {
9008
+ return json({ error: "Invalid JSON body" }, { status: 400 });
9009
+ }
9010
+ const patch = parseScheduleBody(body);
9011
+ if (!patch.name) return json({ error: "name is required" }, { status: 400 });
9012
+ const draft = Object.assign(new JobSchedule(), patch);
9013
+ const validationError = validateScheduleInput(draft);
9014
+ if (validationError) return json({ error: validationError }, { status: 400 });
9015
+ const repo = dataSource.getRepository(ScheduleEntity);
9016
+ let row = repo.create({
9017
+ ...patch,
9018
+ pgBossScheduleName: `pending-${randomUUID()}`
9019
+ });
9020
+ row = await repo.save(row);
9021
+ row.pgBossScheduleName = pgBossScheduleNameForId(row.id);
9022
+ row = await repo.save(row);
9023
+ const cms = await getCms();
9024
+ await syncAfterSave(cms, row);
9025
+ return json({ schedule: serializeSchedule(row) }, { status: 201 });
9026
+ },
9027
+ async update(req, id) {
9028
+ const a = await requireAuth(req);
9029
+ if (a) return a;
9030
+ const pe = await perm(req, "update");
9031
+ if (pe) return pe;
9032
+ if (!getCms) return json({ error: "CMS not configured" }, { status: 503 });
9033
+ const repo = dataSource.getRepository(ScheduleEntity);
9034
+ const existing = await repo.findOne({ where: { id } });
9035
+ if (!existing) return json({ error: "Schedule not found" }, { status: 404 });
9036
+ let body;
9037
+ try {
9038
+ body = await req.json();
9039
+ } catch {
9040
+ return json({ error: "Invalid JSON body" }, { status: 400 });
9041
+ }
9042
+ const patch = parseScheduleBody(body, existing);
9043
+ Object.assign(existing, patch);
9044
+ const validationError = validateScheduleInput(existing);
9045
+ if (validationError) return json({ error: validationError }, { status: 400 });
9046
+ const row = await repo.save(existing);
9047
+ const cms = await getCms();
9048
+ await syncAfterSave(cms, row);
9049
+ return json({ schedule: serializeSchedule(row) });
9050
+ },
9051
+ async remove(req, id) {
9052
+ const a = await requireAuth(req);
9053
+ if (a) return a;
9054
+ const pe = await perm(req, "delete");
9055
+ if (pe) return pe;
9056
+ if (!getCms) return json({ error: "CMS not configured" }, { status: 503 });
9057
+ const repo = dataSource.getRepository(ScheduleEntity);
9058
+ const existing = await repo.findOne({ where: { id } });
9059
+ if (!existing) return json({ error: "Schedule not found" }, { status: 404 });
9060
+ const cms = await getCms();
9061
+ existing.enabled = false;
9062
+ await syncJobScheduleToPgBoss(cms, existing);
9063
+ await repo.delete({ id });
9064
+ return json({ ok: true });
9065
+ },
9066
+ async runNow(req, id) {
9067
+ const a = await requireAuth(req);
9068
+ if (a) return a;
9069
+ const pe = await perm(req, "create");
9070
+ if (pe) return pe;
9071
+ if (!getCms) return json({ error: "CMS not configured" }, { status: 503 });
9072
+ const repo = dataSource.getRepository(ScheduleEntity);
9073
+ const existing = await repo.findOne({ where: { id } });
9074
+ if (!existing) return json({ error: "Schedule not found" }, { status: 404 });
9075
+ try {
9076
+ const cms = await getCms();
9077
+ await queueJobScheduleNow(cms, id);
9078
+ return json({ ok: true, message: "Job queued" });
9079
+ } catch (e) {
9080
+ const message = e instanceof Error ? e.message : "Failed to queue job";
9081
+ return json({ error: message }, { status: 503 });
9082
+ }
9083
+ },
9084
+ async listRuns(req, id) {
9085
+ const a = await requireAuth(req);
9086
+ if (a) return a;
9087
+ const pe = await perm(req, "read");
9088
+ if (pe) return pe;
9089
+ const runs = await dataSource.getRepository(RunEntity).find({
9090
+ where: { scheduleId: id },
9091
+ order: { startedAt: "DESC" },
9092
+ take: 50
9093
+ });
9094
+ return json({ runs: runs.map(serializeRun) });
9095
+ }
9096
+ };
9097
+ }
9098
+
8573
9099
  // src/api/cms-api-handler.ts
8574
9100
  var KNOWLEDGE_SUFFIX = "knowledge";
8575
9101
  var CMS_API_LOG = "[cms-api]";
@@ -8630,7 +9156,9 @@ var DEFAULT_EXCLUDE = /* @__PURE__ */ new Set([
8630
9156
  "message_templates",
8631
9157
  "llm_agent_knowledge_documents",
8632
9158
  "rss_feeds",
8633
- "rss_articles"
9159
+ "rss_articles",
9160
+ "job_schedules",
9161
+ "job_schedule_runs"
8634
9162
  ]);
8635
9163
  function createCmsApiHandler(config) {
8636
9164
  const {
@@ -8793,6 +9321,15 @@ function createCmsApiHandler(config) {
8793
9321
  ...llmAgentKnowledgeMerged,
8794
9322
  requireEntityPermission: requireEntityPermissionEffective
8795
9323
  }) : null;
9324
+ const jobScheduleHandlers = getCms ? createJobScheduleHandlers({
9325
+ dataSource,
9326
+ entityMap,
9327
+ json: config.json,
9328
+ requireAuth: config.requireAuth,
9329
+ requireEntityPermission: requireEntityPermissionEffective,
9330
+ getCms,
9331
+ config: typeof process !== "undefined" ? process.env : {}
9332
+ }) : null;
8796
9333
  function resolveResource(segment) {
8797
9334
  const model = pathToModel(segment);
8798
9335
  return crudResources.includes(model) ? model : segment;
@@ -8823,6 +9360,30 @@ function createCmsApiHandler(config) {
8823
9360
  if (g) return g;
8824
9361
  return ecommerceAnalyticsGet(req);
8825
9362
  }
9363
+ if (path2[0] === "job-schedules" && jobScheduleHandlers) {
9364
+ if (path2.length === 2 && m === "GET") {
9365
+ return jobScheduleHandlers.getById(req, path2[1]);
9366
+ }
9367
+ if (path2.length === 2 && m === "PATCH") {
9368
+ return jobScheduleHandlers.update(req, path2[1]);
9369
+ }
9370
+ if (path2.length === 2 && m === "DELETE") {
9371
+ return jobScheduleHandlers.remove(req, path2[1]);
9372
+ }
9373
+ if (path2.length === 3 && path2[2] === "run-now" && m === "POST") {
9374
+ return jobScheduleHandlers.runNow(req, path2[1]);
9375
+ }
9376
+ if (path2.length === 3 && path2[2] === "runs" && m === "GET") {
9377
+ return jobScheduleHandlers.listRuns(req, path2[1]);
9378
+ }
9379
+ if (path2.length === 1 && m === "GET") {
9380
+ return jobScheduleHandlers.list(req);
9381
+ }
9382
+ if (path2.length === 1 && m === "POST") {
9383
+ return jobScheduleHandlers.create(req);
9384
+ }
9385
+ return config.json({ error: "Not found" }, { status: 404 });
9386
+ }
8826
9387
  if (path2[0] === "blog-generator" && path2[1] === "feeds" && path2.length === 3 && m === "DELETE" && getCms) {
8827
9388
  const id = path2[2];
8828
9389
  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);
@@ -9468,7 +10029,7 @@ function createCmsApiHandler(config) {
9468
10029
  }
9469
10030
 
9470
10031
  // src/api/storefront-handlers.ts
9471
- import { In as In3, IsNull as IsNull4 } from "typeorm";
10032
+ import { In as In4, IsNull as IsNull4 } from "typeorm";
9472
10033
 
9473
10034
  // src/lib/is-valid-signup-email.ts
9474
10035
  var MAX_EMAIL = 254;
@@ -11082,7 +11643,7 @@ function createStorefrontApiHandler(config) {
11082
11643
  const previewByOrder = {};
11083
11644
  if (orderIds.length) {
11084
11645
  const oItems = await orderItemRepo().find({
11085
- where: { orderId: In3(orderIds) },
11646
+ where: { orderId: In4(orderIds) },
11086
11647
  relations: ["product"],
11087
11648
  order: { id: "ASC" }
11088
11649
  });
@@ -11218,6 +11779,7 @@ export {
11218
11779
  createForgotPasswordHandler,
11219
11780
  createFormBySlugHandler,
11220
11781
  createInviteAcceptHandler,
11782
+ createJobScheduleHandlers,
11221
11783
  createLlmAgentKnowledgeHandlers,
11222
11784
  createMediaZipExtractHandler,
11223
11785
  createSetPasswordHandler,