@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.js CHANGED
@@ -1906,14 +1906,14 @@ function createUserAuthApiRouter(config) {
1906
1906
  }) : null;
1907
1907
  return {
1908
1908
  async POST(req, pathname) {
1909
- const path = pathname.replace(/\/$/, "");
1910
- if (!USER_AUTH_PATHS.includes(path)) {
1909
+ const path2 = pathname.replace(/\/$/, "");
1910
+ if (!USER_AUTH_PATHS.includes(path2)) {
1911
1911
  return config.json({ error: "Not found" }, { status: 404 });
1912
1912
  }
1913
- if (path === "forgot-password") return forgot(req);
1914
- if (path === "set-password") return setPass(req);
1915
- if (path === "invite") return invite(req);
1916
- if (path === "reset-password" && changePass) return changePass(req);
1913
+ if (path2 === "forgot-password") return forgot(req);
1914
+ if (path2 === "set-password") return setPass(req);
1915
+ if (path2 === "invite") return invite(req);
1916
+ if (path2 === "reset-password" && changePass) return changePass(req);
1917
1917
  return config.json({ error: "Not found" }, { status: 404 });
1918
1918
  }
1919
1919
  };
@@ -2182,12 +2182,12 @@ async function extractZipMediaIntoParentTree(opts) {
2182
2182
  if (opts.storage) {
2183
2183
  publicUrl = await opts.storage.upload(buf, `uploads/${relativeUnderUploads}`, contentType);
2184
2184
  } else {
2185
- const fs = await import("fs/promises");
2185
+ const fs2 = await import("fs/promises");
2186
2186
  const pathMod = await import("path");
2187
2187
  const dir = pathMod.join(process.cwd(), opts.localUploadDir);
2188
2188
  const filePath = pathMod.join(dir, relativeUnderUploads);
2189
- await fs.mkdir(pathMod.dirname(filePath), { recursive: true });
2190
- await fs.writeFile(filePath, buf);
2189
+ await fs2.mkdir(pathMod.dirname(filePath), { recursive: true });
2190
+ await fs2.writeFile(filePath, buf);
2191
2191
  publicUrl = `/${opts.localUploadDir.replace(/^\/+/, "").replace(/\\/g, "/")}/${relativeUnderUploads.replace(/\\/g, "/")}`;
2192
2192
  }
2193
2193
  await repo.save(
@@ -2609,12 +2609,12 @@ function createUploadHandler(config) {
2609
2609
  const fileUrl = await storageService.upload(buffer, `uploads/${relativeUnderUploads}`, contentType);
2610
2610
  return json({ filePath: fileUrl, parentId });
2611
2611
  }
2612
- const fs = await import("fs/promises");
2613
- const path = await import("path");
2614
- const dir = path.join(process.cwd(), localUploadDir);
2615
- const filePath = path.join(dir, relativeUnderUploads);
2616
- await fs.mkdir(path.dirname(filePath), { recursive: true });
2617
- await fs.writeFile(filePath, buffer);
2612
+ const fs2 = await import("fs/promises");
2613
+ const path2 = await import("path");
2614
+ const dir = path2.join(process.cwd(), localUploadDir);
2615
+ const filePath = path2.join(dir, relativeUnderUploads);
2616
+ await fs2.mkdir(path2.dirname(filePath), { recursive: true });
2617
+ await fs2.writeFile(filePath, buffer);
2618
2618
  const urlRel = `${localUploadDir.replace(/^\/+/, "").replace(/\\/g, "/")}/${relativeUnderUploads.replace(/\\/g, "/")}`;
2619
2619
  return json({ filePath: `/${urlRel}`, parentId });
2620
2620
  } catch (err) {
@@ -3268,11 +3268,11 @@ function createUserAvatarHandler(config) {
3268
3268
  const ext = file.name.split(".").pop() || "jpg";
3269
3269
  const fileName = `avatar_${session.user.email}_${Date.now()}.${ext}`;
3270
3270
  const avatarUrl = saveAvatar ? await saveAvatar(buffer, fileName) : await (async () => {
3271
- const fs = await import("fs/promises");
3272
- const path = await import("path");
3273
- const dir = path.join(process.cwd(), "public", "uploads", "avatars");
3274
- await fs.mkdir(dir, { recursive: true });
3275
- await fs.writeFile(path.join(dir, fileName), buffer);
3271
+ const fs2 = await import("fs/promises");
3272
+ const path2 = await import("path");
3273
+ const dir = path2.join(process.cwd(), "public", "uploads", "avatars");
3274
+ await fs2.mkdir(dir, { recursive: true });
3275
+ await fs2.writeFile(path2.join(dir, fileName), buffer);
3276
3276
  return `/uploads/avatars/${fileName}`;
3277
3277
  })();
3278
3278
  return json({ message: "Avatar uploaded successfully", avatarUrl });
@@ -5168,6 +5168,7 @@ var Blog = class {
5168
5168
  id;
5169
5169
  title;
5170
5170
  content;
5171
+ socialMediaContent;
5171
5172
  coverImage;
5172
5173
  authorId;
5173
5174
  categoryId;
@@ -5196,6 +5197,9 @@ __decorateClass([
5196
5197
  __decorateClass([
5197
5198
  Column11("text")
5198
5199
  ], Blog.prototype, "content", 2);
5200
+ __decorateClass([
5201
+ Column11("text", { nullable: true })
5202
+ ], Blog.prototype, "socialMediaContent", 2);
5199
5203
  __decorateClass([
5200
5204
  Column11("varchar", { nullable: true })
5201
5205
  ], Blog.prototype, "coverImage", 2);
@@ -5885,7 +5889,7 @@ __decorateClass([
5885
5889
  JoinColumn12({ name: "userId" })
5886
5890
  ], Contact.prototype, "user", 2);
5887
5891
  __decorateClass([
5888
- OneToMany8(() => FormSubmission, (fs) => fs.contact)
5892
+ OneToMany8(() => FormSubmission, (fs2) => fs2.contact)
5889
5893
  ], Contact.prototype, "form_submissions", 2);
5890
5894
  __decorateClass([
5891
5895
  OneToMany8(() => Address, (a) => a.contact)
@@ -7181,6 +7185,145 @@ LlmAgentKnowledgeDocument = __decorateClass([
7181
7185
  Index2("IDX_llm_agent_knowledge_agent", ["agentId"])
7182
7186
  ], LlmAgentKnowledgeDocument);
7183
7187
 
7188
+ // src/entities/rss-feed.entity.ts
7189
+ import {
7190
+ Entity as Entity42,
7191
+ PrimaryGeneratedColumn as PrimaryGeneratedColumn42,
7192
+ Column as Column42,
7193
+ CreateDateColumn as CreateDateColumn2,
7194
+ UpdateDateColumn,
7195
+ OneToMany as OneToMany17
7196
+ } from "typeorm";
7197
+
7198
+ // src/entities/rss-article.entity.ts
7199
+ import {
7200
+ Entity as Entity41,
7201
+ PrimaryGeneratedColumn as PrimaryGeneratedColumn41,
7202
+ Column as Column41,
7203
+ ManyToOne as ManyToOne28,
7204
+ JoinColumn as JoinColumn28,
7205
+ CreateDateColumn,
7206
+ Unique as Unique3
7207
+ } from "typeorm";
7208
+ var RssArticle = class {
7209
+ id;
7210
+ rssFeedId;
7211
+ rssFeed;
7212
+ externalId;
7213
+ title;
7214
+ articleUrl;
7215
+ summary;
7216
+ content;
7217
+ author;
7218
+ publishedAt;
7219
+ imageUrl;
7220
+ rawData;
7221
+ contentHash;
7222
+ isProcessed;
7223
+ createdAt;
7224
+ };
7225
+ __decorateClass([
7226
+ PrimaryGeneratedColumn41("uuid")
7227
+ ], RssArticle.prototype, "id", 2);
7228
+ __decorateClass([
7229
+ Column41("uuid")
7230
+ ], RssArticle.prototype, "rssFeedId", 2);
7231
+ __decorateClass([
7232
+ ManyToOne28(() => RssFeed, (f) => f.articles, { onDelete: "CASCADE" }),
7233
+ JoinColumn28({ name: "rssFeedId" })
7234
+ ], RssArticle.prototype, "rssFeed", 2);
7235
+ __decorateClass([
7236
+ Column41({ type: "text", nullable: true })
7237
+ ], RssArticle.prototype, "externalId", 2);
7238
+ __decorateClass([
7239
+ Column41({ type: "text" })
7240
+ ], RssArticle.prototype, "title", 2);
7241
+ __decorateClass([
7242
+ Column41({ type: "text" })
7243
+ ], RssArticle.prototype, "articleUrl", 2);
7244
+ __decorateClass([
7245
+ Column41({ type: "text", nullable: true })
7246
+ ], RssArticle.prototype, "summary", 2);
7247
+ __decorateClass([
7248
+ Column41({ type: "text", nullable: true })
7249
+ ], RssArticle.prototype, "content", 2);
7250
+ __decorateClass([
7251
+ Column41({ type: "text", nullable: true })
7252
+ ], RssArticle.prototype, "author", 2);
7253
+ __decorateClass([
7254
+ Column41({ type: "timestamp", nullable: true })
7255
+ ], RssArticle.prototype, "publishedAt", 2);
7256
+ __decorateClass([
7257
+ Column41({ type: "text", nullable: true })
7258
+ ], RssArticle.prototype, "imageUrl", 2);
7259
+ __decorateClass([
7260
+ Column41({ type: "jsonb", nullable: true })
7261
+ ], RssArticle.prototype, "rawData", 2);
7262
+ __decorateClass([
7263
+ Column41({ type: "text", nullable: true })
7264
+ ], RssArticle.prototype, "contentHash", 2);
7265
+ __decorateClass([
7266
+ Column41({ type: "boolean", default: false })
7267
+ ], RssArticle.prototype, "isProcessed", 2);
7268
+ __decorateClass([
7269
+ CreateDateColumn()
7270
+ ], RssArticle.prototype, "createdAt", 2);
7271
+ RssArticle = __decorateClass([
7272
+ Entity41("rss_articles"),
7273
+ Unique3("UQ_rss_articles_feed_article_url", ["rssFeedId", "articleUrl"])
7274
+ ], RssArticle);
7275
+
7276
+ // src/entities/rss-feed.entity.ts
7277
+ var RssFeed = class {
7278
+ id;
7279
+ name;
7280
+ rssUrl;
7281
+ websiteUrl;
7282
+ isActive;
7283
+ fetchFrequencyMinutes;
7284
+ lastFetchedAt;
7285
+ lastArticleDate;
7286
+ articles;
7287
+ createdAt;
7288
+ updatedAt;
7289
+ };
7290
+ __decorateClass([
7291
+ PrimaryGeneratedColumn42("uuid")
7292
+ ], RssFeed.prototype, "id", 2);
7293
+ __decorateClass([
7294
+ Column42({ type: "text" })
7295
+ ], RssFeed.prototype, "name", 2);
7296
+ __decorateClass([
7297
+ Column42({ type: "text", unique: true })
7298
+ ], RssFeed.prototype, "rssUrl", 2);
7299
+ __decorateClass([
7300
+ Column42({ type: "text", nullable: true })
7301
+ ], RssFeed.prototype, "websiteUrl", 2);
7302
+ __decorateClass([
7303
+ Column42({ type: "boolean", default: true })
7304
+ ], RssFeed.prototype, "isActive", 2);
7305
+ __decorateClass([
7306
+ Column42({ type: "int", default: 60 })
7307
+ ], RssFeed.prototype, "fetchFrequencyMinutes", 2);
7308
+ __decorateClass([
7309
+ Column42({ type: "timestamp", nullable: true })
7310
+ ], RssFeed.prototype, "lastFetchedAt", 2);
7311
+ __decorateClass([
7312
+ Column42({ type: "timestamp", nullable: true })
7313
+ ], RssFeed.prototype, "lastArticleDate", 2);
7314
+ __decorateClass([
7315
+ OneToMany17(() => RssArticle, (article) => article.rssFeed)
7316
+ ], RssFeed.prototype, "articles", 2);
7317
+ __decorateClass([
7318
+ CreateDateColumn2()
7319
+ ], RssFeed.prototype, "createdAt", 2);
7320
+ __decorateClass([
7321
+ UpdateDateColumn()
7322
+ ], RssFeed.prototype, "updatedAt", 2);
7323
+ RssFeed = __decorateClass([
7324
+ Entity42("rss_feeds")
7325
+ ], RssFeed);
7326
+
7184
7327
  // src/entities/index.ts
7185
7328
  var CMS_ENTITY_MAP = {
7186
7329
  users: User,
@@ -7222,9 +7365,1211 @@ var CMS_ENTITY_MAP = {
7222
7365
  wishlists: Wishlist,
7223
7366
  wishlist_items: WishlistItem,
7224
7367
  llm_agents: LlmAgent,
7225
- llm_agent_knowledge_documents: LlmAgentKnowledgeDocument
7368
+ llm_agent_knowledge_documents: LlmAgentKnowledgeDocument,
7369
+ rss_feeds: RssFeed,
7370
+ rss_articles: RssArticle
7226
7371
  };
7227
7372
 
7373
+ // src/plugins/blog-generator/blog-generator-service.ts
7374
+ import Parser from "rss-parser";
7375
+
7376
+ // src/plugins/blog-generator/blog-generator-agent-defaults.ts
7377
+ var BLOG_GENERATOR_LLM_AGENT_SLUG = "blog-generator";
7378
+ var BLOG_GENERATOR_MARKDOWN_ARTICLE_SEPARATOR = "---BLOG_GENERATOR_NEXT---";
7379
+ var BLOG_GENERATOR_DEFAULT_SYSTEM_INSTRUCTION = `You are a professional financial and business content writer.
7380
+
7381
+ 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.
7382
+
7383
+ IMPORTANT RULES:
7384
+
7385
+ 1. NEVER copy the RSS article directly.
7386
+ 2. Rewrite the information into a fresh, human-like article.
7387
+ 3. Expand the topic with professional insights and explanations.
7388
+ 4. Maintain a natural editorial tone.
7389
+ 5. Make the article SEO-friendly.
7390
+ 6. Use engaging headings and subheadings.
7391
+ 7. Avoid robotic AI phrasing.
7392
+ 8. Do not mention that the content came from RSS or feeds.
7393
+ 9. Preserve factual accuracy from the source material.
7394
+ 10. The output should feel like a professionally written industry blog.
7395
+
7396
+ MULTIPLE FEEDS:
7397
+ - If several feeds are provided, compare them: if they largely cover the same story or overlap heavily, produce ONE cohesive article.
7398
+ - If they cover clearly different topics or distinct stories, produce MULTIPLE articles (one per distinct story).
7399
+ - You may also produce one synthesis plus a short separate angle when that best serves the reader\u2014use your judgment.
7400
+
7401
+ WRITING STYLE:
7402
+ - Professional
7403
+ - Clear
7404
+ - Informative
7405
+ - Business-focused
7406
+ - Human sounding
7407
+ - Modern editorial style
7408
+
7409
+ ARTICLE STRUCTURE (each article \u2014 express in HTML):
7410
+ 1. Engaging introduction
7411
+ 2. Industry context
7412
+ 3. Main developments
7413
+ 4. Key implications
7414
+ 5. Expert/business analysis
7415
+ 6. Conclusion
7416
+
7417
+ HTML STRUCTURE (STRICT \u2014 each article must be valid, semantic HTML only):
7418
+ - Output HTML only. Do not use Markdown (no # headings, no **bold**, no \`code fences\`).
7419
+ - Wrap each complete article in a single root: <article class="blog-post"> ... </article>
7420
+ - Inside <article>, use this outline:
7421
+ - <header><h1 class="blog-post-title">\u2026main title\u2026</h1></header> (exactly one h1 per article)
7422
+ - <section class="blog-post-body"> for all following content
7423
+ - Use <h2> and <h3> for section and subsection titles (never skip levels: h1 \u2192 h2 \u2192 h3).
7424
+ - Use <p> for paragraphs; keep paragraphs focused (avoid huge unbroken text).
7425
+ - Use <ul>/<ol> with <li> for lists where appropriate.
7426
+ - Use <strong> and <em> for emphasis sparingly; use <blockquote> only when quoting or callouts fit.
7427
+ - Do not include <html>, <head>, <body>, or document-level wrappers \u2014 only the fragment(s) described above.
7428
+ - Do not use <script>, <style>, <iframe>, or inline event handlers. Avoid inline style="" except when essential for accessibility (prefer none).
7429
+ - Escape angle brackets in body text if you must mention markup; keep output safe for embedding in a CMS.
7430
+
7431
+ CONTENT REQUIREMENTS:
7432
+ - Minimum 800 words per article unless source material is very small.
7433
+ - Add context around industry trends where appropriate.
7434
+ - Explain why the topic matters.
7435
+ - Include practical implications for businesses/professionals.
7436
+
7437
+ IF RSS CONTENT IS LIMITED:
7438
+ - Expand intelligently using general industry knowledge.
7439
+ - Keep the article relevant to the original topic.
7440
+ - Do not invent fake facts or statistics.
7441
+
7442
+ OUTPUT FORMAT (STRICT \u2014 HTML only):
7443
+ - Return ONLY the article HTML fragment(s). No JSON, no preamble or postscript ("Here is your article\u2026"), no markdown.
7444
+ - 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):
7445
+ ${BLOG_GENERATOR_MARKDOWN_ARTICLE_SEPARATOR}
7446
+ - If you output a single article, use one <article> only and do not use that separator line.`;
7447
+ var BLOG_GENERATOR_DEFAULT_VALIDATION_RULES = JSON.stringify(
7448
+ {
7449
+ maxUserChars: 5e5,
7450
+ 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.`
7451
+ },
7452
+ null,
7453
+ 2
7454
+ );
7455
+
7456
+ // src/plugins/blog-generator/blog-generator-metadata-defaults.ts
7457
+ var BLOG_METADATA_ENRICHER_LLM_AGENT_SLUG = "blog-generator-metadata";
7458
+ var BLOG_METADATA_ENRICHER_DEFAULT_VALIDATION_RULES = JSON.stringify(
7459
+ {
7460
+ maxUserChars: 5e5,
7461
+ 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)."
7462
+ },
7463
+ null,
7464
+ 2
7465
+ );
7466
+
7467
+ // src/plugins/blog-generator/blog-generator-social-defaults.ts
7468
+ var BLOG_SOCIAL_ENRICHER_LLM_AGENT_SLUG = "blog-generator-social";
7469
+ var BLOG_SOCIAL_ENRICHER_DEFAULT_VALIDATION_RULES = JSON.stringify(
7470
+ {
7471
+ maxUserChars: 5e5,
7472
+ guardrails: "Reply with one valid JSON object only. Key: socialMediaContent (string, plain text, max ~2900 chars). No markdown fences."
7473
+ },
7474
+ null,
7475
+ 2
7476
+ );
7477
+
7478
+ // src/plugins/blog-generator/blog-generator-service.ts
7479
+ var parser = new Parser({
7480
+ defaultRSS: 2
7481
+ });
7482
+ function resolveBlogCategoryIdByName(needle, rows) {
7483
+ if (!needle?.trim()) return { categoryId: null, matched: false };
7484
+ const n = needle.trim().toLowerCase();
7485
+ const hit = rows.find((r) => r.name.trim().toLowerCase() === n);
7486
+ return hit ? { categoryId: hit.id, matched: true } : { categoryId: null, matched: false };
7487
+ }
7488
+
7489
+ // src/plugins/blog-generator/blog-generator-persist.ts
7490
+ function slugBase(draft) {
7491
+ const raw = (draft.slug?.trim() || draft.title || "post").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 100);
7492
+ return raw || "post";
7493
+ }
7494
+ function normalizeSlugSegment(input) {
7495
+ const t = input.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 100);
7496
+ return t || "post";
7497
+ }
7498
+ async function allocateUniqueSlug(repo, base) {
7499
+ let b = normalizeSlugSegment(base);
7500
+ for (let i = 0; i < 120; i++) {
7501
+ const s = i === 0 ? b : `${b}-${i + 1}`.slice(0, 160);
7502
+ const exists = await repo.findOne({ where: { slug: s } });
7503
+ if (!exists) return s;
7504
+ }
7505
+ throw new Error("Could not allocate a unique slug");
7506
+ }
7507
+ async function findOrCreateTag(repo, name) {
7508
+ const trimmed = name.trim().slice(0, 500);
7509
+ if (!trimmed) throw new Error("Empty tag name");
7510
+ let row = await repo.findOne({ where: { name: trimmed } });
7511
+ if (row && row.deleted) {
7512
+ await repo.update(
7513
+ { id: row.id },
7514
+ { deleted: false, updatedAt: /* @__PURE__ */ new Date() }
7515
+ );
7516
+ row = await repo.findOne({ where: { id: row.id } });
7517
+ return row;
7518
+ }
7519
+ if (row) return row;
7520
+ const now = /* @__PURE__ */ new Date();
7521
+ return repo.save(
7522
+ repo.create({
7523
+ name: trimmed,
7524
+ deleted: false,
7525
+ createdAt: now,
7526
+ updatedAt: now
7527
+ })
7528
+ );
7529
+ }
7530
+ async function findOrCreateCategory(repo, displayName) {
7531
+ const n = displayName.trim().slice(0, 500);
7532
+ if (!n) return null;
7533
+ let row = await repo.createQueryBuilder("c").where("LOWER(TRIM(c.name)) = LOWER(TRIM(:n))", { n }).getOne();
7534
+ if (row && row.deleted) {
7535
+ await repo.update(
7536
+ { id: row.id },
7537
+ { deleted: false, updatedAt: /* @__PURE__ */ new Date() }
7538
+ );
7539
+ row = await repo.findOne({ where: { id: row.id } });
7540
+ return row;
7541
+ }
7542
+ if (row) return row;
7543
+ const now = /* @__PURE__ */ new Date();
7544
+ return repo.save(
7545
+ repo.create({
7546
+ name: n,
7547
+ deleted: false,
7548
+ createdAt: now,
7549
+ updatedAt: now
7550
+ })
7551
+ );
7552
+ }
7553
+ async function persistGeneratedBlogDraft(manager, maps, params) {
7554
+ const { draft, authorId } = params;
7555
+ const blogRepo = manager.getRepository(maps.blogs);
7556
+ const tagRepo = manager.getRepository(maps.tags);
7557
+ const catRepo = manager.getRepository(maps.categories);
7558
+ const seoRepo = manager.getRepository(maps.seos);
7559
+ const uniqueBlogSlug = await allocateUniqueSlug(blogRepo, slugBase(draft));
7560
+ const uniqueSeoSlug = await allocateUniqueSlug(seoRepo, uniqueBlogSlug);
7561
+ const seoRow = await seoRepo.save(
7562
+ seoRepo.create({
7563
+ slug: uniqueSeoSlug,
7564
+ title: draft.seo.title,
7565
+ description: draft.seo.description,
7566
+ keywords: draft.seo.keywords,
7567
+ ogTitle: draft.seo.ogTitle ?? draft.seo.title,
7568
+ ogDescription: draft.seo.ogDescription ?? draft.seo.description,
7569
+ ogImage: null,
7570
+ deleted: false,
7571
+ createdAt: /* @__PURE__ */ new Date(),
7572
+ updatedAt: /* @__PURE__ */ new Date()
7573
+ })
7574
+ );
7575
+ const seoId = Number(seoRow.id);
7576
+ let categoryId = null;
7577
+ if (draft.categoryName?.trim()) {
7578
+ const cat = await findOrCreateCategory(catRepo, draft.categoryName);
7579
+ if (cat) categoryId = Number(cat.id);
7580
+ }
7581
+ const tagEntities = [];
7582
+ const seen = /* @__PURE__ */ new Set();
7583
+ for (const t of draft.tags ?? []) {
7584
+ const key = t.trim().toLowerCase();
7585
+ if (!key || seen.has(key)) continue;
7586
+ seen.add(key);
7587
+ tagEntities.push(await findOrCreateTag(tagRepo, t));
7588
+ }
7589
+ const socialTrim = typeof draft.socialMediaContent === "string" && draft.socialMediaContent.trim() ? draft.socialMediaContent.trim().slice(0, 2900) : null;
7590
+ const now = /* @__PURE__ */ new Date();
7591
+ const blogRow = await blogRepo.save(
7592
+ blogRepo.create({
7593
+ title: draft.title.trim().slice(0, 500) || "Untitled",
7594
+ content: draft.markdown,
7595
+ socialMediaContent: socialTrim,
7596
+ slug: uniqueBlogSlug,
7597
+ authorId,
7598
+ categoryId,
7599
+ seoId,
7600
+ published: false,
7601
+ deleted: false,
7602
+ coverImage: null,
7603
+ createdBy: authorId,
7604
+ updatedBy: authorId,
7605
+ createdAt: now,
7606
+ updatedAt: now
7607
+ })
7608
+ );
7609
+ const blogId = Number(blogRow.id);
7610
+ if (tagEntities.length > 0) {
7611
+ const blog = await blogRepo.findOne({
7612
+ where: { id: blogId },
7613
+ relations: ["tags"]
7614
+ });
7615
+ if (blog) {
7616
+ blog.tags = tagEntities;
7617
+ await blogRepo.save(blog);
7618
+ }
7619
+ }
7620
+ const tagIds = tagEntities.map((e) => Number(e.id)).filter((id) => Number.isFinite(id));
7621
+ return { blogId, slug: uniqueBlogSlug, categoryId, seoId, tagIds };
7622
+ }
7623
+ async function persistAllGeneratedBlogDrafts(dataSource, maps, params) {
7624
+ const out = [];
7625
+ for (const draft of params.drafts) {
7626
+ const row = await dataSource.transaction(
7627
+ (manager) => persistGeneratedBlogDraft(manager, maps, { draft, authorId: params.authorId })
7628
+ );
7629
+ out.push(row);
7630
+ }
7631
+ return out;
7632
+ }
7633
+
7634
+ // src/api/rss-feed-blog-api.ts
7635
+ function deriveRssFeedDisplayName(rssUrl) {
7636
+ try {
7637
+ const u = new URL(rssUrl);
7638
+ const h = u.hostname.replace(/^www\./i, "");
7639
+ return (h || rssUrl).slice(0, 500);
7640
+ } catch {
7641
+ return rssUrl.slice(0, 500);
7642
+ }
7643
+ }
7644
+ async function upsertRssFeedRow(dataSource, feedEntity, rssUrl, opts) {
7645
+ const repo = dataSource.getRepository(feedEntity);
7646
+ const trimmed = rssUrl.trim();
7647
+ const name = (opts?.name?.trim() || deriveRssFeedDisplayName(trimmed)).slice(0, 500);
7648
+ let row = await repo.findOne({ where: { rssUrl: trimmed } });
7649
+ if (!row) {
7650
+ row = repo.create({
7651
+ name,
7652
+ rssUrl: trimmed,
7653
+ websiteUrl: opts?.websiteUrl?.trim() || null,
7654
+ isActive: true,
7655
+ fetchFrequencyMinutes: 60
7656
+ });
7657
+ } else {
7658
+ row.name = name;
7659
+ if (opts?.websiteUrl !== void 0) {
7660
+ row.websiteUrl = opts.websiteUrl?.trim() || null;
7661
+ }
7662
+ row.updatedAt = /* @__PURE__ */ new Date();
7663
+ }
7664
+ return repo.save(row);
7665
+ }
7666
+ async function recordRssFetchSuccess(dataSource, feedEntity, articleEntity, rssUrl, latest) {
7667
+ const feedRepo = dataSource.getRepository(feedEntity);
7668
+ const row = await feedRepo.findOne({ where: { rssUrl: rssUrl.trim() } });
7669
+ if (!row) return;
7670
+ row.lastFetchedAt = /* @__PURE__ */ new Date();
7671
+ if (latest?.date) {
7672
+ row.lastArticleDate = latest.date;
7673
+ }
7674
+ row.updatedAt = /* @__PURE__ */ new Date();
7675
+ await feedRepo.save(row);
7676
+ const articleUrl = latest?.link?.trim();
7677
+ if (!latest || !articleUrl || !articleEntity) return;
7678
+ const artRepo = dataSource.getRepository(articleEntity);
7679
+ await artRepo.upsert(
7680
+ {
7681
+ rssFeedId: row.id,
7682
+ externalId: articleUrl.slice(0, 2e3),
7683
+ title: (latest.title?.trim() || "Untitled").slice(0, 2e3),
7684
+ articleUrl: articleUrl.slice(0, 2e3),
7685
+ summary: latest.summary?.trim() ? latest.summary.trim().slice(0, 1e5) : null,
7686
+ content: latest.content?.trim() ? latest.content.trim().slice(0, 5e5) : null,
7687
+ author: latest.creator?.trim() ? latest.creator.trim().slice(0, 500) : null,
7688
+ publishedAt: latest.date,
7689
+ imageUrl: null,
7690
+ rawData: { source: "blog-generator-latest", rssUrl: rssUrl.trim() },
7691
+ contentHash: null,
7692
+ isProcessed: false
7693
+ },
7694
+ { conflictPaths: ["rssFeedId", "articleUrl"], skipUpdateIfNoValuesChanged: false }
7695
+ );
7696
+ }
7697
+ function serializeRssFeed(f) {
7698
+ return {
7699
+ id: f.id,
7700
+ name: f.name,
7701
+ rssUrl: f.rssUrl,
7702
+ websiteUrl: f.websiteUrl,
7703
+ isActive: f.isActive,
7704
+ fetchFrequencyMinutes: f.fetchFrequencyMinutes,
7705
+ lastFetchedAt: f.lastFetchedAt?.toISOString() ?? null,
7706
+ lastArticleDate: f.lastArticleDate?.toISOString() ?? null,
7707
+ createdAt: f.createdAt.toISOString(),
7708
+ updatedAt: f.updatedAt.toISOString()
7709
+ };
7710
+ }
7711
+
7712
+ // src/plugins/social-media/social-media-api-handlers.ts
7713
+ import fs from "fs/promises";
7714
+ import path from "path";
7715
+
7716
+ // src/plugins/social-media/linkedin-client.ts
7717
+ var RESTLI = "2.0.0";
7718
+ async function linkedInGetUserinfo(accessToken) {
7719
+ const r = await fetch("https://api.linkedin.com/v2/userinfo", {
7720
+ headers: { Authorization: `Bearer ${accessToken}` }
7721
+ });
7722
+ const text = await r.text();
7723
+ if (!r.ok) {
7724
+ throw new Error(`LinkedIn userinfo failed (${r.status}): ${text.slice(0, 500)}`);
7725
+ }
7726
+ try {
7727
+ return JSON.parse(text);
7728
+ } catch {
7729
+ throw new Error("LinkedIn userinfo: invalid JSON");
7730
+ }
7731
+ }
7732
+ function personUrn(personSub) {
7733
+ const s = String(personSub || "").trim();
7734
+ if (s.startsWith("urn:li:person:")) return s;
7735
+ return `urn:li:person:${s}`;
7736
+ }
7737
+ async function linkedInRegisterImageUpload(accessToken, personSub) {
7738
+ const owner = personUrn(personSub);
7739
+ const r = await fetch("https://api.linkedin.com/v2/assets?action=registerUpload", {
7740
+ method: "POST",
7741
+ headers: {
7742
+ Authorization: `Bearer ${accessToken}`,
7743
+ "X-Restli-Protocol-Version": RESTLI,
7744
+ "Content-Type": "application/json"
7745
+ },
7746
+ body: JSON.stringify({
7747
+ registerUploadRequest: {
7748
+ recipes: ["urn:li:digitalmediaRecipe:feedshare-image"],
7749
+ owner,
7750
+ serviceRelationships: [
7751
+ {
7752
+ relationshipType: "OWNER",
7753
+ identifier: "urn:li:userGeneratedContent"
7754
+ }
7755
+ ]
7756
+ }
7757
+ })
7758
+ });
7759
+ const text = await r.text();
7760
+ if (!r.ok) {
7761
+ throw new Error(`LinkedIn registerUpload failed (${r.status}): ${text.slice(0, 800)}`);
7762
+ }
7763
+ let data;
7764
+ try {
7765
+ data = JSON.parse(text);
7766
+ } catch {
7767
+ throw new Error("LinkedIn registerUpload: invalid JSON");
7768
+ }
7769
+ const v = data.value;
7770
+ const uploadUrl = v?.uploadMechanism?.["com.linkedin.digitalmedia.uploading.MediaUploadHttpRequest"]?.uploadUrl;
7771
+ const asset = v?.asset;
7772
+ if (!uploadUrl || !asset) {
7773
+ throw new Error("LinkedIn registerUpload: missing uploadUrl or asset");
7774
+ }
7775
+ return { uploadUrl, asset };
7776
+ }
7777
+ async function linkedInUploadBinary(accessToken, uploadUrl, body, contentType) {
7778
+ const r = await fetch(uploadUrl, {
7779
+ method: "PUT",
7780
+ headers: {
7781
+ Authorization: `Bearer ${accessToken}`,
7782
+ "X-Restli-Protocol-Version": RESTLI,
7783
+ "Content-Type": contentType || "application/octet-stream"
7784
+ },
7785
+ body: new Uint8Array(body)
7786
+ });
7787
+ if (!r.ok) {
7788
+ const t = await r.text();
7789
+ throw new Error(`LinkedIn binary upload failed (${r.status}): ${t.slice(0, 500)}`);
7790
+ }
7791
+ }
7792
+ async function linkedInCreateImageShare(params) {
7793
+ const author = personUrn(params.personSub);
7794
+ const commentary = params.commentary.slice(0, 2900);
7795
+ const title = params.title.slice(0, 200);
7796
+ const description = (params.description ?? title).slice(0, 200);
7797
+ const r = await fetch("https://api.linkedin.com/v2/ugcPosts", {
7798
+ method: "POST",
7799
+ headers: {
7800
+ Authorization: `Bearer ${params.accessToken}`,
7801
+ "X-Restli-Protocol-Version": RESTLI,
7802
+ "Content-Type": "application/json"
7803
+ },
7804
+ body: JSON.stringify({
7805
+ author,
7806
+ lifecycleState: "PUBLISHED",
7807
+ specificContent: {
7808
+ "com.linkedin.ugc.ShareContent": {
7809
+ shareCommentary: { attributes: [], text: commentary },
7810
+ shareMediaCategory: "IMAGE",
7811
+ media: [
7812
+ {
7813
+ status: "READY",
7814
+ description: { attributes: [], text: description },
7815
+ media: params.asset,
7816
+ title: { attributes: [], text: title }
7817
+ }
7818
+ ]
7819
+ }
7820
+ },
7821
+ visibility: {
7822
+ "com.linkedin.ugc.MemberNetworkVisibility": "PUBLIC"
7823
+ }
7824
+ })
7825
+ });
7826
+ const text = await r.text();
7827
+ if (!r.ok) {
7828
+ throw new Error(`LinkedIn ugcPosts failed (${r.status}): ${text.slice(0, 1200)}`);
7829
+ }
7830
+ let data;
7831
+ try {
7832
+ data = JSON.parse(text);
7833
+ } catch {
7834
+ return { status: r.status };
7835
+ }
7836
+ return { status: r.status, id: data.id };
7837
+ }
7838
+ async function linkedInCreateTextShare(params) {
7839
+ const author = personUrn(params.personSub);
7840
+ const commentary = params.commentary.slice(0, 2900);
7841
+ const r = await fetch("https://api.linkedin.com/v2/ugcPosts", {
7842
+ method: "POST",
7843
+ headers: {
7844
+ Authorization: `Bearer ${params.accessToken}`,
7845
+ "X-Restli-Protocol-Version": RESTLI,
7846
+ "Content-Type": "application/json"
7847
+ },
7848
+ body: JSON.stringify({
7849
+ author,
7850
+ lifecycleState: "PUBLISHED",
7851
+ specificContent: {
7852
+ "com.linkedin.ugc.ShareContent": {
7853
+ shareCommentary: { attributes: [], text: commentary },
7854
+ shareMediaCategory: "NONE"
7855
+ }
7856
+ },
7857
+ visibility: {
7858
+ "com.linkedin.ugc.MemberNetworkVisibility": "PUBLIC"
7859
+ }
7860
+ })
7861
+ });
7862
+ const text = await r.text();
7863
+ if (!r.ok) {
7864
+ throw new Error(`LinkedIn ugcPosts (text) failed (${r.status}): ${text.slice(0, 1200)}`);
7865
+ }
7866
+ let data;
7867
+ try {
7868
+ data = JSON.parse(text);
7869
+ } catch {
7870
+ return { status: r.status };
7871
+ }
7872
+ return { status: r.status, id: data.id };
7873
+ }
7874
+
7875
+ // src/plugins/social-media/meta-service.ts
7876
+ var GRAPH_API_VERSION = "v23.0";
7877
+ var GRAPH_BASE = `https://graph.facebook.com/${GRAPH_API_VERSION}`;
7878
+ async function metaFetchUserManagedPages(userAccessToken) {
7879
+ const token = String(userAccessToken || "").trim();
7880
+ if (!token) {
7881
+ throw new Error("User access token is required");
7882
+ }
7883
+ const url = new URL(`${GRAPH_BASE}/me/accounts`);
7884
+ url.searchParams.set("access_token", token);
7885
+ const r = await fetch(url.toString(), { method: "GET", headers: { Accept: "application/json" } });
7886
+ const text = await r.text();
7887
+ let body;
7888
+ try {
7889
+ body = JSON.parse(text);
7890
+ } catch {
7891
+ throw new Error(`Facebook Graph returned invalid JSON (${r.status})`);
7892
+ }
7893
+ if (body.error?.message) {
7894
+ const code = body.error.code != null ? ` (code ${body.error.code})` : "";
7895
+ throw new Error(`${body.error.message}${code}`);
7896
+ }
7897
+ if (!r.ok) {
7898
+ throw new Error(`Facebook Graph request failed (${r.status}): ${text.slice(0, 600)}`);
7899
+ }
7900
+ return body;
7901
+ }
7902
+ function parseGraphMutation(text, httpOk) {
7903
+ let body;
7904
+ try {
7905
+ body = JSON.parse(text);
7906
+ } catch {
7907
+ throw new Error(`Facebook Graph returned invalid JSON (${httpOk ? "200" : "error"})`);
7908
+ }
7909
+ if (body.error?.message) {
7910
+ const code = body.error.code != null ? ` (code ${body.error.code})` : "";
7911
+ throw new Error(`${body.error.message}${code}`);
7912
+ }
7913
+ if (!httpOk) {
7914
+ throw new Error(`Facebook Graph request failed: ${text.slice(0, 600)}`);
7915
+ }
7916
+ return body;
7917
+ }
7918
+ function metaResolvePageAccessToken(accounts, pageId) {
7919
+ const id = String(pageId || "").trim();
7920
+ if (!id || !accounts.data?.length) return null;
7921
+ const row = accounts.data.find((p) => String(p.id ?? "").trim() === id);
7922
+ const tok = row?.access_token;
7923
+ return typeof tok === "string" && tok.trim() ? tok.trim() : null;
7924
+ }
7925
+ async function metaPostPagePhoto(opts) {
7926
+ const { pageId, pageAccessToken, imageBuffer, contentType, caption } = opts;
7927
+ const pid = String(pageId || "").trim();
7928
+ const tok = String(pageAccessToken || "").trim();
7929
+ if (!pid || !tok) throw new Error("Page ID and page access token are required");
7930
+ if (!imageBuffer?.length) throw new Error("Image buffer is empty");
7931
+ const url = `${GRAPH_BASE}/${encodeURIComponent(pid)}/photos`;
7932
+ const lower = contentType.split(";")[0].trim().toLowerCase();
7933
+ const filename = lower.includes("png") ? "photo.png" : "photo.jpg";
7934
+ const blob = new Blob([new Uint8Array(imageBuffer)], { type: lower || "image/jpeg" });
7935
+ const form = new FormData();
7936
+ form.append("access_token", tok);
7937
+ form.append("caption", String(caption || "").trim().slice(0, 2200));
7938
+ form.append("source", blob, filename);
7939
+ const r = await fetch(url, { method: "POST", body: form });
7940
+ const text = await r.text();
7941
+ return parseGraphMutation(text, r.ok);
7942
+ }
7943
+ async function metaPostPageFeed(opts) {
7944
+ const { pageId, pageAccessToken, message } = opts;
7945
+ const pid = String(pageId || "").trim();
7946
+ const tok = String(pageAccessToken || "").trim();
7947
+ if (!pid || !tok) throw new Error("Page ID and page access token are required");
7948
+ const url = new URL(`${GRAPH_BASE}/${encodeURIComponent(pid)}/feed`);
7949
+ const params = new URLSearchParams();
7950
+ params.set("access_token", tok);
7951
+ params.set("message", String(message || "").trim().slice(0, 5e3));
7952
+ const r = await fetch(url.toString(), {
7953
+ method: "POST",
7954
+ headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
7955
+ body: params.toString()
7956
+ });
7957
+ const text = await r.text();
7958
+ return parseGraphMutation(text, r.ok);
7959
+ }
7960
+
7961
+ // src/plugins/social-media/social-media-api-handlers.ts
7962
+ var SETTINGS_GROUP = "social_media";
7963
+ async function loadGroupMap(dataSource, entityMap, encryptionKey) {
7964
+ if (!entityMap.configs) return {};
7965
+ const repo = dataSource.getRepository(entityMap.configs);
7966
+ const rows = await repo.find({ where: { settings: SETTINGS_GROUP, deleted: false } });
7967
+ const out = {};
7968
+ for (const row of rows) {
7969
+ let val = row.value;
7970
+ if (row.encrypted && encryptionKey) {
7971
+ try {
7972
+ val = simpleDecrypt(val, encryptionKey);
7973
+ } catch {
7974
+ }
7975
+ }
7976
+ out[row.key] = val;
7977
+ }
7978
+ return out;
7979
+ }
7980
+ async function upsertConfigKey(dataSource, entityMap, key, value, opts) {
7981
+ if (!entityMap.configs) throw new Error("configs entity missing");
7982
+ const repo = dataSource.getRepository(entityMap.configs);
7983
+ let stored = value;
7984
+ if (opts.encrypted && opts.encryptionKey) {
7985
+ stored = simpleEncrypt(value, opts.encryptionKey);
7986
+ }
7987
+ const existing = await repo.findOne({ where: { settings: SETTINGS_GROUP, key } });
7988
+ if (existing) {
7989
+ await repo.update(existing.id, {
7990
+ value: stored,
7991
+ type: opts.type,
7992
+ encrypted: opts.encrypted,
7993
+ updatedAt: /* @__PURE__ */ new Date()
7994
+ });
7995
+ } else {
7996
+ await repo.save(
7997
+ repo.create({
7998
+ settings: SETTINGS_GROUP,
7999
+ key,
8000
+ value: stored,
8001
+ type: opts.type,
8002
+ encrypted: opts.encrypted
8003
+ })
8004
+ );
8005
+ }
8006
+ }
8007
+ function stripHtml(html, maxLen) {
8008
+ const t = String(html || "").replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<style[\s\S]*?<\/style>/gi, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
8009
+ return t.slice(0, maxLen);
8010
+ }
8011
+ function decodeHtmlAttr(s) {
8012
+ return String(s).replace(/&amp;/gi, "&").replace(/&quot;/gi, '"').replace(/&#39;/g, "'").replace(/&lt;/gi, "<").replace(/&gt;/gi, ">");
8013
+ }
8014
+ function extractFirstImageSrcFromHtml(html) {
8015
+ const s = String(html);
8016
+ const patterns = [
8017
+ /<img[^>]*\bsrc\s*=\s*["']([^"']+)["']/i,
8018
+ /<img[^>]*\bsrc\s*=\s*([^\s>]+)/i,
8019
+ /<img[^>]*\bdata-src\s*=\s*["']([^"']+)["']/i,
8020
+ /<img[^>]*\bdata-src\s*=\s*([^\s>]+)/i
8021
+ ];
8022
+ for (const re of patterns) {
8023
+ const m = s.match(re);
8024
+ const raw = m?.[1]?.trim();
8025
+ if (raw) return decodeHtmlAttr(raw);
8026
+ }
8027
+ return null;
8028
+ }
8029
+ function resolveAbsoluteUrl(publicSiteUrl, pathOrUrl) {
8030
+ if (!pathOrUrl || !String(pathOrUrl).trim()) return null;
8031
+ const s = decodeHtmlAttr(String(pathOrUrl).trim());
8032
+ if (/^https?:\/\//i.test(s)) return s;
8033
+ if (s.startsWith("//")) return `https:${s}`;
8034
+ const base = (publicSiteUrl || "").replace(/\/+$/, "");
8035
+ if (!base) return null;
8036
+ const rel = s.startsWith("/") ? s : `/${s}`;
8037
+ return `${base}${rel}`;
8038
+ }
8039
+ function guessImageContentType(filePathOrUrl, buf) {
8040
+ const lower = filePathOrUrl.toLowerCase();
8041
+ if (lower.includes(".png")) return "image/png";
8042
+ if (lower.includes(".webp")) return "image/webp";
8043
+ if (lower.includes(".gif")) return "image/gif";
8044
+ if (lower.includes(".jpg") || lower.includes(".jpeg")) return "image/jpeg";
8045
+ if (buf.length >= 2 && buf[0] === 255 && buf[1] === 216) return "image/jpeg";
8046
+ if (buf.length >= 8 && buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71) return "image/png";
8047
+ if (buf.length >= 12 && buf[8] === 87 && buf[9] === 69 && buf[10] === 66 && buf[11] === 80) return "image/webp";
8048
+ return "image/jpeg";
8049
+ }
8050
+ function isLinkedInFeedshareCompatibleContentType(mime) {
8051
+ const m = mime.split(";")[0].trim().toLowerCase();
8052
+ return m === "image/jpeg" || m === "image/png";
8053
+ }
8054
+ async function loadImageBytesForBlogShare(coverImage, contentHtml, publicSiteUrl, req) {
8055
+ const origin = publicSiteUrl || inferRequestOrigin(req) || "";
8056
+ const fromHtml = extractFirstImageSrcFromHtml(contentHtml);
8057
+ const candidates = [];
8058
+ const seenDecoded = /* @__PURE__ */ new Set();
8059
+ const pushCand = (role, raw) => {
8060
+ const decoded = decodeHtmlAttr(raw.trim());
8061
+ if (!decoded || decoded.startsWith("data:")) return;
8062
+ if (seenDecoded.has(decoded)) return;
8063
+ seenDecoded.add(decoded);
8064
+ candidates.push({ role, raw });
8065
+ };
8066
+ if (coverImage?.trim()) pushCand("cover", coverImage.trim());
8067
+ if (fromHtml) pushCand("bodyImage", fromHtml);
8068
+ for (const { role, raw } of candidates) {
8069
+ const decoded = decodeHtmlAttr(raw.trim());
8070
+ if (decoded.startsWith("data:")) continue;
8071
+ if (decoded.startsWith("/uploads/") || decoded.startsWith("uploads/")) {
8072
+ const rel = decoded.startsWith("/") ? decoded.slice(1) : decoded;
8073
+ const tries = [path.join(process.cwd(), "public", rel), path.join(process.cwd(), rel)];
8074
+ for (const filePath of tries) {
8075
+ try {
8076
+ const buf = await fs.readFile(filePath);
8077
+ if (buf.length > 0) {
8078
+ const contentType = guessImageContentType(filePath, buf);
8079
+ if (!isLinkedInFeedshareCompatibleContentType(contentType)) {
8080
+ continue;
8081
+ }
8082
+ return {
8083
+ buffer: buf,
8084
+ contentType,
8085
+ source: `${role}:local_file:${truncateForLog(rel, 120)}`
8086
+ };
8087
+ }
8088
+ } catch {
8089
+ }
8090
+ }
8091
+ }
8092
+ const url = resolveAbsoluteUrl(origin, decoded);
8093
+ if (!url) continue;
8094
+ try {
8095
+ const imgRes = await fetch(url, {
8096
+ headers: {
8097
+ "User-Agent": "InfuroCMS/1.0 (social share; +https://infuro.com)",
8098
+ Accept: "image/*,*/*;q=0.5"
8099
+ }
8100
+ });
8101
+ if (!imgRes.ok) continue;
8102
+ const buf = Buffer.from(await imgRes.arrayBuffer());
8103
+ if (buf.length === 0) continue;
8104
+ const hdr = imgRes.headers.get("content-type");
8105
+ const contentType = hdr && /^image\//i.test(hdr) ? hdr.split(";")[0].trim() : guessImageContentType(url, buf);
8106
+ if (!isLinkedInFeedshareCompatibleContentType(contentType)) {
8107
+ continue;
8108
+ }
8109
+ return {
8110
+ buffer: buf,
8111
+ contentType,
8112
+ source: `${role}:fetch:${truncateForLog(url, 160)}`
8113
+ };
8114
+ } catch {
8115
+ }
8116
+ }
8117
+ return null;
8118
+ }
8119
+ function inferRequestOrigin(req) {
8120
+ const host = req.headers.get("x-forwarded-host") || req.headers.get("host");
8121
+ if (!host) return void 0;
8122
+ const rawProto = req.headers.get("x-forwarded-proto") || "https";
8123
+ const proto = rawProto.split(",")[0]?.trim() || "https";
8124
+ return `${proto}://${host}`.replace(/\/+$/, "");
8125
+ }
8126
+ function linkedInPublishLoggingEnabled(config) {
8127
+ if (config.linkedInPublishLogging === false) return false;
8128
+ if (config.linkedInPublishLogging === true) return true;
8129
+ try {
8130
+ if (typeof process !== "undefined" && String(process.env.CMS_LINKEDIN_PUBLISH_LOG).trim() === "0") {
8131
+ return false;
8132
+ }
8133
+ } catch {
8134
+ }
8135
+ return true;
8136
+ }
8137
+ function logLinkedInPublish(enabled, event, detail) {
8138
+ if (!enabled) return;
8139
+ const line = `[LinkedIn publish] ${event} ${JSON.stringify(detail)}`;
8140
+ if (event === "failed" || event === "image_path_failed_falling_back_to_text") {
8141
+ console.warn(line);
8142
+ } else {
8143
+ console.info(line);
8144
+ }
8145
+ }
8146
+ function truncateForLog(s, max = 160) {
8147
+ const t = String(s).replace(/\s+/g, " ").trim();
8148
+ if (t.length <= max) return t;
8149
+ return `${t.slice(0, max)}\u2026`;
8150
+ }
8151
+ function createSocialMediaHandlers(config) {
8152
+ const {
8153
+ dataSource,
8154
+ entityMap,
8155
+ json,
8156
+ requireAuth,
8157
+ requireEntityPermission,
8158
+ encryptionKey,
8159
+ publicSiteUrl
8160
+ } = config;
8161
+ const publishLog = linkedInPublishLoggingEnabled(config);
8162
+ return {
8163
+ async getLinkedInStatus(req) {
8164
+ const a = await requireAuth(req);
8165
+ if (a) return a;
8166
+ const pe = await requireEntityPermission(req, "blogs", "read");
8167
+ if (pe) return pe;
8168
+ try {
8169
+ const map = await loadGroupMap(dataSource, entityMap, encryptionKey);
8170
+ const enabled = map.enabled !== "false";
8171
+ const token = (map.linkedin_access_token ?? "").trim();
8172
+ const sub = (map.linkedin_person_sub ?? "").trim();
8173
+ return json({
8174
+ ok: true,
8175
+ linkedInReady: enabled && Boolean(token) && Boolean(sub),
8176
+ enabled,
8177
+ hasToken: Boolean(token),
8178
+ hasPersonSub: Boolean(sub)
8179
+ });
8180
+ } catch (e) {
8181
+ const msg = e instanceof Error ? e.message : "Failed to load status";
8182
+ return json({ error: msg }, { status: 500 });
8183
+ }
8184
+ },
8185
+ async syncLinkedInProfile(req) {
8186
+ const a = await requireAuth(req);
8187
+ if (a) return a;
8188
+ const pe = await requireEntityPermission(req, "blogs", "read");
8189
+ if (pe) return pe;
8190
+ try {
8191
+ const map = await loadGroupMap(dataSource, entityMap, encryptionKey);
8192
+ const token = (map.linkedin_access_token ?? "").trim();
8193
+ if (!token) {
8194
+ return json({ error: "Save a LinkedIn access token in Plugins \u2192 Social media first." }, { status: 400 });
8195
+ }
8196
+ const info = await linkedInGetUserinfo(token);
8197
+ const sub = String(info.sub ?? "").trim();
8198
+ if (!sub) {
8199
+ return json({ error: "LinkedIn userinfo did not return a subject (sub)." }, { status: 502 });
8200
+ }
8201
+ await upsertConfigKey(dataSource, entityMap, "linkedin_person_sub", sub, {
8202
+ type: "private",
8203
+ encrypted: false,
8204
+ encryptionKey
8205
+ });
8206
+ return json({
8207
+ ok: true,
8208
+ linkedin_person_sub: sub,
8209
+ name: info.name ?? null
8210
+ });
8211
+ } catch (e) {
8212
+ const msg = e instanceof Error ? e.message : "Sync failed";
8213
+ return json({ error: msg }, { status: 502 });
8214
+ }
8215
+ },
8216
+ async publishBlogToLinkedIn(req, blogId) {
8217
+ const a = await requireAuth(req);
8218
+ if (a) return a;
8219
+ const pe = await requireEntityPermission(req, "blogs", "update");
8220
+ if (pe) return pe;
8221
+ if (!Number.isFinite(blogId) || blogId < 1) {
8222
+ return json({ error: "Invalid blog id" }, { status: 400 });
8223
+ }
8224
+ if (!entityMap.blogs) {
8225
+ return json({ error: "Blogs entity not configured" }, { status: 503 });
8226
+ }
8227
+ try {
8228
+ const map = await loadGroupMap(dataSource, entityMap, encryptionKey);
8229
+ if (map.enabled === "false") {
8230
+ return json({ error: "Social media plugin is disabled." }, { status: 400 });
8231
+ }
8232
+ const token = (map.linkedin_access_token ?? "").trim();
8233
+ const personSub = (map.linkedin_person_sub ?? "").trim();
8234
+ if (!token || !personSub) {
8235
+ return json(
8236
+ { error: "Configure LinkedIn access token and sync profile (Plugins \u2192 Social media)." },
8237
+ { status: 400 }
8238
+ );
8239
+ }
8240
+ const blogRepo = dataSource.getRepository(entityMap.blogs);
8241
+ const blog = await blogRepo.findOne({
8242
+ where: { id: blogId, deleted: false }
8243
+ });
8244
+ if (!blog) {
8245
+ return json({ error: "Blog not found" }, { status: 404 });
8246
+ }
8247
+ const b = blog;
8248
+ const title = String(b.title ?? "Blog").trim() || "Blog";
8249
+ const social = String(b.socialMediaContent ?? "").trim();
8250
+ const excerpt = stripHtml(String(b.content ?? ""), 2800);
8251
+ const commentary = (social || `${title}
8252
+
8253
+ ${excerpt}`).trim().slice(0, 2900);
8254
+ const commentaryFrom = social ? "socialMediaContent" : "titleAndExcerpt";
8255
+ logLinkedInPublish(publishLog, "start", {
8256
+ blogId,
8257
+ titleLen: title.length,
8258
+ commentaryFrom,
8259
+ commentaryChars: commentary.length,
8260
+ hasCover: Boolean(b.coverImage?.trim()),
8261
+ contentHtmlChars: String(b.content ?? "").length
8262
+ });
8263
+ const imagePayload = await loadImageBytesForBlogShare(
8264
+ b.coverImage,
8265
+ String(b.content ?? ""),
8266
+ publicSiteUrl || inferRequestOrigin(req),
8267
+ req
8268
+ );
8269
+ if (imagePayload) {
8270
+ logLinkedInPublish(publishLog, "image_bytes_ready", {
8271
+ blogId,
8272
+ source: imagePayload.source,
8273
+ bytes: imagePayload.buffer.length,
8274
+ contentType: imagePayload.contentType
8275
+ });
8276
+ try {
8277
+ const { uploadUrl, asset } = await linkedInRegisterImageUpload(token, personSub);
8278
+ logLinkedInPublish(publishLog, "image_registered", {
8279
+ blogId,
8280
+ asset: truncateForLog(asset, 120),
8281
+ uploadHost: (() => {
8282
+ try {
8283
+ return new URL(uploadUrl).host;
8284
+ } catch {
8285
+ return "unknown";
8286
+ }
8287
+ })()
8288
+ });
8289
+ await linkedInUploadBinary(token, uploadUrl, imagePayload.buffer, imagePayload.contentType);
8290
+ logLinkedInPublish(publishLog, "image_uploaded", { blogId, bytes: imagePayload.buffer.length });
8291
+ const out2 = await linkedInCreateImageShare({
8292
+ accessToken: token,
8293
+ personSub,
8294
+ asset,
8295
+ commentary,
8296
+ title,
8297
+ description: title
8298
+ });
8299
+ logLinkedInPublish(publishLog, "success", {
8300
+ blogId,
8301
+ mode: "image_plus_text",
8302
+ linkedinPostId: out2.id ?? null,
8303
+ commentaryFrom,
8304
+ commentaryChars: commentary.length,
8305
+ imageSource: imagePayload.source
8306
+ });
8307
+ return json({
8308
+ ok: true,
8309
+ linkedin: out2,
8310
+ usedImage: true,
8311
+ publishSummary: {
8312
+ mode: "image_plus_text",
8313
+ commentaryFrom,
8314
+ commentaryChars: commentary.length,
8315
+ image: {
8316
+ bytes: imagePayload.buffer.length,
8317
+ contentType: imagePayload.contentType,
8318
+ source: imagePayload.source
8319
+ },
8320
+ linkedinPostId: out2.id ?? null
8321
+ }
8322
+ });
8323
+ } catch (imgErr) {
8324
+ const imgMsg = imgErr instanceof Error ? imgErr.message : String(imgErr);
8325
+ logLinkedInPublish(publishLog, "image_path_failed_falling_back_to_text", {
8326
+ blogId,
8327
+ message: truncateForLog(imgMsg, 500)
8328
+ });
8329
+ const out2 = await linkedInCreateTextShare({
8330
+ accessToken: token,
8331
+ personSub,
8332
+ commentary
8333
+ });
8334
+ logLinkedInPublish(publishLog, "success", {
8335
+ blogId,
8336
+ mode: "text_only_after_image_failure",
8337
+ linkedinPostId: out2.id ?? null,
8338
+ commentaryFrom,
8339
+ commentaryChars: commentary.length,
8340
+ imageError: truncateForLog(imgMsg, 400)
8341
+ });
8342
+ return json({
8343
+ ok: true,
8344
+ linkedin: out2,
8345
+ usedImage: false,
8346
+ usedTextOnly: true,
8347
+ imagePathError: imgMsg,
8348
+ publishSummary: {
8349
+ mode: "text_only_after_image_failure",
8350
+ commentaryFrom,
8351
+ commentaryChars: commentary.length,
8352
+ linkedinPostId: out2.id ?? null,
8353
+ imageAttempt: {
8354
+ bytes: imagePayload.buffer.length,
8355
+ contentType: imagePayload.contentType,
8356
+ source: imagePayload.source
8357
+ }
8358
+ }
8359
+ });
8360
+ }
8361
+ }
8362
+ logLinkedInPublish(publishLog, "no_image_bytes", {
8363
+ blogId,
8364
+ commentaryFrom,
8365
+ commentaryChars: commentary.length,
8366
+ note: "posting_text_only"
8367
+ });
8368
+ const out = await linkedInCreateTextShare({
8369
+ accessToken: token,
8370
+ personSub,
8371
+ commentary
8372
+ });
8373
+ logLinkedInPublish(publishLog, "success", {
8374
+ blogId,
8375
+ mode: "text_only",
8376
+ linkedinPostId: out.id ?? null,
8377
+ commentaryFrom,
8378
+ commentaryChars: commentary.length
8379
+ });
8380
+ return json({
8381
+ ok: true,
8382
+ linkedin: out,
8383
+ usedTextOnly: true,
8384
+ publishSummary: {
8385
+ mode: "text_only",
8386
+ commentaryFrom,
8387
+ commentaryChars: commentary.length,
8388
+ linkedinPostId: out.id ?? null
8389
+ },
8390
+ 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."
8391
+ });
8392
+ } catch (e) {
8393
+ const msg = e instanceof Error ? e.message : "Publish failed";
8394
+ logLinkedInPublish(publishLog, "failed", { blogId, message: truncateForLog(msg, 600) });
8395
+ return json({ error: msg }, { status: 502 });
8396
+ }
8397
+ },
8398
+ async getFacebookStatus(req) {
8399
+ const a = await requireAuth(req);
8400
+ if (a) return a;
8401
+ const pe = await requireEntityPermission(req, "blogs", "read");
8402
+ if (pe) return pe;
8403
+ try {
8404
+ const map = await loadGroupMap(dataSource, entityMap, encryptionKey);
8405
+ const enabled = map.enabled !== "false";
8406
+ const userToken = (map.meta_user_access_token ?? "").trim();
8407
+ const pageId = (map.meta_facebook_page_id ?? "").trim();
8408
+ let hasPageToken = false;
8409
+ if (enabled && userToken && pageId) {
8410
+ try {
8411
+ const accounts = await metaFetchUserManagedPages(userToken);
8412
+ hasPageToken = Boolean(metaResolvePageAccessToken(accounts, pageId));
8413
+ } catch {
8414
+ hasPageToken = false;
8415
+ }
8416
+ }
8417
+ const facebookReady = enabled && Boolean(userToken) && Boolean(pageId);
8418
+ return json({
8419
+ ok: true,
8420
+ facebookReady,
8421
+ enabled,
8422
+ hasUserToken: Boolean(userToken),
8423
+ hasPageId: Boolean(pageId),
8424
+ hasPageToken
8425
+ });
8426
+ } catch (e) {
8427
+ const msg = e instanceof Error ? e.message : "Failed to load status";
8428
+ return json({ error: msg }, { status: 500 });
8429
+ }
8430
+ },
8431
+ async publishBlogToFacebook(req, blogId) {
8432
+ const a = await requireAuth(req);
8433
+ if (a) return a;
8434
+ const pe = await requireEntityPermission(req, "blogs", "update");
8435
+ if (pe) return pe;
8436
+ if (!Number.isFinite(blogId) || blogId < 1) {
8437
+ return json({ error: "Invalid blog id" }, { status: 400 });
8438
+ }
8439
+ if (!entityMap.blogs) {
8440
+ return json({ error: "Blogs entity not configured" }, { status: 503 });
8441
+ }
8442
+ try {
8443
+ const map = await loadGroupMap(dataSource, entityMap, encryptionKey);
8444
+ if (map.enabled === "false") {
8445
+ return json({ error: "Social media plugin is disabled." }, { status: 400 });
8446
+ }
8447
+ const userToken = (map.meta_user_access_token ?? "").trim();
8448
+ const pageId = (map.meta_facebook_page_id ?? "").trim();
8449
+ if (!userToken || !pageId) {
8450
+ return json(
8451
+ {
8452
+ error: "Configure Meta user access token and Facebook Page ID in Plugins \u2192 Social media \u2192 Facebook, then save."
8453
+ },
8454
+ { status: 400 }
8455
+ );
8456
+ }
8457
+ let accounts;
8458
+ try {
8459
+ accounts = await metaFetchUserManagedPages(userToken);
8460
+ } catch (e) {
8461
+ const msg = e instanceof Error ? e.message : "Failed to load Facebook pages";
8462
+ return json({ error: msg }, { status: 502 });
8463
+ }
8464
+ const pageAccessToken = metaResolvePageAccessToken(accounts, pageId);
8465
+ if (!pageAccessToken) {
8466
+ return json(
8467
+ {
8468
+ 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."
8469
+ },
8470
+ { status: 400 }
8471
+ );
8472
+ }
8473
+ const blogRepo = dataSource.getRepository(entityMap.blogs);
8474
+ const blog = await blogRepo.findOne({
8475
+ where: { id: blogId, deleted: false }
8476
+ });
8477
+ if (!blog) {
8478
+ return json({ error: "Blog not found" }, { status: 404 });
8479
+ }
8480
+ const b = blog;
8481
+ const title = String(b.title ?? "Blog").trim() || "Blog";
8482
+ const social = String(b.socialMediaContent ?? "").trim();
8483
+ const excerpt = stripHtml(String(b.content ?? ""), 2800);
8484
+ const caption = (social || `${title}
8485
+
8486
+ ${excerpt}`).trim().slice(0, 2200);
8487
+ const imagePayload = await loadImageBytesForBlogShare(
8488
+ b.coverImage,
8489
+ String(b.content ?? ""),
8490
+ publicSiteUrl || inferRequestOrigin(req),
8491
+ req
8492
+ );
8493
+ if (imagePayload) {
8494
+ const out2 = await metaPostPagePhoto({
8495
+ pageId,
8496
+ pageAccessToken,
8497
+ imageBuffer: imagePayload.buffer,
8498
+ contentType: imagePayload.contentType,
8499
+ caption
8500
+ });
8501
+ return json({
8502
+ ok: true,
8503
+ facebook: out2,
8504
+ usedImage: true,
8505
+ publishSummary: {
8506
+ mode: "page_photo",
8507
+ captionChars: caption.length,
8508
+ imageSource: imagePayload.source,
8509
+ facebookPostId: out2.post_id ?? out2.id ?? null
8510
+ }
8511
+ });
8512
+ }
8513
+ const out = await metaPostPageFeed({
8514
+ pageId,
8515
+ pageAccessToken,
8516
+ message: caption
8517
+ });
8518
+ return json({
8519
+ ok: true,
8520
+ facebook: out,
8521
+ usedImage: false,
8522
+ usedFeed: true,
8523
+ publishSummary: {
8524
+ mode: "page_feed_text",
8525
+ messageChars: caption.length,
8526
+ facebookPostId: out.id ?? null
8527
+ },
8528
+ 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."
8529
+ });
8530
+ } catch (e) {
8531
+ const msg = e instanceof Error ? e.message : "Facebook publish failed";
8532
+ return json({ error: msg }, { status: 502 });
8533
+ }
8534
+ },
8535
+ /**
8536
+ * POST JSON `{ appId, appSecret, userAccessToken }`. Calls Graph `GET /me/accounts` with the user token.
8537
+ * App ID and secret are validated but not used yet (reserved for future token flows).
8538
+ * Persist credentials via Plugins → Save: `meta_app_id` (public), `meta_app_secret` and `meta_user_access_token` (encrypted).
8539
+ */
8540
+ async fetchFacebookManagedPages(req) {
8541
+ const a = await requireAuth(req);
8542
+ if (a) return a;
8543
+ const pe = await requireEntityPermission(req, "settings", "read");
8544
+ if (pe) return pe;
8545
+ let body;
8546
+ try {
8547
+ body = await req.json();
8548
+ } catch {
8549
+ return json({ error: "Invalid JSON body" }, { status: 400 });
8550
+ }
8551
+ const appId = String(body?.appId ?? "").trim();
8552
+ const appSecret = String(body?.appSecret ?? "").trim();
8553
+ const userAccessToken = String(body?.userAccessToken ?? "").trim();
8554
+ if (!appId || !appSecret || !userAccessToken) {
8555
+ return json(
8556
+ { error: "App ID, app secret, and user access token are required." },
8557
+ { status: 400 }
8558
+ );
8559
+ }
8560
+ void appId;
8561
+ void appSecret;
8562
+ try {
8563
+ const graph = await metaFetchUserManagedPages(userAccessToken);
8564
+ return json({ ok: true, graph });
8565
+ } catch (e) {
8566
+ const msg = e instanceof Error ? e.message : "Facebook Graph request failed";
8567
+ return json({ error: msg }, { status: 502 });
8568
+ }
8569
+ }
8570
+ };
8571
+ }
8572
+
7228
8573
  // src/api/cms-api-handler.ts
7229
8574
  var KNOWLEDGE_SUFFIX = "knowledge";
7230
8575
  var CMS_API_LOG = "[cms-api]";
@@ -7233,7 +8578,9 @@ function withLlmKnowledgeEntityFallbacks(base) {
7233
8578
  "llm_agents",
7234
8579
  "llm_agent_knowledge_documents",
7235
8580
  "knowledge_base_documents",
7236
- "knowledge_base_chunks"
8581
+ "knowledge_base_chunks",
8582
+ "rss_feeds",
8583
+ "rss_articles"
7237
8584
  ];
7238
8585
  const patch = {};
7239
8586
  for (const key of keys) {
@@ -7249,8 +8596,8 @@ function withLlmKnowledgeEntityFallbacks(base) {
7249
8596
  }
7250
8597
  return merged;
7251
8598
  }
7252
- function matchLlmAgentKnowledgeRoute(path) {
7253
- const p = path[0] === "api" ? path.slice(1) : path;
8599
+ function matchLlmAgentKnowledgeRoute(path2) {
8600
+ const p = path2[0] === "api" ? path2.slice(1) : path2;
7254
8601
  if (p[0] !== "llm_agents" || p.length < 2) return null;
7255
8602
  const seg1 = p[1];
7256
8603
  if (!seg1) return null;
@@ -7281,7 +8628,9 @@ var DEFAULT_EXCLUDE = /* @__PURE__ */ new Set([
7281
8628
  "wishlists",
7282
8629
  "wishlist_items",
7283
8630
  "message_templates",
7284
- "llm_agent_knowledge_documents"
8631
+ "llm_agent_knowledge_documents",
8632
+ "rss_feeds",
8633
+ "rss_articles"
7285
8634
  ]);
7286
8635
  function createCmsApiHandler(config) {
7287
8636
  const {
@@ -7408,6 +8757,23 @@ function createCmsApiHandler(config) {
7408
8757
  const avatarPost = userAvatar ? createUserAvatarHandler(userAvatar) : null;
7409
8758
  const profileHandlers = userProfile ? createUserProfileHandler(userProfile) : null;
7410
8759
  const settingsHandlers = settingsConfig ? createSettingsApiHandlers(settingsConfig) : null;
8760
+ function resolvePublicSiteUrlForSocial() {
8761
+ if (typeof process === "undefined") return void 0;
8762
+ const u = (process.env.CMS_PUBLIC_URL || process.env.NEXT_PUBLIC_SITE_URL || "").trim();
8763
+ if (u) return u.replace(/\/+$/, "");
8764
+ const v = (process.env.VERCEL_URL || "").trim();
8765
+ if (!v) return void 0;
8766
+ return v.startsWith("http") ? v.replace(/\/+$/, "") : `https://${v.replace(/\/+$/, "")}`;
8767
+ }
8768
+ const socialMediaHandlers = settingsConfig?.dataSource && entityMap.configs ? createSocialMediaHandlers({
8769
+ dataSource: settingsConfig.dataSource,
8770
+ entityMap,
8771
+ json: config.json,
8772
+ requireAuth: config.requireAuth,
8773
+ requireEntityPermission: requireEntityPermissionEffective,
8774
+ encryptionKey: settingsConfig.encryptionKey,
8775
+ publicSiteUrl: resolvePublicSiteUrlForSocial()
8776
+ }) : null;
7411
8777
  const smsMessageTemplateHandlers = createSmsMessageTemplateHandlers({
7412
8778
  dataSource,
7413
8779
  entityMap,
@@ -7434,95 +8800,526 @@ function createCmsApiHandler(config) {
7434
8800
  return {
7435
8801
  async handle(method, pathInput, req) {
7436
8802
  const m = typeof method === "string" ? method.toUpperCase() : "GET";
7437
- const path = pathInput.length > 0 && pathInput[0] === "api" ? pathInput.slice(1) : pathInput;
8803
+ const path2 = pathInput.length > 0 && pathInput[0] === "api" ? pathInput.slice(1) : pathInput;
7438
8804
  async function analyticsGate() {
7439
8805
  const a = await config.requireAuth(req);
7440
8806
  if (a) return a;
7441
8807
  return requireEntityPermissionEffective(req, "analytics", "read");
7442
8808
  }
7443
- if (path[0] === "admin" && path[1] === "roles") {
8809
+ if (path2[0] === "admin" && path2[1] === "roles") {
7444
8810
  if (!adminRoles) return config.json({ error: "Not found" }, { status: 404 });
7445
- if (path.length === 2 && m === "GET") return adminRoles.list();
7446
- if (path.length === 2 && m === "POST") return adminRoles.createGroup(req);
7447
- if (path.length === 3 && m === "PATCH") return adminRoles.patchGroup(req, path[2]);
7448
- if (path.length === 3 && m === "DELETE") return adminRoles.deleteGroup(path[2]);
7449
- if (path.length === 4 && path[3] === "permissions" && m === "PUT") return adminRoles.putPermissions(req, path[2]);
8811
+ if (path2.length === 2 && m === "GET") return adminRoles.list();
8812
+ if (path2.length === 2 && m === "POST") return adminRoles.createGroup(req);
8813
+ if (path2.length === 3 && m === "PATCH") return adminRoles.patchGroup(req, path2[2]);
8814
+ if (path2.length === 3 && m === "DELETE") return adminRoles.deleteGroup(path2[2]);
8815
+ if (path2.length === 4 && path2[3] === "permissions" && m === "PUT") return adminRoles.putPermissions(req, path2[2]);
7450
8816
  return config.json({ error: "Not found" }, { status: 404 });
7451
8817
  }
7452
- if (path[0] === "dashboard" && path[1] === "stats" && path.length === 2 && m === "GET" && dashboardGet) {
8818
+ if (path2[0] === "dashboard" && path2[1] === "stats" && path2.length === 2 && m === "GET" && dashboardGet) {
7453
8819
  return dashboardGet(req);
7454
8820
  }
7455
- if (path[0] === "dashboard" && path[1] === "ecommerce" && path.length === 2 && m === "GET" && ecommerceAnalyticsGet) {
8821
+ if (path2[0] === "dashboard" && path2[1] === "ecommerce" && path2.length === 2 && m === "GET" && ecommerceAnalyticsGet) {
7456
8822
  const g = await analyticsGate();
7457
8823
  if (g) return g;
7458
8824
  return ecommerceAnalyticsGet(req);
7459
8825
  }
7460
- if (path[0] === "analytics" && analyticsHandlers) {
7461
- if (path.length === 1 && m === "GET") {
8826
+ if (path2[0] === "blog-generator" && path2[1] === "feeds" && path2.length === 3 && m === "DELETE" && getCms) {
8827
+ const id = path2[2];
8828
+ 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);
8829
+ const a = await config.requireAuth(req);
8830
+ if (a) return a;
8831
+ const pe = await requireEntityPermissionEffective(req, "blogs", "read");
8832
+ if (pe) return pe;
8833
+ if (!entityMap.rss_feeds) {
8834
+ return config.json({ error: "RSS feeds are not configured (missing entity map)." }, { status: 503 });
8835
+ }
8836
+ if (!uuidLooksValid) {
8837
+ return config.json({ error: "Invalid feed id" }, { status: 400 });
8838
+ }
8839
+ try {
8840
+ await dataSource.getRepository(entityMap.rss_feeds).delete({ id });
8841
+ return config.json({ ok: true });
8842
+ } catch (e) {
8843
+ const message = e instanceof Error ? e.message : "Delete failed";
8844
+ return config.json({ error: message }, { status: 500 });
8845
+ }
8846
+ }
8847
+ if (path2[0] === "blog-generator" && path2[1] === "feeds" && path2.length === 2 && m === "GET" && getCms) {
8848
+ const a = await config.requireAuth(req);
8849
+ if (a) return a;
8850
+ const pe = await requireEntityPermissionEffective(req, "blogs", "read");
8851
+ if (pe) return pe;
8852
+ if (!entityMap.rss_feeds) {
8853
+ return config.json({ error: "RSS feeds are not configured (missing entity map)." }, { status: 503 });
8854
+ }
8855
+ try {
8856
+ const list = await dataSource.getRepository(entityMap.rss_feeds).find({
8857
+ where: { isActive: true },
8858
+ order: { updatedAt: "DESC" }
8859
+ });
8860
+ return config.json({ feeds: list.map(serializeRssFeed) });
8861
+ } catch (e) {
8862
+ const message = e instanceof Error ? e.message : "Failed to list feeds";
8863
+ return config.json({ error: message }, { status: 500 });
8864
+ }
8865
+ }
8866
+ if (path2[0] === "blog-generator" && path2[1] === "feeds" && path2.length === 2 && m === "POST" && getCms) {
8867
+ const a = await config.requireAuth(req);
8868
+ if (a) return a;
8869
+ const pe = await requireEntityPermissionEffective(req, "blogs", "read");
8870
+ if (pe) return pe;
8871
+ if (!entityMap.rss_feeds) {
8872
+ return config.json({ error: "RSS feeds are not configured (missing entity map)." }, { status: 503 });
8873
+ }
8874
+ let body;
8875
+ try {
8876
+ body = await req.json();
8877
+ } catch {
8878
+ return config.json({ error: "Invalid JSON body" }, { status: 400 });
8879
+ }
8880
+ const rowsIn = Array.isArray(body.rows) ? body.rows : [];
8881
+ const rows = rowsIn.map((r) => r).map((r) => {
8882
+ let websiteUrl;
8883
+ if ("websiteUrl" in r) {
8884
+ const w = r.websiteUrl;
8885
+ if (w === null || w === "") websiteUrl = null;
8886
+ else if (typeof w === "string") websiteUrl = w;
8887
+ }
8888
+ return {
8889
+ rssUrl: typeof r.rssUrl === "string" ? r.rssUrl.trim() : "",
8890
+ name: typeof r.name === "string" ? r.name : void 0,
8891
+ websiteUrl
8892
+ };
8893
+ }).filter((r) => r.rssUrl !== "");
8894
+ try {
8895
+ const Feed = entityMap.rss_feeds;
8896
+ for (const r of rows) {
8897
+ await upsertRssFeedRow(dataSource, Feed, r.rssUrl, {
8898
+ name: r.name,
8899
+ websiteUrl: r.websiteUrl
8900
+ });
8901
+ }
8902
+ const list = await dataSource.getRepository(Feed).find({
8903
+ where: { isActive: true },
8904
+ order: { updatedAt: "DESC" }
8905
+ });
8906
+ return config.json({ feeds: list.map(serializeRssFeed) });
8907
+ } catch (e) {
8908
+ const message = e instanceof Error ? e.message : "Failed to save feeds";
8909
+ return config.json({ error: message }, { status: 500 });
8910
+ }
8911
+ }
8912
+ if (path2[0] === "blog-generator" && path2[1] === "latest" && path2.length === 2 && m === "POST" && getCms) {
8913
+ const a = await config.requireAuth(req);
8914
+ if (a) return a;
8915
+ const pe = await requireEntityPermissionEffective(req, "blogs", "read");
8916
+ if (pe) return pe;
8917
+ const cms = await getCms();
8918
+ const svc = cms.getPlugin("blog_generator");
8919
+ if (!svc) {
8920
+ return config.json({ error: "Blog Generator plugin is not enabled" }, { status: 503 });
8921
+ }
8922
+ let body;
8923
+ try {
8924
+ body = await req.json();
8925
+ } catch {
8926
+ return config.json({ error: "Invalid JSON body" }, { status: 400 });
8927
+ }
8928
+ const rawUrls = [];
8929
+ if (Array.isArray(body.rssUrls)) {
8930
+ for (const u of body.rssUrls) {
8931
+ if (typeof u === "string") {
8932
+ const t = u.trim();
8933
+ if (t) rawUrls.push(t);
8934
+ }
8935
+ }
8936
+ }
8937
+ if (rawUrls.length === 0 && typeof body.rssUrl === "string" && body.rssUrl.trim()) {
8938
+ rawUrls.push(body.rssUrl.trim());
8939
+ }
8940
+ const seen = /* @__PURE__ */ new Set();
8941
+ const urls = rawUrls.filter((u) => {
8942
+ if (seen.has(u)) return false;
8943
+ seen.add(u);
8944
+ return true;
8945
+ });
8946
+ if (urls.length === 0) {
8947
+ return config.json({ error: "Provide rssUrls (array) or rssUrl (string)" }, { status: 400 });
8948
+ }
8949
+ const results = [];
8950
+ for (const rssUrl of urls) {
8951
+ try {
8952
+ if (entityMap.rss_feeds) {
8953
+ await upsertRssFeedRow(dataSource, entityMap.rss_feeds, rssUrl);
8954
+ }
8955
+ const latest = await svc.getLatestArticleFromFeed(rssUrl);
8956
+ if (entityMap.rss_feeds) {
8957
+ await recordRssFetchSuccess(
8958
+ dataSource,
8959
+ entityMap.rss_feeds,
8960
+ entityMap.rss_articles,
8961
+ rssUrl,
8962
+ latest == null ? null : {
8963
+ title: latest.title,
8964
+ link: latest.link,
8965
+ summary: latest.summary,
8966
+ content: latest.content,
8967
+ creator: latest.creator,
8968
+ date: latest.date
8969
+ }
8970
+ );
8971
+ }
8972
+ const article = latest == null ? null : {
8973
+ title: latest.title,
8974
+ link: latest.link,
8975
+ summary: latest.summary,
8976
+ content: latest.content,
8977
+ date: latest.date.toISOString()
8978
+ };
8979
+ results.push({ rssUrl, article });
8980
+ } catch (e) {
8981
+ const message = e instanceof Error ? e.message : "Failed to parse feed";
8982
+ results.push({ rssUrl, article: null, error: message });
8983
+ }
8984
+ }
8985
+ const singleArticle = results.length === 1 ? results[0].article : void 0;
8986
+ return config.json({ results, article: singleArticle });
8987
+ }
8988
+ if (path2[0] === "blog-generator" && path2[1] === "generate" && path2.length === 2 && m === "POST" && getCms) {
8989
+ const a = await config.requireAuth(req);
8990
+ if (a) return a;
8991
+ const pe = await requireEntityPermissionEffective(req, "blogs", "read");
8992
+ if (pe) return pe;
8993
+ const cms = await getCms();
8994
+ const svc = cms.getPlugin("blog_generator");
8995
+ if (!svc) {
8996
+ return config.json({ error: "Blog Generator plugin is not enabled" }, { status: 503 });
8997
+ }
8998
+ const llm = cms.getPlugin("llm");
8999
+ if (!llm) {
9000
+ return config.json({ error: "LLM plugin is not enabled. Configure the LLM gateway to generate articles." }, { status: 503 });
9001
+ }
9002
+ let body;
9003
+ try {
9004
+ body = await req.json();
9005
+ } catch {
9006
+ return config.json({ error: "Invalid JSON body" }, { status: 400 });
9007
+ }
9008
+ const rawUrls = [];
9009
+ if (Array.isArray(body.rssUrls)) {
9010
+ for (const u of body.rssUrls) {
9011
+ if (typeof u === "string") {
9012
+ const t = u.trim();
9013
+ if (t) rawUrls.push(t);
9014
+ }
9015
+ }
9016
+ }
9017
+ if (rawUrls.length === 0 && typeof body.rssUrl === "string" && body.rssUrl.trim()) {
9018
+ rawUrls.push(body.rssUrl.trim());
9019
+ }
9020
+ const seen = /* @__PURE__ */ new Set();
9021
+ const rssUrls = rawUrls.filter((u) => {
9022
+ if (seen.has(u)) return false;
9023
+ seen.add(u);
9024
+ return true;
9025
+ });
9026
+ if (rssUrls.length === 0) {
9027
+ return config.json({ error: "Provide rssUrls (non-empty array) or rssUrl (string)" }, { status: 400 });
9028
+ }
9029
+ const systemInstruction = typeof body.systemInstruction === "string" ? body.systemInstruction : void 0;
9030
+ const validationRules = typeof body.validationRules === "string" ? body.validationRules : void 0;
9031
+ const persistToCms = body.persistToCms === true;
9032
+ let llmAgentChatOptions;
9033
+ let llmAgentResolution = null;
9034
+ let metadataLlmAgentChatOptions;
9035
+ let metadataLlmAgentResolution = null;
9036
+ let socialLlmAgentChatOptions;
9037
+ let socialLlmAgentResolution = null;
9038
+ let metadataRow = null;
9039
+ let socialRow = null;
9040
+ if (entityMap.llm_agents) {
9041
+ const agentRepo = dataSource.getRepository(entityMap.llm_agents);
9042
+ const agentRow = await agentRepo.findOne({
9043
+ where: { slug: BLOG_GENERATOR_LLM_AGENT_SLUG, deleted: false, enabled: true }
9044
+ });
9045
+ if (agentRow) {
9046
+ const o = llmAgentToChatAgentOptions(agentRow);
9047
+ llmAgentChatOptions = {
9048
+ model: o.model,
9049
+ temperature: o.temperature,
9050
+ max_tokens: o.max_tokens
9051
+ };
9052
+ llmAgentResolution = {
9053
+ slug: BLOG_GENERATOR_LLM_AGENT_SLUG,
9054
+ model: agentRow.model?.trim() || null,
9055
+ temperature: agentRow.temperature ?? null,
9056
+ maxTokens: agentRow.maxTokens ?? null
9057
+ };
9058
+ }
9059
+ metadataRow = await agentRepo.findOne({
9060
+ where: { slug: BLOG_METADATA_ENRICHER_LLM_AGENT_SLUG, deleted: false, enabled: true }
9061
+ });
9062
+ if (metadataRow) {
9063
+ const mo = llmAgentToChatAgentOptions(metadataRow);
9064
+ metadataLlmAgentChatOptions = {
9065
+ model: mo.model,
9066
+ temperature: mo.temperature,
9067
+ max_tokens: mo.max_tokens
9068
+ };
9069
+ metadataLlmAgentResolution = {
9070
+ slug: BLOG_METADATA_ENRICHER_LLM_AGENT_SLUG,
9071
+ model: metadataRow.model?.trim() || null,
9072
+ temperature: metadataRow.temperature ?? null,
9073
+ maxTokens: metadataRow.maxTokens ?? null
9074
+ };
9075
+ }
9076
+ socialRow = await agentRepo.findOne({
9077
+ where: { slug: BLOG_SOCIAL_ENRICHER_LLM_AGENT_SLUG, deleted: false, enabled: true }
9078
+ });
9079
+ if (socialRow) {
9080
+ const so = llmAgentToChatAgentOptions(socialRow);
9081
+ socialLlmAgentChatOptions = {
9082
+ model: so.model,
9083
+ temperature: so.temperature,
9084
+ max_tokens: so.max_tokens
9085
+ };
9086
+ socialLlmAgentResolution = {
9087
+ slug: BLOG_SOCIAL_ENRICHER_LLM_AGENT_SLUG,
9088
+ model: socialRow.model?.trim() || null,
9089
+ temperature: socialRow.temperature ?? null,
9090
+ maxTokens: socialRow.maxTokens ?? null
9091
+ };
9092
+ }
9093
+ }
9094
+ try {
9095
+ const categoryEntities = entityMap.categories ? await dataSource.getRepository(entityMap.categories).find({
9096
+ where: { deleted: false },
9097
+ select: ["id", "name"]
9098
+ }) : [];
9099
+ const categoryRows = categoryEntities.map((r) => ({
9100
+ id: Number(r.id),
9101
+ name: String(r.name ?? "").trim()
9102
+ })).filter((r) => Number.isFinite(r.id) && r.name !== "");
9103
+ const tagEntities = entityMap.tags ? await dataSource.getRepository(entityMap.tags).find({
9104
+ where: { deleted: false },
9105
+ select: ["name"]
9106
+ }) : [];
9107
+ const tagNames = tagEntities.map((r) => String(r.name ?? "").trim()).filter((n) => n !== "");
9108
+ const out = await svc.generateBlogMarkdownFromRss({
9109
+ llm,
9110
+ rssUrls,
9111
+ systemInstruction,
9112
+ validationRules,
9113
+ categoryNamesHint: categoryRows.map((c) => c.name),
9114
+ tagNamesHint: tagNames,
9115
+ llmAgentChatOptions,
9116
+ metadataSystemInstruction: metadataRow?.systemInstruction,
9117
+ metadataValidationRules: metadataRow?.validationRules ?? void 0,
9118
+ metadataLlmAgentChatOptions,
9119
+ socialSystemInstruction: socialRow?.systemInstruction,
9120
+ socialValidationRules: socialRow?.validationRules ?? void 0,
9121
+ socialLlmAgentChatOptions
9122
+ });
9123
+ let savedBlogs;
9124
+ if (persistToCms) {
9125
+ const pCreate = await requireEntityPermissionEffective(req, "blogs", "create");
9126
+ if (pCreate) return pCreate;
9127
+ if (!entityMap.blogs || !entityMap.tags || !entityMap.categories || !entityMap.seos) {
9128
+ return config.json(
9129
+ { error: "persistToCms requires blogs, tags, categories, and seos in the entity map." },
9130
+ { status: 503 }
9131
+ );
9132
+ }
9133
+ let authorId = null;
9134
+ if (getSessionUser) {
9135
+ const su = await getSessionUser();
9136
+ if (su?.id) {
9137
+ const n = Number(su.id);
9138
+ if (Number.isFinite(n)) authorId = n;
9139
+ }
9140
+ if (authorId == null && su?.email?.trim() && entityMap.users) {
9141
+ const ur = await dataSource.getRepository(entityMap.users).findOne({
9142
+ where: { email: su.email.trim(), deleted: false },
9143
+ select: ["id"]
9144
+ });
9145
+ if (ur) authorId = Number(ur.id);
9146
+ }
9147
+ }
9148
+ if (authorId == null || !Number.isFinite(authorId)) {
9149
+ return config.json(
9150
+ {
9151
+ error: "persistToCms requires a logged-in user with id in session or a users row matching session email."
9152
+ },
9153
+ { status: 400 }
9154
+ );
9155
+ }
9156
+ savedBlogs = await persistAllGeneratedBlogDrafts(
9157
+ dataSource,
9158
+ {
9159
+ blogs: entityMap.blogs,
9160
+ tags: entityMap.tags,
9161
+ categories: entityMap.categories,
9162
+ seos: entityMap.seos
9163
+ },
9164
+ { drafts: out.blogDrafts, authorId }
9165
+ );
9166
+ }
9167
+ const blogs = out.blogDrafts.map((draft) => {
9168
+ const { categoryId, matched: categoryMatched } = resolveBlogCategoryIdByName(
9169
+ draft.categoryName,
9170
+ categoryRows
9171
+ );
9172
+ return {
9173
+ title: draft.title,
9174
+ slug: draft.slug ?? null,
9175
+ markdown: draft.markdown,
9176
+ socialMediaContent: draft.socialMediaContent ?? null,
9177
+ categoryName: draft.categoryName,
9178
+ categoryId,
9179
+ categoryMatched,
9180
+ seo: draft.seo,
9181
+ tags: draft.tags,
9182
+ parseMode: draft.parseMode
9183
+ };
9184
+ });
9185
+ const firstArticle = out.article;
9186
+ return config.json({
9187
+ agentName: out.agentName,
9188
+ blogMarkdown: out.blogMarkdown,
9189
+ blogs,
9190
+ blog: blogs[0] ?? null,
9191
+ feedArticles: out.feedArticles.map((f) => ({
9192
+ rssUrl: f.rssUrl,
9193
+ article: {
9194
+ title: f.article.title,
9195
+ link: f.article.link,
9196
+ summary: f.article.summary,
9197
+ content: f.article.content,
9198
+ date: f.article.date.toISOString()
9199
+ }
9200
+ })),
9201
+ llmAgent: llmAgentResolution,
9202
+ metadataLlmAgent: metadataLlmAgentResolution,
9203
+ socialLlmAgent: socialLlmAgentResolution,
9204
+ article: {
9205
+ title: firstArticle.title,
9206
+ link: firstArticle.link,
9207
+ summary: firstArticle.summary,
9208
+ content: firstArticle.content,
9209
+ date: firstArticle.date.toISOString()
9210
+ },
9211
+ ...savedBlogs != null ? { savedBlogs } : {}
9212
+ });
9213
+ } catch (e) {
9214
+ const message = e instanceof Error ? e.message : "Failed to generate blog";
9215
+ const status = /No items found|LLM returned empty/i.test(message) ? 400 : 502;
9216
+ return config.json({ error: message }, { status });
9217
+ }
9218
+ }
9219
+ if (path2[0] === "analytics" && analyticsHandlers) {
9220
+ if (path2.length === 1 && m === "GET") {
7462
9221
  const g = await analyticsGate();
7463
9222
  if (g) return g;
7464
9223
  return analyticsHandlers.GET(req);
7465
9224
  }
7466
- if (path.length === 2 && path[1] === "property-id" && m === "GET") {
9225
+ if (path2.length === 2 && path2[1] === "property-id" && m === "GET") {
7467
9226
  const g = await analyticsGate();
7468
9227
  if (g) return g;
7469
9228
  return analyticsHandlers.propertyId();
7470
9229
  }
7471
- if (path.length === 2 && path[1] === "permissions" && m === "GET") {
9230
+ if (path2.length === 2 && path2[1] === "permissions" && m === "GET") {
7472
9231
  const g = await analyticsGate();
7473
9232
  if (g) return g;
7474
9233
  return analyticsHandlers.permissions();
7475
9234
  }
7476
9235
  }
7477
- if (path[0] === "upload" && path.length === 1 && m === "POST" && uploadPost) return uploadPost(req);
7478
- if (path[0] === "media" && path[1] === "extract" && path.length === 3 && m === "POST" && zipExtractPost) {
7479
- return zipExtractPost(req, path[2]);
9236
+ if (path2[0] === "upload" && path2.length === 1 && m === "POST" && uploadPost) return uploadPost(req);
9237
+ if (path2[0] === "media" && path2[1] === "extract" && path2.length === 3 && m === "POST" && zipExtractPost) {
9238
+ return zipExtractPost(req, path2[2]);
7480
9239
  }
7481
- if (path[0] === "blogs" && path[1] === "slug" && path.length === 3 && m === "GET" && blogBySlugGet) {
7482
- return blogBySlugGet(req, path[2]);
9240
+ if (path2[0] === "blogs" && path2[1] === "slug" && path2.length === 3 && m === "GET" && blogBySlugGet) {
9241
+ return blogBySlugGet(req, path2[2]);
7483
9242
  }
7484
- if (path[0] === "forms" && path[1] === "slug" && path.length === 3 && m === "GET" && formBySlugGet) {
7485
- return formBySlugGet(req, path[2]);
9243
+ if (path2[0] === "forms" && path2[1] === "slug" && path2.length === 3 && m === "GET" && formBySlugGet) {
9244
+ return formBySlugGet(req, path2[2]);
7486
9245
  }
7487
- if (path[0] === "form-submissions") {
7488
- if (path.length === 1) {
9246
+ if (path2[0] === "form-submissions") {
9247
+ if (path2.length === 1) {
7489
9248
  if (m === "GET" && formSubmissionList) return formSubmissionList(req);
7490
9249
  if (m === "POST" && formSubmissionPost) return formSubmissionPost(req);
7491
9250
  }
7492
- if (path.length === 2 && m === "GET" && formSubmissionGetById) return formSubmissionGetById(req, path[1]);
9251
+ if (path2.length === 2 && m === "GET" && formSubmissionGetById) return formSubmissionGetById(req, path2[1]);
7493
9252
  }
7494
- if (path[0] === "forms" && formSaveHandlers) {
7495
- if (path.length === 1 && m === "POST") return formSaveHandlers.POST(req);
7496
- if (path.length === 2) {
7497
- if (m === "GET") return formSaveHandlers.GET(req, path[1]);
7498
- if (m === "PUT" || m === "PATCH") return formSaveHandlers.PUT(req, path[1]);
9253
+ if (path2[0] === "forms" && formSaveHandlers) {
9254
+ if (path2.length === 1 && m === "POST") return formSaveHandlers.POST(req);
9255
+ if (path2.length === 2) {
9256
+ if (m === "GET") return formSaveHandlers.GET(req, path2[1]);
9257
+ if (m === "PUT" || m === "PATCH") return formSaveHandlers.PUT(req, path2[1]);
7499
9258
  }
7500
9259
  }
7501
- if (path[0] === "users" && usersHandlers) {
7502
- if (path.length === 1) {
9260
+ if (path2[0] === "users" && usersHandlers) {
9261
+ if (path2.length === 1) {
7503
9262
  if (m === "GET") return usersHandlers.list(req);
7504
9263
  if (m === "POST") return usersHandlers.create(req);
7505
9264
  }
7506
- if (path.length === 2) {
7507
- if (path[1] === "avatar" && m === "POST" && avatarPost) return avatarPost(req);
7508
- if (path[1] === "profile" && profileHandlers) {
9265
+ if (path2.length === 2) {
9266
+ if (path2[1] === "avatar" && m === "POST" && avatarPost) return avatarPost(req);
9267
+ if (path2[1] === "profile" && profileHandlers) {
7509
9268
  if (m === "GET") return profileHandlers.GET(req);
7510
9269
  if (m === "PUT") return profileHandlers.PUT(req);
7511
9270
  }
7512
- const id = path[1];
9271
+ const id = path2[1];
7513
9272
  if (m === "GET") return usersHandlers.getById(req, id);
7514
9273
  if (m === "PUT" || m === "PATCH") return usersHandlers.update(req, id);
7515
9274
  if (m === "DELETE") return usersHandlers.delete(req, id);
7516
9275
  }
7517
- if (path.length === 3 && path[2] === "regenerate-invite" && m === "POST") {
7518
- return usersHandlers.regenerateInvite(req, path[1]);
9276
+ if (path2.length === 3 && path2[2] === "regenerate-invite" && m === "POST") {
9277
+ return usersHandlers.regenerateInvite(req, path2[1]);
7519
9278
  }
7520
9279
  }
7521
- if (path[0] === "users" && path.length === 2 && userAuthRouter && m === "POST") {
7522
- return userAuthRouter.POST(req, path[1]);
9280
+ if (path2[0] === "users" && path2.length === 2 && userAuthRouter && m === "POST") {
9281
+ return userAuthRouter.POST(req, path2[1]);
9282
+ }
9283
+ if (path2[0] === "social-media" && path2[1] === "linkedin" && socialMediaHandlers && path2.length === 3) {
9284
+ const tail = path2[2];
9285
+ if (tail === "status" && m === "GET") {
9286
+ return socialMediaHandlers.getLinkedInStatus(req);
9287
+ }
9288
+ if (tail === "sync-profile" && m === "POST") {
9289
+ return socialMediaHandlers.syncLinkedInProfile(req);
9290
+ }
9291
+ if (tail === "publish-blog" && m === "POST") {
9292
+ let body;
9293
+ try {
9294
+ body = await req.json();
9295
+ } catch {
9296
+ return config.json({ error: "Invalid JSON body" }, { status: 400 });
9297
+ }
9298
+ const id = Number(body?.blogId);
9299
+ return socialMediaHandlers.publishBlogToLinkedIn(req, id);
9300
+ }
9301
+ }
9302
+ if (path2[0] === "social-media" && path2[1] === "facebook" && socialMediaHandlers && path2.length === 3) {
9303
+ const tail = path2[2];
9304
+ if (tail === "fetch-pages" && m === "POST") {
9305
+ return socialMediaHandlers.fetchFacebookManagedPages(req);
9306
+ }
9307
+ if (tail === "status" && m === "GET") {
9308
+ return socialMediaHandlers.getFacebookStatus(req);
9309
+ }
9310
+ if (tail === "publish-blog" && m === "POST") {
9311
+ let body;
9312
+ try {
9313
+ body = await req.json();
9314
+ } catch {
9315
+ return config.json({ error: "Invalid JSON body" }, { status: 400 });
9316
+ }
9317
+ const id = Number(body?.blogId);
9318
+ return socialMediaHandlers.publishBlogToFacebook(req, id);
9319
+ }
7523
9320
  }
7524
- if (path[0] === "settings" && path.length === 2 && settingsHandlers) {
7525
- const group = path[1];
9321
+ if (path2[0] === "settings" && path2.length === 2 && settingsHandlers) {
9322
+ const group = path2[1];
7526
9323
  const isPublic = settingsConfig?.publicGetGroups?.includes(group);
7527
9324
  if (m === "GET") {
7528
9325
  if (!isPublic) {
@@ -7539,15 +9336,15 @@ function createCmsApiHandler(config) {
7539
9336
  return settingsHandlers.PUT(req, group);
7540
9337
  }
7541
9338
  }
7542
- if (path[0] === "message-templates" && path[1] === "sms" && path.length === 2) {
9339
+ if (path2[0] === "message-templates" && path2[1] === "sms" && path2.length === 2) {
7543
9340
  if (m === "GET") return smsMessageTemplateHandlers.GET(req);
7544
9341
  if (m === "PUT") return smsMessageTemplateHandlers.PUT(req);
7545
9342
  }
7546
- if (path[0] === "llm_agents") {
9343
+ if (path2[0] === "llm_agents") {
7547
9344
  if (!entityMap.llm_agents) {
7548
9345
  console.error(CMS_API_LOG, "llm_agents route: entity missing after merge", {
7549
9346
  method: m,
7550
- pathJoined: path.join("/"),
9347
+ pathJoined: path2.join("/"),
7551
9348
  entityMapKeyCount: Object.keys(entityMap).length
7552
9349
  });
7553
9350
  return config.json(
@@ -7555,19 +9352,19 @@ function createCmsApiHandler(config) {
7555
9352
  { status: 503 }
7556
9353
  );
7557
9354
  }
7558
- if (path.length === 1) {
9355
+ if (path2.length === 1) {
7559
9356
  if (m === "GET") return crud.GET(req, "llm_agents");
7560
9357
  if (m === "POST") return crud.POST(req, "llm_agents");
7561
9358
  }
7562
- if (path.length === 2 && !path[1]?.includes("knowledge")) {
7563
- const id = path[1];
9359
+ if (path2.length === 2 && !path2[1]?.includes("knowledge")) {
9360
+ const id = path2[1];
7564
9361
  if (m === "GET") return crudById.GET(req, "llm_agents", id);
7565
9362
  if (m === "PUT" || m === "PATCH") return crudById.PUT(req, "llm_agents", id);
7566
9363
  if (m === "DELETE") return crudById.DELETE(req, "llm_agents", id);
7567
9364
  }
7568
9365
  }
7569
9366
  {
7570
- const kbMatch = matchLlmAgentKnowledgeRoute(path);
9367
+ const kbMatch = matchLlmAgentKnowledgeRoute(path2);
7571
9368
  if (kbMatch) {
7572
9369
  if (!llmAgentKnowledgeHandlers) {
7573
9370
  return config.json(
@@ -7590,29 +9387,29 @@ function createCmsApiHandler(config) {
7590
9387
  }
7591
9388
  }
7592
9389
  }
7593
- if (path[0] === "chat" && chatHandlers) {
7594
- if (path.length === 2 && path[1] === "config" && m === "GET") return chatHandlers.publicConfig(req);
7595
- if (path.length === 2 && path[1] === "identify" && m === "POST") return chatHandlers.identify(req);
7596
- if (path.length === 4 && path[1] === "conversations" && path[3] === "messages" && m === "GET") return chatHandlers.getMessages(req, path[2]);
7597
- if (path.length === 2 && path[1] === "messages" && m === "POST") return chatHandlers.postMessage(req);
9390
+ if (path2[0] === "chat" && chatHandlers) {
9391
+ if (path2.length === 2 && path2[1] === "config" && m === "GET") return chatHandlers.publicConfig(req);
9392
+ if (path2.length === 2 && path2[1] === "identify" && m === "POST") return chatHandlers.identify(req);
9393
+ if (path2.length === 4 && path2[1] === "conversations" && path2[3] === "messages" && m === "GET") return chatHandlers.getMessages(req, path2[2]);
9394
+ if (path2.length === 2 && path2[1] === "messages" && m === "POST") return chatHandlers.postMessage(req);
7598
9395
  }
7599
- if (path[0] === "orders" && path.length === 3 && path[2] === "invoice" && m === "GET" && getCms) {
9396
+ if (path2[0] === "orders" && path2.length === 3 && path2[2] === "invoice" && m === "GET" && getCms) {
7600
9397
  const a = await config.requireAuth(req);
7601
9398
  if (a) return a;
7602
9399
  const pe = await requireEntityPermissionEffective(req, "orders", "read");
7603
9400
  if (pe) return pe;
7604
9401
  const cms = await getCms();
7605
9402
  const { streamOrderInvoicePdf: streamOrderInvoicePdf2 } = await Promise.resolve().then(() => (init_erp_order_invoice(), erp_order_invoice_exports));
7606
- const oid = Number(path[1]);
9403
+ const oid = Number(path2[1]);
7607
9404
  if (!Number.isFinite(oid)) return config.json({ error: "Invalid id" }, { status: 400 });
7608
9405
  return streamOrderInvoicePdf2(cms, dataSource, entityMap, oid, {});
7609
9406
  }
7610
- if (path[0] === "orders" && path.length === 3 && path[2] === "repost-erp" && getCms) {
9407
+ if (path2[0] === "orders" && path2.length === 3 && path2[2] === "repost-erp" && getCms) {
7611
9408
  const a = await config.requireAuth(req);
7612
9409
  if (a) return a;
7613
9410
  const pe = await requireEntityPermissionEffective(req, "orders", m === "GET" ? "read" : "update");
7614
9411
  if (pe) return pe;
7615
- const oid = Number(path[1]);
9412
+ const oid = Number(path2[1]);
7616
9413
  if (!Number.isFinite(oid)) return config.json({ error: "Invalid id" }, { status: 400 });
7617
9414
  const cms = await getCms();
7618
9415
  const { isErpIntegrationEnabled: isErpIntegrationEnabled3 } = await Promise.resolve().then(() => (init_erp_config_enabled(), erp_config_enabled_exports));
@@ -7628,13 +9425,13 @@ function createCmsApiHandler(config) {
7628
9425
  }
7629
9426
  return config.json({ error: "Method not allowed" }, { status: 405 });
7630
9427
  }
7631
- if (path.length === 0) return config.json({ error: "Not found" }, { status: 404 });
7632
- const resource = resolveResource(path[0]);
9428
+ if (path2.length === 0) return config.json({ error: "Not found" }, { status: 404 });
9429
+ const resource = resolveResource(path2[0]);
7633
9430
  if (!crudResources.includes(resource)) {
7634
9431
  console.warn(CMS_API_LOG, "generic CRUD gate: Invalid resource (not in crudResources)", {
7635
9432
  method: m,
7636
- pathJoined: path.join("/"),
7637
- pathSegments: path,
9433
+ pathJoined: path2.join("/"),
9434
+ pathSegments: path2,
7638
9435
  resource,
7639
9436
  hasLlmAgentsEntity: Boolean(entityMap.llm_agents),
7640
9437
  crudResourcesHasResource: crudResources.includes(resource),
@@ -7642,24 +9439,24 @@ function createCmsApiHandler(config) {
7642
9439
  });
7643
9440
  return config.json({ error: "Invalid resource" }, { status: 400 });
7644
9441
  }
7645
- if (path.length === 2) {
7646
- if (path[1] === "metadata" && m === "GET") {
9442
+ if (path2.length === 2) {
9443
+ if (path2[1] === "metadata" && m === "GET") {
7647
9444
  return crud.GET_METADATA(req, resource);
7648
9445
  }
7649
- if (path[1] === "bulk" && m === "POST") {
9446
+ if (path2[1] === "bulk" && m === "POST") {
7650
9447
  return crud.BULK_POST(req, resource);
7651
9448
  }
7652
- if (path[1] === "export" && m === "GET") {
9449
+ if (path2[1] === "export" && m === "GET") {
7653
9450
  return crud.GET_EXPORT(req, resource);
7654
9451
  }
7655
9452
  }
7656
- if (path.length === 1) {
9453
+ if (path2.length === 1) {
7657
9454
  if (m === "GET") return crud.GET(req, resource);
7658
9455
  if (m === "POST") return crud.POST(req, resource);
7659
9456
  return config.json({ error: "Method not allowed" }, { status: 405 });
7660
9457
  }
7661
- if (path.length === 2) {
7662
- const id = path[1];
9458
+ if (path2.length === 2) {
9459
+ const id = path2[1];
7663
9460
  if (m === "GET") return crudById.GET(req, resource, id);
7664
9461
  if (m === "PUT" || m === "PATCH") return crudById.PUT(req, resource, id);
7665
9462
  if (m === "DELETE") return crudById.DELETE(req, resource, id);
@@ -8377,7 +10174,7 @@ function createStorefrontApiHandler(config) {
8377
10174
  };
8378
10175
  }
8379
10176
  return {
8380
- async handle(method, path, req) {
10177
+ async handle(method, path2, req) {
8381
10178
  try {
8382
10179
  let serializeAddress2 = function(a) {
8383
10180
  return {
@@ -8393,7 +10190,7 @@ function createStorefrontApiHandler(config) {
8393
10190
  };
8394
10191
  };
8395
10192
  var serializeAddress = serializeAddress2;
8396
- if (path[0] === "products" && path.length === 1 && method === "GET") {
10193
+ if (path2[0] === "products" && path2.length === 1 && method === "GET") {
8397
10194
  const url = new URL(req.url || "", "http://localhost");
8398
10195
  const collectionSlug = url.searchParams.get("collection")?.trim();
8399
10196
  const collectionId = url.searchParams.get("collectionId");
@@ -8435,8 +10232,8 @@ function createStorefrontApiHandler(config) {
8435
10232
  ...collectionFilter && { collection: collectionFilter }
8436
10233
  });
8437
10234
  }
8438
- if (path[0] === "products" && path.length === 2 && method === "GET") {
8439
- const idOrSlug = path[1];
10235
+ if (path2[0] === "products" && path2.length === 2 && method === "GET") {
10236
+ const idOrSlug = path2[1];
8440
10237
  const byId = /^\d+$/.test(idOrSlug);
8441
10238
  const product = await productRepo().findOne({
8442
10239
  where: byId ? { id: parseInt(idOrSlug, 10), status: "available", deleted: false } : { slug: idOrSlug, status: "available", deleted: false },
@@ -8451,7 +10248,7 @@ function createStorefrontApiHandler(config) {
8451
10248
  })).filter((t) => t.name || t.value);
8452
10249
  return json({ ...serializeProduct(p), attributes: attributeTags });
8453
10250
  }
8454
- if (path[0] === "collections" && path.length === 1 && method === "GET") {
10251
+ if (path2[0] === "collections" && path2.length === 1 && method === "GET") {
8455
10252
  const items = await collectionRepo().find({
8456
10253
  where: { active: true, deleted: false },
8457
10254
  order: { sortOrder: "ASC", id: "ASC" }
@@ -8480,8 +10277,8 @@ function createStorefrontApiHandler(config) {
8480
10277
  })
8481
10278
  });
8482
10279
  }
8483
- if (path[0] === "collections" && path.length === 2 && method === "GET") {
8484
- const idOrSlug = path[1];
10280
+ if (path2[0] === "collections" && path2.length === 2 && method === "GET") {
10281
+ const idOrSlug = path2[1];
8485
10282
  const byId = /^\d+$/.test(idOrSlug);
8486
10283
  const collection = await collectionRepo().findOne({
8487
10284
  where: byId ? { id: parseInt(idOrSlug, 10), active: true, deleted: false } : { slug: idOrSlug, active: true, deleted: false },
@@ -8504,7 +10301,7 @@ function createStorefrontApiHandler(config) {
8504
10301
  products: products.map((p) => serializeProduct(p))
8505
10302
  });
8506
10303
  }
8507
- if (path[0] === "profile" && path.length === 1 && method === "GET") {
10304
+ if (path2[0] === "profile" && path2.length === 1 && method === "GET") {
8508
10305
  const u = await getSessionUser();
8509
10306
  const uid = u?.id ? parseInt(String(u.id), 10) : NaN;
8510
10307
  if (!Number.isFinite(uid)) return json({ error: "Unauthorized" }, { status: 401 });
@@ -8521,7 +10318,7 @@ function createStorefrontApiHandler(config) {
8521
10318
  } : null
8522
10319
  });
8523
10320
  }
8524
- if (path[0] === "profile" && path.length === 1 && method === "PUT") {
10321
+ if (path2[0] === "profile" && path2.length === 1 && method === "PUT") {
8525
10322
  const u = await getSessionUser();
8526
10323
  const uid = u?.id ? parseInt(String(u.id), 10) : NaN;
8527
10324
  if (!Number.isFinite(uid)) return json({ error: "Unauthorized" }, { status: 401 });
@@ -8558,7 +10355,7 @@ function createStorefrontApiHandler(config) {
8558
10355
  if (!contact) return json({ error: "Contact not found" }, { status: 404 });
8559
10356
  return { contactId: contact.id };
8560
10357
  }
8561
- if (path[0] === "addresses" && path.length === 1 && method === "GET") {
10358
+ if (path2[0] === "addresses" && path2.length === 1 && method === "GET") {
8562
10359
  const contactOrErr = await getContactForAddresses();
8563
10360
  if (contactOrErr instanceof Response) return contactOrErr;
8564
10361
  const list = await addressRepo().find({
@@ -8567,7 +10364,7 @@ function createStorefrontApiHandler(config) {
8567
10364
  });
8568
10365
  return json({ addresses: list.map((a) => serializeAddress2(a)) });
8569
10366
  }
8570
- if (path[0] === "addresses" && path.length === 1 && method === "POST") {
10367
+ if (path2[0] === "addresses" && path2.length === 1 && method === "POST") {
8571
10368
  const contactOrErr = await getContactForAddresses();
8572
10369
  if (contactOrErr instanceof Response) return contactOrErr;
8573
10370
  const b = await req.json().catch(() => ({}));
@@ -8586,10 +10383,10 @@ function createStorefrontApiHandler(config) {
8586
10383
  const created = await addressRepo().save(addressRepo().create(row));
8587
10384
  return json(serializeAddress2(created));
8588
10385
  }
8589
- if (path[0] === "addresses" && path.length === 2 && (method === "PATCH" || method === "PUT")) {
10386
+ if (path2[0] === "addresses" && path2.length === 2 && (method === "PATCH" || method === "PUT")) {
8590
10387
  const contactOrErr = await getContactForAddresses();
8591
10388
  if (contactOrErr instanceof Response) return contactOrErr;
8592
- const id = parseInt(path[1], 10);
10389
+ const id = parseInt(path2[1], 10);
8593
10390
  if (!Number.isFinite(id)) return json({ error: "Invalid id" }, { status: 400 });
8594
10391
  const existing = await addressRepo().findOne({ where: { id, contactId: contactOrErr.contactId } });
8595
10392
  if (!existing) return json({ error: "Not found" }, { status: 404 });
@@ -8615,17 +10412,17 @@ function createStorefrontApiHandler(config) {
8615
10412
  const updated = await addressRepo().findOne({ where: { id } });
8616
10413
  return json(serializeAddress2(updated));
8617
10414
  }
8618
- if (path[0] === "addresses" && path.length === 2 && method === "DELETE") {
10415
+ if (path2[0] === "addresses" && path2.length === 2 && method === "DELETE") {
8619
10416
  const contactOrErr = await getContactForAddresses();
8620
10417
  if (contactOrErr instanceof Response) return contactOrErr;
8621
- const id = parseInt(path[1], 10);
10418
+ const id = parseInt(path2[1], 10);
8622
10419
  if (!Number.isFinite(id)) return json({ error: "Invalid id" }, { status: 400 });
8623
10420
  const existing = await addressRepo().findOne({ where: { id, contactId: contactOrErr.contactId } });
8624
10421
  if (!existing) return json({ error: "Not found" }, { status: 404 });
8625
10422
  await addressRepo().delete(id);
8626
10423
  return json({ deleted: true });
8627
10424
  }
8628
- if (path[0] === "verify-email" && path.length === 1 && method === "POST") {
10425
+ if (path2[0] === "verify-email" && path2.length === 1 && method === "POST") {
8629
10426
  const b = await req.json().catch(() => ({}));
8630
10427
  const token = typeof b.token === "string" ? b.token.trim() : "";
8631
10428
  if (!token) return json({ error: "token is required" }, { status: 400 });
@@ -8644,7 +10441,7 @@ function createStorefrontApiHandler(config) {
8644
10441
  await tokenRepo().delete({ email });
8645
10442
  return json({ success: true, message: "Email verified. You can sign in." });
8646
10443
  }
8647
- if (path[0] === "auth" && path[1] === "otp" && path[2] === "send" && path.length === 3 && method === "POST") {
10444
+ if (path2[0] === "auth" && path2[1] === "otp" && path2[2] === "send" && path2.length === 3 && method === "POST") {
8648
10445
  const b = await req.json().catch(() => ({}));
8649
10446
  const purposeRaw = typeof b.purpose === "string" ? b.purpose.trim() : "";
8650
10447
  const purpose = purposeRaw === "login" || purposeRaw === "verify_email" || purposeRaw === "verify_phone" ? purposeRaw : "";
@@ -8729,7 +10526,7 @@ function createStorefrontApiHandler(config) {
8729
10526
  }
8730
10527
  return json({ ok: true });
8731
10528
  }
8732
- if (path[0] === "auth" && path[1] === "otp" && path[2] === "verify-email" && path.length === 3 && method === "POST") {
10529
+ if (path2[0] === "auth" && path2[1] === "otp" && path2[2] === "verify-email" && path2.length === 3 && method === "POST") {
8733
10530
  if (otpOff("verifyEmail")) return json({ error: "otp_disabled" }, { status: 403 });
8734
10531
  const b = await req.json().catch(() => ({}));
8735
10532
  const email = typeof b.email === "string" ? b.email.trim().toLowerCase() : "";
@@ -8752,7 +10549,7 @@ function createStorefrontApiHandler(config) {
8752
10549
  await tokenRepo().delete({ email });
8753
10550
  return json({ success: true, message: "Email verified. You can sign in." });
8754
10551
  }
8755
- if (path[0] === "auth" && path[1] === "otp" && path[2] === "verify-phone" && path.length === 3 && method === "POST") {
10552
+ if (path2[0] === "auth" && path2[1] === "otp" && path2[2] === "verify-phone" && path2.length === 3 && method === "POST") {
8756
10553
  if (otpOff("verifyPhone")) return json({ error: "otp_disabled" }, { status: 403 });
8757
10554
  const su = await getSessionUser();
8758
10555
  const uid = su?.id ? parseInt(String(su.id), 10) : NaN;
@@ -8780,7 +10577,7 @@ function createStorefrontApiHandler(config) {
8780
10577
  }
8781
10578
  return json({ success: true });
8782
10579
  }
8783
- if (path[0] === "register" && path.length === 1 && method === "POST") {
10580
+ if (path2[0] === "register" && path2.length === 1 && method === "POST") {
8784
10581
  if (!config.hashPassword) return json({ error: "Registration not configured" }, { status: 501 });
8785
10582
  const b = await req.json().catch(() => ({}));
8786
10583
  const capReg = await assertCaptchaOk(getCms, b, req, json);
@@ -8837,14 +10634,14 @@ function createStorefrontApiHandler(config) {
8837
10634
  emailVerificationSent
8838
10635
  });
8839
10636
  }
8840
- if (path[0] === "cart" && path.length === 1 && method === "GET") {
10637
+ if (path2[0] === "cart" && path2.length === 1 && method === "GET") {
8841
10638
  const { cart, setCookie, err } = await getOrCreateCart(req);
8842
10639
  if (err) return err;
8843
10640
  const body = serializeCart(cart);
8844
10641
  if (setCookie) return json(body, { headers: { "Set-Cookie": setCookie } });
8845
10642
  return json(body);
8846
10643
  }
8847
- if (path[0] === "cart" && path[1] === "items" && path.length === 2 && method === "POST") {
10644
+ if (path2[0] === "cart" && path2[1] === "items" && path2.length === 2 && method === "POST") {
8848
10645
  const body = await req.json().catch(() => ({}));
8849
10646
  const capCart = await assertCaptchaOk(getCms, body, req, json);
8850
10647
  if (capCart) return capCart;
@@ -8875,8 +10672,8 @@ function createStorefrontApiHandler(config) {
8875
10672
  if (setCookie) return json(out, { headers: { "Set-Cookie": setCookie } });
8876
10673
  return json(out);
8877
10674
  }
8878
- if (path[0] === "cart" && path[1] === "items" && path.length === 3) {
8879
- const itemId = parseInt(path[2], 10);
10675
+ if (path2[0] === "cart" && path2[1] === "items" && path2.length === 3) {
10676
+ const itemId = parseInt(path2[2], 10);
8880
10677
  if (!Number.isFinite(itemId)) return json({ error: "Invalid item id" }, { status: 400 });
8881
10678
  const { cart, setCookie, err } = await getOrCreateCart(req);
8882
10679
  if (err) return err;
@@ -8909,7 +10706,7 @@ function createStorefrontApiHandler(config) {
8909
10706
  return json(out);
8910
10707
  }
8911
10708
  }
8912
- if (path[0] === "cart" && path[1] === "merge" && method === "POST") {
10709
+ if (path2[0] === "cart" && path2[1] === "merge" && method === "POST") {
8913
10710
  const u = await getSessionUser();
8914
10711
  const uid = u?.id ? parseInt(String(u.id), 10) : NaN;
8915
10712
  if (!Number.isFinite(uid)) return json({ error: "Unauthorized" }, { status: 401 });
@@ -9015,7 +10812,7 @@ function createStorefrontApiHandler(config) {
9015
10812
  }
9016
10813
  return { wishlist: w, setCookie: null, err: null };
9017
10814
  }
9018
- if (path[0] === "wishlist" && path.length === 1 && method === "GET") {
10815
+ if (path2[0] === "wishlist" && path2.length === 1 && method === "GET") {
9019
10816
  const { wishlist, setCookie, err } = await getOrCreateWishlist(req);
9020
10817
  if (err) return err;
9021
10818
  const items = await wishlistItemRepo().find({
@@ -9043,7 +10840,7 @@ function createStorefrontApiHandler(config) {
9043
10840
  if (setCookie) return json(body, { headers: { "Set-Cookie": setCookie } });
9044
10841
  return json(body);
9045
10842
  }
9046
- if (path[0] === "wishlist" && path[1] === "items" && path.length === 2 && method === "POST") {
10843
+ if (path2[0] === "wishlist" && path2[1] === "items" && path2.length === 2 && method === "POST") {
9047
10844
  const { wishlist, setCookie, err } = await getOrCreateWishlist(req);
9048
10845
  if (err) return err;
9049
10846
  const b = await req.json().catch(() => ({}));
@@ -9057,15 +10854,15 @@ function createStorefrontApiHandler(config) {
9057
10854
  if (setCookie) return json({ ok: true }, { headers: { "Set-Cookie": setCookie } });
9058
10855
  return json({ ok: true });
9059
10856
  }
9060
- if (path[0] === "wishlist" && path[1] === "items" && path.length === 3 && method === "DELETE") {
10857
+ if (path2[0] === "wishlist" && path2[1] === "items" && path2.length === 3 && method === "DELETE") {
9061
10858
  const { wishlist, setCookie, err } = await getOrCreateWishlist(req);
9062
10859
  if (err) return err;
9063
- const productId = parseInt(path[2], 10);
10860
+ const productId = parseInt(path2[2], 10);
9064
10861
  await wishlistItemRepo().delete({ wishlistId: wishlist.id, productId });
9065
10862
  if (setCookie) return json({ ok: true }, { headers: { "Set-Cookie": setCookie } });
9066
10863
  return json({ ok: true });
9067
10864
  }
9068
- if (path[0] === "checkout" && path[1] === "order" && path.length === 2 && method === "POST") {
10865
+ if (path2[0] === "checkout" && path2[1] === "order" && path2.length === 2 && method === "POST") {
9069
10866
  const b = await req.json().catch(() => ({}));
9070
10867
  const capOrd = await assertCaptchaOk(getCms, b, req, json);
9071
10868
  if (capOrd) return capOrd;
@@ -9168,7 +10965,7 @@ function createStorefrontApiHandler(config) {
9168
10965
  currency: cart.currency || "INR"
9169
10966
  });
9170
10967
  }
9171
- if (path[0] === "checkout" && path.length === 1 && method === "POST") {
10968
+ if (path2[0] === "checkout" && path2.length === 1 && method === "POST") {
9172
10969
  const b = await req.json().catch(() => ({}));
9173
10970
  const capChk = await assertCaptchaOk(getCms, b, req, json);
9174
10971
  if (capChk) return capChk;
@@ -9270,7 +11067,7 @@ function createStorefrontApiHandler(config) {
9270
11067
  total: prepChk.orderTotal
9271
11068
  });
9272
11069
  }
9273
- if (path[0] === "orders" && path.length === 1 && method === "GET") {
11070
+ if (path2[0] === "orders" && path2.length === 1 && method === "GET") {
9274
11071
  const u = await getSessionUser();
9275
11072
  const uid = u?.id ? parseInt(String(u.id), 10) : NaN;
9276
11073
  if (!Number.isFinite(uid)) return json({ error: "Unauthorized" }, { status: 401 });
@@ -9312,27 +11109,27 @@ function createStorefrontApiHandler(config) {
9312
11109
  })
9313
11110
  });
9314
11111
  }
9315
- if (path[0] === "orders" && path.length === 3 && path[2] === "invoice" && method === "GET") {
11112
+ if (path2[0] === "orders" && path2.length === 3 && path2[2] === "invoice" && method === "GET") {
9316
11113
  const u = await getSessionUser();
9317
11114
  const uid = u?.id ? parseInt(String(u.id), 10) : NaN;
9318
11115
  if (!Number.isFinite(uid)) return json({ error: "Unauthorized" }, { status: 401 });
9319
11116
  if (!getCms) return json({ error: "Not found" }, { status: 404 });
9320
11117
  const contact = await contactRepo().findOne({ where: { userId: uid, deleted: false } });
9321
11118
  if (!contact) return json({ error: "Not found" }, { status: 404 });
9322
- const orderId = parseInt(path[1], 10);
11119
+ const orderId = parseInt(path2[1], 10);
9323
11120
  if (!Number.isFinite(orderId)) return json({ error: "Invalid id" }, { status: 400 });
9324
11121
  const cms = await getCms();
9325
11122
  return streamOrderInvoicePdf(cms, dataSource, entityMap, orderId, {
9326
11123
  ownerContactId: contact.id
9327
11124
  });
9328
11125
  }
9329
- if (path[0] === "orders" && path.length === 2 && method === "GET") {
11126
+ if (path2[0] === "orders" && path2.length === 2 && method === "GET") {
9330
11127
  const u = await getSessionUser();
9331
11128
  const uid = u?.id ? parseInt(String(u.id), 10) : NaN;
9332
11129
  if (!Number.isFinite(uid)) return json({ error: "Unauthorized" }, { status: 401 });
9333
11130
  const contact = await contactRepo().findOne({ where: { userId: uid, deleted: false } });
9334
11131
  if (!contact) return json({ error: "Not found" }, { status: 404 });
9335
- const orderId = parseInt(path[1], 10);
11132
+ const orderId = parseInt(path2[1], 10);
9336
11133
  let order = await orderRepo().findOne({
9337
11134
  where: { id: orderId, contactId: contact.id, deleted: false },
9338
11135
  relations: ["items", "items.product"]
@@ -9434,6 +11231,8 @@ export {
9434
11231
  getPublicSettingsGroup,
9435
11232
  mergeGuardrailsIntoSystemPrompt,
9436
11233
  parseLlmAgentValidationRules,
11234
+ simpleDecrypt,
11235
+ simpleEncrypt,
9437
11236
  validateUserMessageAgainstAgentRules,
9438
11237
  validateUserMessageAgainstStructuredRules
9439
11238
  };