@infuro/cms-core 1.0.25 → 1.0.27

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
@@ -521,6 +521,8 @@ __export(api_exports, {
521
521
  getPublicSettingsGroup: () => getPublicSettingsGroup,
522
522
  mergeGuardrailsIntoSystemPrompt: () => mergeGuardrailsIntoSystemPrompt,
523
523
  parseLlmAgentValidationRules: () => parseLlmAgentValidationRules,
524
+ simpleDecrypt: () => simpleDecrypt,
525
+ simpleEncrypt: () => simpleEncrypt,
524
526
  validateUserMessageAgainstAgentRules: () => validateUserMessageAgainstAgentRules,
525
527
  validateUserMessageAgainstStructuredRules: () => validateUserMessageAgainstStructuredRules
526
528
  });
@@ -1959,14 +1961,14 @@ function createUserAuthApiRouter(config) {
1959
1961
  }) : null;
1960
1962
  return {
1961
1963
  async POST(req, pathname) {
1962
- const path = pathname.replace(/\/$/, "");
1963
- if (!USER_AUTH_PATHS.includes(path)) {
1964
+ const path2 = pathname.replace(/\/$/, "");
1965
+ if (!USER_AUTH_PATHS.includes(path2)) {
1964
1966
  return config.json({ error: "Not found" }, { status: 404 });
1965
1967
  }
1966
- if (path === "forgot-password") return forgot(req);
1967
- if (path === "set-password") return setPass(req);
1968
- if (path === "invite") return invite(req);
1969
- if (path === "reset-password" && changePass) return changePass(req);
1968
+ if (path2 === "forgot-password") return forgot(req);
1969
+ if (path2 === "set-password") return setPass(req);
1970
+ if (path2 === "invite") return invite(req);
1971
+ if (path2 === "reset-password" && changePass) return changePass(req);
1970
1972
  return config.json({ error: "Not found" }, { status: 404 });
1971
1973
  }
1972
1974
  };
@@ -2235,12 +2237,12 @@ async function extractZipMediaIntoParentTree(opts) {
2235
2237
  if (opts.storage) {
2236
2238
  publicUrl = await opts.storage.upload(buf, `uploads/${relativeUnderUploads}`, contentType);
2237
2239
  } else {
2238
- const fs = await import("fs/promises");
2240
+ const fs2 = await import("fs/promises");
2239
2241
  const pathMod = await import("path");
2240
2242
  const dir = pathMod.join(process.cwd(), opts.localUploadDir);
2241
2243
  const filePath = pathMod.join(dir, relativeUnderUploads);
2242
- await fs.mkdir(pathMod.dirname(filePath), { recursive: true });
2243
- await fs.writeFile(filePath, buf);
2244
+ await fs2.mkdir(pathMod.dirname(filePath), { recursive: true });
2245
+ await fs2.writeFile(filePath, buf);
2244
2246
  publicUrl = `/${opts.localUploadDir.replace(/^\/+/, "").replace(/\\/g, "/")}/${relativeUnderUploads.replace(/\\/g, "/")}`;
2245
2247
  }
2246
2248
  await repo.save(
@@ -2662,12 +2664,12 @@ function createUploadHandler(config) {
2662
2664
  const fileUrl = await storageService.upload(buffer, `uploads/${relativeUnderUploads}`, contentType);
2663
2665
  return json({ filePath: fileUrl, parentId });
2664
2666
  }
2665
- const fs = await import("fs/promises");
2666
- const path = await import("path");
2667
- const dir = path.join(process.cwd(), localUploadDir);
2668
- const filePath = path.join(dir, relativeUnderUploads);
2669
- await fs.mkdir(path.dirname(filePath), { recursive: true });
2670
- await fs.writeFile(filePath, buffer);
2667
+ const fs2 = await import("fs/promises");
2668
+ const path2 = await import("path");
2669
+ const dir = path2.join(process.cwd(), localUploadDir);
2670
+ const filePath = path2.join(dir, relativeUnderUploads);
2671
+ await fs2.mkdir(path2.dirname(filePath), { recursive: true });
2672
+ await fs2.writeFile(filePath, buffer);
2671
2673
  const urlRel = `${localUploadDir.replace(/^\/+/, "").replace(/\\/g, "/")}/${relativeUnderUploads.replace(/\\/g, "/")}`;
2672
2674
  return json({ filePath: `/${urlRel}`, parentId });
2673
2675
  } catch (err) {
@@ -3321,11 +3323,11 @@ function createUserAvatarHandler(config) {
3321
3323
  const ext = file.name.split(".").pop() || "jpg";
3322
3324
  const fileName = `avatar_${session.user.email}_${Date.now()}.${ext}`;
3323
3325
  const avatarUrl = saveAvatar ? await saveAvatar(buffer, fileName) : await (async () => {
3324
- const fs = await import("fs/promises");
3325
- const path = await import("path");
3326
- const dir = path.join(process.cwd(), "public", "uploads", "avatars");
3327
- await fs.mkdir(dir, { recursive: true });
3328
- await fs.writeFile(path.join(dir, fileName), buffer);
3326
+ const fs2 = await import("fs/promises");
3327
+ const path2 = await import("path");
3328
+ const dir = path2.join(process.cwd(), "public", "uploads", "avatars");
3329
+ await fs2.mkdir(dir, { recursive: true });
3330
+ await fs2.writeFile(path2.join(dir, fileName), buffer);
3329
3331
  return `/uploads/avatars/${fileName}`;
3330
3332
  })();
3331
3333
  return json({ message: "Avatar uploaded successfully", avatarUrl });
@@ -5212,6 +5214,7 @@ var Blog = class {
5212
5214
  id;
5213
5215
  title;
5214
5216
  content;
5217
+ socialMediaContent;
5215
5218
  coverImage;
5216
5219
  authorId;
5217
5220
  categoryId;
@@ -5240,6 +5243,9 @@ __decorateClass([
5240
5243
  __decorateClass([
5241
5244
  (0, import_typeorm16.Column)("text")
5242
5245
  ], Blog.prototype, "content", 2);
5246
+ __decorateClass([
5247
+ (0, import_typeorm16.Column)("text", { nullable: true })
5248
+ ], Blog.prototype, "socialMediaContent", 2);
5243
5249
  __decorateClass([
5244
5250
  (0, import_typeorm16.Column)("varchar", { nullable: true })
5245
5251
  ], Blog.prototype, "coverImage", 2);
@@ -5929,7 +5935,7 @@ __decorateClass([
5929
5935
  (0, import_typeorm25.JoinColumn)({ name: "userId" })
5930
5936
  ], Contact.prototype, "user", 2);
5931
5937
  __decorateClass([
5932
- (0, import_typeorm25.OneToMany)(() => FormSubmission, (fs) => fs.contact)
5938
+ (0, import_typeorm25.OneToMany)(() => FormSubmission, (fs2) => fs2.contact)
5933
5939
  ], Contact.prototype, "form_submissions", 2);
5934
5940
  __decorateClass([
5935
5941
  (0, import_typeorm25.OneToMany)(() => Address, (a) => a.contact)
@@ -7225,6 +7231,130 @@ LlmAgentKnowledgeDocument = __decorateClass([
7225
7231
  (0, import_typeorm45.Index)("IDX_llm_agent_knowledge_agent", ["agentId"])
7226
7232
  ], LlmAgentKnowledgeDocument);
7227
7233
 
7234
+ // src/entities/rss-feed.entity.ts
7235
+ var import_typeorm47 = require("typeorm");
7236
+
7237
+ // src/entities/rss-article.entity.ts
7238
+ var import_typeorm46 = require("typeorm");
7239
+ var RssArticle = class {
7240
+ id;
7241
+ rssFeedId;
7242
+ rssFeed;
7243
+ externalId;
7244
+ title;
7245
+ articleUrl;
7246
+ summary;
7247
+ content;
7248
+ author;
7249
+ publishedAt;
7250
+ imageUrl;
7251
+ rawData;
7252
+ contentHash;
7253
+ isProcessed;
7254
+ createdAt;
7255
+ };
7256
+ __decorateClass([
7257
+ (0, import_typeorm46.PrimaryGeneratedColumn)("uuid")
7258
+ ], RssArticle.prototype, "id", 2);
7259
+ __decorateClass([
7260
+ (0, import_typeorm46.Column)("uuid")
7261
+ ], RssArticle.prototype, "rssFeedId", 2);
7262
+ __decorateClass([
7263
+ (0, import_typeorm46.ManyToOne)(() => RssFeed, (f) => f.articles, { onDelete: "CASCADE" }),
7264
+ (0, import_typeorm46.JoinColumn)({ name: "rssFeedId" })
7265
+ ], RssArticle.prototype, "rssFeed", 2);
7266
+ __decorateClass([
7267
+ (0, import_typeorm46.Column)({ type: "text", nullable: true })
7268
+ ], RssArticle.prototype, "externalId", 2);
7269
+ __decorateClass([
7270
+ (0, import_typeorm46.Column)({ type: "text" })
7271
+ ], RssArticle.prototype, "title", 2);
7272
+ __decorateClass([
7273
+ (0, import_typeorm46.Column)({ type: "text" })
7274
+ ], RssArticle.prototype, "articleUrl", 2);
7275
+ __decorateClass([
7276
+ (0, import_typeorm46.Column)({ type: "text", nullable: true })
7277
+ ], RssArticle.prototype, "summary", 2);
7278
+ __decorateClass([
7279
+ (0, import_typeorm46.Column)({ type: "text", nullable: true })
7280
+ ], RssArticle.prototype, "content", 2);
7281
+ __decorateClass([
7282
+ (0, import_typeorm46.Column)({ type: "text", nullable: true })
7283
+ ], RssArticle.prototype, "author", 2);
7284
+ __decorateClass([
7285
+ (0, import_typeorm46.Column)({ type: "timestamp", nullable: true })
7286
+ ], RssArticle.prototype, "publishedAt", 2);
7287
+ __decorateClass([
7288
+ (0, import_typeorm46.Column)({ type: "text", nullable: true })
7289
+ ], RssArticle.prototype, "imageUrl", 2);
7290
+ __decorateClass([
7291
+ (0, import_typeorm46.Column)({ type: "jsonb", nullable: true })
7292
+ ], RssArticle.prototype, "rawData", 2);
7293
+ __decorateClass([
7294
+ (0, import_typeorm46.Column)({ type: "text", nullable: true })
7295
+ ], RssArticle.prototype, "contentHash", 2);
7296
+ __decorateClass([
7297
+ (0, import_typeorm46.Column)({ type: "boolean", default: false })
7298
+ ], RssArticle.prototype, "isProcessed", 2);
7299
+ __decorateClass([
7300
+ (0, import_typeorm46.CreateDateColumn)()
7301
+ ], RssArticle.prototype, "createdAt", 2);
7302
+ RssArticle = __decorateClass([
7303
+ (0, import_typeorm46.Entity)("rss_articles"),
7304
+ (0, import_typeorm46.Unique)("UQ_rss_articles_feed_article_url", ["rssFeedId", "articleUrl"])
7305
+ ], RssArticle);
7306
+
7307
+ // src/entities/rss-feed.entity.ts
7308
+ var RssFeed = class {
7309
+ id;
7310
+ name;
7311
+ rssUrl;
7312
+ websiteUrl;
7313
+ isActive;
7314
+ fetchFrequencyMinutes;
7315
+ lastFetchedAt;
7316
+ lastArticleDate;
7317
+ articles;
7318
+ createdAt;
7319
+ updatedAt;
7320
+ };
7321
+ __decorateClass([
7322
+ (0, import_typeorm47.PrimaryGeneratedColumn)("uuid")
7323
+ ], RssFeed.prototype, "id", 2);
7324
+ __decorateClass([
7325
+ (0, import_typeorm47.Column)({ type: "text" })
7326
+ ], RssFeed.prototype, "name", 2);
7327
+ __decorateClass([
7328
+ (0, import_typeorm47.Column)({ type: "text", unique: true })
7329
+ ], RssFeed.prototype, "rssUrl", 2);
7330
+ __decorateClass([
7331
+ (0, import_typeorm47.Column)({ type: "text", nullable: true })
7332
+ ], RssFeed.prototype, "websiteUrl", 2);
7333
+ __decorateClass([
7334
+ (0, import_typeorm47.Column)({ type: "boolean", default: true })
7335
+ ], RssFeed.prototype, "isActive", 2);
7336
+ __decorateClass([
7337
+ (0, import_typeorm47.Column)({ type: "int", default: 60 })
7338
+ ], RssFeed.prototype, "fetchFrequencyMinutes", 2);
7339
+ __decorateClass([
7340
+ (0, import_typeorm47.Column)({ type: "timestamp", nullable: true })
7341
+ ], RssFeed.prototype, "lastFetchedAt", 2);
7342
+ __decorateClass([
7343
+ (0, import_typeorm47.Column)({ type: "timestamp", nullable: true })
7344
+ ], RssFeed.prototype, "lastArticleDate", 2);
7345
+ __decorateClass([
7346
+ (0, import_typeorm47.OneToMany)(() => RssArticle, (article) => article.rssFeed)
7347
+ ], RssFeed.prototype, "articles", 2);
7348
+ __decorateClass([
7349
+ (0, import_typeorm47.CreateDateColumn)()
7350
+ ], RssFeed.prototype, "createdAt", 2);
7351
+ __decorateClass([
7352
+ (0, import_typeorm47.UpdateDateColumn)()
7353
+ ], RssFeed.prototype, "updatedAt", 2);
7354
+ RssFeed = __decorateClass([
7355
+ (0, import_typeorm47.Entity)("rss_feeds")
7356
+ ], RssFeed);
7357
+
7228
7358
  // src/entities/index.ts
7229
7359
  var CMS_ENTITY_MAP = {
7230
7360
  users: User,
@@ -7266,9 +7396,1211 @@ var CMS_ENTITY_MAP = {
7266
7396
  wishlists: Wishlist,
7267
7397
  wishlist_items: WishlistItem,
7268
7398
  llm_agents: LlmAgent,
7269
- llm_agent_knowledge_documents: LlmAgentKnowledgeDocument
7399
+ llm_agent_knowledge_documents: LlmAgentKnowledgeDocument,
7400
+ rss_feeds: RssFeed,
7401
+ rss_articles: RssArticle
7270
7402
  };
7271
7403
 
7404
+ // src/plugins/blog-generator/blog-generator-service.ts
7405
+ var import_rss_parser = __toESM(require("rss-parser"), 1);
7406
+
7407
+ // src/plugins/blog-generator/blog-generator-agent-defaults.ts
7408
+ var BLOG_GENERATOR_LLM_AGENT_SLUG = "blog-generator";
7409
+ var BLOG_GENERATOR_MARKDOWN_ARTICLE_SEPARATOR = "---BLOG_GENERATOR_NEXT---";
7410
+ var BLOG_GENERATOR_DEFAULT_SYSTEM_INSTRUCTION = `You are a professional financial and business content writer.
7411
+
7412
+ Your task is to generate completely original, high-quality blog article(s) from the latest RSS-derived source material in the user message (one block per feed). The user message is factual input only.
7413
+
7414
+ IMPORTANT RULES:
7415
+
7416
+ 1. NEVER copy the RSS article directly.
7417
+ 2. Rewrite the information into a fresh, human-like article.
7418
+ 3. Expand the topic with professional insights and explanations.
7419
+ 4. Maintain a natural editorial tone.
7420
+ 5. Make the article SEO-friendly.
7421
+ 6. Use engaging headings and subheadings.
7422
+ 7. Avoid robotic AI phrasing.
7423
+ 8. Do not mention that the content came from RSS or feeds.
7424
+ 9. Preserve factual accuracy from the source material.
7425
+ 10. The output should feel like a professionally written industry blog.
7426
+
7427
+ MULTIPLE FEEDS:
7428
+ - If several feeds are provided, compare them: if they largely cover the same story or overlap heavily, produce ONE cohesive article.
7429
+ - If they cover clearly different topics or distinct stories, produce MULTIPLE articles (one per distinct story).
7430
+ - You may also produce one synthesis plus a short separate angle when that best serves the reader\u2014use your judgment.
7431
+
7432
+ WRITING STYLE:
7433
+ - Professional
7434
+ - Clear
7435
+ - Informative
7436
+ - Business-focused
7437
+ - Human sounding
7438
+ - Modern editorial style
7439
+
7440
+ ARTICLE STRUCTURE (each article \u2014 express in HTML):
7441
+ 1. Engaging introduction
7442
+ 2. Industry context
7443
+ 3. Main developments
7444
+ 4. Key implications
7445
+ 5. Expert/business analysis
7446
+ 6. Conclusion
7447
+
7448
+ HTML STRUCTURE (STRICT \u2014 each article must be valid, semantic HTML only):
7449
+ - Output HTML only. Do not use Markdown (no # headings, no **bold**, no \`code fences\`).
7450
+ - Wrap each complete article in a single root: <article class="blog-post"> ... </article>
7451
+ - Inside <article>, use this outline:
7452
+ - <header><h1 class="blog-post-title">\u2026main title\u2026</h1></header> (exactly one h1 per article)
7453
+ - <section class="blog-post-body"> for all following content
7454
+ - Use <h2> and <h3> for section and subsection titles (never skip levels: h1 \u2192 h2 \u2192 h3).
7455
+ - Use <p> for paragraphs; keep paragraphs focused (avoid huge unbroken text).
7456
+ - Use <ul>/<ol> with <li> for lists where appropriate.
7457
+ - Use <strong> and <em> for emphasis sparingly; use <blockquote> only when quoting or callouts fit.
7458
+ - Do not include <html>, <head>, <body>, or document-level wrappers \u2014 only the fragment(s) described above.
7459
+ - Do not use <script>, <style>, <iframe>, or inline event handlers. Avoid inline style="" except when essential for accessibility (prefer none).
7460
+ - Escape angle brackets in body text if you must mention markup; keep output safe for embedding in a CMS.
7461
+
7462
+ CONTENT REQUIREMENTS:
7463
+ - Minimum 800 words per article unless source material is very small.
7464
+ - Add context around industry trends where appropriate.
7465
+ - Explain why the topic matters.
7466
+ - Include practical implications for businesses/professionals.
7467
+
7468
+ IF RSS CONTENT IS LIMITED:
7469
+ - Expand intelligently using general industry knowledge.
7470
+ - Keep the article relevant to the original topic.
7471
+ - Do not invent fake facts or statistics.
7472
+
7473
+ OUTPUT FORMAT (STRICT \u2014 HTML only):
7474
+ - Return ONLY the article HTML fragment(s). No JSON, no preamble or postscript ("Here is your article\u2026"), no markdown.
7475
+ - If you output more than one article, output multiple <article class="blog-post">\u2026</article> blocks in sequence, and between articles put a single line containing exactly this (and nothing else on that line):
7476
+ ${BLOG_GENERATOR_MARKDOWN_ARTICLE_SEPARATOR}
7477
+ - If you output a single article, use one <article> only and do not use that separator line.`;
7478
+ var BLOG_GENERATOR_DEFAULT_VALIDATION_RULES = JSON.stringify(
7479
+ {
7480
+ maxUserChars: 5e5,
7481
+ guardrails: `Your reply must be semantic HTML blog article(s) only, as in the system OUTPUT FORMAT (<article class="blog-post">\u2026). No JSON, no Markdown, no explanations before or after the HTML. If multiple articles, separate them with a line containing exactly ${BLOG_GENERATOR_MARKDOWN_ARTICLE_SEPARATOR} alone. Do not mention RSS, feeds, or scraping. Preserve factual accuracy; do not invent statistics or quotes.`
7482
+ },
7483
+ null,
7484
+ 2
7485
+ );
7486
+
7487
+ // src/plugins/blog-generator/blog-generator-metadata-defaults.ts
7488
+ var BLOG_METADATA_ENRICHER_LLM_AGENT_SLUG = "blog-generator-metadata";
7489
+ var BLOG_METADATA_ENRICHER_DEFAULT_VALIDATION_RULES = JSON.stringify(
7490
+ {
7491
+ maxUserChars: 5e5,
7492
+ guardrails: "Reply with one valid JSON object only (see system OUTPUT FORMAT). No markdown fences, no text before or after JSON. Keys: categoryName, blogSlug, seo { title, description, keywords, ogTitle, ogDescription }, tags (array of strings)."
7493
+ },
7494
+ null,
7495
+ 2
7496
+ );
7497
+
7498
+ // src/plugins/blog-generator/blog-generator-social-defaults.ts
7499
+ var BLOG_SOCIAL_ENRICHER_LLM_AGENT_SLUG = "blog-generator-social";
7500
+ var BLOG_SOCIAL_ENRICHER_DEFAULT_VALIDATION_RULES = JSON.stringify(
7501
+ {
7502
+ maxUserChars: 5e5,
7503
+ guardrails: "Reply with one valid JSON object only. Key: socialMediaContent (string, plain text, max ~2900 chars). No markdown fences."
7504
+ },
7505
+ null,
7506
+ 2
7507
+ );
7508
+
7509
+ // src/plugins/blog-generator/blog-generator-service.ts
7510
+ var parser = new import_rss_parser.default({
7511
+ defaultRSS: 2
7512
+ });
7513
+ function resolveBlogCategoryIdByName(needle, rows) {
7514
+ if (!needle?.trim()) return { categoryId: null, matched: false };
7515
+ const n = needle.trim().toLowerCase();
7516
+ const hit = rows.find((r) => r.name.trim().toLowerCase() === n);
7517
+ return hit ? { categoryId: hit.id, matched: true } : { categoryId: null, matched: false };
7518
+ }
7519
+
7520
+ // src/plugins/blog-generator/blog-generator-persist.ts
7521
+ function slugBase(draft) {
7522
+ const raw = (draft.slug?.trim() || draft.title || "post").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 100);
7523
+ return raw || "post";
7524
+ }
7525
+ function normalizeSlugSegment(input) {
7526
+ const t = input.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 100);
7527
+ return t || "post";
7528
+ }
7529
+ async function allocateUniqueSlug(repo, base) {
7530
+ let b = normalizeSlugSegment(base);
7531
+ for (let i = 0; i < 120; i++) {
7532
+ const s = i === 0 ? b : `${b}-${i + 1}`.slice(0, 160);
7533
+ const exists = await repo.findOne({ where: { slug: s } });
7534
+ if (!exists) return s;
7535
+ }
7536
+ throw new Error("Could not allocate a unique slug");
7537
+ }
7538
+ async function findOrCreateTag(repo, name) {
7539
+ const trimmed = name.trim().slice(0, 500);
7540
+ if (!trimmed) throw new Error("Empty tag name");
7541
+ let row = await repo.findOne({ where: { name: trimmed } });
7542
+ if (row && row.deleted) {
7543
+ await repo.update(
7544
+ { id: row.id },
7545
+ { deleted: false, updatedAt: /* @__PURE__ */ new Date() }
7546
+ );
7547
+ row = await repo.findOne({ where: { id: row.id } });
7548
+ return row;
7549
+ }
7550
+ if (row) return row;
7551
+ const now = /* @__PURE__ */ new Date();
7552
+ return repo.save(
7553
+ repo.create({
7554
+ name: trimmed,
7555
+ deleted: false,
7556
+ createdAt: now,
7557
+ updatedAt: now
7558
+ })
7559
+ );
7560
+ }
7561
+ async function findOrCreateCategory(repo, displayName) {
7562
+ const n = displayName.trim().slice(0, 500);
7563
+ if (!n) return null;
7564
+ let row = await repo.createQueryBuilder("c").where("LOWER(TRIM(c.name)) = LOWER(TRIM(:n))", { n }).getOne();
7565
+ if (row && row.deleted) {
7566
+ await repo.update(
7567
+ { id: row.id },
7568
+ { deleted: false, updatedAt: /* @__PURE__ */ new Date() }
7569
+ );
7570
+ row = await repo.findOne({ where: { id: row.id } });
7571
+ return row;
7572
+ }
7573
+ if (row) return row;
7574
+ const now = /* @__PURE__ */ new Date();
7575
+ return repo.save(
7576
+ repo.create({
7577
+ name: n,
7578
+ deleted: false,
7579
+ createdAt: now,
7580
+ updatedAt: now
7581
+ })
7582
+ );
7583
+ }
7584
+ async function persistGeneratedBlogDraft(manager, maps, params) {
7585
+ const { draft, authorId } = params;
7586
+ const blogRepo = manager.getRepository(maps.blogs);
7587
+ const tagRepo = manager.getRepository(maps.tags);
7588
+ const catRepo = manager.getRepository(maps.categories);
7589
+ const seoRepo = manager.getRepository(maps.seos);
7590
+ const uniqueBlogSlug = await allocateUniqueSlug(blogRepo, slugBase(draft));
7591
+ const uniqueSeoSlug = await allocateUniqueSlug(seoRepo, uniqueBlogSlug);
7592
+ const seoRow = await seoRepo.save(
7593
+ seoRepo.create({
7594
+ slug: uniqueSeoSlug,
7595
+ title: draft.seo.title,
7596
+ description: draft.seo.description,
7597
+ keywords: draft.seo.keywords,
7598
+ ogTitle: draft.seo.ogTitle ?? draft.seo.title,
7599
+ ogDescription: draft.seo.ogDescription ?? draft.seo.description,
7600
+ ogImage: null,
7601
+ deleted: false,
7602
+ createdAt: /* @__PURE__ */ new Date(),
7603
+ updatedAt: /* @__PURE__ */ new Date()
7604
+ })
7605
+ );
7606
+ const seoId = Number(seoRow.id);
7607
+ let categoryId = null;
7608
+ if (draft.categoryName?.trim()) {
7609
+ const cat = await findOrCreateCategory(catRepo, draft.categoryName);
7610
+ if (cat) categoryId = Number(cat.id);
7611
+ }
7612
+ const tagEntities = [];
7613
+ const seen = /* @__PURE__ */ new Set();
7614
+ for (const t of draft.tags ?? []) {
7615
+ const key = t.trim().toLowerCase();
7616
+ if (!key || seen.has(key)) continue;
7617
+ seen.add(key);
7618
+ tagEntities.push(await findOrCreateTag(tagRepo, t));
7619
+ }
7620
+ const socialTrim = typeof draft.socialMediaContent === "string" && draft.socialMediaContent.trim() ? draft.socialMediaContent.trim().slice(0, 2900) : null;
7621
+ const now = /* @__PURE__ */ new Date();
7622
+ const blogRow = await blogRepo.save(
7623
+ blogRepo.create({
7624
+ title: draft.title.trim().slice(0, 500) || "Untitled",
7625
+ content: draft.markdown,
7626
+ socialMediaContent: socialTrim,
7627
+ slug: uniqueBlogSlug,
7628
+ authorId,
7629
+ categoryId,
7630
+ seoId,
7631
+ published: false,
7632
+ deleted: false,
7633
+ coverImage: null,
7634
+ createdBy: authorId,
7635
+ updatedBy: authorId,
7636
+ createdAt: now,
7637
+ updatedAt: now
7638
+ })
7639
+ );
7640
+ const blogId = Number(blogRow.id);
7641
+ if (tagEntities.length > 0) {
7642
+ const blog = await blogRepo.findOne({
7643
+ where: { id: blogId },
7644
+ relations: ["tags"]
7645
+ });
7646
+ if (blog) {
7647
+ blog.tags = tagEntities;
7648
+ await blogRepo.save(blog);
7649
+ }
7650
+ }
7651
+ const tagIds = tagEntities.map((e) => Number(e.id)).filter((id) => Number.isFinite(id));
7652
+ return { blogId, slug: uniqueBlogSlug, categoryId, seoId, tagIds };
7653
+ }
7654
+ async function persistAllGeneratedBlogDrafts(dataSource, maps, params) {
7655
+ const out = [];
7656
+ for (const draft of params.drafts) {
7657
+ const row = await dataSource.transaction(
7658
+ (manager) => persistGeneratedBlogDraft(manager, maps, { draft, authorId: params.authorId })
7659
+ );
7660
+ out.push(row);
7661
+ }
7662
+ return out;
7663
+ }
7664
+
7665
+ // src/api/rss-feed-blog-api.ts
7666
+ function deriveRssFeedDisplayName(rssUrl) {
7667
+ try {
7668
+ const u = new URL(rssUrl);
7669
+ const h = u.hostname.replace(/^www\./i, "");
7670
+ return (h || rssUrl).slice(0, 500);
7671
+ } catch {
7672
+ return rssUrl.slice(0, 500);
7673
+ }
7674
+ }
7675
+ async function upsertRssFeedRow(dataSource, feedEntity, rssUrl, opts) {
7676
+ const repo = dataSource.getRepository(feedEntity);
7677
+ const trimmed = rssUrl.trim();
7678
+ const name = (opts?.name?.trim() || deriveRssFeedDisplayName(trimmed)).slice(0, 500);
7679
+ let row = await repo.findOne({ where: { rssUrl: trimmed } });
7680
+ if (!row) {
7681
+ row = repo.create({
7682
+ name,
7683
+ rssUrl: trimmed,
7684
+ websiteUrl: opts?.websiteUrl?.trim() || null,
7685
+ isActive: true,
7686
+ fetchFrequencyMinutes: 60
7687
+ });
7688
+ } else {
7689
+ row.name = name;
7690
+ if (opts?.websiteUrl !== void 0) {
7691
+ row.websiteUrl = opts.websiteUrl?.trim() || null;
7692
+ }
7693
+ row.updatedAt = /* @__PURE__ */ new Date();
7694
+ }
7695
+ return repo.save(row);
7696
+ }
7697
+ async function recordRssFetchSuccess(dataSource, feedEntity, articleEntity, rssUrl, latest) {
7698
+ const feedRepo = dataSource.getRepository(feedEntity);
7699
+ const row = await feedRepo.findOne({ where: { rssUrl: rssUrl.trim() } });
7700
+ if (!row) return;
7701
+ row.lastFetchedAt = /* @__PURE__ */ new Date();
7702
+ if (latest?.date) {
7703
+ row.lastArticleDate = latest.date;
7704
+ }
7705
+ row.updatedAt = /* @__PURE__ */ new Date();
7706
+ await feedRepo.save(row);
7707
+ const articleUrl = latest?.link?.trim();
7708
+ if (!latest || !articleUrl || !articleEntity) return;
7709
+ const artRepo = dataSource.getRepository(articleEntity);
7710
+ await artRepo.upsert(
7711
+ {
7712
+ rssFeedId: row.id,
7713
+ externalId: articleUrl.slice(0, 2e3),
7714
+ title: (latest.title?.trim() || "Untitled").slice(0, 2e3),
7715
+ articleUrl: articleUrl.slice(0, 2e3),
7716
+ summary: latest.summary?.trim() ? latest.summary.trim().slice(0, 1e5) : null,
7717
+ content: latest.content?.trim() ? latest.content.trim().slice(0, 5e5) : null,
7718
+ author: latest.creator?.trim() ? latest.creator.trim().slice(0, 500) : null,
7719
+ publishedAt: latest.date,
7720
+ imageUrl: null,
7721
+ rawData: { source: "blog-generator-latest", rssUrl: rssUrl.trim() },
7722
+ contentHash: null,
7723
+ isProcessed: false
7724
+ },
7725
+ { conflictPaths: ["rssFeedId", "articleUrl"], skipUpdateIfNoValuesChanged: false }
7726
+ );
7727
+ }
7728
+ function serializeRssFeed(f) {
7729
+ return {
7730
+ id: f.id,
7731
+ name: f.name,
7732
+ rssUrl: f.rssUrl,
7733
+ websiteUrl: f.websiteUrl,
7734
+ isActive: f.isActive,
7735
+ fetchFrequencyMinutes: f.fetchFrequencyMinutes,
7736
+ lastFetchedAt: f.lastFetchedAt?.toISOString() ?? null,
7737
+ lastArticleDate: f.lastArticleDate?.toISOString() ?? null,
7738
+ createdAt: f.createdAt.toISOString(),
7739
+ updatedAt: f.updatedAt.toISOString()
7740
+ };
7741
+ }
7742
+
7743
+ // src/plugins/social-media/social-media-api-handlers.ts
7744
+ var import_promises = __toESM(require("fs/promises"), 1);
7745
+ var import_node_path = __toESM(require("path"), 1);
7746
+
7747
+ // src/plugins/social-media/linkedin-client.ts
7748
+ var RESTLI = "2.0.0";
7749
+ async function linkedInGetUserinfo(accessToken) {
7750
+ const r = await fetch("https://api.linkedin.com/v2/userinfo", {
7751
+ headers: { Authorization: `Bearer ${accessToken}` }
7752
+ });
7753
+ const text = await r.text();
7754
+ if (!r.ok) {
7755
+ throw new Error(`LinkedIn userinfo failed (${r.status}): ${text.slice(0, 500)}`);
7756
+ }
7757
+ try {
7758
+ return JSON.parse(text);
7759
+ } catch {
7760
+ throw new Error("LinkedIn userinfo: invalid JSON");
7761
+ }
7762
+ }
7763
+ function personUrn(personSub) {
7764
+ const s = String(personSub || "").trim();
7765
+ if (s.startsWith("urn:li:person:")) return s;
7766
+ return `urn:li:person:${s}`;
7767
+ }
7768
+ async function linkedInRegisterImageUpload(accessToken, personSub) {
7769
+ const owner = personUrn(personSub);
7770
+ const r = await fetch("https://api.linkedin.com/v2/assets?action=registerUpload", {
7771
+ method: "POST",
7772
+ headers: {
7773
+ Authorization: `Bearer ${accessToken}`,
7774
+ "X-Restli-Protocol-Version": RESTLI,
7775
+ "Content-Type": "application/json"
7776
+ },
7777
+ body: JSON.stringify({
7778
+ registerUploadRequest: {
7779
+ recipes: ["urn:li:digitalmediaRecipe:feedshare-image"],
7780
+ owner,
7781
+ serviceRelationships: [
7782
+ {
7783
+ relationshipType: "OWNER",
7784
+ identifier: "urn:li:userGeneratedContent"
7785
+ }
7786
+ ]
7787
+ }
7788
+ })
7789
+ });
7790
+ const text = await r.text();
7791
+ if (!r.ok) {
7792
+ throw new Error(`LinkedIn registerUpload failed (${r.status}): ${text.slice(0, 800)}`);
7793
+ }
7794
+ let data;
7795
+ try {
7796
+ data = JSON.parse(text);
7797
+ } catch {
7798
+ throw new Error("LinkedIn registerUpload: invalid JSON");
7799
+ }
7800
+ const v = data.value;
7801
+ const uploadUrl = v?.uploadMechanism?.["com.linkedin.digitalmedia.uploading.MediaUploadHttpRequest"]?.uploadUrl;
7802
+ const asset = v?.asset;
7803
+ if (!uploadUrl || !asset) {
7804
+ throw new Error("LinkedIn registerUpload: missing uploadUrl or asset");
7805
+ }
7806
+ return { uploadUrl, asset };
7807
+ }
7808
+ async function linkedInUploadBinary(accessToken, uploadUrl, body, contentType) {
7809
+ const r = await fetch(uploadUrl, {
7810
+ method: "PUT",
7811
+ headers: {
7812
+ Authorization: `Bearer ${accessToken}`,
7813
+ "X-Restli-Protocol-Version": RESTLI,
7814
+ "Content-Type": contentType || "application/octet-stream"
7815
+ },
7816
+ body: new Uint8Array(body)
7817
+ });
7818
+ if (!r.ok) {
7819
+ const t = await r.text();
7820
+ throw new Error(`LinkedIn binary upload failed (${r.status}): ${t.slice(0, 500)}`);
7821
+ }
7822
+ }
7823
+ async function linkedInCreateImageShare(params) {
7824
+ const author = personUrn(params.personSub);
7825
+ const commentary = params.commentary.slice(0, 2900);
7826
+ const title = params.title.slice(0, 200);
7827
+ const description = (params.description ?? title).slice(0, 200);
7828
+ const r = await fetch("https://api.linkedin.com/v2/ugcPosts", {
7829
+ method: "POST",
7830
+ headers: {
7831
+ Authorization: `Bearer ${params.accessToken}`,
7832
+ "X-Restli-Protocol-Version": RESTLI,
7833
+ "Content-Type": "application/json"
7834
+ },
7835
+ body: JSON.stringify({
7836
+ author,
7837
+ lifecycleState: "PUBLISHED",
7838
+ specificContent: {
7839
+ "com.linkedin.ugc.ShareContent": {
7840
+ shareCommentary: { attributes: [], text: commentary },
7841
+ shareMediaCategory: "IMAGE",
7842
+ media: [
7843
+ {
7844
+ status: "READY",
7845
+ description: { attributes: [], text: description },
7846
+ media: params.asset,
7847
+ title: { attributes: [], text: title }
7848
+ }
7849
+ ]
7850
+ }
7851
+ },
7852
+ visibility: {
7853
+ "com.linkedin.ugc.MemberNetworkVisibility": "PUBLIC"
7854
+ }
7855
+ })
7856
+ });
7857
+ const text = await r.text();
7858
+ if (!r.ok) {
7859
+ throw new Error(`LinkedIn ugcPosts failed (${r.status}): ${text.slice(0, 1200)}`);
7860
+ }
7861
+ let data;
7862
+ try {
7863
+ data = JSON.parse(text);
7864
+ } catch {
7865
+ return { status: r.status };
7866
+ }
7867
+ return { status: r.status, id: data.id };
7868
+ }
7869
+ async function linkedInCreateTextShare(params) {
7870
+ const author = personUrn(params.personSub);
7871
+ const commentary = params.commentary.slice(0, 2900);
7872
+ const r = await fetch("https://api.linkedin.com/v2/ugcPosts", {
7873
+ method: "POST",
7874
+ headers: {
7875
+ Authorization: `Bearer ${params.accessToken}`,
7876
+ "X-Restli-Protocol-Version": RESTLI,
7877
+ "Content-Type": "application/json"
7878
+ },
7879
+ body: JSON.stringify({
7880
+ author,
7881
+ lifecycleState: "PUBLISHED",
7882
+ specificContent: {
7883
+ "com.linkedin.ugc.ShareContent": {
7884
+ shareCommentary: { attributes: [], text: commentary },
7885
+ shareMediaCategory: "NONE"
7886
+ }
7887
+ },
7888
+ visibility: {
7889
+ "com.linkedin.ugc.MemberNetworkVisibility": "PUBLIC"
7890
+ }
7891
+ })
7892
+ });
7893
+ const text = await r.text();
7894
+ if (!r.ok) {
7895
+ throw new Error(`LinkedIn ugcPosts (text) failed (${r.status}): ${text.slice(0, 1200)}`);
7896
+ }
7897
+ let data;
7898
+ try {
7899
+ data = JSON.parse(text);
7900
+ } catch {
7901
+ return { status: r.status };
7902
+ }
7903
+ return { status: r.status, id: data.id };
7904
+ }
7905
+
7906
+ // src/plugins/social-media/meta-service.ts
7907
+ var GRAPH_API_VERSION = "v23.0";
7908
+ var GRAPH_BASE = `https://graph.facebook.com/${GRAPH_API_VERSION}`;
7909
+ async function metaFetchUserManagedPages(userAccessToken) {
7910
+ const token = String(userAccessToken || "").trim();
7911
+ if (!token) {
7912
+ throw new Error("User access token is required");
7913
+ }
7914
+ const url = new URL(`${GRAPH_BASE}/me/accounts`);
7915
+ url.searchParams.set("access_token", token);
7916
+ const r = await fetch(url.toString(), { method: "GET", headers: { Accept: "application/json" } });
7917
+ const text = await r.text();
7918
+ let body;
7919
+ try {
7920
+ body = JSON.parse(text);
7921
+ } catch {
7922
+ throw new Error(`Facebook Graph returned invalid JSON (${r.status})`);
7923
+ }
7924
+ if (body.error?.message) {
7925
+ const code = body.error.code != null ? ` (code ${body.error.code})` : "";
7926
+ throw new Error(`${body.error.message}${code}`);
7927
+ }
7928
+ if (!r.ok) {
7929
+ throw new Error(`Facebook Graph request failed (${r.status}): ${text.slice(0, 600)}`);
7930
+ }
7931
+ return body;
7932
+ }
7933
+ function parseGraphMutation(text, httpOk) {
7934
+ let body;
7935
+ try {
7936
+ body = JSON.parse(text);
7937
+ } catch {
7938
+ throw new Error(`Facebook Graph returned invalid JSON (${httpOk ? "200" : "error"})`);
7939
+ }
7940
+ if (body.error?.message) {
7941
+ const code = body.error.code != null ? ` (code ${body.error.code})` : "";
7942
+ throw new Error(`${body.error.message}${code}`);
7943
+ }
7944
+ if (!httpOk) {
7945
+ throw new Error(`Facebook Graph request failed: ${text.slice(0, 600)}`);
7946
+ }
7947
+ return body;
7948
+ }
7949
+ function metaResolvePageAccessToken(accounts, pageId) {
7950
+ const id = String(pageId || "").trim();
7951
+ if (!id || !accounts.data?.length) return null;
7952
+ const row = accounts.data.find((p) => String(p.id ?? "").trim() === id);
7953
+ const tok = row?.access_token;
7954
+ return typeof tok === "string" && tok.trim() ? tok.trim() : null;
7955
+ }
7956
+ async function metaPostPagePhoto(opts) {
7957
+ const { pageId, pageAccessToken, imageBuffer, contentType, caption } = opts;
7958
+ const pid = String(pageId || "").trim();
7959
+ const tok = String(pageAccessToken || "").trim();
7960
+ if (!pid || !tok) throw new Error("Page ID and page access token are required");
7961
+ if (!imageBuffer?.length) throw new Error("Image buffer is empty");
7962
+ const url = `${GRAPH_BASE}/${encodeURIComponent(pid)}/photos`;
7963
+ const lower = contentType.split(";")[0].trim().toLowerCase();
7964
+ const filename = lower.includes("png") ? "photo.png" : "photo.jpg";
7965
+ const blob = new Blob([new Uint8Array(imageBuffer)], { type: lower || "image/jpeg" });
7966
+ const form = new FormData();
7967
+ form.append("access_token", tok);
7968
+ form.append("caption", String(caption || "").trim().slice(0, 2200));
7969
+ form.append("source", blob, filename);
7970
+ const r = await fetch(url, { method: "POST", body: form });
7971
+ const text = await r.text();
7972
+ return parseGraphMutation(text, r.ok);
7973
+ }
7974
+ async function metaPostPageFeed(opts) {
7975
+ const { pageId, pageAccessToken, message } = opts;
7976
+ const pid = String(pageId || "").trim();
7977
+ const tok = String(pageAccessToken || "").trim();
7978
+ if (!pid || !tok) throw new Error("Page ID and page access token are required");
7979
+ const url = new URL(`${GRAPH_BASE}/${encodeURIComponent(pid)}/feed`);
7980
+ const params = new URLSearchParams();
7981
+ params.set("access_token", tok);
7982
+ params.set("message", String(message || "").trim().slice(0, 5e3));
7983
+ const r = await fetch(url.toString(), {
7984
+ method: "POST",
7985
+ headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
7986
+ body: params.toString()
7987
+ });
7988
+ const text = await r.text();
7989
+ return parseGraphMutation(text, r.ok);
7990
+ }
7991
+
7992
+ // src/plugins/social-media/social-media-api-handlers.ts
7993
+ var SETTINGS_GROUP = "social_media";
7994
+ async function loadGroupMap(dataSource, entityMap, encryptionKey) {
7995
+ if (!entityMap.configs) return {};
7996
+ const repo = dataSource.getRepository(entityMap.configs);
7997
+ const rows = await repo.find({ where: { settings: SETTINGS_GROUP, deleted: false } });
7998
+ const out = {};
7999
+ for (const row of rows) {
8000
+ let val = row.value;
8001
+ if (row.encrypted && encryptionKey) {
8002
+ try {
8003
+ val = simpleDecrypt(val, encryptionKey);
8004
+ } catch {
8005
+ }
8006
+ }
8007
+ out[row.key] = val;
8008
+ }
8009
+ return out;
8010
+ }
8011
+ async function upsertConfigKey(dataSource, entityMap, key, value, opts) {
8012
+ if (!entityMap.configs) throw new Error("configs entity missing");
8013
+ const repo = dataSource.getRepository(entityMap.configs);
8014
+ let stored = value;
8015
+ if (opts.encrypted && opts.encryptionKey) {
8016
+ stored = simpleEncrypt(value, opts.encryptionKey);
8017
+ }
8018
+ const existing = await repo.findOne({ where: { settings: SETTINGS_GROUP, key } });
8019
+ if (existing) {
8020
+ await repo.update(existing.id, {
8021
+ value: stored,
8022
+ type: opts.type,
8023
+ encrypted: opts.encrypted,
8024
+ updatedAt: /* @__PURE__ */ new Date()
8025
+ });
8026
+ } else {
8027
+ await repo.save(
8028
+ repo.create({
8029
+ settings: SETTINGS_GROUP,
8030
+ key,
8031
+ value: stored,
8032
+ type: opts.type,
8033
+ encrypted: opts.encrypted
8034
+ })
8035
+ );
8036
+ }
8037
+ }
8038
+ function stripHtml(html, maxLen) {
8039
+ const t = String(html || "").replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<style[\s\S]*?<\/style>/gi, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
8040
+ return t.slice(0, maxLen);
8041
+ }
8042
+ function decodeHtmlAttr(s) {
8043
+ return String(s).replace(/&amp;/gi, "&").replace(/&quot;/gi, '"').replace(/&#39;/g, "'").replace(/&lt;/gi, "<").replace(/&gt;/gi, ">");
8044
+ }
8045
+ function extractFirstImageSrcFromHtml(html) {
8046
+ const s = String(html);
8047
+ const patterns = [
8048
+ /<img[^>]*\bsrc\s*=\s*["']([^"']+)["']/i,
8049
+ /<img[^>]*\bsrc\s*=\s*([^\s>]+)/i,
8050
+ /<img[^>]*\bdata-src\s*=\s*["']([^"']+)["']/i,
8051
+ /<img[^>]*\bdata-src\s*=\s*([^\s>]+)/i
8052
+ ];
8053
+ for (const re of patterns) {
8054
+ const m = s.match(re);
8055
+ const raw = m?.[1]?.trim();
8056
+ if (raw) return decodeHtmlAttr(raw);
8057
+ }
8058
+ return null;
8059
+ }
8060
+ function resolveAbsoluteUrl(publicSiteUrl, pathOrUrl) {
8061
+ if (!pathOrUrl || !String(pathOrUrl).trim()) return null;
8062
+ const s = decodeHtmlAttr(String(pathOrUrl).trim());
8063
+ if (/^https?:\/\//i.test(s)) return s;
8064
+ if (s.startsWith("//")) return `https:${s}`;
8065
+ const base = (publicSiteUrl || "").replace(/\/+$/, "");
8066
+ if (!base) return null;
8067
+ const rel = s.startsWith("/") ? s : `/${s}`;
8068
+ return `${base}${rel}`;
8069
+ }
8070
+ function guessImageContentType(filePathOrUrl, buf) {
8071
+ const lower = filePathOrUrl.toLowerCase();
8072
+ if (lower.includes(".png")) return "image/png";
8073
+ if (lower.includes(".webp")) return "image/webp";
8074
+ if (lower.includes(".gif")) return "image/gif";
8075
+ if (lower.includes(".jpg") || lower.includes(".jpeg")) return "image/jpeg";
8076
+ if (buf.length >= 2 && buf[0] === 255 && buf[1] === 216) return "image/jpeg";
8077
+ if (buf.length >= 8 && buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71) return "image/png";
8078
+ if (buf.length >= 12 && buf[8] === 87 && buf[9] === 69 && buf[10] === 66 && buf[11] === 80) return "image/webp";
8079
+ return "image/jpeg";
8080
+ }
8081
+ function isLinkedInFeedshareCompatibleContentType(mime) {
8082
+ const m = mime.split(";")[0].trim().toLowerCase();
8083
+ return m === "image/jpeg" || m === "image/png";
8084
+ }
8085
+ async function loadImageBytesForBlogShare(coverImage, contentHtml, publicSiteUrl, req) {
8086
+ const origin = publicSiteUrl || inferRequestOrigin(req) || "";
8087
+ const fromHtml = extractFirstImageSrcFromHtml(contentHtml);
8088
+ const candidates = [];
8089
+ const seenDecoded = /* @__PURE__ */ new Set();
8090
+ const pushCand = (role, raw) => {
8091
+ const decoded = decodeHtmlAttr(raw.trim());
8092
+ if (!decoded || decoded.startsWith("data:")) return;
8093
+ if (seenDecoded.has(decoded)) return;
8094
+ seenDecoded.add(decoded);
8095
+ candidates.push({ role, raw });
8096
+ };
8097
+ if (coverImage?.trim()) pushCand("cover", coverImage.trim());
8098
+ if (fromHtml) pushCand("bodyImage", fromHtml);
8099
+ for (const { role, raw } of candidates) {
8100
+ const decoded = decodeHtmlAttr(raw.trim());
8101
+ if (decoded.startsWith("data:")) continue;
8102
+ if (decoded.startsWith("/uploads/") || decoded.startsWith("uploads/")) {
8103
+ const rel = decoded.startsWith("/") ? decoded.slice(1) : decoded;
8104
+ const tries = [import_node_path.default.join(process.cwd(), "public", rel), import_node_path.default.join(process.cwd(), rel)];
8105
+ for (const filePath of tries) {
8106
+ try {
8107
+ const buf = await import_promises.default.readFile(filePath);
8108
+ if (buf.length > 0) {
8109
+ const contentType = guessImageContentType(filePath, buf);
8110
+ if (!isLinkedInFeedshareCompatibleContentType(contentType)) {
8111
+ continue;
8112
+ }
8113
+ return {
8114
+ buffer: buf,
8115
+ contentType,
8116
+ source: `${role}:local_file:${truncateForLog(rel, 120)}`
8117
+ };
8118
+ }
8119
+ } catch {
8120
+ }
8121
+ }
8122
+ }
8123
+ const url = resolveAbsoluteUrl(origin, decoded);
8124
+ if (!url) continue;
8125
+ try {
8126
+ const imgRes = await fetch(url, {
8127
+ headers: {
8128
+ "User-Agent": "InfuroCMS/1.0 (social share; +https://infuro.com)",
8129
+ Accept: "image/*,*/*;q=0.5"
8130
+ }
8131
+ });
8132
+ if (!imgRes.ok) continue;
8133
+ const buf = Buffer.from(await imgRes.arrayBuffer());
8134
+ if (buf.length === 0) continue;
8135
+ const hdr = imgRes.headers.get("content-type");
8136
+ const contentType = hdr && /^image\//i.test(hdr) ? hdr.split(";")[0].trim() : guessImageContentType(url, buf);
8137
+ if (!isLinkedInFeedshareCompatibleContentType(contentType)) {
8138
+ continue;
8139
+ }
8140
+ return {
8141
+ buffer: buf,
8142
+ contentType,
8143
+ source: `${role}:fetch:${truncateForLog(url, 160)}`
8144
+ };
8145
+ } catch {
8146
+ }
8147
+ }
8148
+ return null;
8149
+ }
8150
+ function inferRequestOrigin(req) {
8151
+ const host = req.headers.get("x-forwarded-host") || req.headers.get("host");
8152
+ if (!host) return void 0;
8153
+ const rawProto = req.headers.get("x-forwarded-proto") || "https";
8154
+ const proto = rawProto.split(",")[0]?.trim() || "https";
8155
+ return `${proto}://${host}`.replace(/\/+$/, "");
8156
+ }
8157
+ function linkedInPublishLoggingEnabled(config) {
8158
+ if (config.linkedInPublishLogging === false) return false;
8159
+ if (config.linkedInPublishLogging === true) return true;
8160
+ try {
8161
+ if (typeof process !== "undefined" && String(process.env.CMS_LINKEDIN_PUBLISH_LOG).trim() === "0") {
8162
+ return false;
8163
+ }
8164
+ } catch {
8165
+ }
8166
+ return true;
8167
+ }
8168
+ function logLinkedInPublish(enabled, event, detail) {
8169
+ if (!enabled) return;
8170
+ const line = `[LinkedIn publish] ${event} ${JSON.stringify(detail)}`;
8171
+ if (event === "failed" || event === "image_path_failed_falling_back_to_text") {
8172
+ console.warn(line);
8173
+ } else {
8174
+ console.info(line);
8175
+ }
8176
+ }
8177
+ function truncateForLog(s, max = 160) {
8178
+ const t = String(s).replace(/\s+/g, " ").trim();
8179
+ if (t.length <= max) return t;
8180
+ return `${t.slice(0, max)}\u2026`;
8181
+ }
8182
+ function createSocialMediaHandlers(config) {
8183
+ const {
8184
+ dataSource,
8185
+ entityMap,
8186
+ json,
8187
+ requireAuth,
8188
+ requireEntityPermission,
8189
+ encryptionKey,
8190
+ publicSiteUrl
8191
+ } = config;
8192
+ const publishLog = linkedInPublishLoggingEnabled(config);
8193
+ return {
8194
+ async getLinkedInStatus(req) {
8195
+ const a = await requireAuth(req);
8196
+ if (a) return a;
8197
+ const pe = await requireEntityPermission(req, "blogs", "read");
8198
+ if (pe) return pe;
8199
+ try {
8200
+ const map = await loadGroupMap(dataSource, entityMap, encryptionKey);
8201
+ const enabled = map.enabled !== "false";
8202
+ const token = (map.linkedin_access_token ?? "").trim();
8203
+ const sub = (map.linkedin_person_sub ?? "").trim();
8204
+ return json({
8205
+ ok: true,
8206
+ linkedInReady: enabled && Boolean(token) && Boolean(sub),
8207
+ enabled,
8208
+ hasToken: Boolean(token),
8209
+ hasPersonSub: Boolean(sub)
8210
+ });
8211
+ } catch (e) {
8212
+ const msg = e instanceof Error ? e.message : "Failed to load status";
8213
+ return json({ error: msg }, { status: 500 });
8214
+ }
8215
+ },
8216
+ async syncLinkedInProfile(req) {
8217
+ const a = await requireAuth(req);
8218
+ if (a) return a;
8219
+ const pe = await requireEntityPermission(req, "blogs", "read");
8220
+ if (pe) return pe;
8221
+ try {
8222
+ const map = await loadGroupMap(dataSource, entityMap, encryptionKey);
8223
+ const token = (map.linkedin_access_token ?? "").trim();
8224
+ if (!token) {
8225
+ return json({ error: "Save a LinkedIn access token in Plugins \u2192 Social media first." }, { status: 400 });
8226
+ }
8227
+ const info = await linkedInGetUserinfo(token);
8228
+ const sub = String(info.sub ?? "").trim();
8229
+ if (!sub) {
8230
+ return json({ error: "LinkedIn userinfo did not return a subject (sub)." }, { status: 502 });
8231
+ }
8232
+ await upsertConfigKey(dataSource, entityMap, "linkedin_person_sub", sub, {
8233
+ type: "private",
8234
+ encrypted: false,
8235
+ encryptionKey
8236
+ });
8237
+ return json({
8238
+ ok: true,
8239
+ linkedin_person_sub: sub,
8240
+ name: info.name ?? null
8241
+ });
8242
+ } catch (e) {
8243
+ const msg = e instanceof Error ? e.message : "Sync failed";
8244
+ return json({ error: msg }, { status: 502 });
8245
+ }
8246
+ },
8247
+ async publishBlogToLinkedIn(req, blogId) {
8248
+ const a = await requireAuth(req);
8249
+ if (a) return a;
8250
+ const pe = await requireEntityPermission(req, "blogs", "update");
8251
+ if (pe) return pe;
8252
+ if (!Number.isFinite(blogId) || blogId < 1) {
8253
+ return json({ error: "Invalid blog id" }, { status: 400 });
8254
+ }
8255
+ if (!entityMap.blogs) {
8256
+ return json({ error: "Blogs entity not configured" }, { status: 503 });
8257
+ }
8258
+ try {
8259
+ const map = await loadGroupMap(dataSource, entityMap, encryptionKey);
8260
+ if (map.enabled === "false") {
8261
+ return json({ error: "Social media plugin is disabled." }, { status: 400 });
8262
+ }
8263
+ const token = (map.linkedin_access_token ?? "").trim();
8264
+ const personSub = (map.linkedin_person_sub ?? "").trim();
8265
+ if (!token || !personSub) {
8266
+ return json(
8267
+ { error: "Configure LinkedIn access token and sync profile (Plugins \u2192 Social media)." },
8268
+ { status: 400 }
8269
+ );
8270
+ }
8271
+ const blogRepo = dataSource.getRepository(entityMap.blogs);
8272
+ const blog = await blogRepo.findOne({
8273
+ where: { id: blogId, deleted: false }
8274
+ });
8275
+ if (!blog) {
8276
+ return json({ error: "Blog not found" }, { status: 404 });
8277
+ }
8278
+ const b = blog;
8279
+ const title = String(b.title ?? "Blog").trim() || "Blog";
8280
+ const social = String(b.socialMediaContent ?? "").trim();
8281
+ const excerpt = stripHtml(String(b.content ?? ""), 2800);
8282
+ const commentary = (social || `${title}
8283
+
8284
+ ${excerpt}`).trim().slice(0, 2900);
8285
+ const commentaryFrom = social ? "socialMediaContent" : "titleAndExcerpt";
8286
+ logLinkedInPublish(publishLog, "start", {
8287
+ blogId,
8288
+ titleLen: title.length,
8289
+ commentaryFrom,
8290
+ commentaryChars: commentary.length,
8291
+ hasCover: Boolean(b.coverImage?.trim()),
8292
+ contentHtmlChars: String(b.content ?? "").length
8293
+ });
8294
+ const imagePayload = await loadImageBytesForBlogShare(
8295
+ b.coverImage,
8296
+ String(b.content ?? ""),
8297
+ publicSiteUrl || inferRequestOrigin(req),
8298
+ req
8299
+ );
8300
+ if (imagePayload) {
8301
+ logLinkedInPublish(publishLog, "image_bytes_ready", {
8302
+ blogId,
8303
+ source: imagePayload.source,
8304
+ bytes: imagePayload.buffer.length,
8305
+ contentType: imagePayload.contentType
8306
+ });
8307
+ try {
8308
+ const { uploadUrl, asset } = await linkedInRegisterImageUpload(token, personSub);
8309
+ logLinkedInPublish(publishLog, "image_registered", {
8310
+ blogId,
8311
+ asset: truncateForLog(asset, 120),
8312
+ uploadHost: (() => {
8313
+ try {
8314
+ return new URL(uploadUrl).host;
8315
+ } catch {
8316
+ return "unknown";
8317
+ }
8318
+ })()
8319
+ });
8320
+ await linkedInUploadBinary(token, uploadUrl, imagePayload.buffer, imagePayload.contentType);
8321
+ logLinkedInPublish(publishLog, "image_uploaded", { blogId, bytes: imagePayload.buffer.length });
8322
+ const out2 = await linkedInCreateImageShare({
8323
+ accessToken: token,
8324
+ personSub,
8325
+ asset,
8326
+ commentary,
8327
+ title,
8328
+ description: title
8329
+ });
8330
+ logLinkedInPublish(publishLog, "success", {
8331
+ blogId,
8332
+ mode: "image_plus_text",
8333
+ linkedinPostId: out2.id ?? null,
8334
+ commentaryFrom,
8335
+ commentaryChars: commentary.length,
8336
+ imageSource: imagePayload.source
8337
+ });
8338
+ return json({
8339
+ ok: true,
8340
+ linkedin: out2,
8341
+ usedImage: true,
8342
+ publishSummary: {
8343
+ mode: "image_plus_text",
8344
+ commentaryFrom,
8345
+ commentaryChars: commentary.length,
8346
+ image: {
8347
+ bytes: imagePayload.buffer.length,
8348
+ contentType: imagePayload.contentType,
8349
+ source: imagePayload.source
8350
+ },
8351
+ linkedinPostId: out2.id ?? null
8352
+ }
8353
+ });
8354
+ } catch (imgErr) {
8355
+ const imgMsg = imgErr instanceof Error ? imgErr.message : String(imgErr);
8356
+ logLinkedInPublish(publishLog, "image_path_failed_falling_back_to_text", {
8357
+ blogId,
8358
+ message: truncateForLog(imgMsg, 500)
8359
+ });
8360
+ const out2 = await linkedInCreateTextShare({
8361
+ accessToken: token,
8362
+ personSub,
8363
+ commentary
8364
+ });
8365
+ logLinkedInPublish(publishLog, "success", {
8366
+ blogId,
8367
+ mode: "text_only_after_image_failure",
8368
+ linkedinPostId: out2.id ?? null,
8369
+ commentaryFrom,
8370
+ commentaryChars: commentary.length,
8371
+ imageError: truncateForLog(imgMsg, 400)
8372
+ });
8373
+ return json({
8374
+ ok: true,
8375
+ linkedin: out2,
8376
+ usedImage: false,
8377
+ usedTextOnly: true,
8378
+ imagePathError: imgMsg,
8379
+ publishSummary: {
8380
+ mode: "text_only_after_image_failure",
8381
+ commentaryFrom,
8382
+ commentaryChars: commentary.length,
8383
+ linkedinPostId: out2.id ?? null,
8384
+ imageAttempt: {
8385
+ bytes: imagePayload.buffer.length,
8386
+ contentType: imagePayload.contentType,
8387
+ source: imagePayload.source
8388
+ }
8389
+ }
8390
+ });
8391
+ }
8392
+ }
8393
+ logLinkedInPublish(publishLog, "no_image_bytes", {
8394
+ blogId,
8395
+ commentaryFrom,
8396
+ commentaryChars: commentary.length,
8397
+ note: "posting_text_only"
8398
+ });
8399
+ const out = await linkedInCreateTextShare({
8400
+ accessToken: token,
8401
+ personSub,
8402
+ commentary
8403
+ });
8404
+ logLinkedInPublish(publishLog, "success", {
8405
+ blogId,
8406
+ mode: "text_only",
8407
+ linkedinPostId: out.id ?? null,
8408
+ commentaryFrom,
8409
+ commentaryChars: commentary.length
8410
+ });
8411
+ return json({
8412
+ ok: true,
8413
+ linkedin: out,
8414
+ usedTextOnly: true,
8415
+ publishSummary: {
8416
+ mode: "text_only",
8417
+ commentaryFrom,
8418
+ commentaryChars: commentary.length,
8419
+ linkedinPostId: out.id ?? null
8420
+ },
8421
+ hint: "No image bytes loaded, or only non-JPEG/PNG images were found. Use JPEG or PNG for cover or an inline <img>, set CMS_PUBLIC_URL for absolute URLs, or paths under /uploads/. LinkedIn feedshare uploads expect JPEG or PNG."
8422
+ });
8423
+ } catch (e) {
8424
+ const msg = e instanceof Error ? e.message : "Publish failed";
8425
+ logLinkedInPublish(publishLog, "failed", { blogId, message: truncateForLog(msg, 600) });
8426
+ return json({ error: msg }, { status: 502 });
8427
+ }
8428
+ },
8429
+ async getFacebookStatus(req) {
8430
+ const a = await requireAuth(req);
8431
+ if (a) return a;
8432
+ const pe = await requireEntityPermission(req, "blogs", "read");
8433
+ if (pe) return pe;
8434
+ try {
8435
+ const map = await loadGroupMap(dataSource, entityMap, encryptionKey);
8436
+ const enabled = map.enabled !== "false";
8437
+ const userToken = (map.meta_user_access_token ?? "").trim();
8438
+ const pageId = (map.meta_facebook_page_id ?? "").trim();
8439
+ let hasPageToken = false;
8440
+ if (enabled && userToken && pageId) {
8441
+ try {
8442
+ const accounts = await metaFetchUserManagedPages(userToken);
8443
+ hasPageToken = Boolean(metaResolvePageAccessToken(accounts, pageId));
8444
+ } catch {
8445
+ hasPageToken = false;
8446
+ }
8447
+ }
8448
+ const facebookReady = enabled && Boolean(userToken) && Boolean(pageId);
8449
+ return json({
8450
+ ok: true,
8451
+ facebookReady,
8452
+ enabled,
8453
+ hasUserToken: Boolean(userToken),
8454
+ hasPageId: Boolean(pageId),
8455
+ hasPageToken
8456
+ });
8457
+ } catch (e) {
8458
+ const msg = e instanceof Error ? e.message : "Failed to load status";
8459
+ return json({ error: msg }, { status: 500 });
8460
+ }
8461
+ },
8462
+ async publishBlogToFacebook(req, blogId) {
8463
+ const a = await requireAuth(req);
8464
+ if (a) return a;
8465
+ const pe = await requireEntityPermission(req, "blogs", "update");
8466
+ if (pe) return pe;
8467
+ if (!Number.isFinite(blogId) || blogId < 1) {
8468
+ return json({ error: "Invalid blog id" }, { status: 400 });
8469
+ }
8470
+ if (!entityMap.blogs) {
8471
+ return json({ error: "Blogs entity not configured" }, { status: 503 });
8472
+ }
8473
+ try {
8474
+ const map = await loadGroupMap(dataSource, entityMap, encryptionKey);
8475
+ if (map.enabled === "false") {
8476
+ return json({ error: "Social media plugin is disabled." }, { status: 400 });
8477
+ }
8478
+ const userToken = (map.meta_user_access_token ?? "").trim();
8479
+ const pageId = (map.meta_facebook_page_id ?? "").trim();
8480
+ if (!userToken || !pageId) {
8481
+ return json(
8482
+ {
8483
+ error: "Configure Meta user access token and Facebook Page ID in Plugins \u2192 Social media \u2192 Facebook, then save."
8484
+ },
8485
+ { status: 400 }
8486
+ );
8487
+ }
8488
+ let accounts;
8489
+ try {
8490
+ accounts = await metaFetchUserManagedPages(userToken);
8491
+ } catch (e) {
8492
+ const msg = e instanceof Error ? e.message : "Failed to load Facebook pages";
8493
+ return json({ error: msg }, { status: 502 });
8494
+ }
8495
+ const pageAccessToken = metaResolvePageAccessToken(accounts, pageId);
8496
+ if (!pageAccessToken) {
8497
+ return json(
8498
+ {
8499
+ error: "No page access token for the saved Page ID. In Plugins \u2192 Facebook, use Fetch managed pages, copy a Page `id` you manage, set Target Page ID, and save."
8500
+ },
8501
+ { status: 400 }
8502
+ );
8503
+ }
8504
+ const blogRepo = dataSource.getRepository(entityMap.blogs);
8505
+ const blog = await blogRepo.findOne({
8506
+ where: { id: blogId, deleted: false }
8507
+ });
8508
+ if (!blog) {
8509
+ return json({ error: "Blog not found" }, { status: 404 });
8510
+ }
8511
+ const b = blog;
8512
+ const title = String(b.title ?? "Blog").trim() || "Blog";
8513
+ const social = String(b.socialMediaContent ?? "").trim();
8514
+ const excerpt = stripHtml(String(b.content ?? ""), 2800);
8515
+ const caption = (social || `${title}
8516
+
8517
+ ${excerpt}`).trim().slice(0, 2200);
8518
+ const imagePayload = await loadImageBytesForBlogShare(
8519
+ b.coverImage,
8520
+ String(b.content ?? ""),
8521
+ publicSiteUrl || inferRequestOrigin(req),
8522
+ req
8523
+ );
8524
+ if (imagePayload) {
8525
+ const out2 = await metaPostPagePhoto({
8526
+ pageId,
8527
+ pageAccessToken,
8528
+ imageBuffer: imagePayload.buffer,
8529
+ contentType: imagePayload.contentType,
8530
+ caption
8531
+ });
8532
+ return json({
8533
+ ok: true,
8534
+ facebook: out2,
8535
+ usedImage: true,
8536
+ publishSummary: {
8537
+ mode: "page_photo",
8538
+ captionChars: caption.length,
8539
+ imageSource: imagePayload.source,
8540
+ facebookPostId: out2.post_id ?? out2.id ?? null
8541
+ }
8542
+ });
8543
+ }
8544
+ const out = await metaPostPageFeed({
8545
+ pageId,
8546
+ pageAccessToken,
8547
+ message: caption
8548
+ });
8549
+ return json({
8550
+ ok: true,
8551
+ facebook: out,
8552
+ usedImage: false,
8553
+ usedFeed: true,
8554
+ publishSummary: {
8555
+ mode: "page_feed_text",
8556
+ messageChars: caption.length,
8557
+ facebookPostId: out.id ?? null
8558
+ },
8559
+ hint: "Posted as a Page text update (no JPEG/PNG cover or body image found). Add a JPEG/PNG cover or inline image for a photo post."
8560
+ });
8561
+ } catch (e) {
8562
+ const msg = e instanceof Error ? e.message : "Facebook publish failed";
8563
+ return json({ error: msg }, { status: 502 });
8564
+ }
8565
+ },
8566
+ /**
8567
+ * POST JSON `{ appId, appSecret, userAccessToken }`. Calls Graph `GET /me/accounts` with the user token.
8568
+ * App ID and secret are validated but not used yet (reserved for future token flows).
8569
+ * Persist credentials via Plugins → Save: `meta_app_id` (public), `meta_app_secret` and `meta_user_access_token` (encrypted).
8570
+ */
8571
+ async fetchFacebookManagedPages(req) {
8572
+ const a = await requireAuth(req);
8573
+ if (a) return a;
8574
+ const pe = await requireEntityPermission(req, "settings", "read");
8575
+ if (pe) return pe;
8576
+ let body;
8577
+ try {
8578
+ body = await req.json();
8579
+ } catch {
8580
+ return json({ error: "Invalid JSON body" }, { status: 400 });
8581
+ }
8582
+ const appId = String(body?.appId ?? "").trim();
8583
+ const appSecret = String(body?.appSecret ?? "").trim();
8584
+ const userAccessToken = String(body?.userAccessToken ?? "").trim();
8585
+ if (!appId || !appSecret || !userAccessToken) {
8586
+ return json(
8587
+ { error: "App ID, app secret, and user access token are required." },
8588
+ { status: 400 }
8589
+ );
8590
+ }
8591
+ void appId;
8592
+ void appSecret;
8593
+ try {
8594
+ const graph = await metaFetchUserManagedPages(userAccessToken);
8595
+ return json({ ok: true, graph });
8596
+ } catch (e) {
8597
+ const msg = e instanceof Error ? e.message : "Facebook Graph request failed";
8598
+ return json({ error: msg }, { status: 502 });
8599
+ }
8600
+ }
8601
+ };
8602
+ }
8603
+
7272
8604
  // src/api/cms-api-handler.ts
7273
8605
  var KNOWLEDGE_SUFFIX = "knowledge";
7274
8606
  var CMS_API_LOG = "[cms-api]";
@@ -7277,7 +8609,9 @@ function withLlmKnowledgeEntityFallbacks(base) {
7277
8609
  "llm_agents",
7278
8610
  "llm_agent_knowledge_documents",
7279
8611
  "knowledge_base_documents",
7280
- "knowledge_base_chunks"
8612
+ "knowledge_base_chunks",
8613
+ "rss_feeds",
8614
+ "rss_articles"
7281
8615
  ];
7282
8616
  const patch = {};
7283
8617
  for (const key of keys) {
@@ -7293,8 +8627,8 @@ function withLlmKnowledgeEntityFallbacks(base) {
7293
8627
  }
7294
8628
  return merged;
7295
8629
  }
7296
- function matchLlmAgentKnowledgeRoute(path) {
7297
- const p = path[0] === "api" ? path.slice(1) : path;
8630
+ function matchLlmAgentKnowledgeRoute(path2) {
8631
+ const p = path2[0] === "api" ? path2.slice(1) : path2;
7298
8632
  if (p[0] !== "llm_agents" || p.length < 2) return null;
7299
8633
  const seg1 = p[1];
7300
8634
  if (!seg1) return null;
@@ -7325,7 +8659,9 @@ var DEFAULT_EXCLUDE = /* @__PURE__ */ new Set([
7325
8659
  "wishlists",
7326
8660
  "wishlist_items",
7327
8661
  "message_templates",
7328
- "llm_agent_knowledge_documents"
8662
+ "llm_agent_knowledge_documents",
8663
+ "rss_feeds",
8664
+ "rss_articles"
7329
8665
  ]);
7330
8666
  function createCmsApiHandler(config) {
7331
8667
  const {
@@ -7452,6 +8788,23 @@ function createCmsApiHandler(config) {
7452
8788
  const avatarPost = userAvatar ? createUserAvatarHandler(userAvatar) : null;
7453
8789
  const profileHandlers = userProfile ? createUserProfileHandler(userProfile) : null;
7454
8790
  const settingsHandlers = settingsConfig ? createSettingsApiHandlers(settingsConfig) : null;
8791
+ function resolvePublicSiteUrlForSocial() {
8792
+ if (typeof process === "undefined") return void 0;
8793
+ const u = (process.env.CMS_PUBLIC_URL || process.env.NEXT_PUBLIC_SITE_URL || "").trim();
8794
+ if (u) return u.replace(/\/+$/, "");
8795
+ const v = (process.env.VERCEL_URL || "").trim();
8796
+ if (!v) return void 0;
8797
+ return v.startsWith("http") ? v.replace(/\/+$/, "") : `https://${v.replace(/\/+$/, "")}`;
8798
+ }
8799
+ const socialMediaHandlers = settingsConfig?.dataSource && entityMap.configs ? createSocialMediaHandlers({
8800
+ dataSource: settingsConfig.dataSource,
8801
+ entityMap,
8802
+ json: config.json,
8803
+ requireAuth: config.requireAuth,
8804
+ requireEntityPermission: requireEntityPermissionEffective,
8805
+ encryptionKey: settingsConfig.encryptionKey,
8806
+ publicSiteUrl: resolvePublicSiteUrlForSocial()
8807
+ }) : null;
7455
8808
  const smsMessageTemplateHandlers = createSmsMessageTemplateHandlers({
7456
8809
  dataSource,
7457
8810
  entityMap,
@@ -7478,95 +8831,526 @@ function createCmsApiHandler(config) {
7478
8831
  return {
7479
8832
  async handle(method, pathInput, req) {
7480
8833
  const m = typeof method === "string" ? method.toUpperCase() : "GET";
7481
- const path = pathInput.length > 0 && pathInput[0] === "api" ? pathInput.slice(1) : pathInput;
8834
+ const path2 = pathInput.length > 0 && pathInput[0] === "api" ? pathInput.slice(1) : pathInput;
7482
8835
  async function analyticsGate() {
7483
8836
  const a = await config.requireAuth(req);
7484
8837
  if (a) return a;
7485
8838
  return requireEntityPermissionEffective(req, "analytics", "read");
7486
8839
  }
7487
- if (path[0] === "admin" && path[1] === "roles") {
8840
+ if (path2[0] === "admin" && path2[1] === "roles") {
7488
8841
  if (!adminRoles) return config.json({ error: "Not found" }, { status: 404 });
7489
- if (path.length === 2 && m === "GET") return adminRoles.list();
7490
- if (path.length === 2 && m === "POST") return adminRoles.createGroup(req);
7491
- if (path.length === 3 && m === "PATCH") return adminRoles.patchGroup(req, path[2]);
7492
- if (path.length === 3 && m === "DELETE") return adminRoles.deleteGroup(path[2]);
7493
- if (path.length === 4 && path[3] === "permissions" && m === "PUT") return adminRoles.putPermissions(req, path[2]);
8842
+ if (path2.length === 2 && m === "GET") return adminRoles.list();
8843
+ if (path2.length === 2 && m === "POST") return adminRoles.createGroup(req);
8844
+ if (path2.length === 3 && m === "PATCH") return adminRoles.patchGroup(req, path2[2]);
8845
+ if (path2.length === 3 && m === "DELETE") return adminRoles.deleteGroup(path2[2]);
8846
+ if (path2.length === 4 && path2[3] === "permissions" && m === "PUT") return adminRoles.putPermissions(req, path2[2]);
7494
8847
  return config.json({ error: "Not found" }, { status: 404 });
7495
8848
  }
7496
- if (path[0] === "dashboard" && path[1] === "stats" && path.length === 2 && m === "GET" && dashboardGet) {
8849
+ if (path2[0] === "dashboard" && path2[1] === "stats" && path2.length === 2 && m === "GET" && dashboardGet) {
7497
8850
  return dashboardGet(req);
7498
8851
  }
7499
- if (path[0] === "dashboard" && path[1] === "ecommerce" && path.length === 2 && m === "GET" && ecommerceAnalyticsGet) {
8852
+ if (path2[0] === "dashboard" && path2[1] === "ecommerce" && path2.length === 2 && m === "GET" && ecommerceAnalyticsGet) {
7500
8853
  const g = await analyticsGate();
7501
8854
  if (g) return g;
7502
8855
  return ecommerceAnalyticsGet(req);
7503
8856
  }
7504
- if (path[0] === "analytics" && analyticsHandlers) {
7505
- if (path.length === 1 && m === "GET") {
8857
+ if (path2[0] === "blog-generator" && path2[1] === "feeds" && path2.length === 3 && m === "DELETE" && getCms) {
8858
+ const id = path2[2];
8859
+ 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);
8860
+ const a = await config.requireAuth(req);
8861
+ if (a) return a;
8862
+ const pe = await requireEntityPermissionEffective(req, "blogs", "read");
8863
+ if (pe) return pe;
8864
+ if (!entityMap.rss_feeds) {
8865
+ return config.json({ error: "RSS feeds are not configured (missing entity map)." }, { status: 503 });
8866
+ }
8867
+ if (!uuidLooksValid) {
8868
+ return config.json({ error: "Invalid feed id" }, { status: 400 });
8869
+ }
8870
+ try {
8871
+ await dataSource.getRepository(entityMap.rss_feeds).delete({ id });
8872
+ return config.json({ ok: true });
8873
+ } catch (e) {
8874
+ const message = e instanceof Error ? e.message : "Delete failed";
8875
+ return config.json({ error: message }, { status: 500 });
8876
+ }
8877
+ }
8878
+ if (path2[0] === "blog-generator" && path2[1] === "feeds" && path2.length === 2 && m === "GET" && getCms) {
8879
+ const a = await config.requireAuth(req);
8880
+ if (a) return a;
8881
+ const pe = await requireEntityPermissionEffective(req, "blogs", "read");
8882
+ if (pe) return pe;
8883
+ if (!entityMap.rss_feeds) {
8884
+ return config.json({ error: "RSS feeds are not configured (missing entity map)." }, { status: 503 });
8885
+ }
8886
+ try {
8887
+ const list = await dataSource.getRepository(entityMap.rss_feeds).find({
8888
+ where: { isActive: true },
8889
+ order: { updatedAt: "DESC" }
8890
+ });
8891
+ return config.json({ feeds: list.map(serializeRssFeed) });
8892
+ } catch (e) {
8893
+ const message = e instanceof Error ? e.message : "Failed to list feeds";
8894
+ return config.json({ error: message }, { status: 500 });
8895
+ }
8896
+ }
8897
+ if (path2[0] === "blog-generator" && path2[1] === "feeds" && path2.length === 2 && m === "POST" && getCms) {
8898
+ const a = await config.requireAuth(req);
8899
+ if (a) return a;
8900
+ const pe = await requireEntityPermissionEffective(req, "blogs", "read");
8901
+ if (pe) return pe;
8902
+ if (!entityMap.rss_feeds) {
8903
+ return config.json({ error: "RSS feeds are not configured (missing entity map)." }, { status: 503 });
8904
+ }
8905
+ let body;
8906
+ try {
8907
+ body = await req.json();
8908
+ } catch {
8909
+ return config.json({ error: "Invalid JSON body" }, { status: 400 });
8910
+ }
8911
+ const rowsIn = Array.isArray(body.rows) ? body.rows : [];
8912
+ const rows = rowsIn.map((r) => r).map((r) => {
8913
+ let websiteUrl;
8914
+ if ("websiteUrl" in r) {
8915
+ const w = r.websiteUrl;
8916
+ if (w === null || w === "") websiteUrl = null;
8917
+ else if (typeof w === "string") websiteUrl = w;
8918
+ }
8919
+ return {
8920
+ rssUrl: typeof r.rssUrl === "string" ? r.rssUrl.trim() : "",
8921
+ name: typeof r.name === "string" ? r.name : void 0,
8922
+ websiteUrl
8923
+ };
8924
+ }).filter((r) => r.rssUrl !== "");
8925
+ try {
8926
+ const Feed = entityMap.rss_feeds;
8927
+ for (const r of rows) {
8928
+ await upsertRssFeedRow(dataSource, Feed, r.rssUrl, {
8929
+ name: r.name,
8930
+ websiteUrl: r.websiteUrl
8931
+ });
8932
+ }
8933
+ const list = await dataSource.getRepository(Feed).find({
8934
+ where: { isActive: true },
8935
+ order: { updatedAt: "DESC" }
8936
+ });
8937
+ return config.json({ feeds: list.map(serializeRssFeed) });
8938
+ } catch (e) {
8939
+ const message = e instanceof Error ? e.message : "Failed to save feeds";
8940
+ return config.json({ error: message }, { status: 500 });
8941
+ }
8942
+ }
8943
+ if (path2[0] === "blog-generator" && path2[1] === "latest" && path2.length === 2 && m === "POST" && getCms) {
8944
+ const a = await config.requireAuth(req);
8945
+ if (a) return a;
8946
+ const pe = await requireEntityPermissionEffective(req, "blogs", "read");
8947
+ if (pe) return pe;
8948
+ const cms = await getCms();
8949
+ const svc = cms.getPlugin("blog_generator");
8950
+ if (!svc) {
8951
+ return config.json({ error: "Blog Generator plugin is not enabled" }, { status: 503 });
8952
+ }
8953
+ let body;
8954
+ try {
8955
+ body = await req.json();
8956
+ } catch {
8957
+ return config.json({ error: "Invalid JSON body" }, { status: 400 });
8958
+ }
8959
+ const rawUrls = [];
8960
+ if (Array.isArray(body.rssUrls)) {
8961
+ for (const u of body.rssUrls) {
8962
+ if (typeof u === "string") {
8963
+ const t = u.trim();
8964
+ if (t) rawUrls.push(t);
8965
+ }
8966
+ }
8967
+ }
8968
+ if (rawUrls.length === 0 && typeof body.rssUrl === "string" && body.rssUrl.trim()) {
8969
+ rawUrls.push(body.rssUrl.trim());
8970
+ }
8971
+ const seen = /* @__PURE__ */ new Set();
8972
+ const urls = rawUrls.filter((u) => {
8973
+ if (seen.has(u)) return false;
8974
+ seen.add(u);
8975
+ return true;
8976
+ });
8977
+ if (urls.length === 0) {
8978
+ return config.json({ error: "Provide rssUrls (array) or rssUrl (string)" }, { status: 400 });
8979
+ }
8980
+ const results = [];
8981
+ for (const rssUrl of urls) {
8982
+ try {
8983
+ if (entityMap.rss_feeds) {
8984
+ await upsertRssFeedRow(dataSource, entityMap.rss_feeds, rssUrl);
8985
+ }
8986
+ const latest = await svc.getLatestArticleFromFeed(rssUrl);
8987
+ if (entityMap.rss_feeds) {
8988
+ await recordRssFetchSuccess(
8989
+ dataSource,
8990
+ entityMap.rss_feeds,
8991
+ entityMap.rss_articles,
8992
+ rssUrl,
8993
+ latest == null ? null : {
8994
+ title: latest.title,
8995
+ link: latest.link,
8996
+ summary: latest.summary,
8997
+ content: latest.content,
8998
+ creator: latest.creator,
8999
+ date: latest.date
9000
+ }
9001
+ );
9002
+ }
9003
+ const article = latest == null ? null : {
9004
+ title: latest.title,
9005
+ link: latest.link,
9006
+ summary: latest.summary,
9007
+ content: latest.content,
9008
+ date: latest.date.toISOString()
9009
+ };
9010
+ results.push({ rssUrl, article });
9011
+ } catch (e) {
9012
+ const message = e instanceof Error ? e.message : "Failed to parse feed";
9013
+ results.push({ rssUrl, article: null, error: message });
9014
+ }
9015
+ }
9016
+ const singleArticle = results.length === 1 ? results[0].article : void 0;
9017
+ return config.json({ results, article: singleArticle });
9018
+ }
9019
+ if (path2[0] === "blog-generator" && path2[1] === "generate" && path2.length === 2 && m === "POST" && getCms) {
9020
+ const a = await config.requireAuth(req);
9021
+ if (a) return a;
9022
+ const pe = await requireEntityPermissionEffective(req, "blogs", "read");
9023
+ if (pe) return pe;
9024
+ const cms = await getCms();
9025
+ const svc = cms.getPlugin("blog_generator");
9026
+ if (!svc) {
9027
+ return config.json({ error: "Blog Generator plugin is not enabled" }, { status: 503 });
9028
+ }
9029
+ const llm = cms.getPlugin("llm");
9030
+ if (!llm) {
9031
+ return config.json({ error: "LLM plugin is not enabled. Configure the LLM gateway to generate articles." }, { status: 503 });
9032
+ }
9033
+ let body;
9034
+ try {
9035
+ body = await req.json();
9036
+ } catch {
9037
+ return config.json({ error: "Invalid JSON body" }, { status: 400 });
9038
+ }
9039
+ const rawUrls = [];
9040
+ if (Array.isArray(body.rssUrls)) {
9041
+ for (const u of body.rssUrls) {
9042
+ if (typeof u === "string") {
9043
+ const t = u.trim();
9044
+ if (t) rawUrls.push(t);
9045
+ }
9046
+ }
9047
+ }
9048
+ if (rawUrls.length === 0 && typeof body.rssUrl === "string" && body.rssUrl.trim()) {
9049
+ rawUrls.push(body.rssUrl.trim());
9050
+ }
9051
+ const seen = /* @__PURE__ */ new Set();
9052
+ const rssUrls = rawUrls.filter((u) => {
9053
+ if (seen.has(u)) return false;
9054
+ seen.add(u);
9055
+ return true;
9056
+ });
9057
+ if (rssUrls.length === 0) {
9058
+ return config.json({ error: "Provide rssUrls (non-empty array) or rssUrl (string)" }, { status: 400 });
9059
+ }
9060
+ const systemInstruction = typeof body.systemInstruction === "string" ? body.systemInstruction : void 0;
9061
+ const validationRules = typeof body.validationRules === "string" ? body.validationRules : void 0;
9062
+ const persistToCms = body.persistToCms === true;
9063
+ let llmAgentChatOptions;
9064
+ let llmAgentResolution = null;
9065
+ let metadataLlmAgentChatOptions;
9066
+ let metadataLlmAgentResolution = null;
9067
+ let socialLlmAgentChatOptions;
9068
+ let socialLlmAgentResolution = null;
9069
+ let metadataRow = null;
9070
+ let socialRow = null;
9071
+ if (entityMap.llm_agents) {
9072
+ const agentRepo = dataSource.getRepository(entityMap.llm_agents);
9073
+ const agentRow = await agentRepo.findOne({
9074
+ where: { slug: BLOG_GENERATOR_LLM_AGENT_SLUG, deleted: false, enabled: true }
9075
+ });
9076
+ if (agentRow) {
9077
+ const o = llmAgentToChatAgentOptions(agentRow);
9078
+ llmAgentChatOptions = {
9079
+ model: o.model,
9080
+ temperature: o.temperature,
9081
+ max_tokens: o.max_tokens
9082
+ };
9083
+ llmAgentResolution = {
9084
+ slug: BLOG_GENERATOR_LLM_AGENT_SLUG,
9085
+ model: agentRow.model?.trim() || null,
9086
+ temperature: agentRow.temperature ?? null,
9087
+ maxTokens: agentRow.maxTokens ?? null
9088
+ };
9089
+ }
9090
+ metadataRow = await agentRepo.findOne({
9091
+ where: { slug: BLOG_METADATA_ENRICHER_LLM_AGENT_SLUG, deleted: false, enabled: true }
9092
+ });
9093
+ if (metadataRow) {
9094
+ const mo = llmAgentToChatAgentOptions(metadataRow);
9095
+ metadataLlmAgentChatOptions = {
9096
+ model: mo.model,
9097
+ temperature: mo.temperature,
9098
+ max_tokens: mo.max_tokens
9099
+ };
9100
+ metadataLlmAgentResolution = {
9101
+ slug: BLOG_METADATA_ENRICHER_LLM_AGENT_SLUG,
9102
+ model: metadataRow.model?.trim() || null,
9103
+ temperature: metadataRow.temperature ?? null,
9104
+ maxTokens: metadataRow.maxTokens ?? null
9105
+ };
9106
+ }
9107
+ socialRow = await agentRepo.findOne({
9108
+ where: { slug: BLOG_SOCIAL_ENRICHER_LLM_AGENT_SLUG, deleted: false, enabled: true }
9109
+ });
9110
+ if (socialRow) {
9111
+ const so = llmAgentToChatAgentOptions(socialRow);
9112
+ socialLlmAgentChatOptions = {
9113
+ model: so.model,
9114
+ temperature: so.temperature,
9115
+ max_tokens: so.max_tokens
9116
+ };
9117
+ socialLlmAgentResolution = {
9118
+ slug: BLOG_SOCIAL_ENRICHER_LLM_AGENT_SLUG,
9119
+ model: socialRow.model?.trim() || null,
9120
+ temperature: socialRow.temperature ?? null,
9121
+ maxTokens: socialRow.maxTokens ?? null
9122
+ };
9123
+ }
9124
+ }
9125
+ try {
9126
+ const categoryEntities = entityMap.categories ? await dataSource.getRepository(entityMap.categories).find({
9127
+ where: { deleted: false },
9128
+ select: ["id", "name"]
9129
+ }) : [];
9130
+ const categoryRows = categoryEntities.map((r) => ({
9131
+ id: Number(r.id),
9132
+ name: String(r.name ?? "").trim()
9133
+ })).filter((r) => Number.isFinite(r.id) && r.name !== "");
9134
+ const tagEntities = entityMap.tags ? await dataSource.getRepository(entityMap.tags).find({
9135
+ where: { deleted: false },
9136
+ select: ["name"]
9137
+ }) : [];
9138
+ const tagNames = tagEntities.map((r) => String(r.name ?? "").trim()).filter((n) => n !== "");
9139
+ const out = await svc.generateBlogMarkdownFromRss({
9140
+ llm,
9141
+ rssUrls,
9142
+ systemInstruction,
9143
+ validationRules,
9144
+ categoryNamesHint: categoryRows.map((c) => c.name),
9145
+ tagNamesHint: tagNames,
9146
+ llmAgentChatOptions,
9147
+ metadataSystemInstruction: metadataRow?.systemInstruction,
9148
+ metadataValidationRules: metadataRow?.validationRules ?? void 0,
9149
+ metadataLlmAgentChatOptions,
9150
+ socialSystemInstruction: socialRow?.systemInstruction,
9151
+ socialValidationRules: socialRow?.validationRules ?? void 0,
9152
+ socialLlmAgentChatOptions
9153
+ });
9154
+ let savedBlogs;
9155
+ if (persistToCms) {
9156
+ const pCreate = await requireEntityPermissionEffective(req, "blogs", "create");
9157
+ if (pCreate) return pCreate;
9158
+ if (!entityMap.blogs || !entityMap.tags || !entityMap.categories || !entityMap.seos) {
9159
+ return config.json(
9160
+ { error: "persistToCms requires blogs, tags, categories, and seos in the entity map." },
9161
+ { status: 503 }
9162
+ );
9163
+ }
9164
+ let authorId = null;
9165
+ if (getSessionUser) {
9166
+ const su = await getSessionUser();
9167
+ if (su?.id) {
9168
+ const n = Number(su.id);
9169
+ if (Number.isFinite(n)) authorId = n;
9170
+ }
9171
+ if (authorId == null && su?.email?.trim() && entityMap.users) {
9172
+ const ur = await dataSource.getRepository(entityMap.users).findOne({
9173
+ where: { email: su.email.trim(), deleted: false },
9174
+ select: ["id"]
9175
+ });
9176
+ if (ur) authorId = Number(ur.id);
9177
+ }
9178
+ }
9179
+ if (authorId == null || !Number.isFinite(authorId)) {
9180
+ return config.json(
9181
+ {
9182
+ error: "persistToCms requires a logged-in user with id in session or a users row matching session email."
9183
+ },
9184
+ { status: 400 }
9185
+ );
9186
+ }
9187
+ savedBlogs = await persistAllGeneratedBlogDrafts(
9188
+ dataSource,
9189
+ {
9190
+ blogs: entityMap.blogs,
9191
+ tags: entityMap.tags,
9192
+ categories: entityMap.categories,
9193
+ seos: entityMap.seos
9194
+ },
9195
+ { drafts: out.blogDrafts, authorId }
9196
+ );
9197
+ }
9198
+ const blogs = out.blogDrafts.map((draft) => {
9199
+ const { categoryId, matched: categoryMatched } = resolveBlogCategoryIdByName(
9200
+ draft.categoryName,
9201
+ categoryRows
9202
+ );
9203
+ return {
9204
+ title: draft.title,
9205
+ slug: draft.slug ?? null,
9206
+ markdown: draft.markdown,
9207
+ socialMediaContent: draft.socialMediaContent ?? null,
9208
+ categoryName: draft.categoryName,
9209
+ categoryId,
9210
+ categoryMatched,
9211
+ seo: draft.seo,
9212
+ tags: draft.tags,
9213
+ parseMode: draft.parseMode
9214
+ };
9215
+ });
9216
+ const firstArticle = out.article;
9217
+ return config.json({
9218
+ agentName: out.agentName,
9219
+ blogMarkdown: out.blogMarkdown,
9220
+ blogs,
9221
+ blog: blogs[0] ?? null,
9222
+ feedArticles: out.feedArticles.map((f) => ({
9223
+ rssUrl: f.rssUrl,
9224
+ article: {
9225
+ title: f.article.title,
9226
+ link: f.article.link,
9227
+ summary: f.article.summary,
9228
+ content: f.article.content,
9229
+ date: f.article.date.toISOString()
9230
+ }
9231
+ })),
9232
+ llmAgent: llmAgentResolution,
9233
+ metadataLlmAgent: metadataLlmAgentResolution,
9234
+ socialLlmAgent: socialLlmAgentResolution,
9235
+ article: {
9236
+ title: firstArticle.title,
9237
+ link: firstArticle.link,
9238
+ summary: firstArticle.summary,
9239
+ content: firstArticle.content,
9240
+ date: firstArticle.date.toISOString()
9241
+ },
9242
+ ...savedBlogs != null ? { savedBlogs } : {}
9243
+ });
9244
+ } catch (e) {
9245
+ const message = e instanceof Error ? e.message : "Failed to generate blog";
9246
+ const status = /No items found|LLM returned empty/i.test(message) ? 400 : 502;
9247
+ return config.json({ error: message }, { status });
9248
+ }
9249
+ }
9250
+ if (path2[0] === "analytics" && analyticsHandlers) {
9251
+ if (path2.length === 1 && m === "GET") {
7506
9252
  const g = await analyticsGate();
7507
9253
  if (g) return g;
7508
9254
  return analyticsHandlers.GET(req);
7509
9255
  }
7510
- if (path.length === 2 && path[1] === "property-id" && m === "GET") {
9256
+ if (path2.length === 2 && path2[1] === "property-id" && m === "GET") {
7511
9257
  const g = await analyticsGate();
7512
9258
  if (g) return g;
7513
9259
  return analyticsHandlers.propertyId();
7514
9260
  }
7515
- if (path.length === 2 && path[1] === "permissions" && m === "GET") {
9261
+ if (path2.length === 2 && path2[1] === "permissions" && m === "GET") {
7516
9262
  const g = await analyticsGate();
7517
9263
  if (g) return g;
7518
9264
  return analyticsHandlers.permissions();
7519
9265
  }
7520
9266
  }
7521
- if (path[0] === "upload" && path.length === 1 && m === "POST" && uploadPost) return uploadPost(req);
7522
- if (path[0] === "media" && path[1] === "extract" && path.length === 3 && m === "POST" && zipExtractPost) {
7523
- return zipExtractPost(req, path[2]);
9267
+ if (path2[0] === "upload" && path2.length === 1 && m === "POST" && uploadPost) return uploadPost(req);
9268
+ if (path2[0] === "media" && path2[1] === "extract" && path2.length === 3 && m === "POST" && zipExtractPost) {
9269
+ return zipExtractPost(req, path2[2]);
7524
9270
  }
7525
- if (path[0] === "blogs" && path[1] === "slug" && path.length === 3 && m === "GET" && blogBySlugGet) {
7526
- return blogBySlugGet(req, path[2]);
9271
+ if (path2[0] === "blogs" && path2[1] === "slug" && path2.length === 3 && m === "GET" && blogBySlugGet) {
9272
+ return blogBySlugGet(req, path2[2]);
7527
9273
  }
7528
- if (path[0] === "forms" && path[1] === "slug" && path.length === 3 && m === "GET" && formBySlugGet) {
7529
- return formBySlugGet(req, path[2]);
9274
+ if (path2[0] === "forms" && path2[1] === "slug" && path2.length === 3 && m === "GET" && formBySlugGet) {
9275
+ return formBySlugGet(req, path2[2]);
7530
9276
  }
7531
- if (path[0] === "form-submissions") {
7532
- if (path.length === 1) {
9277
+ if (path2[0] === "form-submissions") {
9278
+ if (path2.length === 1) {
7533
9279
  if (m === "GET" && formSubmissionList) return formSubmissionList(req);
7534
9280
  if (m === "POST" && formSubmissionPost) return formSubmissionPost(req);
7535
9281
  }
7536
- if (path.length === 2 && m === "GET" && formSubmissionGetById) return formSubmissionGetById(req, path[1]);
9282
+ if (path2.length === 2 && m === "GET" && formSubmissionGetById) return formSubmissionGetById(req, path2[1]);
7537
9283
  }
7538
- if (path[0] === "forms" && formSaveHandlers) {
7539
- if (path.length === 1 && m === "POST") return formSaveHandlers.POST(req);
7540
- if (path.length === 2) {
7541
- if (m === "GET") return formSaveHandlers.GET(req, path[1]);
7542
- if (m === "PUT" || m === "PATCH") return formSaveHandlers.PUT(req, path[1]);
9284
+ if (path2[0] === "forms" && formSaveHandlers) {
9285
+ if (path2.length === 1 && m === "POST") return formSaveHandlers.POST(req);
9286
+ if (path2.length === 2) {
9287
+ if (m === "GET") return formSaveHandlers.GET(req, path2[1]);
9288
+ if (m === "PUT" || m === "PATCH") return formSaveHandlers.PUT(req, path2[1]);
7543
9289
  }
7544
9290
  }
7545
- if (path[0] === "users" && usersHandlers) {
7546
- if (path.length === 1) {
9291
+ if (path2[0] === "users" && usersHandlers) {
9292
+ if (path2.length === 1) {
7547
9293
  if (m === "GET") return usersHandlers.list(req);
7548
9294
  if (m === "POST") return usersHandlers.create(req);
7549
9295
  }
7550
- if (path.length === 2) {
7551
- if (path[1] === "avatar" && m === "POST" && avatarPost) return avatarPost(req);
7552
- if (path[1] === "profile" && profileHandlers) {
9296
+ if (path2.length === 2) {
9297
+ if (path2[1] === "avatar" && m === "POST" && avatarPost) return avatarPost(req);
9298
+ if (path2[1] === "profile" && profileHandlers) {
7553
9299
  if (m === "GET") return profileHandlers.GET(req);
7554
9300
  if (m === "PUT") return profileHandlers.PUT(req);
7555
9301
  }
7556
- const id = path[1];
9302
+ const id = path2[1];
7557
9303
  if (m === "GET") return usersHandlers.getById(req, id);
7558
9304
  if (m === "PUT" || m === "PATCH") return usersHandlers.update(req, id);
7559
9305
  if (m === "DELETE") return usersHandlers.delete(req, id);
7560
9306
  }
7561
- if (path.length === 3 && path[2] === "regenerate-invite" && m === "POST") {
7562
- return usersHandlers.regenerateInvite(req, path[1]);
9307
+ if (path2.length === 3 && path2[2] === "regenerate-invite" && m === "POST") {
9308
+ return usersHandlers.regenerateInvite(req, path2[1]);
7563
9309
  }
7564
9310
  }
7565
- if (path[0] === "users" && path.length === 2 && userAuthRouter && m === "POST") {
7566
- return userAuthRouter.POST(req, path[1]);
9311
+ if (path2[0] === "users" && path2.length === 2 && userAuthRouter && m === "POST") {
9312
+ return userAuthRouter.POST(req, path2[1]);
7567
9313
  }
7568
- if (path[0] === "settings" && path.length === 2 && settingsHandlers) {
7569
- const group = path[1];
9314
+ if (path2[0] === "social-media" && path2[1] === "linkedin" && socialMediaHandlers && path2.length === 3) {
9315
+ const tail = path2[2];
9316
+ if (tail === "status" && m === "GET") {
9317
+ return socialMediaHandlers.getLinkedInStatus(req);
9318
+ }
9319
+ if (tail === "sync-profile" && m === "POST") {
9320
+ return socialMediaHandlers.syncLinkedInProfile(req);
9321
+ }
9322
+ if (tail === "publish-blog" && m === "POST") {
9323
+ let body;
9324
+ try {
9325
+ body = await req.json();
9326
+ } catch {
9327
+ return config.json({ error: "Invalid JSON body" }, { status: 400 });
9328
+ }
9329
+ const id = Number(body?.blogId);
9330
+ return socialMediaHandlers.publishBlogToLinkedIn(req, id);
9331
+ }
9332
+ }
9333
+ if (path2[0] === "social-media" && path2[1] === "facebook" && socialMediaHandlers && path2.length === 3) {
9334
+ const tail = path2[2];
9335
+ if (tail === "fetch-pages" && m === "POST") {
9336
+ return socialMediaHandlers.fetchFacebookManagedPages(req);
9337
+ }
9338
+ if (tail === "status" && m === "GET") {
9339
+ return socialMediaHandlers.getFacebookStatus(req);
9340
+ }
9341
+ if (tail === "publish-blog" && m === "POST") {
9342
+ let body;
9343
+ try {
9344
+ body = await req.json();
9345
+ } catch {
9346
+ return config.json({ error: "Invalid JSON body" }, { status: 400 });
9347
+ }
9348
+ const id = Number(body?.blogId);
9349
+ return socialMediaHandlers.publishBlogToFacebook(req, id);
9350
+ }
9351
+ }
9352
+ if (path2[0] === "settings" && path2.length === 2 && settingsHandlers) {
9353
+ const group = path2[1];
7570
9354
  const isPublic = settingsConfig?.publicGetGroups?.includes(group);
7571
9355
  if (m === "GET") {
7572
9356
  if (!isPublic) {
@@ -7583,15 +9367,15 @@ function createCmsApiHandler(config) {
7583
9367
  return settingsHandlers.PUT(req, group);
7584
9368
  }
7585
9369
  }
7586
- if (path[0] === "message-templates" && path[1] === "sms" && path.length === 2) {
9370
+ if (path2[0] === "message-templates" && path2[1] === "sms" && path2.length === 2) {
7587
9371
  if (m === "GET") return smsMessageTemplateHandlers.GET(req);
7588
9372
  if (m === "PUT") return smsMessageTemplateHandlers.PUT(req);
7589
9373
  }
7590
- if (path[0] === "llm_agents") {
9374
+ if (path2[0] === "llm_agents") {
7591
9375
  if (!entityMap.llm_agents) {
7592
9376
  console.error(CMS_API_LOG, "llm_agents route: entity missing after merge", {
7593
9377
  method: m,
7594
- pathJoined: path.join("/"),
9378
+ pathJoined: path2.join("/"),
7595
9379
  entityMapKeyCount: Object.keys(entityMap).length
7596
9380
  });
7597
9381
  return config.json(
@@ -7599,19 +9383,19 @@ function createCmsApiHandler(config) {
7599
9383
  { status: 503 }
7600
9384
  );
7601
9385
  }
7602
- if (path.length === 1) {
9386
+ if (path2.length === 1) {
7603
9387
  if (m === "GET") return crud.GET(req, "llm_agents");
7604
9388
  if (m === "POST") return crud.POST(req, "llm_agents");
7605
9389
  }
7606
- if (path.length === 2 && !path[1]?.includes("knowledge")) {
7607
- const id = path[1];
9390
+ if (path2.length === 2 && !path2[1]?.includes("knowledge")) {
9391
+ const id = path2[1];
7608
9392
  if (m === "GET") return crudById.GET(req, "llm_agents", id);
7609
9393
  if (m === "PUT" || m === "PATCH") return crudById.PUT(req, "llm_agents", id);
7610
9394
  if (m === "DELETE") return crudById.DELETE(req, "llm_agents", id);
7611
9395
  }
7612
9396
  }
7613
9397
  {
7614
- const kbMatch = matchLlmAgentKnowledgeRoute(path);
9398
+ const kbMatch = matchLlmAgentKnowledgeRoute(path2);
7615
9399
  if (kbMatch) {
7616
9400
  if (!llmAgentKnowledgeHandlers) {
7617
9401
  return config.json(
@@ -7634,29 +9418,29 @@ function createCmsApiHandler(config) {
7634
9418
  }
7635
9419
  }
7636
9420
  }
7637
- if (path[0] === "chat" && chatHandlers) {
7638
- if (path.length === 2 && path[1] === "config" && m === "GET") return chatHandlers.publicConfig(req);
7639
- if (path.length === 2 && path[1] === "identify" && m === "POST") return chatHandlers.identify(req);
7640
- if (path.length === 4 && path[1] === "conversations" && path[3] === "messages" && m === "GET") return chatHandlers.getMessages(req, path[2]);
7641
- if (path.length === 2 && path[1] === "messages" && m === "POST") return chatHandlers.postMessage(req);
9421
+ if (path2[0] === "chat" && chatHandlers) {
9422
+ if (path2.length === 2 && path2[1] === "config" && m === "GET") return chatHandlers.publicConfig(req);
9423
+ if (path2.length === 2 && path2[1] === "identify" && m === "POST") return chatHandlers.identify(req);
9424
+ if (path2.length === 4 && path2[1] === "conversations" && path2[3] === "messages" && m === "GET") return chatHandlers.getMessages(req, path2[2]);
9425
+ if (path2.length === 2 && path2[1] === "messages" && m === "POST") return chatHandlers.postMessage(req);
7642
9426
  }
7643
- if (path[0] === "orders" && path.length === 3 && path[2] === "invoice" && m === "GET" && getCms) {
9427
+ if (path2[0] === "orders" && path2.length === 3 && path2[2] === "invoice" && m === "GET" && getCms) {
7644
9428
  const a = await config.requireAuth(req);
7645
9429
  if (a) return a;
7646
9430
  const pe = await requireEntityPermissionEffective(req, "orders", "read");
7647
9431
  if (pe) return pe;
7648
9432
  const cms = await getCms();
7649
9433
  const { streamOrderInvoicePdf: streamOrderInvoicePdf2 } = await Promise.resolve().then(() => (init_erp_order_invoice(), erp_order_invoice_exports));
7650
- const oid = Number(path[1]);
9434
+ const oid = Number(path2[1]);
7651
9435
  if (!Number.isFinite(oid)) return config.json({ error: "Invalid id" }, { status: 400 });
7652
9436
  return streamOrderInvoicePdf2(cms, dataSource, entityMap, oid, {});
7653
9437
  }
7654
- if (path[0] === "orders" && path.length === 3 && path[2] === "repost-erp" && getCms) {
9438
+ if (path2[0] === "orders" && path2.length === 3 && path2[2] === "repost-erp" && getCms) {
7655
9439
  const a = await config.requireAuth(req);
7656
9440
  if (a) return a;
7657
9441
  const pe = await requireEntityPermissionEffective(req, "orders", m === "GET" ? "read" : "update");
7658
9442
  if (pe) return pe;
7659
- const oid = Number(path[1]);
9443
+ const oid = Number(path2[1]);
7660
9444
  if (!Number.isFinite(oid)) return config.json({ error: "Invalid id" }, { status: 400 });
7661
9445
  const cms = await getCms();
7662
9446
  const { isErpIntegrationEnabled: isErpIntegrationEnabled3 } = await Promise.resolve().then(() => (init_erp_config_enabled(), erp_config_enabled_exports));
@@ -7672,13 +9456,13 @@ function createCmsApiHandler(config) {
7672
9456
  }
7673
9457
  return config.json({ error: "Method not allowed" }, { status: 405 });
7674
9458
  }
7675
- if (path.length === 0) return config.json({ error: "Not found" }, { status: 404 });
7676
- const resource = resolveResource(path[0]);
9459
+ if (path2.length === 0) return config.json({ error: "Not found" }, { status: 404 });
9460
+ const resource = resolveResource(path2[0]);
7677
9461
  if (!crudResources.includes(resource)) {
7678
9462
  console.warn(CMS_API_LOG, "generic CRUD gate: Invalid resource (not in crudResources)", {
7679
9463
  method: m,
7680
- pathJoined: path.join("/"),
7681
- pathSegments: path,
9464
+ pathJoined: path2.join("/"),
9465
+ pathSegments: path2,
7682
9466
  resource,
7683
9467
  hasLlmAgentsEntity: Boolean(entityMap.llm_agents),
7684
9468
  crudResourcesHasResource: crudResources.includes(resource),
@@ -7686,24 +9470,24 @@ function createCmsApiHandler(config) {
7686
9470
  });
7687
9471
  return config.json({ error: "Invalid resource" }, { status: 400 });
7688
9472
  }
7689
- if (path.length === 2) {
7690
- if (path[1] === "metadata" && m === "GET") {
9473
+ if (path2.length === 2) {
9474
+ if (path2[1] === "metadata" && m === "GET") {
7691
9475
  return crud.GET_METADATA(req, resource);
7692
9476
  }
7693
- if (path[1] === "bulk" && m === "POST") {
9477
+ if (path2[1] === "bulk" && m === "POST") {
7694
9478
  return crud.BULK_POST(req, resource);
7695
9479
  }
7696
- if (path[1] === "export" && m === "GET") {
9480
+ if (path2[1] === "export" && m === "GET") {
7697
9481
  return crud.GET_EXPORT(req, resource);
7698
9482
  }
7699
9483
  }
7700
- if (path.length === 1) {
9484
+ if (path2.length === 1) {
7701
9485
  if (m === "GET") return crud.GET(req, resource);
7702
9486
  if (m === "POST") return crud.POST(req, resource);
7703
9487
  return config.json({ error: "Method not allowed" }, { status: 405 });
7704
9488
  }
7705
- if (path.length === 2) {
7706
- const id = path[1];
9489
+ if (path2.length === 2) {
9490
+ const id = path2[1];
7707
9491
  if (m === "GET") return crudById.GET(req, resource, id);
7708
9492
  if (m === "PUT" || m === "PATCH") return crudById.PUT(req, resource, id);
7709
9493
  if (m === "DELETE") return crudById.DELETE(req, resource, id);
@@ -7715,7 +9499,7 @@ function createCmsApiHandler(config) {
7715
9499
  }
7716
9500
 
7717
9501
  // src/api/storefront-handlers.ts
7718
- var import_typeorm47 = require("typeorm");
9502
+ var import_typeorm49 = require("typeorm");
7719
9503
 
7720
9504
  // src/lib/is-valid-signup-email.ts
7721
9505
  var MAX_EMAIL = 254;
@@ -7975,7 +9759,7 @@ async function queueSms(cms, payload) {
7975
9759
 
7976
9760
  // src/lib/otp-challenge.ts
7977
9761
  var import_crypto = require("crypto");
7978
- var import_typeorm46 = require("typeorm");
9762
+ var import_typeorm48 = require("typeorm");
7979
9763
  var OTP_TTL_MS = 10 * 60 * 1e3;
7980
9764
  var MAX_SENDS_PER_HOUR = 5;
7981
9765
  var MAX_VERIFY_ATTEMPTS = 8;
@@ -8009,7 +9793,7 @@ function normalizePhoneE164(raw, defaultCountryCode) {
8009
9793
  async function countRecentOtpSends(dataSource, entityMap, purpose, identifier, since) {
8010
9794
  const repo = dataSource.getRepository(entityMap.otp_challenges);
8011
9795
  return repo.count({
8012
- where: { purpose, identifier, createdAt: (0, import_typeorm46.MoreThan)(since) }
9796
+ where: { purpose, identifier, createdAt: (0, import_typeorm48.MoreThan)(since) }
8013
9797
  });
8014
9798
  }
8015
9799
  async function createOtpChallenge(dataSource, entityMap, input) {
@@ -8023,7 +9807,7 @@ async function createOtpChallenge(dataSource, entityMap, input) {
8023
9807
  await repo.delete({
8024
9808
  purpose,
8025
9809
  identifier,
8026
- consumedAt: (0, import_typeorm46.IsNull)()
9810
+ consumedAt: (0, import_typeorm48.IsNull)()
8027
9811
  });
8028
9812
  const expiresAt = new Date(Date.now() + OTP_TTL_MS);
8029
9813
  const codeHash = hashOtpCode(code, purpose, identifier, pepper);
@@ -8044,7 +9828,7 @@ async function verifyAndConsumeOtpChallenge(dataSource, entityMap, input) {
8044
9828
  const { purpose, identifier, code, pepper } = input;
8045
9829
  const repo = dataSource.getRepository(entityMap.otp_challenges);
8046
9830
  const row = await repo.findOne({
8047
- where: { purpose, identifier, consumedAt: (0, import_typeorm46.IsNull)() },
9831
+ where: { purpose, identifier, consumedAt: (0, import_typeorm48.IsNull)() },
8048
9832
  order: { id: "DESC" }
8049
9833
  });
8050
9834
  if (!row) {
@@ -8278,7 +10062,7 @@ function createStorefrontApiHandler(config) {
8278
10062
  const u = await userRepo().findOne({ where: { id: userId } });
8279
10063
  if (!u) return null;
8280
10064
  const unclaimed = await contactRepo().findOne({
8281
- where: { email: u.email, userId: (0, import_typeorm47.IsNull)(), deleted: false }
10065
+ where: { email: u.email, userId: (0, import_typeorm49.IsNull)(), deleted: false }
8282
10066
  });
8283
10067
  if (unclaimed) {
8284
10068
  await contactRepo().update(unclaimed.id, { userId });
@@ -8421,7 +10205,7 @@ function createStorefrontApiHandler(config) {
8421
10205
  };
8422
10206
  }
8423
10207
  return {
8424
- async handle(method, path, req) {
10208
+ async handle(method, path2, req) {
8425
10209
  try {
8426
10210
  let serializeAddress2 = function(a) {
8427
10211
  return {
@@ -8437,7 +10221,7 @@ function createStorefrontApiHandler(config) {
8437
10221
  };
8438
10222
  };
8439
10223
  var serializeAddress = serializeAddress2;
8440
- if (path[0] === "products" && path.length === 1 && method === "GET") {
10224
+ if (path2[0] === "products" && path2.length === 1 && method === "GET") {
8441
10225
  const url = new URL(req.url || "", "http://localhost");
8442
10226
  const collectionSlug = url.searchParams.get("collection")?.trim();
8443
10227
  const collectionId = url.searchParams.get("collectionId");
@@ -8479,8 +10263,8 @@ function createStorefrontApiHandler(config) {
8479
10263
  ...collectionFilter && { collection: collectionFilter }
8480
10264
  });
8481
10265
  }
8482
- if (path[0] === "products" && path.length === 2 && method === "GET") {
8483
- const idOrSlug = path[1];
10266
+ if (path2[0] === "products" && path2.length === 2 && method === "GET") {
10267
+ const idOrSlug = path2[1];
8484
10268
  const byId = /^\d+$/.test(idOrSlug);
8485
10269
  const product = await productRepo().findOne({
8486
10270
  where: byId ? { id: parseInt(idOrSlug, 10), status: "available", deleted: false } : { slug: idOrSlug, status: "available", deleted: false },
@@ -8495,7 +10279,7 @@ function createStorefrontApiHandler(config) {
8495
10279
  })).filter((t) => t.name || t.value);
8496
10280
  return json({ ...serializeProduct(p), attributes: attributeTags });
8497
10281
  }
8498
- if (path[0] === "collections" && path.length === 1 && method === "GET") {
10282
+ if (path2[0] === "collections" && path2.length === 1 && method === "GET") {
8499
10283
  const items = await collectionRepo().find({
8500
10284
  where: { active: true, deleted: false },
8501
10285
  order: { sortOrder: "ASC", id: "ASC" }
@@ -8524,8 +10308,8 @@ function createStorefrontApiHandler(config) {
8524
10308
  })
8525
10309
  });
8526
10310
  }
8527
- if (path[0] === "collections" && path.length === 2 && method === "GET") {
8528
- const idOrSlug = path[1];
10311
+ if (path2[0] === "collections" && path2.length === 2 && method === "GET") {
10312
+ const idOrSlug = path2[1];
8529
10313
  const byId = /^\d+$/.test(idOrSlug);
8530
10314
  const collection = await collectionRepo().findOne({
8531
10315
  where: byId ? { id: parseInt(idOrSlug, 10), active: true, deleted: false } : { slug: idOrSlug, active: true, deleted: false },
@@ -8548,7 +10332,7 @@ function createStorefrontApiHandler(config) {
8548
10332
  products: products.map((p) => serializeProduct(p))
8549
10333
  });
8550
10334
  }
8551
- if (path[0] === "profile" && path.length === 1 && method === "GET") {
10335
+ if (path2[0] === "profile" && path2.length === 1 && method === "GET") {
8552
10336
  const u = await getSessionUser();
8553
10337
  const uid = u?.id ? parseInt(String(u.id), 10) : NaN;
8554
10338
  if (!Number.isFinite(uid)) return json({ error: "Unauthorized" }, { status: 401 });
@@ -8565,7 +10349,7 @@ function createStorefrontApiHandler(config) {
8565
10349
  } : null
8566
10350
  });
8567
10351
  }
8568
- if (path[0] === "profile" && path.length === 1 && method === "PUT") {
10352
+ if (path2[0] === "profile" && path2.length === 1 && method === "PUT") {
8569
10353
  const u = await getSessionUser();
8570
10354
  const uid = u?.id ? parseInt(String(u.id), 10) : NaN;
8571
10355
  if (!Number.isFinite(uid)) return json({ error: "Unauthorized" }, { status: 401 });
@@ -8602,7 +10386,7 @@ function createStorefrontApiHandler(config) {
8602
10386
  if (!contact) return json({ error: "Contact not found" }, { status: 404 });
8603
10387
  return { contactId: contact.id };
8604
10388
  }
8605
- if (path[0] === "addresses" && path.length === 1 && method === "GET") {
10389
+ if (path2[0] === "addresses" && path2.length === 1 && method === "GET") {
8606
10390
  const contactOrErr = await getContactForAddresses();
8607
10391
  if (contactOrErr instanceof Response) return contactOrErr;
8608
10392
  const list = await addressRepo().find({
@@ -8611,7 +10395,7 @@ function createStorefrontApiHandler(config) {
8611
10395
  });
8612
10396
  return json({ addresses: list.map((a) => serializeAddress2(a)) });
8613
10397
  }
8614
- if (path[0] === "addresses" && path.length === 1 && method === "POST") {
10398
+ if (path2[0] === "addresses" && path2.length === 1 && method === "POST") {
8615
10399
  const contactOrErr = await getContactForAddresses();
8616
10400
  if (contactOrErr instanceof Response) return contactOrErr;
8617
10401
  const b = await req.json().catch(() => ({}));
@@ -8630,10 +10414,10 @@ function createStorefrontApiHandler(config) {
8630
10414
  const created = await addressRepo().save(addressRepo().create(row));
8631
10415
  return json(serializeAddress2(created));
8632
10416
  }
8633
- if (path[0] === "addresses" && path.length === 2 && (method === "PATCH" || method === "PUT")) {
10417
+ if (path2[0] === "addresses" && path2.length === 2 && (method === "PATCH" || method === "PUT")) {
8634
10418
  const contactOrErr = await getContactForAddresses();
8635
10419
  if (contactOrErr instanceof Response) return contactOrErr;
8636
- const id = parseInt(path[1], 10);
10420
+ const id = parseInt(path2[1], 10);
8637
10421
  if (!Number.isFinite(id)) return json({ error: "Invalid id" }, { status: 400 });
8638
10422
  const existing = await addressRepo().findOne({ where: { id, contactId: contactOrErr.contactId } });
8639
10423
  if (!existing) return json({ error: "Not found" }, { status: 404 });
@@ -8659,17 +10443,17 @@ function createStorefrontApiHandler(config) {
8659
10443
  const updated = await addressRepo().findOne({ where: { id } });
8660
10444
  return json(serializeAddress2(updated));
8661
10445
  }
8662
- if (path[0] === "addresses" && path.length === 2 && method === "DELETE") {
10446
+ if (path2[0] === "addresses" && path2.length === 2 && method === "DELETE") {
8663
10447
  const contactOrErr = await getContactForAddresses();
8664
10448
  if (contactOrErr instanceof Response) return contactOrErr;
8665
- const id = parseInt(path[1], 10);
10449
+ const id = parseInt(path2[1], 10);
8666
10450
  if (!Number.isFinite(id)) return json({ error: "Invalid id" }, { status: 400 });
8667
10451
  const existing = await addressRepo().findOne({ where: { id, contactId: contactOrErr.contactId } });
8668
10452
  if (!existing) return json({ error: "Not found" }, { status: 404 });
8669
10453
  await addressRepo().delete(id);
8670
10454
  return json({ deleted: true });
8671
10455
  }
8672
- if (path[0] === "verify-email" && path.length === 1 && method === "POST") {
10456
+ if (path2[0] === "verify-email" && path2.length === 1 && method === "POST") {
8673
10457
  const b = await req.json().catch(() => ({}));
8674
10458
  const token = typeof b.token === "string" ? b.token.trim() : "";
8675
10459
  if (!token) return json({ error: "token is required" }, { status: 400 });
@@ -8688,7 +10472,7 @@ function createStorefrontApiHandler(config) {
8688
10472
  await tokenRepo().delete({ email });
8689
10473
  return json({ success: true, message: "Email verified. You can sign in." });
8690
10474
  }
8691
- if (path[0] === "auth" && path[1] === "otp" && path[2] === "send" && path.length === 3 && method === "POST") {
10475
+ if (path2[0] === "auth" && path2[1] === "otp" && path2[2] === "send" && path2.length === 3 && method === "POST") {
8692
10476
  const b = await req.json().catch(() => ({}));
8693
10477
  const purposeRaw = typeof b.purpose === "string" ? b.purpose.trim() : "";
8694
10478
  const purpose = purposeRaw === "login" || purposeRaw === "verify_email" || purposeRaw === "verify_phone" ? purposeRaw : "";
@@ -8773,7 +10557,7 @@ function createStorefrontApiHandler(config) {
8773
10557
  }
8774
10558
  return json({ ok: true });
8775
10559
  }
8776
- if (path[0] === "auth" && path[1] === "otp" && path[2] === "verify-email" && path.length === 3 && method === "POST") {
10560
+ if (path2[0] === "auth" && path2[1] === "otp" && path2[2] === "verify-email" && path2.length === 3 && method === "POST") {
8777
10561
  if (otpOff("verifyEmail")) return json({ error: "otp_disabled" }, { status: 403 });
8778
10562
  const b = await req.json().catch(() => ({}));
8779
10563
  const email = typeof b.email === "string" ? b.email.trim().toLowerCase() : "";
@@ -8796,7 +10580,7 @@ function createStorefrontApiHandler(config) {
8796
10580
  await tokenRepo().delete({ email });
8797
10581
  return json({ success: true, message: "Email verified. You can sign in." });
8798
10582
  }
8799
- if (path[0] === "auth" && path[1] === "otp" && path[2] === "verify-phone" && path.length === 3 && method === "POST") {
10583
+ if (path2[0] === "auth" && path2[1] === "otp" && path2[2] === "verify-phone" && path2.length === 3 && method === "POST") {
8800
10584
  if (otpOff("verifyPhone")) return json({ error: "otp_disabled" }, { status: 403 });
8801
10585
  const su = await getSessionUser();
8802
10586
  const uid = su?.id ? parseInt(String(su.id), 10) : NaN;
@@ -8824,7 +10608,7 @@ function createStorefrontApiHandler(config) {
8824
10608
  }
8825
10609
  return json({ success: true });
8826
10610
  }
8827
- if (path[0] === "register" && path.length === 1 && method === "POST") {
10611
+ if (path2[0] === "register" && path2.length === 1 && method === "POST") {
8828
10612
  if (!config.hashPassword) return json({ error: "Registration not configured" }, { status: 501 });
8829
10613
  const b = await req.json().catch(() => ({}));
8830
10614
  const capReg = await assertCaptchaOk(getCms, b, req, json);
@@ -8881,14 +10665,14 @@ function createStorefrontApiHandler(config) {
8881
10665
  emailVerificationSent
8882
10666
  });
8883
10667
  }
8884
- if (path[0] === "cart" && path.length === 1 && method === "GET") {
10668
+ if (path2[0] === "cart" && path2.length === 1 && method === "GET") {
8885
10669
  const { cart, setCookie, err } = await getOrCreateCart(req);
8886
10670
  if (err) return err;
8887
10671
  const body = serializeCart(cart);
8888
10672
  if (setCookie) return json(body, { headers: { "Set-Cookie": setCookie } });
8889
10673
  return json(body);
8890
10674
  }
8891
- if (path[0] === "cart" && path[1] === "items" && path.length === 2 && method === "POST") {
10675
+ if (path2[0] === "cart" && path2[1] === "items" && path2.length === 2 && method === "POST") {
8892
10676
  const body = await req.json().catch(() => ({}));
8893
10677
  const capCart = await assertCaptchaOk(getCms, body, req, json);
8894
10678
  if (capCart) return capCart;
@@ -8919,8 +10703,8 @@ function createStorefrontApiHandler(config) {
8919
10703
  if (setCookie) return json(out, { headers: { "Set-Cookie": setCookie } });
8920
10704
  return json(out);
8921
10705
  }
8922
- if (path[0] === "cart" && path[1] === "items" && path.length === 3) {
8923
- const itemId = parseInt(path[2], 10);
10706
+ if (path2[0] === "cart" && path2[1] === "items" && path2.length === 3) {
10707
+ const itemId = parseInt(path2[2], 10);
8924
10708
  if (!Number.isFinite(itemId)) return json({ error: "Invalid item id" }, { status: 400 });
8925
10709
  const { cart, setCookie, err } = await getOrCreateCart(req);
8926
10710
  if (err) return err;
@@ -8953,7 +10737,7 @@ function createStorefrontApiHandler(config) {
8953
10737
  return json(out);
8954
10738
  }
8955
10739
  }
8956
- if (path[0] === "cart" && path[1] === "merge" && method === "POST") {
10740
+ if (path2[0] === "cart" && path2[1] === "merge" && method === "POST") {
8957
10741
  const u = await getSessionUser();
8958
10742
  const uid = u?.id ? parseInt(String(u.id), 10) : NaN;
8959
10743
  if (!Number.isFinite(uid)) return json({ error: "Unauthorized" }, { status: 401 });
@@ -9059,7 +10843,7 @@ function createStorefrontApiHandler(config) {
9059
10843
  }
9060
10844
  return { wishlist: w, setCookie: null, err: null };
9061
10845
  }
9062
- if (path[0] === "wishlist" && path.length === 1 && method === "GET") {
10846
+ if (path2[0] === "wishlist" && path2.length === 1 && method === "GET") {
9063
10847
  const { wishlist, setCookie, err } = await getOrCreateWishlist(req);
9064
10848
  if (err) return err;
9065
10849
  const items = await wishlistItemRepo().find({
@@ -9087,7 +10871,7 @@ function createStorefrontApiHandler(config) {
9087
10871
  if (setCookie) return json(body, { headers: { "Set-Cookie": setCookie } });
9088
10872
  return json(body);
9089
10873
  }
9090
- if (path[0] === "wishlist" && path[1] === "items" && path.length === 2 && method === "POST") {
10874
+ if (path2[0] === "wishlist" && path2[1] === "items" && path2.length === 2 && method === "POST") {
9091
10875
  const { wishlist, setCookie, err } = await getOrCreateWishlist(req);
9092
10876
  if (err) return err;
9093
10877
  const b = await req.json().catch(() => ({}));
@@ -9101,15 +10885,15 @@ function createStorefrontApiHandler(config) {
9101
10885
  if (setCookie) return json({ ok: true }, { headers: { "Set-Cookie": setCookie } });
9102
10886
  return json({ ok: true });
9103
10887
  }
9104
- if (path[0] === "wishlist" && path[1] === "items" && path.length === 3 && method === "DELETE") {
10888
+ if (path2[0] === "wishlist" && path2[1] === "items" && path2.length === 3 && method === "DELETE") {
9105
10889
  const { wishlist, setCookie, err } = await getOrCreateWishlist(req);
9106
10890
  if (err) return err;
9107
- const productId = parseInt(path[2], 10);
10891
+ const productId = parseInt(path2[2], 10);
9108
10892
  await wishlistItemRepo().delete({ wishlistId: wishlist.id, productId });
9109
10893
  if (setCookie) return json({ ok: true }, { headers: { "Set-Cookie": setCookie } });
9110
10894
  return json({ ok: true });
9111
10895
  }
9112
- if (path[0] === "checkout" && path[1] === "order" && path.length === 2 && method === "POST") {
10896
+ if (path2[0] === "checkout" && path2[1] === "order" && path2.length === 2 && method === "POST") {
9113
10897
  const b = await req.json().catch(() => ({}));
9114
10898
  const capOrd = await assertCaptchaOk(getCms, b, req, json);
9115
10899
  if (capOrd) return capOrd;
@@ -9212,7 +10996,7 @@ function createStorefrontApiHandler(config) {
9212
10996
  currency: cart.currency || "INR"
9213
10997
  });
9214
10998
  }
9215
- if (path[0] === "checkout" && path.length === 1 && method === "POST") {
10999
+ if (path2[0] === "checkout" && path2.length === 1 && method === "POST") {
9216
11000
  const b = await req.json().catch(() => ({}));
9217
11001
  const capChk = await assertCaptchaOk(getCms, b, req, json);
9218
11002
  if (capChk) return capChk;
@@ -9314,7 +11098,7 @@ function createStorefrontApiHandler(config) {
9314
11098
  total: prepChk.orderTotal
9315
11099
  });
9316
11100
  }
9317
- if (path[0] === "orders" && path.length === 1 && method === "GET") {
11101
+ if (path2[0] === "orders" && path2.length === 1 && method === "GET") {
9318
11102
  const u = await getSessionUser();
9319
11103
  const uid = u?.id ? parseInt(String(u.id), 10) : NaN;
9320
11104
  if (!Number.isFinite(uid)) return json({ error: "Unauthorized" }, { status: 401 });
@@ -9329,7 +11113,7 @@ function createStorefrontApiHandler(config) {
9329
11113
  const previewByOrder = {};
9330
11114
  if (orderIds.length) {
9331
11115
  const oItems = await orderItemRepo().find({
9332
- where: { orderId: (0, import_typeorm47.In)(orderIds) },
11116
+ where: { orderId: (0, import_typeorm49.In)(orderIds) },
9333
11117
  relations: ["product"],
9334
11118
  order: { id: "ASC" }
9335
11119
  });
@@ -9356,27 +11140,27 @@ function createStorefrontApiHandler(config) {
9356
11140
  })
9357
11141
  });
9358
11142
  }
9359
- if (path[0] === "orders" && path.length === 3 && path[2] === "invoice" && method === "GET") {
11143
+ if (path2[0] === "orders" && path2.length === 3 && path2[2] === "invoice" && method === "GET") {
9360
11144
  const u = await getSessionUser();
9361
11145
  const uid = u?.id ? parseInt(String(u.id), 10) : NaN;
9362
11146
  if (!Number.isFinite(uid)) return json({ error: "Unauthorized" }, { status: 401 });
9363
11147
  if (!getCms) return json({ error: "Not found" }, { status: 404 });
9364
11148
  const contact = await contactRepo().findOne({ where: { userId: uid, deleted: false } });
9365
11149
  if (!contact) return json({ error: "Not found" }, { status: 404 });
9366
- const orderId = parseInt(path[1], 10);
11150
+ const orderId = parseInt(path2[1], 10);
9367
11151
  if (!Number.isFinite(orderId)) return json({ error: "Invalid id" }, { status: 400 });
9368
11152
  const cms = await getCms();
9369
11153
  return streamOrderInvoicePdf(cms, dataSource, entityMap, orderId, {
9370
11154
  ownerContactId: contact.id
9371
11155
  });
9372
11156
  }
9373
- if (path[0] === "orders" && path.length === 2 && method === "GET") {
11157
+ if (path2[0] === "orders" && path2.length === 2 && method === "GET") {
9374
11158
  const u = await getSessionUser();
9375
11159
  const uid = u?.id ? parseInt(String(u.id), 10) : NaN;
9376
11160
  if (!Number.isFinite(uid)) return json({ error: "Unauthorized" }, { status: 401 });
9377
11161
  const contact = await contactRepo().findOne({ where: { userId: uid, deleted: false } });
9378
11162
  if (!contact) return json({ error: "Not found" }, { status: 404 });
9379
- const orderId = parseInt(path[1], 10);
11163
+ const orderId = parseInt(path2[1], 10);
9380
11164
  let order = await orderRepo().findOne({
9381
11165
  where: { id: orderId, contactId: contact.id, deleted: false },
9382
11166
  relations: ["items", "items.product"]
@@ -9479,6 +11263,8 @@ function createStorefrontApiHandler(config) {
9479
11263
  getPublicSettingsGroup,
9480
11264
  mergeGuardrailsIntoSystemPrompt,
9481
11265
  parseLlmAgentValidationRules,
11266
+ simpleDecrypt,
11267
+ simpleEncrypt,
9482
11268
  validateUserMessageAgainstAgentRules,
9483
11269
  validateUserMessageAgainstStructuredRules
9484
11270
  });