@sonicjs-cms/core 2.4.0 → 2.5.0

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 (73) hide show
  1. package/dist/{chunk-CPXAVWCU.js → chunk-3YUHXWSG.js} +278 -3
  2. package/dist/chunk-3YUHXWSG.js.map +1 -0
  3. package/dist/chunk-AI2JJIJX.cjs +211 -0
  4. package/dist/chunk-AI2JJIJX.cjs.map +1 -0
  5. package/dist/{chunk-VNCYCH3H.js → chunk-BHNDALCA.js} +56 -4
  6. package/dist/chunk-BHNDALCA.js.map +1 -0
  7. package/dist/{chunk-2MI3LZFH.cjs → chunk-I4V3VZWF.cjs} +46 -2
  8. package/dist/chunk-I4V3VZWF.cjs.map +1 -0
  9. package/dist/{chunk-DTLB6UIH.cjs → chunk-LWG2MWDA.cjs} +280 -2
  10. package/dist/chunk-LWG2MWDA.cjs.map +1 -0
  11. package/dist/{chunk-FT6NBHNX.js → chunk-OJZ45OJD.js} +507 -275
  12. package/dist/chunk-OJZ45OJD.js.map +1 -0
  13. package/dist/chunk-QDBNW7KQ.js +209 -0
  14. package/dist/chunk-QDBNW7KQ.js.map +1 -0
  15. package/dist/{chunk-DXM575E2.js → chunk-TJTWRO4G.js} +5 -5
  16. package/dist/chunk-TJTWRO4G.js.map +1 -0
  17. package/dist/{chunk-A4SVOGG6.cjs → chunk-UAQL2VWX.cjs} +591 -360
  18. package/dist/chunk-UAQL2VWX.cjs.map +1 -0
  19. package/dist/{chunk-D2NLCPO2.js → chunk-VEL7QRYI.js} +46 -2
  20. package/dist/chunk-VEL7QRYI.js.map +1 -0
  21. package/dist/{chunk-7I5INVNR.cjs → chunk-YYV3XQOQ.cjs} +6 -6
  22. package/dist/chunk-YYV3XQOQ.cjs.map +1 -0
  23. package/dist/{chunk-FYEDK7K7.cjs → chunk-ZWV3EBZ7.cjs} +58 -3
  24. package/dist/chunk-ZWV3EBZ7.cjs.map +1 -0
  25. package/dist/{collection-config-FLlGtsh9.d.cts → collection-config-B6gMPunn.d.cts} +9 -1
  26. package/dist/{collection-config-FLlGtsh9.d.ts → collection-config-B6gMPunn.d.ts} +9 -1
  27. package/dist/index.cjs +90 -87
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +4 -4
  30. package/dist/index.d.ts +4 -4
  31. package/dist/index.js +12 -9
  32. package/dist/index.js.map +1 -1
  33. package/dist/middleware.cjs +23 -23
  34. package/dist/middleware.js +2 -2
  35. package/dist/migrations-NIEUFG44.cjs +13 -0
  36. package/dist/{migrations-32QAYLWJ.cjs.map → migrations-NIEUFG44.cjs.map} +1 -1
  37. package/dist/migrations-TGZKJKV4.js +4 -0
  38. package/dist/{migrations-57ZHBQ4X.js.map → migrations-TGZKJKV4.js.map} +1 -1
  39. package/dist/{plugin-bootstrap-C0E3jdz-.d.cts → plugin-bootstrap-SHsdjE6X.d.cts} +1 -1
  40. package/dist/{plugin-bootstrap-CDh0JHtW.d.ts → plugin-bootstrap-dYhD9fQR.d.ts} +1 -1
  41. package/dist/plugin-manager-Baa6xXqB.d.ts +328 -0
  42. package/dist/plugin-manager-vBal9Zip.d.cts +328 -0
  43. package/dist/plugins.cjs +20 -7
  44. package/dist/plugins.d.cts +53 -310
  45. package/dist/plugins.d.ts +53 -310
  46. package/dist/plugins.js +2 -1
  47. package/dist/routes.cjs +25 -24
  48. package/dist/routes.js +5 -4
  49. package/dist/services.cjs +2 -2
  50. package/dist/services.d.cts +2 -2
  51. package/dist/services.d.ts +2 -2
  52. package/dist/services.js +1 -1
  53. package/dist/types.d.cts +1 -1
  54. package/dist/types.d.ts +1 -1
  55. package/dist/utils.cjs +23 -11
  56. package/dist/utils.d.cts +38 -1
  57. package/dist/utils.d.ts +38 -1
  58. package/dist/utils.js +1 -1
  59. package/migrations/027_fix_slug_field_type.sql +18 -0
  60. package/migrations/028_fix_slug_field_type_in_schemas.sql +30 -0
  61. package/package.json +2 -1
  62. package/dist/chunk-2MI3LZFH.cjs.map +0 -1
  63. package/dist/chunk-7I5INVNR.cjs.map +0 -1
  64. package/dist/chunk-A4SVOGG6.cjs.map +0 -1
  65. package/dist/chunk-CPXAVWCU.js.map +0 -1
  66. package/dist/chunk-D2NLCPO2.js.map +0 -1
  67. package/dist/chunk-DTLB6UIH.cjs.map +0 -1
  68. package/dist/chunk-DXM575E2.js.map +0 -1
  69. package/dist/chunk-FT6NBHNX.js.map +0 -1
  70. package/dist/chunk-FYEDK7K7.cjs.map +0 -1
  71. package/dist/chunk-VNCYCH3H.js.map +0 -1
  72. package/dist/migrations-32QAYLWJ.cjs +0 -13
  73. package/dist/migrations-57ZHBQ4X.js +0 -4
@@ -1,11 +1,12 @@
1
1
  'use strict';
2
2
 
3
3
  var chunk7FOAMNTI_cjs = require('./chunk-7FOAMNTI.cjs');
4
- var chunk7I5INVNR_cjs = require('./chunk-7I5INVNR.cjs');
4
+ var chunkYYV3XQOQ_cjs = require('./chunk-YYV3XQOQ.cjs');
5
5
  var chunkILZ3DP4I_cjs = require('./chunk-ILZ3DP4I.cjs');
6
- var chunk2MI3LZFH_cjs = require('./chunk-2MI3LZFH.cjs');
6
+ var chunkI4V3VZWF_cjs = require('./chunk-I4V3VZWF.cjs');
7
7
  var chunkAZLU3ROK_cjs = require('./chunk-AZLU3ROK.cjs');
8
- var chunkFYEDK7K7_cjs = require('./chunk-FYEDK7K7.cjs');
8
+ var chunkAI2JJIJX_cjs = require('./chunk-AI2JJIJX.cjs');
9
+ var chunkZWV3EBZ7_cjs = require('./chunk-ZWV3EBZ7.cjs');
9
10
  var chunkRCQ2HIQD_cjs = require('./chunk-RCQ2HIQD.cjs');
10
11
  var hono = require('hono');
11
12
  var cors = require('hono/cors');
@@ -16,6 +17,37 @@ var html = require('hono/html');
16
17
  // src/schemas/index.ts
17
18
  var schemaDefinitions = [];
18
19
  var apiContentCrudRoutes = new hono.Hono();
20
+ apiContentCrudRoutes.get("/check-slug", async (c) => {
21
+ try {
22
+ const db = c.env.DB;
23
+ const collectionId = c.req.query("collectionId");
24
+ const slug = c.req.query("slug");
25
+ const excludeId = c.req.query("excludeId");
26
+ if (!collectionId || !slug) {
27
+ return c.json({ error: "collectionId and slug are required" }, 400);
28
+ }
29
+ let query = "SELECT id FROM content WHERE collection_id = ? AND slug = ?";
30
+ const params = [collectionId, slug];
31
+ if (excludeId) {
32
+ query += " AND id != ?";
33
+ params.push(excludeId);
34
+ }
35
+ const existing = await db.prepare(query).bind(...params).first();
36
+ if (existing) {
37
+ return c.json({
38
+ available: false,
39
+ message: "This URL slug is already in use in this collection"
40
+ });
41
+ }
42
+ return c.json({ available: true });
43
+ } catch (error) {
44
+ console.error("Error checking slug:", error);
45
+ return c.json({
46
+ error: "Failed to check slug availability",
47
+ details: error instanceof Error ? error.message : String(error)
48
+ }, 500);
49
+ }
50
+ });
19
51
  apiContentCrudRoutes.get("/:id", async (c) => {
20
52
  try {
21
53
  const id = c.req.param("id");
@@ -44,7 +76,7 @@ apiContentCrudRoutes.get("/:id", async (c) => {
44
76
  }, 500);
45
77
  }
46
78
  });
47
- apiContentCrudRoutes.post("/", chunk7I5INVNR_cjs.requireAuth(), async (c) => {
79
+ apiContentCrudRoutes.post("/", chunkYYV3XQOQ_cjs.requireAuth(), async (c) => {
48
80
  try {
49
81
  const db = c.env.DB;
50
82
  const user = c.get("user");
@@ -110,7 +142,7 @@ apiContentCrudRoutes.post("/", chunk7I5INVNR_cjs.requireAuth(), async (c) => {
110
142
  }, 500);
111
143
  }
112
144
  });
113
- apiContentCrudRoutes.put("/:id", chunk7I5INVNR_cjs.requireAuth(), async (c) => {
145
+ apiContentCrudRoutes.put("/:id", chunkYYV3XQOQ_cjs.requireAuth(), async (c) => {
114
146
  try {
115
147
  const id = c.req.param("id");
116
148
  const db = c.env.DB;
@@ -174,7 +206,7 @@ apiContentCrudRoutes.put("/:id", chunk7I5INVNR_cjs.requireAuth(), async (c) => {
174
206
  }, 500);
175
207
  }
176
208
  });
177
- apiContentCrudRoutes.delete("/:id", chunk7I5INVNR_cjs.requireAuth(), async (c) => {
209
+ apiContentCrudRoutes.delete("/:id", chunkYYV3XQOQ_cjs.requireAuth(), async (c) => {
178
210
  try {
179
211
  const id = c.req.param("id");
180
212
  const db = c.env.DB;
@@ -210,7 +242,7 @@ apiRoutes.use("*", async (c, next) => {
210
242
  c.header("X-Response-Time", `${totalTime}ms`);
211
243
  });
212
244
  apiRoutes.use("*", async (c, next) => {
213
- const cacheEnabled = await chunk7I5INVNR_cjs.isPluginActive(c.env.DB, "core-cache");
245
+ const cacheEnabled = await chunkYYV3XQOQ_cjs.isPluginActive(c.env.DB, "core-cache");
214
246
  c.set("cacheEnabled", cacheEnabled);
215
247
  await next();
216
248
  });
@@ -335,12 +367,12 @@ apiRoutes.get("/content", async (c) => {
335
367
  });
336
368
  }
337
369
  }
338
- const filter = chunkFYEDK7K7_cjs.QueryFilterBuilder.parseFromQuery(queryParams);
370
+ const filter = chunkZWV3EBZ7_cjs.QueryFilterBuilder.parseFromQuery(queryParams);
339
371
  if (!filter.limit) {
340
372
  filter.limit = 50;
341
373
  }
342
374
  filter.limit = Math.min(filter.limit, 1e3);
343
- const builder3 = new chunkFYEDK7K7_cjs.QueryFilterBuilder();
375
+ const builder3 = new chunkZWV3EBZ7_cjs.QueryFilterBuilder();
344
376
  const queryResult = builder3.build("content", filter);
345
377
  if (queryResult.errors.length > 0) {
346
378
  return c.json({
@@ -427,7 +459,7 @@ apiRoutes.get("/collections/:collection/content", async (c) => {
427
459
  if (!collectionResult) {
428
460
  return c.json({ error: "Collection not found" }, 404);
429
461
  }
430
- const filter = chunkFYEDK7K7_cjs.QueryFilterBuilder.parseFromQuery(queryParams);
462
+ const filter = chunkZWV3EBZ7_cjs.QueryFilterBuilder.parseFromQuery(queryParams);
431
463
  if (!filter.where) {
432
464
  filter.where = { and: [] };
433
465
  }
@@ -443,7 +475,7 @@ apiRoutes.get("/collections/:collection/content", async (c) => {
443
475
  filter.limit = 50;
444
476
  }
445
477
  filter.limit = Math.min(filter.limit, 1e3);
446
- const builder3 = new chunkFYEDK7K7_cjs.QueryFilterBuilder();
478
+ const builder3 = new chunkZWV3EBZ7_cjs.QueryFilterBuilder();
447
479
  const queryResult = builder3.build("content", filter);
448
480
  if (queryResult.errors.length > 0) {
449
481
  return c.json({
@@ -568,7 +600,7 @@ var fileValidationSchema = zod.z.object({
568
600
  // 50MB max
569
601
  });
570
602
  var apiMediaRoutes = new hono.Hono();
571
- apiMediaRoutes.use("*", chunk7I5INVNR_cjs.requireAuth());
603
+ apiMediaRoutes.use("*", chunkYYV3XQOQ_cjs.requireAuth());
572
604
  apiMediaRoutes.post("/upload", async (c) => {
573
605
  try {
574
606
  const user = c.get("user");
@@ -1312,8 +1344,8 @@ apiSystemRoutes.get("/env", (c) => {
1312
1344
  });
1313
1345
  var api_system_default = apiSystemRoutes;
1314
1346
  var adminApiRoutes = new hono.Hono();
1315
- adminApiRoutes.use("*", chunk7I5INVNR_cjs.requireAuth());
1316
- adminApiRoutes.use("*", chunk7I5INVNR_cjs.requireRole(["admin", "editor"]));
1347
+ adminApiRoutes.use("*", chunkYYV3XQOQ_cjs.requireAuth());
1348
+ adminApiRoutes.use("*", chunkYYV3XQOQ_cjs.requireRole(["admin", "editor"]));
1317
1349
  adminApiRoutes.get("/stats", async (c) => {
1318
1350
  try {
1319
1351
  const db = c.env.DB;
@@ -1722,7 +1754,7 @@ adminApiRoutes.delete("/collections/:id", async (c) => {
1722
1754
  });
1723
1755
  adminApiRoutes.get("/migrations/status", async (c) => {
1724
1756
  try {
1725
- const { MigrationService: MigrationService2 } = await import('./migrations-32QAYLWJ.cjs');
1757
+ const { MigrationService: MigrationService2 } = await import('./migrations-NIEUFG44.cjs');
1726
1758
  const db = c.env.DB;
1727
1759
  const migrationService = new MigrationService2(db);
1728
1760
  const status = await migrationService.getMigrationStatus();
@@ -1747,7 +1779,7 @@ adminApiRoutes.post("/migrations/run", async (c) => {
1747
1779
  error: "Unauthorized. Admin access required."
1748
1780
  }, 403);
1749
1781
  }
1750
- const { MigrationService: MigrationService2 } = await import('./migrations-32QAYLWJ.cjs');
1782
+ const { MigrationService: MigrationService2 } = await import('./migrations-NIEUFG44.cjs');
1751
1783
  const db = c.env.DB;
1752
1784
  const migrationService = new MigrationService2(db);
1753
1785
  const result = await migrationService.runPendingMigrations();
@@ -1766,7 +1798,7 @@ adminApiRoutes.post("/migrations/run", async (c) => {
1766
1798
  });
1767
1799
  adminApiRoutes.get("/migrations/validate", async (c) => {
1768
1800
  try {
1769
- const { MigrationService: MigrationService2 } = await import('./migrations-32QAYLWJ.cjs');
1801
+ const { MigrationService: MigrationService2 } = await import('./migrations-NIEUFG44.cjs');
1770
1802
  const db = c.env.DB;
1771
1803
  const migrationService = new MigrationService2(db);
1772
1804
  const validation = await migrationService.validateSchema();
@@ -2289,7 +2321,7 @@ authRoutes.post(
2289
2321
  if (existingUser) {
2290
2322
  return c.json({ error: "User with this email or username already exists" }, 400);
2291
2323
  }
2292
- const passwordHash = await chunk7I5INVNR_cjs.AuthManager.hashPassword(password);
2324
+ const passwordHash = await chunkYYV3XQOQ_cjs.AuthManager.hashPassword(password);
2293
2325
  const userId = crypto.randomUUID();
2294
2326
  const now = /* @__PURE__ */ new Date();
2295
2327
  await db.prepare(`
@@ -2309,7 +2341,7 @@ authRoutes.post(
2309
2341
  now.getTime(),
2310
2342
  now.getTime()
2311
2343
  ).run();
2312
- const token = await chunk7I5INVNR_cjs.AuthManager.generateToken(userId, normalizedEmail, "viewer");
2344
+ const token = await chunkYYV3XQOQ_cjs.AuthManager.generateToken(userId, normalizedEmail, "viewer");
2313
2345
  cookie.setCookie(c, "auth_token", token, {
2314
2346
  httpOnly: true,
2315
2347
  secure: true,
@@ -2362,11 +2394,11 @@ authRoutes.post("/login", async (c) => {
2362
2394
  if (!user) {
2363
2395
  return c.json({ error: "Invalid email or password" }, 401);
2364
2396
  }
2365
- const isValidPassword = await chunk7I5INVNR_cjs.AuthManager.verifyPassword(password, user.password_hash);
2397
+ const isValidPassword = await chunkYYV3XQOQ_cjs.AuthManager.verifyPassword(password, user.password_hash);
2366
2398
  if (!isValidPassword) {
2367
2399
  return c.json({ error: "Invalid email or password" }, 401);
2368
2400
  }
2369
- const token = await chunk7I5INVNR_cjs.AuthManager.generateToken(user.id, user.email, user.role);
2401
+ const token = await chunkYYV3XQOQ_cjs.AuthManager.generateToken(user.id, user.email, user.role);
2370
2402
  cookie.setCookie(c, "auth_token", token, {
2371
2403
  httpOnly: true,
2372
2404
  secure: true,
@@ -2415,7 +2447,7 @@ authRoutes.get("/logout", (c) => {
2415
2447
  });
2416
2448
  return c.redirect("/auth/login?message=You have been logged out successfully");
2417
2449
  });
2418
- authRoutes.get("/me", chunk7I5INVNR_cjs.requireAuth(), async (c) => {
2450
+ authRoutes.get("/me", chunkYYV3XQOQ_cjs.requireAuth(), async (c) => {
2419
2451
  try {
2420
2452
  const user = c.get("user");
2421
2453
  if (!user) {
@@ -2432,13 +2464,13 @@ authRoutes.get("/me", chunk7I5INVNR_cjs.requireAuth(), async (c) => {
2432
2464
  return c.json({ error: "Failed to get user" }, 500);
2433
2465
  }
2434
2466
  });
2435
- authRoutes.post("/refresh", chunk7I5INVNR_cjs.requireAuth(), async (c) => {
2467
+ authRoutes.post("/refresh", chunkYYV3XQOQ_cjs.requireAuth(), async (c) => {
2436
2468
  try {
2437
2469
  const user = c.get("user");
2438
2470
  if (!user) {
2439
2471
  return c.json({ error: "Not authenticated" }, 401);
2440
2472
  }
2441
- const token = await chunk7I5INVNR_cjs.AuthManager.generateToken(user.userId, user.email, user.role);
2473
+ const token = await chunkYYV3XQOQ_cjs.AuthManager.generateToken(user.userId, user.email, user.role);
2442
2474
  cookie.setCookie(c, "auth_token", token, {
2443
2475
  httpOnly: true,
2444
2476
  secure: true,
@@ -2498,7 +2530,7 @@ authRoutes.post("/register/form", async (c) => {
2498
2530
  </div>
2499
2531
  `);
2500
2532
  }
2501
- const passwordHash = await chunk7I5INVNR_cjs.AuthManager.hashPassword(password);
2533
+ const passwordHash = await chunkYYV3XQOQ_cjs.AuthManager.hashPassword(password);
2502
2534
  const role = isFirstUser ? "admin" : "viewer";
2503
2535
  const userId = crypto.randomUUID();
2504
2536
  const now = /* @__PURE__ */ new Date();
@@ -2521,7 +2553,7 @@ authRoutes.post("/register/form", async (c) => {
2521
2553
  if (isFirstUser) {
2522
2554
  setAdminExists();
2523
2555
  }
2524
- const token = await chunk7I5INVNR_cjs.AuthManager.generateToken(userId, normalizedEmail, role);
2556
+ const token = await chunkYYV3XQOQ_cjs.AuthManager.generateToken(userId, normalizedEmail, role);
2525
2557
  cookie.setCookie(c, "auth_token", token, {
2526
2558
  httpOnly: true,
2527
2559
  secure: false,
@@ -2573,7 +2605,7 @@ authRoutes.post("/login/form", async (c) => {
2573
2605
  </div>
2574
2606
  `);
2575
2607
  }
2576
- const isValidPassword = await chunk7I5INVNR_cjs.AuthManager.verifyPassword(password, user.password_hash);
2608
+ const isValidPassword = await chunkYYV3XQOQ_cjs.AuthManager.verifyPassword(password, user.password_hash);
2577
2609
  if (!isValidPassword) {
2578
2610
  return c.html(html.html`
2579
2611
  <div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
@@ -2581,7 +2613,7 @@ authRoutes.post("/login/form", async (c) => {
2581
2613
  </div>
2582
2614
  `);
2583
2615
  }
2584
- const token = await chunk7I5INVNR_cjs.AuthManager.generateToken(user.id, user.email, user.role);
2616
+ const token = await chunkYYV3XQOQ_cjs.AuthManager.generateToken(user.id, user.email, user.role);
2585
2617
  cookie.setCookie(c, "auth_token", token, {
2586
2618
  httpOnly: true,
2587
2619
  secure: false,
@@ -2640,7 +2672,7 @@ authRoutes.post("/seed-admin", async (c) => {
2640
2672
  `).run();
2641
2673
  const existingAdmin = await db.prepare("SELECT id FROM users WHERE email = ? OR username = ?").bind("admin@sonicjs.com", "admin").first();
2642
2674
  if (existingAdmin) {
2643
- const passwordHash2 = await chunk7I5INVNR_cjs.AuthManager.hashPassword("sonicjs!");
2675
+ const passwordHash2 = await chunkYYV3XQOQ_cjs.AuthManager.hashPassword("sonicjs!");
2644
2676
  await db.prepare("UPDATE users SET password_hash = ?, updated_at = ? WHERE id = ?").bind(passwordHash2, Date.now(), existingAdmin.id).run();
2645
2677
  setAdminExists();
2646
2678
  return c.json({
@@ -2653,7 +2685,7 @@ authRoutes.post("/seed-admin", async (c) => {
2653
2685
  }
2654
2686
  });
2655
2687
  }
2656
- const passwordHash = await chunk7I5INVNR_cjs.AuthManager.hashPassword("sonicjs!");
2688
+ const passwordHash = await chunkYYV3XQOQ_cjs.AuthManager.hashPassword("sonicjs!");
2657
2689
  const userId = "admin-user-id";
2658
2690
  const now = Date.now();
2659
2691
  const adminEmail = "admin@sonicjs.com".toLowerCase();
@@ -2874,7 +2906,7 @@ authRoutes.post("/accept-invitation", async (c) => {
2874
2906
  if (existingUsername) {
2875
2907
  return c.json({ error: "Username is already taken" }, 400);
2876
2908
  }
2877
- const passwordHash = await chunk7I5INVNR_cjs.AuthManager.hashPassword(password);
2909
+ const passwordHash = await chunkYYV3XQOQ_cjs.AuthManager.hashPassword(password);
2878
2910
  const updateStmt = db.prepare(`
2879
2911
  UPDATE users SET
2880
2912
  username = ?,
@@ -2893,7 +2925,7 @@ authRoutes.post("/accept-invitation", async (c) => {
2893
2925
  Date.now(),
2894
2926
  invitedUser.id
2895
2927
  ).run();
2896
- const authToken = await chunk7I5INVNR_cjs.AuthManager.generateToken(invitedUser.id, invitedUser.email, invitedUser.role);
2928
+ const authToken = await chunkYYV3XQOQ_cjs.AuthManager.generateToken(invitedUser.id, invitedUser.email, invitedUser.role);
2897
2929
  cookie.setCookie(c, "auth_token", authToken, {
2898
2930
  httpOnly: true,
2899
2931
  secure: true,
@@ -3123,7 +3155,7 @@ authRoutes.post("/reset-password", async (c) => {
3123
3155
  if (Date.now() > user.password_reset_expires) {
3124
3156
  return c.json({ error: "Reset token has expired" }, 400);
3125
3157
  }
3126
- const newPasswordHash = await chunk7I5INVNR_cjs.AuthManager.hashPassword(password);
3158
+ const newPasswordHash = await chunkYYV3XQOQ_cjs.AuthManager.hashPassword(password);
3127
3159
  try {
3128
3160
  const historyStmt = db.prepare(`
3129
3161
  INSERT INTO password_history (id, user_id, password_hash, created_at)
@@ -3385,7 +3417,7 @@ chunkAZLU3ROK_cjs.init_admin_layout_catalyst_template();
3385
3417
 
3386
3418
  // src/templates/components/dynamic-field.template.ts
3387
3419
  function renderDynamicField(field, options = {}) {
3388
- const { value = "", errors = [], disabled = false, className = "", pluginStatuses = {} } = options;
3420
+ const { value = "", errors = [], disabled = false, className = "", pluginStatuses = {}, collectionId = "", contentId = "" } = options;
3389
3421
  const opts = field.field_options || {};
3390
3422
  const required = field.is_required ? "required" : "";
3391
3423
  const baseClasses = `w-full rounded-lg px-3 py-2 text-sm text-zinc-950 dark:text-white bg-white dark:bg-zinc-800 shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow ${className}`;
@@ -3638,67 +3670,171 @@ function renderDynamicField(field, options = {}) {
3638
3670
  `;
3639
3671
  break;
3640
3672
  case "slug":
3641
- let slugPattern = opts.pattern || "^[a-z0-9-]+$";
3642
- let slugHelp = '<p class="mt-2 text-xs text-zinc-500 dark:text-zinc-400">Use lowercase letters, numbers, and hyphens only</p>';
3643
- slugHelp += `<button type="button" class="mt-1 text-xs text-cyan-600 dark:text-cyan-400 hover:text-cyan-700 dark:hover:text-cyan-300" onclick="generateSlugFromTitle('\${fieldId}')">Generate from title</button>`;
3673
+ const slugPattern = opts.pattern || "^[a-z0-9-]+$";
3674
+ const collectionIdValue = collectionId || opts.collectionId || "";
3675
+ const contentIdValue = contentId || opts.contentId || "";
3676
+ const isEditMode = !!value;
3644
3677
  fieldHTML = `
3645
- <input
3646
- type="text"
3647
- id="${fieldId}"
3648
- name="${fieldName}"
3649
- value="${escapeHtml2(value)}"
3650
- placeholder="${opts.placeholder || "url-friendly-slug"}"
3651
- maxlength="${opts.maxLength || ""}"
3652
- data-pattern="${slugPattern}"
3653
- class="${baseClasses} ${errorClasses}"
3654
- ${required}
3655
- ${disabled ? "disabled" : ""}
3656
- >
3657
- ${slugHelp}
3678
+ <div class="slug-field-container">
3679
+ <input
3680
+ type="text"
3681
+ id="${fieldId}"
3682
+ name="${fieldName}"
3683
+ value="${escapeHtml2(value)}"
3684
+ placeholder="${opts.placeholder || "url-friendly-slug"}"
3685
+ maxlength="${opts.maxLength || 100}"
3686
+ data-pattern="${slugPattern}"
3687
+ data-collection-id="${collectionIdValue}"
3688
+ data-content-id="${contentIdValue}"
3689
+ data-is-edit-mode="${isEditMode}"
3690
+ class="${baseClasses} ${errorClasses}"
3691
+ ${required}
3692
+ ${disabled ? "disabled" : ""}
3693
+ >
3694
+ <div id="${fieldId}-status" class="slug-status mt-1 text-sm min-h-[20px]"></div>
3695
+ <button
3696
+ type="button"
3697
+ class="regenerate-slug-btn mt-2 text-sm text-cyan-600 dark:text-cyan-400 hover:text-cyan-700 dark:hover:text-cyan-300 flex items-center gap-1 transition-colors"
3698
+ onclick="window.regenerateSlugFromTitle_${fieldId.replace(/-/g, "_")}()"
3699
+ >
3700
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
3701
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
3702
+ </svg>
3703
+ Regenerate from title
3704
+ </button>
3705
+ <p class="text-xs text-zinc-500 dark:text-zinc-400 mt-1">Use lowercase letters, numbers, and hyphens only</p>
3706
+ </div>
3707
+
3658
3708
  <script>
3659
3709
  (function() {
3660
- const field = document.getElementById('${fieldId}');
3710
+ const slugField = document.getElementById('${fieldId}');
3711
+ const statusDiv = document.getElementById('${fieldId}-status');
3712
+ const isEditMode = slugField.dataset.isEditMode === 'true';
3661
3713
  const pattern = new RegExp('${slugPattern}');
3662
-
3663
- field.addEventListener('input', function() {
3664
- if (this.value && !pattern.test(this.value)) {
3665
- this.setCustomValidity('Please use only lowercase letters, numbers, and hyphens.');
3666
- } else {
3667
- this.setCustomValidity('');
3668
- }
3669
- });
3670
-
3671
- field.addEventListener('blur', function() {
3672
- this.reportValidity();
3673
- });
3674
- })();
3675
-
3676
- function generateSlugFromTitle(slugFieldId) {
3677
- const titleField = document.querySelector('input[name="title"]');
3678
- const slugField = document.getElementById(slugFieldId);
3679
- if (titleField && slugField) {
3680
- const slug = titleField.value
3714
+ const collectionId = slugField.dataset.collectionId;
3715
+ const contentId = slugField.dataset.contentId;
3716
+
3717
+ let checkTimeout;
3718
+ let lastCheckedSlug = '';
3719
+ let manuallyEdited = false;
3720
+
3721
+ // Shared slug generation function
3722
+ function generateSlug(text) {
3723
+ if (!text) return '';
3724
+
3725
+ return text
3681
3726
  .toLowerCase()
3727
+ .normalize('NFD')
3728
+ .replace(/[\\u0300-\\u036f]/g, '')
3682
3729
  .replace(/[^a-z0-9\\s_-]/g, '')
3683
3730
  .replace(/\\s+/g, '-')
3684
3731
  .replace(/[-_]+/g, '-')
3685
- .replace(/^[-_]|[-_]$/g, '');
3686
- slugField.value = slug;
3732
+ .replace(/^[-_]+|[-_]+$/g, '')
3733
+ .substring(0, 100);
3687
3734
  }
3688
- }
3689
-
3690
- // Auto-generate slug when title changes
3691
- document.addEventListener('DOMContentLoaded', function() {
3692
- const titleField = document.querySelector('input[name="title"]');
3693
- const slugField = document.getElementById('${fieldId}');
3694
- if (titleField && slugField && !slugField.value) {
3695
- titleField.addEventListener('input', function() {
3696
- if (!slugField.value) {
3697
- generateSlugFromTitle('${fieldId}');
3735
+
3736
+ // Check if slug is available
3737
+ async function checkSlugAvailability(slug) {
3738
+ if (!slug || !collectionId) return;
3739
+
3740
+ // Don't check if it's the same as last time
3741
+ if (slug === lastCheckedSlug) return;
3742
+ lastCheckedSlug = slug;
3743
+
3744
+ try {
3745
+ // Show checking status
3746
+ statusDiv.innerHTML = '<span class="text-gray-400">\u23F3 Checking availability...</span>';
3747
+
3748
+ // Build URL
3749
+ let url = \`/api/content/check-slug?collectionId=\${encodeURIComponent(collectionId)}&slug=\${encodeURIComponent(slug)}\`;
3750
+ if (contentId) {
3751
+ url += \`&excludeId=\${encodeURIComponent(contentId)}\`;
3698
3752
  }
3699
- });
3753
+
3754
+ const response = await fetch(url);
3755
+ const data = await response.json();
3756
+
3757
+ if (data.available) {
3758
+ statusDiv.innerHTML = '<span class="text-green-500 dark:text-green-400">\u2713 Available</span>';
3759
+ slugField.setCustomValidity('');
3760
+ } else {
3761
+ statusDiv.innerHTML = \`<span class="text-red-500 dark:text-red-400">\u2717 \${data.message || 'Already in use'}</span>\`;
3762
+ slugField.setCustomValidity(data.message || 'This slug is already in use');
3763
+ }
3764
+ } catch (error) {
3765
+ console.error('Error checking slug:', error);
3766
+ statusDiv.innerHTML = '<span class="text-yellow-500 dark:text-yellow-400">\u26A0 Could not verify</span>';
3767
+ }
3700
3768
  }
3701
- });
3769
+
3770
+ // Format validation and duplicate checking
3771
+ slugField.addEventListener('input', function() {
3772
+ const value = this.value;
3773
+
3774
+ // Mark as manually edited if user types directly
3775
+ if (document.activeElement === this) {
3776
+ manuallyEdited = true;
3777
+ }
3778
+
3779
+ // Clear status if empty
3780
+ if (!value) {
3781
+ statusDiv.innerHTML = '';
3782
+ this.setCustomValidity('');
3783
+ return;
3784
+ }
3785
+
3786
+ // Pattern validation
3787
+ if (!pattern.test(value)) {
3788
+ this.setCustomValidity('Please use only lowercase letters, numbers, and hyphens.');
3789
+ statusDiv.innerHTML = '<span class="text-red-500 dark:text-red-400">\u2717 Invalid format</span>';
3790
+ return;
3791
+ }
3792
+
3793
+ // Debounce the availability check
3794
+ clearTimeout(checkTimeout);
3795
+ checkTimeout = setTimeout(() => {
3796
+ checkSlugAvailability(value);
3797
+ }, 500); // Wait 500ms after user stops typing
3798
+ });
3799
+
3800
+ // Initial check if field has value
3801
+ if (slugField.value) {
3802
+ checkSlugAvailability(slugField.value);
3803
+ }
3804
+
3805
+ // Auto-generate only in create mode
3806
+ // Wait for all fields to be rendered before attaching listeners
3807
+ if (!isEditMode) {
3808
+ // Use setTimeout to ensure all fields in the form are rendered
3809
+ setTimeout(() => {
3810
+ const titleField = document.querySelector('input[name="title"]');
3811
+ if (titleField) {
3812
+ titleField.addEventListener('input', function() {
3813
+ if (!manuallyEdited) {
3814
+ const slug = generateSlug(this.value);
3815
+ slugField.value = slug;
3816
+
3817
+ // Trigger validation and duplicate check
3818
+ slugField.dispatchEvent(new Event('input', { bubbles: true }));
3819
+ }
3820
+ });
3821
+ }
3822
+ }, 0);
3823
+ }
3824
+
3825
+ // Global function for regenerate button
3826
+ window.regenerateSlugFromTitle_${fieldId.replace(/-/g, "_")} = function() {
3827
+ const titleField = document.querySelector('input[name="title"]');
3828
+ if (titleField && slugField) {
3829
+ const slug = generateSlug(titleField.value);
3830
+ slugField.value = slug;
3831
+ manuallyEdited = false;
3832
+
3833
+ // Trigger validation and duplicate check
3834
+ slugField.dispatchEvent(new Event('input', { bubbles: true }));
3835
+ }
3836
+ };
3837
+ })();
3702
3838
  </script>
3703
3839
  `;
3704
3840
  break;
@@ -3831,210 +3967,9 @@ function escapeHtml2(text) {
3831
3967
  "'": "&#39;"
3832
3968
  })[char] || char);
3833
3969
  }
3834
- var PluginBuilder = class _PluginBuilder {
3835
- plugin;
3836
- constructor(options) {
3837
- this.plugin = {
3838
- name: options.name,
3839
- version: options.version,
3840
- description: options.description,
3841
- author: options.author,
3842
- dependencies: options.dependencies,
3843
- routes: [],
3844
- middleware: [],
3845
- models: [],
3846
- services: [],
3847
- adminPages: [],
3848
- adminComponents: [],
3849
- menuItems: [],
3850
- hooks: []
3851
- };
3852
- }
3853
- /**
3854
- * Create a new plugin builder
3855
- */
3856
- static create(options) {
3857
- return new _PluginBuilder(options);
3858
- }
3859
- /**
3860
- * Add metadata to the plugin
3861
- */
3862
- metadata(metadata) {
3863
- Object.assign(this.plugin, metadata);
3864
- return this;
3865
- }
3866
- /**
3867
- * Add routes to plugin
3868
- */
3869
- addRoutes(routes) {
3870
- this.plugin.routes = [...this.plugin.routes || [], ...routes];
3871
- return this;
3872
- }
3873
- /**
3874
- * Add a single route to plugin
3875
- */
3876
- addRoute(path, handler, options) {
3877
- const route = {
3878
- path,
3879
- handler,
3880
- ...options
3881
- };
3882
- this.plugin.routes = [...this.plugin.routes || [], route];
3883
- return this;
3884
- }
3885
- /**
3886
- * Add middleware to plugin
3887
- */
3888
- addMiddleware(middleware) {
3889
- this.plugin.middleware = [...this.plugin.middleware || [], ...middleware];
3890
- return this;
3891
- }
3892
- /**
3893
- * Add a single middleware to plugin
3894
- */
3895
- addSingleMiddleware(name, handler, options) {
3896
- const middleware = {
3897
- name,
3898
- handler,
3899
- ...options
3900
- };
3901
- this.plugin.middleware = [...this.plugin.middleware || [], middleware];
3902
- return this;
3903
- }
3904
- /**
3905
- * Add models to plugin
3906
- */
3907
- addModels(models) {
3908
- this.plugin.models = [...this.plugin.models || [], ...models];
3909
- return this;
3910
- }
3911
- /**
3912
- * Add a single model to plugin
3913
- */
3914
- addModel(name, options) {
3915
- const model = {
3916
- name,
3917
- ...options
3918
- };
3919
- this.plugin.models = [...this.plugin.models || [], model];
3920
- return this;
3921
- }
3922
- /**
3923
- * Add services to plugin
3924
- */
3925
- addServices(services) {
3926
- this.plugin.services = [...this.plugin.services || [], ...services];
3927
- return this;
3928
- }
3929
- /**
3930
- * Add a single service to plugin
3931
- */
3932
- addService(name, implementation, options) {
3933
- const service = {
3934
- name,
3935
- implementation,
3936
- ...options
3937
- };
3938
- this.plugin.services = [...this.plugin.services || [], service];
3939
- return this;
3940
- }
3941
- /**
3942
- * Add admin pages to plugin
3943
- */
3944
- addAdminPages(pages) {
3945
- this.plugin.adminPages = [...this.plugin.adminPages || [], ...pages];
3946
- return this;
3947
- }
3948
- /**
3949
- * Add a single admin page to plugin
3950
- */
3951
- addAdminPage(path, title, component, options) {
3952
- const page = {
3953
- path,
3954
- title,
3955
- component,
3956
- ...options
3957
- };
3958
- this.plugin.adminPages = [...this.plugin.adminPages || [], page];
3959
- return this;
3960
- }
3961
- /**
3962
- * Add admin components to plugin
3963
- */
3964
- addComponents(components) {
3965
- this.plugin.adminComponents = [...this.plugin.adminComponents || [], ...components];
3966
- return this;
3967
- }
3968
- /**
3969
- * Add a single admin component to plugin
3970
- */
3971
- addComponent(name, template, options) {
3972
- const component = {
3973
- name,
3974
- template,
3975
- ...options
3976
- };
3977
- this.plugin.adminComponents = [...this.plugin.adminComponents || [], component];
3978
- return this;
3979
- }
3980
- /**
3981
- * Add menu items to plugin
3982
- */
3983
- addMenuItems(items) {
3984
- this.plugin.menuItems = [...this.plugin.menuItems || [], ...items];
3985
- return this;
3986
- }
3987
- /**
3988
- * Add a single menu item to plugin
3989
- */
3990
- addMenuItem(label, path, options) {
3991
- const menuItem = {
3992
- label,
3993
- path,
3994
- ...options
3995
- };
3996
- this.plugin.menuItems = [...this.plugin.menuItems || [], menuItem];
3997
- return this;
3998
- }
3999
- /**
4000
- * Add hooks to plugin
4001
- */
4002
- addHooks(hooks) {
4003
- this.plugin.hooks = [...this.plugin.hooks || [], ...hooks];
4004
- return this;
4005
- }
4006
- /**
4007
- * Add a single hook to plugin
4008
- */
4009
- addHook(name, handler, options) {
4010
- const hook = {
4011
- name,
4012
- handler,
4013
- ...options
4014
- };
4015
- this.plugin.hooks = [...this.plugin.hooks || [], hook];
4016
- return this;
4017
- }
4018
- /**
4019
- * Add lifecycle hooks
4020
- */
4021
- lifecycle(hooks) {
4022
- Object.assign(this.plugin, hooks);
4023
- return this;
4024
- }
4025
- /**
4026
- * Build the plugin
4027
- */
4028
- build() {
4029
- if (!this.plugin.name || !this.plugin.version) {
4030
- throw new Error("Plugin name and version are required");
4031
- }
4032
- return this.plugin;
4033
- }
4034
- };
4035
3970
 
4036
3971
  // src/plugins/available/tinymce-plugin/index.ts
4037
- var builder = PluginBuilder.create({
3972
+ var builder = chunkAI2JJIJX_cjs.PluginBuilder.create({
4038
3973
  name: "tinymce-plugin",
4039
3974
  version: "1.0.0",
4040
3975
  description: "Powerful WYSIWYG rich text editor for content creation"
@@ -4317,7 +4252,7 @@ function getQuillCDN(version = "2.0.2") {
4317
4252
  `;
4318
4253
  }
4319
4254
  function createQuillEditorPlugin() {
4320
- const builder3 = PluginBuilder.create({
4255
+ const builder3 = chunkAI2JJIJX_cjs.PluginBuilder.create({
4321
4256
  name: "quill-editor",
4322
4257
  version: "1.0.0",
4323
4258
  description: "Quill rich text editor integration for SonicJS"
@@ -4343,7 +4278,7 @@ function createQuillEditorPlugin() {
4343
4278
  createQuillEditorPlugin();
4344
4279
 
4345
4280
  // src/plugins/available/easy-mdx/index.ts
4346
- var builder2 = PluginBuilder.create({
4281
+ var builder2 = chunkAI2JJIJX_cjs.PluginBuilder.create({
4347
4282
  name: "easy-mdx",
4348
4283
  version: "1.0.0",
4349
4284
  description: "Lightweight markdown editor with live preview"
@@ -4575,17 +4510,24 @@ function renderContentFormPage(data) {
4575
4510
  const coreFieldsHTML = coreFields.sort((a, b) => a.field_order - b.field_order).map((field) => renderDynamicField(field, {
4576
4511
  value: getFieldValue(field.field_name),
4577
4512
  errors: data.validationErrors?.[field.field_name] || [],
4578
- pluginStatuses
4513
+ pluginStatuses,
4514
+ collectionId: data.collection.id,
4515
+ contentId: data.id
4516
+ // Pass content ID when editing
4579
4517
  }));
4580
4518
  const contentFieldsHTML = contentFields.sort((a, b) => a.field_order - b.field_order).map((field) => renderDynamicField(field, {
4581
4519
  value: getFieldValue(field.field_name),
4582
4520
  errors: data.validationErrors?.[field.field_name] || [],
4583
- pluginStatuses
4521
+ pluginStatuses,
4522
+ collectionId: data.collection.id,
4523
+ contentId: data.id
4584
4524
  }));
4585
4525
  const metaFieldsHTML = metaFields.sort((a, b) => a.field_order - b.field_order).map((field) => renderDynamicField(field, {
4586
4526
  value: getFieldValue(field.field_name),
4587
4527
  errors: data.validationErrors?.[field.field_name] || [],
4588
- pluginStatuses
4528
+ pluginStatuses,
4529
+ collectionId: data.collection.id,
4530
+ contentId: data.id
4589
4531
  }));
4590
4532
  const pageContent = `
4591
4533
  <div class="space-y-6">
@@ -6025,7 +5967,7 @@ async function isPluginActive2(db, pluginId) {
6025
5967
 
6026
5968
  // src/routes/admin-content.ts
6027
5969
  var adminContentRoutes = new hono.Hono();
6028
- adminContentRoutes.use("*", chunk7I5INVNR_cjs.requireAuth());
5970
+ adminContentRoutes.use("*", chunkYYV3XQOQ_cjs.requireAuth());
6029
5971
  async function getCollectionFields(db, collectionId) {
6030
5972
  const cache = chunk7FOAMNTI_cjs.getCacheService(chunk7FOAMNTI_cjs.CACHE_CONFIGS.collection);
6031
5973
  return cache.getOrSet(
@@ -6502,6 +6444,18 @@ adminContentRoutes.post("/", async (c) => {
6502
6444
  const errors = {};
6503
6445
  for (const field of fields) {
6504
6446
  const value = formData.get(field.field_name);
6447
+ const blocksConfig = chunkZWV3EBZ7_cjs.getBlocksFieldConfig(field.field_options);
6448
+ if (blocksConfig) {
6449
+ const parsed = chunkZWV3EBZ7_cjs.parseBlocksValue(value, blocksConfig);
6450
+ if (field.is_required && parsed.value.length === 0) {
6451
+ parsed.errors.push(`${field.field_label} is required`);
6452
+ }
6453
+ if (parsed.errors.length > 0) {
6454
+ errors[field.field_name] = parsed.errors;
6455
+ }
6456
+ data[field.field_name] = parsed.value;
6457
+ continue;
6458
+ }
6505
6459
  if (field.is_required && (!value || value.toString().trim() === "")) {
6506
6460
  errors[field.field_name] = [`${field.field_label} is required`];
6507
6461
  continue;
@@ -6524,6 +6478,67 @@ adminContentRoutes.post("/", async (c) => {
6524
6478
  data[field.field_name] = value;
6525
6479
  }
6526
6480
  break;
6481
+ case "array": {
6482
+ if (!value || value.toString().trim() === "") {
6483
+ data[field.field_name] = [];
6484
+ if (field.is_required) {
6485
+ errors[field.field_name] = [`${field.field_label} is required`];
6486
+ }
6487
+ break;
6488
+ }
6489
+ try {
6490
+ const parsed = JSON.parse(value.toString());
6491
+ if (!Array.isArray(parsed)) {
6492
+ errors[field.field_name] = [`${field.field_label} must be a JSON array`];
6493
+ } else {
6494
+ if (field.is_required && parsed.length === 0) {
6495
+ errors[field.field_name] = [`${field.field_label} is required`];
6496
+ }
6497
+ data[field.field_name] = parsed;
6498
+ }
6499
+ } catch {
6500
+ errors[field.field_name] = [`${field.field_label} must be valid JSON`];
6501
+ }
6502
+ break;
6503
+ }
6504
+ case "object": {
6505
+ if (!value || value.toString().trim() === "") {
6506
+ data[field.field_name] = {};
6507
+ if (field.is_required) {
6508
+ errors[field.field_name] = [`${field.field_label} is required`];
6509
+ }
6510
+ break;
6511
+ }
6512
+ try {
6513
+ const parsed = JSON.parse(value.toString());
6514
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
6515
+ errors[field.field_name] = [`${field.field_label} must be a JSON object`];
6516
+ } else {
6517
+ if (field.is_required && Object.keys(parsed).length === 0) {
6518
+ errors[field.field_name] = [`${field.field_label} is required`];
6519
+ }
6520
+ data[field.field_name] = parsed;
6521
+ }
6522
+ } catch {
6523
+ errors[field.field_name] = [`${field.field_label} must be valid JSON`];
6524
+ }
6525
+ break;
6526
+ }
6527
+ case "json": {
6528
+ if (!value || value.toString().trim() === "") {
6529
+ data[field.field_name] = null;
6530
+ if (field.is_required) {
6531
+ errors[field.field_name] = [`${field.field_label} is required`];
6532
+ }
6533
+ break;
6534
+ }
6535
+ try {
6536
+ data[field.field_name] = JSON.parse(value.toString());
6537
+ } catch {
6538
+ errors[field.field_name] = [`${field.field_label} must be valid JSON`];
6539
+ }
6540
+ break;
6541
+ }
6527
6542
  default:
6528
6543
  data[field.field_name] = value;
6529
6544
  }
@@ -6648,6 +6663,18 @@ adminContentRoutes.put("/:id", async (c) => {
6648
6663
  const errors = {};
6649
6664
  for (const field of fields) {
6650
6665
  const value = formData.get(field.field_name);
6666
+ const blocksConfig = chunkZWV3EBZ7_cjs.getBlocksFieldConfig(field.field_options);
6667
+ if (blocksConfig) {
6668
+ const parsed = chunkZWV3EBZ7_cjs.parseBlocksValue(value, blocksConfig);
6669
+ if (field.is_required && parsed.value.length === 0) {
6670
+ parsed.errors.push(`${field.field_label} is required`);
6671
+ }
6672
+ if (parsed.errors.length > 0) {
6673
+ errors[field.field_name] = parsed.errors;
6674
+ }
6675
+ data[field.field_name] = parsed.value;
6676
+ continue;
6677
+ }
6651
6678
  if (field.is_required && (!value || value.toString().trim() === "")) {
6652
6679
  errors[field.field_name] = [`${field.field_label} is required`];
6653
6680
  continue;
@@ -6670,6 +6697,67 @@ adminContentRoutes.put("/:id", async (c) => {
6670
6697
  data[field.field_name] = value;
6671
6698
  }
6672
6699
  break;
6700
+ case "array": {
6701
+ if (!value || value.toString().trim() === "") {
6702
+ data[field.field_name] = [];
6703
+ if (field.is_required) {
6704
+ errors[field.field_name] = [`${field.field_label} is required`];
6705
+ }
6706
+ break;
6707
+ }
6708
+ try {
6709
+ const parsed = JSON.parse(value.toString());
6710
+ if (!Array.isArray(parsed)) {
6711
+ errors[field.field_name] = [`${field.field_label} must be a JSON array`];
6712
+ } else {
6713
+ if (field.is_required && parsed.length === 0) {
6714
+ errors[field.field_name] = [`${field.field_label} is required`];
6715
+ }
6716
+ data[field.field_name] = parsed;
6717
+ }
6718
+ } catch {
6719
+ errors[field.field_name] = [`${field.field_label} must be valid JSON`];
6720
+ }
6721
+ break;
6722
+ }
6723
+ case "object": {
6724
+ if (!value || value.toString().trim() === "") {
6725
+ data[field.field_name] = {};
6726
+ if (field.is_required) {
6727
+ errors[field.field_name] = [`${field.field_label} is required`];
6728
+ }
6729
+ break;
6730
+ }
6731
+ try {
6732
+ const parsed = JSON.parse(value.toString());
6733
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
6734
+ errors[field.field_name] = [`${field.field_label} must be a JSON object`];
6735
+ } else {
6736
+ if (field.is_required && Object.keys(parsed).length === 0) {
6737
+ errors[field.field_name] = [`${field.field_label} is required`];
6738
+ }
6739
+ data[field.field_name] = parsed;
6740
+ }
6741
+ } catch {
6742
+ errors[field.field_name] = [`${field.field_label} must be valid JSON`];
6743
+ }
6744
+ break;
6745
+ }
6746
+ case "json": {
6747
+ if (!value || value.toString().trim() === "") {
6748
+ data[field.field_name] = null;
6749
+ if (field.is_required) {
6750
+ errors[field.field_name] = [`${field.field_label} is required`];
6751
+ }
6752
+ break;
6753
+ }
6754
+ try {
6755
+ data[field.field_name] = JSON.parse(value.toString());
6756
+ } catch {
6757
+ errors[field.field_name] = [`${field.field_label} must be valid JSON`];
6758
+ }
6759
+ break;
6760
+ }
6673
6761
  default:
6674
6762
  data[field.field_name] = value;
6675
6763
  }
@@ -6789,6 +6877,12 @@ adminContentRoutes.post("/preview", async (c) => {
6789
6877
  const data = {};
6790
6878
  for (const field of fields) {
6791
6879
  const value = formData.get(field.field_name);
6880
+ const blocksConfig = chunkZWV3EBZ7_cjs.getBlocksFieldConfig(field.field_options);
6881
+ if (blocksConfig) {
6882
+ const parsed = chunkZWV3EBZ7_cjs.parseBlocksValue(value, blocksConfig);
6883
+ data[field.field_name] = parsed.value;
6884
+ continue;
6885
+ }
6792
6886
  switch (field.field_type) {
6793
6887
  case "number":
6794
6888
  data[field.field_name] = value ? Number(value) : null;
@@ -8086,7 +8180,7 @@ function renderUserEditPage(data) {
8086
8180
  <input
8087
8181
  type="text"
8088
8182
  name="first_name"
8089
- value="${chunkFYEDK7K7_cjs.escapeHtml(data.userToEdit.firstName || "")}"
8183
+ value="${chunkZWV3EBZ7_cjs.escapeHtml(data.userToEdit.firstName || "")}"
8090
8184
  required
8091
8185
  class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
8092
8186
  />
@@ -8097,7 +8191,7 @@ function renderUserEditPage(data) {
8097
8191
  <input
8098
8192
  type="text"
8099
8193
  name="last_name"
8100
- value="${chunkFYEDK7K7_cjs.escapeHtml(data.userToEdit.lastName || "")}"
8194
+ value="${chunkZWV3EBZ7_cjs.escapeHtml(data.userToEdit.lastName || "")}"
8101
8195
  required
8102
8196
  class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
8103
8197
  />
@@ -8108,7 +8202,7 @@ function renderUserEditPage(data) {
8108
8202
  <input
8109
8203
  type="text"
8110
8204
  name="username"
8111
- value="${chunkFYEDK7K7_cjs.escapeHtml(data.userToEdit.username || "")}"
8205
+ value="${chunkZWV3EBZ7_cjs.escapeHtml(data.userToEdit.username || "")}"
8112
8206
  required
8113
8207
  class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
8114
8208
  />
@@ -8119,7 +8213,7 @@ function renderUserEditPage(data) {
8119
8213
  <input
8120
8214
  type="email"
8121
8215
  name="email"
8122
- value="${chunkFYEDK7K7_cjs.escapeHtml(data.userToEdit.email || "")}"
8216
+ value="${chunkZWV3EBZ7_cjs.escapeHtml(data.userToEdit.email || "")}"
8123
8217
  required
8124
8218
  class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
8125
8219
  />
@@ -8130,7 +8224,7 @@ function renderUserEditPage(data) {
8130
8224
  <input
8131
8225
  type="tel"
8132
8226
  name="phone"
8133
- value="${chunkFYEDK7K7_cjs.escapeHtml(data.userToEdit.phone || "")}"
8227
+ value="${chunkZWV3EBZ7_cjs.escapeHtml(data.userToEdit.phone || "")}"
8134
8228
  class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
8135
8229
  />
8136
8230
  </div>
@@ -8144,7 +8238,7 @@ function renderUserEditPage(data) {
8144
8238
  class="col-start-1 row-start-1 w-full appearance-none rounded-md bg-white/5 dark:bg-white/5 py-1.5 pl-3 pr-8 text-base text-zinc-950 dark:text-white outline outline-1 -outline-offset-1 outline-zinc-500/30 dark:outline-zinc-400/30 *:bg-white dark:*:bg-zinc-800 focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-zinc-500 dark:focus-visible:outline-zinc-400 sm:text-sm/6"
8145
8239
  >
8146
8240
  ${data.roles.map((role) => `
8147
- <option value="${chunkFYEDK7K7_cjs.escapeHtml(role.value)}" ${data.userToEdit.role === role.value ? "selected" : ""}>${chunkFYEDK7K7_cjs.escapeHtml(role.label)}</option>
8241
+ <option value="${chunkZWV3EBZ7_cjs.escapeHtml(role.value)}" ${data.userToEdit.role === role.value ? "selected" : ""}>${chunkZWV3EBZ7_cjs.escapeHtml(role.label)}</option>
8148
8242
  `).join("")}
8149
8243
  </select>
8150
8244
  <svg viewBox="0 0 16 16" fill="currentColor" data-slot="icon" aria-hidden="true" class="pointer-events-none col-start-1 row-start-1 mr-2 size-5 self-center justify-self-end text-zinc-600 dark:text-zinc-400 sm:size-4">
@@ -8160,7 +8254,7 @@ function renderUserEditPage(data) {
8160
8254
  name="bio"
8161
8255
  rows="3"
8162
8256
  class="w-full rounded-lg bg-white dark:bg-zinc-800 px-3 py-2 text-sm text-zinc-950 dark:text-white shadow-sm ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-950 dark:focus:ring-white transition-shadow"
8163
- >${chunkFYEDK7K7_cjs.escapeHtml(data.userToEdit.bio || "")}</textarea>
8257
+ >${chunkZWV3EBZ7_cjs.escapeHtml(data.userToEdit.bio || "")}</textarea>
8164
8258
  </div>
8165
8259
  </div>
8166
8260
 
@@ -9060,7 +9154,7 @@ function renderUsersListPage(data) {
9060
9154
 
9061
9155
  // src/routes/admin-users.ts
9062
9156
  var userRoutes = new hono.Hono();
9063
- userRoutes.use("*", chunk7I5INVNR_cjs.requireAuth());
9157
+ userRoutes.use("*", chunkYYV3XQOQ_cjs.requireAuth());
9064
9158
  userRoutes.get("/", (c) => {
9065
9159
  return c.redirect("/admin/dashboard");
9066
9160
  });
@@ -9159,12 +9253,12 @@ userRoutes.put("/profile", async (c) => {
9159
9253
  const db = c.env.DB;
9160
9254
  try {
9161
9255
  const formData = await c.req.formData();
9162
- const firstName = chunkFYEDK7K7_cjs.sanitizeInput(formData.get("first_name")?.toString());
9163
- const lastName = chunkFYEDK7K7_cjs.sanitizeInput(formData.get("last_name")?.toString());
9164
- const username = chunkFYEDK7K7_cjs.sanitizeInput(formData.get("username")?.toString());
9256
+ const firstName = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("first_name")?.toString());
9257
+ const lastName = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("last_name")?.toString());
9258
+ const username = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("username")?.toString());
9165
9259
  const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
9166
- const phone = chunkFYEDK7K7_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
9167
- const bio = chunkFYEDK7K7_cjs.sanitizeInput(formData.get("bio")?.toString()) || null;
9260
+ const phone = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
9261
+ const bio = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("bio")?.toString()) || null;
9168
9262
  const timezone = formData.get("timezone")?.toString() || "UTC";
9169
9263
  const language = formData.get("language")?.toString() || "en";
9170
9264
  const emailNotifications = formData.get("email_notifications") === "1";
@@ -9215,7 +9309,7 @@ userRoutes.put("/profile", async (c) => {
9215
9309
  Date.now(),
9216
9310
  user.userId
9217
9311
  ).run();
9218
- await chunk7I5INVNR_cjs.logActivity(
9312
+ await chunkYYV3XQOQ_cjs.logActivity(
9219
9313
  db,
9220
9314
  user.userId,
9221
9315
  "profile.update",
@@ -9278,7 +9372,7 @@ userRoutes.post("/profile/avatar", async (c) => {
9278
9372
  SELECT first_name, last_name FROM users WHERE id = ?
9279
9373
  `);
9280
9374
  const userData = await userStmt.bind(user.userId).first();
9281
- await chunk7I5INVNR_cjs.logActivity(
9375
+ await chunkYYV3XQOQ_cjs.logActivity(
9282
9376
  db,
9283
9377
  user.userId,
9284
9378
  "profile.avatar_update",
@@ -9349,7 +9443,7 @@ userRoutes.post("/profile/password", async (c) => {
9349
9443
  dismissible: true
9350
9444
  }));
9351
9445
  }
9352
- const validPassword = await chunk7I5INVNR_cjs.AuthManager.verifyPassword(currentPassword, userData.password_hash);
9446
+ const validPassword = await chunkYYV3XQOQ_cjs.AuthManager.verifyPassword(currentPassword, userData.password_hash);
9353
9447
  if (!validPassword) {
9354
9448
  return c.html(renderAlert2({
9355
9449
  type: "error",
@@ -9357,7 +9451,7 @@ userRoutes.post("/profile/password", async (c) => {
9357
9451
  dismissible: true
9358
9452
  }));
9359
9453
  }
9360
- const newPasswordHash = await chunk7I5INVNR_cjs.AuthManager.hashPassword(newPassword);
9454
+ const newPasswordHash = await chunkYYV3XQOQ_cjs.AuthManager.hashPassword(newPassword);
9361
9455
  const historyStmt = db.prepare(`
9362
9456
  INSERT INTO password_history (id, user_id, password_hash, created_at)
9363
9457
  VALUES (?, ?, ?, ?)
@@ -9373,7 +9467,7 @@ userRoutes.post("/profile/password", async (c) => {
9373
9467
  WHERE id = ?
9374
9468
  `);
9375
9469
  await updateStmt.bind(newPasswordHash, Date.now(), user.userId).run();
9376
- await chunk7I5INVNR_cjs.logActivity(
9470
+ await chunkYYV3XQOQ_cjs.logActivity(
9377
9471
  db,
9378
9472
  user.userId,
9379
9473
  "profile.password_change",
@@ -9440,7 +9534,7 @@ userRoutes.get("/users", async (c) => {
9440
9534
  `);
9441
9535
  const countResult = await countStmt.bind(...params).first();
9442
9536
  const totalUsers = countResult?.total || 0;
9443
- await chunk7I5INVNR_cjs.logActivity(
9537
+ await chunkYYV3XQOQ_cjs.logActivity(
9444
9538
  db,
9445
9539
  user.userId,
9446
9540
  "users.list_view",
@@ -9542,12 +9636,12 @@ userRoutes.post("/users/new", async (c) => {
9542
9636
  const user = c.get("user");
9543
9637
  try {
9544
9638
  const formData = await c.req.formData();
9545
- const firstName = chunkFYEDK7K7_cjs.sanitizeInput(formData.get("first_name")?.toString());
9546
- const lastName = chunkFYEDK7K7_cjs.sanitizeInput(formData.get("last_name")?.toString());
9547
- const username = chunkFYEDK7K7_cjs.sanitizeInput(formData.get("username")?.toString());
9639
+ const firstName = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("first_name")?.toString());
9640
+ const lastName = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("last_name")?.toString());
9641
+ const username = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("username")?.toString());
9548
9642
  const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
9549
- const phone = chunkFYEDK7K7_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
9550
- const bio = chunkFYEDK7K7_cjs.sanitizeInput(formData.get("bio")?.toString()) || null;
9643
+ const phone = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
9644
+ const bio = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("bio")?.toString()) || null;
9551
9645
  const role = formData.get("role")?.toString() || "viewer";
9552
9646
  const password = formData.get("password")?.toString() || "";
9553
9647
  const confirmPassword = formData.get("confirm_password")?.toString() || "";
@@ -9594,7 +9688,7 @@ userRoutes.post("/users/new", async (c) => {
9594
9688
  dismissible: true
9595
9689
  }));
9596
9690
  }
9597
- const passwordHash = await chunk7I5INVNR_cjs.AuthManager.hashPassword(password);
9691
+ const passwordHash = await chunkYYV3XQOQ_cjs.AuthManager.hashPassword(password);
9598
9692
  const userId = crypto.randomUUID();
9599
9693
  const createStmt = db.prepare(`
9600
9694
  INSERT INTO users (
@@ -9617,7 +9711,7 @@ userRoutes.post("/users/new", async (c) => {
9617
9711
  Date.now(),
9618
9712
  Date.now()
9619
9713
  ).run();
9620
- await chunk7I5INVNR_cjs.logActivity(
9714
+ await chunkYYV3XQOQ_cjs.logActivity(
9621
9715
  db,
9622
9716
  user.userId,
9623
9717
  "user!.create",
@@ -9655,7 +9749,7 @@ userRoutes.get("/users/:id", async (c) => {
9655
9749
  if (!userRecord) {
9656
9750
  return c.json({ error: "User not found" }, 404);
9657
9751
  }
9658
- await chunk7I5INVNR_cjs.logActivity(
9752
+ await chunkYYV3XQOQ_cjs.logActivity(
9659
9753
  db,
9660
9754
  user.userId,
9661
9755
  "user!.view",
@@ -9748,12 +9842,12 @@ userRoutes.put("/users/:id", async (c) => {
9748
9842
  const userId = c.req.param("id");
9749
9843
  try {
9750
9844
  const formData = await c.req.formData();
9751
- const firstName = chunkFYEDK7K7_cjs.sanitizeInput(formData.get("first_name")?.toString());
9752
- const lastName = chunkFYEDK7K7_cjs.sanitizeInput(formData.get("last_name")?.toString());
9753
- const username = chunkFYEDK7K7_cjs.sanitizeInput(formData.get("username")?.toString());
9845
+ const firstName = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("first_name")?.toString());
9846
+ const lastName = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("last_name")?.toString());
9847
+ const username = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("username")?.toString());
9754
9848
  const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
9755
- const phone = chunkFYEDK7K7_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
9756
- const bio = chunkFYEDK7K7_cjs.sanitizeInput(formData.get("bio")?.toString()) || null;
9849
+ const phone = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("phone")?.toString()) || null;
9850
+ const bio = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("bio")?.toString()) || null;
9757
9851
  const role = formData.get("role")?.toString() || "viewer";
9758
9852
  const isActive = formData.get("is_active") === "1";
9759
9853
  const emailVerified = formData.get("email_verified") === "1";
@@ -9804,7 +9898,7 @@ userRoutes.put("/users/:id", async (c) => {
9804
9898
  Date.now(),
9805
9899
  userId
9806
9900
  ).run();
9807
- await chunk7I5INVNR_cjs.logActivity(
9901
+ await chunkYYV3XQOQ_cjs.logActivity(
9808
9902
  db,
9809
9903
  user.userId,
9810
9904
  "user!.update",
@@ -9849,7 +9943,7 @@ userRoutes.post("/users/:id/toggle", async (c) => {
9849
9943
  UPDATE users SET is_active = ?, updated_at = ? WHERE id = ?
9850
9944
  `);
9851
9945
  await toggleStmt.bind(active ? 1 : 0, Date.now(), userId).run();
9852
- await chunk7I5INVNR_cjs.logActivity(
9946
+ await chunkYYV3XQOQ_cjs.logActivity(
9853
9947
  db,
9854
9948
  user.userId,
9855
9949
  active ? "user.activate" : "user.deactivate",
@@ -9890,7 +9984,7 @@ userRoutes.delete("/users/:id", async (c) => {
9890
9984
  DELETE FROM users WHERE id = ?
9891
9985
  `);
9892
9986
  await deleteStmt.bind(userId).run();
9893
- await chunk7I5INVNR_cjs.logActivity(
9987
+ await chunkYYV3XQOQ_cjs.logActivity(
9894
9988
  db,
9895
9989
  user.userId,
9896
9990
  "user!.hard_delete",
@@ -9909,7 +10003,7 @@ userRoutes.delete("/users/:id", async (c) => {
9909
10003
  UPDATE users SET is_active = 0, updated_at = ? WHERE id = ?
9910
10004
  `);
9911
10005
  await deleteStmt.bind(Date.now(), userId).run();
9912
- await chunk7I5INVNR_cjs.logActivity(
10006
+ await chunkYYV3XQOQ_cjs.logActivity(
9913
10007
  db,
9914
10008
  user.userId,
9915
10009
  "user!.soft_delete",
@@ -9936,8 +10030,8 @@ userRoutes.post("/invite-user", async (c) => {
9936
10030
  const formData = await c.req.formData();
9937
10031
  const email = formData.get("email")?.toString()?.trim().toLowerCase() || "";
9938
10032
  const role = formData.get("role")?.toString()?.trim() || "viewer";
9939
- const firstName = chunkFYEDK7K7_cjs.sanitizeInput(formData.get("first_name")?.toString());
9940
- const lastName = chunkFYEDK7K7_cjs.sanitizeInput(formData.get("last_name")?.toString());
10033
+ const firstName = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("first_name")?.toString());
10034
+ const lastName = chunkZWV3EBZ7_cjs.sanitizeInput(formData.get("last_name")?.toString());
9941
10035
  if (!email || !firstName || !lastName) {
9942
10036
  return c.json({ error: "Email, first name, and last name are required" }, 400);
9943
10037
  }
@@ -9975,7 +10069,7 @@ userRoutes.post("/invite-user", async (c) => {
9975
10069
  Date.now(),
9976
10070
  Date.now()
9977
10071
  ).run();
9978
- await chunk7I5INVNR_cjs.logActivity(
10072
+ await chunkYYV3XQOQ_cjs.logActivity(
9979
10073
  db,
9980
10074
  user.userId,
9981
10075
  "user!.invite_sent",
@@ -10032,7 +10126,7 @@ userRoutes.post("/resend-invitation/:id", async (c) => {
10032
10126
  Date.now(),
10033
10127
  userId
10034
10128
  ).run();
10035
- await chunk7I5INVNR_cjs.logActivity(
10129
+ await chunkYYV3XQOQ_cjs.logActivity(
10036
10130
  db,
10037
10131
  user.userId,
10038
10132
  "user!.invitation_resent",
@@ -10068,7 +10162,7 @@ userRoutes.delete("/cancel-invitation/:id", async (c) => {
10068
10162
  }
10069
10163
  const deleteStmt = db.prepare(`DELETE FROM users WHERE id = ?`);
10070
10164
  await deleteStmt.bind(userId).run();
10071
- await chunk7I5INVNR_cjs.logActivity(
10165
+ await chunkYYV3XQOQ_cjs.logActivity(
10072
10166
  db,
10073
10167
  user.userId,
10074
10168
  "user!.invitation_cancelled",
@@ -10151,7 +10245,7 @@ userRoutes.get("/activity-logs", async (c) => {
10151
10245
  ...log,
10152
10246
  details: log.details ? JSON.parse(log.details) : null
10153
10247
  }));
10154
- await chunk7I5INVNR_cjs.logActivity(
10248
+ await chunkYYV3XQOQ_cjs.logActivity(
10155
10249
  db,
10156
10250
  user.userId,
10157
10251
  "activity.logs_viewed",
@@ -10258,7 +10352,7 @@ userRoutes.get("/activity-logs/export", async (c) => {
10258
10352
  csvRows.push(row.join(","));
10259
10353
  }
10260
10354
  const csvContent = csvRows.join("\n");
10261
- await chunk7I5INVNR_cjs.logActivity(
10355
+ await chunkYYV3XQOQ_cjs.logActivity(
10262
10356
  db,
10263
10357
  user.userId,
10264
10358
  "activity.logs_exported",
@@ -11597,7 +11691,7 @@ var fileValidationSchema2 = zod.z.object({
11597
11691
  // 50MB max
11598
11692
  });
11599
11693
  var adminMediaRoutes = new hono.Hono();
11600
- adminMediaRoutes.use("*", chunk7I5INVNR_cjs.requireAuth());
11694
+ adminMediaRoutes.use("*", chunkYYV3XQOQ_cjs.requireAuth());
11601
11695
  adminMediaRoutes.get("/", async (c) => {
11602
11696
  try {
11603
11697
  const user = c.get("user");
@@ -12183,7 +12277,7 @@ adminMediaRoutes.put("/:id", async (c) => {
12183
12277
  `);
12184
12278
  }
12185
12279
  });
12186
- adminMediaRoutes.delete("/cleanup", chunk7I5INVNR_cjs.requireRole("admin"), async (c) => {
12280
+ adminMediaRoutes.delete("/cleanup", chunkYYV3XQOQ_cjs.requireRole("admin"), async (c) => {
12187
12281
  try {
12188
12282
  const db = c.env.DB;
12189
12283
  const allMediaStmt = db.prepare("SELECT id, r2_key, filename FROM media WHERE deleted_at IS NULL");
@@ -13317,6 +13411,9 @@ function renderAuthSettingsForm(settings) {
13317
13411
  }
13318
13412
 
13319
13413
  // src/templates/pages/admin-plugin-settings.template.ts
13414
+ function escapeHtmlAttr(value) {
13415
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
13416
+ }
13320
13417
  function renderPluginSettingsPage(data) {
13321
13418
  const { plugin, activity = [], user } = data;
13322
13419
  const pageContent = `
@@ -13594,6 +13691,7 @@ function renderSettingsTab(plugin) {
13594
13691
  const settings = plugin.settings || {};
13595
13692
  const isSeedDataPlugin = plugin.id === "seed-data" || plugin.name === "seed-data";
13596
13693
  const isAuthPlugin = plugin.id === "core-auth" || plugin.name === "core-auth";
13694
+ const isTurnstilePlugin = plugin.id === "turnstile" || plugin.name === "turnstile";
13597
13695
  return `
13598
13696
  ${isSeedDataPlugin ? `
13599
13697
  <div class="backdrop-blur-md bg-black/20 rounded-xl border border-white/10 shadow-xl p-6 mb-6">
@@ -13620,12 +13718,15 @@ function renderSettingsTab(plugin) {
13620
13718
  ${isAuthPlugin ? `
13621
13719
  <h2 class="text-xl font-semibold text-white mb-4">Authentication Settings</h2>
13622
13720
  <p class="text-gray-400 mb-6">Configure user registration fields and validation rules.</p>
13721
+ ` : isTurnstilePlugin ? `
13722
+ <h2 class="text-xl font-semibold text-white mb-4">Cloudflare Turnstile Settings</h2>
13723
+ <p class="text-gray-400 mb-6">Configure CAPTCHA-free bot protection for your forms.</p>
13623
13724
  ` : `
13624
13725
  <h2 class="text-xl font-semibold text-white mb-4">Plugin Settings</h2>
13625
13726
  `}
13626
13727
 
13627
13728
  <form id="settings-form" class="space-y-6">
13628
- ${isAuthPlugin && Object.keys(settings).length > 0 ? renderAuthSettingsForm(settings) : Object.keys(settings).length > 0 ? renderSettingsFields(settings) : renderNoSettings(plugin)}
13729
+ ${isAuthPlugin && Object.keys(settings).length > 0 ? renderAuthSettingsForm(settings) : isTurnstilePlugin && Object.keys(settings).length > 0 ? renderTurnstileSettingsForm(settings) : Object.keys(settings).length > 0 ? renderSettingsFields(settings) : renderNoSettings(plugin)}
13629
13730
 
13630
13731
  ${Object.keys(settings).length > 0 ? `
13631
13732
  <div class="flex items-center justify-end pt-6 border-t border-white/10">
@@ -13689,6 +13790,80 @@ function renderSettingsFields(settings) {
13689
13790
  }
13690
13791
  }).join("");
13691
13792
  }
13793
+ function renderTurnstileSettingsForm(settings) {
13794
+ const inputClass = "backdrop-blur-sm bg-white/10 border border-white/20 rounded-lg px-3 py-2 text-white placeholder-gray-300 focus:border-blue-400 focus:outline-none transition-colors w-full";
13795
+ const selectClass = "backdrop-blur-sm bg-zinc-800 border border-white/20 rounded-lg px-3 py-2 text-white focus:border-blue-400 focus:outline-none transition-colors w-full [&>option]:bg-zinc-800 [&>option]:text-white";
13796
+ return `
13797
+ <!-- Enable Toggle -->
13798
+ <div class="flex items-center justify-between">
13799
+ <div>
13800
+ <label for="setting_enabled" class="text-sm font-medium text-gray-300">Enable Turnstile</label>
13801
+ <p class="text-xs text-gray-400">Enable or disable Turnstile verification globally</p>
13802
+ </div>
13803
+ <label class="relative inline-flex items-center cursor-pointer">
13804
+ <input type="checkbox" name="setting_enabled" id="setting_enabled" ${settings.enabled ? "checked" : ""} class="sr-only peer">
13805
+ <div class="w-11 h-6 bg-gray-600 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-800 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-600"></div>
13806
+ </label>
13807
+ </div>
13808
+
13809
+ <!-- Site Key -->
13810
+ <div>
13811
+ <label for="setting_siteKey" class="block text-sm font-medium text-gray-300 mb-2">Site Key</label>
13812
+ <input type="text" name="setting_siteKey" id="setting_siteKey" value="${escapeHtmlAttr(settings.siteKey || "")}" placeholder="0x4AAAAAAAA..." class="${inputClass}">
13813
+ <p class="text-xs text-gray-400 mt-1">Your Cloudflare Turnstile site key (public)</p>
13814
+ </div>
13815
+
13816
+ <!-- Secret Key -->
13817
+ <div>
13818
+ <label for="setting_secretKey" class="block text-sm font-medium text-gray-300 mb-2">Secret Key</label>
13819
+ <input type="password" name="setting_secretKey" id="setting_secretKey" value="${escapeHtmlAttr(settings.secretKey || "")}" placeholder="0x4AAAAAAAA..." class="${inputClass}">
13820
+ <p class="text-xs text-gray-400 mt-1">Your Cloudflare Turnstile secret key (private)</p>
13821
+ </div>
13822
+
13823
+ <!-- Theme -->
13824
+ <div>
13825
+ <label for="setting_theme" class="block text-sm font-medium text-gray-300 mb-2">Widget Theme</label>
13826
+ <select name="setting_theme" id="setting_theme" class="${selectClass}" style="color: white; background-color: rgb(39, 39, 42);">
13827
+ <option value="auto" ${settings.theme === "auto" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Auto (matches page theme)</option>
13828
+ <option value="light" ${settings.theme === "light" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Light</option>
13829
+ <option value="dark" ${settings.theme === "dark" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Dark</option>
13830
+ </select>
13831
+ <p class="text-xs text-gray-400 mt-1">Visual appearance of the Turnstile widget</p>
13832
+ </div>
13833
+
13834
+ <!-- Size -->
13835
+ <div>
13836
+ <label for="setting_size" class="block text-sm font-medium text-gray-300 mb-2">Widget Size</label>
13837
+ <select name="setting_size" id="setting_size" class="${selectClass}" style="color: white; background-color: rgb(39, 39, 42);">
13838
+ <option value="normal" ${settings.size === "normal" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Normal (300x65px)</option>
13839
+ <option value="compact" ${settings.size === "compact" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Compact (130x120px)</option>
13840
+ </select>
13841
+ <p class="text-xs text-gray-400 mt-1">Size of the Turnstile challenge widget</p>
13842
+ </div>
13843
+
13844
+ <!-- Widget Mode -->
13845
+ <div>
13846
+ <label for="setting_mode" class="block text-sm font-medium text-gray-300 mb-2">Widget Mode</label>
13847
+ <select name="setting_mode" id="setting_mode" class="${selectClass}" style="color: white; background-color: rgb(39, 39, 42);">
13848
+ <option value="managed" ${!settings.mode || settings.mode === "managed" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Managed (Recommended) - Adaptive challenge</option>
13849
+ <option value="non-interactive" ${settings.mode === "non-interactive" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Non-Interactive - Always visible, minimal friction</option>
13850
+ <option value="invisible" ${settings.mode === "invisible" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Invisible - No visible widget</option>
13851
+ </select>
13852
+ <p class="text-xs text-gray-400 mt-1"><strong>Managed:</strong> Shows challenge only when needed. <strong>Non-Interactive:</strong> Always shows but doesn't require interaction. <strong>Invisible:</strong> Runs in background without UI.</p>
13853
+ </div>
13854
+
13855
+ <!-- Appearance (Pre-clearance) -->
13856
+ <div>
13857
+ <label for="setting_appearance" class="block text-sm font-medium text-gray-300 mb-2">Pre-clearance / Appearance</label>
13858
+ <select name="setting_appearance" id="setting_appearance" class="${selectClass}" style="color: white; background-color: rgb(39, 39, 42);">
13859
+ <option value="always" ${!settings.appearance || settings.appearance === "always" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Always - Pre-clearance enabled (verifies immediately)</option>
13860
+ <option value="execute" ${settings.appearance === "execute" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Execute - Challenge on form submit</option>
13861
+ <option value="interaction-only" ${settings.appearance === "interaction-only" ? "selected" : ""} style="background-color: rgb(39, 39, 42); color: white;">Interaction Only - Only after user interaction</option>
13862
+ </select>
13863
+ <p class="text-xs text-gray-400 mt-1">Controls when Turnstile verification occurs. <strong>Always:</strong> Verifies immediately (pre-clearance). <strong>Execute:</strong> Verifies on form submit. <strong>Interaction Only:</strong> Only after user interaction.</p>
13864
+ </div>
13865
+ `;
13866
+ }
13692
13867
  function renderNoSettings(plugin) {
13693
13868
  if (plugin.id === "seed-data" || plugin.name === "seed-data") {
13694
13869
  return `
@@ -13828,7 +14003,7 @@ function formatTimestamp(timestamp) {
13828
14003
 
13829
14004
  // src/routes/admin-plugins.ts
13830
14005
  var adminPluginRoutes = new hono.Hono();
13831
- adminPluginRoutes.use("*", chunk7I5INVNR_cjs.requireAuth());
14006
+ adminPluginRoutes.use("*", chunkYYV3XQOQ_cjs.requireAuth());
13832
14007
  var AVAILABLE_PLUGINS = [
13833
14008
  {
13834
14009
  id: "third-party-faq",
@@ -13920,6 +14095,19 @@ var AVAILABLE_PLUGINS = [
13920
14095
  permissions: [],
13921
14096
  dependencies: [],
13922
14097
  is_core: false
14098
+ },
14099
+ {
14100
+ id: "turnstile",
14101
+ name: "turnstile-plugin",
14102
+ display_name: "Cloudflare Turnstile",
14103
+ description: "CAPTCHA-free bot protection for forms using Cloudflare Turnstile. Provides seamless spam prevention with configurable modes, themes, and pre-clearance options.",
14104
+ version: "1.0.0",
14105
+ author: "SonicJS Team",
14106
+ category: "security",
14107
+ icon: "\u{1F6E1}\uFE0F",
14108
+ permissions: [],
14109
+ dependencies: [],
14110
+ is_core: true
13923
14111
  }
13924
14112
  ];
13925
14113
  adminPluginRoutes.get("/", async (c) => {
@@ -14290,6 +14478,33 @@ adminPluginRoutes.post("/install", async (c) => {
14290
14478
  });
14291
14479
  return c.json({ success: true, plugin: easyMdxPlugin2 });
14292
14480
  }
14481
+ if (body.name === "turnstile-plugin") {
14482
+ const turnstilePlugin = await pluginService.installPlugin({
14483
+ id: "turnstile",
14484
+ name: "turnstile-plugin",
14485
+ display_name: "Cloudflare Turnstile",
14486
+ description: "CAPTCHA-free bot protection for forms using Cloudflare Turnstile. Provides seamless spam prevention with configurable modes, themes, and pre-clearance options.",
14487
+ version: "1.0.0",
14488
+ author: "SonicJS Team",
14489
+ category: "security",
14490
+ icon: "\u{1F6E1}\uFE0F",
14491
+ permissions: [],
14492
+ dependencies: [],
14493
+ is_core: true,
14494
+ settings: {
14495
+ siteKey: "",
14496
+ secretKey: "",
14497
+ theme: "auto",
14498
+ size: "normal",
14499
+ mode: "managed",
14500
+ appearance: "always",
14501
+ preClearanceEnabled: false,
14502
+ preClearanceLevel: "managed",
14503
+ enabled: false
14504
+ }
14505
+ });
14506
+ return c.json({ success: true, plugin: turnstilePlugin });
14507
+ }
14293
14508
  return c.json({ error: "Plugin not found in registry" }, 404);
14294
14509
  } catch (error) {
14295
14510
  console.error("Error installing plugin:", error);
@@ -15119,7 +15334,7 @@ function renderLogConfigPage(data) {
15119
15334
 
15120
15335
  // src/routes/admin-logs.ts
15121
15336
  var adminLogsRoutes = new hono.Hono();
15122
- adminLogsRoutes.use("*", chunk7I5INVNR_cjs.requireAuth());
15337
+ adminLogsRoutes.use("*", chunkYYV3XQOQ_cjs.requireAuth());
15123
15338
  adminLogsRoutes.get("/", async (c) => {
15124
15339
  try {
15125
15340
  const user = c.get("user");
@@ -17447,9 +17662,9 @@ function renderStorageUsage(databaseSizeBytes, mediaSizeBytes) {
17447
17662
  }
17448
17663
 
17449
17664
  // src/routes/admin-dashboard.ts
17450
- var VERSION = chunkFYEDK7K7_cjs.getCoreVersion();
17665
+ var VERSION = chunkZWV3EBZ7_cjs.getCoreVersion();
17451
17666
  var router = new hono.Hono();
17452
- router.use("*", chunk7I5INVNR_cjs.requireAuth());
17667
+ router.use("*", chunkYYV3XQOQ_cjs.requireAuth());
17453
17668
  router.get("/", async (c) => {
17454
17669
  const user = c.get("user");
17455
17670
  try {
@@ -19207,7 +19422,7 @@ function renderCollectionFormPage(data) {
19207
19422
 
19208
19423
  // src/routes/admin-collections.ts
19209
19424
  var adminCollectionsRoutes = new hono.Hono();
19210
- adminCollectionsRoutes.use("*", chunk7I5INVNR_cjs.requireAuth());
19425
+ adminCollectionsRoutes.use("*", chunkYYV3XQOQ_cjs.requireAuth());
19211
19426
  adminCollectionsRoutes.get("/", async (c) => {
19212
19427
  try {
19213
19428
  const user = c.get("user");
@@ -19463,16 +19678,30 @@ adminCollectionsRoutes.get("/:id", async (c) => {
19463
19678
  const schema = typeof collection.schema === "string" ? JSON.parse(collection.schema) : collection.schema;
19464
19679
  if (schema && schema.properties) {
19465
19680
  let fieldOrder = 0;
19466
- fields = Object.entries(schema.properties).map(([fieldName, fieldConfig]) => ({
19467
- id: `schema-${fieldName}`,
19468
- field_name: fieldName,
19469
- field_type: fieldConfig.type || "string",
19470
- field_label: fieldConfig.title || fieldName,
19471
- field_options: fieldConfig,
19472
- field_order: fieldOrder++,
19473
- is_required: fieldConfig.required === true || schema.required && schema.required.includes(fieldName),
19474
- is_searchable: fieldConfig.searchable === true || false
19475
- }));
19681
+ fields = Object.entries(schema.properties).map(([fieldName, fieldConfig]) => {
19682
+ let fieldType = fieldConfig.type || "string";
19683
+ if (fieldConfig.enum) {
19684
+ fieldType = "select";
19685
+ } else if (fieldConfig.format === "richtext") {
19686
+ fieldType = "richtext";
19687
+ } else if (fieldConfig.format === "media") {
19688
+ fieldType = "media";
19689
+ } else if (fieldConfig.format === "date-time") {
19690
+ fieldType = "date";
19691
+ } else if (fieldConfig.type === "slug" || fieldConfig.format === "slug") {
19692
+ fieldType = "slug";
19693
+ }
19694
+ return {
19695
+ id: `schema-${fieldName}`,
19696
+ field_name: fieldName,
19697
+ field_type: fieldType,
19698
+ field_label: fieldConfig.title || fieldName,
19699
+ field_options: fieldConfig,
19700
+ field_order: fieldOrder++,
19701
+ is_required: fieldConfig.required === true || schema.required && schema.required.includes(fieldName),
19702
+ is_searchable: fieldConfig.searchable === true || false
19703
+ };
19704
+ });
19476
19705
  }
19477
19706
  } catch (e) {
19478
19707
  console.error("Error parsing collection schema:", e);
@@ -19687,6 +19916,9 @@ adminCollectionsRoutes.post("/:id/fields", async (c) => {
19687
19916
  fieldConfig.enum = parsedOptions.options || [];
19688
19917
  } else if (fieldType === "media") {
19689
19918
  fieldConfig.format = "media";
19919
+ } else if (fieldType === "slug") {
19920
+ fieldConfig.type = "slug";
19921
+ fieldConfig.format = "slug";
19690
19922
  } else if (fieldType === "quill") {
19691
19923
  fieldConfig.type = "quill";
19692
19924
  } else if (fieldType === "mdxeditor") {
@@ -21370,7 +21602,7 @@ function renderDatabaseToolsSettings(settings) {
21370
21602
 
21371
21603
  // src/routes/admin-settings.ts
21372
21604
  var adminSettingsRoutes = new hono.Hono();
21373
- adminSettingsRoutes.use("*", chunk7I5INVNR_cjs.requireAuth());
21605
+ adminSettingsRoutes.use("*", chunkYYV3XQOQ_cjs.requireAuth());
21374
21606
  function getMockSettings(user) {
21375
21607
  return {
21376
21608
  general: {
@@ -21538,7 +21770,7 @@ adminSettingsRoutes.get("/database-tools", (c) => {
21538
21770
  adminSettingsRoutes.get("/api/migrations/status", async (c) => {
21539
21771
  try {
21540
21772
  const db = c.env.DB;
21541
- const migrationService = new chunk2MI3LZFH_cjs.MigrationService(db);
21773
+ const migrationService = new chunkI4V3VZWF_cjs.MigrationService(db);
21542
21774
  const status = await migrationService.getMigrationStatus();
21543
21775
  return c.json({
21544
21776
  success: true,
@@ -21562,7 +21794,7 @@ adminSettingsRoutes.post("/api/migrations/run", async (c) => {
21562
21794
  }, 403);
21563
21795
  }
21564
21796
  const db = c.env.DB;
21565
- const migrationService = new chunk2MI3LZFH_cjs.MigrationService(db);
21797
+ const migrationService = new chunkI4V3VZWF_cjs.MigrationService(db);
21566
21798
  const result = await migrationService.runPendingMigrations();
21567
21799
  return c.json({
21568
21800
  success: result.success,
@@ -21580,7 +21812,7 @@ adminSettingsRoutes.post("/api/migrations/run", async (c) => {
21580
21812
  adminSettingsRoutes.get("/api/migrations/validate", async (c) => {
21581
21813
  try {
21582
21814
  const db = c.env.DB;
21583
- const migrationService = new chunk2MI3LZFH_cjs.MigrationService(db);
21815
+ const migrationService = new chunkI4V3VZWF_cjs.MigrationService(db);
21584
21816
  const validation = await migrationService.validateSchema();
21585
21817
  return c.json({
21586
21818
  success: true,
@@ -21804,7 +22036,6 @@ var ROUTES_INFO = {
21804
22036
  reference: "https://github.com/sonicjs/sonicjs"
21805
22037
  };
21806
22038
 
21807
- exports.PluginBuilder = PluginBuilder;
21808
22039
  exports.ROUTES_INFO = ROUTES_INFO;
21809
22040
  exports.adminCheckboxRoutes = adminCheckboxRoutes;
21810
22041
  exports.adminCollectionsRoutes = adminCollectionsRoutes;
@@ -21826,5 +22057,5 @@ exports.checkAdminUserExists = checkAdminUserExists;
21826
22057
  exports.router = router;
21827
22058
  exports.test_cleanup_default = test_cleanup_default;
21828
22059
  exports.userRoutes = userRoutes;
21829
- //# sourceMappingURL=chunk-A4SVOGG6.cjs.map
21830
- //# sourceMappingURL=chunk-A4SVOGG6.cjs.map
22060
+ //# sourceMappingURL=chunk-UAQL2VWX.cjs.map
22061
+ //# sourceMappingURL=chunk-UAQL2VWX.cjs.map