@sonicjs-cms/core 2.0.4 → 2.0.7

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.
Files changed (57) hide show
  1. package/README.md +16 -0
  2. package/dist/{chunk-4BJGEGX5.cjs → chunk-3R7EQNGO.cjs} +19 -32
  3. package/dist/chunk-3R7EQNGO.cjs.map +1 -0
  4. package/dist/{chunk-CDBVZEWR.js → chunk-4MBTSUI6.js} +35 -3
  5. package/dist/chunk-4MBTSUI6.js.map +1 -0
  6. package/dist/{chunk-LH4Z7QID.js → chunk-6FR25MPC.js} +111 -3
  7. package/dist/chunk-6FR25MPC.js.map +1 -0
  8. package/dist/{chunk-3SPQ3J4N.cjs → chunk-7XEESVSX.cjs} +73 -2
  9. package/dist/chunk-7XEESVSX.cjs.map +1 -0
  10. package/dist/{chunk-3NVJ6W27.cjs → chunk-DOR2IU73.cjs} +111 -2
  11. package/dist/chunk-DOR2IU73.cjs.map +1 -0
  12. package/dist/{chunk-CQ2VMJQO.js → chunk-JB2NUJJ5.js} +253 -85
  13. package/dist/chunk-JB2NUJJ5.js.map +1 -0
  14. package/dist/{chunk-5APKEYFK.cjs → chunk-KHNSPJ6X.cjs} +5 -5
  15. package/dist/{chunk-5APKEYFK.cjs.map → chunk-KHNSPJ6X.cjs.map} +1 -1
  16. package/dist/{chunk-RYQCT2IV.js → chunk-LS5CMDNL.js} +3 -3
  17. package/dist/{chunk-RYQCT2IV.js.map → chunk-LS5CMDNL.js.map} +1 -1
  18. package/dist/{chunk-M6FPVS7E.js → chunk-O7LMFJMZ.js} +16 -29
  19. package/dist/chunk-O7LMFJMZ.js.map +1 -0
  20. package/dist/{chunk-RZW752PE.cjs → chunk-OOV64BK4.cjs} +429 -261
  21. package/dist/chunk-OOV64BK4.cjs.map +1 -0
  22. package/dist/{chunk-BRPONFW6.cjs → chunk-RSFXIU6A.cjs} +14 -14
  23. package/dist/{chunk-BRPONFW6.cjs.map → chunk-RSFXIU6A.cjs.map} +1 -1
  24. package/dist/{chunk-LEG4KNFP.cjs → chunk-YGVWY6KO.cjs} +35 -3
  25. package/dist/chunk-YGVWY6KO.cjs.map +1 -0
  26. package/dist/{chunk-3LZ6TLPC.js → chunk-YHLLVUJC.js} +73 -2
  27. package/dist/chunk-YHLLVUJC.js.map +1 -0
  28. package/dist/{chunk-WKGONLHK.js → chunk-YURRY22X.js} +14 -14
  29. package/dist/{chunk-WKGONLHK.js.map → chunk-YURRY22X.js.map} +1 -1
  30. package/dist/index.cjs +886 -137
  31. package/dist/index.cjs.map +1 -1
  32. package/dist/index.js +761 -11
  33. package/dist/index.js.map +1 -1
  34. package/dist/middleware.cjs +23 -23
  35. package/dist/middleware.js +2 -2
  36. package/dist/routes.cjs +26 -26
  37. package/dist/routes.js +6 -6
  38. package/dist/services.cjs +25 -21
  39. package/dist/services.js +2 -2
  40. package/dist/templates.cjs +18 -18
  41. package/dist/templates.js +2 -2
  42. package/dist/utils.cjs +11 -11
  43. package/dist/utils.js +1 -1
  44. package/migrations/006_plugin_system.sql +2 -2
  45. package/migrations/011_config_managed_collections.sql +1 -0
  46. package/migrations/018_settings_table.sql +23 -0
  47. package/package.json +12 -12
  48. package/dist/chunk-3LZ6TLPC.js.map +0 -1
  49. package/dist/chunk-3NVJ6W27.cjs.map +0 -1
  50. package/dist/chunk-3SPQ3J4N.cjs.map +0 -1
  51. package/dist/chunk-4BJGEGX5.cjs.map +0 -1
  52. package/dist/chunk-CDBVZEWR.js.map +0 -1
  53. package/dist/chunk-CQ2VMJQO.js.map +0 -1
  54. package/dist/chunk-LEG4KNFP.cjs.map +0 -1
  55. package/dist/chunk-LH4Z7QID.js.map +0 -1
  56. package/dist/chunk-M6FPVS7E.js.map +0 -1
  57. package/dist/chunk-RZW752PE.cjs.map +0 -1
@@ -1,8 +1,8 @@
1
- import { getCacheService, CACHE_CONFIGS, getLogger } from './chunk-LH4Z7QID.js';
2
- import { requireAuth, isPluginActive, requireRole, AuthManager, logActivity } from './chunk-M6FPVS7E.js';
3
- import { PluginService, MigrationService } from './chunk-CDBVZEWR.js';
4
- import { init_admin_layout_catalyst_template, renderDesignPage, renderCheckboxPage, renderFAQList, renderTestimonialsList, renderCodeExamplesList, renderAlert, renderTable, renderPagination, renderConfirmationDialog, getConfirmationDialogScript, renderAdminLayoutCatalyst, renderAdminLayout, adminLayoutV2, renderForm } from './chunk-3LZ6TLPC.js';
5
- import { QueryFilterBuilder, sanitizeInput, getCoreVersion, escapeHtml } from './chunk-WKGONLHK.js';
1
+ import { getCacheService, CACHE_CONFIGS, getLogger, SettingsService } from './chunk-6FR25MPC.js';
2
+ import { requireAuth, isPluginActive, requireRole, AuthManager, logActivity } from './chunk-O7LMFJMZ.js';
3
+ import { PluginService, MigrationService } from './chunk-4MBTSUI6.js';
4
+ import { init_admin_layout_catalyst_template, renderDesignPage, renderCheckboxPage, renderFAQList, renderTestimonialsList, renderCodeExamplesList, renderAlert, renderTable, renderPagination, renderConfirmationDialog, getConfirmationDialogScript, renderAdminLayoutCatalyst, renderAdminLayout, adminLayoutV2, renderForm } from './chunk-YHLLVUJC.js';
5
+ import { QueryFilterBuilder, sanitizeInput, getCoreVersion, escapeHtml } from './chunk-YURRY22X.js';
6
6
  import { metricsTracker } from './chunk-FICTAGD4.js';
7
7
  import { Hono } from 'hono';
8
8
  import { cors } from 'hono/cors';
@@ -78,7 +78,7 @@ apiContentCrudRoutes.post("/", requireAuth(), async (c) => {
78
78
  title,
79
79
  JSON.stringify(data || {}),
80
80
  status || "draft",
81
- user?.userId || "unknown",
81
+ user?.userId || "system",
82
82
  now,
83
83
  now
84
84
  ).run();
@@ -671,6 +671,7 @@ apiMediaRoutes.post("/upload", async (c) => {
671
671
  size: mediaRecord.size,
672
672
  width: mediaRecord.width,
673
673
  height: mediaRecord.height,
674
+ r2_key: mediaRecord.r2_key,
674
675
  publicUrl: mediaRecord.public_url,
675
676
  thumbnailUrl: mediaRecord.thumbnail_url,
676
677
  uploadedAt: new Date(mediaRecord.uploaded_at * 1e3).toISOString()
@@ -797,6 +798,7 @@ apiMediaRoutes.post("/upload-multiple", async (c) => {
797
798
  size: mediaRecord.size,
798
799
  width: mediaRecord.width,
799
800
  height: mediaRecord.height,
801
+ r2_key: mediaRecord.r2_key,
800
802
  publicUrl: mediaRecord.public_url,
801
803
  thumbnailUrl: mediaRecord.thumbnail_url,
802
804
  uploadedAt: new Date(mediaRecord.uploaded_at * 1e3).toISOString()
@@ -1440,8 +1442,12 @@ adminApiRoutes.get("/activity", async (c) => {
1440
1442
  });
1441
1443
  var createCollectionSchema = z.object({
1442
1444
  name: z.string().min(1).max(255).regex(/^[a-z0-9_]+$/, "Must contain only lowercase letters, numbers, and underscores"),
1443
- display_name: z.string().min(1).max(255),
1445
+ displayName: z.string().min(1).max(255).optional(),
1446
+ display_name: z.string().min(1).max(255).optional(),
1444
1447
  description: z.string().optional()
1448
+ }).refine((data) => data.displayName || data.display_name, {
1449
+ message: "Either displayName or display_name is required",
1450
+ path: ["displayName"]
1445
1451
  });
1446
1452
  var updateCollectionSchema = z.object({
1447
1453
  display_name: z.string().min(1).max(255).optional(),
@@ -1528,15 +1534,16 @@ adminApiRoutes.get("/collections/:id", async (c) => {
1528
1534
  updated_at: Number(row.updated_at)
1529
1535
  }));
1530
1536
  return c.json({
1531
- data: {
1532
- ...collection,
1533
- is_active: collection.is_active === 1,
1534
- managed: collection.managed === 1,
1535
- schema: collection.schema ? JSON.parse(collection.schema) : null,
1536
- created_at: Number(collection.created_at),
1537
- updated_at: Number(collection.updated_at),
1538
- fields
1539
- }
1537
+ id: collection.id,
1538
+ name: collection.name,
1539
+ display_name: collection.display_name,
1540
+ description: collection.description,
1541
+ is_active: collection.is_active === 1,
1542
+ managed: collection.managed === 1,
1543
+ schema: collection.schema ? JSON.parse(collection.schema) : null,
1544
+ created_at: Number(collection.created_at),
1545
+ updated_at: Number(collection.updated_at),
1546
+ fields
1540
1547
  });
1541
1548
  } catch (error) {
1542
1549
  console.error("Error fetching collection:", error);
@@ -1545,7 +1552,16 @@ adminApiRoutes.get("/collections/:id", async (c) => {
1545
1552
  });
1546
1553
  adminApiRoutes.post("/collections", async (c) => {
1547
1554
  try {
1548
- const body = await c.req.json();
1555
+ const contentType = c.req.header("Content-Type");
1556
+ if (!contentType || !contentType.includes("application/json")) {
1557
+ return c.json({ error: "Content-Type must be application/json" }, 400);
1558
+ }
1559
+ let body;
1560
+ try {
1561
+ body = await c.req.json();
1562
+ } catch (e) {
1563
+ return c.json({ error: "Invalid JSON in request body" }, 400);
1564
+ }
1549
1565
  const validation = createCollectionSchema.safeParse(body);
1550
1566
  if (!validation.success) {
1551
1567
  return c.json({ error: "Validation failed", details: validation.error.errors }, 400);
@@ -1553,6 +1569,7 @@ adminApiRoutes.post("/collections", async (c) => {
1553
1569
  const validatedData = validation.data;
1554
1570
  const db = c.env.DB;
1555
1571
  const user = c.get("user");
1572
+ const displayName = validatedData.displayName || validatedData.display_name || "";
1556
1573
  const existingStmt = db.prepare("SELECT id FROM collections WHERE name = ?");
1557
1574
  const existing = await existingStmt.bind(validatedData.name).first();
1558
1575
  if (existing) {
@@ -1589,7 +1606,7 @@ adminApiRoutes.post("/collections", async (c) => {
1589
1606
  await insertStmt.bind(
1590
1607
  collectionId,
1591
1608
  validatedData.name,
1592
- validatedData.display_name,
1609
+ displayName,
1593
1610
  validatedData.description || null,
1594
1611
  JSON.stringify(basicSchema),
1595
1612
  1,
@@ -1604,13 +1621,11 @@ adminApiRoutes.post("/collections", async (c) => {
1604
1621
  console.error("Error clearing cache:", e);
1605
1622
  }
1606
1623
  return c.json({
1607
- data: {
1608
- id: collectionId,
1609
- name: validatedData.name,
1610
- display_name: validatedData.display_name,
1611
- description: validatedData.description,
1612
- created_at: now
1613
- }
1624
+ id: collectionId,
1625
+ name: validatedData.name,
1626
+ displayName,
1627
+ description: validatedData.description,
1628
+ created_at: now
1614
1629
  }, 201);
1615
1630
  } catch (error) {
1616
1631
  console.error("Error creating collection:", error);
@@ -1674,6 +1689,11 @@ adminApiRoutes.delete("/collections/:id", async (c) => {
1674
1689
  try {
1675
1690
  const id = c.req.param("id");
1676
1691
  const db = c.env.DB;
1692
+ const collectionStmt = db.prepare("SELECT name FROM collections WHERE id = ?");
1693
+ const collection = await collectionStmt.bind(id).first();
1694
+ if (!collection) {
1695
+ return c.json({ error: "Collection not found" }, 404);
1696
+ }
1677
1697
  const contentStmt = db.prepare("SELECT COUNT(*) as count FROM content WHERE collection_id = ?");
1678
1698
  const contentResult = await contentStmt.bind(id).first();
1679
1699
  if (contentResult && contentResult.count > 0) {
@@ -1681,17 +1701,13 @@ adminApiRoutes.delete("/collections/:id", async (c) => {
1681
1701
  error: `Cannot delete collection: it contains ${contentResult.count} content item(s). Delete all content first.`
1682
1702
  }, 400);
1683
1703
  }
1684
- const collectionStmt = db.prepare("SELECT name FROM collections WHERE id = ?");
1685
- const collection = await collectionStmt.bind(id).first();
1686
1704
  const deleteFieldsStmt = db.prepare("DELETE FROM content_fields WHERE collection_id = ?");
1687
1705
  await deleteFieldsStmt.bind(id).run();
1688
1706
  const deleteStmt = db.prepare("DELETE FROM collections WHERE id = ?");
1689
1707
  await deleteStmt.bind(id).run();
1690
1708
  try {
1691
1709
  await c.env.CACHE_KV.delete("cache:collections:all");
1692
- if (collection) {
1693
- await c.env.CACHE_KV.delete(`cache:collection:${collection.name}`);
1694
- }
1710
+ await c.env.CACHE_KV.delete(`cache:collection:${collection.name}`);
1695
1711
  } catch (e) {
1696
1712
  console.error("Error clearing cache:", e);
1697
1713
  }
@@ -5190,11 +5206,11 @@ adminContentRoutes.post("/", async (c) => {
5190
5206
  const now = Date.now();
5191
5207
  const insertStmt = db.prepare(`
5192
5208
  INSERT INTO content (
5193
- id, collection_id, slug, title, data, status,
5209
+ id, collection_id, slug, title, data, status,
5194
5210
  scheduled_publish_at, scheduled_unpublish_at,
5195
- meta_title, meta_description, author_id, created_at, updated_at
5211
+ meta_title, meta_description, author_id, created_by, created_at, updated_at
5196
5212
  )
5197
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
5213
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
5198
5214
  `);
5199
5215
  await insertStmt.bind(
5200
5216
  contentId,
@@ -5208,6 +5224,7 @@ adminContentRoutes.post("/", async (c) => {
5208
5224
  data.meta_title || null,
5209
5225
  data.meta_description || null,
5210
5226
  user?.userId || "unknown",
5227
+ user?.userId || "unknown",
5211
5228
  now,
5212
5229
  now
5213
5230
  ).run();
@@ -5509,10 +5526,10 @@ adminContentRoutes.post("/duplicate", async (c) => {
5509
5526
  originalData.title = `${originalData.title || "Untitled"} (Copy)`;
5510
5527
  const insertStmt = db.prepare(`
5511
5528
  INSERT INTO content (
5512
- id, collection_id, slug, title, data, status,
5513
- author_id, created_at, updated_at
5529
+ id, collection_id, slug, title, data, status,
5530
+ author_id, created_by, created_at, updated_at
5514
5531
  )
5515
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
5532
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
5516
5533
  `);
5517
5534
  await insertStmt.bind(
5518
5535
  newId,
@@ -5523,6 +5540,7 @@ adminContentRoutes.post("/duplicate", async (c) => {
5523
5540
  "draft",
5524
5541
  // Always start as draft
5525
5542
  user?.userId || "unknown",
5543
+ user?.userId || "unknown",
5526
5544
  now,
5527
5545
  now
5528
5546
  ).run();
@@ -5873,6 +5891,11 @@ var admin_content_default = adminContentRoutes;
5873
5891
 
5874
5892
  // src/templates/pages/admin-profile.template.ts
5875
5893
  init_admin_layout_catalyst_template();
5894
+ function renderAvatarImage(avatarUrl, firstName, lastName) {
5895
+ return `<div id="avatar-image-container" class="w-24 h-24 rounded-full mx-auto mb-4 overflow-hidden bg-gradient-to-br from-cyan-400 to-purple-400 flex items-center justify-center ring-4 ring-zinc-950/5 dark:ring-white/10">
5896
+ ${avatarUrl ? `<img src="${avatarUrl}" alt="Profile picture" class="w-full h-full object-cover">` : `<span class="text-2xl font-bold text-white">${firstName.charAt(0)}${lastName.charAt(0)}</span>`}
5897
+ </div>`;
5898
+ }
5876
5899
  function renderProfilePage(data) {
5877
5900
  const pageContent = `
5878
5901
  <div class="space-y-8">
@@ -6071,9 +6094,7 @@ function renderProfilePage(data) {
6071
6094
  <h3 class="text-base font-semibold text-zinc-950 dark:text-white mb-4">Profile Picture</h3>
6072
6095
 
6073
6096
  <div class="text-center">
6074
- <div class="w-24 h-24 rounded-full mx-auto mb-4 overflow-hidden bg-gradient-to-br from-cyan-400 to-purple-400 flex items-center justify-center ring-4 ring-zinc-950/5 dark:ring-white/10">
6075
- ${data.profile.avatar_url ? `<img src="${data.profile.avatar_url}" alt="Profile picture" class="w-full h-full object-cover">` : `<span class="text-2xl font-bold text-white">${data.profile.first_name.charAt(0)}${data.profile.last_name.charAt(0)}</span>`}
6076
- </div>
6097
+ ${renderAvatarImage(data.profile.avatar_url, data.profile.first_name, data.profile.last_name)}
6077
6098
 
6078
6099
  <form id="avatar-form" hx-post="/admin/profile/avatar" hx-target="#avatar-messages" hx-encoding="multipart/form-data">
6079
6100
  <input
@@ -7909,6 +7930,10 @@ userRoutes.post("/profile/avatar", async (c) => {
7909
7930
  WHERE id = ?
7910
7931
  `);
7911
7932
  await updateStmt.bind(avatarUrl, Date.now(), user.userId).run();
7933
+ const userStmt = db.prepare(`
7934
+ SELECT first_name, last_name FROM users WHERE id = ?
7935
+ `);
7936
+ const userData = await userStmt.bind(user.userId).first();
7912
7937
  await logActivity(
7913
7938
  db,
7914
7939
  user.userId,
@@ -7919,11 +7944,18 @@ userRoutes.post("/profile/avatar", async (c) => {
7919
7944
  c.req.header("x-forwarded-for") || c.req.header("cf-connecting-ip"),
7920
7945
  c.req.header("user-agent")
7921
7946
  );
7922
- return c.html(renderAlert2({
7947
+ const alertHtml = renderAlert2({
7923
7948
  type: "success",
7924
7949
  message: "Profile picture updated successfully!",
7925
7950
  dismissible: true
7926
- }));
7951
+ });
7952
+ const avatarUrlWithCache = `${avatarUrl}?t=${Date.now()}`;
7953
+ const avatarImageHtml = renderAvatarImage(avatarUrlWithCache, userData.first_name, userData.last_name);
7954
+ const avatarImageWithOob = avatarImageHtml.replace(
7955
+ 'id="avatar-image-container"',
7956
+ 'id="avatar-image-container" hx-swap-oob="true"'
7957
+ );
7958
+ return c.html(alertHtml + avatarImageWithOob);
7927
7959
  } catch (error) {
7928
7960
  console.error("Avatar upload error:", error);
7929
7961
  return c.html(renderAlert2({
@@ -9155,8 +9187,10 @@ function renderMediaLibraryPage(data) {
9155
9187
  </button>
9156
9188
  <button
9157
9189
  class="w-full text-left px-3 py-2 text-sm text-zinc-700 dark:text-zinc-300 hover:text-zinc-950 dark:hover:text-white hover:bg-zinc-50 dark:hover:bg-zinc-800/50 rounded-lg transition-colors"
9158
- hx-delete="/media/cleanup"
9190
+ hx-delete="/admin/media/cleanup"
9159
9191
  hx-confirm="Delete unused files?"
9192
+ hx-target="body"
9193
+ hx-swap="beforeend"
9160
9194
  >
9161
9195
  Cleanup Unused
9162
9196
  </button>
@@ -10759,6 +10793,87 @@ adminMediaRoutes.put("/:id", async (c) => {
10759
10793
  `);
10760
10794
  }
10761
10795
  });
10796
+ adminMediaRoutes.delete("/cleanup", requireRole("admin"), async (c) => {
10797
+ try {
10798
+ const db = c.env.DB;
10799
+ const allMediaStmt = db.prepare("SELECT id, r2_key, filename FROM media WHERE deleted_at IS NULL");
10800
+ const { results: allMedia } = await allMediaStmt.all();
10801
+ const contentStmt = db.prepare("SELECT data FROM content");
10802
+ const { results: contentRecords } = await contentStmt.all();
10803
+ const referencedUrls = /* @__PURE__ */ new Set();
10804
+ for (const record of contentRecords) {
10805
+ if (record.data) {
10806
+ const dataStr = typeof record.data === "string" ? record.data : JSON.stringify(record.data);
10807
+ const urlMatches = dataStr.matchAll(/\/files\/([^\s"',]+)/g);
10808
+ for (const match of urlMatches) {
10809
+ referencedUrls.add(match[1]);
10810
+ }
10811
+ }
10812
+ }
10813
+ const unusedFiles = allMedia.filter((file) => !referencedUrls.has(file.r2_key));
10814
+ if (unusedFiles.length === 0) {
10815
+ return c.html(html`
10816
+ <div class="bg-blue-100 border border-blue-400 text-blue-700 px-4 py-3 rounded">
10817
+ No unused media files found. All files are referenced in content.
10818
+ </div>
10819
+ <script>
10820
+ setTimeout(() => {
10821
+ window.location.href = '/admin/media?t=' + Date.now();
10822
+ }, 2000);
10823
+ </script>
10824
+ `);
10825
+ }
10826
+ let deletedCount = 0;
10827
+ const errors = [];
10828
+ for (const file of unusedFiles) {
10829
+ try {
10830
+ await c.env.MEDIA_BUCKET.delete(file.r2_key);
10831
+ const deleteStmt = db.prepare("UPDATE media SET deleted_at = ? WHERE id = ?");
10832
+ await deleteStmt.bind(Math.floor(Date.now() / 1e3), file.id).run();
10833
+ deletedCount++;
10834
+ } catch (error) {
10835
+ console.error(`Failed to delete ${file.filename}:`, error);
10836
+ errors.push({
10837
+ filename: file.filename,
10838
+ error: error instanceof Error ? error.message : "Unknown error"
10839
+ });
10840
+ }
10841
+ }
10842
+ return c.html(html`
10843
+ <div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4">
10844
+ Successfully cleaned up ${deletedCount} unused media file${deletedCount !== 1 ? "s" : ""}.
10845
+ ${errors.length > 0 ? html`
10846
+ <br><span class="text-sm">Failed to delete ${errors.length} file${errors.length !== 1 ? "s" : ""}.</span>
10847
+ ` : ""}
10848
+ </div>
10849
+
10850
+ ${errors.length > 0 ? html`
10851
+ <div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
10852
+ <p class="font-medium">Cleanup errors:</p>
10853
+ <ul class="list-disc list-inside mt-2 text-sm">
10854
+ ${errors.map((error) => html`
10855
+ <li>${error.filename}: ${error.error}</li>
10856
+ `)}
10857
+ </ul>
10858
+ </div>
10859
+ ` : ""}
10860
+
10861
+ <script>
10862
+ // Refresh media library after cleanup
10863
+ setTimeout(() => {
10864
+ window.location.href = '/admin/media?t=' + Date.now();
10865
+ }, 2500);
10866
+ </script>
10867
+ `);
10868
+ } catch (error) {
10869
+ console.error("Cleanup error:", error);
10870
+ return c.html(html`
10871
+ <div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
10872
+ Cleanup failed: ${error instanceof Error ? error.message : "Unknown error"}
10873
+ </div>
10874
+ `);
10875
+ }
10876
+ });
10762
10877
  adminMediaRoutes.delete("/:id", async (c) => {
10763
10878
  try {
10764
10879
  const user = c.get("user");
@@ -16322,14 +16437,14 @@ router.get("/stats", async (c) => {
16322
16437
  } catch (error) {
16323
16438
  console.error("Error fetching users count:", error);
16324
16439
  }
16325
- const html9 = renderStatsCards({
16440
+ const html8 = renderStatsCards({
16326
16441
  collections: collectionsCount,
16327
16442
  contentItems: contentCount,
16328
16443
  mediaFiles: mediaCount,
16329
16444
  users: usersCount,
16330
16445
  mediaSize
16331
16446
  });
16332
- return c.html(html9);
16447
+ return c.html(html8);
16333
16448
  } catch (error) {
16334
16449
  console.error("Error fetching stats:", error);
16335
16450
  return c.html('<div class="text-red-500">Failed to load statistics</div>');
@@ -16353,8 +16468,8 @@ router.get("/storage", async (c) => {
16353
16468
  } catch (error) {
16354
16469
  console.error("Error fetching media size:", error);
16355
16470
  }
16356
- const html9 = renderStorageUsage(databaseSize, mediaSize);
16357
- return c.html(html9);
16471
+ const html8 = renderStorageUsage(databaseSize, mediaSize);
16472
+ return c.html(html8);
16358
16473
  } catch (error) {
16359
16474
  console.error("Error fetching storage usage:", error);
16360
16475
  return c.html('<div class="text-red-500">Failed to load storage information</div>');
@@ -16403,12 +16518,12 @@ router.get("/recent-activity", async (c) => {
16403
16518
  user: userName
16404
16519
  };
16405
16520
  });
16406
- const html9 = renderRecentActivity(activities);
16407
- return c.html(html9);
16521
+ const html8 = renderRecentActivity(activities);
16522
+ return c.html(html8);
16408
16523
  } catch (error) {
16409
16524
  console.error("Error fetching recent activity:", error);
16410
- const html9 = renderRecentActivity([]);
16411
- return c.html(html9);
16525
+ const html8 = renderRecentActivity([]);
16526
+ return c.html(html8);
16412
16527
  }
16413
16528
  });
16414
16529
  router.get("/api/metrics", async (c) => {
@@ -16421,7 +16536,7 @@ router.get("/api/metrics", async (c) => {
16421
16536
  });
16422
16537
  router.get("/system-status", async (c) => {
16423
16538
  try {
16424
- const html9 = `
16539
+ const html8 = `
16425
16540
  <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
16426
16541
  <div class="relative group">
16427
16542
  <div class="absolute inset-0 bg-gradient-to-br from-blue-500/20 to-cyan-500/20 dark:from-blue-500/10 dark:to-cyan-500/10 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
@@ -16476,7 +16591,7 @@ router.get("/system-status", async (c) => {
16476
16591
  </div>
16477
16592
  </div>
16478
16593
  `;
16479
- return c.html(html9);
16594
+ return c.html(html8);
16480
16595
  } catch (error) {
16481
16596
  console.error("Error fetching system status:", error);
16482
16597
  return c.html('<div class="text-red-500">Failed to load system status</div>');
@@ -18232,31 +18347,52 @@ function renderSettingsPage(data) {
18232
18347
  // Initialize tab-specific features on page load
18233
18348
  const currentTab = '${activeTab}';
18234
18349
 
18235
- function saveAllSettings() {
18350
+ async function saveAllSettings() {
18236
18351
  // Collect all form data
18237
18352
  const formData = new FormData();
18238
-
18239
- // Get all form inputs
18240
- document.querySelectorAll('input, select, textarea').forEach(input => {
18353
+
18354
+ // Get all form inputs in the settings content area
18355
+ document.querySelectorAll('#settings-content input, #settings-content select, #settings-content textarea').forEach(input => {
18241
18356
  if (input.type === 'checkbox') {
18242
- formData.append(input.name, input.checked);
18357
+ formData.append(input.name, input.checked ? 'true' : 'false');
18243
18358
  } else if (input.name) {
18244
18359
  formData.append(input.name, input.value);
18245
18360
  }
18246
18361
  });
18247
-
18362
+
18248
18363
  // Show loading state
18249
18364
  const saveBtn = document.querySelector('button[onclick="saveAllSettings()"]');
18250
18365
  const originalText = saveBtn.innerHTML;
18251
- saveBtn.innerHTML = 'Saving...';
18366
+ saveBtn.innerHTML = '<svg class="animate-spin -ml-0.5 mr-1.5 h-5 w-5 inline" fill="none" stroke="currentColor" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path></svg>Saving...';
18252
18367
  saveBtn.disabled = true;
18253
-
18254
- // Simulate save (replace with actual API call)
18255
- setTimeout(() => {
18368
+
18369
+ try {
18370
+ // Determine which endpoint to call based on current tab
18371
+ let endpoint = '/admin/settings/general';
18372
+ if (currentTab === 'general') {
18373
+ endpoint = '/admin/settings/general';
18374
+ }
18375
+ // Add more endpoints for other tabs when implemented
18376
+
18377
+ const response = await fetch(endpoint, {
18378
+ method: 'POST',
18379
+ body: formData
18380
+ });
18381
+
18382
+ const result = await response.json();
18383
+
18384
+ if (result.success) {
18385
+ showNotification(result.message || 'Settings saved successfully!', 'success');
18386
+ } else {
18387
+ showNotification(result.error || 'Failed to save settings', 'error');
18388
+ }
18389
+ } catch (error) {
18390
+ console.error('Error saving settings:', error);
18391
+ showNotification('Failed to save settings. Please try again.', 'error');
18392
+ } finally {
18256
18393
  saveBtn.innerHTML = originalText;
18257
18394
  saveBtn.disabled = false;
18258
- showNotification('Settings saved successfully!', 'success');
18259
- }, 1000);
18395
+ }
18260
18396
  }
18261
18397
 
18262
18398
  function resetSettings() {
@@ -19661,15 +19797,20 @@ function getMockSettings(user) {
19661
19797
  adminSettingsRoutes.get("/", (c) => {
19662
19798
  return c.redirect("/admin/settings/general");
19663
19799
  });
19664
- adminSettingsRoutes.get("/general", (c) => {
19800
+ adminSettingsRoutes.get("/general", async (c) => {
19665
19801
  const user = c.get("user");
19802
+ const db = c.env.DB;
19803
+ const settingsService = new SettingsService(db);
19804
+ const generalSettings = await settingsService.getGeneralSettings(user?.email);
19805
+ const mockSettings = getMockSettings(user);
19806
+ mockSettings.general = generalSettings;
19666
19807
  const pageData = {
19667
19808
  user: user ? {
19668
19809
  name: user.email,
19669
19810
  email: user.email,
19670
19811
  role: user.role
19671
19812
  } : void 0,
19672
- settings: getMockSettings(user),
19813
+ settings: mockSettings,
19673
19814
  activeTab: "general",
19674
19815
  version: c.get("appVersion")
19675
19816
  };
@@ -19950,28 +20091,55 @@ adminSettingsRoutes.post("/api/database-tools/truncate", async (c) => {
19950
20091
  }, 500);
19951
20092
  }
19952
20093
  });
19953
- adminSettingsRoutes.post("/", async (c) => {
20094
+ adminSettingsRoutes.post("/general", async (c) => {
19954
20095
  try {
20096
+ const user = c.get("user");
20097
+ if (!user || user.role !== "admin") {
20098
+ return c.json({
20099
+ success: false,
20100
+ error: "Unauthorized. Admin access required."
20101
+ }, 403);
20102
+ }
19955
20103
  const formData = await c.req.formData();
19956
- return c.html(html`
19957
- <div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded">
19958
- Settings saved successfully!
19959
- <script>
19960
- setTimeout(() => {
19961
- showNotification('Settings saved successfully!', 'success');
19962
- }, 100);
19963
- </script>
19964
- </div>
19965
- `);
20104
+ const db = c.env.DB;
20105
+ const settingsService = new SettingsService(db);
20106
+ const settings = {
20107
+ siteName: formData.get("siteName"),
20108
+ siteDescription: formData.get("siteDescription"),
20109
+ adminEmail: formData.get("adminEmail"),
20110
+ timezone: formData.get("timezone"),
20111
+ language: formData.get("language"),
20112
+ maintenanceMode: formData.get("maintenanceMode") === "true"
20113
+ };
20114
+ if (!settings.siteName || !settings.siteDescription) {
20115
+ return c.json({
20116
+ success: false,
20117
+ error: "Site name and description are required"
20118
+ }, 400);
20119
+ }
20120
+ const success = await settingsService.saveGeneralSettings(settings);
20121
+ if (success) {
20122
+ return c.json({
20123
+ success: true,
20124
+ message: "General settings saved successfully!"
20125
+ });
20126
+ } else {
20127
+ return c.json({
20128
+ success: false,
20129
+ error: "Failed to save settings"
20130
+ }, 500);
20131
+ }
19966
20132
  } catch (error) {
19967
- console.error("Error saving settings:", error);
19968
- return c.html(html`
19969
- <div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
19970
- Failed to save settings. Please try again.
19971
- </div>
19972
- `);
20133
+ console.error("Error saving general settings:", error);
20134
+ return c.json({
20135
+ success: false,
20136
+ error: "Failed to save settings. Please try again."
20137
+ }, 500);
19973
20138
  }
19974
20139
  });
20140
+ adminSettingsRoutes.post("/", async (c) => {
20141
+ return c.redirect("/admin/settings/general");
20142
+ });
19975
20143
 
19976
20144
  // src/routes/index.ts
19977
20145
  var ROUTES_INFO = {
@@ -20002,5 +20170,5 @@ var ROUTES_INFO = {
20002
20170
  };
20003
20171
 
20004
20172
  export { ROUTES_INFO, adminCheckboxRoutes, adminCollectionsRoutes, adminDesignRoutes, adminLogsRoutes, adminMediaRoutes, adminPluginRoutes, adminSettingsRoutes, admin_api_default, admin_code_examples_default, admin_content_default, admin_faq_default, admin_testimonials_default, api_content_crud_default, api_default, api_media_default, api_system_default, auth_default, router, userRoutes };
20005
- //# sourceMappingURL=chunk-CQ2VMJQO.js.map
20006
- //# sourceMappingURL=chunk-CQ2VMJQO.js.map
20173
+ //# sourceMappingURL=chunk-JB2NUJJ5.js.map
20174
+ //# sourceMappingURL=chunk-JB2NUJJ5.js.map