@kyro-cms/core 0.9.0 → 0.9.1

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 (248) hide show
  1. package/README.md +57 -589
  2. package/dist/{WebhookService-118ZTFis.d.ts → WebhookService-CUTb9XOy.d.ts} +1 -1
  3. package/dist/{WebhookService-AefJfqX0.d.cts → WebhookService-Yg2UEOB4.d.cts} +1 -1
  4. package/dist/api-handler-graphql.cjs +44 -0
  5. package/dist/api-handler-graphql.cjs.map +1 -0
  6. package/dist/api-handler-graphql.d.cts +6 -0
  7. package/dist/api-handler-graphql.d.ts +6 -0
  8. package/dist/api-handler-graphql.js +41 -0
  9. package/dist/api-handler-graphql.js.map +1 -0
  10. package/dist/api-handler-trpc.cjs +38 -0
  11. package/dist/api-handler-trpc.cjs.map +1 -0
  12. package/dist/api-handler-trpc.d.cts +5 -0
  13. package/dist/api-handler-trpc.d.ts +5 -0
  14. package/dist/api-handler-trpc.js +36 -0
  15. package/dist/api-handler-trpc.js.map +1 -0
  16. package/dist/api-handler.cjs +31 -97
  17. package/dist/api-handler.cjs.map +1 -1
  18. package/dist/api-handler.d.cts +2 -1
  19. package/dist/api-handler.d.ts +2 -1
  20. package/dist/api-handler.js +19 -95
  21. package/dist/api-handler.js.map +1 -1
  22. package/dist/{tenant-B1YB0Jy8.d.ts → base-B71y_EAF.d.cts} +6 -12
  23. package/dist/{tenant-Cpeveji6.d.cts → base-DaqY2GhA.d.ts} +6 -12
  24. package/dist/bootstrap-5NLASFOG.cjs +32 -0
  25. package/dist/{bootstrap-AKAUP6F6.cjs.map → bootstrap-5NLASFOG.cjs.map} +1 -1
  26. package/dist/bootstrap-T5BK77LD.js +7 -0
  27. package/dist/{bootstrap-JCML6NFO.js.map → bootstrap-T5BK77LD.js.map} +1 -1
  28. package/dist/{chunk-35U3FROB.js → chunk-22M4O4ZJ.js} +607 -63
  29. package/dist/chunk-22M4O4ZJ.js.map +1 -0
  30. package/dist/chunk-2HZRBATX.cjs +253 -0
  31. package/dist/chunk-2HZRBATX.cjs.map +1 -0
  32. package/dist/{chunk-VJT6P4N6.cjs → chunk-3HR772HI.cjs} +199 -32
  33. package/dist/chunk-3HR772HI.cjs.map +1 -0
  34. package/dist/chunk-3KTWGODI.cjs +178 -0
  35. package/dist/chunk-3KTWGODI.cjs.map +1 -0
  36. package/dist/{chunk-QXIQWPAP.js → chunk-3UK5XBVJ.js} +4 -134
  37. package/dist/chunk-3UK5XBVJ.js.map +1 -0
  38. package/dist/{chunk-FXYP2HA6.js → chunk-4AO3A3JM.js} +48 -4
  39. package/dist/chunk-4AO3A3JM.js.map +1 -0
  40. package/dist/chunk-4M7X5HAB.cjs +173 -0
  41. package/dist/chunk-4M7X5HAB.cjs.map +1 -0
  42. package/dist/chunk-5EPFQUQD.js +3243 -0
  43. package/dist/chunk-5EPFQUQD.js.map +1 -0
  44. package/dist/{chunk-Y3N7UUDO.js → chunk-7OGPN7MP.js} +5 -2
  45. package/dist/chunk-7OGPN7MP.js.map +1 -0
  46. package/dist/{chunk-WOWUL7ZY.js → chunk-AL5KX63J.js} +4 -3
  47. package/dist/chunk-AL5KX63J.js.map +1 -0
  48. package/dist/{chunk-2OL4O2TH.cjs → chunk-C36TMDTY.cjs} +66 -61
  49. package/dist/chunk-C36TMDTY.cjs.map +1 -0
  50. package/dist/{chunk-ES5HNFFT.js → chunk-CF7OL6HR.js} +4 -2
  51. package/dist/chunk-CF7OL6HR.js.map +1 -0
  52. package/dist/chunk-CJONKRHJ.js +162 -0
  53. package/dist/chunk-CJONKRHJ.js.map +1 -0
  54. package/dist/{chunk-2KVHZE6O.cjs → chunk-COIASRDK.cjs} +202 -46
  55. package/dist/chunk-COIASRDK.cjs.map +1 -0
  56. package/dist/chunk-DEVFAKCQ.cjs +3291 -0
  57. package/dist/chunk-DEVFAKCQ.cjs.map +1 -0
  58. package/dist/{chunk-3ZFYL34R.js → chunk-DYTZ6FQ7.js} +12 -185
  59. package/dist/chunk-DYTZ6FQ7.js.map +1 -0
  60. package/dist/{chunk-QPPDLRNR.js → chunk-EJN2PAOE.js} +197 -41
  61. package/dist/chunk-EJN2PAOE.js.map +1 -0
  62. package/dist/chunk-FAXU7BMP.js +220 -0
  63. package/dist/chunk-FAXU7BMP.js.map +1 -0
  64. package/dist/{chunk-OHVB4AJ7.js → chunk-FOPGUM27.js} +22 -17
  65. package/dist/chunk-FOPGUM27.js.map +1 -0
  66. package/dist/chunk-GAOXD3XT.js +175 -0
  67. package/dist/chunk-GAOXD3XT.js.map +1 -0
  68. package/dist/{chunk-4DA7QPLA.cjs → chunk-GXFOGU7N.cjs} +5 -2
  69. package/dist/chunk-GXFOGU7N.cjs.map +1 -0
  70. package/dist/{chunk-I7HHI6QV.cjs → chunk-IDVRRRAK.cjs} +17 -9
  71. package/dist/chunk-IDVRRRAK.cjs.map +1 -0
  72. package/dist/{chunk-WQBRWOQT.cjs → chunk-JOPVMWTM.cjs} +3 -2
  73. package/dist/chunk-JOPVMWTM.cjs.map +1 -0
  74. package/dist/chunk-KC2GDBLS.cjs +84 -0
  75. package/dist/chunk-KC2GDBLS.cjs.map +1 -0
  76. package/dist/{chunk-K7JPTH3G.cjs → chunk-KNRSROWB.cjs} +132 -74
  77. package/dist/chunk-KNRSROWB.cjs.map +1 -0
  78. package/dist/{chunk-3AJE4SEG.js → chunk-KPA4AN4R.js} +125 -67
  79. package/dist/chunk-KPA4AN4R.js.map +1 -0
  80. package/dist/{chunk-QUW2RZTM.cjs → chunk-L46ROHUS.cjs} +51 -7
  81. package/dist/chunk-L46ROHUS.cjs.map +1 -0
  82. package/dist/chunk-L4EZKIEX.js +185 -0
  83. package/dist/chunk-L4EZKIEX.js.map +1 -0
  84. package/dist/{chunk-REK7AYOC.js → chunk-L5UKKZQN.js} +199 -32
  85. package/dist/chunk-L5UKKZQN.js.map +1 -0
  86. package/dist/chunk-NKPKR5BW.cjs +188 -0
  87. package/dist/chunk-NKPKR5BW.cjs.map +1 -0
  88. package/dist/{chunk-Y3QQN7PN.js → chunk-P2HKJ7P5.js} +13 -4
  89. package/dist/chunk-P2HKJ7P5.js.map +1 -0
  90. package/dist/{chunk-SA7NSSIQ.cjs → chunk-PI73NNOK.cjs} +13 -187
  91. package/dist/chunk-PI73NNOK.cjs.map +1 -0
  92. package/dist/{chunk-HXRD4B37.js → chunk-PU2Z5VWF.js} +1279 -556
  93. package/dist/chunk-PU2Z5VWF.js.map +1 -0
  94. package/dist/{chunk-H727JIG7.js → chunk-Q72BOAPK.js} +16 -8
  95. package/dist/chunk-Q72BOAPK.js.map +1 -0
  96. package/dist/{chunk-IBG6V56E.cjs → chunk-QFLB4EIJ.cjs} +2 -139
  97. package/dist/chunk-QFLB4EIJ.cjs.map +1 -0
  98. package/dist/{chunk-YVUJBEXE.cjs → chunk-RAMGUDJN.cjs} +16 -7
  99. package/dist/chunk-RAMGUDJN.cjs.map +1 -0
  100. package/dist/{chunk-LINKCEG4.cjs → chunk-ROJHKAQ4.cjs} +617 -73
  101. package/dist/chunk-ROJHKAQ4.cjs.map +1 -0
  102. package/dist/{chunk-5KVM3WEY.cjs → chunk-RSF3UU7H.cjs} +1330 -602
  103. package/dist/chunk-RSF3UU7H.cjs.map +1 -0
  104. package/dist/{chunk-V3LKPM3O.cjs → chunk-SHTTJMLT.cjs} +4 -2
  105. package/dist/chunk-SHTTJMLT.cjs.map +1 -0
  106. package/dist/chunk-SPBTLUN6.js +92 -0
  107. package/dist/chunk-SPBTLUN6.js.map +1 -0
  108. package/dist/{chunk-57P6MJKC.js → chunk-TXSZFA4G.js} +3 -3
  109. package/dist/chunk-TXSZFA4G.js.map +1 -0
  110. package/dist/chunk-UERVXYVK.cjs +99 -0
  111. package/dist/chunk-UERVXYVK.cjs.map +1 -0
  112. package/dist/{chunk-PDYFVNUX.cjs → chunk-V2TVSCV5.cjs} +16 -23
  113. package/dist/chunk-V2TVSCV5.cjs.map +1 -0
  114. package/dist/{chunk-DXHRBMGB.js → chunk-VO35MNPH.js} +12 -19
  115. package/dist/chunk-VO35MNPH.js.map +1 -0
  116. package/dist/{chunk-IA6AU5PI.cjs → chunk-WNCYAKF3.cjs} +3 -3
  117. package/dist/chunk-WNCYAKF3.cjs.map +1 -0
  118. package/dist/chunk-XEB7PH2E.js +81 -0
  119. package/dist/chunk-XEB7PH2E.js.map +1 -0
  120. package/dist/cli/index.cjs +5 -5
  121. package/dist/cli/index.cjs.map +1 -1
  122. package/dist/cli/index.js +5 -5
  123. package/dist/cli/index.js.map +1 -1
  124. package/dist/client.cjs +3 -3
  125. package/dist/client.d.cts +3 -3
  126. package/dist/client.d.ts +3 -3
  127. package/dist/client.js +1 -1
  128. package/dist/drizzle/index.cjs +14 -13
  129. package/dist/drizzle/index.d.cts +9 -7
  130. package/dist/drizzle/index.d.ts +9 -7
  131. package/dist/drizzle/index.js +5 -4
  132. package/dist/fields/index.cjs +21 -37
  133. package/dist/fields/index.d.cts +2 -22
  134. package/dist/fields/index.d.ts +2 -22
  135. package/dist/fields/index.js +1 -1
  136. package/dist/graphql/index.cjs +5 -4
  137. package/dist/graphql/index.d.cts +5 -3
  138. package/dist/graphql/index.d.ts +5 -3
  139. package/dist/graphql/index.js +3 -2
  140. package/dist/index-CJXPB_ot.d.ts +276 -0
  141. package/dist/index-CaTNnLGd.d.cts +276 -0
  142. package/dist/index.cjs +304 -162
  143. package/dist/index.cjs.map +1 -1
  144. package/dist/index.d.cts +129 -205
  145. package/dist/index.d.ts +129 -205
  146. package/dist/index.js +172 -33
  147. package/dist/index.js.map +1 -1
  148. package/dist/integration.cjs +2 -2
  149. package/dist/integration.js +1 -1
  150. package/dist/mongo-auth-adapter-ISOM7FSS.cjs +17 -0
  151. package/dist/{mongo-auth-adapter-NHHUJHVH.cjs.map → mongo-auth-adapter-ISOM7FSS.cjs.map} +1 -1
  152. package/dist/mongo-auth-adapter-MO6STCV3.js +4 -0
  153. package/dist/{mongo-auth-adapter-NJQUUCTP.js.map → mongo-auth-adapter-MO6STCV3.js.map} +1 -1
  154. package/dist/mongodb/index.cjs +8 -7
  155. package/dist/mongodb/index.d.cts +5 -7
  156. package/dist/mongodb/index.d.ts +5 -7
  157. package/dist/mongodb/index.js +4 -3
  158. package/dist/postgres-auth-adapter-DWDR7P5G.js +5 -0
  159. package/dist/{postgres-auth-adapter-3T2NKTSE.js.map → postgres-auth-adapter-DWDR7P5G.js.map} +1 -1
  160. package/dist/postgres-auth-adapter-WRWSJD4E.cjs +14 -0
  161. package/dist/{postgres-auth-adapter-7IEENCKQ.cjs.map → postgres-auth-adapter-WRWSJD4E.cjs.map} +1 -1
  162. package/dist/redis-adapter-HGTPWIGV.js +4 -0
  163. package/dist/{redis-adapter-VQXD7ESY.js.map → redis-adapter-HGTPWIGV.js.map} +1 -1
  164. package/dist/redis-adapter-KJ3YOOT6.cjs +13 -0
  165. package/dist/{redis-adapter-D2E2S3GB.cjs.map → redis-adapter-KJ3YOOT6.cjs.map} +1 -1
  166. package/dist/rest/index.cjs +15 -14
  167. package/dist/rest/index.d.cts +4 -4
  168. package/dist/rest/index.d.ts +4 -4
  169. package/dist/rest/index.js +13 -12
  170. package/dist/{schema-5PHL5IVB.js → schema-6I5OFR4Z.js} +3 -3
  171. package/dist/{schema-5PHL5IVB.js.map → schema-6I5OFR4Z.js.map} +1 -1
  172. package/dist/{schema-37SE2F4B.cjs → schema-TTFE4467.cjs} +14 -14
  173. package/dist/{schema-37SE2F4B.cjs.map → schema-TTFE4467.cjs.map} +1 -1
  174. package/dist/sqlite-adapter-6GEUSVXQ.js +4 -0
  175. package/dist/{sqlite-adapter-TR3U3W6Q.js.map → sqlite-adapter-6GEUSVXQ.js.map} +1 -1
  176. package/dist/sqlite-adapter-CSIZE5SX.cjs +13 -0
  177. package/dist/{sqlite-adapter-LVK5PS4T.cjs.map → sqlite-adapter-CSIZE5SX.cjs.map} +1 -1
  178. package/dist/templates/index.cjs +133 -31
  179. package/dist/templates/index.d.cts +52 -9
  180. package/dist/templates/index.d.ts +52 -9
  181. package/dist/templates/index.js +3 -1
  182. package/dist/trpc/index.cjs +13 -12
  183. package/dist/trpc/index.d.cts +55 -49
  184. package/dist/trpc/index.d.ts +55 -49
  185. package/dist/trpc/index.js +4 -3
  186. package/dist/{types-D6ZLRGbH.d.cts → types-CpjuXbe7.d.cts} +2 -0
  187. package/dist/{types-D6ZLRGbH.d.ts → types-CpjuXbe7.d.ts} +2 -0
  188. package/dist/{types-Bs1up4yP.d.ts → types-CyCQ6SAI.d.ts} +28 -2
  189. package/dist/{types-J3R9nVsZ.d.cts → types-DJxD9394.d.cts} +28 -2
  190. package/dist/{types-VtjUxIMp.d.cts → types-Z6FBiqa2.d.cts} +35 -14
  191. package/dist/{types-VtjUxIMp.d.ts → types-Z6FBiqa2.d.ts} +35 -14
  192. package/package.json +22 -4
  193. package/dist/bootstrap-AKAUP6F6.cjs +0 -32
  194. package/dist/bootstrap-JCML6NFO.js +0 -7
  195. package/dist/chunk-2KVHZE6O.cjs.map +0 -1
  196. package/dist/chunk-2OL4O2TH.cjs.map +0 -1
  197. package/dist/chunk-35U3FROB.js.map +0 -1
  198. package/dist/chunk-3AJE4SEG.js.map +0 -1
  199. package/dist/chunk-3J4MFTI3.js +0 -3872
  200. package/dist/chunk-3J4MFTI3.js.map +0 -1
  201. package/dist/chunk-3ZFYL34R.js.map +0 -1
  202. package/dist/chunk-4DA7QPLA.cjs.map +0 -1
  203. package/dist/chunk-57P6MJKC.js.map +0 -1
  204. package/dist/chunk-5KVM3WEY.cjs.map +0 -1
  205. package/dist/chunk-6IMPH6WV.cjs +0 -3897
  206. package/dist/chunk-6IMPH6WV.cjs.map +0 -1
  207. package/dist/chunk-ATBOUGQP.cjs +0 -513
  208. package/dist/chunk-ATBOUGQP.cjs.map +0 -1
  209. package/dist/chunk-DXHRBMGB.js.map +0 -1
  210. package/dist/chunk-ES5HNFFT.js.map +0 -1
  211. package/dist/chunk-FXYP2HA6.js.map +0 -1
  212. package/dist/chunk-H727JIG7.js.map +0 -1
  213. package/dist/chunk-HXRD4B37.js.map +0 -1
  214. package/dist/chunk-I7HHI6QV.cjs.map +0 -1
  215. package/dist/chunk-IA6AU5PI.cjs.map +0 -1
  216. package/dist/chunk-IBG6V56E.cjs.map +0 -1
  217. package/dist/chunk-K7JPTH3G.cjs.map +0 -1
  218. package/dist/chunk-LINKCEG4.cjs.map +0 -1
  219. package/dist/chunk-OHVB4AJ7.js.map +0 -1
  220. package/dist/chunk-PDYFVNUX.cjs.map +0 -1
  221. package/dist/chunk-Q23JB3KL.js +0 -488
  222. package/dist/chunk-Q23JB3KL.js.map +0 -1
  223. package/dist/chunk-QPPDLRNR.js.map +0 -1
  224. package/dist/chunk-QUW2RZTM.cjs.map +0 -1
  225. package/dist/chunk-QXIQWPAP.js.map +0 -1
  226. package/dist/chunk-R3XIBBAW.cjs +0 -34
  227. package/dist/chunk-R3XIBBAW.cjs.map +0 -1
  228. package/dist/chunk-REK7AYOC.js.map +0 -1
  229. package/dist/chunk-SA7NSSIQ.cjs.map +0 -1
  230. package/dist/chunk-SDMNUYVU.js +0 -30
  231. package/dist/chunk-SDMNUYVU.js.map +0 -1
  232. package/dist/chunk-V3LKPM3O.cjs.map +0 -1
  233. package/dist/chunk-VJT6P4N6.cjs.map +0 -1
  234. package/dist/chunk-WOWUL7ZY.js.map +0 -1
  235. package/dist/chunk-WQBRWOQT.cjs.map +0 -1
  236. package/dist/chunk-Y3N7UUDO.js.map +0 -1
  237. package/dist/chunk-Y3QQN7PN.js.map +0 -1
  238. package/dist/chunk-YVUJBEXE.cjs.map +0 -1
  239. package/dist/index-CLp-DRKA.d.ts +0 -64
  240. package/dist/index-DfO7G4kN.d.cts +0 -64
  241. package/dist/mongo-auth-adapter-NHHUJHVH.cjs +0 -17
  242. package/dist/mongo-auth-adapter-NJQUUCTP.js +0 -4
  243. package/dist/postgres-auth-adapter-3T2NKTSE.js +0 -5
  244. package/dist/postgres-auth-adapter-7IEENCKQ.cjs +0 -14
  245. package/dist/redis-adapter-D2E2S3GB.cjs +0 -13
  246. package/dist/redis-adapter-VQXD7ESY.js +0 -4
  247. package/dist/sqlite-adapter-LVK5PS4T.cjs +0 -13
  248. package/dist/sqlite-adapter-TR3U3W6Q.js +0 -4
@@ -1,14 +1,14 @@
1
- import { PasswordPolicy, ConfigService, EmailTransport } from './chunk-57P6MJKC.js';
2
- import { SQLiteAuthAdapter } from './chunk-H727JIG7.js';
1
+ import { PasswordPolicy, ConfigService, EmailTransport } from './chunk-TXSZFA4G.js';
2
+ import { usersCollection } from './chunk-XEB7PH2E.js';
3
+ import { SQLiteAuthAdapter } from './chunk-Q72BOAPK.js';
3
4
  import { createAuditContext } from './chunk-P2YW545G.js';
4
- import { WEBHOOK_EVENTS, extractApiKeyFromRequest, validateApiKey, createApiKeyContext, API_KEY_COLLECTION, generateApiKey, generateApiKeyPrefix, hasApiKeyPermission } from './chunk-QXIQWPAP.js';
5
- import { buildGraphQLSchema } from './chunk-REK7AYOC.js';
6
- import { evaluateAccess } from './chunk-SDMNUYVU.js';
7
- import { genId, DrizzleAdapter } from './chunk-QPPDLRNR.js';
8
- import { PostgresAuthAdapter } from './chunk-OHVB4AJ7.js';
9
- import { MongoDBAdapter } from './chunk-DXHRBMGB.js';
10
- import { MongoDBAuthAdapter } from './chunk-Y3N7UUDO.js';
11
- import { hasPermission } from './chunk-3ZFYL34R.js';
5
+ import { WEBHOOK_EVENTS } from './chunk-3UK5XBVJ.js';
6
+ import { API_KEY_COLLECTION, generateApiKey, generateApiKeyPrefix, evaluateAccess, hasApiKeyPermission, extractApiKeyFromRequest, validateApiKey, createApiKeyContext } from './chunk-CJONKRHJ.js';
7
+ import { genId, DrizzleAdapter } from './chunk-EJN2PAOE.js';
8
+ import { PostgresAuthAdapter } from './chunk-FOPGUM27.js';
9
+ import { MongoDBAdapter } from './chunk-VO35MNPH.js';
10
+ import { MongoDBAuthAdapter } from './chunk-7OGPN7MP.js';
11
+ import { hasPermission } from './chunk-L4EZKIEX.js';
12
12
  import { __esm, __export, __toCommonJS, __require } from './chunk-Z6ZWNWWR.js';
13
13
  import crypto, { randomBytes, createHash } from 'crypto';
14
14
  import { createStorage } from 'unstorage';
@@ -17,11 +17,12 @@ import { Hono } from 'hono';
17
17
  import path, { join, basename, extname, resolve } from 'path';
18
18
  import { existsSync, readFileSync } from 'fs';
19
19
  import sharp from 'sharp';
20
- import { parse, execute } from 'graphql';
21
20
  import { mkdir, readdir, stat, rename, unlink, writeFile } from 'fs/promises';
21
+ import process2 from 'process';
22
22
  import { S3Client, ListObjectsV2Command, DeleteObjectsCommand, DeleteObjectCommand, PutObjectCommand, HeadObjectCommand, CopyObjectCommand } from '@aws-sdk/client-s3';
23
23
  import { Readable } from 'stream';
24
24
  import { Client } from 'basic-ftp';
25
+ import { zodToJsonSchema } from 'zod-to-json-schema';
25
26
 
26
27
  function setDbAdapter(adapter) {
27
28
  dbAdapter = adapter;
@@ -773,7 +774,7 @@ var AuditLogger = class {
773
774
  serializeLog(log) {
774
775
  const result = {
775
776
  id: log.id,
776
- timestamp: log.timestamp.toISOString(),
777
+ timestamp: new Date(log.timestamp).toISOString(),
777
778
  action: log.action,
778
779
  resource: log.resource,
779
780
  success: log.success ? "1" : "0"
@@ -1168,23 +1169,19 @@ var AuthRoutes = class {
1168
1169
  }
1169
1170
  }
1170
1171
  async me(req) {
1171
- try {
1172
- const session = await getCurrentUser(req);
1173
- if (!session) {
1174
- return this.errorResponse("Not authenticated", 401);
1175
- }
1176
- const user = await this.authAdapter.findUserById(session.userId);
1177
- if (!user) {
1178
- return this.errorResponse("User not found", 404);
1179
- }
1180
- return this.jsonResponse({
1181
- success: true,
1182
- user: this.sanitizeUser(user)
1183
- });
1184
- } catch (error) {
1185
- console.error("[AuthRoutes.me] Authentication failed:", error.message);
1186
- return this.errorResponse("Authentication failed", 401);
1172
+ const session = await getCurrentUser(req);
1173
+ if (!session) {
1174
+ return this.errorResponse("Not authenticated", 401);
1187
1175
  }
1176
+ return this.jsonResponse({
1177
+ success: true,
1178
+ user: {
1179
+ id: session.userId,
1180
+ email: session.email,
1181
+ role: session.role,
1182
+ tenantId: session.tenantId
1183
+ }
1184
+ });
1188
1185
  }
1189
1186
  async changePassword(req) {
1190
1187
  const session = await getCurrentUser(req);
@@ -1294,7 +1291,7 @@ var AuthRoutes = class {
1294
1291
  }
1295
1292
  if (this.auditLogger) {
1296
1293
  await this.auditLogger.log({
1297
- action: "password_reset_request",
1294
+ action: "password_reset",
1298
1295
  userId: user.id,
1299
1296
  userEmail: user.email,
1300
1297
  resource: "auth",
@@ -1482,7 +1479,7 @@ var AuthRoutes = class {
1482
1479
  }
1483
1480
  };
1484
1481
  function createLocalStorage(config) {
1485
- const { uploadDir, baseUrl = "/uploads" } = config;
1482
+ const { uploadDir = join(process2.cwd(), "public", "uploads"), baseUrl = "/uploads" } = config;
1486
1483
  async function ensureDir(dir) {
1487
1484
  if (!existsSync(dir)) {
1488
1485
  await mkdir(dir, { recursive: true });
@@ -1670,6 +1667,467 @@ function createLocalStorage(config) {
1670
1667
  }
1671
1668
  };
1672
1669
  }
1670
+
1671
+ // src/storage/imgix.ts
1672
+ function createImgixStorage(config) {
1673
+ const signUrl = (path3, params) => {
1674
+ if (!config.signKey) return path3;
1675
+ const signer = new TextEncoder();
1676
+ const key = signer.encode(config.signKey);
1677
+ const data = signer.encode(path3 + params.toString());
1678
+ let hash = 0;
1679
+ const combined = new Uint8Array(key.length + data.length);
1680
+ combined.set(key);
1681
+ combined.set(data, key.length);
1682
+ for (let i = 0; i < combined.length; i++) {
1683
+ hash = (hash << 5) - hash + combined[i] | 0;
1684
+ }
1685
+ params.set("s", Math.abs(hash).toString(16));
1686
+ return path3;
1687
+ };
1688
+ return {
1689
+ name: "imgix",
1690
+ displayName: "Imgix",
1691
+ supportsDynamicResize: true,
1692
+ async upload(_file, _options) {
1693
+ throw new Error(
1694
+ "Imgix is a transformation service. Use another provider for uploads."
1695
+ );
1696
+ },
1697
+ async uploadFromUrl(url, options) {
1698
+ const filename = options?.filename || url.split("/").pop() || "file";
1699
+ const response = await fetch(url);
1700
+ if (!response.ok) {
1701
+ throw new Error(`Failed to fetch: ${response.statusText}`);
1702
+ }
1703
+ const blob = await response.blob();
1704
+ new File([blob], filename, { type: blob.type });
1705
+ return {
1706
+ id: Buffer.from(url).toString("base64").slice(0, 20),
1707
+ filename,
1708
+ originalName: filename,
1709
+ mimeType: blob.type,
1710
+ size: blob.size,
1711
+ url: this.getImageUrl(url),
1712
+ thumbnailUrl: this.getImageUrl(url, {
1713
+ width: 200,
1714
+ height: 200,
1715
+ fit: "crop"
1716
+ }),
1717
+ provider: "imgix",
1718
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1719
+ };
1720
+ },
1721
+ async delete(_url) {
1722
+ },
1723
+ async rename(_oldUrl, newKey) {
1724
+ return `https://${config.domain}/${newKey}`;
1725
+ },
1726
+ getImageUrl(url, transforms) {
1727
+ const parsed = new URL(url);
1728
+ const params = new URLSearchParams(parsed.search);
1729
+ if (config.defaultParameters) {
1730
+ Object.entries(config.defaultParameters).forEach(([key, value]) => {
1731
+ if (!params.has(key)) {
1732
+ params.set(key, value);
1733
+ }
1734
+ });
1735
+ }
1736
+ if (transforms) {
1737
+ if (transforms.width) params.set("w", String(transforms.width));
1738
+ if (transforms.height) params.set("h", String(transforms.height));
1739
+ if (transforms.quality) params.set("q", String(transforms.quality));
1740
+ if (transforms.format) params.set("fm", transforms.format);
1741
+ if (transforms.fit) params.set("fit", transforms.fit);
1742
+ if (transforms.blur) params.set("blur", String(transforms.blur));
1743
+ if (transforms.sharpen) params.set("sharp", String(transforms.sharpen));
1744
+ }
1745
+ params.set("auto", "compress,format");
1746
+ parsed.pathname + "?" + params.toString();
1747
+ if (config.signKey) {
1748
+ signUrl(parsed.pathname + params.toString(), params);
1749
+ }
1750
+ return `https://${config.domain}${parsed.pathname}${params.toString() ? "?" + params.toString() : ""}`;
1751
+ },
1752
+ async generateThumbnail(file) {
1753
+ return this.getImageUrl(file.url, {
1754
+ width: 200,
1755
+ height: 200,
1756
+ fit: "crop"
1757
+ });
1758
+ },
1759
+ async list() {
1760
+ return [];
1761
+ },
1762
+ async exists(url) {
1763
+ try {
1764
+ const response = await fetch(url, { method: "HEAD" });
1765
+ return response.ok;
1766
+ } catch {
1767
+ return false;
1768
+ }
1769
+ },
1770
+ async createFolder() {
1771
+ },
1772
+ async deleteFolder() {
1773
+ }
1774
+ };
1775
+ }
1776
+
1777
+ // src/storage/bunny.ts
1778
+ function getUrl(key, config) {
1779
+ if (config.cdnUrl) {
1780
+ return `${config.cdnUrl.replace(/\/$/, "")}/${key}`;
1781
+ }
1782
+ return `https://${config.storageZone}.b-cdn.net/${key}`;
1783
+ }
1784
+ function getUrlPrefix(config) {
1785
+ if (config.cdnUrl) {
1786
+ return config.cdnUrl.replace(/\/$/, "") + "/";
1787
+ }
1788
+ return `https://${config.storageZone}.b-cdn.net/`;
1789
+ }
1790
+ function createBunnyStorage(config) {
1791
+ const baseUrl = `https://storage.bunnycdn.com/${config.storageZone}`;
1792
+ const getKey = (path3) => {
1793
+ const prefix = config.prefix ? `${config.prefix}/` : "";
1794
+ return `${prefix}${path3}`.replace(/\/+/g, "/");
1795
+ };
1796
+ return {
1797
+ name: "bunny",
1798
+ displayName: "Bunny.net Storage",
1799
+ supportsDynamicResize: true,
1800
+ async upload(file, options) {
1801
+ const key = getKey(
1802
+ `${options?.folder ? `${options.folder}/` : ""}${options?.filename || file.name}`
1803
+ );
1804
+ const buffer = Buffer.from(await file.arrayBuffer());
1805
+ const response = await fetch(`${baseUrl}/${key}`, {
1806
+ method: "PUT",
1807
+ headers: {
1808
+ AccessKey: config.apiKey,
1809
+ "Content-Type": file.type
1810
+ },
1811
+ body: buffer
1812
+ });
1813
+ if (!response.ok) {
1814
+ const errorText = await response.text();
1815
+ throw new Error(
1816
+ `Bunny.net upload failed: ${response.status} ${errorText}`
1817
+ );
1818
+ }
1819
+ return {
1820
+ id: Buffer.from(key).toString("base64url"),
1821
+ filename: options?.filename || file.name,
1822
+ originalName: file.name,
1823
+ mimeType: file.type,
1824
+ size: buffer.length,
1825
+ url: getUrl(key, config),
1826
+ thumbnailUrl: file.type.startsWith("image/") ? getUrl(key, config) : void 0,
1827
+ folder: options?.folder,
1828
+ provider: "bunny",
1829
+ metadata: options?.metadata,
1830
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1831
+ };
1832
+ },
1833
+ async uploadFromUrl(url) {
1834
+ const response = await fetch(url);
1835
+ if (!response.ok) {
1836
+ throw new Error(`Failed to fetch: ${response.statusText}`);
1837
+ }
1838
+ const blob = await response.blob();
1839
+ const filename = url.split("/").pop() || "file";
1840
+ const file = new File([blob], filename, { type: blob.type });
1841
+ return this.upload(file);
1842
+ },
1843
+ async delete(url) {
1844
+ const key = url.replace(getUrlPrefix(config), "");
1845
+ const response = await fetch(`${baseUrl}/${key}`, {
1846
+ method: "DELETE",
1847
+ headers: {
1848
+ AccessKey: config.apiKey
1849
+ }
1850
+ });
1851
+ if (!response.ok && response.status !== 404) {
1852
+ const errorText = await response.text();
1853
+ throw new Error(
1854
+ `Bunny.net delete failed: ${response.status} ${errorText}`
1855
+ );
1856
+ }
1857
+ },
1858
+ async rename(oldUrl, newKey) {
1859
+ const oldKey = oldUrl.replace(getUrlPrefix(config), "");
1860
+ const fullPath = config.prefix ? `${config.prefix}/${newKey}` : newKey;
1861
+ const response = await fetch(`${baseUrl}/${oldKey}`, {
1862
+ method: "GET",
1863
+ headers: {
1864
+ AccessKey: config.apiKey
1865
+ }
1866
+ });
1867
+ if (!response.ok) {
1868
+ throw new Error(`Bunny.net rename failed: could not read old file`);
1869
+ }
1870
+ const content = await response.arrayBuffer();
1871
+ await fetch(`${baseUrl}/${fullPath}`, {
1872
+ method: "PUT",
1873
+ headers: {
1874
+ AccessKey: config.apiKey,
1875
+ "Content-Type": response.headers.get("Content-Type") || "application/octet-stream"
1876
+ },
1877
+ body: content
1878
+ });
1879
+ await this.delete(oldUrl);
1880
+ return getUrl(fullPath, config);
1881
+ },
1882
+ getImageUrl(url, transforms) {
1883
+ if (!transforms || Object.keys(transforms).length === 0) return url;
1884
+ const params = new URLSearchParams({ url });
1885
+ if (transforms.width) params.set("w", String(transforms.width));
1886
+ if (transforms.height) params.set("h", String(transforms.height));
1887
+ if (transforms.quality) params.set("q", String(transforms.quality));
1888
+ if (transforms.format) params.set("f", transforms.format);
1889
+ return `/api/media/resize?${params.toString()}`;
1890
+ },
1891
+ async generateThumbnail(file) {
1892
+ return this.getImageUrl(file.url, { width: 400, height: 400 });
1893
+ },
1894
+ async list(prefix) {
1895
+ const key = getKey(prefix || "");
1896
+ const response = await fetch(`${baseUrl}/${key}`, {
1897
+ method: "GET",
1898
+ headers: {
1899
+ AccessKey: config.apiKey
1900
+ }
1901
+ });
1902
+ if (!response.ok) {
1903
+ const errorText = await response.text();
1904
+ throw new Error(
1905
+ `Bunny.net list failed: ${response.status} ${errorText}`
1906
+ );
1907
+ }
1908
+ const items = await response.json();
1909
+ return items.map((item) => ({
1910
+ id: Buffer.from(item.ObjectName || "").toString("base64url"),
1911
+ filename: item.ObjectName?.split("/").pop() || "",
1912
+ originalName: item.ObjectName?.split("/").pop() || "",
1913
+ mimeType: "application/octet-stream",
1914
+ size: item.Length || 0,
1915
+ url: getUrl(item.ObjectName || "", config),
1916
+ provider: "bunny",
1917
+ createdAt: item.LastChanged || (/* @__PURE__ */ new Date()).toISOString()
1918
+ }));
1919
+ },
1920
+ async exists(url) {
1921
+ const key = url.replace(getUrlPrefix(config), "");
1922
+ const response = await fetch(`${baseUrl}/${key}`, {
1923
+ method: "HEAD",
1924
+ headers: {
1925
+ AccessKey: config.apiKey
1926
+ }
1927
+ });
1928
+ return response.ok;
1929
+ },
1930
+ async createFolder(folder) {
1931
+ const key = getKey(`${folder}/`);
1932
+ await fetch(`${baseUrl}/${key}`, {
1933
+ method: "PUT",
1934
+ headers: {
1935
+ AccessKey: config.apiKey,
1936
+ "Content-Length": "0"
1937
+ },
1938
+ body: ""
1939
+ });
1940
+ },
1941
+ async deleteFolder(folder) {
1942
+ const key = getKey(`${folder}/`);
1943
+ await fetch(`${baseUrl}/${key}`, {
1944
+ method: "DELETE",
1945
+ headers: {
1946
+ AccessKey: config.apiKey
1947
+ }
1948
+ });
1949
+ }
1950
+ };
1951
+ }
1952
+ var StorageProviderRegistry = class {
1953
+ providers = /* @__PURE__ */ new Map();
1954
+ providerToPlugin = /* @__PURE__ */ new Map();
1955
+ disabledPlugins = /* @__PURE__ */ new Set();
1956
+ constructor() {
1957
+ this.registerLocal();
1958
+ this.registerImgix();
1959
+ this.registerBunny();
1960
+ }
1961
+ registerLocal() {
1962
+ this.register({
1963
+ type: "local",
1964
+ displayName: "Local Server",
1965
+ configKey: "local",
1966
+ configFields: [
1967
+ {
1968
+ name: "uploadDir",
1969
+ type: "text",
1970
+ label: "Upload Directory",
1971
+ defaultValue: "./public/uploads"
1972
+ },
1973
+ {
1974
+ name: "baseUrl",
1975
+ type: "text",
1976
+ label: "Base URL",
1977
+ defaultValue: "/uploads"
1978
+ }
1979
+ ],
1980
+ extractConfig: (sc, key) => sc[key] || {},
1981
+ extractRawConfig: (c) => {
1982
+ const localConfig = c?.local || c;
1983
+ const savedUploadDir = (localConfig?.uploadDir || "").trim();
1984
+ let uploadDir;
1985
+ if (savedUploadDir) {
1986
+ if (path.isAbsolute(savedUploadDir)) {
1987
+ uploadDir = savedUploadDir;
1988
+ } else if (savedUploadDir.includes("/") || savedUploadDir.includes("\\")) {
1989
+ uploadDir = path.resolve(process.cwd(), savedUploadDir);
1990
+ } else {
1991
+ uploadDir = path.join(process.cwd(), "public", savedUploadDir);
1992
+ }
1993
+ } else {
1994
+ uploadDir = path.join(process.cwd(), "public", "uploads");
1995
+ }
1996
+ const savedBaseUrl = (localConfig?.baseUrl || "").trim();
1997
+ let baseUrl;
1998
+ if (savedBaseUrl) {
1999
+ baseUrl = savedBaseUrl.startsWith("/") ? savedBaseUrl : `/${savedBaseUrl}`;
2000
+ } else {
2001
+ baseUrl = "/uploads";
2002
+ }
2003
+ return { uploadDir, baseUrl };
2004
+ },
2005
+ factory: (c) => createLocalStorage(c)
2006
+ });
2007
+ }
2008
+ registerImgix() {
2009
+ this.register({
2010
+ type: "imgix",
2011
+ displayName: "Imgix",
2012
+ configKey: "imgix",
2013
+ configFields: [
2014
+ { name: "domain", type: "text", label: "Domain", required: true },
2015
+ { name: "signKey", type: "password", label: "Sign Key" }
2016
+ ],
2017
+ extractConfig: (sc, key) => sc[key] || {},
2018
+ extractRawConfig: (c) => c?.imgix || c,
2019
+ factory: (c) => createImgixStorage(c)
2020
+ });
2021
+ }
2022
+ registerBunny() {
2023
+ this.register({
2024
+ type: "bunny",
2025
+ displayName: "Bunny.net",
2026
+ configKey: "bunny",
2027
+ configFields: [
2028
+ {
2029
+ name: "storageZone",
2030
+ type: "text",
2031
+ label: "Storage Zone",
2032
+ required: true
2033
+ },
2034
+ { name: "apiKey", type: "password", label: "API Key", required: true },
2035
+ { name: "cdnUrl", type: "text", label: "CDN URL" },
2036
+ { name: "prefix", type: "text", label: "Path Prefix" }
2037
+ ],
2038
+ extractConfig: (sc, key) => sc[key] || {},
2039
+ extractRawConfig: (c) => c?.bunny || c,
2040
+ factory: (c) => createBunnyStorage(c)
2041
+ });
2042
+ }
2043
+ register(registration) {
2044
+ if (this.providers.has(registration.type)) {
2045
+ console.warn(
2046
+ `[StorageRegistry] Provider "${registration.type}" already registered, skipping`
2047
+ );
2048
+ return;
2049
+ }
2050
+ if (registration.pluginName) {
2051
+ this.providerToPlugin.set(registration.type, registration.pluginName);
2052
+ }
2053
+ this.providers.set(registration.type, registration);
2054
+ }
2055
+ unregister(type) {
2056
+ this.providers.delete(type);
2057
+ this.providerToPlugin.delete(type);
2058
+ }
2059
+ get(type) {
2060
+ return this.providers.get(type);
2061
+ }
2062
+ getAll() {
2063
+ return Array.from(this.providers.values());
2064
+ }
2065
+ getAllAvailable(isPluginEnabled) {
2066
+ const all = this.getAll();
2067
+ if (!isPluginEnabled) return all;
2068
+ return all.filter((p) => {
2069
+ if (!p.pluginName) return true;
2070
+ return isPluginEnabled(p.pluginName);
2071
+ });
2072
+ }
2073
+ has(type) {
2074
+ return this.providers.has(type);
2075
+ }
2076
+ async resolve(type, storageConfig) {
2077
+ const reg = this.providers.get(type);
2078
+ if (!reg) {
2079
+ throw new Error(
2080
+ `Unknown storage provider type: "${type}". Is the plugin that provides it enabled?`
2081
+ );
2082
+ }
2083
+ if (reg.pluginName && this.disabledPlugins.has(reg.pluginName)) {
2084
+ throw new Error(
2085
+ `Storage provider "${type}" is not available (plugin "${reg.pluginName}" is disabled)`
2086
+ );
2087
+ }
2088
+ const configKey = reg.configKey || type;
2089
+ const config = reg.extractConfig(storageConfig, configKey);
2090
+ return reg.factory(config);
2091
+ }
2092
+ async resolveWithConfig(type, config) {
2093
+ const reg = this.providers.get(type);
2094
+ if (!reg) {
2095
+ throw new Error(
2096
+ `Unknown storage provider type: "${type}". Is the plugin that provides it enabled?`
2097
+ );
2098
+ }
2099
+ if (reg.pluginName && this.disabledPlugins.has(reg.pluginName)) {
2100
+ throw new Error(
2101
+ `Storage provider "${type}" is not available (plugin "${reg.pluginName}" is disabled)`
2102
+ );
2103
+ }
2104
+ const providerConfig = reg.extractRawConfig(config);
2105
+ return reg.factory(providerConfig);
2106
+ }
2107
+ getProviderPluginName(type) {
2108
+ return this.providerToPlugin.get(type);
2109
+ }
2110
+ getAllPluginNames() {
2111
+ return Array.from(new Set(this.providerToPlugin.values()));
2112
+ }
2113
+ setPluginEnabled(name, enabled) {
2114
+ if (enabled) {
2115
+ this.disabledPlugins.delete(name);
2116
+ } else {
2117
+ this.disabledPlugins.add(name);
2118
+ }
2119
+ }
2120
+ isPluginEnabled(name) {
2121
+ return !this.disabledPlugins.has(name);
2122
+ }
2123
+ };
2124
+ var instance = null;
2125
+ function getDefaultRegistry() {
2126
+ if (!instance) {
2127
+ instance = new StorageProviderRegistry();
2128
+ }
2129
+ return instance;
2130
+ }
1673
2131
  function extractPublicDevUrlId(url) {
1674
2132
  if (!url) return "";
1675
2133
  if (url.startsWith("pub-")) return url;
@@ -1703,7 +2161,7 @@ function getPublicUrl(key, config) {
1703
2161
  return `https://${config.bucket}.s3.${config.region}.amazonaws.com/${normalizedKey}`;
1704
2162
  }
1705
2163
  }
1706
- function getUrlPrefix(config) {
2164
+ function getUrlPrefix2(config) {
1707
2165
  if (config.cdnUrl) {
1708
2166
  return config.cdnUrl.replace(/\/$/, "") + "/";
1709
2167
  }
@@ -1771,11 +2229,11 @@ function createS3Storage(config) {
1771
2229
  })
1772
2230
  }
1773
2231
  });
1774
- const getKey = (path2) => {
2232
+ const getKey = (path3) => {
1775
2233
  const prefix = config.prefix ? `${config.prefix}/` : "";
1776
- return `${prefix}${path2}`.replace(/\/+/g, "/");
2234
+ return `${prefix}${path3}`.replace(/\/+/g, "/");
1777
2235
  };
1778
- const getUrl = (key) => getPublicUrl(key, config);
2236
+ const getUrl2 = (key) => getPublicUrl(key, config);
1779
2237
  return {
1780
2238
  name: config.provider,
1781
2239
  displayName: getDisplayName(config.provider),
@@ -1806,8 +2264,8 @@ function createS3Storage(config) {
1806
2264
  originalName: file.name,
1807
2265
  mimeType: file.type,
1808
2266
  size: buffer.length,
1809
- url: getUrl(key),
1810
- thumbnailUrl: file.type.startsWith("image/") ? getUrl(key) : void 0,
2267
+ url: getUrl2(key),
2268
+ thumbnailUrl: file.type.startsWith("image/") ? getUrl2(key) : void 0,
1811
2269
  folder: options?.folder,
1812
2270
  provider: config.provider,
1813
2271
  metadata: {
@@ -1828,7 +2286,7 @@ function createS3Storage(config) {
1828
2286
  return this.upload(file);
1829
2287
  },
1830
2288
  async delete(url) {
1831
- const key = url.replace(getUrlPrefix(config), "");
2289
+ const key = url.replace(getUrlPrefix2(config), "");
1832
2290
  await client.send(
1833
2291
  new DeleteObjectCommand({
1834
2292
  Bucket: config.bucket,
@@ -1837,7 +2295,7 @@ function createS3Storage(config) {
1837
2295
  );
1838
2296
  },
1839
2297
  async rename(oldUrl, newKey) {
1840
- const oldKey = oldUrl.replace(getUrlPrefix(config), "");
2298
+ const oldKey = oldUrl.replace(getUrlPrefix2(config), "");
1841
2299
  const newKeyWithPrefix = config.prefix ? `${config.prefix}/${newKey}` : newKey;
1842
2300
  await client.send(
1843
2301
  new CopyObjectCommand({
@@ -1852,7 +2310,7 @@ function createS3Storage(config) {
1852
2310
  Key: oldKey
1853
2311
  })
1854
2312
  );
1855
- return getUrl(newKeyWithPrefix);
2313
+ return getUrl2(newKeyWithPrefix);
1856
2314
  },
1857
2315
  getImageUrl(url, transforms) {
1858
2316
  if (!transforms || Object.keys(transforms).length === 0) return url;
@@ -1880,14 +2338,14 @@ function createS3Storage(config) {
1880
2338
  originalName: item.Key?.split("/").pop() || "",
1881
2339
  mimeType: "application/octet-stream",
1882
2340
  size: item.Size || 0,
1883
- url: getUrl(item.Key || ""),
2341
+ url: getUrl2(item.Key || ""),
1884
2342
  provider: config.provider,
1885
2343
  createdAt: item.LastModified?.toISOString() || (/* @__PURE__ */ new Date()).toISOString()
1886
2344
  }));
1887
2345
  },
1888
2346
  async exists(url) {
1889
2347
  try {
1890
- const key = url.replace(getUrlPrefix(config), "");
2348
+ const key = url.replace(getUrlPrefix2(config), "");
1891
2349
  await client.send(
1892
2350
  new HeadObjectCommand({
1893
2351
  Bucket: config.bucket,
@@ -1947,9 +2405,9 @@ function createS3Storage(config) {
1947
2405
  function createCloudinaryStorage(config) {
1948
2406
  const getBaseUrl = () => `https://api.cloudinary.com/v1_1/${config.cloudName}/upload`;
1949
2407
  const generateSignature = async (params) => {
1950
- const crypto3 = await import('crypto');
2408
+ const crypto4 = await import('crypto');
1951
2409
  const sortedParams = Object.keys(params).sort().map((key) => `${key}=${params[key]}`).join("&");
1952
- return crypto3.createHash("sha256").update(sortedParams + config.apiSecret).digest("hex");
2410
+ return crypto4.createHash("sha256").update(sortedParams + config.apiSecret).digest("hex");
1953
2411
  };
1954
2412
  return {
1955
2413
  name: "cloudinary",
@@ -2051,8 +2509,8 @@ function createCloudinaryStorage(config) {
2051
2509
  public_id: publicId
2052
2510
  };
2053
2511
  const sortedParams = Object.keys(signatureParams).sort().map((key) => `${key}=${signatureParams[key]}`).join("&");
2054
- const crypto3 = await import('crypto');
2055
- const signature = crypto3.createHash("sha256").update(sortedParams + config.apiSecret).digest("hex");
2512
+ const crypto4 = await import('crypto');
2513
+ const signature = crypto4.createHash("sha256").update(sortedParams + config.apiSecret).digest("hex");
2056
2514
  const deleteUrl = `https://api.cloudinary.com/v1_1/${config.cloudName}/image/destroy`;
2057
2515
  const formData = new FormData();
2058
2516
  formData.append("public_id", publicId);
@@ -2103,8 +2561,8 @@ function createCloudinaryStorage(config) {
2103
2561
  timestamp: String(timestamp)
2104
2562
  };
2105
2563
  const sortedParams = Object.keys(signatureParams).sort().map((key) => `${key}=${signatureParams[key]}`).join("&");
2106
- const crypto3 = await import('crypto');
2107
- const signature = crypto3.createHash("sha256").update(sortedParams + config.apiSecret).digest("hex");
2564
+ const crypto4 = await import('crypto');
2565
+ const signature = crypto4.createHash("sha256").update(sortedParams + config.apiSecret).digest("hex");
2108
2566
  const formData = new FormData();
2109
2567
  formData.append("from_public_id", publicIdWithoutVersion);
2110
2568
  formData.append("to_public_id", newPublicId);
@@ -2175,112 +2633,6 @@ function createCloudinaryStorage(config) {
2175
2633
  }
2176
2634
  };
2177
2635
  }
2178
-
2179
- // src/storage/imgix.ts
2180
- function createImgixStorage(config) {
2181
- const signUrl = (path2, params) => {
2182
- if (!config.signKey) return path2;
2183
- const signer = new TextEncoder();
2184
- const key = signer.encode(config.signKey);
2185
- const data = signer.encode(path2 + params.toString());
2186
- let hash = 0;
2187
- const combined = new Uint8Array(key.length + data.length);
2188
- combined.set(key);
2189
- combined.set(data, key.length);
2190
- for (let i = 0; i < combined.length; i++) {
2191
- hash = (hash << 5) - hash + combined[i] | 0;
2192
- }
2193
- params.set("s", Math.abs(hash).toString(16));
2194
- return path2;
2195
- };
2196
- return {
2197
- name: "imgix",
2198
- displayName: "Imgix",
2199
- supportsDynamicResize: true,
2200
- async upload(_file, _options) {
2201
- throw new Error(
2202
- "Imgix is a transformation service. Use another provider for uploads."
2203
- );
2204
- },
2205
- async uploadFromUrl(url, options) {
2206
- const filename = options?.filename || url.split("/").pop() || "file";
2207
- const response = await fetch(url);
2208
- if (!response.ok) {
2209
- throw new Error(`Failed to fetch: ${response.statusText}`);
2210
- }
2211
- const blob = await response.blob();
2212
- new File([blob], filename, { type: blob.type });
2213
- return {
2214
- id: Buffer.from(url).toString("base64").slice(0, 20),
2215
- filename,
2216
- originalName: filename,
2217
- mimeType: blob.type,
2218
- size: blob.size,
2219
- url: this.getImageUrl(url),
2220
- thumbnailUrl: this.getImageUrl(url, {
2221
- width: 200,
2222
- height: 200,
2223
- fit: "crop"
2224
- }),
2225
- provider: "imgix",
2226
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
2227
- };
2228
- },
2229
- async delete(_url) {
2230
- },
2231
- async rename(_oldUrl, newKey) {
2232
- return `https://${config.domain}/${newKey}`;
2233
- },
2234
- getImageUrl(url, transforms) {
2235
- const parsed = new URL(url);
2236
- const params = new URLSearchParams(parsed.search);
2237
- if (config.defaultParameters) {
2238
- Object.entries(config.defaultParameters).forEach(([key, value]) => {
2239
- if (!params.has(key)) {
2240
- params.set(key, value);
2241
- }
2242
- });
2243
- }
2244
- if (transforms) {
2245
- if (transforms.width) params.set("w", String(transforms.width));
2246
- if (transforms.height) params.set("h", String(transforms.height));
2247
- if (transforms.quality) params.set("q", String(transforms.quality));
2248
- if (transforms.format) params.set("fm", transforms.format);
2249
- if (transforms.fit) params.set("fit", transforms.fit);
2250
- if (transforms.blur) params.set("blur", String(transforms.blur));
2251
- if (transforms.sharpen) params.set("sharp", String(transforms.sharpen));
2252
- }
2253
- params.set("auto", "compress,format");
2254
- parsed.pathname + "?" + params.toString();
2255
- if (config.signKey) {
2256
- signUrl(parsed.pathname + params.toString(), params);
2257
- }
2258
- return `https://${config.domain}${parsed.pathname}${params.toString() ? "?" + params.toString() : ""}`;
2259
- },
2260
- async generateThumbnail(file) {
2261
- return this.getImageUrl(file.url, {
2262
- width: 200,
2263
- height: 200,
2264
- fit: "crop"
2265
- });
2266
- },
2267
- async list() {
2268
- return [];
2269
- },
2270
- async exists(url) {
2271
- try {
2272
- const response = await fetch(url, { method: "HEAD" });
2273
- return response.ok;
2274
- } catch {
2275
- return false;
2276
- }
2277
- },
2278
- async createFolder() {
2279
- },
2280
- async deleteFolder() {
2281
- }
2282
- };
2283
- }
2284
2636
  function createFtpStorage(config) {
2285
2637
  let client = null;
2286
2638
  async function getClient() {
@@ -2298,15 +2650,15 @@ function createFtpStorage(config) {
2298
2650
  }
2299
2651
  return client;
2300
2652
  }
2301
- const getKey = (path2) => {
2653
+ const getKey = (path3) => {
2302
2654
  const prefix = config.prefix ? `${config.prefix}/` : "";
2303
- return `${prefix}${path2}`.replace(/\/+/g, "/");
2655
+ return `${prefix}${path3}`.replace(/\/+/g, "/");
2304
2656
  };
2305
- const getUrl = (key) => {
2657
+ const getUrl2 = (key) => {
2306
2658
  const base = config.baseUrl.replace(/\/$/, "");
2307
2659
  return `${base}/${key}`;
2308
2660
  };
2309
- const getUrlPrefix2 = () => {
2661
+ const getUrlPrefix3 = () => {
2310
2662
  const base = config.baseUrl.replace(/\/$/, "");
2311
2663
  return base + "/";
2312
2664
  };
@@ -2337,8 +2689,8 @@ function createFtpStorage(config) {
2337
2689
  originalName: file.name,
2338
2690
  mimeType: file.type,
2339
2691
  size: buffer.length,
2340
- url: getUrl(key),
2341
- thumbnailUrl: file.type.startsWith("image/") ? getUrl(key) : void 0,
2692
+ url: getUrl2(key),
2693
+ thumbnailUrl: file.type.startsWith("image/") ? getUrl2(key) : void 0,
2342
2694
  folder: options?.folder,
2343
2695
  provider: config.type,
2344
2696
  metadata: options?.metadata,
@@ -2357,15 +2709,15 @@ function createFtpStorage(config) {
2357
2709
  },
2358
2710
  async delete(url) {
2359
2711
  const ftp = await getClient();
2360
- const key = url.replace(getUrlPrefix2(), "");
2712
+ const key = url.replace(getUrlPrefix3(), "");
2361
2713
  await ftp.remove(key);
2362
2714
  },
2363
2715
  async rename(oldUrl, newKey) {
2364
2716
  const ftp = await getClient();
2365
- const oldKey = oldUrl.replace(getUrlPrefix2(), "");
2717
+ const oldKey = oldUrl.replace(getUrlPrefix3(), "");
2366
2718
  const fullPath = config.prefix ? `${config.prefix}/${newKey}` : newKey;
2367
2719
  await ftp.rename(oldKey, fullPath);
2368
- return getUrl(fullPath);
2720
+ return getUrl2(fullPath);
2369
2721
  },
2370
2722
  getImageUrl(url, transforms) {
2371
2723
  if (!transforms || Object.keys(transforms).length === 0) return url;
@@ -2394,14 +2746,14 @@ function createFtpStorage(config) {
2394
2746
  originalName: item.name,
2395
2747
  mimeType: "application/octet-stream",
2396
2748
  size: Number(item.size) || 0,
2397
- url: getUrl(`${key}/${item.name}`.replace(/\/+/g, "/")),
2749
+ url: getUrl2(`${key}/${item.name}`.replace(/\/+/g, "/")),
2398
2750
  provider: config.type,
2399
2751
  createdAt: item.modifiedAt ? item.modifiedAt.toISOString() : (/* @__PURE__ */ new Date()).toISOString()
2400
2752
  }));
2401
2753
  },
2402
2754
  async exists(url) {
2403
2755
  const ftp = await getClient();
2404
- const key = url.replace(getUrlPrefix2(), "");
2756
+ const key = url.replace(getUrlPrefix3(), "");
2405
2757
  try {
2406
2758
  await ftp.size(key);
2407
2759
  return true;
@@ -2425,105 +2777,17 @@ function createFtpStorage(config) {
2425
2777
  // src/storage/index.ts
2426
2778
  async function resolveProvider(configService) {
2427
2779
  const config = configService.getStorageConfig();
2428
- switch (config.type) {
2429
- case "aws":
2430
- return createS3Storage({
2431
- provider: "aws",
2432
- bucket: config.s3.bucket || "",
2433
- region: config.s3.region || "us-east-1",
2434
- accessKeyId: config.s3.accessKeyId || "",
2435
- secretAccessKey: config.s3.secretAccessKey || "",
2436
- endpoint: config.s3.endpoint,
2437
- cdnUrl: config.s3.cdnUrl,
2438
- prefix: config.s3.prefix
2439
- });
2440
- case "r2":
2441
- return createS3Storage({
2442
- provider: "r2",
2443
- bucket: config.r2.bucket || "",
2444
- region: "auto",
2445
- accessKeyId: config.r2.accessKeyId || "",
2446
- secretAccessKey: config.r2.secretAccessKey || "",
2447
- accountId: config.r2.accountId || "",
2448
- publicDevUrl: config.r2.publicDevUrl,
2449
- endpoint: `https://${config.r2.accountId || ""}.r2.cloudflarestorage.com`,
2450
- cdnUrl: config.r2.cdnUrl,
2451
- prefix: config.r2.prefix
2452
- });
2453
- case "gcs":
2454
- return createS3Storage({
2455
- provider: "gcs",
2456
- bucket: config.gcs.bucket || "",
2457
- region: config.gcs.projectId || "auto",
2458
- accessKeyId: config.gcs.clientEmail || "",
2459
- secretAccessKey: config.gcs.privateKey || "",
2460
- cdnUrl: config.gcs.cdnUrl,
2461
- prefix: config.gcs.prefix
2462
- });
2463
- case "digitalocean":
2464
- return createS3Storage({
2465
- provider: "digitalocean",
2466
- bucket: config.digitalocean.bucket || "",
2467
- region: config.digitalocean.region || "nyc3",
2468
- accessKeyId: config.digitalocean.accessKeyId || "",
2469
- secretAccessKey: config.digitalocean.secretAccessKey || "",
2470
- endpoint: `https://${config.digitalocean.region || "nyc3"}.digitaloceanspaces.com`,
2471
- cdnUrl: config.digitalocean.cdnUrl,
2472
- prefix: config.digitalocean.prefix
2473
- });
2474
- case "backblaze":
2475
- return createS3Storage({
2476
- provider: "backblaze",
2477
- bucket: config.backblaze.bucket || "",
2478
- region: "auto",
2479
- accessKeyId: config.backblaze.applicationKeyId || "",
2480
- secretAccessKey: config.backblaze.applicationKey || "",
2481
- accountId: config.backblaze.accountId || "",
2482
- endpoint: `https://s3.backblazeb2.com`,
2483
- cdnUrl: config.backblaze.cdnUrl,
2484
- prefix: config.backblaze.prefix
2485
- });
2486
- case "wasabi":
2487
- return createS3Storage({
2488
- provider: "wasabi",
2489
- bucket: config.wasabi.bucket || "",
2490
- region: config.wasabi.region || "us-east-1",
2491
- accessKeyId: config.wasabi.accessKeyId || "",
2492
- secretAccessKey: config.wasabi.secretAccessKey || "",
2493
- endpoint: `https://s3.${config.wasabi.region || "us-east-1"}.wasabisys.com`,
2494
- cdnUrl: config.wasabi.cdnUrl,
2495
- prefix: config.wasabi.prefix
2496
- });
2497
- case "ftp":
2498
- case "sftp":
2499
- return createFtpStorage({
2500
- host: config.ftp?.host || "",
2501
- port: config.ftp?.port || 21,
2502
- user: config.ftp?.user || "",
2503
- password: config.ftp?.password || "",
2504
- secure: config.ftp?.secure || false,
2505
- baseUrl: config.ftp?.baseUrl || "",
2506
- prefix: config.ftp?.prefix,
2507
- type: "ftp"
2508
- });
2509
- case "cloudinary":
2510
- return createCloudinaryStorage({
2511
- cloudName: config.cloudinary.cloudName || "",
2512
- apiKey: config.cloudinary.apiKey || "",
2513
- apiSecret: config.cloudinary.apiSecret || "",
2514
- folder: config.cloudinary.folder
2515
- });
2516
- case "imgix":
2517
- return createImgixStorage({
2518
- domain: config.imgix.domain || "",
2519
- signKey: config.imgix.signKey
2520
- });
2521
- case "local":
2522
- default:
2523
- return createLocalStorage({
2524
- uploadDir: config.local.uploadDir || path.join(process.cwd(), "public", "uploads"),
2525
- baseUrl: config.local.baseUrl || "/uploads"
2526
- });
2780
+ const registry = getDefaultRegistry();
2781
+ try {
2782
+ return await registry.resolve(config.type, config);
2783
+ } catch (err) {
2784
+ console.warn(
2785
+ `[resolveProvider] ${err.message} \u2014 falling back to local storage`
2786
+ );
2787
+ return createLocalStorage({
2788
+ uploadDir: path.join(process.cwd(), "public", "uploads"),
2789
+ baseUrl: "/uploads"
2790
+ });
2527
2791
  }
2528
2792
  }
2529
2793
  async function resolveProviderWithConfig(config) {
@@ -2534,121 +2798,18 @@ async function resolveProviderWithConfig(config) {
2534
2798
  baseUrl: "/uploads"
2535
2799
  });
2536
2800
  }
2537
- console.log("[resolveProviderWithConfig] Creating provider:", config.type);
2538
- switch (config.type) {
2539
- case "aws":
2540
- return createS3Storage({
2541
- provider: "aws",
2542
- bucket: config.s3?.bucket || "",
2543
- region: config.s3?.region || "us-east-1",
2544
- accessKeyId: config.s3?.accessKeyId || "",
2545
- secretAccessKey: config.s3?.secretAccessKey || "",
2546
- endpoint: config.s3?.endpoint,
2547
- cdnUrl: config.s3?.cdnUrl,
2548
- prefix: config.s3?.prefix
2549
- });
2550
- case "r2":
2551
- return createS3Storage({
2552
- provider: "r2",
2553
- bucket: config.r2?.bucket || "",
2554
- region: "auto",
2555
- accessKeyId: config.r2?.accessKeyId || "",
2556
- secretAccessKey: config.r2?.secretAccessKey || "",
2557
- accountId: config.r2?.accountId || "",
2558
- publicDevUrl: config.r2?.publicDevUrl,
2559
- endpoint: `https://${config.r2?.accountId || ""}.r2.cloudflarestorage.com`,
2560
- cdnUrl: config.r2?.cdnUrl,
2561
- prefix: config.r2?.prefix
2562
- });
2563
- case "gcs":
2564
- return createS3Storage({
2565
- provider: "gcs",
2566
- bucket: config.gcs?.bucket || "",
2567
- region: config.gcs?.projectId || "auto",
2568
- accessKeyId: config.gcs?.clientEmail || "",
2569
- secretAccessKey: config.gcs?.privateKey || "",
2570
- cdnUrl: config.gcs?.cdnUrl,
2571
- prefix: config.gcs?.prefix
2572
- });
2573
- case "digitalocean":
2574
- return createS3Storage({
2575
- provider: "digitalocean",
2576
- bucket: config.digitalocean?.bucket || "",
2577
- region: config.digitalocean?.region || "nyc3",
2578
- accessKeyId: config.digitalocean?.accessKeyId || "",
2579
- secretAccessKey: config.digitalocean?.secretAccessKey || "",
2580
- cdnUrl: config.digitalocean?.cdnUrl,
2581
- prefix: config.digitalocean?.prefix
2582
- });
2583
- case "backblaze":
2584
- return createS3Storage({
2585
- provider: "backblaze",
2586
- bucket: config.backblaze?.bucket || "",
2587
- region: "auto",
2588
- accessKeyId: config.backblaze?.applicationKeyId || "",
2589
- secretAccessKey: config.backblaze?.applicationKey || "",
2590
- cdnUrl: config.backblaze?.cdnUrl,
2591
- prefix: config.backblaze?.prefix
2592
- });
2593
- case "wasabi":
2594
- return createS3Storage({
2595
- provider: "wasabi",
2596
- bucket: config.wasabi?.bucket || "",
2597
- region: config.wasabi?.region || "us-east-1",
2598
- accessKeyId: config.wasabi?.accessKeyId || "",
2599
- secretAccessKey: config.wasabi?.secretAccessKey || "",
2600
- cdnUrl: config.wasabi?.cdnUrl,
2601
- prefix: config.wasabi?.prefix
2602
- });
2603
- case "cloudinary":
2604
- return createCloudinaryStorage({
2605
- cloudName: config.cloudinary?.cloudName || "",
2606
- apiKey: config.cloudinary?.apiKey || "",
2607
- apiSecret: config.cloudinary?.apiSecret || "",
2608
- folder: config.cloudinary?.folder
2609
- });
2610
- case "ftp":
2611
- case "sftp": {
2612
- const ftpConf = config.ftp || config;
2613
- return createFtpStorage({
2614
- type: "ftp",
2615
- host: ftpConf.host || "",
2616
- port: ftpConf.port || 21,
2617
- user: ftpConf.user || "",
2618
- password: ftpConf.password || "",
2619
- secure: ftpConf.secure || false,
2620
- baseUrl: ftpConf.baseUrl || "",
2621
- prefix: ftpConf.prefix
2622
- });
2623
- }
2624
- case "local":
2625
- default: {
2626
- const localConfig = config.local || {
2627
- uploadDir: config["local.uploadDir"],
2628
- baseUrl: config["local.baseUrl"]
2629
- };
2630
- const savedUploadDir = (localConfig?.uploadDir || "").trim();
2631
- let uploadDir;
2632
- if (savedUploadDir) {
2633
- if (path.isAbsolute(savedUploadDir)) {
2634
- uploadDir = savedUploadDir;
2635
- } else if (savedUploadDir.includes("/") || savedUploadDir.includes("\\")) {
2636
- uploadDir = path.resolve(process.cwd(), savedUploadDir);
2637
- } else {
2638
- uploadDir = path.join(process.cwd(), "public", savedUploadDir);
2639
- }
2640
- } else {
2641
- uploadDir = path.join(process.cwd(), "public", "uploads");
2642
- }
2643
- const savedBaseUrl = (localConfig?.baseUrl || "").trim();
2644
- let baseUrl;
2645
- if (savedBaseUrl) {
2646
- baseUrl = savedBaseUrl.startsWith("/") ? savedBaseUrl : `/${savedBaseUrl}`;
2647
- } else {
2648
- baseUrl = "/uploads";
2649
- }
2650
- return createLocalStorage({ uploadDir, baseUrl });
2651
- }
2801
+ const type = config.type || "local";
2802
+ const registry = getDefaultRegistry();
2803
+ try {
2804
+ return await registry.resolveWithConfig(type, config);
2805
+ } catch (err) {
2806
+ console.warn(
2807
+ `[resolveProviderWithConfig] ${err.message} \u2014 falling back to local storage`
2808
+ );
2809
+ return createLocalStorage({
2810
+ uploadDir: path.join(process.cwd(), "public", "uploads"),
2811
+ baseUrl: "/uploads"
2812
+ });
2652
2813
  }
2653
2814
  }
2654
2815
  async function processImage(buffer) {
@@ -3252,8 +3413,33 @@ var MediaService = class _MediaService {
3252
3413
  }
3253
3414
  }
3254
3415
  };
3255
-
3256
- // src/api/rest/hono-app.ts
3416
+ function formatZodErrors(errors) {
3417
+ return errors.map((e) => `${e.path.join(".")}: ${e.message}`).join(", ");
3418
+ }
3419
+ function convertRichtextFields(fields, data) {
3420
+ if (!data || typeof data !== "object") return;
3421
+ for (const field of fields) {
3422
+ if (field.type === "richtext" && field.name) {
3423
+ const val = data[field.name];
3424
+ if (typeof val === "string") {
3425
+ data[field.name] = [{ type: "paragraph", children: [{ text: val }] }];
3426
+ } else if (val && typeof val === "object" && !Array.isArray(val) && val.type === "doc" && Array.isArray(val.content)) {
3427
+ data[field.name] = val.content;
3428
+ }
3429
+ }
3430
+ if (field.type === "tabs" && field.name && Array.isArray(field.tabs) && data[field.name] && typeof data[field.name] === "object") {
3431
+ for (const tab of field.tabs) {
3432
+ if (Array.isArray(tab.fields)) convertRichtextFields(tab.fields, data[field.name]);
3433
+ }
3434
+ } else if ((field.type === "group" || field.type === "collapsible") && field.name && Array.isArray(field.fields) && data[field.name] && typeof data[field.name] === "object") {
3435
+ convertRichtextFields(field.fields, data[field.name]);
3436
+ } else if (field.type === "array" && field.name && Array.isArray(field.fields) && Array.isArray(data[field.name])) {
3437
+ for (const item of data[field.name]) {
3438
+ if (item && typeof item === "object") convertRichtextFields(field.fields, item);
3439
+ }
3440
+ }
3441
+ }
3442
+ }
3257
3443
  var COLLECTION_EVENT_MAP = {
3258
3444
  _media: {
3259
3445
  create: WEBHOOK_EVENTS.MEDIA_UPLOAD,
@@ -3406,31 +3592,30 @@ async function checkGlobalAccess(global, operation, req, ctxUser, ctxTenantID, e
3406
3592
  return { allowed: true };
3407
3593
  }
3408
3594
  async function resolveAuthContext(req, authMw, staticUser, staticTenantID) {
3409
- if (!authMw) {
3595
+ if (staticUser) {
3410
3596
  return {
3411
3597
  user: staticUser,
3412
3598
  tenantID: staticTenantID,
3413
- apiKeyContext: void 0
3599
+ apiKeyContext: void 0,
3600
+ authType: "static"
3414
3601
  };
3415
3602
  }
3416
- let result;
3417
- try {
3418
- result = await authMw(req);
3419
- } catch (err) {
3420
- return {
3421
- user: staticUser,
3422
- tenantID: staticTenantID,
3423
- apiKeyContext: void 0
3424
- };
3425
- }
3426
- if (result.status === 401) {
3427
- return { user: void 0, tenantID: void 0, apiKeyContext: void 0 };
3603
+ if (authMw) {
3604
+ const res = await authMw(req);
3605
+ if (res.status === 200 && res.user) {
3606
+ return {
3607
+ user: res.user,
3608
+ tenantID: res.tenantContext?.tenantId,
3609
+ apiKeyContext: res.apiKeyContext,
3610
+ authType: res.authType
3611
+ };
3612
+ }
3428
3613
  }
3429
3614
  return {
3430
- user: result.user || staticUser,
3431
- tenantID: result.tenantContext?.tenantId || staticTenantID,
3432
- apiKeyContext: result.apiKeyContext,
3433
- authType: result.authType
3615
+ user: void 0,
3616
+ tenantID: void 0,
3617
+ apiKeyContext: void 0,
3618
+ authType: void 0
3434
3619
  };
3435
3620
  }
3436
3621
  function createDefaultAuthAdapter(db, rootDir) {
@@ -3545,78 +3730,15 @@ function createHonoApp(options) {
3545
3730
  rateLimiter
3546
3731
  });
3547
3732
  app.post("/api/auth/login", async (c) => authRoutes.login(c.req.raw));
3548
- app.post("/api/auth/register", async (c) => authRoutes.register(c.req.raw));
3549
- app.post("/api/auth/logout", async (c) => authRoutes.logout(c.req.raw));
3550
- app.post("/api/auth/refresh", async (c) => authRoutes.refresh(c.req.raw));
3551
- app.get("/api/auth/me", async (c) => authRoutes.me(c.req.raw));
3552
- app.get("/api/auth/sessions", async (c) => authRoutes.listSessions(c.req.raw));
3553
- app.post("/api/auth/sessions/refresh", async (c) => authRoutes.refreshSession(c.req.raw));
3554
- app.delete("/api/auth/sessions", async (c) => authRoutes.revokeOtherSessions(c.req.raw));
3555
- app.delete("/api/auth/sessions/:id", async (c) => authRoutes.revokeSession(c.req.raw, c.req.param("id")));
3556
- app.put("/api/auth/sessions/:id/name", async (c) => authRoutes.renameSession(c.req.raw, c.req.param("id")));
3557
- app.post("/api/graphql", async (c) => {
3558
- try {
3559
- const req = c.req.raw;
3560
- const apiKeyRaw = extractApiKeyFromRequest(req);
3561
- if (apiKeyRaw && db) {
3562
- const apiKeyResult = await validateApiKey(apiKeyRaw, db);
3563
- if (!apiKeyResult.valid) {
3564
- return c.json({ errors: [{ message: apiKeyResult.error || "Invalid API key" }] }, 401);
3565
- }
3566
- const apiKeyId = apiKeyResult.apiKeyId || "";
3567
- await sessionAuthAdapter?.createAuditLog({
3568
- action: "api_key_request",
3569
- userId: apiKeyResult.userId || "",
3570
- resource: "api_key",
3571
- resourceId: apiKeyId,
3572
- success: true,
3573
- metadata: {
3574
- endpoint: "/api/graphql",
3575
- method: "POST",
3576
- ip: extractIp(req)
3577
- }
3578
- });
3579
- }
3580
- const body = await req.json().catch(() => ({}));
3581
- const { query, variables } = body;
3582
- if (!query) {
3583
- return c.json({ errors: [{ message: "No query provided" }] }, 400);
3584
- }
3585
- let gqlUser;
3586
- let apiKeyCtx;
3587
- if (apiKeyRaw && db) {
3588
- const apiKeyResult = await validateApiKey(apiKeyRaw, db);
3589
- if (apiKeyResult.valid && apiKeyResult.user) {
3590
- gqlUser = apiKeyResult.user;
3591
- apiKeyCtx = createApiKeyContext(apiKeyResult);
3592
- }
3593
- }
3594
- const schema = buildGraphQLSchema({
3595
- registry,
3596
- db,
3597
- user: gqlUser,
3598
- req,
3599
- settings
3600
- });
3601
- const document = parse(query);
3602
- const result = await execute({
3603
- schema,
3604
- document,
3605
- variableValues: variables,
3606
- contextValue: { user: gqlUser, apiKeyContext: apiKeyCtx, req, db }
3607
- });
3608
- return c.json(result);
3609
- } catch (error) {
3610
- if (error.message?.includes("GraphQL is disabled")) {
3611
- return c.json({ errors: [{ message: "GraphQL API is disabled" }] }, 403);
3612
- }
3613
- if (error instanceof SyntaxError) {
3614
- return c.json({ errors: [{ message: "Invalid request body" }] }, 400);
3615
- }
3616
- console.error("[GraphQL] execution error:", error);
3617
- return c.json({ errors: [{ message: error.message || "GraphQL execution failed" }] }, 500);
3618
- }
3619
- });
3733
+ app.post("/api/auth/register", async (c) => authRoutes.register(c.req.raw));
3734
+ app.post("/api/auth/logout", async (c) => authRoutes.logout(c.req.raw));
3735
+ app.post("/api/auth/refresh", async (c) => authRoutes.refresh(c.req.raw));
3736
+ app.get("/api/auth/me", async (c) => authRoutes.me(c.req.raw));
3737
+ app.get("/api/auth/sessions", async (c) => authRoutes.listSessions(c.req.raw));
3738
+ app.post("/api/auth/sessions/refresh", async (c) => authRoutes.refreshSession(c.req.raw));
3739
+ app.delete("/api/auth/sessions", async (c) => authRoutes.revokeOtherSessions(c.req.raw));
3740
+ app.delete("/api/auth/sessions/:id", async (c) => authRoutes.revokeSession(c.req.raw, c.req.param("id")));
3741
+ app.put("/api/auth/sessions/:id/name", async (c) => authRoutes.renameSession(c.req.raw, c.req.param("id")));
3620
3742
  app.get("/api/auth/access", async (c) => {
3621
3743
  try {
3622
3744
  const { user: ctxUser, tenantID: ctxTenantID } = await resolveAuthContext(
@@ -3701,7 +3823,13 @@ function createHonoApp(options) {
3701
3823
  return c.json({ error: error.message }, 500);
3702
3824
  }
3703
3825
  });
3704
- const usersCollection = registry.getCollection("users");
3826
+ const usersCollection2 = typeof registry.hasCollection === "function" && registry.hasCollection("users") ? registry.getCollection("users") : (() => {
3827
+ try {
3828
+ return registry.getCollection("users");
3829
+ } catch {
3830
+ return usersCollection;
3831
+ }
3832
+ })();
3705
3833
  app.get("/api/users", async (c) => {
3706
3834
  try {
3707
3835
  const { user: ctxUser, tenantID: ctxTenantID } = await resolveAuthContext(
@@ -3711,7 +3839,7 @@ function createHonoApp(options) {
3711
3839
  tenantID
3712
3840
  );
3713
3841
  const access = await checkCollectionAccess(
3714
- usersCollection,
3842
+ usersCollection2,
3715
3843
  "read",
3716
3844
  c.req.raw,
3717
3845
  ctxUser,
@@ -3756,19 +3884,6 @@ function createHonoApp(options) {
3756
3884
  user,
3757
3885
  tenantID
3758
3886
  );
3759
- const access = await checkCollectionAccess(
3760
- usersCollection,
3761
- "read",
3762
- c.req.raw,
3763
- ctxUser,
3764
- ctxTenantID,
3765
- void 0,
3766
- enablePublicAccess,
3767
- defaultCollectionAccess
3768
- );
3769
- if (!access.allowed) {
3770
- return c.json({ error: access.error }, access.status || 403);
3771
- }
3772
3887
  const id = c.req.param("id");
3773
3888
  const found = await sessionAuthAdapter.findUserById(id);
3774
3889
  if (!found) {
@@ -3788,7 +3903,7 @@ function createHonoApp(options) {
3788
3903
  tenantID
3789
3904
  );
3790
3905
  const access = await checkCollectionAccess(
3791
- usersCollection,
3906
+ usersCollection2,
3792
3907
  "create",
3793
3908
  c.req.raw,
3794
3909
  ctxUser,
@@ -3813,8 +3928,18 @@ function createHonoApp(options) {
3813
3928
  password: body.password,
3814
3929
  name: body.name,
3815
3930
  role: body.role || "customer",
3931
+ avatar: body.avatar,
3816
3932
  tenantId: body.tenantId
3817
3933
  });
3934
+ if (ctxUser) {
3935
+ sessionAuthAdapter?.createAuditLog({
3936
+ action: "user_create",
3937
+ userId: ctxUser.id,
3938
+ resource: "users",
3939
+ resourceId: created.id,
3940
+ success: true
3941
+ });
3942
+ }
3818
3943
  return c.json(
3819
3944
  { data: created, message: "User created successfully" },
3820
3945
  201
@@ -3832,7 +3957,7 @@ function createHonoApp(options) {
3832
3957
  tenantID
3833
3958
  );
3834
3959
  const access = await checkCollectionAccess(
3835
- usersCollection,
3960
+ usersCollection2,
3836
3961
  "update",
3837
3962
  c.req.raw,
3838
3963
  ctxUser,
@@ -3854,6 +3979,9 @@ function createHonoApp(options) {
3854
3979
  if (body.name !== void 0) updateData.name = body.name;
3855
3980
  if (body.email !== void 0) updateData.email = body.email;
3856
3981
  if (body.role !== void 0) updateData.role = body.role;
3982
+ if (body.avatar !== void 0) {
3983
+ updateData.avatar = typeof body.avatar === "object" && body.avatar !== null ? body.avatar.id || String(body.avatar) : body.avatar;
3984
+ }
3857
3985
  if (body.tenantId !== void 0) updateData.tenantId = body.tenantId;
3858
3986
  if (body.emailVerified !== void 0)
3859
3987
  updateData.emailVerified = body.emailVerified;
@@ -3865,6 +3993,25 @@ function createHonoApp(options) {
3865
3993
  if (!updated) {
3866
3994
  return c.json({ error: "User update failed" }, 500);
3867
3995
  }
3996
+ if (ctxUser) {
3997
+ sessionAuthAdapter?.createAuditLog({
3998
+ action: "user_update",
3999
+ userId: ctxUser.id,
4000
+ resource: "users",
4001
+ resourceId: id,
4002
+ success: true
4003
+ });
4004
+ if (body.role && existing.role !== body.role) {
4005
+ sessionAuthAdapter?.createAuditLog({
4006
+ action: "role_change",
4007
+ userId: ctxUser.id,
4008
+ resource: "users",
4009
+ resourceId: id,
4010
+ success: true,
4011
+ metadata: { oldRole: existing.role, newRole: body.role }
4012
+ });
4013
+ }
4014
+ }
3868
4015
  return c.json({ data: updated, message: "User updated successfully" });
3869
4016
  } catch (error) {
3870
4017
  return c.json({ error: error.message }, 500);
@@ -3879,7 +4026,7 @@ function createHonoApp(options) {
3879
4026
  tenantID
3880
4027
  );
3881
4028
  const access = await checkCollectionAccess(
3882
- usersCollection,
4029
+ usersCollection2,
3883
4030
  "delete",
3884
4031
  c.req.raw,
3885
4032
  ctxUser,
@@ -3903,6 +4050,15 @@ function createHonoApp(options) {
3903
4050
  if (!deleted) {
3904
4051
  return c.json({ error: "User deletion failed" }, 500);
3905
4052
  }
4053
+ if (ctxUser) {
4054
+ sessionAuthAdapter?.createAuditLog({
4055
+ action: "user_delete",
4056
+ userId: ctxUser.id,
4057
+ resource: "users",
4058
+ resourceId: id,
4059
+ success: true
4060
+ });
4061
+ }
3906
4062
  return c.json({ data: existing, message: "User deleted successfully" });
3907
4063
  } catch (error) {
3908
4064
  return c.json({ error: error.message }, 500);
@@ -4059,11 +4215,11 @@ function createHonoApp(options) {
4059
4215
  const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
4060
4216
  if (!ctxUser) return c.json({ error: "Authentication required" }, 401);
4061
4217
  const service = await getMedia();
4062
- const path2 = c.req.query("path");
4063
- if (!path2) {
4218
+ const path3 = c.req.query("path");
4219
+ if (!path3) {
4064
4220
  return c.json({ error: "Path is required" }, 400);
4065
4221
  }
4066
- await service.deleteFolder(path2);
4222
+ await service.deleteFolder(path3);
4067
4223
  return c.json({ message: "Folder deleted" });
4068
4224
  } catch (error) {
4069
4225
  console.error("[Media] delete folder error:", error);
@@ -4216,6 +4372,119 @@ function createHonoApp(options) {
4216
4372
  return c.json({ error: error.message }, 500);
4217
4373
  }
4218
4374
  });
4375
+ app.get("/api/plugins", async (c) => {
4376
+ try {
4377
+ const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
4378
+ if (!ctxUser) return c.json({ error: "Authentication required" }, 401);
4379
+ const plugins = registry.getPlugins();
4380
+ const storageRegistry = registry.storageProviders;
4381
+ const pluginList = await Promise.all(
4382
+ plugins.map(async (p) => {
4383
+ let enabled = true;
4384
+ const pluginName = p.name;
4385
+ let states = {};
4386
+ try {
4387
+ const doc = await db.findOne({
4388
+ collection: "_globals_plugin-settings",
4389
+ where: {}
4390
+ });
4391
+ if (doc && doc.states) states = doc.states;
4392
+ } catch {
4393
+ }
4394
+ if (states[pluginName] !== void 0) {
4395
+ enabled = states[pluginName];
4396
+ }
4397
+ storageRegistry.setPluginEnabled(pluginName, enabled);
4398
+ return {
4399
+ id: pluginName,
4400
+ name: pluginName,
4401
+ version: p.version || "1.0.0",
4402
+ description: p.description || "",
4403
+ enabled,
4404
+ status: enabled ? "active" : "disabled"
4405
+ };
4406
+ })
4407
+ );
4408
+ return c.json(pluginList);
4409
+ } catch (error) {
4410
+ console.error("[Plugins] list error:", error);
4411
+ return c.json({ error: error.message }, 500);
4412
+ }
4413
+ });
4414
+ app.put("/api/plugins/:name/toggle", async (c) => {
4415
+ try {
4416
+ const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
4417
+ if (!ctxUser) return c.json({ error: "Authentication required" }, 401);
4418
+ const pluginName = c.req.param("name");
4419
+ const storageRegistry = registry.storageProviders;
4420
+ const currentEnabled = storageRegistry.isPluginEnabled(pluginName);
4421
+ const newEnabled = !currentEnabled;
4422
+ const affectedProviders = [];
4423
+ if (!newEnabled) {
4424
+ for (const p of storageRegistry.getAll()) {
4425
+ if (p.pluginName === pluginName) {
4426
+ affectedProviders.push(p.type);
4427
+ }
4428
+ }
4429
+ }
4430
+ const force = c.req.query("force") === "1";
4431
+ if (!force && !newEnabled && affectedProviders.length > 0) {
4432
+ let activeProvider = "local";
4433
+ try {
4434
+ const row = db?.prepare?.(`SELECT provider FROM "_globals_storage-settings" LIMIT 1`)?.get();
4435
+ if (row?.provider) activeProvider = row.provider;
4436
+ } catch {
4437
+ try {
4438
+ const result = await db?.findOne?.({
4439
+ collection: "_globals_storage-settings",
4440
+ where: {}
4441
+ });
4442
+ if (result?.provider) activeProvider = result.provider;
4443
+ } catch {
4444
+ }
4445
+ }
4446
+ if (affectedProviders.includes(activeProvider)) {
4447
+ return c.json({
4448
+ error: `Cannot disable "${pluginName}" \u2014 storage provider "${activeProvider}" is currently active. Switch to Local storage first.`,
4449
+ requiresAction: true,
4450
+ activeProvider
4451
+ }, 409);
4452
+ }
4453
+ }
4454
+ try {
4455
+ let states = {};
4456
+ let docId = "global";
4457
+ const doc = await db.findOne({
4458
+ collection: "_globals_plugin-settings",
4459
+ where: {}
4460
+ });
4461
+ if (doc) {
4462
+ states = doc.states || {};
4463
+ docId = doc.id;
4464
+ }
4465
+ states[pluginName] = newEnabled;
4466
+ if (doc) {
4467
+ await db.update({
4468
+ collection: "_globals_plugin-settings",
4469
+ id: docId,
4470
+ data: { states }
4471
+ });
4472
+ } else {
4473
+ await db.create({
4474
+ collection: "_globals_plugin-settings",
4475
+ data: { states, id: "global" }
4476
+ });
4477
+ }
4478
+ } catch (e) {
4479
+ console.warn(`[Plugins] Could not persist state for "${pluginName}":`, e);
4480
+ }
4481
+ storageRegistry.setPluginEnabled(pluginName, newEnabled);
4482
+ return c.json({ name: pluginName, enabled: newEnabled });
4483
+ } catch (error) {
4484
+ console.error("[Plugins] toggle error:", error);
4485
+ return c.json({ error: error.message }, 500);
4486
+ }
4487
+ });
4219
4488
  app.get("/api/health", (c) => {
4220
4489
  return c.json({
4221
4490
  status: "ok",
@@ -4224,6 +4493,102 @@ function createHonoApp(options) {
4224
4493
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
4225
4494
  });
4226
4495
  });
4496
+ app.get("/api/kyro/schema", async (c) => {
4497
+ const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
4498
+ if (!ctxUser) return c.json({ error: "Authentication required" }, 401);
4499
+ const extractFields = (fields) => fields.map((f) => {
4500
+ const meta = {
4501
+ name: f.name,
4502
+ type: f.type,
4503
+ label: f.label,
4504
+ required: f.required ?? false,
4505
+ unique: f.unique ?? false,
4506
+ indexed: f.indexed ?? false,
4507
+ defaultValue: f.defaultValue,
4508
+ admin: f.admin ? { ...f.admin } : void 0
4509
+ };
4510
+ if (f.minLength !== void 0) meta.minLength = f.minLength;
4511
+ if (f.maxLength !== void 0) meta.maxLength = f.maxLength;
4512
+ if (f.pattern !== void 0) meta.pattern = f.pattern;
4513
+ if (f.variant !== void 0) meta.variant = f.variant;
4514
+ if (f.hasMany !== void 0) meta.hasMany = f.hasMany;
4515
+ if (f.min !== void 0) meta.min = f.min;
4516
+ if (f.max !== void 0) meta.max = f.max;
4517
+ if (f.step !== void 0) meta.step = f.step;
4518
+ if (f.integer !== void 0) meta.integer = f.integer;
4519
+ if (f.options) meta.options = f.options;
4520
+ if (f.relationTo) meta.relationTo = f.relationTo;
4521
+ if (f.maxDepth !== void 0) meta.maxDepth = f.maxDepth;
4522
+ if (f.minRows !== void 0) meta.minRows = f.minRows;
4523
+ if (f.maxRows !== void 0) meta.maxRows = f.maxRows;
4524
+ if (f.localized !== void 0) meta.localized = f.localized;
4525
+ if (f.language) meta.language = f.language;
4526
+ if (f.format) meta.format = f.format;
4527
+ if (f.allowedTypes) meta.allowedTypes = f.allowedTypes;
4528
+ if (f.maxSize) meta.maxSize = f.maxSize;
4529
+ if (f.type === "group" || f.type === "row" || f.type === "collapsible" && f.fields) {
4530
+ meta.fields = extractFields(f.fields);
4531
+ }
4532
+ if (f.type === "array" && f.fields) {
4533
+ meta.fields = extractFields(f.fields);
4534
+ }
4535
+ if (f.type === "blocks" && f.blocks) {
4536
+ meta.blocks = f.blocks.map((b) => ({
4537
+ slug: b.slug,
4538
+ label: b.label,
4539
+ fields: extractFields(b.fields)
4540
+ }));
4541
+ }
4542
+ if (f.type === "tabs" && f.tabs) {
4543
+ meta.tabs = f.tabs.map((t) => ({
4544
+ label: t.label,
4545
+ name: t.name,
4546
+ fields: extractFields(t.fields)
4547
+ }));
4548
+ }
4549
+ return meta;
4550
+ });
4551
+ const data = { collections: {}, globals: {} };
4552
+ for (const col of registry.getCollections()) {
4553
+ const slug = col.slug;
4554
+ try {
4555
+ data.collections[slug] = {
4556
+ slug,
4557
+ label: col.label || slug,
4558
+ fields: extractFields(col.fields),
4559
+ jsonSchema: zodToJsonSchema(registry.getZodSchema(slug), { target: "openApi3" }),
4560
+ createSchema: zodToJsonSchema(registry.getCreateZodSchema(slug), { target: "openApi3" }),
4561
+ updateSchema: zodToJsonSchema(registry.getUpdateZodSchema(slug), { target: "openApi3" }),
4562
+ procedures: {
4563
+ find: { collection: slug, where: "Record<string,any>", sort: "string", limit: "number", page: "number", depth: "number", select: "string[]", draft: "boolean" },
4564
+ findByID: { collection: slug, id: "string", depth: "number", select: "string[]", draft: "boolean" },
4565
+ create: { collection: slug, data: "Record<string,any>", depth: "number", select: "string[]" },
4566
+ update: { collection: slug, id: "string", data: "Record<string,any>", depth: "number", select: "string[]", baseUpdatedAt: "string" },
4567
+ delete: { collection: slug, id: "string" },
4568
+ count: { collection: slug, where: "Record<string,any>" }
4569
+ }
4570
+ };
4571
+ } catch {
4572
+ }
4573
+ }
4574
+ for (const global of registry.getGlobals()) {
4575
+ const slug = global.slug;
4576
+ try {
4577
+ data.globals[slug] = {
4578
+ slug,
4579
+ label: global.label || slug,
4580
+ fields: extractFields(global.fields),
4581
+ jsonSchema: zodToJsonSchema(registry.getZodSchema(slug), { target: "openApi3" }),
4582
+ procedures: {
4583
+ get: {},
4584
+ update: { data: "Record<string,any>" }
4585
+ }
4586
+ };
4587
+ } catch {
4588
+ }
4589
+ }
4590
+ return c.json(data);
4591
+ });
4227
4592
  app.get("/api/collections", async (c) => {
4228
4593
  const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
4229
4594
  if (!ctxUser) return c.json({ error: "Authentication required" }, 401);
@@ -4239,6 +4604,129 @@ function createHonoApp(options) {
4239
4604
  }));
4240
4605
  return c.json(collections2);
4241
4606
  });
4607
+ function resolveDocField(fields, doc, fieldName) {
4608
+ if (fieldName in doc) return doc[fieldName];
4609
+ for (const field of fields) {
4610
+ if (!field.name) continue;
4611
+ if (field.type === "tabs" && field.tabs) {
4612
+ const data = doc[field.name];
4613
+ if (data && typeof data === "object" && fieldName in data) return data[fieldName];
4614
+ }
4615
+ if ((field.type === "group" || field.type === "collapsible") && field.fields) {
4616
+ const data = doc[field.name];
4617
+ if (data && typeof data === "object") {
4618
+ if (fieldName in data) return data[fieldName];
4619
+ const nested = resolveDocField(field.fields, data, fieldName);
4620
+ if (nested !== void 0) return nested;
4621
+ }
4622
+ }
4623
+ }
4624
+ return void 0;
4625
+ }
4626
+ function flattenRelationshipFields(fields) {
4627
+ const relFields = [];
4628
+ for (const field of fields) {
4629
+ if (field.type === "relationship") {
4630
+ relFields.push(field);
4631
+ } else if (field.type === "tabs" && field.tabs) {
4632
+ for (const tab of field.tabs) {
4633
+ relFields.push(...flattenRelationshipFields(tab.fields || []));
4634
+ }
4635
+ } else if ((field.type === "group" || field.type === "collapsible") && field.fields) {
4636
+ relFields.push(...flattenRelationshipFields(field.fields || []));
4637
+ }
4638
+ }
4639
+ return relFields;
4640
+ }
4641
+ function extractRelValue(value) {
4642
+ if (!value) return [];
4643
+ if (typeof value === "string") return [value];
4644
+ if (Array.isArray(value)) return value.map((v) => typeof v === "object" ? v.value ?? v : v);
4645
+ if (typeof value === "object") {
4646
+ const v = value.value ?? value;
4647
+ return typeof v === "string" ? [v] : Array.isArray(v) ? v : [];
4648
+ }
4649
+ return [];
4650
+ }
4651
+ async function populateRelationships(docs, collection, db2, registry2) {
4652
+ if (docs.length === 0) return;
4653
+ const relFields = flattenRelationshipFields(collection.fields);
4654
+ if (relFields.length === 0) return;
4655
+ for (const relField of relFields) {
4656
+ const targetSlugs = Array.isArray(relField.relationTo) ? relField.relationTo : [relField.relationTo];
4657
+ const idsBySlug = {};
4658
+ for (const slug of targetSlugs) {
4659
+ idsBySlug[slug] = /* @__PURE__ */ new Set();
4660
+ }
4661
+ for (const doc of docs) {
4662
+ const raw = resolveDocField(collection.fields, doc, relField.name);
4663
+ const ids = extractRelValue(raw);
4664
+ for (const id of ids) {
4665
+ if (!id) continue;
4666
+ if (targetSlugs.length === 1) {
4667
+ idsBySlug[targetSlugs[0]].add(id);
4668
+ } else {
4669
+ for (const slug of targetSlugs) {
4670
+ idsBySlug[slug].add(id);
4671
+ }
4672
+ }
4673
+ }
4674
+ }
4675
+ for (const [targetSlug, idSet] of Object.entries(idsBySlug)) {
4676
+ if (idSet.size === 0) continue;
4677
+ const targetCollection = registry2.getCollection(targetSlug);
4678
+ if (!targetCollection) continue;
4679
+ const titleField = targetCollection.admin?.useAsTitle || "title";
4680
+ const idArr = Array.from(idSet);
4681
+ const relatedDocs = [];
4682
+ for (const id of idArr) {
4683
+ try {
4684
+ const relDoc = await db2.findByID({ collection: targetSlug, id, draft: true });
4685
+ if (relDoc) {
4686
+ const title = resolveDocField(targetCollection.fields, relDoc, titleField) ?? id;
4687
+ relatedDocs.push({ id, title: String(title) });
4688
+ }
4689
+ } catch {
4690
+ relatedDocs.push({ id, title: id });
4691
+ }
4692
+ }
4693
+ const titleMap = new Map(relatedDocs.map((d) => [d.id, d.title]));
4694
+ for (const doc of docs) {
4695
+ const raw = resolveDocField(collection.fields, doc, relField.name);
4696
+ if (!raw) continue;
4697
+ const setValue = (val) => {
4698
+ if (relField.name in doc) {
4699
+ doc[relField.name] = val;
4700
+ } else {
4701
+ for (const f of collection.fields) {
4702
+ if (f.type === "tabs" && f.tabs) {
4703
+ for (const tab of f.tabs) {
4704
+ if (tab.fields?.some((tf) => tf.name === relField.name)) {
4705
+ const tabData = doc[f.name];
4706
+ if (tabData && typeof tabData === "object") {
4707
+ tabData[relField.name] = val;
4708
+ }
4709
+ }
4710
+ }
4711
+ }
4712
+ }
4713
+ }
4714
+ };
4715
+ if (typeof raw === "string") {
4716
+ setValue({ id: raw, title: titleMap.get(raw) || raw });
4717
+ } else if (Array.isArray(raw)) {
4718
+ setValue(raw.map((v) => {
4719
+ const id = typeof v === "object" ? v.value ?? v.id : v;
4720
+ return { id, title: titleMap.get(id) || id };
4721
+ }));
4722
+ } else if (typeof raw === "object") {
4723
+ const id = raw.value ?? raw.id;
4724
+ setValue({ id, title: titleMap.get(id) || id });
4725
+ }
4726
+ }
4727
+ }
4728
+ }
4729
+ }
4242
4730
  app.get("/api/search", async (c) => {
4243
4731
  try {
4244
4732
  const query = c.req.query("q") || "";
@@ -4290,11 +4778,12 @@ function createHonoApp(options) {
4290
4778
  limit,
4291
4779
  tenantID: ctxTenantID
4292
4780
  });
4781
+ await populateRelationships(searchResult.docs, collection, db, registry);
4293
4782
  for (const doc of searchResult.docs) {
4294
4783
  const titleField = collection.admin?.useAsTitle || searchableFields.find(
4295
4784
  (f) => f === "title" || f === "name" || f === "heading" || f === "slug"
4296
4785
  );
4297
- const title = titleField ? doc[titleField] : doc.id;
4786
+ const title = titleField ? resolveDocField(collection.fields, doc, titleField) ?? doc.id : doc.id;
4298
4787
  results.push({
4299
4788
  collection: collection.slug,
4300
4789
  label: collection.label || collection.slug,
@@ -4634,37 +5123,31 @@ function createHonoApp(options) {
4634
5123
  if (!access.allowed) {
4635
5124
  return c.json({ error: access.error }, access.status || 403);
4636
5125
  }
4637
- auditApiKeyUsage(sessionAuthAdapter, apiKeyContext, basePath, "GET", c.req.raw);
5126
+ if (ctxTenantID) {
5127
+ db.setTenantContext({ tenantId: ctxTenantID, userId: ctxUser?.id ?? "", role: ctxUser?.role, isSuperAdmin: ctxUser?.role === "super_admin" });
5128
+ }
4638
5129
  const url = new URL(c.req.url);
4639
5130
  const page = parseInt(url.searchParams.get("page") || "1");
4640
- const limit = Math.min(
4641
- parseInt(url.searchParams.get("limit") || "10"),
4642
- 100
4643
- );
5131
+ const limit = Math.min(parseInt(url.searchParams.get("limit") || "10"), 100);
4644
5132
  const sort = url.searchParams.get("sort") || void 0;
4645
5133
  const depth = parseInt(url.searchParams.get("depth") || "0");
4646
5134
  const select = url.searchParams.get("select")?.split(",") || void 0;
4647
- let where = {};
4648
- const whereParam = url.searchParams.get("where");
4649
- if (whereParam) {
4650
- try {
4651
- where = JSON.parse(whereParam);
4652
- } catch {
4653
- }
4654
- }
5135
+ const isDraftRequest = !!ctxUser;
4655
5136
  const result = await db.find({
4656
5137
  collection: slug,
4657
- where,
5138
+ where: {},
4658
5139
  sort,
4659
5140
  limit,
4660
5141
  page,
4661
5142
  depth,
4662
5143
  tenantID: ctxTenantID,
4663
- select
5144
+ select,
5145
+ draft: isDraftRequest
4664
5146
  });
5147
+ await populateRelationships(result.docs, collection, db, registry);
4665
5148
  return c.json(result);
4666
5149
  } catch (error) {
4667
- console.error(`[API] Error listing ${slug}:`, error);
5150
+ console.error("[API] list error:", error);
4668
5151
  return c.json({ error: error.message }, 500);
4669
5152
  }
4670
5153
  });
@@ -4689,6 +5172,9 @@ function createHonoApp(options) {
4689
5172
  return c.json({ error: access.error }, access.status || 403);
4690
5173
  }
4691
5174
  const id = c.req.param("id");
5175
+ if (!id) {
5176
+ return c.json({ error: "Missing document ID" }, 400);
5177
+ }
4692
5178
  const url = new URL(c.req.url);
4693
5179
  const compareA = url.searchParams.get("compareA");
4694
5180
  const compareB = url.searchParams.get("compareB");
@@ -4772,23 +5258,39 @@ function createHonoApp(options) {
4772
5258
  const id = c.req.param("id");
4773
5259
  const body = await c.req.json();
4774
5260
  const baseUpdatedAt = readBaseUpdatedAt(body);
4775
- const data = body.data ?? omitRevisionFields(body);
4776
5261
  const originalDoc = await db.findByID({
4777
5262
  collection: slug,
4778
5263
  id,
4779
- tenantID: ctxTenantID
5264
+ tenantID: ctxTenantID,
5265
+ draft: true
4780
5266
  });
4781
5267
  if (!originalDoc) {
4782
5268
  return c.json({ error: "Document not found" }, 404);
4783
5269
  }
5270
+ let finalData;
5271
+ if (body.delta) {
5272
+ finalData = { ...originalDoc, ...body.delta };
5273
+ } else {
5274
+ finalData = body.data ?? omitRevisionFields(body);
5275
+ }
4784
5276
  const draft = await db.upsertDraft({
4785
5277
  collection: slug,
4786
5278
  documentId: id,
4787
5279
  tenantID: ctxTenantID,
4788
- data,
5280
+ data: finalData,
4789
5281
  baseUpdatedAt,
4790
5282
  draftUpdatedAt: body.draftUpdatedAt
4791
5283
  });
5284
+ if (ctxUser) {
5285
+ sessionAuthAdapter?.createAuditLog({
5286
+ action: "document_update",
5287
+ userId: ctxUser.id,
5288
+ resource: slug,
5289
+ resourceId: id,
5290
+ success: true,
5291
+ metadata: { type: "draft_save" }
5292
+ });
5293
+ }
4792
5294
  return c.json({ data: draft, message: "Draft saved successfully" });
4793
5295
  } catch (error) {
4794
5296
  return c.json({ error: error.message }, 500);
@@ -4819,6 +5321,16 @@ function createHonoApp(options) {
4819
5321
  documentId: c.req.param("id"),
4820
5322
  tenantID: ctxTenantID
4821
5323
  });
5324
+ if (ctxUser) {
5325
+ sessionAuthAdapter?.createAuditLog({
5326
+ action: "document_update",
5327
+ userId: ctxUser.id,
5328
+ resource: slug,
5329
+ resourceId: c.req.param("id"),
5330
+ success: true,
5331
+ metadata: { type: "draft_discard" }
5332
+ });
5333
+ }
4822
5334
  return c.json({ message: "Draft discarded successfully" });
4823
5335
  } catch (error) {
4824
5336
  return c.json({ error: error.message }, 500);
@@ -4861,12 +5373,14 @@ function createHonoApp(options) {
4861
5373
  doc = await db.findOne({
4862
5374
  collection: slug,
4863
5375
  where: { slug: id },
4864
- tenantID: ctxTenantID
5376
+ tenantID: ctxTenantID,
5377
+ draft: isDraftRequest
4865
5378
  });
4866
5379
  }
4867
5380
  if (!doc) {
4868
5381
  return c.json({ error: "Document not found" }, 404);
4869
5382
  }
5383
+ await populateRelationships([doc], collection, db, registry);
4870
5384
  return c.json({ data: doc });
4871
5385
  } catch (error) {
4872
5386
  return c.json({ error: error.message }, 500);
@@ -4892,23 +5406,80 @@ function createHonoApp(options) {
4892
5406
  if (!access.allowed) {
4893
5407
  return c.json({ error: access.error }, access.status || 403);
4894
5408
  }
5409
+ if (ctxTenantID) {
5410
+ db.setTenantContext({ tenantId: ctxTenantID, userId: ctxUser?.id ?? "", role: ctxUser?.role, isSuperAdmin: ctxUser?.role === "super_admin" });
5411
+ }
4895
5412
  auditApiKeyUsage(sessionAuthAdapter, apiKeyContext, basePath, "POST", c.req.raw);
4896
5413
  const body = await c.req.json();
5414
+ let validated = body;
5415
+ for (const field of collection.fields) {
5416
+ if (field.name && validated[field.name] === "") {
5417
+ const isTextual = field.type === "text" || field.type === "textarea" || field.type === "code" || field.type === "markdown" || field.type === "email" || field.type === "password" || field.type === "color";
5418
+ if (!isTextual) {
5419
+ validated[field.name] = null;
5420
+ }
5421
+ }
5422
+ }
5423
+ convertRichtextFields(collection.fields, validated);
5424
+ const hookReq = c.req.raw;
5425
+ if (collection.hooks?.beforeValidate) {
5426
+ for (const hook of collection.hooks.beforeValidate) {
5427
+ const hookResult = await hook({
5428
+ collection: slug,
5429
+ data: validated,
5430
+ req: hookReq,
5431
+ user: ctxUser,
5432
+ tenantID: ctxTenantID,
5433
+ operation: "create"
5434
+ });
5435
+ if (hookResult) Object.assign(validated, hookResult);
5436
+ }
5437
+ }
4897
5438
  const schema = registry.getCreateZodSchema(slug);
4898
- let validated;
4899
5439
  try {
4900
- validated = schema.parse(body);
5440
+ validated = schema.parse(validated);
4901
5441
  } catch (zodErr) {
4902
- return c.json({ error: "Validation failed", details: zodErr.errors }, 400);
5442
+ return c.json({ error: `Validation failed: ${formatZodErrors(zodErr.errors)}`, details: zodErr.errors }, 400);
4903
5443
  }
4904
5444
  if (collection.tenantScoped && ctxTenantID) {
4905
5445
  validated.tenantID = ctxTenantID;
4906
5446
  }
5447
+ const isDraftEnabled = collection.versions?.drafts === true;
5448
+ if (isDraftEnabled) {
5449
+ validated.publishStatus = "draft";
5450
+ validated.hasDraft = false;
5451
+ }
5452
+ if (collection.hooks?.beforeChange) {
5453
+ for (const hook of collection.hooks.beforeChange) {
5454
+ const hookResult = await hook({
5455
+ collection: slug,
5456
+ data: validated,
5457
+ req: hookReq,
5458
+ user: ctxUser,
5459
+ tenantID: ctxTenantID,
5460
+ operation: "create"
5461
+ });
5462
+ if (hookResult) Object.assign(validated, hookResult);
5463
+ }
5464
+ }
4907
5465
  const doc = await db.create({
4908
5466
  collection: slug,
4909
5467
  data: validated,
4910
5468
  tenantID: ctxTenantID
4911
5469
  });
5470
+ if (collection.hooks?.afterChange) {
5471
+ for (const hook of collection.hooks.afterChange) {
5472
+ await hook({
5473
+ collection: slug,
5474
+ doc,
5475
+ data: validated,
5476
+ req: hookReq,
5477
+ user: ctxUser,
5478
+ tenantID: ctxTenantID,
5479
+ operation: "create"
5480
+ });
5481
+ }
5482
+ }
4912
5483
  if (webhookService) {
4913
5484
  webhookService.trigger(getWebhookEvent(slug, "create"), {
4914
5485
  collection: slug,
@@ -4918,11 +5489,20 @@ function createHonoApp(options) {
4918
5489
  tenantId: ctxTenantID
4919
5490
  }).catch((err) => console.error(`[Webhook] Failed to trigger:`, err));
4920
5491
  }
5492
+ if (ctxUser) {
5493
+ sessionAuthAdapter?.createAuditLog({
5494
+ action: "document_create",
5495
+ userId: ctxUser.id,
5496
+ resource: slug,
5497
+ resourceId: doc.id,
5498
+ success: true
5499
+ });
5500
+ }
4921
5501
  return c.json({ data: doc, message: "Created successfully" }, 201);
4922
5502
  } catch (error) {
4923
5503
  if (error.name === "ZodError") {
4924
5504
  return c.json(
4925
- { error: "Validation failed", details: error.errors },
5505
+ { error: `Validation failed: ${formatZodErrors(error.errors)}`, details: error.errors },
4926
5506
  400
4927
5507
  );
4928
5508
  }
@@ -4957,32 +5537,66 @@ function createHonoApp(options) {
4957
5537
  bodyKeys: Object.keys(body),
4958
5538
  tenantID: ctxTenantID
4959
5539
  });
4960
- const cleaned = Object.fromEntries(
4961
- Object.entries(omitRevisionFields(body)).filter(
4962
- ([_, v]) => v !== "null" && v !== void 0
4963
- )
4964
- );
4965
- const schema = registry.getUpdateZodSchema(slug);
4966
- const validated = schema.parse(cleaned);
4967
- console.log(`[PATCH] Validated data:`, Object.keys(validated));
4968
5540
  const originalDoc = await db.findByID({
4969
5541
  collection: slug,
4970
5542
  id,
4971
5543
  tenantID: ctxTenantID,
4972
5544
  draft: true
4973
- // Always fetch current doc regardless of status
4974
5545
  });
4975
- if (originalDoc) {
4976
- console.log(`[PATCH] Original doc updatedAt:`, originalDoc.updatedAt);
4977
- }
4978
5546
  if (!originalDoc) {
4979
5547
  return c.json({ error: "Document not found" }, 404);
4980
5548
  }
4981
5549
  if (baseUpdatedAt && originalDoc.updatedAt && baseUpdatedAt !== originalDoc.updatedAt) {
4982
5550
  return c.json(buildConflictResponse(baseUpdatedAt, originalDoc), 409);
4983
5551
  }
5552
+ let validated = Object.fromEntries(
5553
+ Object.entries(omitRevisionFields(body)).filter(
5554
+ ([_, v]) => v !== "null" && v !== void 0
5555
+ )
5556
+ );
5557
+ for (const field of collection.fields) {
5558
+ if (field.name && validated[field.name] === "") {
5559
+ const isTextual = field.type === "text" || field.type === "textarea" || field.type === "code" || field.type === "markdown" || field.type === "email" || field.type === "password" || field.type === "color";
5560
+ if (!isTextual) {
5561
+ validated[field.name] = null;
5562
+ }
5563
+ }
5564
+ }
5565
+ convertRichtextFields(collection.fields, validated);
5566
+ const hookReq = c.req.raw;
5567
+ if (collection.hooks?.beforeValidate) {
5568
+ for (const hook of collection.hooks.beforeValidate) {
5569
+ const hookResult = await hook({
5570
+ collection: slug,
5571
+ data: validated,
5572
+ originalDoc,
5573
+ req: hookReq,
5574
+ user: ctxUser,
5575
+ tenantID: ctxTenantID,
5576
+ operation: "update"
5577
+ });
5578
+ if (hookResult) Object.assign(validated, hookResult);
5579
+ }
5580
+ }
5581
+ const schema = registry.getUpdateZodSchema(slug);
5582
+ validated = schema.parse(validated);
5583
+ if (collection.hooks?.beforeChange) {
5584
+ for (const hook of collection.hooks.beforeChange) {
5585
+ const hookResult = await hook({
5586
+ collection: slug,
5587
+ data: validated,
5588
+ originalDoc,
5589
+ req: hookReq,
5590
+ user: ctxUser,
5591
+ tenantID: ctxTenantID,
5592
+ operation: "update"
5593
+ });
5594
+ if (hookResult) Object.assign(validated, hookResult);
5595
+ }
5596
+ }
5597
+ console.log(`[PATCH] Validated data:`, Object.keys(validated));
4984
5598
  const isDraftEnabled = collection.versions?.drafts === true;
4985
- const isAlreadyPublished = originalDoc._status === "published";
5599
+ const isAlreadyPublished = originalDoc.publishStatus === "published";
4986
5600
  let doc;
4987
5601
  if (isDraftEnabled && isAlreadyPublished) {
4988
5602
  await db.createVersion({
@@ -4997,18 +5611,20 @@ function createHonoApp(options) {
4997
5611
  await db.update({
4998
5612
  collection: slug,
4999
5613
  id,
5000
- data: { _has_draft: true },
5614
+ data: { hasDraft: true },
5001
5615
  // Keep old data, just set flag
5002
5616
  tenantID: ctxTenantID
5003
5617
  });
5004
5618
  } else {
5005
- const saveData = isDraftEnabled ? { ...validated, _status: "draft", _has_draft: false } : validated;
5619
+ const saveData = isDraftEnabled ? { ...validated, publishStatus: "draft", hasDraft: false } : validated;
5620
+ console.log(`[PATCH] About to call db.update for ${slug}/${id} with keys:`, Object.keys(saveData));
5006
5621
  await db.update({
5007
5622
  collection: slug,
5008
5623
  id,
5009
5624
  data: saveData,
5010
5625
  tenantID: ctxTenantID
5011
5626
  });
5627
+ console.log(`[PATCH] db.update SUCCEEDED for ${slug}/${id}`);
5012
5628
  }
5013
5629
  doc = await db.findByID({
5014
5630
  collection: slug,
@@ -5035,6 +5651,20 @@ function createHonoApp(options) {
5035
5651
  tenantID: ctxTenantID
5036
5652
  });
5037
5653
  }
5654
+ if (collection.hooks?.afterChange) {
5655
+ for (const hook of collection.hooks.afterChange) {
5656
+ await hook({
5657
+ collection: slug,
5658
+ doc,
5659
+ data: validated,
5660
+ originalDoc,
5661
+ req: hookReq,
5662
+ user: ctxUser,
5663
+ tenantID: ctxTenantID,
5664
+ operation: "update"
5665
+ });
5666
+ }
5667
+ }
5038
5668
  if (webhookService) {
5039
5669
  webhookService.trigger(getWebhookEvent(slug, "update"), {
5040
5670
  collection: slug,
@@ -5045,16 +5675,26 @@ function createHonoApp(options) {
5045
5675
  tenantId: ctxTenantID
5046
5676
  }).catch((err) => console.error(`[Webhook] Failed to trigger:`, err));
5047
5677
  }
5048
- console.log(`[PATCH] Result doc updatedAt:`, doc?.updatedAt);
5678
+ auditApiKeyUsage(sessionAuthAdapter, apiKeyContext, `${basePath}/${id}`, "PATCH", c.req.raw);
5679
+ if (ctxUser) {
5680
+ sessionAuthAdapter?.createAuditLog({
5681
+ action: "document_update",
5682
+ userId: ctxUser.id,
5683
+ resource: slug,
5684
+ resourceId: id,
5685
+ success: true
5686
+ });
5687
+ }
5049
5688
  return c.json({ data: doc, message: isDraftEnabled ? "Draft saved" : "Updated successfully" });
5050
5689
  } catch (error) {
5051
5690
  if (error.name === "ZodError") {
5052
5691
  console.error(`[PATCH ${basePath}/:id] Validation failed:`, error.errors);
5053
5692
  return c.json(
5054
- { error: "Validation failed", details: error.errors },
5693
+ { error: `Validation failed: ${formatZodErrors(error.errors)}`, details: error.errors },
5055
5694
  400
5056
5695
  );
5057
5696
  }
5697
+ console.error(`[PATCH ${basePath}/:id] ERROR:`, error.message, `CAUSE:`, error.cause?.message || error.cause, `QUERY:`, error.query);
5058
5698
  return c.json({ error: error.message }, 500);
5059
5699
  }
5060
5700
  });
@@ -5078,13 +5718,33 @@ function createHonoApp(options) {
5078
5718
  if (!access.allowed) {
5079
5719
  return c.json({ error: access.error }, access.status || 403);
5080
5720
  }
5721
+ if (ctxTenantID) {
5722
+ db.setTenantContext({ tenantId: ctxTenantID, userId: ctxUser?.id ?? "", role: ctxUser?.role, isSuperAdmin: ctxUser?.role === "super_admin" });
5723
+ }
5081
5724
  const id = c.req.param("id");
5082
5725
  console.log(`[DELETE] Deleting ${slug}/${id}`);
5726
+ const hookReq = c.req.raw;
5083
5727
  const originalDoc = await db.findByID({
5084
5728
  collection: slug,
5085
5729
  id,
5086
- tenantID: ctxTenantID
5730
+ tenantID: ctxTenantID,
5731
+ draft: true
5087
5732
  });
5733
+ if (!originalDoc) {
5734
+ return c.json({ error: "Document not found" }, 404);
5735
+ }
5736
+ if (collection.hooks?.beforeDelete) {
5737
+ for (const hook of collection.hooks.beforeDelete) {
5738
+ await hook({
5739
+ collection: slug,
5740
+ doc: originalDoc,
5741
+ req: hookReq,
5742
+ user: ctxUser,
5743
+ tenantID: ctxTenantID,
5744
+ operation: "delete"
5745
+ });
5746
+ }
5747
+ }
5088
5748
  const doc = await db.delete({
5089
5749
  collection: slug,
5090
5750
  id,
@@ -5095,6 +5755,19 @@ function createHonoApp(options) {
5095
5755
  documentId: id,
5096
5756
  tenantID: ctxTenantID
5097
5757
  });
5758
+ if (collection.hooks?.afterDelete) {
5759
+ for (const hook of collection.hooks.afterDelete) {
5760
+ await hook({
5761
+ collection: slug,
5762
+ doc,
5763
+ originalDoc,
5764
+ req: hookReq,
5765
+ user: ctxUser,
5766
+ tenantID: ctxTenantID,
5767
+ operation: "delete"
5768
+ });
5769
+ }
5770
+ }
5098
5771
  if (webhookService) {
5099
5772
  webhookService.trigger(getWebhookEvent(slug, "delete"), {
5100
5773
  collection: slug,
@@ -5105,6 +5778,16 @@ function createHonoApp(options) {
5105
5778
  tenantId: ctxTenantID
5106
5779
  }).catch((err) => console.error(`[Webhook] Failed to trigger:`, err));
5107
5780
  }
5781
+ auditApiKeyUsage(sessionAuthAdapter, apiKeyContext, `${basePath}/${id}`, "DELETE", c.req.raw);
5782
+ if (ctxUser) {
5783
+ sessionAuthAdapter?.createAuditLog({
5784
+ action: "document_delete",
5785
+ userId: ctxUser.id,
5786
+ resource: slug,
5787
+ resourceId: id,
5788
+ success: true
5789
+ });
5790
+ }
5108
5791
  return c.json({ data: doc, message: "Deleted successfully" });
5109
5792
  } catch (error) {
5110
5793
  console.error(`[DELETE] Error deleting ${slug}:`, error);
@@ -5138,7 +5821,8 @@ function createHonoApp(options) {
5138
5821
  const originalDoc = await db.findByID({
5139
5822
  collection: slug,
5140
5823
  id,
5141
- tenantID: ctxTenantID
5824
+ tenantID: ctxTenantID,
5825
+ draft: true
5142
5826
  });
5143
5827
  if (!originalDoc) {
5144
5828
  console.log("[Duplicate] Document not found");
@@ -5198,7 +5882,7 @@ function createHonoApp(options) {
5198
5882
  const doc = await db.update({
5199
5883
  collection: slug,
5200
5884
  id,
5201
- data: { ...version.data, _status: "draft", _has_draft: false },
5885
+ data: { ...version.data, publishStatus: "draft", hasDraft: false },
5202
5886
  tenantID: ctxTenantID
5203
5887
  });
5204
5888
  return c.json({
@@ -5243,7 +5927,7 @@ function createHonoApp(options) {
5243
5927
  const doc = await db.update({
5244
5928
  collection: slug,
5245
5929
  id: c.req.param("id"),
5246
- data: { ...version.data, _status: "draft", _has_draft: false },
5930
+ data: { ...version.data, publishStatus: "draft", hasDraft: false },
5247
5931
  tenantID: ctxTenantID
5248
5932
  });
5249
5933
  return c.json({
@@ -5276,6 +5960,9 @@ function createHonoApp(options) {
5276
5960
  if (!access.allowed) {
5277
5961
  return c.json({ error: access.error }, access.status || 403);
5278
5962
  }
5963
+ if (ctxTenantID) {
5964
+ db.setTenantContext({ tenantId: ctxTenantID, userId: ctxUser?.id ?? "", role: ctxUser?.role, isSuperAdmin: ctxUser?.role === "super_admin" });
5965
+ }
5279
5966
  const id = c.req.param("id");
5280
5967
  const body = await c.req.json().catch(() => ({}));
5281
5968
  const baseUpdatedAt = readBaseUpdatedAt(body);
@@ -5291,9 +5978,9 @@ function createHonoApp(options) {
5291
5978
  if (baseUpdatedAt && originalDoc.updatedAt && baseUpdatedAt !== originalDoc.updatedAt) {
5292
5979
  return c.json(buildConflictResponse(baseUpdatedAt, originalDoc), 409);
5293
5980
  }
5294
- let publishData = { _status: "published", _has_draft: false };
5981
+ let publishData = { publishStatus: "published", hasDraft: false };
5295
5982
  let finalContent = originalDoc;
5296
- if (originalDoc._has_draft) {
5983
+ if (originalDoc.hasDraft) {
5297
5984
  const versions = await db.findVersions({
5298
5985
  collection: slug,
5299
5986
  documentId: id,
@@ -5316,7 +6003,7 @@ function createHonoApp(options) {
5316
6003
  await db.createVersion({
5317
6004
  collection: slug,
5318
6005
  documentId: id,
5319
- data: { ...finalContent, _status: "published" },
6006
+ data: { ...finalContent, publishStatus: "published" },
5320
6007
  status: "published",
5321
6008
  createdBy: ctxUser?.id,
5322
6009
  changeDescription: "Published",
@@ -5375,7 +6062,7 @@ function createHonoApp(options) {
5375
6062
  const doc = await db.update({
5376
6063
  collection: slug,
5377
6064
  id,
5378
- data: { _status: "draft" },
6065
+ data: { publishStatus: "draft" },
5379
6066
  tenantID: ctxTenantID
5380
6067
  });
5381
6068
  if (webhookService) {
@@ -5411,12 +6098,30 @@ function createHonoApp(options) {
5411
6098
  return c.json({ error: access.error }, access.status || 403);
5412
6099
  }
5413
6100
  const isDraftRequest = c.req.query("draft") === "true" && !!ctxUser;
5414
- const doc = await db.findOne({
6101
+ let doc = await db.findOne({
5415
6102
  collection: `_globals_${slug}`,
5416
6103
  where: {},
5417
6104
  tenantID: ctxTenantID,
5418
6105
  draft: isDraftRequest
5419
6106
  });
6107
+ if (slug === "system") {
6108
+ const newSecret = crypto.randomBytes(32).toString("hex");
6109
+ if (!doc) {
6110
+ doc = await db.create({
6111
+ collection: `_globals_${slug}`,
6112
+ data: { id: slug, appSecret: newSecret },
6113
+ tenantID: ctxTenantID
6114
+ });
6115
+ } else if (!doc.appSecret) {
6116
+ await db.update({
6117
+ collection: `_globals_${slug}`,
6118
+ id: slug,
6119
+ data: { appSecret: newSecret },
6120
+ tenantID: ctxTenantID
6121
+ });
6122
+ doc.appSecret = newSecret;
6123
+ }
6124
+ }
5420
6125
  return c.json({ data: doc || {} });
5421
6126
  } catch (error) {
5422
6127
  return c.json({ error: error.message }, 500);
@@ -5436,18 +6141,27 @@ function createHonoApp(options) {
5436
6141
  if (!access.allowed) {
5437
6142
  return c.json({ error: access.error }, access.status || 403);
5438
6143
  }
5439
- const body = await c.req.json();
6144
+ const body = omitRevisionFields(await c.req.json());
5440
6145
  const cleaned = Object.fromEntries(
5441
6146
  Object.entries(body).filter(([_, v]) => v !== null && v !== "null" && v !== void 0)
5442
6147
  );
6148
+ for (const field of globalConfig.fields) {
6149
+ if (field.name && cleaned[field.name] === "") {
6150
+ const isTextual = field.type === "text" || field.type === "textarea" || field.type === "code" || field.type === "markdown" || field.type === "email" || field.type === "password" || field.type === "color";
6151
+ if (!isTextual) {
6152
+ cleaned[field.name] = null;
6153
+ }
6154
+ }
6155
+ }
6156
+ convertRichtextFields(globalConfig.fields, cleaned);
5443
6157
  const schema = registry.getZodSchema(slug);
5444
6158
  let validated;
5445
6159
  try {
5446
6160
  validated = schema.parse(cleaned);
5447
6161
  } catch (zodErr) {
5448
- return c.json({ error: "Validation failed", details: zodErr.errors }, 400);
6162
+ return c.json({ error: `Validation failed: ${formatZodErrors(zodErr.errors)}`, details: zodErr.errors }, 400);
5449
6163
  }
5450
- const SYSTEM_FIELDS = /* @__PURE__ */ new Set(["id", "createdAt", "updatedAt", "_status", "_has_draft"]);
6164
+ const SYSTEM_FIELDS = /* @__PURE__ */ new Set(["id", "createdAt", "updatedAt", "publishStatus", "hasDraft", "baseUpdatedAt", "_baseUpdatedAt"]);
5451
6165
  const userData = Object.fromEntries(
5452
6166
  Object.entries(validated).filter(([k]) => !SYSTEM_FIELDS.has(k))
5453
6167
  );
@@ -5459,7 +6173,7 @@ function createHonoApp(options) {
5459
6173
  draft: true
5460
6174
  });
5461
6175
  const isDraftEnabled = globalConfig.versions?.drafts === true;
5462
- const isAlreadyPublished = originalDoc?._status === "published";
6176
+ const isAlreadyPublished = originalDoc?.publishStatus === "published";
5463
6177
  let doc;
5464
6178
  if (isDraftEnabled && isAlreadyPublished) {
5465
6179
  await db.createVersion({
@@ -5474,11 +6188,11 @@ function createHonoApp(options) {
5474
6188
  doc = await db.update({
5475
6189
  collection: collectionSlug,
5476
6190
  id: slug,
5477
- data: { _has_draft: true },
6191
+ data: { hasDraft: true },
5478
6192
  tenantID: ctxTenantID
5479
6193
  });
5480
6194
  } else {
5481
- const saveData = isDraftEnabled ? { ...userData, _status: "draft", _has_draft: false } : { ...userData, _status: "published", _has_draft: false };
6195
+ const saveData = isDraftEnabled ? { ...userData, publishStatus: "draft", hasDraft: false } : { ...userData, publishStatus: "published", hasDraft: false };
5482
6196
  if (originalDoc) {
5483
6197
  doc = await db.update({
5484
6198
  collection: collectionSlug,
@@ -5509,6 +6223,15 @@ function createHonoApp(options) {
5509
6223
  mediaService = null;
5510
6224
  mediaServiceInitError = null;
5511
6225
  }
6226
+ if (ctxUser) {
6227
+ sessionAuthAdapter?.createAuditLog({
6228
+ action: "settings_change",
6229
+ userId: ctxUser.id,
6230
+ resource: `global:${slug}`,
6231
+ resourceId: slug,
6232
+ success: true
6233
+ });
6234
+ }
5512
6235
  return c.json({ data: doc, message: "Updated successfully" });
5513
6236
  } catch (error) {
5514
6237
  console.error(`[API] Save global "${slug}" failed:`, error);
@@ -5533,9 +6256,9 @@ function createHonoApp(options) {
5533
6256
  draft: true
5534
6257
  });
5535
6258
  if (!originalDoc) return c.json({ error: "Global not found" }, 404);
5536
- let publishData = { _status: "published", _has_draft: false };
6259
+ let publishData = { publishStatus: "published", hasDraft: false };
5537
6260
  let finalContent = originalDoc;
5538
- if (originalDoc._has_draft) {
6261
+ if (originalDoc.hasDraft) {
5539
6262
  const versions = await db.findVersions({
5540
6263
  collection: collectionSlug,
5541
6264
  documentId: slug,
@@ -5558,7 +6281,7 @@ function createHonoApp(options) {
5558
6281
  await db.createVersion({
5559
6282
  collection: collectionSlug,
5560
6283
  documentId: slug,
5561
- data: { ...finalContent, _status: "published" },
6284
+ data: { ...finalContent, publishStatus: "published" },
5562
6285
  status: "published",
5563
6286
  createdBy: ctxUser?.id,
5564
6287
  changeDescription: "Published",
@@ -5578,7 +6301,7 @@ function createHonoApp(options) {
5578
6301
  const doc = await db.update({
5579
6302
  collection: `_globals_${slug}`,
5580
6303
  id: slug,
5581
- data: { _status: "draft", _has_draft: false },
6304
+ data: { publishStatus: "draft", hasDraft: false },
5582
6305
  tenantID: ctxTenantID
5583
6306
  });
5584
6307
  return c.json({ data: doc, message: "Unpublished successfully" });
@@ -5638,7 +6361,7 @@ function createHonoApp(options) {
5638
6361
  const doc = await db.update({
5639
6362
  collection: collectionSlug,
5640
6363
  id: slug,
5641
- data: { ...version.data, _status: "draft", _has_draft: false },
6364
+ data: { ...version.data, publishStatus: "draft", hasDraft: false },
5642
6365
  tenantID: ctxTenantID
5643
6366
  });
5644
6367
  return c.json({ data: doc, message: "Restored successfully" });
@@ -5742,6 +6465,6 @@ function createRESTAPI(registry, db, options) {
5742
6465
  });
5743
6466
  }
5744
6467
 
5745
- export { AuditLogger, AuthRoutes, InMemoryAuditLogger, InMemoryRateLimiter, MediaService, createAuditContext2 as createAuditContext, createHonoApp, createLocalStorage, createRESTAPI, getAppSecret, getEncryptionKey, getSessionConfig, init_secret, loadSecrets, resolveProvider, setDbAdapter };
5746
- //# sourceMappingURL=chunk-HXRD4B37.js.map
5747
- //# sourceMappingURL=chunk-HXRD4B37.js.map
6468
+ export { AuditLogger, AuthRoutes, InMemoryAuditLogger, InMemoryRateLimiter, MediaService, createAuditContext2 as createAuditContext, createCloudinaryStorage, createFtpStorage, createHonoApp, createLocalStorage, createRESTAPI, createS3Storage, getAppSecret, getDefaultRegistry, getEncryptionKey, getSessionConfig, init_secret, loadSecrets, resolveProvider, setDbAdapter };
6469
+ //# sourceMappingURL=chunk-PU2Z5VWF.js.map
6470
+ //# sourceMappingURL=chunk-PU2Z5VWF.js.map