@kyro-cms/core 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -593
- package/dist/{WebhookService-AefJfqX0.d.cts → WebhookService-BKszZlG0.d.cts} +1 -1
- package/dist/{WebhookService-118ZTFis.d.ts → WebhookService-Ccf1j-IN.d.ts} +1 -1
- package/dist/api-handler-graphql.cjs +44 -0
- package/dist/api-handler-graphql.cjs.map +1 -0
- package/dist/api-handler-graphql.d.cts +6 -0
- package/dist/api-handler-graphql.d.ts +6 -0
- package/dist/api-handler-graphql.js +41 -0
- package/dist/api-handler-graphql.js.map +1 -0
- package/dist/api-handler-trpc.cjs +38 -0
- package/dist/api-handler-trpc.cjs.map +1 -0
- package/dist/api-handler-trpc.d.cts +5 -0
- package/dist/api-handler-trpc.d.ts +5 -0
- package/dist/api-handler-trpc.js +36 -0
- package/dist/api-handler-trpc.js.map +1 -0
- package/dist/api-handler.cjs +33 -99
- package/dist/api-handler.cjs.map +1 -1
- package/dist/api-handler.d.cts +2 -1
- package/dist/api-handler.d.ts +2 -1
- package/dist/api-handler.js +21 -97
- package/dist/api-handler.js.map +1 -1
- package/dist/{tenant-B1YB0Jy8.d.ts → base-CIuXkrH4.d.cts} +7 -15
- package/dist/{tenant-Cpeveji6.d.cts → base-fFo4lqER.d.ts} +7 -15
- package/dist/bootstrap-3PV3GJ3S.js +7 -0
- package/dist/{bootstrap-JCML6NFO.js.map → bootstrap-3PV3GJ3S.js.map} +1 -1
- package/dist/bootstrap-4CELFLJO.cjs +32 -0
- package/dist/{bootstrap-AKAUP6F6.cjs.map → bootstrap-4CELFLJO.cjs.map} +1 -1
- package/dist/{chunk-VJT6P4N6.cjs → chunk-3HR772HI.cjs} +199 -32
- package/dist/chunk-3HR772HI.cjs.map +1 -0
- package/dist/chunk-3KTWGODI.cjs +178 -0
- package/dist/chunk-3KTWGODI.cjs.map +1 -0
- package/dist/{chunk-QXIQWPAP.js → chunk-3UK5XBVJ.js} +4 -134
- package/dist/chunk-3UK5XBVJ.js.map +1 -0
- package/dist/{chunk-FXYP2HA6.js → chunk-4AO3A3JM.js} +48 -4
- package/dist/chunk-4AO3A3JM.js.map +1 -0
- package/dist/{chunk-Z6ZWNWWR.js → chunk-4CV4JOE5.js} +3 -9
- package/dist/{chunk-Z6ZWNWWR.js.map → chunk-4CV4JOE5.js.map} +1 -1
- package/dist/chunk-4M7X5HAB.cjs +173 -0
- package/dist/chunk-4M7X5HAB.cjs.map +1 -0
- package/dist/chunk-53NYVYVX.js +3243 -0
- package/dist/chunk-53NYVYVX.js.map +1 -0
- package/dist/{chunk-35U3FROB.js → chunk-5H3MWQJS.js} +714 -184
- package/dist/chunk-5H3MWQJS.js.map +1 -0
- package/dist/{chunk-YVUJBEXE.cjs → chunk-5PMQQFRE.cjs} +16 -7
- package/dist/chunk-5PMQQFRE.cjs.map +1 -0
- package/dist/{chunk-57P6MJKC.js → chunk-6UNONDW7.js} +94 -10
- package/dist/chunk-6UNONDW7.js.map +1 -0
- package/dist/{chunk-Y3N7UUDO.js → chunk-7OGPN7MP.js} +5 -2
- package/dist/chunk-7OGPN7MP.js.map +1 -0
- package/dist/{chunk-2OL4O2TH.cjs → chunk-7OS7TX2Q.cjs} +68 -62
- package/dist/chunk-7OS7TX2Q.cjs.map +1 -0
- package/dist/{chunk-3TPQ2BU6.js → chunk-BYBMTIMT.js} +2 -6
- package/dist/chunk-BYBMTIMT.js.map +1 -0
- package/dist/{chunk-ES5HNFFT.js → chunk-CF7OL6HR.js} +4 -2
- package/dist/chunk-CF7OL6HR.js.map +1 -0
- package/dist/chunk-CJONKRHJ.js +162 -0
- package/dist/chunk-CJONKRHJ.js.map +1 -0
- package/dist/{chunk-OHVB4AJ7.js → chunk-CJX74IYK.js} +24 -18
- package/dist/chunk-CJX74IYK.js.map +1 -0
- package/dist/{chunk-5KVM3WEY.cjs → chunk-CNKT4PME.cjs} +1592 -868
- package/dist/chunk-CNKT4PME.cjs.map +1 -0
- package/dist/{chunk-G7VZBCD6.cjs → chunk-CZLDE2OZ.cjs} +2 -9
- package/dist/{chunk-G7VZBCD6.cjs.map → chunk-CZLDE2OZ.cjs.map} +1 -1
- package/dist/{chunk-WQBRWOQT.cjs → chunk-DPA3KWPY.cjs} +4 -3
- package/dist/chunk-DPA3KWPY.cjs.map +1 -0
- package/dist/{chunk-LINKCEG4.cjs → chunk-E2763JUP.cjs} +726 -196
- package/dist/chunk-E2763JUP.cjs.map +1 -0
- package/dist/chunk-E5UJBLQ7.js +220 -0
- package/dist/chunk-E5UJBLQ7.js.map +1 -0
- package/dist/{chunk-DVD5P72E.cjs → chunk-EEJUFDMF.cjs} +2 -6
- package/dist/chunk-EEJUFDMF.cjs.map +1 -0
- package/dist/chunk-FSKONGCX.cjs +253 -0
- package/dist/chunk-FSKONGCX.cjs.map +1 -0
- package/dist/{chunk-Y3QQN7PN.js → chunk-GAAHG2Z4.js} +13 -4
- package/dist/chunk-GAAHG2Z4.js.map +1 -0
- package/dist/chunk-GAOXD3XT.js +175 -0
- package/dist/chunk-GAOXD3XT.js.map +1 -0
- package/dist/{chunk-SA7NSSIQ.cjs → chunk-GUUB5EAG.cjs} +13 -187
- package/dist/chunk-GUUB5EAG.cjs.map +1 -0
- package/dist/{chunk-4DA7QPLA.cjs → chunk-GXFOGU7N.cjs} +5 -2
- package/dist/chunk-GXFOGU7N.cjs.map +1 -0
- package/dist/{chunk-I7HHI6QV.cjs → chunk-IDVRRRAK.cjs} +17 -9
- package/dist/chunk-IDVRRRAK.cjs.map +1 -0
- package/dist/{chunk-HXRD4B37.js → chunk-IPTZM3VE.js} +1423 -704
- package/dist/chunk-IPTZM3VE.js.map +1 -0
- package/dist/chunk-KC2GDBLS.cjs +84 -0
- package/dist/chunk-KC2GDBLS.cjs.map +1 -0
- package/dist/{chunk-QUW2RZTM.cjs → chunk-L46ROHUS.cjs} +51 -7
- package/dist/chunk-L46ROHUS.cjs.map +1 -0
- package/dist/chunk-L4EZKIEX.js +185 -0
- package/dist/chunk-L4EZKIEX.js.map +1 -0
- package/dist/{chunk-REK7AYOC.js → chunk-L5UKKZQN.js} +199 -32
- package/dist/chunk-L5UKKZQN.js.map +1 -0
- package/dist/chunk-NKPKR5BW.cjs +188 -0
- package/dist/chunk-NKPKR5BW.cjs.map +1 -0
- package/dist/chunk-NWUEVLQT.cjs +99 -0
- package/dist/chunk-NWUEVLQT.cjs.map +1 -0
- package/dist/{chunk-3AJE4SEG.js → chunk-OHC6UHFY.js} +208 -76
- package/dist/chunk-OHC6UHFY.js.map +1 -0
- package/dist/chunk-PHJRNPHY.cjs +3291 -0
- package/dist/chunk-PHJRNPHY.cjs.map +1 -0
- package/dist/{chunk-DXHRBMGB.js → chunk-PQ72Z6WC.js} +67 -112
- package/dist/chunk-PQ72Z6WC.js.map +1 -0
- package/dist/{chunk-K7JPTH3G.cjs → chunk-PV2I2KMI.cjs} +214 -82
- package/dist/chunk-PV2I2KMI.cjs.map +1 -0
- package/dist/{chunk-PDYFVNUX.cjs → chunk-Q23GAMLE.cjs} +71 -116
- package/dist/chunk-Q23GAMLE.cjs.map +1 -0
- package/dist/{chunk-H727JIG7.js → chunk-Q72BOAPK.js} +16 -8
- package/dist/chunk-Q72BOAPK.js.map +1 -0
- package/dist/{chunk-IBG6V56E.cjs → chunk-QFLB4EIJ.cjs} +2 -139
- package/dist/chunk-QFLB4EIJ.cjs.map +1 -0
- package/dist/{chunk-2KVHZE6O.cjs → chunk-RFFSZSCL.cjs} +282 -190
- package/dist/chunk-RFFSZSCL.cjs.map +1 -0
- package/dist/{chunk-V3LKPM3O.cjs → chunk-SHTTJMLT.cjs} +4 -2
- package/dist/chunk-SHTTJMLT.cjs.map +1 -0
- package/dist/{chunk-WOWUL7ZY.js → chunk-UUDTPZX6.js} +5 -4
- package/dist/chunk-UUDTPZX6.js.map +1 -0
- package/dist/{chunk-QPPDLRNR.js → chunk-V7KZQIZ6.js} +277 -185
- package/dist/chunk-V7KZQIZ6.js.map +1 -0
- package/dist/{chunk-3ZFYL34R.js → chunk-WXVB364T.js} +12 -185
- package/dist/chunk-WXVB364T.js.map +1 -0
- package/dist/chunk-XEB7PH2E.js +81 -0
- package/dist/chunk-XEB7PH2E.js.map +1 -0
- package/dist/{chunk-IA6AU5PI.cjs → chunk-Y7AQK4R4.cjs} +94 -10
- package/dist/chunk-Y7AQK4R4.cjs.map +1 -0
- package/dist/chunk-YFAVQQTU.js +92 -0
- package/dist/chunk-YFAVQQTU.js.map +1 -0
- package/dist/cli/index.cjs +6 -6
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +6 -6
- package/dist/cli/index.js.map +1 -1
- package/dist/client.cjs +4 -4
- package/dist/client.d.cts +3 -3
- package/dist/client.d.ts +3 -3
- package/dist/client.js +2 -2
- package/dist/drizzle/index.cjs +15 -14
- package/dist/drizzle/index.d.cts +10 -14
- package/dist/drizzle/index.d.ts +10 -14
- package/dist/drizzle/index.js +6 -5
- package/dist/fields/index.cjs +22 -38
- package/dist/fields/index.d.cts +2 -22
- package/dist/fields/index.d.ts +2 -22
- package/dist/fields/index.js +2 -2
- package/dist/graphql/index.cjs +6 -5
- package/dist/graphql/index.d.cts +5 -3
- package/dist/graphql/index.d.ts +5 -3
- package/dist/graphql/index.js +4 -3
- package/dist/index-BKta3cBH.d.cts +277 -0
- package/dist/index-ClOqnkTO.d.ts +277 -0
- package/dist/index.cjs +310 -168
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +130 -211
- package/dist/index.d.ts +130 -211
- package/dist/index.js +174 -35
- package/dist/index.js.map +1 -1
- package/dist/integration.cjs +3 -3
- package/dist/integration.js +2 -2
- package/dist/media-7WDX4BDJ.js +4 -0
- package/dist/{media-GPPTZ43E.js.map → media-7WDX4BDJ.js.map} +1 -1
- package/dist/{media-XNTUFJZR.cjs → media-TUSLVRQ6.cjs} +3 -3
- package/dist/{media-XNTUFJZR.cjs.map → media-TUSLVRQ6.cjs.map} +1 -1
- package/dist/mongo-auth-adapter-GT4S7SCU.cjs +17 -0
- package/dist/{mongo-auth-adapter-NHHUJHVH.cjs.map → mongo-auth-adapter-GT4S7SCU.cjs.map} +1 -1
- package/dist/mongo-auth-adapter-M7VV4LNB.js +4 -0
- package/dist/{mongo-auth-adapter-NJQUUCTP.js.map → mongo-auth-adapter-M7VV4LNB.js.map} +1 -1
- package/dist/mongodb/index.cjs +9 -8
- package/dist/mongodb/index.d.cts +6 -13
- package/dist/mongodb/index.d.ts +6 -13
- package/dist/mongodb/index.js +5 -4
- package/dist/postgres-auth-adapter-AFAPISH7.js +5 -0
- package/dist/{postgres-auth-adapter-3T2NKTSE.js.map → postgres-auth-adapter-AFAPISH7.js.map} +1 -1
- package/dist/postgres-auth-adapter-SFDTLONT.cjs +14 -0
- package/dist/{postgres-auth-adapter-7IEENCKQ.cjs.map → postgres-auth-adapter-SFDTLONT.cjs.map} +1 -1
- package/dist/redis-adapter-UQX4EE3B.cjs +13 -0
- package/dist/{redis-adapter-D2E2S3GB.cjs.map → redis-adapter-UQX4EE3B.cjs.map} +1 -1
- package/dist/redis-adapter-XALOGWY3.js +4 -0
- package/dist/{redis-adapter-VQXD7ESY.js.map → redis-adapter-XALOGWY3.js.map} +1 -1
- package/dist/rest/index.cjs +16 -15
- package/dist/rest/index.d.cts +4 -4
- package/dist/rest/index.d.ts +4 -4
- package/dist/rest/index.js +14 -13
- package/dist/{schema-37SE2F4B.cjs → schema-6QL3USNB.cjs} +15 -15
- package/dist/{schema-37SE2F4B.cjs.map → schema-6QL3USNB.cjs.map} +1 -1
- package/dist/{schema-5PHL5IVB.js → schema-FNNWEAAW.js} +4 -4
- package/dist/{schema-5PHL5IVB.js.map → schema-FNNWEAAW.js.map} +1 -1
- package/dist/sqlite-adapter-AQB5TCGV.cjs +13 -0
- package/dist/{sqlite-adapter-LVK5PS4T.cjs.map → sqlite-adapter-AQB5TCGV.cjs.map} +1 -1
- package/dist/sqlite-adapter-N5H6IM2X.js +4 -0
- package/dist/{sqlite-adapter-TR3U3W6Q.js.map → sqlite-adapter-N5H6IM2X.js.map} +1 -1
- package/dist/templates/index.cjs +134 -32
- package/dist/templates/index.d.cts +52 -9
- package/dist/templates/index.d.ts +52 -9
- package/dist/templates/index.js +4 -2
- package/dist/trpc/index.cjs +14 -13
- package/dist/trpc/index.d.cts +55 -49
- package/dist/trpc/index.d.ts +55 -49
- package/dist/trpc/index.js +5 -4
- package/dist/{types-D6ZLRGbH.d.cts → types-CpjuXbe7.d.cts} +2 -0
- package/dist/{types-D6ZLRGbH.d.ts → types-CpjuXbe7.d.ts} +2 -0
- package/dist/{types-VtjUxIMp.d.cts → types-DeSApf9T.d.cts} +36 -14
- package/dist/{types-VtjUxIMp.d.ts → types-DeSApf9T.d.ts} +36 -14
- package/dist/{types-J3R9nVsZ.d.cts → types-Dgzlftb7.d.ts} +32 -28
- package/dist/{types-Bs1up4yP.d.ts → types-Ds0tCA3L.d.cts} +32 -28
- package/dist/ws/index.cjs +6 -6
- package/dist/ws/index.js +2 -2
- package/package.json +22 -4
- package/dist/bootstrap-AKAUP6F6.cjs +0 -32
- package/dist/bootstrap-JCML6NFO.js +0 -7
- package/dist/chunk-2KVHZE6O.cjs.map +0 -1
- package/dist/chunk-2OL4O2TH.cjs.map +0 -1
- package/dist/chunk-35U3FROB.js.map +0 -1
- package/dist/chunk-3AJE4SEG.js.map +0 -1
- package/dist/chunk-3J4MFTI3.js +0 -3872
- package/dist/chunk-3J4MFTI3.js.map +0 -1
- package/dist/chunk-3TPQ2BU6.js.map +0 -1
- package/dist/chunk-3ZFYL34R.js.map +0 -1
- package/dist/chunk-4DA7QPLA.cjs.map +0 -1
- package/dist/chunk-57P6MJKC.js.map +0 -1
- package/dist/chunk-5KVM3WEY.cjs.map +0 -1
- package/dist/chunk-6IMPH6WV.cjs +0 -3897
- package/dist/chunk-6IMPH6WV.cjs.map +0 -1
- package/dist/chunk-ATBOUGQP.cjs +0 -513
- package/dist/chunk-ATBOUGQP.cjs.map +0 -1
- package/dist/chunk-DVD5P72E.cjs.map +0 -1
- package/dist/chunk-DXHRBMGB.js.map +0 -1
- package/dist/chunk-ES5HNFFT.js.map +0 -1
- package/dist/chunk-FXYP2HA6.js.map +0 -1
- package/dist/chunk-H727JIG7.js.map +0 -1
- package/dist/chunk-HXRD4B37.js.map +0 -1
- package/dist/chunk-I7HHI6QV.cjs.map +0 -1
- package/dist/chunk-IA6AU5PI.cjs.map +0 -1
- package/dist/chunk-IBG6V56E.cjs.map +0 -1
- package/dist/chunk-K7JPTH3G.cjs.map +0 -1
- package/dist/chunk-LINKCEG4.cjs.map +0 -1
- package/dist/chunk-OHVB4AJ7.js.map +0 -1
- package/dist/chunk-PDYFVNUX.cjs.map +0 -1
- package/dist/chunk-Q23JB3KL.js +0 -488
- package/dist/chunk-Q23JB3KL.js.map +0 -1
- package/dist/chunk-QPPDLRNR.js.map +0 -1
- package/dist/chunk-QUW2RZTM.cjs.map +0 -1
- package/dist/chunk-QXIQWPAP.js.map +0 -1
- package/dist/chunk-R3XIBBAW.cjs +0 -34
- package/dist/chunk-R3XIBBAW.cjs.map +0 -1
- package/dist/chunk-REK7AYOC.js.map +0 -1
- package/dist/chunk-SA7NSSIQ.cjs.map +0 -1
- package/dist/chunk-SDMNUYVU.js +0 -30
- package/dist/chunk-SDMNUYVU.js.map +0 -1
- package/dist/chunk-V3LKPM3O.cjs.map +0 -1
- package/dist/chunk-VJT6P4N6.cjs.map +0 -1
- package/dist/chunk-WOWUL7ZY.js.map +0 -1
- package/dist/chunk-WQBRWOQT.cjs.map +0 -1
- package/dist/chunk-Y3N7UUDO.js.map +0 -1
- package/dist/chunk-Y3QQN7PN.js.map +0 -1
- package/dist/chunk-YVUJBEXE.cjs.map +0 -1
- package/dist/index-CLp-DRKA.d.ts +0 -64
- package/dist/index-DfO7G4kN.d.cts +0 -64
- package/dist/media-GPPTZ43E.js +0 -4
- package/dist/mongo-auth-adapter-NHHUJHVH.cjs +0 -17
- package/dist/mongo-auth-adapter-NJQUUCTP.js +0 -4
- package/dist/postgres-auth-adapter-3T2NKTSE.js +0 -5
- package/dist/postgres-auth-adapter-7IEENCKQ.cjs +0 -14
- package/dist/redis-adapter-D2E2S3GB.cjs +0 -13
- package/dist/redis-adapter-VQXD7ESY.js +0 -4
- package/dist/sqlite-adapter-LVK5PS4T.cjs +0 -13
- package/dist/sqlite-adapter-TR3U3W6Q.js +0 -4
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
3
|
+
var chunkY7AQK4R4_cjs = require('./chunk-Y7AQK4R4.cjs');
|
|
4
|
+
var chunkKC2GDBLS_cjs = require('./chunk-KC2GDBLS.cjs');
|
|
5
|
+
var chunkIDVRRRAK_cjs = require('./chunk-IDVRRRAK.cjs');
|
|
5
6
|
var chunkADLJSJSN_cjs = require('./chunk-ADLJSJSN.cjs');
|
|
6
|
-
var
|
|
7
|
-
var
|
|
8
|
-
var
|
|
9
|
-
var
|
|
10
|
-
var
|
|
11
|
-
var
|
|
12
|
-
var
|
|
13
|
-
var
|
|
14
|
-
var chunkG7VZBCD6_cjs = require('./chunk-G7VZBCD6.cjs');
|
|
7
|
+
var chunkQFLB4EIJ_cjs = require('./chunk-QFLB4EIJ.cjs');
|
|
8
|
+
var chunk4M7X5HAB_cjs = require('./chunk-4M7X5HAB.cjs');
|
|
9
|
+
var chunkRFFSZSCL_cjs = require('./chunk-RFFSZSCL.cjs');
|
|
10
|
+
var chunk7OS7TX2Q_cjs = require('./chunk-7OS7TX2Q.cjs');
|
|
11
|
+
var chunkQ23GAMLE_cjs = require('./chunk-Q23GAMLE.cjs');
|
|
12
|
+
var chunkGXFOGU7N_cjs = require('./chunk-GXFOGU7N.cjs');
|
|
13
|
+
var chunkNKPKR5BW_cjs = require('./chunk-NKPKR5BW.cjs');
|
|
14
|
+
var chunkCZLDE2OZ_cjs = require('./chunk-CZLDE2OZ.cjs');
|
|
15
15
|
var crypto = require('crypto');
|
|
16
16
|
var unstorage = require('unstorage');
|
|
17
17
|
var fsDriver = require('unstorage/drivers/fs');
|
|
@@ -19,11 +19,13 @@ var hono = require('hono');
|
|
|
19
19
|
var path = require('path');
|
|
20
20
|
var fs = require('fs');
|
|
21
21
|
var sharp = require('sharp');
|
|
22
|
-
var graphql = require('graphql');
|
|
23
22
|
var promises = require('fs/promises');
|
|
23
|
+
var process2 = require('process');
|
|
24
24
|
var clientS3 = require('@aws-sdk/client-s3');
|
|
25
|
+
var nodeHttpHandler = require('@smithy/node-http-handler');
|
|
25
26
|
var stream = require('stream');
|
|
26
27
|
var basicFtp = require('basic-ftp');
|
|
28
|
+
var zodToJsonSchema = require('zod-to-json-schema');
|
|
27
29
|
|
|
28
30
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
29
31
|
|
|
@@ -31,6 +33,7 @@ var crypto__default = /*#__PURE__*/_interopDefault(crypto);
|
|
|
31
33
|
var fsDriver__default = /*#__PURE__*/_interopDefault(fsDriver);
|
|
32
34
|
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
33
35
|
var sharp__default = /*#__PURE__*/_interopDefault(sharp);
|
|
36
|
+
var process2__default = /*#__PURE__*/_interopDefault(process2);
|
|
34
37
|
|
|
35
38
|
function setDbAdapter(adapter) {
|
|
36
39
|
dbAdapter = adapter;
|
|
@@ -39,8 +42,8 @@ async function loadSecrets() {
|
|
|
39
42
|
if (dbAdapter) {
|
|
40
43
|
try {
|
|
41
44
|
const result = await dbAdapter.findOne({
|
|
42
|
-
collection: "
|
|
43
|
-
where: {
|
|
45
|
+
collection: "_globals_system",
|
|
46
|
+
where: {}
|
|
44
47
|
});
|
|
45
48
|
if (result) {
|
|
46
49
|
cachedSecrets = {
|
|
@@ -89,7 +92,7 @@ function getSessionConfig() {
|
|
|
89
92
|
};
|
|
90
93
|
}
|
|
91
94
|
var dbAdapter, cachedSecrets;
|
|
92
|
-
var init_secret =
|
|
95
|
+
var init_secret = chunkCZLDE2OZ_cjs.__esm({
|
|
93
96
|
"src/lib/secret.ts"() {
|
|
94
97
|
dbAdapter = null;
|
|
95
98
|
cachedSecrets = null;
|
|
@@ -98,7 +101,7 @@ var init_secret = chunkG7VZBCD6_cjs.__esm({
|
|
|
98
101
|
|
|
99
102
|
// src/api/rest/auth-session.ts
|
|
100
103
|
var auth_session_exports = {};
|
|
101
|
-
|
|
104
|
+
chunkCZLDE2OZ_cjs.__export(auth_session_exports, {
|
|
102
105
|
SESSION_CONFIG: () => SESSION_CONFIG,
|
|
103
106
|
clearSessionCookie: () => clearSessionCookie,
|
|
104
107
|
createSession: () => createSession,
|
|
@@ -364,7 +367,7 @@ async function getCurrentUser(request) {
|
|
|
364
367
|
return session;
|
|
365
368
|
}
|
|
366
369
|
var sessionConfig, SESSION_CONFIG, SESSION_COOKIE_NAME, storage;
|
|
367
|
-
var init_auth_session =
|
|
370
|
+
var init_auth_session = chunkCZLDE2OZ_cjs.__esm({
|
|
368
371
|
"src/api/rest/auth-session.ts"() {
|
|
369
372
|
init_secret();
|
|
370
373
|
sessionConfig = getSessionConfig();
|
|
@@ -388,14 +391,14 @@ function createAuthMiddleware(config) {
|
|
|
388
391
|
userLookup
|
|
389
392
|
} = config;
|
|
390
393
|
return async function authMiddleware(req) {
|
|
391
|
-
const apiKeyRaw =
|
|
394
|
+
const apiKeyRaw = chunk4M7X5HAB_cjs.extractApiKeyFromRequest(req);
|
|
392
395
|
if (apiKeyRaw && db) {
|
|
393
|
-
const result = await
|
|
396
|
+
const result = await chunk4M7X5HAB_cjs.validateApiKey(apiKeyRaw, db, userLookup);
|
|
394
397
|
if (result.valid && result.user) {
|
|
395
398
|
return {
|
|
396
399
|
user: result.user,
|
|
397
400
|
tenantContext: createTenantContextFromUser(result.user),
|
|
398
|
-
apiKeyContext:
|
|
401
|
+
apiKeyContext: chunk4M7X5HAB_cjs.createApiKeyContext(result),
|
|
399
402
|
status: 200,
|
|
400
403
|
authType: "apikey"
|
|
401
404
|
};
|
|
@@ -450,6 +453,9 @@ function createTenantContextFromUser(user) {
|
|
|
450
453
|
};
|
|
451
454
|
}
|
|
452
455
|
|
|
456
|
+
// src/api/rest/hono-app.ts
|
|
457
|
+
init_secret();
|
|
458
|
+
|
|
453
459
|
// src/auth/security/in-memory-rate-limit.ts
|
|
454
460
|
var InMemoryRateLimiter = class {
|
|
455
461
|
storage = /* @__PURE__ */ new Map();
|
|
@@ -782,7 +788,7 @@ var AuditLogger = class {
|
|
|
782
788
|
serializeLog(log) {
|
|
783
789
|
const result = {
|
|
784
790
|
id: log.id,
|
|
785
|
-
timestamp: log.timestamp.toISOString(),
|
|
791
|
+
timestamp: new Date(log.timestamp).toISOString(),
|
|
786
792
|
action: log.action,
|
|
787
793
|
resource: log.resource,
|
|
788
794
|
success: log.success ? "1" : "0"
|
|
@@ -957,7 +963,7 @@ var AuthRoutes = class {
|
|
|
957
963
|
constructor(config) {
|
|
958
964
|
this.authAdapter = config.redis;
|
|
959
965
|
this.email = config.email;
|
|
960
|
-
this.passwordPolicy = config.passwordPolicy || new
|
|
966
|
+
this.passwordPolicy = config.passwordPolicy || new chunkY7AQK4R4_cjs.PasswordPolicy();
|
|
961
967
|
this.lockout = config.lockout;
|
|
962
968
|
this.rateLimiter = config.rateLimiter;
|
|
963
969
|
this.auditLogger = config.auditLogger;
|
|
@@ -1177,23 +1183,19 @@ var AuthRoutes = class {
|
|
|
1177
1183
|
}
|
|
1178
1184
|
}
|
|
1179
1185
|
async me(req) {
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
return this.errorResponse("Not authenticated", 401);
|
|
1184
|
-
}
|
|
1185
|
-
const user = await this.authAdapter.findUserById(session.userId);
|
|
1186
|
-
if (!user) {
|
|
1187
|
-
return this.errorResponse("User not found", 404);
|
|
1188
|
-
}
|
|
1189
|
-
return this.jsonResponse({
|
|
1190
|
-
success: true,
|
|
1191
|
-
user: this.sanitizeUser(user)
|
|
1192
|
-
});
|
|
1193
|
-
} catch (error) {
|
|
1194
|
-
console.error("[AuthRoutes.me] Authentication failed:", error.message);
|
|
1195
|
-
return this.errorResponse("Authentication failed", 401);
|
|
1186
|
+
const session = await getCurrentUser(req);
|
|
1187
|
+
if (!session) {
|
|
1188
|
+
return this.errorResponse("Not authenticated", 401);
|
|
1196
1189
|
}
|
|
1190
|
+
return this.jsonResponse({
|
|
1191
|
+
success: true,
|
|
1192
|
+
user: {
|
|
1193
|
+
id: session.userId,
|
|
1194
|
+
email: session.email,
|
|
1195
|
+
role: session.role,
|
|
1196
|
+
tenantId: session.tenantId
|
|
1197
|
+
}
|
|
1198
|
+
});
|
|
1197
1199
|
}
|
|
1198
1200
|
async changePassword(req) {
|
|
1199
1201
|
const session = await getCurrentUser(req);
|
|
@@ -1303,7 +1305,7 @@ var AuthRoutes = class {
|
|
|
1303
1305
|
}
|
|
1304
1306
|
if (this.auditLogger) {
|
|
1305
1307
|
await this.auditLogger.log({
|
|
1306
|
-
action: "
|
|
1308
|
+
action: "password_reset",
|
|
1307
1309
|
userId: user.id,
|
|
1308
1310
|
userEmail: user.email,
|
|
1309
1311
|
resource: "auth",
|
|
@@ -1452,7 +1454,7 @@ var AuthRoutes = class {
|
|
|
1452
1454
|
return this.errorResponse("No session", 401);
|
|
1453
1455
|
}
|
|
1454
1456
|
try {
|
|
1455
|
-
const updatedSession = await (init_auth_session(),
|
|
1457
|
+
const updatedSession = await (init_auth_session(), chunkCZLDE2OZ_cjs.__toCommonJS(auth_session_exports)).refreshSession(sessionId, req);
|
|
1456
1458
|
if (!updatedSession) {
|
|
1457
1459
|
return this.errorResponse("Session not found", 404);
|
|
1458
1460
|
}
|
|
@@ -1491,7 +1493,7 @@ var AuthRoutes = class {
|
|
|
1491
1493
|
}
|
|
1492
1494
|
};
|
|
1493
1495
|
function createLocalStorage(config) {
|
|
1494
|
-
const { uploadDir, baseUrl = "/uploads" } = config;
|
|
1496
|
+
const { uploadDir = path.join(process2__default.default.cwd(), "public", "uploads"), baseUrl = "/uploads" } = config;
|
|
1495
1497
|
async function ensureDir(dir) {
|
|
1496
1498
|
if (!fs.existsSync(dir)) {
|
|
1497
1499
|
await promises.mkdir(dir, { recursive: true });
|
|
@@ -1679,150 +1681,166 @@ function createLocalStorage(config) {
|
|
|
1679
1681
|
}
|
|
1680
1682
|
};
|
|
1681
1683
|
}
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
const
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
const
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1684
|
+
|
|
1685
|
+
// src/storage/imgix.ts
|
|
1686
|
+
function createImgixStorage(config) {
|
|
1687
|
+
const signUrl = (path3, params) => {
|
|
1688
|
+
if (!config.signKey) return path3;
|
|
1689
|
+
const signer = new TextEncoder();
|
|
1690
|
+
const key = signer.encode(config.signKey);
|
|
1691
|
+
const data = signer.encode(path3 + params.toString());
|
|
1692
|
+
let hash = 0;
|
|
1693
|
+
const combined = new Uint8Array(key.length + data.length);
|
|
1694
|
+
combined.set(key);
|
|
1695
|
+
combined.set(data, key.length);
|
|
1696
|
+
for (let i = 0; i < combined.length; i++) {
|
|
1697
|
+
hash = (hash << 5) - hash + combined[i] | 0;
|
|
1698
|
+
}
|
|
1699
|
+
params.set("s", Math.abs(hash).toString(16));
|
|
1700
|
+
return path3;
|
|
1701
|
+
};
|
|
1702
|
+
return {
|
|
1703
|
+
name: "imgix",
|
|
1704
|
+
displayName: "Imgix",
|
|
1705
|
+
supportsDynamicResize: true,
|
|
1706
|
+
async upload(_file, _options) {
|
|
1707
|
+
throw new Error(
|
|
1708
|
+
"Imgix is a transformation service. Use another provider for uploads."
|
|
1709
|
+
);
|
|
1710
|
+
},
|
|
1711
|
+
async uploadFromUrl(url, options) {
|
|
1712
|
+
const filename = options?.filename || url.split("/").pop() || "file";
|
|
1713
|
+
const response = await fetch(url);
|
|
1714
|
+
if (!response.ok) {
|
|
1715
|
+
throw new Error(`Failed to fetch: ${response.statusText}`);
|
|
1699
1716
|
}
|
|
1700
|
-
|
|
1717
|
+
const blob = await response.blob();
|
|
1718
|
+
new File([blob], filename, { type: blob.type });
|
|
1719
|
+
return {
|
|
1720
|
+
id: Buffer.from(url).toString("base64").slice(0, 20),
|
|
1721
|
+
filename,
|
|
1722
|
+
originalName: filename,
|
|
1723
|
+
mimeType: blob.type,
|
|
1724
|
+
size: blob.size,
|
|
1725
|
+
url: this.getImageUrl(url),
|
|
1726
|
+
thumbnailUrl: this.getImageUrl(url, {
|
|
1727
|
+
width: 200,
|
|
1728
|
+
height: 200,
|
|
1729
|
+
fit: "crop"
|
|
1730
|
+
}),
|
|
1731
|
+
provider: "imgix",
|
|
1732
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1733
|
+
};
|
|
1734
|
+
},
|
|
1735
|
+
async delete(_url) {
|
|
1736
|
+
},
|
|
1737
|
+
async rename(_oldUrl, newKey) {
|
|
1738
|
+
return `https://${config.domain}/${newKey}`;
|
|
1739
|
+
},
|
|
1740
|
+
getImageUrl(url, transforms) {
|
|
1741
|
+
const parsed = new URL(url);
|
|
1742
|
+
const params = new URLSearchParams(parsed.search);
|
|
1743
|
+
if (config.defaultParameters) {
|
|
1744
|
+
Object.entries(config.defaultParameters).forEach(([key, value]) => {
|
|
1745
|
+
if (!params.has(key)) {
|
|
1746
|
+
params.set(key, value);
|
|
1747
|
+
}
|
|
1748
|
+
});
|
|
1749
|
+
}
|
|
1750
|
+
if (transforms) {
|
|
1751
|
+
if (transforms.width) params.set("w", String(transforms.width));
|
|
1752
|
+
if (transforms.height) params.set("h", String(transforms.height));
|
|
1753
|
+
if (transforms.quality) params.set("q", String(transforms.quality));
|
|
1754
|
+
if (transforms.format) params.set("fm", transforms.format);
|
|
1755
|
+
if (transforms.fit) params.set("fit", transforms.fit);
|
|
1756
|
+
if (transforms.blur) params.set("blur", String(transforms.blur));
|
|
1757
|
+
if (transforms.sharpen) params.set("sharp", String(transforms.sharpen));
|
|
1758
|
+
}
|
|
1759
|
+
params.set("auto", "compress,format");
|
|
1760
|
+
parsed.pathname + "?" + params.toString();
|
|
1761
|
+
if (config.signKey) {
|
|
1762
|
+
signUrl(parsed.pathname + params.toString(), params);
|
|
1763
|
+
}
|
|
1764
|
+
return `https://${config.domain}${parsed.pathname}${params.toString() ? "?" + params.toString() : ""}`;
|
|
1765
|
+
},
|
|
1766
|
+
async generateThumbnail(file) {
|
|
1767
|
+
return this.getImageUrl(file.url, {
|
|
1768
|
+
width: 200,
|
|
1769
|
+
height: 200,
|
|
1770
|
+
fit: "crop"
|
|
1771
|
+
});
|
|
1772
|
+
},
|
|
1773
|
+
async list() {
|
|
1774
|
+
return [];
|
|
1775
|
+
},
|
|
1776
|
+
async exists(url) {
|
|
1777
|
+
try {
|
|
1778
|
+
const response = await fetch(url, { method: "HEAD" });
|
|
1779
|
+
return response.ok;
|
|
1780
|
+
} catch {
|
|
1781
|
+
return false;
|
|
1782
|
+
}
|
|
1783
|
+
},
|
|
1784
|
+
async createFolder() {
|
|
1785
|
+
},
|
|
1786
|
+
async deleteFolder() {
|
|
1701
1787
|
}
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
return `https://${config.bucket}.s3.wasabisys.com/${normalizedKey}`;
|
|
1710
|
-
case "aws":
|
|
1711
|
-
default:
|
|
1712
|
-
return `https://${config.bucket}.s3.${config.region}.amazonaws.com/${normalizedKey}`;
|
|
1788
|
+
};
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
// src/storage/bunny.ts
|
|
1792
|
+
function getUrl(key, config) {
|
|
1793
|
+
if (config.cdnUrl) {
|
|
1794
|
+
return `${config.cdnUrl.replace(/\/$/, "")}/${key}`;
|
|
1713
1795
|
}
|
|
1796
|
+
return `https://${config.storageZone}.b-cdn.net/${key}`;
|
|
1714
1797
|
}
|
|
1715
1798
|
function getUrlPrefix(config) {
|
|
1716
1799
|
if (config.cdnUrl) {
|
|
1717
1800
|
return config.cdnUrl.replace(/\/$/, "") + "/";
|
|
1718
1801
|
}
|
|
1719
|
-
|
|
1720
|
-
case "r2": {
|
|
1721
|
-
const pubId = extractPublicDevUrlId(config.publicDevUrl);
|
|
1722
|
-
if (pubId) {
|
|
1723
|
-
return `https://${pubId}.r2.dev/`;
|
|
1724
|
-
}
|
|
1725
|
-
return `https://${config.bucket}.${config.accountId}.r2.cloudflarestorage.com/`;
|
|
1726
|
-
}
|
|
1727
|
-
case "gcs":
|
|
1728
|
-
return `https://storage.googleapis.com/${config.bucket}/`;
|
|
1729
|
-
case "digitalocean":
|
|
1730
|
-
return `https://${config.bucket}.${config.region}.cdn.digitaloceanspaces.com/`;
|
|
1731
|
-
case "backblaze":
|
|
1732
|
-
return `https://${config.bucket}.s3.backblazeb2.com/`;
|
|
1733
|
-
case "wasabi":
|
|
1734
|
-
return `https://${config.bucket}.s3.wasabisys.com/`;
|
|
1735
|
-
case "aws":
|
|
1736
|
-
default:
|
|
1737
|
-
return `https://${config.bucket}.s3.${config.region}.amazonaws.com/`;
|
|
1738
|
-
}
|
|
1739
|
-
}
|
|
1740
|
-
function getDisplayName(provider) {
|
|
1741
|
-
switch (provider) {
|
|
1742
|
-
case "r2":
|
|
1743
|
-
return "Cloudflare R2";
|
|
1744
|
-
case "gcs":
|
|
1745
|
-
return "Google Cloud Storage";
|
|
1746
|
-
case "digitalocean":
|
|
1747
|
-
return "DigitalOcean Spaces";
|
|
1748
|
-
case "backblaze":
|
|
1749
|
-
return "Backblaze B2";
|
|
1750
|
-
case "wasabi":
|
|
1751
|
-
return "Wasabi";
|
|
1752
|
-
case "aws":
|
|
1753
|
-
default:
|
|
1754
|
-
return "AWS S3";
|
|
1755
|
-
}
|
|
1802
|
+
return `https://${config.storageZone}.b-cdn.net/`;
|
|
1756
1803
|
}
|
|
1757
|
-
function
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
accessKeyId: config.accessKeyId ? "SET" : "UNDEFINED",
|
|
1761
|
-
secretAccessKey: config.secretAccessKey ? "SET" : "UNDEFINED",
|
|
1762
|
-
bucket: config.bucket,
|
|
1763
|
-
accountId: config.accountId,
|
|
1764
|
-
endpoint: config.endpoint
|
|
1765
|
-
});
|
|
1766
|
-
const client = new clientS3.S3Client({
|
|
1767
|
-
region: config.region || "auto",
|
|
1768
|
-
endpoint: config.endpoint,
|
|
1769
|
-
credentials: {
|
|
1770
|
-
accessKeyId: config.accessKeyId,
|
|
1771
|
-
secretAccessKey: config.secretAccessKey
|
|
1772
|
-
},
|
|
1773
|
-
forcePathStyle: true,
|
|
1774
|
-
tls: true,
|
|
1775
|
-
// R2 requires specific SSL configuration
|
|
1776
|
-
...config.provider === "r2" && {
|
|
1777
|
-
requestHandler: new (chunkG7VZBCD6_cjs.__require("@smithy/node-http-handler")).NodeHttpHandler({
|
|
1778
|
-
connectionTimeout: 1e4,
|
|
1779
|
-
socketTimeout: 1e4
|
|
1780
|
-
})
|
|
1781
|
-
}
|
|
1782
|
-
});
|
|
1783
|
-
const getKey = (path2) => {
|
|
1804
|
+
function createBunnyStorage(config) {
|
|
1805
|
+
const baseUrl = `https://storage.bunnycdn.com/${config.storageZone}`;
|
|
1806
|
+
const getKey = (path3) => {
|
|
1784
1807
|
const prefix = config.prefix ? `${config.prefix}/` : "";
|
|
1785
|
-
return `${prefix}${
|
|
1808
|
+
return `${prefix}${path3}`.replace(/\/+/g, "/");
|
|
1786
1809
|
};
|
|
1787
|
-
const getUrl = (key) => getPublicUrl(key, config);
|
|
1788
1810
|
return {
|
|
1789
|
-
name:
|
|
1790
|
-
displayName:
|
|
1811
|
+
name: "bunny",
|
|
1812
|
+
displayName: "Bunny.net Storage",
|
|
1791
1813
|
supportsDynamicResize: true,
|
|
1792
1814
|
async upload(file, options) {
|
|
1793
1815
|
const key = getKey(
|
|
1794
1816
|
`${options?.folder ? `${options.folder}/` : ""}${options?.filename || file.name}`
|
|
1795
1817
|
);
|
|
1796
1818
|
const buffer = Buffer.from(await file.arrayBuffer());
|
|
1797
|
-
await
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
)
|
|
1806
|
-
|
|
1807
|
-
new
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
);
|
|
1819
|
+
const response = await fetch(`${baseUrl}/${key}`, {
|
|
1820
|
+
method: "PUT",
|
|
1821
|
+
headers: {
|
|
1822
|
+
AccessKey: config.apiKey,
|
|
1823
|
+
"Content-Type": file.type
|
|
1824
|
+
},
|
|
1825
|
+
body: buffer
|
|
1826
|
+
});
|
|
1827
|
+
if (!response.ok) {
|
|
1828
|
+
const errorText = await response.text();
|
|
1829
|
+
throw new Error(
|
|
1830
|
+
`Bunny.net upload failed: ${response.status} ${errorText}`
|
|
1831
|
+
);
|
|
1832
|
+
}
|
|
1812
1833
|
return {
|
|
1813
1834
|
id: Buffer.from(key).toString("base64url"),
|
|
1814
1835
|
filename: options?.filename || file.name,
|
|
1815
1836
|
originalName: file.name,
|
|
1816
1837
|
mimeType: file.type,
|
|
1817
1838
|
size: buffer.length,
|
|
1818
|
-
url: getUrl(key),
|
|
1819
|
-
thumbnailUrl: file.type.startsWith("image/") ? getUrl(key) : void 0,
|
|
1839
|
+
url: getUrl(key, config),
|
|
1840
|
+
thumbnailUrl: file.type.startsWith("image/") ? getUrl(key, config) : void 0,
|
|
1820
1841
|
folder: options?.folder,
|
|
1821
|
-
provider:
|
|
1822
|
-
metadata:
|
|
1823
|
-
...options?.metadata,
|
|
1824
|
-
etag: head.ETag
|
|
1825
|
-
},
|
|
1842
|
+
provider: "bunny",
|
|
1843
|
+
metadata: options?.metadata,
|
|
1826
1844
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1827
1845
|
};
|
|
1828
1846
|
},
|
|
@@ -1838,15 +1856,460 @@ function createS3Storage(config) {
|
|
|
1838
1856
|
},
|
|
1839
1857
|
async delete(url) {
|
|
1840
1858
|
const key = url.replace(getUrlPrefix(config), "");
|
|
1841
|
-
await
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
}
|
|
1846
|
-
);
|
|
1859
|
+
const response = await fetch(`${baseUrl}/${key}`, {
|
|
1860
|
+
method: "DELETE",
|
|
1861
|
+
headers: {
|
|
1862
|
+
AccessKey: config.apiKey
|
|
1863
|
+
}
|
|
1864
|
+
});
|
|
1865
|
+
if (!response.ok && response.status !== 404) {
|
|
1866
|
+
const errorText = await response.text();
|
|
1867
|
+
throw new Error(
|
|
1868
|
+
`Bunny.net delete failed: ${response.status} ${errorText}`
|
|
1869
|
+
);
|
|
1870
|
+
}
|
|
1847
1871
|
},
|
|
1848
1872
|
async rename(oldUrl, newKey) {
|
|
1849
1873
|
const oldKey = oldUrl.replace(getUrlPrefix(config), "");
|
|
1874
|
+
const fullPath = config.prefix ? `${config.prefix}/${newKey}` : newKey;
|
|
1875
|
+
const response = await fetch(`${baseUrl}/${oldKey}`, {
|
|
1876
|
+
method: "GET",
|
|
1877
|
+
headers: {
|
|
1878
|
+
AccessKey: config.apiKey
|
|
1879
|
+
}
|
|
1880
|
+
});
|
|
1881
|
+
if (!response.ok) {
|
|
1882
|
+
throw new Error(`Bunny.net rename failed: could not read old file`);
|
|
1883
|
+
}
|
|
1884
|
+
const content = await response.arrayBuffer();
|
|
1885
|
+
await fetch(`${baseUrl}/${fullPath}`, {
|
|
1886
|
+
method: "PUT",
|
|
1887
|
+
headers: {
|
|
1888
|
+
AccessKey: config.apiKey,
|
|
1889
|
+
"Content-Type": response.headers.get("Content-Type") || "application/octet-stream"
|
|
1890
|
+
},
|
|
1891
|
+
body: content
|
|
1892
|
+
});
|
|
1893
|
+
await this.delete(oldUrl);
|
|
1894
|
+
return getUrl(fullPath, config);
|
|
1895
|
+
},
|
|
1896
|
+
getImageUrl(url, transforms) {
|
|
1897
|
+
if (!transforms || Object.keys(transforms).length === 0) return url;
|
|
1898
|
+
const params = new URLSearchParams({ url });
|
|
1899
|
+
if (transforms.width) params.set("w", String(transforms.width));
|
|
1900
|
+
if (transforms.height) params.set("h", String(transforms.height));
|
|
1901
|
+
if (transforms.quality) params.set("q", String(transforms.quality));
|
|
1902
|
+
if (transforms.format) params.set("f", transforms.format);
|
|
1903
|
+
return `/api/media/resize?${params.toString()}`;
|
|
1904
|
+
},
|
|
1905
|
+
async generateThumbnail(file) {
|
|
1906
|
+
return this.getImageUrl(file.url, { width: 400, height: 400 });
|
|
1907
|
+
},
|
|
1908
|
+
async list(prefix) {
|
|
1909
|
+
const key = getKey(prefix || "");
|
|
1910
|
+
const response = await fetch(`${baseUrl}/${key}`, {
|
|
1911
|
+
method: "GET",
|
|
1912
|
+
headers: {
|
|
1913
|
+
AccessKey: config.apiKey
|
|
1914
|
+
}
|
|
1915
|
+
});
|
|
1916
|
+
if (!response.ok) {
|
|
1917
|
+
const errorText = await response.text();
|
|
1918
|
+
throw new Error(
|
|
1919
|
+
`Bunny.net list failed: ${response.status} ${errorText}`
|
|
1920
|
+
);
|
|
1921
|
+
}
|
|
1922
|
+
const items = await response.json();
|
|
1923
|
+
return items.map((item) => ({
|
|
1924
|
+
id: Buffer.from(item.ObjectName || "").toString("base64url"),
|
|
1925
|
+
filename: item.ObjectName?.split("/").pop() || "",
|
|
1926
|
+
originalName: item.ObjectName?.split("/").pop() || "",
|
|
1927
|
+
mimeType: "application/octet-stream",
|
|
1928
|
+
size: item.Length || 0,
|
|
1929
|
+
url: getUrl(item.ObjectName || "", config),
|
|
1930
|
+
provider: "bunny",
|
|
1931
|
+
createdAt: item.LastChanged || (/* @__PURE__ */ new Date()).toISOString()
|
|
1932
|
+
}));
|
|
1933
|
+
},
|
|
1934
|
+
async exists(url) {
|
|
1935
|
+
const key = url.replace(getUrlPrefix(config), "");
|
|
1936
|
+
const response = await fetch(`${baseUrl}/${key}`, {
|
|
1937
|
+
method: "HEAD",
|
|
1938
|
+
headers: {
|
|
1939
|
+
AccessKey: config.apiKey
|
|
1940
|
+
}
|
|
1941
|
+
});
|
|
1942
|
+
return response.ok;
|
|
1943
|
+
},
|
|
1944
|
+
async createFolder(folder) {
|
|
1945
|
+
const key = getKey(`${folder}/`);
|
|
1946
|
+
await fetch(`${baseUrl}/${key}`, {
|
|
1947
|
+
method: "PUT",
|
|
1948
|
+
headers: {
|
|
1949
|
+
AccessKey: config.apiKey,
|
|
1950
|
+
"Content-Length": "0"
|
|
1951
|
+
},
|
|
1952
|
+
body: ""
|
|
1953
|
+
});
|
|
1954
|
+
},
|
|
1955
|
+
async deleteFolder(folder) {
|
|
1956
|
+
const key = getKey(`${folder}/`);
|
|
1957
|
+
await fetch(`${baseUrl}/${key}`, {
|
|
1958
|
+
method: "DELETE",
|
|
1959
|
+
headers: {
|
|
1960
|
+
AccessKey: config.apiKey
|
|
1961
|
+
}
|
|
1962
|
+
});
|
|
1963
|
+
}
|
|
1964
|
+
};
|
|
1965
|
+
}
|
|
1966
|
+
var StorageProviderRegistry = class {
|
|
1967
|
+
providers = /* @__PURE__ */ new Map();
|
|
1968
|
+
providerToPlugin = /* @__PURE__ */ new Map();
|
|
1969
|
+
disabledPlugins = /* @__PURE__ */ new Set();
|
|
1970
|
+
constructor() {
|
|
1971
|
+
this.registerLocal();
|
|
1972
|
+
this.registerImgix();
|
|
1973
|
+
this.registerBunny();
|
|
1974
|
+
}
|
|
1975
|
+
registerLocal() {
|
|
1976
|
+
this.register({
|
|
1977
|
+
type: "local",
|
|
1978
|
+
displayName: "Local Server",
|
|
1979
|
+
configKey: "local",
|
|
1980
|
+
configFields: [
|
|
1981
|
+
{
|
|
1982
|
+
name: "uploadDir",
|
|
1983
|
+
type: "text",
|
|
1984
|
+
label: "Upload Directory",
|
|
1985
|
+
defaultValue: "./public/uploads"
|
|
1986
|
+
},
|
|
1987
|
+
{
|
|
1988
|
+
name: "baseUrl",
|
|
1989
|
+
type: "text",
|
|
1990
|
+
label: "Base URL",
|
|
1991
|
+
defaultValue: "/uploads"
|
|
1992
|
+
}
|
|
1993
|
+
],
|
|
1994
|
+
extractConfig: (sc, key) => sc[key] || {},
|
|
1995
|
+
extractRawConfig: (c) => {
|
|
1996
|
+
const localConfig = c?.local || c;
|
|
1997
|
+
const savedUploadDir = (localConfig?.uploadDir || "").trim();
|
|
1998
|
+
let uploadDir;
|
|
1999
|
+
if (savedUploadDir) {
|
|
2000
|
+
if (path__default.default.isAbsolute(savedUploadDir)) {
|
|
2001
|
+
uploadDir = savedUploadDir;
|
|
2002
|
+
} else if (savedUploadDir.includes("/") || savedUploadDir.includes("\\")) {
|
|
2003
|
+
uploadDir = path__default.default.resolve(process.cwd(), savedUploadDir);
|
|
2004
|
+
} else {
|
|
2005
|
+
uploadDir = path__default.default.join(process.cwd(), "public", savedUploadDir);
|
|
2006
|
+
}
|
|
2007
|
+
} else {
|
|
2008
|
+
uploadDir = path__default.default.join(process.cwd(), "public", "uploads");
|
|
2009
|
+
}
|
|
2010
|
+
const savedBaseUrl = (localConfig?.baseUrl || "").trim();
|
|
2011
|
+
let baseUrl;
|
|
2012
|
+
if (savedBaseUrl) {
|
|
2013
|
+
baseUrl = savedBaseUrl.startsWith("/") ? savedBaseUrl : `/${savedBaseUrl}`;
|
|
2014
|
+
} else {
|
|
2015
|
+
baseUrl = "/uploads";
|
|
2016
|
+
}
|
|
2017
|
+
return { uploadDir, baseUrl };
|
|
2018
|
+
},
|
|
2019
|
+
factory: (c) => createLocalStorage(c)
|
|
2020
|
+
});
|
|
2021
|
+
}
|
|
2022
|
+
registerImgix() {
|
|
2023
|
+
this.register({
|
|
2024
|
+
type: "imgix",
|
|
2025
|
+
displayName: "Imgix",
|
|
2026
|
+
configKey: "imgix",
|
|
2027
|
+
configFields: [
|
|
2028
|
+
{ name: "domain", type: "text", label: "Domain", required: true },
|
|
2029
|
+
{ name: "signKey", type: "password", label: "Sign Key" }
|
|
2030
|
+
],
|
|
2031
|
+
extractConfig: (sc, key) => sc[key] || {},
|
|
2032
|
+
extractRawConfig: (c) => c?.imgix || c,
|
|
2033
|
+
factory: (c) => createImgixStorage(c)
|
|
2034
|
+
});
|
|
2035
|
+
}
|
|
2036
|
+
registerBunny() {
|
|
2037
|
+
this.register({
|
|
2038
|
+
type: "bunny",
|
|
2039
|
+
displayName: "Bunny.net",
|
|
2040
|
+
configKey: "bunny",
|
|
2041
|
+
configFields: [
|
|
2042
|
+
{
|
|
2043
|
+
name: "storageZone",
|
|
2044
|
+
type: "text",
|
|
2045
|
+
label: "Storage Zone",
|
|
2046
|
+
required: true
|
|
2047
|
+
},
|
|
2048
|
+
{ name: "apiKey", type: "password", label: "API Key", required: true },
|
|
2049
|
+
{ name: "cdnUrl", type: "text", label: "CDN URL" },
|
|
2050
|
+
{ name: "prefix", type: "text", label: "Path Prefix" }
|
|
2051
|
+
],
|
|
2052
|
+
extractConfig: (sc, key) => sc[key] || {},
|
|
2053
|
+
extractRawConfig: (c) => c?.bunny || c,
|
|
2054
|
+
factory: (c) => createBunnyStorage(c)
|
|
2055
|
+
});
|
|
2056
|
+
}
|
|
2057
|
+
register(registration) {
|
|
2058
|
+
if (this.providers.has(registration.type)) {
|
|
2059
|
+
console.warn(
|
|
2060
|
+
`[StorageRegistry] Provider "${registration.type}" already registered, skipping`
|
|
2061
|
+
);
|
|
2062
|
+
return;
|
|
2063
|
+
}
|
|
2064
|
+
if (registration.pluginName) {
|
|
2065
|
+
this.providerToPlugin.set(registration.type, registration.pluginName);
|
|
2066
|
+
}
|
|
2067
|
+
this.providers.set(registration.type, registration);
|
|
2068
|
+
}
|
|
2069
|
+
unregister(type) {
|
|
2070
|
+
this.providers.delete(type);
|
|
2071
|
+
this.providerToPlugin.delete(type);
|
|
2072
|
+
}
|
|
2073
|
+
get(type) {
|
|
2074
|
+
return this.providers.get(type);
|
|
2075
|
+
}
|
|
2076
|
+
getAll() {
|
|
2077
|
+
return Array.from(this.providers.values());
|
|
2078
|
+
}
|
|
2079
|
+
getAllAvailable(isPluginEnabled) {
|
|
2080
|
+
const all = this.getAll();
|
|
2081
|
+
if (!isPluginEnabled) return all;
|
|
2082
|
+
return all.filter((p) => {
|
|
2083
|
+
if (!p.pluginName) return true;
|
|
2084
|
+
return isPluginEnabled(p.pluginName);
|
|
2085
|
+
});
|
|
2086
|
+
}
|
|
2087
|
+
has(type) {
|
|
2088
|
+
return this.providers.has(type);
|
|
2089
|
+
}
|
|
2090
|
+
async resolve(type, storageConfig) {
|
|
2091
|
+
const reg = this.providers.get(type);
|
|
2092
|
+
if (!reg) {
|
|
2093
|
+
throw new Error(
|
|
2094
|
+
`Unknown storage provider type: "${type}". Is the plugin that provides it enabled?`
|
|
2095
|
+
);
|
|
2096
|
+
}
|
|
2097
|
+
if (reg.pluginName && this.disabledPlugins.has(reg.pluginName)) {
|
|
2098
|
+
throw new Error(
|
|
2099
|
+
`Storage provider "${type}" is not available (plugin "${reg.pluginName}" is disabled)`
|
|
2100
|
+
);
|
|
2101
|
+
}
|
|
2102
|
+
const configKey = reg.configKey || type;
|
|
2103
|
+
const config = reg.extractConfig(storageConfig, configKey);
|
|
2104
|
+
return reg.factory(config);
|
|
2105
|
+
}
|
|
2106
|
+
async resolveWithConfig(type, config) {
|
|
2107
|
+
const reg = this.providers.get(type);
|
|
2108
|
+
if (!reg) {
|
|
2109
|
+
throw new Error(
|
|
2110
|
+
`Unknown storage provider type: "${type}". Is the plugin that provides it enabled?`
|
|
2111
|
+
);
|
|
2112
|
+
}
|
|
2113
|
+
if (reg.pluginName && this.disabledPlugins.has(reg.pluginName)) {
|
|
2114
|
+
throw new Error(
|
|
2115
|
+
`Storage provider "${type}" is not available (plugin "${reg.pluginName}" is disabled)`
|
|
2116
|
+
);
|
|
2117
|
+
}
|
|
2118
|
+
const providerConfig = reg.extractRawConfig(config);
|
|
2119
|
+
return reg.factory(providerConfig);
|
|
2120
|
+
}
|
|
2121
|
+
getProviderPluginName(type) {
|
|
2122
|
+
return this.providerToPlugin.get(type);
|
|
2123
|
+
}
|
|
2124
|
+
getAllPluginNames() {
|
|
2125
|
+
return Array.from(new Set(this.providerToPlugin.values()));
|
|
2126
|
+
}
|
|
2127
|
+
setPluginEnabled(name, enabled) {
|
|
2128
|
+
if (enabled) {
|
|
2129
|
+
this.disabledPlugins.delete(name);
|
|
2130
|
+
} else {
|
|
2131
|
+
this.disabledPlugins.add(name);
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
isPluginEnabled(name) {
|
|
2135
|
+
return !this.disabledPlugins.has(name);
|
|
2136
|
+
}
|
|
2137
|
+
};
|
|
2138
|
+
var instance = null;
|
|
2139
|
+
function getDefaultRegistry() {
|
|
2140
|
+
if (!instance) {
|
|
2141
|
+
instance = new StorageProviderRegistry();
|
|
2142
|
+
}
|
|
2143
|
+
return instance;
|
|
2144
|
+
}
|
|
2145
|
+
function extractPublicDevUrlId(url) {
|
|
2146
|
+
if (!url) return "";
|
|
2147
|
+
if (url.startsWith("pub-")) return url;
|
|
2148
|
+
const match = url.match(/pub-[a-zA-Z0-9]+/i);
|
|
2149
|
+
return match ? match[0] : "";
|
|
2150
|
+
}
|
|
2151
|
+
function getPublicUrl(key, config) {
|
|
2152
|
+
const normalizedKey = key.startsWith("/") ? key.slice(1) : key;
|
|
2153
|
+
if (config.cdnUrl) {
|
|
2154
|
+
const cdn = config.cdnUrl.replace(/\/$/, "");
|
|
2155
|
+
return `${cdn}/${normalizedKey}`;
|
|
2156
|
+
}
|
|
2157
|
+
switch (config.provider) {
|
|
2158
|
+
case "r2": {
|
|
2159
|
+
const pubId = extractPublicDevUrlId(config.publicDevUrl);
|
|
2160
|
+
if (pubId) {
|
|
2161
|
+
return `https://${pubId}.r2.dev/${normalizedKey}`;
|
|
2162
|
+
}
|
|
2163
|
+
return `https://${config.bucket}.${config.accountId}.r2.cloudflarestorage.com/${normalizedKey}`;
|
|
2164
|
+
}
|
|
2165
|
+
case "gcs":
|
|
2166
|
+
return `https://storage.googleapis.com/${config.bucket}/${normalizedKey}`;
|
|
2167
|
+
case "digitalocean":
|
|
2168
|
+
return `https://${config.bucket}.${config.region}.cdn.digitaloceanspaces.com/${normalizedKey}`;
|
|
2169
|
+
case "backblaze":
|
|
2170
|
+
return `https://${config.bucket}.s3.backblazeb2.com/${normalizedKey}`;
|
|
2171
|
+
case "wasabi":
|
|
2172
|
+
return `https://${config.bucket}.s3.wasabisys.com/${normalizedKey}`;
|
|
2173
|
+
case "aws":
|
|
2174
|
+
default:
|
|
2175
|
+
return `https://${config.bucket}.s3.${config.region}.amazonaws.com/${normalizedKey}`;
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
function getUrlPrefix2(config) {
|
|
2179
|
+
if (config.cdnUrl) {
|
|
2180
|
+
return config.cdnUrl.replace(/\/$/, "") + "/";
|
|
2181
|
+
}
|
|
2182
|
+
switch (config.provider) {
|
|
2183
|
+
case "r2": {
|
|
2184
|
+
const pubId = extractPublicDevUrlId(config.publicDevUrl);
|
|
2185
|
+
if (pubId) {
|
|
2186
|
+
return `https://${pubId}.r2.dev/`;
|
|
2187
|
+
}
|
|
2188
|
+
return `https://${config.bucket}.${config.accountId}.r2.cloudflarestorage.com/`;
|
|
2189
|
+
}
|
|
2190
|
+
case "gcs":
|
|
2191
|
+
return `https://storage.googleapis.com/${config.bucket}/`;
|
|
2192
|
+
case "digitalocean":
|
|
2193
|
+
return `https://${config.bucket}.${config.region}.cdn.digitaloceanspaces.com/`;
|
|
2194
|
+
case "backblaze":
|
|
2195
|
+
return `https://${config.bucket}.s3.backblazeb2.com/`;
|
|
2196
|
+
case "wasabi":
|
|
2197
|
+
return `https://${config.bucket}.s3.wasabisys.com/`;
|
|
2198
|
+
case "aws":
|
|
2199
|
+
default:
|
|
2200
|
+
return `https://${config.bucket}.s3.${config.region}.amazonaws.com/`;
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
function getDisplayName(provider) {
|
|
2204
|
+
switch (provider) {
|
|
2205
|
+
case "r2":
|
|
2206
|
+
return "Cloudflare R2";
|
|
2207
|
+
case "gcs":
|
|
2208
|
+
return "Google Cloud Storage";
|
|
2209
|
+
case "digitalocean":
|
|
2210
|
+
return "DigitalOcean Spaces";
|
|
2211
|
+
case "backblaze":
|
|
2212
|
+
return "Backblaze B2";
|
|
2213
|
+
case "wasabi":
|
|
2214
|
+
return "Wasabi";
|
|
2215
|
+
case "aws":
|
|
2216
|
+
default:
|
|
2217
|
+
return "AWS S3";
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
function createS3Storage(config) {
|
|
2221
|
+
console.log("[createS3Storage] Creating provider:", config.provider);
|
|
2222
|
+
console.log("[createS3Storage] Credentials:", {
|
|
2223
|
+
accessKeyId: config.accessKeyId ? "SET" : "UNDEFINED",
|
|
2224
|
+
secretAccessKey: config.secretAccessKey ? "SET" : "UNDEFINED",
|
|
2225
|
+
bucket: config.bucket,
|
|
2226
|
+
accountId: config.accountId,
|
|
2227
|
+
endpoint: config.endpoint
|
|
2228
|
+
});
|
|
2229
|
+
const client = new clientS3.S3Client({
|
|
2230
|
+
region: config.region || "auto",
|
|
2231
|
+
endpoint: config.endpoint,
|
|
2232
|
+
credentials: {
|
|
2233
|
+
accessKeyId: config.accessKeyId,
|
|
2234
|
+
secretAccessKey: config.secretAccessKey
|
|
2235
|
+
},
|
|
2236
|
+
forcePathStyle: true,
|
|
2237
|
+
tls: true,
|
|
2238
|
+
// R2 requires specific SSL configuration
|
|
2239
|
+
...config.provider === "r2" && {
|
|
2240
|
+
requestHandler: new nodeHttpHandler.NodeHttpHandler({
|
|
2241
|
+
connectionTimeout: 1e4,
|
|
2242
|
+
socketTimeout: 1e4
|
|
2243
|
+
})
|
|
2244
|
+
}
|
|
2245
|
+
});
|
|
2246
|
+
const getKey = (path3) => {
|
|
2247
|
+
const prefix = config.prefix ? `${config.prefix}/` : "";
|
|
2248
|
+
return `${prefix}${path3}`.replace(/\/+/g, "/");
|
|
2249
|
+
};
|
|
2250
|
+
const getUrl2 = (key) => getPublicUrl(key, config);
|
|
2251
|
+
return {
|
|
2252
|
+
name: config.provider,
|
|
2253
|
+
displayName: getDisplayName(config.provider),
|
|
2254
|
+
supportsDynamicResize: true,
|
|
2255
|
+
async upload(file, options) {
|
|
2256
|
+
const key = getKey(
|
|
2257
|
+
`${options?.folder ? `${options.folder}/` : ""}${options?.filename || file.name}`
|
|
2258
|
+
);
|
|
2259
|
+
const buffer = Buffer.from(await file.arrayBuffer());
|
|
2260
|
+
await client.send(
|
|
2261
|
+
new clientS3.PutObjectCommand({
|
|
2262
|
+
Bucket: config.bucket,
|
|
2263
|
+
Key: key,
|
|
2264
|
+
Body: buffer,
|
|
2265
|
+
ContentType: file.type,
|
|
2266
|
+
Metadata: options?.metadata
|
|
2267
|
+
})
|
|
2268
|
+
);
|
|
2269
|
+
const head = await client.send(
|
|
2270
|
+
new clientS3.HeadObjectCommand({
|
|
2271
|
+
Bucket: config.bucket,
|
|
2272
|
+
Key: key
|
|
2273
|
+
})
|
|
2274
|
+
);
|
|
2275
|
+
return {
|
|
2276
|
+
id: Buffer.from(key).toString("base64url"),
|
|
2277
|
+
filename: options?.filename || file.name,
|
|
2278
|
+
originalName: file.name,
|
|
2279
|
+
mimeType: file.type,
|
|
2280
|
+
size: buffer.length,
|
|
2281
|
+
url: getUrl2(key),
|
|
2282
|
+
thumbnailUrl: file.type.startsWith("image/") ? getUrl2(key) : void 0,
|
|
2283
|
+
folder: options?.folder,
|
|
2284
|
+
provider: config.provider,
|
|
2285
|
+
metadata: {
|
|
2286
|
+
...options?.metadata,
|
|
2287
|
+
etag: head.ETag
|
|
2288
|
+
},
|
|
2289
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2290
|
+
};
|
|
2291
|
+
},
|
|
2292
|
+
async uploadFromUrl(url) {
|
|
2293
|
+
const response = await fetch(url);
|
|
2294
|
+
if (!response.ok) {
|
|
2295
|
+
throw new Error(`Failed to fetch: ${response.statusText}`);
|
|
2296
|
+
}
|
|
2297
|
+
const blob = await response.blob();
|
|
2298
|
+
const filename = url.split("/").pop() || "file";
|
|
2299
|
+
const file = new File([blob], filename, { type: blob.type });
|
|
2300
|
+
return this.upload(file);
|
|
2301
|
+
},
|
|
2302
|
+
async delete(url) {
|
|
2303
|
+
const key = url.replace(getUrlPrefix2(config), "");
|
|
2304
|
+
await client.send(
|
|
2305
|
+
new clientS3.DeleteObjectCommand({
|
|
2306
|
+
Bucket: config.bucket,
|
|
2307
|
+
Key: key
|
|
2308
|
+
})
|
|
2309
|
+
);
|
|
2310
|
+
},
|
|
2311
|
+
async rename(oldUrl, newKey) {
|
|
2312
|
+
const oldKey = oldUrl.replace(getUrlPrefix2(config), "");
|
|
1850
2313
|
const newKeyWithPrefix = config.prefix ? `${config.prefix}/${newKey}` : newKey;
|
|
1851
2314
|
await client.send(
|
|
1852
2315
|
new clientS3.CopyObjectCommand({
|
|
@@ -1861,7 +2324,7 @@ function createS3Storage(config) {
|
|
|
1861
2324
|
Key: oldKey
|
|
1862
2325
|
})
|
|
1863
2326
|
);
|
|
1864
|
-
return
|
|
2327
|
+
return getUrl2(newKeyWithPrefix);
|
|
1865
2328
|
},
|
|
1866
2329
|
getImageUrl(url, transforms) {
|
|
1867
2330
|
if (!transforms || Object.keys(transforms).length === 0) return url;
|
|
@@ -1889,14 +2352,14 @@ function createS3Storage(config) {
|
|
|
1889
2352
|
originalName: item.Key?.split("/").pop() || "",
|
|
1890
2353
|
mimeType: "application/octet-stream",
|
|
1891
2354
|
size: item.Size || 0,
|
|
1892
|
-
url:
|
|
2355
|
+
url: getUrl2(item.Key || ""),
|
|
1893
2356
|
provider: config.provider,
|
|
1894
2357
|
createdAt: item.LastModified?.toISOString() || (/* @__PURE__ */ new Date()).toISOString()
|
|
1895
2358
|
}));
|
|
1896
2359
|
},
|
|
1897
2360
|
async exists(url) {
|
|
1898
2361
|
try {
|
|
1899
|
-
const key = url.replace(
|
|
2362
|
+
const key = url.replace(getUrlPrefix2(config), "");
|
|
1900
2363
|
await client.send(
|
|
1901
2364
|
new clientS3.HeadObjectCommand({
|
|
1902
2365
|
Bucket: config.bucket,
|
|
@@ -1956,9 +2419,9 @@ function createS3Storage(config) {
|
|
|
1956
2419
|
function createCloudinaryStorage(config) {
|
|
1957
2420
|
const getBaseUrl = () => `https://api.cloudinary.com/v1_1/${config.cloudName}/upload`;
|
|
1958
2421
|
const generateSignature = async (params) => {
|
|
1959
|
-
const
|
|
2422
|
+
const crypto4 = await import('crypto');
|
|
1960
2423
|
const sortedParams = Object.keys(params).sort().map((key) => `${key}=${params[key]}`).join("&");
|
|
1961
|
-
return
|
|
2424
|
+
return crypto4.createHash("sha256").update(sortedParams + config.apiSecret).digest("hex");
|
|
1962
2425
|
};
|
|
1963
2426
|
return {
|
|
1964
2427
|
name: "cloudinary",
|
|
@@ -2060,8 +2523,8 @@ function createCloudinaryStorage(config) {
|
|
|
2060
2523
|
public_id: publicId
|
|
2061
2524
|
};
|
|
2062
2525
|
const sortedParams = Object.keys(signatureParams).sort().map((key) => `${key}=${signatureParams[key]}`).join("&");
|
|
2063
|
-
const
|
|
2064
|
-
const signature =
|
|
2526
|
+
const crypto4 = await import('crypto');
|
|
2527
|
+
const signature = crypto4.createHash("sha256").update(sortedParams + config.apiSecret).digest("hex");
|
|
2065
2528
|
const deleteUrl = `https://api.cloudinary.com/v1_1/${config.cloudName}/image/destroy`;
|
|
2066
2529
|
const formData = new FormData();
|
|
2067
2530
|
formData.append("public_id", publicId);
|
|
@@ -2112,8 +2575,8 @@ function createCloudinaryStorage(config) {
|
|
|
2112
2575
|
timestamp: String(timestamp)
|
|
2113
2576
|
};
|
|
2114
2577
|
const sortedParams = Object.keys(signatureParams).sort().map((key) => `${key}=${signatureParams[key]}`).join("&");
|
|
2115
|
-
const
|
|
2116
|
-
const signature =
|
|
2578
|
+
const crypto4 = await import('crypto');
|
|
2579
|
+
const signature = crypto4.createHash("sha256").update(sortedParams + config.apiSecret).digest("hex");
|
|
2117
2580
|
const formData = new FormData();
|
|
2118
2581
|
formData.append("from_public_id", publicIdWithoutVersion);
|
|
2119
2582
|
formData.append("to_public_id", newPublicId);
|
|
@@ -2131,140 +2594,38 @@ function createCloudinaryStorage(config) {
|
|
|
2131
2594
|
const data = await response.json();
|
|
2132
2595
|
console.log("[Cloudinary rename] Success:", data.secure_url);
|
|
2133
2596
|
return data.secure_url;
|
|
2134
|
-
} else {
|
|
2135
|
-
const error = await response.json();
|
|
2136
|
-
console.warn("[Cloudinary rename] Failed:", error);
|
|
2137
|
-
const versionStr = version ? `/${version}/` : "/";
|
|
2138
|
-
return `https://res.cloudinary.com/${config.cloudName}/image/upload${versionStr}${newPublicId}.${newKey.split(".").pop()}`;
|
|
2139
|
-
}
|
|
2140
|
-
} catch (e) {
|
|
2141
|
-
console.warn("[Cloudinary rename] Error:", e.message);
|
|
2142
|
-
const versionStr = version ? `/${version}/` : "/";
|
|
2143
|
-
return `https://res.cloudinary.com/${config.cloudName}/image/upload${versionStr}${newPublicId}.${newKey.split(".").pop()}`;
|
|
2144
|
-
}
|
|
2145
|
-
},
|
|
2146
|
-
getImageUrl(url, transforms) {
|
|
2147
|
-
if (!transforms) return url;
|
|
2148
|
-
const parts = url.split("/upload/");
|
|
2149
|
-
if (parts.length !== 2) return url;
|
|
2150
|
-
const transformationArr = [];
|
|
2151
|
-
if (transforms.width) transformationArr.push(`w_${transforms.width}`);
|
|
2152
|
-
if (transforms.height) transformationArr.push(`h_${transforms.height}`);
|
|
2153
|
-
if (transforms.fit) {
|
|
2154
|
-
const fitMap = {
|
|
2155
|
-
crop: "c_fill",
|
|
2156
|
-
clip: "c_fit",
|
|
2157
|
-
scale: "c_scale",
|
|
2158
|
-
fill: "c_fill"
|
|
2159
|
-
};
|
|
2160
|
-
transformationArr.push(fitMap[transforms.fit] || "c_limit");
|
|
2161
|
-
}
|
|
2162
|
-
if (transforms.quality) transformationArr.push(`q_${transforms.quality}`);
|
|
2163
|
-
if (transforms.format) transformationArr.push(`f_${transforms.format}`);
|
|
2164
|
-
const transformationStr = transformationArr.join(",");
|
|
2165
|
-
return `${parts[0]}/upload/${transformationStr}/${parts[1]}`;
|
|
2166
|
-
},
|
|
2167
|
-
async generateThumbnail(file) {
|
|
2168
|
-
return this.getImageUrl(file.url, {
|
|
2169
|
-
width: 200,
|
|
2170
|
-
height: 200,
|
|
2171
|
-
fit: "crop"
|
|
2172
|
-
});
|
|
2173
|
-
},
|
|
2174
|
-
async list() {
|
|
2175
|
-
return [];
|
|
2176
|
-
},
|
|
2177
|
-
async exists(url) {
|
|
2178
|
-
const response = await fetch(url, { method: "HEAD" });
|
|
2179
|
-
return response.ok;
|
|
2180
|
-
},
|
|
2181
|
-
async createFolder() {
|
|
2182
|
-
},
|
|
2183
|
-
async deleteFolder() {
|
|
2184
|
-
}
|
|
2185
|
-
};
|
|
2186
|
-
}
|
|
2187
|
-
|
|
2188
|
-
// src/storage/imgix.ts
|
|
2189
|
-
function createImgixStorage(config) {
|
|
2190
|
-
const signUrl = (path2, params) => {
|
|
2191
|
-
if (!config.signKey) return path2;
|
|
2192
|
-
const signer = new TextEncoder();
|
|
2193
|
-
const key = signer.encode(config.signKey);
|
|
2194
|
-
const data = signer.encode(path2 + params.toString());
|
|
2195
|
-
let hash = 0;
|
|
2196
|
-
const combined = new Uint8Array(key.length + data.length);
|
|
2197
|
-
combined.set(key);
|
|
2198
|
-
combined.set(data, key.length);
|
|
2199
|
-
for (let i = 0; i < combined.length; i++) {
|
|
2200
|
-
hash = (hash << 5) - hash + combined[i] | 0;
|
|
2201
|
-
}
|
|
2202
|
-
params.set("s", Math.abs(hash).toString(16));
|
|
2203
|
-
return path2;
|
|
2204
|
-
};
|
|
2205
|
-
return {
|
|
2206
|
-
name: "imgix",
|
|
2207
|
-
displayName: "Imgix",
|
|
2208
|
-
supportsDynamicResize: true,
|
|
2209
|
-
async upload(_file, _options) {
|
|
2210
|
-
throw new Error(
|
|
2211
|
-
"Imgix is a transformation service. Use another provider for uploads."
|
|
2212
|
-
);
|
|
2213
|
-
},
|
|
2214
|
-
async uploadFromUrl(url, options) {
|
|
2215
|
-
const filename = options?.filename || url.split("/").pop() || "file";
|
|
2216
|
-
const response = await fetch(url);
|
|
2217
|
-
if (!response.ok) {
|
|
2218
|
-
throw new Error(`Failed to fetch: ${response.statusText}`);
|
|
2597
|
+
} else {
|
|
2598
|
+
const error = await response.json();
|
|
2599
|
+
console.warn("[Cloudinary rename] Failed:", error);
|
|
2600
|
+
const versionStr = version ? `/${version}/` : "/";
|
|
2601
|
+
return `https://res.cloudinary.com/${config.cloudName}/image/upload${versionStr}${newPublicId}.${newKey.split(".").pop()}`;
|
|
2602
|
+
}
|
|
2603
|
+
} catch (e) {
|
|
2604
|
+
console.warn("[Cloudinary rename] Error:", e.message);
|
|
2605
|
+
const versionStr = version ? `/${version}/` : "/";
|
|
2606
|
+
return `https://res.cloudinary.com/${config.cloudName}/image/upload${versionStr}${newPublicId}.${newKey.split(".").pop()}`;
|
|
2219
2607
|
}
|
|
2220
|
-
const blob = await response.blob();
|
|
2221
|
-
new File([blob], filename, { type: blob.type });
|
|
2222
|
-
return {
|
|
2223
|
-
id: Buffer.from(url).toString("base64").slice(0, 20),
|
|
2224
|
-
filename,
|
|
2225
|
-
originalName: filename,
|
|
2226
|
-
mimeType: blob.type,
|
|
2227
|
-
size: blob.size,
|
|
2228
|
-
url: this.getImageUrl(url),
|
|
2229
|
-
thumbnailUrl: this.getImageUrl(url, {
|
|
2230
|
-
width: 200,
|
|
2231
|
-
height: 200,
|
|
2232
|
-
fit: "crop"
|
|
2233
|
-
}),
|
|
2234
|
-
provider: "imgix",
|
|
2235
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2236
|
-
};
|
|
2237
|
-
},
|
|
2238
|
-
async delete(_url) {
|
|
2239
|
-
},
|
|
2240
|
-
async rename(_oldUrl, newKey) {
|
|
2241
|
-
return `https://${config.domain}/${newKey}`;
|
|
2242
2608
|
},
|
|
2243
2609
|
getImageUrl(url, transforms) {
|
|
2244
|
-
|
|
2245
|
-
const
|
|
2246
|
-
if (
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
if (transforms.fit) params.set("fit", transforms.fit);
|
|
2259
|
-
if (transforms.blur) params.set("blur", String(transforms.blur));
|
|
2260
|
-
if (transforms.sharpen) params.set("sharp", String(transforms.sharpen));
|
|
2261
|
-
}
|
|
2262
|
-
params.set("auto", "compress,format");
|
|
2263
|
-
parsed.pathname + "?" + params.toString();
|
|
2264
|
-
if (config.signKey) {
|
|
2265
|
-
signUrl(parsed.pathname + params.toString(), params);
|
|
2610
|
+
if (!transforms) return url;
|
|
2611
|
+
const parts = url.split("/upload/");
|
|
2612
|
+
if (parts.length !== 2) return url;
|
|
2613
|
+
const transformationArr = [];
|
|
2614
|
+
if (transforms.width) transformationArr.push(`w_${transforms.width}`);
|
|
2615
|
+
if (transforms.height) transformationArr.push(`h_${transforms.height}`);
|
|
2616
|
+
if (transforms.fit) {
|
|
2617
|
+
const fitMap = {
|
|
2618
|
+
crop: "c_fill",
|
|
2619
|
+
clip: "c_fit",
|
|
2620
|
+
scale: "c_scale",
|
|
2621
|
+
fill: "c_fill"
|
|
2622
|
+
};
|
|
2623
|
+
transformationArr.push(fitMap[transforms.fit] || "c_limit");
|
|
2266
2624
|
}
|
|
2267
|
-
|
|
2625
|
+
if (transforms.quality) transformationArr.push(`q_${transforms.quality}`);
|
|
2626
|
+
if (transforms.format) transformationArr.push(`f_${transforms.format}`);
|
|
2627
|
+
const transformationStr = transformationArr.join(",");
|
|
2628
|
+
return `${parts[0]}/upload/${transformationStr}/${parts[1]}`;
|
|
2268
2629
|
},
|
|
2269
2630
|
async generateThumbnail(file) {
|
|
2270
2631
|
return this.getImageUrl(file.url, {
|
|
@@ -2277,12 +2638,8 @@ function createImgixStorage(config) {
|
|
|
2277
2638
|
return [];
|
|
2278
2639
|
},
|
|
2279
2640
|
async exists(url) {
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
return response.ok;
|
|
2283
|
-
} catch {
|
|
2284
|
-
return false;
|
|
2285
|
-
}
|
|
2641
|
+
const response = await fetch(url, { method: "HEAD" });
|
|
2642
|
+
return response.ok;
|
|
2286
2643
|
},
|
|
2287
2644
|
async createFolder() {
|
|
2288
2645
|
},
|
|
@@ -2307,15 +2664,15 @@ function createFtpStorage(config) {
|
|
|
2307
2664
|
}
|
|
2308
2665
|
return client;
|
|
2309
2666
|
}
|
|
2310
|
-
const getKey = (
|
|
2667
|
+
const getKey = (path3) => {
|
|
2311
2668
|
const prefix = config.prefix ? `${config.prefix}/` : "";
|
|
2312
|
-
return `${prefix}${
|
|
2669
|
+
return `${prefix}${path3}`.replace(/\/+/g, "/");
|
|
2313
2670
|
};
|
|
2314
|
-
const
|
|
2671
|
+
const getUrl2 = (key) => {
|
|
2315
2672
|
const base = config.baseUrl.replace(/\/$/, "");
|
|
2316
2673
|
return `${base}/${key}`;
|
|
2317
2674
|
};
|
|
2318
|
-
const
|
|
2675
|
+
const getUrlPrefix3 = () => {
|
|
2319
2676
|
const base = config.baseUrl.replace(/\/$/, "");
|
|
2320
2677
|
return base + "/";
|
|
2321
2678
|
};
|
|
@@ -2346,8 +2703,8 @@ function createFtpStorage(config) {
|
|
|
2346
2703
|
originalName: file.name,
|
|
2347
2704
|
mimeType: file.type,
|
|
2348
2705
|
size: buffer.length,
|
|
2349
|
-
url:
|
|
2350
|
-
thumbnailUrl: file.type.startsWith("image/") ?
|
|
2706
|
+
url: getUrl2(key),
|
|
2707
|
+
thumbnailUrl: file.type.startsWith("image/") ? getUrl2(key) : void 0,
|
|
2351
2708
|
folder: options?.folder,
|
|
2352
2709
|
provider: config.type,
|
|
2353
2710
|
metadata: options?.metadata,
|
|
@@ -2366,15 +2723,15 @@ function createFtpStorage(config) {
|
|
|
2366
2723
|
},
|
|
2367
2724
|
async delete(url) {
|
|
2368
2725
|
const ftp = await getClient();
|
|
2369
|
-
const key = url.replace(
|
|
2726
|
+
const key = url.replace(getUrlPrefix3(), "");
|
|
2370
2727
|
await ftp.remove(key);
|
|
2371
2728
|
},
|
|
2372
2729
|
async rename(oldUrl, newKey) {
|
|
2373
2730
|
const ftp = await getClient();
|
|
2374
|
-
const oldKey = oldUrl.replace(
|
|
2731
|
+
const oldKey = oldUrl.replace(getUrlPrefix3(), "");
|
|
2375
2732
|
const fullPath = config.prefix ? `${config.prefix}/${newKey}` : newKey;
|
|
2376
2733
|
await ftp.rename(oldKey, fullPath);
|
|
2377
|
-
return
|
|
2734
|
+
return getUrl2(fullPath);
|
|
2378
2735
|
},
|
|
2379
2736
|
getImageUrl(url, transforms) {
|
|
2380
2737
|
if (!transforms || Object.keys(transforms).length === 0) return url;
|
|
@@ -2403,14 +2760,14 @@ function createFtpStorage(config) {
|
|
|
2403
2760
|
originalName: item.name,
|
|
2404
2761
|
mimeType: "application/octet-stream",
|
|
2405
2762
|
size: Number(item.size) || 0,
|
|
2406
|
-
url:
|
|
2763
|
+
url: getUrl2(`${key}/${item.name}`.replace(/\/+/g, "/")),
|
|
2407
2764
|
provider: config.type,
|
|
2408
2765
|
createdAt: item.modifiedAt ? item.modifiedAt.toISOString() : (/* @__PURE__ */ new Date()).toISOString()
|
|
2409
2766
|
}));
|
|
2410
2767
|
},
|
|
2411
2768
|
async exists(url) {
|
|
2412
2769
|
const ftp = await getClient();
|
|
2413
|
-
const key = url.replace(
|
|
2770
|
+
const key = url.replace(getUrlPrefix3(), "");
|
|
2414
2771
|
try {
|
|
2415
2772
|
await ftp.size(key);
|
|
2416
2773
|
return true;
|
|
@@ -2434,105 +2791,17 @@ function createFtpStorage(config) {
|
|
|
2434
2791
|
// src/storage/index.ts
|
|
2435
2792
|
async function resolveProvider(configService) {
|
|
2436
2793
|
const config = configService.getStorageConfig();
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
});
|
|
2449
|
-
case "r2":
|
|
2450
|
-
return createS3Storage({
|
|
2451
|
-
provider: "r2",
|
|
2452
|
-
bucket: config.r2.bucket || "",
|
|
2453
|
-
region: "auto",
|
|
2454
|
-
accessKeyId: config.r2.accessKeyId || "",
|
|
2455
|
-
secretAccessKey: config.r2.secretAccessKey || "",
|
|
2456
|
-
accountId: config.r2.accountId || "",
|
|
2457
|
-
publicDevUrl: config.r2.publicDevUrl,
|
|
2458
|
-
endpoint: `https://${config.r2.accountId || ""}.r2.cloudflarestorage.com`,
|
|
2459
|
-
cdnUrl: config.r2.cdnUrl,
|
|
2460
|
-
prefix: config.r2.prefix
|
|
2461
|
-
});
|
|
2462
|
-
case "gcs":
|
|
2463
|
-
return createS3Storage({
|
|
2464
|
-
provider: "gcs",
|
|
2465
|
-
bucket: config.gcs.bucket || "",
|
|
2466
|
-
region: config.gcs.projectId || "auto",
|
|
2467
|
-
accessKeyId: config.gcs.clientEmail || "",
|
|
2468
|
-
secretAccessKey: config.gcs.privateKey || "",
|
|
2469
|
-
cdnUrl: config.gcs.cdnUrl,
|
|
2470
|
-
prefix: config.gcs.prefix
|
|
2471
|
-
});
|
|
2472
|
-
case "digitalocean":
|
|
2473
|
-
return createS3Storage({
|
|
2474
|
-
provider: "digitalocean",
|
|
2475
|
-
bucket: config.digitalocean.bucket || "",
|
|
2476
|
-
region: config.digitalocean.region || "nyc3",
|
|
2477
|
-
accessKeyId: config.digitalocean.accessKeyId || "",
|
|
2478
|
-
secretAccessKey: config.digitalocean.secretAccessKey || "",
|
|
2479
|
-
endpoint: `https://${config.digitalocean.region || "nyc3"}.digitaloceanspaces.com`,
|
|
2480
|
-
cdnUrl: config.digitalocean.cdnUrl,
|
|
2481
|
-
prefix: config.digitalocean.prefix
|
|
2482
|
-
});
|
|
2483
|
-
case "backblaze":
|
|
2484
|
-
return createS3Storage({
|
|
2485
|
-
provider: "backblaze",
|
|
2486
|
-
bucket: config.backblaze.bucket || "",
|
|
2487
|
-
region: "auto",
|
|
2488
|
-
accessKeyId: config.backblaze.applicationKeyId || "",
|
|
2489
|
-
secretAccessKey: config.backblaze.applicationKey || "",
|
|
2490
|
-
accountId: config.backblaze.accountId || "",
|
|
2491
|
-
endpoint: `https://s3.backblazeb2.com`,
|
|
2492
|
-
cdnUrl: config.backblaze.cdnUrl,
|
|
2493
|
-
prefix: config.backblaze.prefix
|
|
2494
|
-
});
|
|
2495
|
-
case "wasabi":
|
|
2496
|
-
return createS3Storage({
|
|
2497
|
-
provider: "wasabi",
|
|
2498
|
-
bucket: config.wasabi.bucket || "",
|
|
2499
|
-
region: config.wasabi.region || "us-east-1",
|
|
2500
|
-
accessKeyId: config.wasabi.accessKeyId || "",
|
|
2501
|
-
secretAccessKey: config.wasabi.secretAccessKey || "",
|
|
2502
|
-
endpoint: `https://s3.${config.wasabi.region || "us-east-1"}.wasabisys.com`,
|
|
2503
|
-
cdnUrl: config.wasabi.cdnUrl,
|
|
2504
|
-
prefix: config.wasabi.prefix
|
|
2505
|
-
});
|
|
2506
|
-
case "ftp":
|
|
2507
|
-
case "sftp":
|
|
2508
|
-
return createFtpStorage({
|
|
2509
|
-
host: config.ftp?.host || "",
|
|
2510
|
-
port: config.ftp?.port || 21,
|
|
2511
|
-
user: config.ftp?.user || "",
|
|
2512
|
-
password: config.ftp?.password || "",
|
|
2513
|
-
secure: config.ftp?.secure || false,
|
|
2514
|
-
baseUrl: config.ftp?.baseUrl || "",
|
|
2515
|
-
prefix: config.ftp?.prefix,
|
|
2516
|
-
type: "ftp"
|
|
2517
|
-
});
|
|
2518
|
-
case "cloudinary":
|
|
2519
|
-
return createCloudinaryStorage({
|
|
2520
|
-
cloudName: config.cloudinary.cloudName || "",
|
|
2521
|
-
apiKey: config.cloudinary.apiKey || "",
|
|
2522
|
-
apiSecret: config.cloudinary.apiSecret || "",
|
|
2523
|
-
folder: config.cloudinary.folder
|
|
2524
|
-
});
|
|
2525
|
-
case "imgix":
|
|
2526
|
-
return createImgixStorage({
|
|
2527
|
-
domain: config.imgix.domain || "",
|
|
2528
|
-
signKey: config.imgix.signKey
|
|
2529
|
-
});
|
|
2530
|
-
case "local":
|
|
2531
|
-
default:
|
|
2532
|
-
return createLocalStorage({
|
|
2533
|
-
uploadDir: config.local.uploadDir || path__default.default.join(process.cwd(), "public", "uploads"),
|
|
2534
|
-
baseUrl: config.local.baseUrl || "/uploads"
|
|
2535
|
-
});
|
|
2794
|
+
const registry = getDefaultRegistry();
|
|
2795
|
+
try {
|
|
2796
|
+
return await registry.resolve(config.type, config);
|
|
2797
|
+
} catch (err) {
|
|
2798
|
+
console.warn(
|
|
2799
|
+
`[resolveProvider] ${err.message} \u2014 falling back to local storage`
|
|
2800
|
+
);
|
|
2801
|
+
return createLocalStorage({
|
|
2802
|
+
uploadDir: path__default.default.join(process.cwd(), "public", "uploads"),
|
|
2803
|
+
baseUrl: "/uploads"
|
|
2804
|
+
});
|
|
2536
2805
|
}
|
|
2537
2806
|
}
|
|
2538
2807
|
async function resolveProviderWithConfig(config) {
|
|
@@ -2543,121 +2812,18 @@ async function resolveProviderWithConfig(config) {
|
|
|
2543
2812
|
baseUrl: "/uploads"
|
|
2544
2813
|
});
|
|
2545
2814
|
}
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
});
|
|
2559
|
-
case "r2":
|
|
2560
|
-
return createS3Storage({
|
|
2561
|
-
provider: "r2",
|
|
2562
|
-
bucket: config.r2?.bucket || "",
|
|
2563
|
-
region: "auto",
|
|
2564
|
-
accessKeyId: config.r2?.accessKeyId || "",
|
|
2565
|
-
secretAccessKey: config.r2?.secretAccessKey || "",
|
|
2566
|
-
accountId: config.r2?.accountId || "",
|
|
2567
|
-
publicDevUrl: config.r2?.publicDevUrl,
|
|
2568
|
-
endpoint: `https://${config.r2?.accountId || ""}.r2.cloudflarestorage.com`,
|
|
2569
|
-
cdnUrl: config.r2?.cdnUrl,
|
|
2570
|
-
prefix: config.r2?.prefix
|
|
2571
|
-
});
|
|
2572
|
-
case "gcs":
|
|
2573
|
-
return createS3Storage({
|
|
2574
|
-
provider: "gcs",
|
|
2575
|
-
bucket: config.gcs?.bucket || "",
|
|
2576
|
-
region: config.gcs?.projectId || "auto",
|
|
2577
|
-
accessKeyId: config.gcs?.clientEmail || "",
|
|
2578
|
-
secretAccessKey: config.gcs?.privateKey || "",
|
|
2579
|
-
cdnUrl: config.gcs?.cdnUrl,
|
|
2580
|
-
prefix: config.gcs?.prefix
|
|
2581
|
-
});
|
|
2582
|
-
case "digitalocean":
|
|
2583
|
-
return createS3Storage({
|
|
2584
|
-
provider: "digitalocean",
|
|
2585
|
-
bucket: config.digitalocean?.bucket || "",
|
|
2586
|
-
region: config.digitalocean?.region || "nyc3",
|
|
2587
|
-
accessKeyId: config.digitalocean?.accessKeyId || "",
|
|
2588
|
-
secretAccessKey: config.digitalocean?.secretAccessKey || "",
|
|
2589
|
-
cdnUrl: config.digitalocean?.cdnUrl,
|
|
2590
|
-
prefix: config.digitalocean?.prefix
|
|
2591
|
-
});
|
|
2592
|
-
case "backblaze":
|
|
2593
|
-
return createS3Storage({
|
|
2594
|
-
provider: "backblaze",
|
|
2595
|
-
bucket: config.backblaze?.bucket || "",
|
|
2596
|
-
region: "auto",
|
|
2597
|
-
accessKeyId: config.backblaze?.applicationKeyId || "",
|
|
2598
|
-
secretAccessKey: config.backblaze?.applicationKey || "",
|
|
2599
|
-
cdnUrl: config.backblaze?.cdnUrl,
|
|
2600
|
-
prefix: config.backblaze?.prefix
|
|
2601
|
-
});
|
|
2602
|
-
case "wasabi":
|
|
2603
|
-
return createS3Storage({
|
|
2604
|
-
provider: "wasabi",
|
|
2605
|
-
bucket: config.wasabi?.bucket || "",
|
|
2606
|
-
region: config.wasabi?.region || "us-east-1",
|
|
2607
|
-
accessKeyId: config.wasabi?.accessKeyId || "",
|
|
2608
|
-
secretAccessKey: config.wasabi?.secretAccessKey || "",
|
|
2609
|
-
cdnUrl: config.wasabi?.cdnUrl,
|
|
2610
|
-
prefix: config.wasabi?.prefix
|
|
2611
|
-
});
|
|
2612
|
-
case "cloudinary":
|
|
2613
|
-
return createCloudinaryStorage({
|
|
2614
|
-
cloudName: config.cloudinary?.cloudName || "",
|
|
2615
|
-
apiKey: config.cloudinary?.apiKey || "",
|
|
2616
|
-
apiSecret: config.cloudinary?.apiSecret || "",
|
|
2617
|
-
folder: config.cloudinary?.folder
|
|
2618
|
-
});
|
|
2619
|
-
case "ftp":
|
|
2620
|
-
case "sftp": {
|
|
2621
|
-
const ftpConf = config.ftp || config;
|
|
2622
|
-
return createFtpStorage({
|
|
2623
|
-
type: "ftp",
|
|
2624
|
-
host: ftpConf.host || "",
|
|
2625
|
-
port: ftpConf.port || 21,
|
|
2626
|
-
user: ftpConf.user || "",
|
|
2627
|
-
password: ftpConf.password || "",
|
|
2628
|
-
secure: ftpConf.secure || false,
|
|
2629
|
-
baseUrl: ftpConf.baseUrl || "",
|
|
2630
|
-
prefix: ftpConf.prefix
|
|
2631
|
-
});
|
|
2632
|
-
}
|
|
2633
|
-
case "local":
|
|
2634
|
-
default: {
|
|
2635
|
-
const localConfig = config.local || {
|
|
2636
|
-
uploadDir: config["local.uploadDir"],
|
|
2637
|
-
baseUrl: config["local.baseUrl"]
|
|
2638
|
-
};
|
|
2639
|
-
const savedUploadDir = (localConfig?.uploadDir || "").trim();
|
|
2640
|
-
let uploadDir;
|
|
2641
|
-
if (savedUploadDir) {
|
|
2642
|
-
if (path__default.default.isAbsolute(savedUploadDir)) {
|
|
2643
|
-
uploadDir = savedUploadDir;
|
|
2644
|
-
} else if (savedUploadDir.includes("/") || savedUploadDir.includes("\\")) {
|
|
2645
|
-
uploadDir = path__default.default.resolve(process.cwd(), savedUploadDir);
|
|
2646
|
-
} else {
|
|
2647
|
-
uploadDir = path__default.default.join(process.cwd(), "public", savedUploadDir);
|
|
2648
|
-
}
|
|
2649
|
-
} else {
|
|
2650
|
-
uploadDir = path__default.default.join(process.cwd(), "public", "uploads");
|
|
2651
|
-
}
|
|
2652
|
-
const savedBaseUrl = (localConfig?.baseUrl || "").trim();
|
|
2653
|
-
let baseUrl;
|
|
2654
|
-
if (savedBaseUrl) {
|
|
2655
|
-
baseUrl = savedBaseUrl.startsWith("/") ? savedBaseUrl : `/${savedBaseUrl}`;
|
|
2656
|
-
} else {
|
|
2657
|
-
baseUrl = "/uploads";
|
|
2658
|
-
}
|
|
2659
|
-
return createLocalStorage({ uploadDir, baseUrl });
|
|
2660
|
-
}
|
|
2815
|
+
const type = config.type || "local";
|
|
2816
|
+
const registry = getDefaultRegistry();
|
|
2817
|
+
try {
|
|
2818
|
+
return await registry.resolveWithConfig(type, config);
|
|
2819
|
+
} catch (err) {
|
|
2820
|
+
console.warn(
|
|
2821
|
+
`[resolveProviderWithConfig] ${err.message} \u2014 falling back to local storage`
|
|
2822
|
+
);
|
|
2823
|
+
return createLocalStorage({
|
|
2824
|
+
uploadDir: path__default.default.join(process.cwd(), "public", "uploads"),
|
|
2825
|
+
baseUrl: "/uploads"
|
|
2826
|
+
});
|
|
2661
2827
|
}
|
|
2662
2828
|
}
|
|
2663
2829
|
async function processImage(buffer) {
|
|
@@ -2686,17 +2852,15 @@ var MediaService = class _MediaService {
|
|
|
2686
2852
|
this.db = db;
|
|
2687
2853
|
this.storage = storage2;
|
|
2688
2854
|
this.dialect = options?.dialect || "sqlite";
|
|
2689
|
-
this.genId = options?.genId ||
|
|
2855
|
+
this.genId = options?.genId || chunkRFFSZSCL_cjs.genId;
|
|
2690
2856
|
}
|
|
2691
2857
|
static async init(db, options) {
|
|
2692
2858
|
let storage2;
|
|
2693
2859
|
if (options?.storageConfig) {
|
|
2694
2860
|
storage2 = await resolveProviderWithConfig(options.storageConfig);
|
|
2695
2861
|
} else {
|
|
2696
|
-
const configService = new
|
|
2697
|
-
|
|
2698
|
-
await configService.load();
|
|
2699
|
-
}
|
|
2862
|
+
const configService = new chunkY7AQK4R4_cjs.ConfigService(db);
|
|
2863
|
+
await configService.load();
|
|
2700
2864
|
storage2 = await resolveProvider(configService);
|
|
2701
2865
|
}
|
|
2702
2866
|
const service = new _MediaService(db, storage2, options);
|
|
@@ -2898,7 +3062,7 @@ var MediaService = class _MediaService {
|
|
|
2898
3062
|
]
|
|
2899
3063
|
);
|
|
2900
3064
|
} else {
|
|
2901
|
-
const { media: mediaSchema } = await import('./media-
|
|
3065
|
+
const { media: mediaSchema } = await import('./media-TUSLVRQ6.cjs');
|
|
2902
3066
|
const mime = storageResult.mimeType;
|
|
2903
3067
|
const mediaType = mime.startsWith("image/") ? "image" : mime.startsWith("video/") ? "video" : mime.startsWith("audio/") ? "audio" : mime.startsWith("application/pdf") ? "document" : ["application/zip", "application/x-zip", "application/x-tar", "application/gzip", "application/x-7z"].includes(mime) ? "archive" : "other";
|
|
2904
3068
|
await this.db.insert(mediaSchema).values({
|
|
@@ -2960,7 +3124,7 @@ var MediaService = class _MediaService {
|
|
|
2960
3124
|
id
|
|
2961
3125
|
]);
|
|
2962
3126
|
} else {
|
|
2963
|
-
const { media: mediaSchema } = await import('./media-
|
|
3127
|
+
const { media: mediaSchema } = await import('./media-TUSLVRQ6.cjs');
|
|
2964
3128
|
const { eq } = await import('drizzle-orm');
|
|
2965
3129
|
const [row] = await this.db.select().from(mediaSchema).where(eq(mediaSchema.id, id));
|
|
2966
3130
|
if (row) item = this.rowToMedia(row);
|
|
@@ -2976,7 +3140,7 @@ var MediaService = class _MediaService {
|
|
|
2976
3140
|
if (this.dialect === "sqlite") {
|
|
2977
3141
|
await this.sqliteRun(`DELETE FROM ${this.mediaTable} WHERE id = ?`, [id]);
|
|
2978
3142
|
} else {
|
|
2979
|
-
const { media: mediaSchema } = await import('./media-
|
|
3143
|
+
const { media: mediaSchema } = await import('./media-TUSLVRQ6.cjs');
|
|
2980
3144
|
const { eq } = await import('drizzle-orm');
|
|
2981
3145
|
await this.db.delete(mediaSchema).where(eq(mediaSchema.id, id));
|
|
2982
3146
|
}
|
|
@@ -2991,7 +3155,7 @@ var MediaService = class _MediaService {
|
|
|
2991
3155
|
id
|
|
2992
3156
|
]);
|
|
2993
3157
|
} else {
|
|
2994
|
-
const { media: mediaSchema } = await import('./media-
|
|
3158
|
+
const { media: mediaSchema } = await import('./media-TUSLVRQ6.cjs');
|
|
2995
3159
|
const { eq } = await import('drizzle-orm');
|
|
2996
3160
|
const [row] = await this.db.select().from(mediaSchema).where(eq(mediaSchema.id, id));
|
|
2997
3161
|
if (row) item = this.rowToMedia(row);
|
|
@@ -3026,7 +3190,7 @@ var MediaService = class _MediaService {
|
|
|
3026
3190
|
[...vals, this.now(), id]
|
|
3027
3191
|
);
|
|
3028
3192
|
} else {
|
|
3029
|
-
const { media: mediaSchema } = await import('./media-
|
|
3193
|
+
const { media: mediaSchema } = await import('./media-TUSLVRQ6.cjs');
|
|
3030
3194
|
const { eq } = await import('drizzle-orm');
|
|
3031
3195
|
await this.db.update(mediaSchema).set({ ...updateData, updatedAt: this.now() }).where(eq(mediaSchema.id, id));
|
|
3032
3196
|
}
|
|
@@ -3045,7 +3209,7 @@ var MediaService = class _MediaService {
|
|
|
3045
3209
|
);
|
|
3046
3210
|
return row2 ? this.rowToMedia(row2) : null;
|
|
3047
3211
|
}
|
|
3048
|
-
const { media: mediaSchema } = await import('./media-
|
|
3212
|
+
const { media: mediaSchema } = await import('./media-TUSLVRQ6.cjs');
|
|
3049
3213
|
const { eq } = await import('drizzle-orm');
|
|
3050
3214
|
const [row] = await this.db.select().from(mediaSchema).where(eq(mediaSchema.id, id));
|
|
3051
3215
|
return row ? this.rowToMedia(row) : null;
|
|
@@ -3093,7 +3257,7 @@ var MediaService = class _MediaService {
|
|
|
3093
3257
|
totalPages: Math.ceil(totalDocs2 / limit)
|
|
3094
3258
|
};
|
|
3095
3259
|
}
|
|
3096
|
-
const { media: mediaSchema } = await import('./media-
|
|
3260
|
+
const { media: mediaSchema } = await import('./media-TUSLVRQ6.cjs');
|
|
3097
3261
|
const { like, or, and, asc, desc, eq, sql } = await import('drizzle-orm');
|
|
3098
3262
|
const conditions = [];
|
|
3099
3263
|
if (search) {
|
|
@@ -3172,7 +3336,7 @@ var MediaService = class _MediaService {
|
|
|
3172
3336
|
);
|
|
3173
3337
|
return row ? this.rowToMedia(row) : null;
|
|
3174
3338
|
}
|
|
3175
|
-
const { media: mediaSchema } = await import('./media-
|
|
3339
|
+
const { media: mediaSchema } = await import('./media-TUSLVRQ6.cjs');
|
|
3176
3340
|
const { eq } = await import('drizzle-orm');
|
|
3177
3341
|
const [updated] = await this.db.update(mediaSchema).set({ ...data, updatedAt: /* @__PURE__ */ new Date() }).where(eq(mediaSchema.id, id)).returning();
|
|
3178
3342
|
return updated ? this.rowToMedia(updated) : null;
|
|
@@ -3194,7 +3358,7 @@ var MediaService = class _MediaService {
|
|
|
3194
3358
|
);
|
|
3195
3359
|
}
|
|
3196
3360
|
} else {
|
|
3197
|
-
const { media: mediaSchema } = await import('./media-
|
|
3361
|
+
const { media: mediaSchema } = await import('./media-TUSLVRQ6.cjs');
|
|
3198
3362
|
const { eq } = await import('drizzle-orm');
|
|
3199
3363
|
for (const id of ids) {
|
|
3200
3364
|
await this.db.update(mediaSchema).set({ ...data, updatedAt: /* @__PURE__ */ new Date() }).where(eq(mediaSchema.id, id));
|
|
@@ -3209,7 +3373,7 @@ var MediaService = class _MediaService {
|
|
|
3209
3373
|
);
|
|
3210
3374
|
return rows.map((r) => r.path).filter((f) => f && f !== "").sort();
|
|
3211
3375
|
}
|
|
3212
|
-
const { media: mediaSchema, mediaFolders: folderSchema } = await import('./media-
|
|
3376
|
+
const { media: mediaSchema, mediaFolders: folderSchema } = await import('./media-TUSLVRQ6.cjs');
|
|
3213
3377
|
const { eq, sql } = await import('drizzle-orm');
|
|
3214
3378
|
const fromMedia = await this.db.select({ folder: mediaSchema.folder }).from(mediaSchema).groupBy(mediaSchema.folder);
|
|
3215
3379
|
const fromFolders = await this.db.select({ path: folderSchema.path }).from(folderSchema);
|
|
@@ -3229,7 +3393,7 @@ var MediaService = class _MediaService {
|
|
|
3229
3393
|
[fullPath, name, parentPath || null, now]
|
|
3230
3394
|
);
|
|
3231
3395
|
} else {
|
|
3232
|
-
const { mediaFolders: folderSchema } = await import('./media-
|
|
3396
|
+
const { mediaFolders: folderSchema } = await import('./media-TUSLVRQ6.cjs');
|
|
3233
3397
|
await this.db.insert(folderSchema).values({
|
|
3234
3398
|
path: fullPath,
|
|
3235
3399
|
name,
|
|
@@ -3250,7 +3414,7 @@ var MediaService = class _MediaService {
|
|
|
3250
3414
|
[folder, `${folder}/%`]
|
|
3251
3415
|
);
|
|
3252
3416
|
} else {
|
|
3253
|
-
const { mediaFolders: folderSchema } = await import('./media-
|
|
3417
|
+
const { mediaFolders: folderSchema } = await import('./media-TUSLVRQ6.cjs');
|
|
3254
3418
|
const { like, or, eq } = await import('drizzle-orm');
|
|
3255
3419
|
await this.db.delete(folderSchema).where(
|
|
3256
3420
|
or(
|
|
@@ -3261,13 +3425,80 @@ var MediaService = class _MediaService {
|
|
|
3261
3425
|
}
|
|
3262
3426
|
}
|
|
3263
3427
|
};
|
|
3264
|
-
|
|
3265
|
-
|
|
3428
|
+
function formatZodErrors(errors) {
|
|
3429
|
+
return errors.map((e) => `${e.path.join(".")}: ${e.message}`).join(", ");
|
|
3430
|
+
}
|
|
3431
|
+
function normalizeEmptyStrings(data, fields) {
|
|
3432
|
+
if (!data || typeof data !== "object") return;
|
|
3433
|
+
for (const field of fields) {
|
|
3434
|
+
if (!field.name || !(field.name in data)) continue;
|
|
3435
|
+
const val = data[field.name];
|
|
3436
|
+
if (val === "") {
|
|
3437
|
+
const isTextual = field.type === "text" || field.type === "textarea" || field.type === "code" || field.type === "markdown" || field.type === "email" || field.type === "password" || field.type === "color";
|
|
3438
|
+
if (!isTextual) data[field.name] = null;
|
|
3439
|
+
}
|
|
3440
|
+
if (field.type === "tabs" && field.name && Array.isArray(field.tabs) && data[field.name] && typeof data[field.name] === "object") {
|
|
3441
|
+
for (const tab of field.tabs) {
|
|
3442
|
+
if (Array.isArray(tab.fields)) normalizeEmptyStrings(data[field.name], tab.fields);
|
|
3443
|
+
}
|
|
3444
|
+
} else if ((field.type === "group" || field.type === "collapsible") && field.name && Array.isArray(field.fields) && data[field.name] && typeof data[field.name] === "object") {
|
|
3445
|
+
normalizeEmptyStrings(data[field.name], field.fields);
|
|
3446
|
+
} else if (field.type === "array" && field.name && Array.isArray(field.fields) && Array.isArray(data[field.name])) {
|
|
3447
|
+
for (const item of data[field.name]) {
|
|
3448
|
+
if (item && typeof item === "object") normalizeEmptyStrings(item, field.fields);
|
|
3449
|
+
}
|
|
3450
|
+
} else if (field.type === "blocks" && field.name && Array.isArray(field.blocks) && Array.isArray(data[field.name])) {
|
|
3451
|
+
for (const item of data[field.name]) {
|
|
3452
|
+
if (!item || typeof item !== "object") continue;
|
|
3453
|
+
const blockTypeStr = item.type || item.blockType;
|
|
3454
|
+
if (!blockTypeStr) continue;
|
|
3455
|
+
const blockDef = field.blocks.find((b) => b.slug === blockTypeStr);
|
|
3456
|
+
if (!blockDef || !Array.isArray(blockDef.fields)) continue;
|
|
3457
|
+
const target = item.data && typeof item.data === "object" ? item.data : item;
|
|
3458
|
+
normalizeEmptyStrings(target, blockDef.fields);
|
|
3459
|
+
}
|
|
3460
|
+
}
|
|
3461
|
+
}
|
|
3462
|
+
}
|
|
3463
|
+
function convertRichtextFields(fields, data) {
|
|
3464
|
+
if (!data || typeof data !== "object") return;
|
|
3465
|
+
for (const field of fields) {
|
|
3466
|
+
if (field.type === "richtext" && field.name) {
|
|
3467
|
+
const val = data[field.name];
|
|
3468
|
+
if (typeof val === "string") {
|
|
3469
|
+
data[field.name] = [{ type: "paragraph", children: [{ text: val }] }];
|
|
3470
|
+
} else if (val && typeof val === "object" && !Array.isArray(val) && val.type === "doc" && Array.isArray(val.content)) {
|
|
3471
|
+
data[field.name] = val.content;
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
3474
|
+
if (field.type === "tabs" && field.name && Array.isArray(field.tabs) && data[field.name] && typeof data[field.name] === "object") {
|
|
3475
|
+
for (const tab of field.tabs) {
|
|
3476
|
+
if (Array.isArray(tab.fields)) convertRichtextFields(tab.fields, data[field.name]);
|
|
3477
|
+
}
|
|
3478
|
+
} else if ((field.type === "group" || field.type === "collapsible") && field.name && Array.isArray(field.fields) && data[field.name] && typeof data[field.name] === "object") {
|
|
3479
|
+
convertRichtextFields(field.fields, data[field.name]);
|
|
3480
|
+
} else if (field.type === "array" && field.name && Array.isArray(field.fields) && Array.isArray(data[field.name])) {
|
|
3481
|
+
for (const item of data[field.name]) {
|
|
3482
|
+
if (item && typeof item === "object") convertRichtextFields(field.fields, item);
|
|
3483
|
+
}
|
|
3484
|
+
} else if (field.type === "blocks" && field.name && Array.isArray(field.blocks) && Array.isArray(data[field.name])) {
|
|
3485
|
+
for (const item of data[field.name]) {
|
|
3486
|
+
if (!item || typeof item !== "object") continue;
|
|
3487
|
+
const blockTypeStr = item.type || item.blockType;
|
|
3488
|
+
if (!blockTypeStr) continue;
|
|
3489
|
+
const blockDef = field.blocks.find((b) => b.slug === blockTypeStr);
|
|
3490
|
+
if (!blockDef || !Array.isArray(blockDef.fields)) continue;
|
|
3491
|
+
const target = item.data && typeof item.data === "object" ? item.data : item;
|
|
3492
|
+
convertRichtextFields(blockDef.fields, target);
|
|
3493
|
+
}
|
|
3494
|
+
}
|
|
3495
|
+
}
|
|
3496
|
+
}
|
|
3266
3497
|
var COLLECTION_EVENT_MAP = {
|
|
3267
3498
|
_media: {
|
|
3268
|
-
create:
|
|
3269
|
-
update:
|
|
3270
|
-
delete:
|
|
3499
|
+
create: chunkQFLB4EIJ_cjs.WEBHOOK_EVENTS.MEDIA_UPLOAD,
|
|
3500
|
+
update: chunkQFLB4EIJ_cjs.WEBHOOK_EVENTS.MEDIA_UPLOAD,
|
|
3501
|
+
delete: chunkQFLB4EIJ_cjs.WEBHOOK_EVENTS.MEDIA_DELETE
|
|
3271
3502
|
}
|
|
3272
3503
|
};
|
|
3273
3504
|
function getWebhookEvent(collection, operation) {
|
|
@@ -3326,7 +3557,7 @@ async function checkCollectionAccess(collection, operation, req, ctxUser, ctxTen
|
|
|
3326
3557
|
};
|
|
3327
3558
|
const isDefaultAllowed = accessLevels[defaultCollectionAccess] || false;
|
|
3328
3559
|
if (accessRule) {
|
|
3329
|
-
const allowed = await
|
|
3560
|
+
const allowed = await chunk4M7X5HAB_cjs.evaluateAccess(accessRule, {
|
|
3330
3561
|
req,
|
|
3331
3562
|
user: ctxUser,
|
|
3332
3563
|
tenantID: ctxTenantID
|
|
@@ -3348,7 +3579,7 @@ async function checkCollectionAccess(collection, operation, req, ctxUser, ctxTen
|
|
|
3348
3579
|
const resource = collection.slug;
|
|
3349
3580
|
const action = operation === "read" ? "read" : operation === "create" ? "create" : "update";
|
|
3350
3581
|
const permission = `${resource}:${action}`;
|
|
3351
|
-
if (!
|
|
3582
|
+
if (!chunk4M7X5HAB_cjs.hasApiKeyPermission(apiKeyContext.permissions, permission) && !chunk4M7X5HAB_cjs.hasApiKeyPermission(apiKeyContext.permissions, `${resource}:admin`)) {
|
|
3352
3583
|
return {
|
|
3353
3584
|
allowed: false,
|
|
3354
3585
|
error: `Missing permission: ${permission}`,
|
|
@@ -3356,20 +3587,20 @@ async function checkCollectionAccess(collection, operation, req, ctxUser, ctxTen
|
|
|
3356
3587
|
};
|
|
3357
3588
|
}
|
|
3358
3589
|
}
|
|
3359
|
-
if (ctxUser) {
|
|
3590
|
+
if (ctxUser && !(apiKeyContext?.permissions?.length > 0)) {
|
|
3360
3591
|
const resource = collection.slug;
|
|
3361
3592
|
const action = operation === "read" ? "read" : operation === "create" ? "create" : operation === "update" ? "update" : "delete";
|
|
3362
3593
|
const permission = `${resource}:${action}`;
|
|
3363
3594
|
let rbacAllowed = false;
|
|
3364
3595
|
if (ctxUser.role) {
|
|
3365
|
-
const userHasPermission =
|
|
3596
|
+
const userHasPermission = chunkNKPKR5BW_cjs.hasPermission(
|
|
3366
3597
|
{ id: ctxUser.id, email: ctxUser.email, role: ctxUser.role },
|
|
3367
3598
|
permission
|
|
3368
3599
|
);
|
|
3369
3600
|
if (userHasPermission) {
|
|
3370
3601
|
rbacAllowed = true;
|
|
3371
3602
|
} else {
|
|
3372
|
-
const adminPermission =
|
|
3603
|
+
const adminPermission = chunkNKPKR5BW_cjs.hasPermission(
|
|
3373
3604
|
{ id: ctxUser.id, email: ctxUser.email, role: ctxUser.role },
|
|
3374
3605
|
`${resource}:admin`
|
|
3375
3606
|
);
|
|
@@ -3389,7 +3620,7 @@ async function checkCollectionAccess(collection, operation, req, ctxUser, ctxTen
|
|
|
3389
3620
|
async function checkGlobalAccess(global, operation, req, ctxUser, ctxTenantID, enablePublicAccess = true) {
|
|
3390
3621
|
const accessRule = global.access?.[operation];
|
|
3391
3622
|
if (accessRule) {
|
|
3392
|
-
const allowed = await
|
|
3623
|
+
const allowed = await chunk4M7X5HAB_cjs.evaluateAccess(accessRule, {
|
|
3393
3624
|
req,
|
|
3394
3625
|
user: ctxUser,
|
|
3395
3626
|
tenantID: ctxTenantID
|
|
@@ -3415,42 +3646,41 @@ async function checkGlobalAccess(global, operation, req, ctxUser, ctxTenantID, e
|
|
|
3415
3646
|
return { allowed: true };
|
|
3416
3647
|
}
|
|
3417
3648
|
async function resolveAuthContext(req, authMw, staticUser, staticTenantID) {
|
|
3418
|
-
if (
|
|
3419
|
-
return {
|
|
3420
|
-
user: staticUser,
|
|
3421
|
-
tenantID: staticTenantID,
|
|
3422
|
-
apiKeyContext: void 0
|
|
3423
|
-
};
|
|
3424
|
-
}
|
|
3425
|
-
let result;
|
|
3426
|
-
try {
|
|
3427
|
-
result = await authMw(req);
|
|
3428
|
-
} catch (err) {
|
|
3649
|
+
if (staticUser) {
|
|
3429
3650
|
return {
|
|
3430
3651
|
user: staticUser,
|
|
3431
3652
|
tenantID: staticTenantID,
|
|
3432
|
-
apiKeyContext: void 0
|
|
3653
|
+
apiKeyContext: void 0,
|
|
3654
|
+
authType: "static"
|
|
3433
3655
|
};
|
|
3434
3656
|
}
|
|
3435
|
-
if (
|
|
3436
|
-
|
|
3657
|
+
if (authMw) {
|
|
3658
|
+
const res = await authMw(req);
|
|
3659
|
+
if (res.status === 200 && res.user) {
|
|
3660
|
+
return {
|
|
3661
|
+
user: res.user,
|
|
3662
|
+
tenantID: res.tenantContext?.tenantId,
|
|
3663
|
+
apiKeyContext: res.apiKeyContext,
|
|
3664
|
+
authType: res.authType
|
|
3665
|
+
};
|
|
3666
|
+
}
|
|
3437
3667
|
}
|
|
3438
3668
|
return {
|
|
3439
|
-
user:
|
|
3440
|
-
tenantID:
|
|
3441
|
-
apiKeyContext:
|
|
3442
|
-
authType:
|
|
3669
|
+
user: void 0,
|
|
3670
|
+
tenantID: void 0,
|
|
3671
|
+
apiKeyContext: void 0,
|
|
3672
|
+
authType: void 0
|
|
3443
3673
|
};
|
|
3444
3674
|
}
|
|
3445
3675
|
function createDefaultAuthAdapter(db, rootDir) {
|
|
3446
|
-
if (db instanceof
|
|
3447
|
-
return new
|
|
3676
|
+
if (db instanceof chunkRFFSZSCL_cjs.DrizzleAdapter && db.dialect === "postgres") {
|
|
3677
|
+
return new chunk7OS7TX2Q_cjs.PostgresAuthAdapter({ db: db.client });
|
|
3448
3678
|
}
|
|
3449
|
-
if (db instanceof
|
|
3450
|
-
return new
|
|
3679
|
+
if (db instanceof chunkQ23GAMLE_cjs.MongoDBAdapter) {
|
|
3680
|
+
return new chunkGXFOGU7N_cjs.MongoDBAuthAdapter({ db: db.db });
|
|
3451
3681
|
}
|
|
3452
3682
|
const defaultAuthDbPath = path.resolve(rootDir, "data", "kyro.db");
|
|
3453
|
-
return new
|
|
3683
|
+
return new chunkIDVRRRAK_cjs.SQLiteAuthAdapter({
|
|
3454
3684
|
path: process.env.KYRO_AUTH_DB_PATH || defaultAuthDbPath
|
|
3455
3685
|
});
|
|
3456
3686
|
}
|
|
@@ -3553,6 +3783,13 @@ function createHonoApp(options) {
|
|
|
3553
3783
|
baseUrl: process.env.KYRO_BASE_URL || "http://localhost:4321",
|
|
3554
3784
|
rateLimiter
|
|
3555
3785
|
});
|
|
3786
|
+
chunkY7AQK4R4_cjs.EmailTransport.fromConfig(db).then((transport) => {
|
|
3787
|
+
if (transport) {
|
|
3788
|
+
authRoutes.email = transport;
|
|
3789
|
+
}
|
|
3790
|
+
}).catch((err) => {
|
|
3791
|
+
console.error("[Email] Failed to initialize transport from config:", err);
|
|
3792
|
+
});
|
|
3556
3793
|
app.post("/api/auth/login", async (c) => authRoutes.login(c.req.raw));
|
|
3557
3794
|
app.post("/api/auth/register", async (c) => authRoutes.register(c.req.raw));
|
|
3558
3795
|
app.post("/api/auth/logout", async (c) => authRoutes.logout(c.req.raw));
|
|
@@ -3563,69 +3800,6 @@ function createHonoApp(options) {
|
|
|
3563
3800
|
app.delete("/api/auth/sessions", async (c) => authRoutes.revokeOtherSessions(c.req.raw));
|
|
3564
3801
|
app.delete("/api/auth/sessions/:id", async (c) => authRoutes.revokeSession(c.req.raw, c.req.param("id")));
|
|
3565
3802
|
app.put("/api/auth/sessions/:id/name", async (c) => authRoutes.renameSession(c.req.raw, c.req.param("id")));
|
|
3566
|
-
app.post("/api/graphql", async (c) => {
|
|
3567
|
-
try {
|
|
3568
|
-
const req = c.req.raw;
|
|
3569
|
-
const apiKeyRaw = chunkIBG6V56E_cjs.extractApiKeyFromRequest(req);
|
|
3570
|
-
if (apiKeyRaw && db) {
|
|
3571
|
-
const apiKeyResult = await chunkIBG6V56E_cjs.validateApiKey(apiKeyRaw, db);
|
|
3572
|
-
if (!apiKeyResult.valid) {
|
|
3573
|
-
return c.json({ errors: [{ message: apiKeyResult.error || "Invalid API key" }] }, 401);
|
|
3574
|
-
}
|
|
3575
|
-
const apiKeyId = apiKeyResult.apiKeyId || "";
|
|
3576
|
-
await sessionAuthAdapter?.createAuditLog({
|
|
3577
|
-
action: "api_key_request",
|
|
3578
|
-
userId: apiKeyResult.userId || "",
|
|
3579
|
-
resource: "api_key",
|
|
3580
|
-
resourceId: apiKeyId,
|
|
3581
|
-
success: true,
|
|
3582
|
-
metadata: {
|
|
3583
|
-
endpoint: "/api/graphql",
|
|
3584
|
-
method: "POST",
|
|
3585
|
-
ip: extractIp(req)
|
|
3586
|
-
}
|
|
3587
|
-
});
|
|
3588
|
-
}
|
|
3589
|
-
const body = await req.json().catch(() => ({}));
|
|
3590
|
-
const { query, variables } = body;
|
|
3591
|
-
if (!query) {
|
|
3592
|
-
return c.json({ errors: [{ message: "No query provided" }] }, 400);
|
|
3593
|
-
}
|
|
3594
|
-
let gqlUser;
|
|
3595
|
-
let apiKeyCtx;
|
|
3596
|
-
if (apiKeyRaw && db) {
|
|
3597
|
-
const apiKeyResult = await chunkIBG6V56E_cjs.validateApiKey(apiKeyRaw, db);
|
|
3598
|
-
if (apiKeyResult.valid && apiKeyResult.user) {
|
|
3599
|
-
gqlUser = apiKeyResult.user;
|
|
3600
|
-
apiKeyCtx = chunkIBG6V56E_cjs.createApiKeyContext(apiKeyResult);
|
|
3601
|
-
}
|
|
3602
|
-
}
|
|
3603
|
-
const schema = chunkVJT6P4N6_cjs.buildGraphQLSchema({
|
|
3604
|
-
registry,
|
|
3605
|
-
db,
|
|
3606
|
-
user: gqlUser,
|
|
3607
|
-
req,
|
|
3608
|
-
settings
|
|
3609
|
-
});
|
|
3610
|
-
const document = graphql.parse(query);
|
|
3611
|
-
const result = await graphql.execute({
|
|
3612
|
-
schema,
|
|
3613
|
-
document,
|
|
3614
|
-
variableValues: variables,
|
|
3615
|
-
contextValue: { user: gqlUser, apiKeyContext: apiKeyCtx, req, db }
|
|
3616
|
-
});
|
|
3617
|
-
return c.json(result);
|
|
3618
|
-
} catch (error) {
|
|
3619
|
-
if (error.message?.includes("GraphQL is disabled")) {
|
|
3620
|
-
return c.json({ errors: [{ message: "GraphQL API is disabled" }] }, 403);
|
|
3621
|
-
}
|
|
3622
|
-
if (error instanceof SyntaxError) {
|
|
3623
|
-
return c.json({ errors: [{ message: "Invalid request body" }] }, 400);
|
|
3624
|
-
}
|
|
3625
|
-
console.error("[GraphQL] execution error:", error);
|
|
3626
|
-
return c.json({ errors: [{ message: error.message || "GraphQL execution failed" }] }, 500);
|
|
3627
|
-
}
|
|
3628
|
-
});
|
|
3629
3803
|
app.get("/api/auth/access", async (c) => {
|
|
3630
3804
|
try {
|
|
3631
3805
|
const { user: ctxUser, tenantID: ctxTenantID } = await resolveAuthContext(
|
|
@@ -3710,7 +3884,13 @@ function createHonoApp(options) {
|
|
|
3710
3884
|
return c.json({ error: error.message }, 500);
|
|
3711
3885
|
}
|
|
3712
3886
|
});
|
|
3713
|
-
const
|
|
3887
|
+
const usersCollection2 = typeof registry.hasCollection === "function" && registry.hasCollection("users") ? registry.getCollection("users") : (() => {
|
|
3888
|
+
try {
|
|
3889
|
+
return registry.getCollection("users");
|
|
3890
|
+
} catch {
|
|
3891
|
+
return chunkKC2GDBLS_cjs.usersCollection;
|
|
3892
|
+
}
|
|
3893
|
+
})();
|
|
3714
3894
|
app.get("/api/users", async (c) => {
|
|
3715
3895
|
try {
|
|
3716
3896
|
const { user: ctxUser, tenantID: ctxTenantID } = await resolveAuthContext(
|
|
@@ -3720,7 +3900,7 @@ function createHonoApp(options) {
|
|
|
3720
3900
|
tenantID
|
|
3721
3901
|
);
|
|
3722
3902
|
const access = await checkCollectionAccess(
|
|
3723
|
-
|
|
3903
|
+
usersCollection2,
|
|
3724
3904
|
"read",
|
|
3725
3905
|
c.req.raw,
|
|
3726
3906
|
ctxUser,
|
|
@@ -3765,19 +3945,6 @@ function createHonoApp(options) {
|
|
|
3765
3945
|
user,
|
|
3766
3946
|
tenantID
|
|
3767
3947
|
);
|
|
3768
|
-
const access = await checkCollectionAccess(
|
|
3769
|
-
usersCollection,
|
|
3770
|
-
"read",
|
|
3771
|
-
c.req.raw,
|
|
3772
|
-
ctxUser,
|
|
3773
|
-
ctxTenantID,
|
|
3774
|
-
void 0,
|
|
3775
|
-
enablePublicAccess,
|
|
3776
|
-
defaultCollectionAccess
|
|
3777
|
-
);
|
|
3778
|
-
if (!access.allowed) {
|
|
3779
|
-
return c.json({ error: access.error }, access.status || 403);
|
|
3780
|
-
}
|
|
3781
3948
|
const id = c.req.param("id");
|
|
3782
3949
|
const found = await sessionAuthAdapter.findUserById(id);
|
|
3783
3950
|
if (!found) {
|
|
@@ -3797,7 +3964,7 @@ function createHonoApp(options) {
|
|
|
3797
3964
|
tenantID
|
|
3798
3965
|
);
|
|
3799
3966
|
const access = await checkCollectionAccess(
|
|
3800
|
-
|
|
3967
|
+
usersCollection2,
|
|
3801
3968
|
"create",
|
|
3802
3969
|
c.req.raw,
|
|
3803
3970
|
ctxUser,
|
|
@@ -3822,8 +3989,18 @@ function createHonoApp(options) {
|
|
|
3822
3989
|
password: body.password,
|
|
3823
3990
|
name: body.name,
|
|
3824
3991
|
role: body.role || "customer",
|
|
3992
|
+
avatar: body.avatar,
|
|
3825
3993
|
tenantId: body.tenantId
|
|
3826
3994
|
});
|
|
3995
|
+
if (ctxUser) {
|
|
3996
|
+
sessionAuthAdapter?.createAuditLog({
|
|
3997
|
+
action: "user_create",
|
|
3998
|
+
userId: ctxUser.id,
|
|
3999
|
+
resource: "users",
|
|
4000
|
+
resourceId: created.id,
|
|
4001
|
+
success: true
|
|
4002
|
+
});
|
|
4003
|
+
}
|
|
3827
4004
|
return c.json(
|
|
3828
4005
|
{ data: created, message: "User created successfully" },
|
|
3829
4006
|
201
|
|
@@ -3841,7 +4018,7 @@ function createHonoApp(options) {
|
|
|
3841
4018
|
tenantID
|
|
3842
4019
|
);
|
|
3843
4020
|
const access = await checkCollectionAccess(
|
|
3844
|
-
|
|
4021
|
+
usersCollection2,
|
|
3845
4022
|
"update",
|
|
3846
4023
|
c.req.raw,
|
|
3847
4024
|
ctxUser,
|
|
@@ -3863,6 +4040,9 @@ function createHonoApp(options) {
|
|
|
3863
4040
|
if (body.name !== void 0) updateData.name = body.name;
|
|
3864
4041
|
if (body.email !== void 0) updateData.email = body.email;
|
|
3865
4042
|
if (body.role !== void 0) updateData.role = body.role;
|
|
4043
|
+
if (body.avatar !== void 0) {
|
|
4044
|
+
updateData.avatar = typeof body.avatar === "object" && body.avatar !== null ? body.avatar.id || String(body.avatar) : body.avatar;
|
|
4045
|
+
}
|
|
3866
4046
|
if (body.tenantId !== void 0) updateData.tenantId = body.tenantId;
|
|
3867
4047
|
if (body.emailVerified !== void 0)
|
|
3868
4048
|
updateData.emailVerified = body.emailVerified;
|
|
@@ -3874,6 +4054,25 @@ function createHonoApp(options) {
|
|
|
3874
4054
|
if (!updated) {
|
|
3875
4055
|
return c.json({ error: "User update failed" }, 500);
|
|
3876
4056
|
}
|
|
4057
|
+
if (ctxUser) {
|
|
4058
|
+
sessionAuthAdapter?.createAuditLog({
|
|
4059
|
+
action: "user_update",
|
|
4060
|
+
userId: ctxUser.id,
|
|
4061
|
+
resource: "users",
|
|
4062
|
+
resourceId: id,
|
|
4063
|
+
success: true
|
|
4064
|
+
});
|
|
4065
|
+
if (body.role && existing.role !== body.role) {
|
|
4066
|
+
sessionAuthAdapter?.createAuditLog({
|
|
4067
|
+
action: "role_change",
|
|
4068
|
+
userId: ctxUser.id,
|
|
4069
|
+
resource: "users",
|
|
4070
|
+
resourceId: id,
|
|
4071
|
+
success: true,
|
|
4072
|
+
metadata: { oldRole: existing.role, newRole: body.role }
|
|
4073
|
+
});
|
|
4074
|
+
}
|
|
4075
|
+
}
|
|
3877
4076
|
return c.json({ data: updated, message: "User updated successfully" });
|
|
3878
4077
|
} catch (error) {
|
|
3879
4078
|
return c.json({ error: error.message }, 500);
|
|
@@ -3888,7 +4087,7 @@ function createHonoApp(options) {
|
|
|
3888
4087
|
tenantID
|
|
3889
4088
|
);
|
|
3890
4089
|
const access = await checkCollectionAccess(
|
|
3891
|
-
|
|
4090
|
+
usersCollection2,
|
|
3892
4091
|
"delete",
|
|
3893
4092
|
c.req.raw,
|
|
3894
4093
|
ctxUser,
|
|
@@ -3912,6 +4111,15 @@ function createHonoApp(options) {
|
|
|
3912
4111
|
if (!deleted) {
|
|
3913
4112
|
return c.json({ error: "User deletion failed" }, 500);
|
|
3914
4113
|
}
|
|
4114
|
+
if (ctxUser) {
|
|
4115
|
+
sessionAuthAdapter?.createAuditLog({
|
|
4116
|
+
action: "user_delete",
|
|
4117
|
+
userId: ctxUser.id,
|
|
4118
|
+
resource: "users",
|
|
4119
|
+
resourceId: id,
|
|
4120
|
+
success: true
|
|
4121
|
+
});
|
|
4122
|
+
}
|
|
3915
4123
|
return c.json({ data: existing, message: "User deleted successfully" });
|
|
3916
4124
|
} catch (error) {
|
|
3917
4125
|
return c.json({ error: error.message }, 500);
|
|
@@ -3980,8 +4188,8 @@ function createHonoApp(options) {
|
|
|
3980
4188
|
}
|
|
3981
4189
|
if (!mediaService) {
|
|
3982
4190
|
try {
|
|
3983
|
-
const dialect = db instanceof
|
|
3984
|
-
const mediaDb = dialect === "postgres" && db instanceof
|
|
4191
|
+
const dialect = db instanceof chunkRFFSZSCL_cjs.DrizzleAdapter ? db.dialect : "sqlite";
|
|
4192
|
+
const mediaDb = dialect === "postgres" && db instanceof chunkRFFSZSCL_cjs.DrizzleAdapter ? db.client : db;
|
|
3985
4193
|
mediaService = await MediaService.init(mediaDb, { dialect });
|
|
3986
4194
|
} catch (error) {
|
|
3987
4195
|
console.error("[getMedia] Init error:", error);
|
|
@@ -4068,11 +4276,11 @@ function createHonoApp(options) {
|
|
|
4068
4276
|
const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4069
4277
|
if (!ctxUser) return c.json({ error: "Authentication required" }, 401);
|
|
4070
4278
|
const service = await getMedia();
|
|
4071
|
-
const
|
|
4072
|
-
if (!
|
|
4279
|
+
const path3 = c.req.query("path");
|
|
4280
|
+
if (!path3) {
|
|
4073
4281
|
return c.json({ error: "Path is required" }, 400);
|
|
4074
4282
|
}
|
|
4075
|
-
await service.deleteFolder(
|
|
4283
|
+
await service.deleteFolder(path3);
|
|
4076
4284
|
return c.json({ message: "Folder deleted" });
|
|
4077
4285
|
} catch (error) {
|
|
4078
4286
|
console.error("[Media] delete folder error:", error);
|
|
@@ -4225,6 +4433,119 @@ function createHonoApp(options) {
|
|
|
4225
4433
|
return c.json({ error: error.message }, 500);
|
|
4226
4434
|
}
|
|
4227
4435
|
});
|
|
4436
|
+
app.get("/api/plugins", async (c) => {
|
|
4437
|
+
try {
|
|
4438
|
+
const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4439
|
+
if (!ctxUser) return c.json({ error: "Authentication required" }, 401);
|
|
4440
|
+
const plugins = registry.getPlugins();
|
|
4441
|
+
const storageRegistry = registry.storageProviders;
|
|
4442
|
+
const pluginList = await Promise.all(
|
|
4443
|
+
plugins.map(async (p) => {
|
|
4444
|
+
let enabled = true;
|
|
4445
|
+
const pluginName = p.name;
|
|
4446
|
+
let states = {};
|
|
4447
|
+
try {
|
|
4448
|
+
const doc = await db.findOne({
|
|
4449
|
+
collection: "_globals_plugin-settings",
|
|
4450
|
+
where: {}
|
|
4451
|
+
});
|
|
4452
|
+
if (doc && doc.states) states = doc.states;
|
|
4453
|
+
} catch {
|
|
4454
|
+
}
|
|
4455
|
+
if (states[pluginName] !== void 0) {
|
|
4456
|
+
enabled = states[pluginName];
|
|
4457
|
+
}
|
|
4458
|
+
storageRegistry.setPluginEnabled(pluginName, enabled);
|
|
4459
|
+
return {
|
|
4460
|
+
id: pluginName,
|
|
4461
|
+
name: pluginName,
|
|
4462
|
+
version: p.version || "1.0.0",
|
|
4463
|
+
description: p.description || "",
|
|
4464
|
+
enabled,
|
|
4465
|
+
status: enabled ? "active" : "disabled"
|
|
4466
|
+
};
|
|
4467
|
+
})
|
|
4468
|
+
);
|
|
4469
|
+
return c.json(pluginList);
|
|
4470
|
+
} catch (error) {
|
|
4471
|
+
console.error("[Plugins] list error:", error);
|
|
4472
|
+
return c.json({ error: error.message }, 500);
|
|
4473
|
+
}
|
|
4474
|
+
});
|
|
4475
|
+
app.put("/api/plugins/:name/toggle", async (c) => {
|
|
4476
|
+
try {
|
|
4477
|
+
const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4478
|
+
if (!ctxUser) return c.json({ error: "Authentication required" }, 401);
|
|
4479
|
+
const pluginName = c.req.param("name");
|
|
4480
|
+
const storageRegistry = registry.storageProviders;
|
|
4481
|
+
const currentEnabled = storageRegistry.isPluginEnabled(pluginName);
|
|
4482
|
+
const newEnabled = !currentEnabled;
|
|
4483
|
+
const affectedProviders = [];
|
|
4484
|
+
if (!newEnabled) {
|
|
4485
|
+
for (const p of storageRegistry.getAll()) {
|
|
4486
|
+
if (p.pluginName === pluginName) {
|
|
4487
|
+
affectedProviders.push(p.type);
|
|
4488
|
+
}
|
|
4489
|
+
}
|
|
4490
|
+
}
|
|
4491
|
+
const force = c.req.query("force") === "1";
|
|
4492
|
+
if (!force && !newEnabled && affectedProviders.length > 0) {
|
|
4493
|
+
let activeProvider = "local";
|
|
4494
|
+
try {
|
|
4495
|
+
const row = db?.prepare?.(`SELECT provider FROM "_globals_storage-settings" LIMIT 1`)?.get();
|
|
4496
|
+
if (row?.provider) activeProvider = row.provider;
|
|
4497
|
+
} catch {
|
|
4498
|
+
try {
|
|
4499
|
+
const result = await db?.findOne?.({
|
|
4500
|
+
collection: "_globals_storage-settings",
|
|
4501
|
+
where: {}
|
|
4502
|
+
});
|
|
4503
|
+
if (result?.provider) activeProvider = result.provider;
|
|
4504
|
+
} catch {
|
|
4505
|
+
}
|
|
4506
|
+
}
|
|
4507
|
+
if (affectedProviders.includes(activeProvider)) {
|
|
4508
|
+
return c.json({
|
|
4509
|
+
error: `Cannot disable "${pluginName}" \u2014 storage provider "${activeProvider}" is currently active. Switch to Local storage first.`,
|
|
4510
|
+
requiresAction: true,
|
|
4511
|
+
activeProvider
|
|
4512
|
+
}, 409);
|
|
4513
|
+
}
|
|
4514
|
+
}
|
|
4515
|
+
try {
|
|
4516
|
+
let states = {};
|
|
4517
|
+
let docId = "global";
|
|
4518
|
+
const doc = await db.findOne({
|
|
4519
|
+
collection: "_globals_plugin-settings",
|
|
4520
|
+
where: {}
|
|
4521
|
+
});
|
|
4522
|
+
if (doc) {
|
|
4523
|
+
states = doc.states || {};
|
|
4524
|
+
docId = doc.id;
|
|
4525
|
+
}
|
|
4526
|
+
states[pluginName] = newEnabled;
|
|
4527
|
+
if (doc) {
|
|
4528
|
+
await db.update({
|
|
4529
|
+
collection: "_globals_plugin-settings",
|
|
4530
|
+
id: docId,
|
|
4531
|
+
data: { states }
|
|
4532
|
+
});
|
|
4533
|
+
} else {
|
|
4534
|
+
await db.create({
|
|
4535
|
+
collection: "_globals_plugin-settings",
|
|
4536
|
+
data: { states, id: "global" }
|
|
4537
|
+
});
|
|
4538
|
+
}
|
|
4539
|
+
} catch (e) {
|
|
4540
|
+
console.warn(`[Plugins] Could not persist state for "${pluginName}":`, e);
|
|
4541
|
+
}
|
|
4542
|
+
storageRegistry.setPluginEnabled(pluginName, newEnabled);
|
|
4543
|
+
return c.json({ name: pluginName, enabled: newEnabled });
|
|
4544
|
+
} catch (error) {
|
|
4545
|
+
console.error("[Plugins] toggle error:", error);
|
|
4546
|
+
return c.json({ error: error.message }, 500);
|
|
4547
|
+
}
|
|
4548
|
+
});
|
|
4228
4549
|
app.get("/api/health", (c) => {
|
|
4229
4550
|
return c.json({
|
|
4230
4551
|
status: "ok",
|
|
@@ -4233,6 +4554,102 @@ function createHonoApp(options) {
|
|
|
4233
4554
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4234
4555
|
});
|
|
4235
4556
|
});
|
|
4557
|
+
app.get("/api/kyro/schema", async (c) => {
|
|
4558
|
+
const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4559
|
+
if (!ctxUser) return c.json({ error: "Authentication required" }, 401);
|
|
4560
|
+
const extractFields = (fields) => fields.map((f) => {
|
|
4561
|
+
const meta = {
|
|
4562
|
+
name: f.name,
|
|
4563
|
+
type: f.type,
|
|
4564
|
+
label: f.label,
|
|
4565
|
+
required: f.required ?? false,
|
|
4566
|
+
unique: f.unique ?? false,
|
|
4567
|
+
indexed: f.indexed ?? false,
|
|
4568
|
+
defaultValue: f.defaultValue,
|
|
4569
|
+
admin: f.admin ? { ...f.admin } : void 0
|
|
4570
|
+
};
|
|
4571
|
+
if (f.minLength !== void 0) meta.minLength = f.minLength;
|
|
4572
|
+
if (f.maxLength !== void 0) meta.maxLength = f.maxLength;
|
|
4573
|
+
if (f.pattern !== void 0) meta.pattern = f.pattern;
|
|
4574
|
+
if (f.variant !== void 0) meta.variant = f.variant;
|
|
4575
|
+
if (f.hasMany !== void 0) meta.hasMany = f.hasMany;
|
|
4576
|
+
if (f.min !== void 0) meta.min = f.min;
|
|
4577
|
+
if (f.max !== void 0) meta.max = f.max;
|
|
4578
|
+
if (f.step !== void 0) meta.step = f.step;
|
|
4579
|
+
if (f.integer !== void 0) meta.integer = f.integer;
|
|
4580
|
+
if (f.options) meta.options = f.options;
|
|
4581
|
+
if (f.relationTo) meta.relationTo = f.relationTo;
|
|
4582
|
+
if (f.maxDepth !== void 0) meta.maxDepth = f.maxDepth;
|
|
4583
|
+
if (f.minRows !== void 0) meta.minRows = f.minRows;
|
|
4584
|
+
if (f.maxRows !== void 0) meta.maxRows = f.maxRows;
|
|
4585
|
+
if (f.localized !== void 0) meta.localized = f.localized;
|
|
4586
|
+
if (f.language) meta.language = f.language;
|
|
4587
|
+
if (f.format) meta.format = f.format;
|
|
4588
|
+
if (f.allowedTypes) meta.allowedTypes = f.allowedTypes;
|
|
4589
|
+
if (f.maxSize) meta.maxSize = f.maxSize;
|
|
4590
|
+
if (f.type === "group" || f.type === "row" || f.type === "collapsible" && f.fields) {
|
|
4591
|
+
meta.fields = extractFields(f.fields);
|
|
4592
|
+
}
|
|
4593
|
+
if (f.type === "array" && f.fields) {
|
|
4594
|
+
meta.fields = extractFields(f.fields);
|
|
4595
|
+
}
|
|
4596
|
+
if (f.type === "blocks" && f.blocks) {
|
|
4597
|
+
meta.blocks = f.blocks.map((b) => ({
|
|
4598
|
+
slug: b.slug,
|
|
4599
|
+
label: b.label,
|
|
4600
|
+
fields: extractFields(b.fields)
|
|
4601
|
+
}));
|
|
4602
|
+
}
|
|
4603
|
+
if (f.type === "tabs" && f.tabs) {
|
|
4604
|
+
meta.tabs = f.tabs.map((t) => ({
|
|
4605
|
+
label: t.label,
|
|
4606
|
+
name: t.name,
|
|
4607
|
+
fields: extractFields(t.fields)
|
|
4608
|
+
}));
|
|
4609
|
+
}
|
|
4610
|
+
return meta;
|
|
4611
|
+
});
|
|
4612
|
+
const data = { collections: {}, globals: {} };
|
|
4613
|
+
for (const col of registry.getCollections()) {
|
|
4614
|
+
const slug = col.slug;
|
|
4615
|
+
try {
|
|
4616
|
+
data.collections[slug] = {
|
|
4617
|
+
slug,
|
|
4618
|
+
label: col.label || slug,
|
|
4619
|
+
fields: extractFields(col.fields),
|
|
4620
|
+
jsonSchema: zodToJsonSchema.zodToJsonSchema(registry.getZodSchema(slug), { target: "openApi3" }),
|
|
4621
|
+
createSchema: zodToJsonSchema.zodToJsonSchema(registry.getCreateZodSchema(slug), { target: "openApi3" }),
|
|
4622
|
+
updateSchema: zodToJsonSchema.zodToJsonSchema(registry.getUpdateZodSchema(slug), { target: "openApi3" }),
|
|
4623
|
+
procedures: {
|
|
4624
|
+
find: { collection: slug, where: "Record<string,any>", sort: "string", limit: "number", page: "number", depth: "number", select: "string[]", draft: "boolean" },
|
|
4625
|
+
findByID: { collection: slug, id: "string", depth: "number", select: "string[]", draft: "boolean" },
|
|
4626
|
+
create: { collection: slug, data: "Record<string,any>", depth: "number", select: "string[]" },
|
|
4627
|
+
update: { collection: slug, id: "string", data: "Record<string,any>", depth: "number", select: "string[]", baseUpdatedAt: "string" },
|
|
4628
|
+
delete: { collection: slug, id: "string" },
|
|
4629
|
+
count: { collection: slug, where: "Record<string,any>" }
|
|
4630
|
+
}
|
|
4631
|
+
};
|
|
4632
|
+
} catch {
|
|
4633
|
+
}
|
|
4634
|
+
}
|
|
4635
|
+
for (const global of registry.getGlobals()) {
|
|
4636
|
+
const slug = global.slug;
|
|
4637
|
+
try {
|
|
4638
|
+
data.globals[slug] = {
|
|
4639
|
+
slug,
|
|
4640
|
+
label: global.label || slug,
|
|
4641
|
+
fields: extractFields(global.fields),
|
|
4642
|
+
jsonSchema: zodToJsonSchema.zodToJsonSchema(registry.getZodSchema(slug), { target: "openApi3" }),
|
|
4643
|
+
procedures: {
|
|
4644
|
+
get: {},
|
|
4645
|
+
update: { data: "Record<string,any>" }
|
|
4646
|
+
}
|
|
4647
|
+
};
|
|
4648
|
+
} catch {
|
|
4649
|
+
}
|
|
4650
|
+
}
|
|
4651
|
+
return c.json(data);
|
|
4652
|
+
});
|
|
4236
4653
|
app.get("/api/collections", async (c) => {
|
|
4237
4654
|
const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4238
4655
|
if (!ctxUser) return c.json({ error: "Authentication required" }, 401);
|
|
@@ -4248,6 +4665,129 @@ function createHonoApp(options) {
|
|
|
4248
4665
|
}));
|
|
4249
4666
|
return c.json(collections2);
|
|
4250
4667
|
});
|
|
4668
|
+
function resolveDocField(fields, doc, fieldName) {
|
|
4669
|
+
if (fieldName in doc) return doc[fieldName];
|
|
4670
|
+
for (const field of fields) {
|
|
4671
|
+
if (!field.name) continue;
|
|
4672
|
+
if (field.type === "tabs" && field.tabs) {
|
|
4673
|
+
const data = doc[field.name];
|
|
4674
|
+
if (data && typeof data === "object" && fieldName in data) return data[fieldName];
|
|
4675
|
+
}
|
|
4676
|
+
if ((field.type === "group" || field.type === "collapsible") && field.fields) {
|
|
4677
|
+
const data = doc[field.name];
|
|
4678
|
+
if (data && typeof data === "object") {
|
|
4679
|
+
if (fieldName in data) return data[fieldName];
|
|
4680
|
+
const nested = resolveDocField(field.fields, data, fieldName);
|
|
4681
|
+
if (nested !== void 0) return nested;
|
|
4682
|
+
}
|
|
4683
|
+
}
|
|
4684
|
+
}
|
|
4685
|
+
return void 0;
|
|
4686
|
+
}
|
|
4687
|
+
function flattenRelationshipFields(fields) {
|
|
4688
|
+
const relFields = [];
|
|
4689
|
+
for (const field of fields) {
|
|
4690
|
+
if (field.type === "relationship") {
|
|
4691
|
+
relFields.push(field);
|
|
4692
|
+
} else if (field.type === "tabs" && field.tabs) {
|
|
4693
|
+
for (const tab of field.tabs) {
|
|
4694
|
+
relFields.push(...flattenRelationshipFields(tab.fields || []));
|
|
4695
|
+
}
|
|
4696
|
+
} else if ((field.type === "group" || field.type === "collapsible") && field.fields) {
|
|
4697
|
+
relFields.push(...flattenRelationshipFields(field.fields || []));
|
|
4698
|
+
}
|
|
4699
|
+
}
|
|
4700
|
+
return relFields;
|
|
4701
|
+
}
|
|
4702
|
+
function extractRelValue(value) {
|
|
4703
|
+
if (!value) return [];
|
|
4704
|
+
if (typeof value === "string") return [value];
|
|
4705
|
+
if (Array.isArray(value)) return value.map((v) => typeof v === "object" ? v.value ?? v : v);
|
|
4706
|
+
if (typeof value === "object") {
|
|
4707
|
+
const v = value.value ?? value;
|
|
4708
|
+
return typeof v === "string" ? [v] : Array.isArray(v) ? v : [];
|
|
4709
|
+
}
|
|
4710
|
+
return [];
|
|
4711
|
+
}
|
|
4712
|
+
async function populateRelationships(docs, collection, db2, registry2) {
|
|
4713
|
+
if (docs.length === 0) return;
|
|
4714
|
+
const relFields = flattenRelationshipFields(collection.fields);
|
|
4715
|
+
if (relFields.length === 0) return;
|
|
4716
|
+
for (const relField of relFields) {
|
|
4717
|
+
const targetSlugs = Array.isArray(relField.relationTo) ? relField.relationTo : [relField.relationTo];
|
|
4718
|
+
const idsBySlug = {};
|
|
4719
|
+
for (const slug of targetSlugs) {
|
|
4720
|
+
idsBySlug[slug] = /* @__PURE__ */ new Set();
|
|
4721
|
+
}
|
|
4722
|
+
for (const doc of docs) {
|
|
4723
|
+
const raw = resolveDocField(collection.fields, doc, relField.name);
|
|
4724
|
+
const ids = extractRelValue(raw);
|
|
4725
|
+
for (const id of ids) {
|
|
4726
|
+
if (!id) continue;
|
|
4727
|
+
if (targetSlugs.length === 1) {
|
|
4728
|
+
idsBySlug[targetSlugs[0]].add(id);
|
|
4729
|
+
} else {
|
|
4730
|
+
for (const slug of targetSlugs) {
|
|
4731
|
+
idsBySlug[slug].add(id);
|
|
4732
|
+
}
|
|
4733
|
+
}
|
|
4734
|
+
}
|
|
4735
|
+
}
|
|
4736
|
+
for (const [targetSlug, idSet] of Object.entries(idsBySlug)) {
|
|
4737
|
+
if (idSet.size === 0) continue;
|
|
4738
|
+
const targetCollection = registry2.getCollection(targetSlug);
|
|
4739
|
+
if (!targetCollection) continue;
|
|
4740
|
+
const titleField = targetCollection.admin?.useAsTitle || "title";
|
|
4741
|
+
const idArr = Array.from(idSet);
|
|
4742
|
+
const relatedDocs = [];
|
|
4743
|
+
for (const id of idArr) {
|
|
4744
|
+
try {
|
|
4745
|
+
const relDoc = await db2.findByID({ collection: targetSlug, id, draft: true });
|
|
4746
|
+
if (relDoc) {
|
|
4747
|
+
const title = resolveDocField(targetCollection.fields, relDoc, titleField) ?? id;
|
|
4748
|
+
relatedDocs.push({ id, title: String(title) });
|
|
4749
|
+
}
|
|
4750
|
+
} catch {
|
|
4751
|
+
relatedDocs.push({ id, title: id });
|
|
4752
|
+
}
|
|
4753
|
+
}
|
|
4754
|
+
const titleMap = new Map(relatedDocs.map((d) => [d.id, d.title]));
|
|
4755
|
+
for (const doc of docs) {
|
|
4756
|
+
const raw = resolveDocField(collection.fields, doc, relField.name);
|
|
4757
|
+
if (!raw) continue;
|
|
4758
|
+
const setValue = (val) => {
|
|
4759
|
+
if (relField.name in doc) {
|
|
4760
|
+
doc[relField.name] = val;
|
|
4761
|
+
} else {
|
|
4762
|
+
for (const f of collection.fields) {
|
|
4763
|
+
if (f.type === "tabs" && f.tabs) {
|
|
4764
|
+
for (const tab of f.tabs) {
|
|
4765
|
+
if (tab.fields?.some((tf) => tf.name === relField.name)) {
|
|
4766
|
+
const tabData = doc[f.name];
|
|
4767
|
+
if (tabData && typeof tabData === "object") {
|
|
4768
|
+
tabData[relField.name] = val;
|
|
4769
|
+
}
|
|
4770
|
+
}
|
|
4771
|
+
}
|
|
4772
|
+
}
|
|
4773
|
+
}
|
|
4774
|
+
}
|
|
4775
|
+
};
|
|
4776
|
+
if (typeof raw === "string") {
|
|
4777
|
+
setValue({ id: raw, title: titleMap.get(raw) || raw });
|
|
4778
|
+
} else if (Array.isArray(raw)) {
|
|
4779
|
+
setValue(raw.map((v) => {
|
|
4780
|
+
const id = typeof v === "object" ? v.value ?? v.id : v;
|
|
4781
|
+
return { id, title: titleMap.get(id) || id };
|
|
4782
|
+
}));
|
|
4783
|
+
} else if (typeof raw === "object") {
|
|
4784
|
+
const id = raw.value ?? raw.id;
|
|
4785
|
+
setValue({ id, title: titleMap.get(id) || id });
|
|
4786
|
+
}
|
|
4787
|
+
}
|
|
4788
|
+
}
|
|
4789
|
+
}
|
|
4790
|
+
}
|
|
4251
4791
|
app.get("/api/search", async (c) => {
|
|
4252
4792
|
try {
|
|
4253
4793
|
const query = c.req.query("q") || "";
|
|
@@ -4299,11 +4839,12 @@ function createHonoApp(options) {
|
|
|
4299
4839
|
limit,
|
|
4300
4840
|
tenantID: ctxTenantID
|
|
4301
4841
|
});
|
|
4842
|
+
await populateRelationships(searchResult.docs, collection, db, registry);
|
|
4302
4843
|
for (const doc of searchResult.docs) {
|
|
4303
4844
|
const titleField = collection.admin?.useAsTitle || searchableFields.find(
|
|
4304
4845
|
(f) => f === "title" || f === "name" || f === "heading" || f === "slug"
|
|
4305
4846
|
);
|
|
4306
|
-
const title = titleField ? doc
|
|
4847
|
+
const title = titleField ? resolveDocField(collection.fields, doc, titleField) ?? doc.id : doc.id;
|
|
4307
4848
|
results.push({
|
|
4308
4849
|
collection: collection.slug,
|
|
4309
4850
|
label: collection.label || collection.slug,
|
|
@@ -4324,15 +4865,13 @@ function createHonoApp(options) {
|
|
|
4324
4865
|
});
|
|
4325
4866
|
app.get("/api/keys", async (c) => {
|
|
4326
4867
|
try {
|
|
4327
|
-
const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4328
|
-
if (!ctxUser || !
|
|
4868
|
+
const { user: ctxUser, tenantID: ctxTenantID } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4869
|
+
if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:read")) {
|
|
4329
4870
|
return c.json({ error: "Forbidden" }, 403);
|
|
4330
4871
|
}
|
|
4331
4872
|
const page = parseInt(c.req.query("page") || "1");
|
|
4332
4873
|
const limit = Math.min(parseInt(c.req.query("limit") || "50"), 100);
|
|
4333
|
-
|
|
4334
|
-
const result = await db.find({ collection: chunkIBG6V56E_cjs.API_KEY_COLLECTION, where: {}, page, limit });
|
|
4335
|
-
console.log("[ApiKeys] Result:", result);
|
|
4874
|
+
const result = await db.find({ collection: chunk4M7X5HAB_cjs.API_KEY_COLLECTION, where: {}, page, limit, tenantID: ctxTenantID });
|
|
4336
4875
|
const docs = (result.docs || []).map((doc) => ({
|
|
4337
4876
|
id: doc.id,
|
|
4338
4877
|
name: doc.name,
|
|
@@ -4350,21 +4889,21 @@ function createHonoApp(options) {
|
|
|
4350
4889
|
app.post("/api/keys", async (c) => {
|
|
4351
4890
|
try {
|
|
4352
4891
|
const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4353
|
-
if (!ctxUser || !
|
|
4892
|
+
if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:admin")) {
|
|
4354
4893
|
return c.json({ error: "Forbidden" }, 403);
|
|
4355
4894
|
}
|
|
4356
4895
|
const body = await c.req.json();
|
|
4357
4896
|
if (!body.name || typeof body.name !== "string") {
|
|
4358
4897
|
return c.json({ error: "name is required" }, 400);
|
|
4359
4898
|
}
|
|
4360
|
-
const rawKey =
|
|
4899
|
+
const rawKey = chunk4M7X5HAB_cjs.generateApiKey();
|
|
4361
4900
|
const doc = await db.create({
|
|
4362
|
-
collection:
|
|
4901
|
+
collection: chunk4M7X5HAB_cjs.API_KEY_COLLECTION,
|
|
4363
4902
|
data: {
|
|
4364
4903
|
userId: ctxUser.id,
|
|
4365
4904
|
name: body.name,
|
|
4366
4905
|
key: rawKey,
|
|
4367
|
-
keyPrefix:
|
|
4906
|
+
keyPrefix: chunk4M7X5HAB_cjs.generateApiKeyPrefix(rawKey),
|
|
4368
4907
|
permissions: Array.isArray(body.permissions) ? body.permissions : ["*"],
|
|
4369
4908
|
expiresAt: body.expiresAt || null,
|
|
4370
4909
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -4391,13 +4930,13 @@ function createHonoApp(options) {
|
|
|
4391
4930
|
app.delete("/api/keys/:id", async (c) => {
|
|
4392
4931
|
try {
|
|
4393
4932
|
const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4394
|
-
if (!ctxUser || !
|
|
4933
|
+
if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:admin")) {
|
|
4395
4934
|
return c.json({ error: "Forbidden" }, 403);
|
|
4396
4935
|
}
|
|
4397
4936
|
const id = c.req.param("id");
|
|
4398
|
-
const existing = await db.findByID({ collection:
|
|
4937
|
+
const existing = await db.findByID({ collection: chunk4M7X5HAB_cjs.API_KEY_COLLECTION, id });
|
|
4399
4938
|
if (!existing) return c.json({ error: "API key not found" }, 404);
|
|
4400
|
-
await db.delete({ collection:
|
|
4939
|
+
await db.delete({ collection: chunk4M7X5HAB_cjs.API_KEY_COLLECTION, id });
|
|
4401
4940
|
await sessionAuthAdapter?.createAuditLog({
|
|
4402
4941
|
action: "api_key_delete",
|
|
4403
4942
|
userId: ctxUser.id,
|
|
@@ -4415,19 +4954,19 @@ function createHonoApp(options) {
|
|
|
4415
4954
|
app.patch("/api/keys/:id", async (c) => {
|
|
4416
4955
|
try {
|
|
4417
4956
|
const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4418
|
-
if (!ctxUser || !
|
|
4957
|
+
if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:admin")) {
|
|
4419
4958
|
return c.json({ error: "Forbidden" }, 403);
|
|
4420
4959
|
}
|
|
4421
4960
|
const id = c.req.param("id");
|
|
4422
4961
|
const body = await c.req.json();
|
|
4423
|
-
const existing = await db.findByID({ collection:
|
|
4962
|
+
const existing = await db.findByID({ collection: chunk4M7X5HAB_cjs.API_KEY_COLLECTION, id });
|
|
4424
4963
|
if (!existing) return c.json({ error: "API key not found" }, 404);
|
|
4425
4964
|
const updateData = {};
|
|
4426
4965
|
if (typeof body.name === "string" && body.name.trim()) updateData.name = body.name.trim();
|
|
4427
4966
|
if (Array.isArray(body.permissions)) updateData.permissions = body.permissions;
|
|
4428
4967
|
if (body.expiresAt !== void 0) updateData.expiresAt = body.expiresAt || null;
|
|
4429
4968
|
if (Object.keys(updateData).length === 0) return c.json({ error: "Nothing to update" }, 400);
|
|
4430
|
-
const updated = await db.update({ collection:
|
|
4969
|
+
const updated = await db.update({ collection: chunk4M7X5HAB_cjs.API_KEY_COLLECTION, id, data: updateData });
|
|
4431
4970
|
return c.json({ ...updated, keyPrefix: existing.keyPrefix });
|
|
4432
4971
|
} catch (error) {
|
|
4433
4972
|
console.error("[ApiKeys] PATCH error:", error);
|
|
@@ -4437,19 +4976,19 @@ function createHonoApp(options) {
|
|
|
4437
4976
|
app.post("/api/keys/:id/rotate", async (c) => {
|
|
4438
4977
|
try {
|
|
4439
4978
|
const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4440
|
-
if (!ctxUser || !
|
|
4979
|
+
if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:admin")) {
|
|
4441
4980
|
return c.json({ error: "Forbidden" }, 403);
|
|
4442
4981
|
}
|
|
4443
4982
|
const id = c.req.param("id");
|
|
4444
|
-
const existing = await db.findByID({ collection:
|
|
4983
|
+
const existing = await db.findByID({ collection: chunk4M7X5HAB_cjs.API_KEY_COLLECTION, id });
|
|
4445
4984
|
if (!existing) return c.json({ error: "API key not found" }, 404);
|
|
4446
|
-
const rawKey =
|
|
4985
|
+
const rawKey = chunk4M7X5HAB_cjs.generateApiKey();
|
|
4447
4986
|
const updated = await db.update({
|
|
4448
|
-
collection:
|
|
4987
|
+
collection: chunk4M7X5HAB_cjs.API_KEY_COLLECTION,
|
|
4449
4988
|
id,
|
|
4450
4989
|
data: {
|
|
4451
4990
|
key: rawKey,
|
|
4452
|
-
keyPrefix:
|
|
4991
|
+
keyPrefix: chunk4M7X5HAB_cjs.generateApiKeyPrefix(rawKey),
|
|
4453
4992
|
lastUsedAt: null
|
|
4454
4993
|
}
|
|
4455
4994
|
});
|
|
@@ -4475,7 +5014,7 @@ function createHonoApp(options) {
|
|
|
4475
5014
|
app.get("/api/webhooks", async (c) => {
|
|
4476
5015
|
try {
|
|
4477
5016
|
const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4478
|
-
if (!ctxUser || !
|
|
5017
|
+
if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:read")) {
|
|
4479
5018
|
return c.json({ error: "Forbidden" }, 403);
|
|
4480
5019
|
}
|
|
4481
5020
|
if (!webhookService) return c.json({ error: "Webhook service not available" }, 503);
|
|
@@ -4489,7 +5028,7 @@ function createHonoApp(options) {
|
|
|
4489
5028
|
app.post("/api/webhooks", async (c) => {
|
|
4490
5029
|
try {
|
|
4491
5030
|
const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4492
|
-
if (!ctxUser || !
|
|
5031
|
+
if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:admin")) {
|
|
4493
5032
|
return c.json({ error: "Forbidden" }, 403);
|
|
4494
5033
|
}
|
|
4495
5034
|
if (!webhookService) return c.json({ error: "Webhook service not available" }, 503);
|
|
@@ -4512,7 +5051,7 @@ function createHonoApp(options) {
|
|
|
4512
5051
|
app.get("/api/webhooks/:id", async (c) => {
|
|
4513
5052
|
try {
|
|
4514
5053
|
const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4515
|
-
if (!ctxUser || !
|
|
5054
|
+
if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:read")) {
|
|
4516
5055
|
return c.json({ error: "Forbidden" }, 403);
|
|
4517
5056
|
}
|
|
4518
5057
|
if (!webhookService) return c.json({ error: "Webhook service not available" }, 503);
|
|
@@ -4528,7 +5067,7 @@ function createHonoApp(options) {
|
|
|
4528
5067
|
app.patch("/api/webhooks/:id", async (c) => {
|
|
4529
5068
|
try {
|
|
4530
5069
|
const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4531
|
-
if (!ctxUser || !
|
|
5070
|
+
if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:admin")) {
|
|
4532
5071
|
return c.json({ error: "Forbidden" }, 403);
|
|
4533
5072
|
}
|
|
4534
5073
|
if (!webhookService) return c.json({ error: "Webhook service not available" }, 503);
|
|
@@ -4553,7 +5092,7 @@ function createHonoApp(options) {
|
|
|
4553
5092
|
app.delete("/api/webhooks/:id", async (c) => {
|
|
4554
5093
|
try {
|
|
4555
5094
|
const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4556
|
-
if (!ctxUser || !
|
|
5095
|
+
if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:admin")) {
|
|
4557
5096
|
return c.json({ error: "Forbidden" }, 403);
|
|
4558
5097
|
}
|
|
4559
5098
|
if (!webhookService) return c.json({ error: "Webhook service not available" }, 503);
|
|
@@ -4578,7 +5117,7 @@ function createHonoApp(options) {
|
|
|
4578
5117
|
app.post("/api/webhooks/:id/test", async (c) => {
|
|
4579
5118
|
try {
|
|
4580
5119
|
const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4581
|
-
if (!ctxUser || !
|
|
5120
|
+
if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:admin")) {
|
|
4582
5121
|
return c.json({ error: "Forbidden" }, 403);
|
|
4583
5122
|
}
|
|
4584
5123
|
if (!webhookService) return c.json({ error: "Webhook service not available" }, 503);
|
|
@@ -4594,7 +5133,7 @@ function createHonoApp(options) {
|
|
|
4594
5133
|
app.get("/api/webhooks/:id/history", async (c) => {
|
|
4595
5134
|
try {
|
|
4596
5135
|
const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4597
|
-
if (!ctxUser || !
|
|
5136
|
+
if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:read")) {
|
|
4598
5137
|
return c.json({ error: "Forbidden" }, 403);
|
|
4599
5138
|
}
|
|
4600
5139
|
if (!webhookService) return c.json({ error: "Webhook service not available" }, 503);
|
|
@@ -4643,37 +5182,31 @@ function createHonoApp(options) {
|
|
|
4643
5182
|
if (!access.allowed) {
|
|
4644
5183
|
return c.json({ error: access.error }, access.status || 403);
|
|
4645
5184
|
}
|
|
4646
|
-
|
|
5185
|
+
if (ctxTenantID) {
|
|
5186
|
+
db.setTenantContext({ tenantId: ctxTenantID, userId: ctxUser?.id ?? "", role: ctxUser?.role, isSuperAdmin: ctxUser?.role === "super_admin" });
|
|
5187
|
+
}
|
|
4647
5188
|
const url = new URL(c.req.url);
|
|
4648
5189
|
const page = parseInt(url.searchParams.get("page") || "1");
|
|
4649
|
-
const limit = Math.min(
|
|
4650
|
-
parseInt(url.searchParams.get("limit") || "10"),
|
|
4651
|
-
100
|
|
4652
|
-
);
|
|
5190
|
+
const limit = Math.min(parseInt(url.searchParams.get("limit") || "10"), 100);
|
|
4653
5191
|
const sort = url.searchParams.get("sort") || void 0;
|
|
4654
5192
|
const depth = parseInt(url.searchParams.get("depth") || "0");
|
|
4655
5193
|
const select = url.searchParams.get("select")?.split(",") || void 0;
|
|
4656
|
-
|
|
4657
|
-
const whereParam = url.searchParams.get("where");
|
|
4658
|
-
if (whereParam) {
|
|
4659
|
-
try {
|
|
4660
|
-
where = JSON.parse(whereParam);
|
|
4661
|
-
} catch {
|
|
4662
|
-
}
|
|
4663
|
-
}
|
|
5194
|
+
const isDraftRequest = !!ctxUser;
|
|
4664
5195
|
const result = await db.find({
|
|
4665
5196
|
collection: slug,
|
|
4666
|
-
where,
|
|
5197
|
+
where: {},
|
|
4667
5198
|
sort,
|
|
4668
5199
|
limit,
|
|
4669
5200
|
page,
|
|
4670
5201
|
depth,
|
|
4671
5202
|
tenantID: ctxTenantID,
|
|
4672
|
-
select
|
|
5203
|
+
select,
|
|
5204
|
+
draft: isDraftRequest
|
|
4673
5205
|
});
|
|
5206
|
+
await populateRelationships(result.docs, collection, db, registry);
|
|
4674
5207
|
return c.json(result);
|
|
4675
5208
|
} catch (error) {
|
|
4676
|
-
console.error(
|
|
5209
|
+
console.error("[API] list error:", error);
|
|
4677
5210
|
return c.json({ error: error.message }, 500);
|
|
4678
5211
|
}
|
|
4679
5212
|
});
|
|
@@ -4698,6 +5231,9 @@ function createHonoApp(options) {
|
|
|
4698
5231
|
return c.json({ error: access.error }, access.status || 403);
|
|
4699
5232
|
}
|
|
4700
5233
|
const id = c.req.param("id");
|
|
5234
|
+
if (!id) {
|
|
5235
|
+
return c.json({ error: "Missing document ID" }, 400);
|
|
5236
|
+
}
|
|
4701
5237
|
const url = new URL(c.req.url);
|
|
4702
5238
|
const compareA = url.searchParams.get("compareA");
|
|
4703
5239
|
const compareB = url.searchParams.get("compareB");
|
|
@@ -4728,36 +5264,6 @@ function createHonoApp(options) {
|
|
|
4728
5264
|
return c.json({ error: error.message }, 500);
|
|
4729
5265
|
}
|
|
4730
5266
|
});
|
|
4731
|
-
app.get(`${basePath}/:id/draft`, async (c) => {
|
|
4732
|
-
try {
|
|
4733
|
-
const {
|
|
4734
|
-
user: ctxUser,
|
|
4735
|
-
tenantID: ctxTenantID,
|
|
4736
|
-
apiKeyContext
|
|
4737
|
-
} = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4738
|
-
const access = await checkCollectionAccess(
|
|
4739
|
-
collection,
|
|
4740
|
-
"read",
|
|
4741
|
-
c.req.raw,
|
|
4742
|
-
ctxUser,
|
|
4743
|
-
ctxTenantID,
|
|
4744
|
-
apiKeyContext,
|
|
4745
|
-
enablePublicAccess,
|
|
4746
|
-
defaultCollectionAccess
|
|
4747
|
-
);
|
|
4748
|
-
if (!access.allowed) {
|
|
4749
|
-
return c.json({ error: access.error }, access.status || 403);
|
|
4750
|
-
}
|
|
4751
|
-
const draft = await db.findDraft({
|
|
4752
|
-
collection: slug,
|
|
4753
|
-
documentId: c.req.param("id"),
|
|
4754
|
-
tenantID: ctxTenantID
|
|
4755
|
-
});
|
|
4756
|
-
return c.json({ data: draft });
|
|
4757
|
-
} catch (error) {
|
|
4758
|
-
return c.json({ error: error.message }, 500);
|
|
4759
|
-
}
|
|
4760
|
-
});
|
|
4761
5267
|
app.put(`${basePath}/:id/draft`, async (c) => {
|
|
4762
5268
|
try {
|
|
4763
5269
|
const {
|
|
@@ -4781,24 +5287,39 @@ function createHonoApp(options) {
|
|
|
4781
5287
|
const id = c.req.param("id");
|
|
4782
5288
|
const body = await c.req.json();
|
|
4783
5289
|
const baseUpdatedAt = readBaseUpdatedAt(body);
|
|
4784
|
-
const data = body.data ?? omitRevisionFields(body);
|
|
4785
5290
|
const originalDoc = await db.findByID({
|
|
4786
5291
|
collection: slug,
|
|
4787
5292
|
id,
|
|
4788
|
-
tenantID: ctxTenantID
|
|
5293
|
+
tenantID: ctxTenantID,
|
|
5294
|
+
draft: true
|
|
4789
5295
|
});
|
|
4790
5296
|
if (!originalDoc) {
|
|
4791
5297
|
return c.json({ error: "Document not found" }, 404);
|
|
4792
5298
|
}
|
|
4793
|
-
|
|
5299
|
+
let finalData;
|
|
5300
|
+
if (body.delta) {
|
|
5301
|
+
finalData = { ...originalDoc, ...body.delta };
|
|
5302
|
+
} else {
|
|
5303
|
+
finalData = body.data ?? omitRevisionFields(body);
|
|
5304
|
+
}
|
|
5305
|
+
const version = await db.updateLatestVersion({
|
|
4794
5306
|
collection: slug,
|
|
4795
5307
|
documentId: id,
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
draftUpdatedAt: body.draftUpdatedAt
|
|
5308
|
+
data: finalData,
|
|
5309
|
+
status: "draft",
|
|
5310
|
+
tenantID: ctxTenantID
|
|
4800
5311
|
});
|
|
4801
|
-
|
|
5312
|
+
if (ctxUser) {
|
|
5313
|
+
sessionAuthAdapter?.createAuditLog({
|
|
5314
|
+
action: "document_update",
|
|
5315
|
+
userId: ctxUser.id,
|
|
5316
|
+
resource: slug,
|
|
5317
|
+
resourceId: id,
|
|
5318
|
+
success: true,
|
|
5319
|
+
metadata: { type: "draft_save" }
|
|
5320
|
+
});
|
|
5321
|
+
}
|
|
5322
|
+
return c.json({ data: version, message: "Draft saved successfully" });
|
|
4802
5323
|
} catch (error) {
|
|
4803
5324
|
return c.json({ error: error.message }, 500);
|
|
4804
5325
|
}
|
|
@@ -4823,11 +5344,16 @@ function createHonoApp(options) {
|
|
|
4823
5344
|
if (!access.allowed) {
|
|
4824
5345
|
return c.json({ error: access.error }, access.status || 403);
|
|
4825
5346
|
}
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
5347
|
+
if (ctxUser) {
|
|
5348
|
+
sessionAuthAdapter?.createAuditLog({
|
|
5349
|
+
action: "document_update",
|
|
5350
|
+
userId: ctxUser.id,
|
|
5351
|
+
resource: slug,
|
|
5352
|
+
resourceId: c.req.param("id"),
|
|
5353
|
+
success: true,
|
|
5354
|
+
metadata: { type: "draft_discard" }
|
|
5355
|
+
});
|
|
5356
|
+
}
|
|
4831
5357
|
return c.json({ message: "Draft discarded successfully" });
|
|
4832
5358
|
} catch (error) {
|
|
4833
5359
|
return c.json({ error: error.message }, 500);
|
|
@@ -4870,12 +5396,14 @@ function createHonoApp(options) {
|
|
|
4870
5396
|
doc = await db.findOne({
|
|
4871
5397
|
collection: slug,
|
|
4872
5398
|
where: { slug: id },
|
|
4873
|
-
tenantID: ctxTenantID
|
|
5399
|
+
tenantID: ctxTenantID,
|
|
5400
|
+
draft: isDraftRequest
|
|
4874
5401
|
});
|
|
4875
5402
|
}
|
|
4876
5403
|
if (!doc) {
|
|
4877
5404
|
return c.json({ error: "Document not found" }, 404);
|
|
4878
5405
|
}
|
|
5406
|
+
await populateRelationships([doc], collection, db, registry);
|
|
4879
5407
|
return c.json({ data: doc });
|
|
4880
5408
|
} catch (error) {
|
|
4881
5409
|
return c.json({ error: error.message }, 500);
|
|
@@ -4901,23 +5429,81 @@ function createHonoApp(options) {
|
|
|
4901
5429
|
if (!access.allowed) {
|
|
4902
5430
|
return c.json({ error: access.error }, access.status || 403);
|
|
4903
5431
|
}
|
|
5432
|
+
if (ctxTenantID) {
|
|
5433
|
+
db.setTenantContext({ tenantId: ctxTenantID, userId: ctxUser?.id ?? "", role: ctxUser?.role, isSuperAdmin: ctxUser?.role === "super_admin" });
|
|
5434
|
+
}
|
|
4904
5435
|
auditApiKeyUsage(sessionAuthAdapter, apiKeyContext, basePath, "POST", c.req.raw);
|
|
4905
5436
|
const body = await c.req.json();
|
|
5437
|
+
let validated = body;
|
|
5438
|
+
normalizeEmptyStrings(validated, collection.fields);
|
|
5439
|
+
convertRichtextFields(collection.fields, validated);
|
|
5440
|
+
const hookReq = c.req.raw;
|
|
5441
|
+
if (collection.hooks?.beforeValidate) {
|
|
5442
|
+
for (const hook of collection.hooks.beforeValidate) {
|
|
5443
|
+
const hookResult = await hook({
|
|
5444
|
+
collection: slug,
|
|
5445
|
+
data: validated,
|
|
5446
|
+
req: hookReq,
|
|
5447
|
+
user: ctxUser,
|
|
5448
|
+
tenantID: ctxTenantID,
|
|
5449
|
+
operation: "create"
|
|
5450
|
+
});
|
|
5451
|
+
if (hookResult) Object.assign(validated, hookResult);
|
|
5452
|
+
}
|
|
5453
|
+
}
|
|
4906
5454
|
const schema = registry.getCreateZodSchema(slug);
|
|
4907
|
-
let validated;
|
|
4908
5455
|
try {
|
|
4909
|
-
validated = schema.parse(
|
|
5456
|
+
validated = schema.parse(validated);
|
|
4910
5457
|
} catch (zodErr) {
|
|
4911
|
-
return c.json({ error:
|
|
5458
|
+
return c.json({ error: `Validation failed: ${formatZodErrors(zodErr.errors)}`, details: zodErr.errors }, 400);
|
|
4912
5459
|
}
|
|
4913
5460
|
if (collection.tenantScoped && ctxTenantID) {
|
|
4914
5461
|
validated.tenantID = ctxTenantID;
|
|
4915
5462
|
}
|
|
5463
|
+
const isDraftEnabled = collection.versions?.drafts === true;
|
|
5464
|
+
validated.status = isDraftEnabled ? "draft" : "published";
|
|
5465
|
+
if (collection.hooks?.beforeChange) {
|
|
5466
|
+
for (const hook of collection.hooks.beforeChange) {
|
|
5467
|
+
const hookResult = await hook({
|
|
5468
|
+
collection: slug,
|
|
5469
|
+
data: validated,
|
|
5470
|
+
req: hookReq,
|
|
5471
|
+
user: ctxUser,
|
|
5472
|
+
tenantID: ctxTenantID,
|
|
5473
|
+
operation: "create"
|
|
5474
|
+
});
|
|
5475
|
+
if (hookResult) Object.assign(validated, hookResult);
|
|
5476
|
+
}
|
|
5477
|
+
}
|
|
4916
5478
|
const doc = await db.create({
|
|
4917
5479
|
collection: slug,
|
|
4918
5480
|
data: validated,
|
|
4919
5481
|
tenantID: ctxTenantID
|
|
4920
5482
|
});
|
|
5483
|
+
if (isDraftEnabled) {
|
|
5484
|
+
await db.createVersion({
|
|
5485
|
+
collection: slug,
|
|
5486
|
+
documentId: doc.id,
|
|
5487
|
+
data: validated,
|
|
5488
|
+
status: "draft",
|
|
5489
|
+
createdBy: ctxUser?.id,
|
|
5490
|
+
changeDescription: "Created",
|
|
5491
|
+
tenantID: ctxTenantID
|
|
5492
|
+
});
|
|
5493
|
+
}
|
|
5494
|
+
if (collection.hooks?.afterChange) {
|
|
5495
|
+
for (const hook of collection.hooks.afterChange) {
|
|
5496
|
+
await hook({
|
|
5497
|
+
collection: slug,
|
|
5498
|
+
doc,
|
|
5499
|
+
data: validated,
|
|
5500
|
+
req: hookReq,
|
|
5501
|
+
user: ctxUser,
|
|
5502
|
+
tenantID: ctxTenantID,
|
|
5503
|
+
operation: "create"
|
|
5504
|
+
});
|
|
5505
|
+
}
|
|
5506
|
+
}
|
|
4921
5507
|
if (webhookService) {
|
|
4922
5508
|
webhookService.trigger(getWebhookEvent(slug, "create"), {
|
|
4923
5509
|
collection: slug,
|
|
@@ -4927,11 +5513,20 @@ function createHonoApp(options) {
|
|
|
4927
5513
|
tenantId: ctxTenantID
|
|
4928
5514
|
}).catch((err) => console.error(`[Webhook] Failed to trigger:`, err));
|
|
4929
5515
|
}
|
|
5516
|
+
if (ctxUser) {
|
|
5517
|
+
sessionAuthAdapter?.createAuditLog({
|
|
5518
|
+
action: "document_create",
|
|
5519
|
+
userId: ctxUser.id,
|
|
5520
|
+
resource: slug,
|
|
5521
|
+
resourceId: doc.id,
|
|
5522
|
+
success: true
|
|
5523
|
+
});
|
|
5524
|
+
}
|
|
4930
5525
|
return c.json({ data: doc, message: "Created successfully" }, 201);
|
|
4931
5526
|
} catch (error) {
|
|
4932
5527
|
if (error.name === "ZodError") {
|
|
4933
5528
|
return c.json(
|
|
4934
|
-
{ error:
|
|
5529
|
+
{ error: `Validation failed: ${formatZodErrors(error.errors)}`, details: error.errors },
|
|
4935
5530
|
400
|
|
4936
5531
|
);
|
|
4937
5532
|
}
|
|
@@ -4961,61 +5556,92 @@ function createHonoApp(options) {
|
|
|
4961
5556
|
const id = c.req.param("id");
|
|
4962
5557
|
const body = await c.req.json();
|
|
4963
5558
|
const baseUpdatedAt = readBaseUpdatedAt(body);
|
|
4964
|
-
console.log(`[PATCH] ${slug}/${id}`, {
|
|
4965
|
-
baseUpdatedAt,
|
|
4966
|
-
bodyKeys: Object.keys(body),
|
|
4967
|
-
tenantID: ctxTenantID
|
|
4968
|
-
});
|
|
4969
|
-
const cleaned = Object.fromEntries(
|
|
4970
|
-
Object.entries(omitRevisionFields(body)).filter(
|
|
4971
|
-
([_, v]) => v !== "null" && v !== void 0
|
|
4972
|
-
)
|
|
4973
|
-
);
|
|
4974
|
-
const schema = registry.getUpdateZodSchema(slug);
|
|
4975
|
-
const validated = schema.parse(cleaned);
|
|
4976
|
-
console.log(`[PATCH] Validated data:`, Object.keys(validated));
|
|
4977
5559
|
const originalDoc = await db.findByID({
|
|
4978
5560
|
collection: slug,
|
|
4979
5561
|
id,
|
|
4980
5562
|
tenantID: ctxTenantID,
|
|
4981
5563
|
draft: true
|
|
4982
|
-
// Always fetch current doc regardless of status
|
|
4983
5564
|
});
|
|
4984
|
-
if (originalDoc) {
|
|
4985
|
-
console.log(`[PATCH] Original doc updatedAt:`, originalDoc.updatedAt);
|
|
4986
|
-
}
|
|
4987
5565
|
if (!originalDoc) {
|
|
4988
5566
|
return c.json({ error: "Document not found" }, 404);
|
|
4989
5567
|
}
|
|
4990
5568
|
if (baseUpdatedAt && originalDoc.updatedAt && baseUpdatedAt !== originalDoc.updatedAt) {
|
|
4991
5569
|
return c.json(buildConflictResponse(baseUpdatedAt, originalDoc), 409);
|
|
4992
5570
|
}
|
|
5571
|
+
let validated = Object.fromEntries(
|
|
5572
|
+
Object.entries(omitRevisionFields(body)).filter(
|
|
5573
|
+
([_, v]) => v !== "null" && v !== void 0
|
|
5574
|
+
)
|
|
5575
|
+
);
|
|
5576
|
+
normalizeEmptyStrings(validated, collection.fields);
|
|
5577
|
+
convertRichtextFields(collection.fields, validated);
|
|
5578
|
+
const hookReq = c.req.raw;
|
|
5579
|
+
if (collection.hooks?.beforeValidate) {
|
|
5580
|
+
for (const hook of collection.hooks.beforeValidate) {
|
|
5581
|
+
const hookResult = await hook({
|
|
5582
|
+
collection: slug,
|
|
5583
|
+
data: validated,
|
|
5584
|
+
originalDoc,
|
|
5585
|
+
req: hookReq,
|
|
5586
|
+
user: ctxUser,
|
|
5587
|
+
tenantID: ctxTenantID,
|
|
5588
|
+
operation: "update"
|
|
5589
|
+
});
|
|
5590
|
+
if (hookResult) Object.assign(validated, hookResult);
|
|
5591
|
+
}
|
|
5592
|
+
}
|
|
5593
|
+
const schema = registry.getUpdateZodSchema(slug);
|
|
5594
|
+
validated = schema.parse(validated);
|
|
5595
|
+
if (collection.hooks?.beforeChange) {
|
|
5596
|
+
for (const hook of collection.hooks.beforeChange) {
|
|
5597
|
+
const hookResult = await hook({
|
|
5598
|
+
collection: slug,
|
|
5599
|
+
data: validated,
|
|
5600
|
+
originalDoc,
|
|
5601
|
+
req: hookReq,
|
|
5602
|
+
user: ctxUser,
|
|
5603
|
+
tenantID: ctxTenantID,
|
|
5604
|
+
operation: "update"
|
|
5605
|
+
});
|
|
5606
|
+
if (hookResult) Object.assign(validated, hookResult);
|
|
5607
|
+
}
|
|
5608
|
+
}
|
|
5609
|
+
const isDraft = c.req.header("X-Draft") === "true";
|
|
4993
5610
|
const isDraftEnabled = collection.versions?.drafts === true;
|
|
4994
|
-
const
|
|
5611
|
+
const isAutosave = c.req.query("autosave") === "true";
|
|
4995
5612
|
let doc;
|
|
4996
|
-
if (isDraftEnabled &&
|
|
5613
|
+
if (isDraftEnabled && isDraft) {
|
|
4997
5614
|
await db.createVersion({
|
|
4998
5615
|
collection: slug,
|
|
4999
5616
|
documentId: id,
|
|
5000
5617
|
data: validated,
|
|
5001
5618
|
status: "draft",
|
|
5619
|
+
autosave: isAutosave,
|
|
5002
5620
|
createdBy: ctxUser?.id,
|
|
5003
|
-
changeDescription: "
|
|
5621
|
+
changeDescription: isAutosave ? "Autosave" : "Draft saved",
|
|
5004
5622
|
tenantID: ctxTenantID
|
|
5005
5623
|
});
|
|
5624
|
+
} else if (isDraftEnabled) {
|
|
5006
5625
|
await db.update({
|
|
5007
5626
|
collection: slug,
|
|
5008
5627
|
id,
|
|
5009
|
-
data: {
|
|
5010
|
-
|
|
5628
|
+
data: { ...validated, status: "published" },
|
|
5629
|
+
tenantID: ctxTenantID
|
|
5630
|
+
});
|
|
5631
|
+
await db.createVersion({
|
|
5632
|
+
collection: slug,
|
|
5633
|
+
documentId: id,
|
|
5634
|
+
data: validated,
|
|
5635
|
+
status: "published",
|
|
5636
|
+
createdBy: ctxUser?.id,
|
|
5637
|
+
changeDescription: "Published",
|
|
5011
5638
|
tenantID: ctxTenantID
|
|
5012
5639
|
});
|
|
5013
5640
|
} else {
|
|
5014
|
-
const saveData = isDraftEnabled ? { ...validated, _status: "draft", _has_draft: false } : validated;
|
|
5015
5641
|
await db.update({
|
|
5016
5642
|
collection: slug,
|
|
5017
5643
|
id,
|
|
5018
|
-
data:
|
|
5644
|
+
data: validated,
|
|
5019
5645
|
tenantID: ctxTenantID
|
|
5020
5646
|
});
|
|
5021
5647
|
}
|
|
@@ -5025,24 +5651,19 @@ function createHonoApp(options) {
|
|
|
5025
5651
|
tenantID: ctxTenantID,
|
|
5026
5652
|
draft: true
|
|
5027
5653
|
});
|
|
5028
|
-
if (
|
|
5029
|
-
|
|
5030
|
-
await
|
|
5654
|
+
if (collection.hooks?.afterChange) {
|
|
5655
|
+
for (const hook of collection.hooks.afterChange) {
|
|
5656
|
+
await hook({
|
|
5031
5657
|
collection: slug,
|
|
5032
|
-
|
|
5658
|
+
doc,
|
|
5033
5659
|
data: validated,
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
tenantID: ctxTenantID
|
|
5660
|
+
originalDoc,
|
|
5661
|
+
req: hookReq,
|
|
5662
|
+
user: ctxUser,
|
|
5663
|
+
tenantID: ctxTenantID,
|
|
5664
|
+
operation: "update"
|
|
5038
5665
|
});
|
|
5039
5666
|
}
|
|
5040
|
-
} else {
|
|
5041
|
-
await db.deleteDraft({
|
|
5042
|
-
collection: slug,
|
|
5043
|
-
documentId: id,
|
|
5044
|
-
tenantID: ctxTenantID
|
|
5045
|
-
});
|
|
5046
5667
|
}
|
|
5047
5668
|
if (webhookService) {
|
|
5048
5669
|
webhookService.trigger(getWebhookEvent(slug, "update"), {
|
|
@@ -5054,16 +5675,26 @@ function createHonoApp(options) {
|
|
|
5054
5675
|
tenantId: ctxTenantID
|
|
5055
5676
|
}).catch((err) => console.error(`[Webhook] Failed to trigger:`, err));
|
|
5056
5677
|
}
|
|
5057
|
-
|
|
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
|
+
}
|
|
5058
5688
|
return c.json({ data: doc, message: isDraftEnabled ? "Draft saved" : "Updated successfully" });
|
|
5059
5689
|
} catch (error) {
|
|
5060
5690
|
if (error.name === "ZodError") {
|
|
5061
5691
|
console.error(`[PATCH ${basePath}/:id] Validation failed:`, error.errors);
|
|
5062
5692
|
return c.json(
|
|
5063
|
-
{ error:
|
|
5693
|
+
{ error: `Validation failed: ${formatZodErrors(error.errors)}`, details: error.errors },
|
|
5064
5694
|
400
|
|
5065
5695
|
);
|
|
5066
5696
|
}
|
|
5697
|
+
console.error(`[PATCH ${basePath}/:id] ERROR:`, error.message, `CAUSE:`, error.cause?.message || error.cause, `QUERY:`, error.query);
|
|
5067
5698
|
return c.json({ error: error.message }, 500);
|
|
5068
5699
|
}
|
|
5069
5700
|
});
|
|
@@ -5087,23 +5718,50 @@ function createHonoApp(options) {
|
|
|
5087
5718
|
if (!access.allowed) {
|
|
5088
5719
|
return c.json({ error: access.error }, access.status || 403);
|
|
5089
5720
|
}
|
|
5721
|
+
if (ctxTenantID) {
|
|
5722
|
+
db.setTenantContext({ tenantId: ctxTenantID, userId: ctxUser?.id ?? "", role: ctxUser?.role, isSuperAdmin: ctxUser?.role === "super_admin" });
|
|
5723
|
+
}
|
|
5090
5724
|
const id = c.req.param("id");
|
|
5091
|
-
|
|
5725
|
+
const hookReq = c.req.raw;
|
|
5092
5726
|
const originalDoc = await db.findByID({
|
|
5093
5727
|
collection: slug,
|
|
5094
5728
|
id,
|
|
5095
|
-
tenantID: ctxTenantID
|
|
5729
|
+
tenantID: ctxTenantID,
|
|
5730
|
+
draft: true
|
|
5096
5731
|
});
|
|
5732
|
+
if (!originalDoc) {
|
|
5733
|
+
return c.json({ error: "Document not found" }, 404);
|
|
5734
|
+
}
|
|
5735
|
+
if (collection.hooks?.beforeDelete) {
|
|
5736
|
+
for (const hook of collection.hooks.beforeDelete) {
|
|
5737
|
+
await hook({
|
|
5738
|
+
collection: slug,
|
|
5739
|
+
doc: originalDoc,
|
|
5740
|
+
req: hookReq,
|
|
5741
|
+
user: ctxUser,
|
|
5742
|
+
tenantID: ctxTenantID,
|
|
5743
|
+
operation: "delete"
|
|
5744
|
+
});
|
|
5745
|
+
}
|
|
5746
|
+
}
|
|
5097
5747
|
const doc = await db.delete({
|
|
5098
5748
|
collection: slug,
|
|
5099
5749
|
id,
|
|
5100
5750
|
tenantID: ctxTenantID
|
|
5101
5751
|
});
|
|
5102
|
-
|
|
5103
|
-
collection
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5752
|
+
if (collection.hooks?.afterDelete) {
|
|
5753
|
+
for (const hook of collection.hooks.afterDelete) {
|
|
5754
|
+
await hook({
|
|
5755
|
+
collection: slug,
|
|
5756
|
+
doc,
|
|
5757
|
+
originalDoc,
|
|
5758
|
+
req: hookReq,
|
|
5759
|
+
user: ctxUser,
|
|
5760
|
+
tenantID: ctxTenantID,
|
|
5761
|
+
operation: "delete"
|
|
5762
|
+
});
|
|
5763
|
+
}
|
|
5764
|
+
}
|
|
5107
5765
|
if (webhookService) {
|
|
5108
5766
|
webhookService.trigger(getWebhookEvent(slug, "delete"), {
|
|
5109
5767
|
collection: slug,
|
|
@@ -5114,6 +5772,16 @@ function createHonoApp(options) {
|
|
|
5114
5772
|
tenantId: ctxTenantID
|
|
5115
5773
|
}).catch((err) => console.error(`[Webhook] Failed to trigger:`, err));
|
|
5116
5774
|
}
|
|
5775
|
+
auditApiKeyUsage(sessionAuthAdapter, apiKeyContext, `${basePath}/${id}`, "DELETE", c.req.raw);
|
|
5776
|
+
if (ctxUser) {
|
|
5777
|
+
sessionAuthAdapter?.createAuditLog({
|
|
5778
|
+
action: "document_delete",
|
|
5779
|
+
userId: ctxUser.id,
|
|
5780
|
+
resource: slug,
|
|
5781
|
+
resourceId: id,
|
|
5782
|
+
success: true
|
|
5783
|
+
});
|
|
5784
|
+
}
|
|
5117
5785
|
return c.json({ data: doc, message: "Deleted successfully" });
|
|
5118
5786
|
} catch (error) {
|
|
5119
5787
|
console.error(`[DELETE] Error deleting ${slug}:`, error);
|
|
@@ -5122,7 +5790,6 @@ function createHonoApp(options) {
|
|
|
5122
5790
|
});
|
|
5123
5791
|
app.post(`${basePath}/:id/duplicate`, async (c) => {
|
|
5124
5792
|
try {
|
|
5125
|
-
console.log(`[Duplicate] Request for ${slug}`);
|
|
5126
5793
|
const {
|
|
5127
5794
|
user: ctxUser,
|
|
5128
5795
|
tenantID: ctxTenantID,
|
|
@@ -5139,25 +5806,21 @@ function createHonoApp(options) {
|
|
|
5139
5806
|
defaultCollectionAccess
|
|
5140
5807
|
);
|
|
5141
5808
|
if (!access.allowed) {
|
|
5142
|
-
console.log("[Duplicate] Access denied:", access.error);
|
|
5143
5809
|
return c.json({ error: access.error }, access.status || 403);
|
|
5144
5810
|
}
|
|
5145
5811
|
const id = c.req.param("id");
|
|
5146
|
-
console.log(`[Duplicate] ID: ${id}`);
|
|
5147
5812
|
const originalDoc = await db.findByID({
|
|
5148
5813
|
collection: slug,
|
|
5149
5814
|
id,
|
|
5150
|
-
tenantID: ctxTenantID
|
|
5815
|
+
tenantID: ctxTenantID,
|
|
5816
|
+
draft: true
|
|
5151
5817
|
});
|
|
5152
5818
|
if (!originalDoc) {
|
|
5153
|
-
console.log("[Duplicate] Document not found");
|
|
5154
5819
|
return c.json({ error: "Document not found" }, 404);
|
|
5155
5820
|
}
|
|
5156
|
-
console.log(`[Duplicate] Original doc:`, originalDoc);
|
|
5157
5821
|
const { id: _oldId, createdAt: _oldCreated, updatedAt: _oldUpdated, ...docData } = originalDoc;
|
|
5158
5822
|
const timestamp = Date.now().toString(36);
|
|
5159
5823
|
const newSlug = `${originalDoc.slug || "document"}-copy-${timestamp}`;
|
|
5160
|
-
console.log(`[Duplicate] New slug: ${newSlug}`);
|
|
5161
5824
|
const newDoc = await db.create({
|
|
5162
5825
|
collection: slug,
|
|
5163
5826
|
data: {
|
|
@@ -5167,7 +5830,6 @@ function createHonoApp(options) {
|
|
|
5167
5830
|
},
|
|
5168
5831
|
tenantID: ctxTenantID
|
|
5169
5832
|
});
|
|
5170
|
-
console.log(`[Duplicate] Created successfully:`, newDoc.id);
|
|
5171
5833
|
return c.json({ data: newDoc, message: "Document duplicated successfully" });
|
|
5172
5834
|
} catch (error) {
|
|
5173
5835
|
console.error("[Duplicate] Error:", error);
|
|
@@ -5207,7 +5869,7 @@ function createHonoApp(options) {
|
|
|
5207
5869
|
const doc = await db.update({
|
|
5208
5870
|
collection: slug,
|
|
5209
5871
|
id,
|
|
5210
|
-
data: { ...version.data,
|
|
5872
|
+
data: { ...version.data, status: "draft" },
|
|
5211
5873
|
tenantID: ctxTenantID
|
|
5212
5874
|
});
|
|
5213
5875
|
return c.json({
|
|
@@ -5252,7 +5914,7 @@ function createHonoApp(options) {
|
|
|
5252
5914
|
const doc = await db.update({
|
|
5253
5915
|
collection: slug,
|
|
5254
5916
|
id: c.req.param("id"),
|
|
5255
|
-
data: { ...version.data,
|
|
5917
|
+
data: { ...version.data, status: "draft" },
|
|
5256
5918
|
tenantID: ctxTenantID
|
|
5257
5919
|
});
|
|
5258
5920
|
return c.json({
|
|
@@ -5285,6 +5947,9 @@ function createHonoApp(options) {
|
|
|
5285
5947
|
if (!access.allowed) {
|
|
5286
5948
|
return c.json({ error: access.error }, access.status || 403);
|
|
5287
5949
|
}
|
|
5950
|
+
if (ctxTenantID) {
|
|
5951
|
+
db.setTenantContext({ tenantId: ctxTenantID, userId: ctxUser?.id ?? "", role: ctxUser?.role, isSuperAdmin: ctxUser?.role === "super_admin" });
|
|
5952
|
+
}
|
|
5288
5953
|
const id = c.req.param("id");
|
|
5289
5954
|
const body = await c.req.json().catch(() => ({}));
|
|
5290
5955
|
const baseUpdatedAt = readBaseUpdatedAt(body);
|
|
@@ -5300,9 +5965,9 @@ function createHonoApp(options) {
|
|
|
5300
5965
|
if (baseUpdatedAt && originalDoc.updatedAt && baseUpdatedAt !== originalDoc.updatedAt) {
|
|
5301
5966
|
return c.json(buildConflictResponse(baseUpdatedAt, originalDoc), 409);
|
|
5302
5967
|
}
|
|
5303
|
-
let publishData = {
|
|
5968
|
+
let publishData = { status: "published" };
|
|
5304
5969
|
let finalContent = originalDoc;
|
|
5305
|
-
if (
|
|
5970
|
+
if (collection.versions?.drafts) {
|
|
5306
5971
|
const versions = await db.findVersions({
|
|
5307
5972
|
collection: slug,
|
|
5308
5973
|
documentId: id,
|
|
@@ -5310,9 +5975,10 @@ function createHonoApp(options) {
|
|
|
5310
5975
|
sort: "-createdAt",
|
|
5311
5976
|
tenantID: ctxTenantID
|
|
5312
5977
|
});
|
|
5313
|
-
if (versions.docs.length > 0
|
|
5314
|
-
|
|
5315
|
-
|
|
5978
|
+
if (versions.docs.length > 0) {
|
|
5979
|
+
const latestVersion = versions.docs[0];
|
|
5980
|
+
finalContent = { ...originalDoc, ...latestVersion.data };
|
|
5981
|
+
publishData = { ...latestVersion.data, ...publishData };
|
|
5316
5982
|
}
|
|
5317
5983
|
}
|
|
5318
5984
|
const doc = await db.update({
|
|
@@ -5325,18 +5991,13 @@ function createHonoApp(options) {
|
|
|
5325
5991
|
await db.createVersion({
|
|
5326
5992
|
collection: slug,
|
|
5327
5993
|
documentId: id,
|
|
5328
|
-
data: { ...finalContent,
|
|
5994
|
+
data: { ...finalContent, status: "published" },
|
|
5329
5995
|
status: "published",
|
|
5330
5996
|
createdBy: ctxUser?.id,
|
|
5331
5997
|
changeDescription: "Published",
|
|
5332
5998
|
tenantID: ctxTenantID
|
|
5333
5999
|
});
|
|
5334
6000
|
}
|
|
5335
|
-
await db.deleteDraft({
|
|
5336
|
-
collection: slug,
|
|
5337
|
-
documentId: id,
|
|
5338
|
-
tenantID: ctxTenantID
|
|
5339
|
-
});
|
|
5340
6001
|
if (webhookService) {
|
|
5341
6002
|
webhookService.trigger(getWebhookEvent(slug, "update"), {
|
|
5342
6003
|
collection: slug,
|
|
@@ -5384,7 +6045,7 @@ function createHonoApp(options) {
|
|
|
5384
6045
|
const doc = await db.update({
|
|
5385
6046
|
collection: slug,
|
|
5386
6047
|
id,
|
|
5387
|
-
data: {
|
|
6048
|
+
data: { status: "draft" },
|
|
5388
6049
|
tenantID: ctxTenantID
|
|
5389
6050
|
});
|
|
5390
6051
|
if (webhookService) {
|
|
@@ -5419,13 +6080,31 @@ function createHonoApp(options) {
|
|
|
5419
6080
|
if (!access.allowed) {
|
|
5420
6081
|
return c.json({ error: access.error }, access.status || 403);
|
|
5421
6082
|
}
|
|
5422
|
-
const isDraftRequest =
|
|
5423
|
-
|
|
6083
|
+
const isDraftRequest = !!ctxUser;
|
|
6084
|
+
let doc = await db.findOne({
|
|
5424
6085
|
collection: `_globals_${slug}`,
|
|
5425
6086
|
where: {},
|
|
5426
6087
|
tenantID: ctxTenantID,
|
|
5427
6088
|
draft: isDraftRequest
|
|
5428
6089
|
});
|
|
6090
|
+
if (slug === "system") {
|
|
6091
|
+
const newSecret = crypto__default.default.randomBytes(32).toString("hex");
|
|
6092
|
+
if (!doc) {
|
|
6093
|
+
doc = await db.create({
|
|
6094
|
+
collection: `_globals_${slug}`,
|
|
6095
|
+
data: { id: slug, appSecret: newSecret },
|
|
6096
|
+
tenantID: ctxTenantID
|
|
6097
|
+
});
|
|
6098
|
+
} else if (!doc.appSecret) {
|
|
6099
|
+
await db.update({
|
|
6100
|
+
collection: `_globals_${slug}`,
|
|
6101
|
+
id: slug,
|
|
6102
|
+
data: { appSecret: newSecret },
|
|
6103
|
+
tenantID: ctxTenantID
|
|
6104
|
+
});
|
|
6105
|
+
doc.appSecret = newSecret;
|
|
6106
|
+
}
|
|
6107
|
+
}
|
|
5429
6108
|
return c.json({ data: doc || {} });
|
|
5430
6109
|
} catch (error) {
|
|
5431
6110
|
return c.json({ error: error.message }, 500);
|
|
@@ -5445,18 +6124,20 @@ function createHonoApp(options) {
|
|
|
5445
6124
|
if (!access.allowed) {
|
|
5446
6125
|
return c.json({ error: access.error }, access.status || 403);
|
|
5447
6126
|
}
|
|
5448
|
-
const body = await c.req.json();
|
|
6127
|
+
const body = omitRevisionFields(await c.req.json());
|
|
5449
6128
|
const cleaned = Object.fromEntries(
|
|
5450
6129
|
Object.entries(body).filter(([_, v]) => v !== null && v !== "null" && v !== void 0)
|
|
5451
6130
|
);
|
|
5452
|
-
|
|
6131
|
+
normalizeEmptyStrings(cleaned, globalConfig.fields);
|
|
6132
|
+
convertRichtextFields(globalConfig.fields, cleaned);
|
|
6133
|
+
const schema = registry.getUpdateZodSchema(slug);
|
|
5453
6134
|
let validated;
|
|
5454
6135
|
try {
|
|
5455
6136
|
validated = schema.parse(cleaned);
|
|
5456
6137
|
} catch (zodErr) {
|
|
5457
|
-
return c.json({ error:
|
|
6138
|
+
return c.json({ error: `Validation failed: ${formatZodErrors(zodErr.errors)}`, details: zodErr.errors }, 400);
|
|
5458
6139
|
}
|
|
5459
|
-
const SYSTEM_FIELDS = /* @__PURE__ */ new Set(["id", "createdAt", "updatedAt", "
|
|
6140
|
+
const SYSTEM_FIELDS = /* @__PURE__ */ new Set(["id", "createdAt", "updatedAt", "status", "baseUpdatedAt", "_baseUpdatedAt"]);
|
|
5460
6141
|
const userData = Object.fromEntries(
|
|
5461
6142
|
Object.entries(validated).filter(([k]) => !SYSTEM_FIELDS.has(k))
|
|
5462
6143
|
);
|
|
@@ -5467,49 +6148,68 @@ function createHonoApp(options) {
|
|
|
5467
6148
|
tenantID: ctxTenantID,
|
|
5468
6149
|
draft: true
|
|
5469
6150
|
});
|
|
6151
|
+
const isDraft = c.req.header("X-Draft") === "true";
|
|
5470
6152
|
const isDraftEnabled = globalConfig.versions?.drafts === true;
|
|
5471
|
-
const
|
|
6153
|
+
const isAutosave = c.req.query("autosave") === "true";
|
|
5472
6154
|
let doc;
|
|
5473
|
-
if (isDraftEnabled &&
|
|
6155
|
+
if (isDraftEnabled && isDraft) {
|
|
5474
6156
|
await db.createVersion({
|
|
5475
6157
|
collection: collectionSlug,
|
|
5476
6158
|
documentId: slug,
|
|
5477
6159
|
data: userData,
|
|
5478
6160
|
status: "draft",
|
|
6161
|
+
autosave: isAutosave,
|
|
5479
6162
|
createdBy: ctxUser?.id,
|
|
5480
|
-
changeDescription: "Manual save
|
|
5481
|
-
tenantID: ctxTenantID
|
|
5482
|
-
});
|
|
5483
|
-
doc = await db.update({
|
|
5484
|
-
collection: collectionSlug,
|
|
5485
|
-
id: slug,
|
|
5486
|
-
data: { _has_draft: true },
|
|
6163
|
+
changeDescription: isAutosave ? "Autosave" : "Manual save",
|
|
5487
6164
|
tenantID: ctxTenantID
|
|
5488
6165
|
});
|
|
5489
|
-
|
|
5490
|
-
|
|
6166
|
+
if (!originalDoc) {
|
|
6167
|
+
doc = await db.create({
|
|
6168
|
+
collection: collectionSlug,
|
|
6169
|
+
data: { ...userData, id: slug, status: "draft" },
|
|
6170
|
+
tenantID: ctxTenantID
|
|
6171
|
+
});
|
|
6172
|
+
} else {
|
|
6173
|
+
doc = originalDoc;
|
|
6174
|
+
}
|
|
6175
|
+
} else if (isDraftEnabled && !isDraft) {
|
|
6176
|
+
const publishStatus = "published";
|
|
5491
6177
|
if (originalDoc) {
|
|
5492
6178
|
doc = await db.update({
|
|
5493
6179
|
collection: collectionSlug,
|
|
5494
6180
|
id: slug,
|
|
5495
|
-
data:
|
|
6181
|
+
data: { ...userData, status: publishStatus },
|
|
5496
6182
|
tenantID: ctxTenantID
|
|
5497
6183
|
});
|
|
5498
6184
|
} else {
|
|
5499
6185
|
doc = await db.create({
|
|
5500
6186
|
collection: collectionSlug,
|
|
5501
|
-
data: { ...
|
|
6187
|
+
data: { ...userData, id: slug, status: publishStatus },
|
|
5502
6188
|
tenantID: ctxTenantID
|
|
5503
6189
|
});
|
|
5504
6190
|
}
|
|
5505
|
-
|
|
5506
|
-
|
|
6191
|
+
await db.createVersion({
|
|
6192
|
+
collection: collectionSlug,
|
|
6193
|
+
documentId: slug,
|
|
6194
|
+
data: userData,
|
|
6195
|
+
status: publishStatus,
|
|
6196
|
+
autosave: false,
|
|
6197
|
+
createdBy: ctxUser?.id,
|
|
6198
|
+
changeDescription: "Published",
|
|
6199
|
+
tenantID: ctxTenantID
|
|
6200
|
+
});
|
|
6201
|
+
} else {
|
|
6202
|
+
if (originalDoc) {
|
|
6203
|
+
doc = await db.update({
|
|
5507
6204
|
collection: collectionSlug,
|
|
5508
|
-
|
|
6205
|
+
id: slug,
|
|
5509
6206
|
data: userData,
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
6207
|
+
tenantID: ctxTenantID
|
|
6208
|
+
});
|
|
6209
|
+
} else {
|
|
6210
|
+
doc = await db.create({
|
|
6211
|
+
collection: collectionSlug,
|
|
6212
|
+
data: { ...userData, id: slug },
|
|
5513
6213
|
tenantID: ctxTenantID
|
|
5514
6214
|
});
|
|
5515
6215
|
}
|
|
@@ -5518,6 +6218,22 @@ function createHonoApp(options) {
|
|
|
5518
6218
|
mediaService = null;
|
|
5519
6219
|
mediaServiceInitError = null;
|
|
5520
6220
|
}
|
|
6221
|
+
if (slug === "email-settings") {
|
|
6222
|
+
const newEmailTransport = await chunkY7AQK4R4_cjs.EmailTransport.fromConfig(db);
|
|
6223
|
+
authRoutes.email = newEmailTransport || void 0;
|
|
6224
|
+
}
|
|
6225
|
+
if (slug === "system") {
|
|
6226
|
+
await loadSecrets();
|
|
6227
|
+
}
|
|
6228
|
+
if (ctxUser) {
|
|
6229
|
+
sessionAuthAdapter?.createAuditLog({
|
|
6230
|
+
action: "settings_change",
|
|
6231
|
+
userId: ctxUser.id,
|
|
6232
|
+
resource: `global:${slug}`,
|
|
6233
|
+
resourceId: slug,
|
|
6234
|
+
success: true
|
|
6235
|
+
});
|
|
6236
|
+
}
|
|
5521
6237
|
return c.json({ data: doc, message: "Updated successfully" });
|
|
5522
6238
|
} catch (error) {
|
|
5523
6239
|
console.error(`[API] Save global "${slug}" failed:`, error);
|
|
@@ -5542,9 +6258,9 @@ function createHonoApp(options) {
|
|
|
5542
6258
|
draft: true
|
|
5543
6259
|
});
|
|
5544
6260
|
if (!originalDoc) return c.json({ error: "Global not found" }, 404);
|
|
5545
|
-
let publishData = {
|
|
6261
|
+
let publishData = { status: "published" };
|
|
5546
6262
|
let finalContent = originalDoc;
|
|
5547
|
-
if (
|
|
6263
|
+
if (globalConfig.versions?.drafts) {
|
|
5548
6264
|
const versions = await db.findVersions({
|
|
5549
6265
|
collection: collectionSlug,
|
|
5550
6266
|
documentId: slug,
|
|
@@ -5552,7 +6268,7 @@ function createHonoApp(options) {
|
|
|
5552
6268
|
sort: "-createdAt",
|
|
5553
6269
|
tenantID: ctxTenantID
|
|
5554
6270
|
});
|
|
5555
|
-
if (versions.docs.length > 0
|
|
6271
|
+
if (versions.docs.length > 0) {
|
|
5556
6272
|
finalContent = { ...originalDoc, ...versions.docs[0].data };
|
|
5557
6273
|
publishData = { ...versions.docs[0].data, ...publishData };
|
|
5558
6274
|
}
|
|
@@ -5567,7 +6283,7 @@ function createHonoApp(options) {
|
|
|
5567
6283
|
await db.createVersion({
|
|
5568
6284
|
collection: collectionSlug,
|
|
5569
6285
|
documentId: slug,
|
|
5570
|
-
data: { ...finalContent,
|
|
6286
|
+
data: { ...finalContent, status: "published" },
|
|
5571
6287
|
status: "published",
|
|
5572
6288
|
createdBy: ctxUser?.id,
|
|
5573
6289
|
changeDescription: "Published",
|
|
@@ -5587,7 +6303,7 @@ function createHonoApp(options) {
|
|
|
5587
6303
|
const doc = await db.update({
|
|
5588
6304
|
collection: `_globals_${slug}`,
|
|
5589
6305
|
id: slug,
|
|
5590
|
-
data: {
|
|
6306
|
+
data: { status: "draft" },
|
|
5591
6307
|
tenantID: ctxTenantID
|
|
5592
6308
|
});
|
|
5593
6309
|
return c.json({ data: doc, message: "Unpublished successfully" });
|
|
@@ -5647,9 +6363,13 @@ function createHonoApp(options) {
|
|
|
5647
6363
|
const doc = await db.update({
|
|
5648
6364
|
collection: collectionSlug,
|
|
5649
6365
|
id: slug,
|
|
5650
|
-
data: { ...version.data,
|
|
6366
|
+
data: { ...version.data, status: "draft" },
|
|
5651
6367
|
tenantID: ctxTenantID
|
|
5652
6368
|
});
|
|
6369
|
+
return c.json({
|
|
6370
|
+
data: doc,
|
|
6371
|
+
message: "Version restored successfully"
|
|
6372
|
+
});
|
|
5653
6373
|
return c.json({ data: doc, message: "Restored successfully" });
|
|
5654
6374
|
} catch (error) {
|
|
5655
6375
|
return c.json({ error: error.message }, 500);
|
|
@@ -5693,7 +6413,7 @@ function createHonoApp(options) {
|
|
|
5693
6413
|
mailgun: body.mailgun,
|
|
5694
6414
|
ses: body.ses
|
|
5695
6415
|
};
|
|
5696
|
-
const transport = new
|
|
6416
|
+
const transport = new chunkY7AQK4R4_cjs.EmailTransport(transportConfig);
|
|
5697
6417
|
const recipient = body.testEmail || body.testEmailSection && body.testEmailSection.testEmail;
|
|
5698
6418
|
if (!recipient) {
|
|
5699
6419
|
return c.json({ error: "No test recipient email provided" }, 400);
|
|
@@ -5757,15 +6477,19 @@ exports.InMemoryAuditLogger = InMemoryAuditLogger;
|
|
|
5757
6477
|
exports.InMemoryRateLimiter = InMemoryRateLimiter;
|
|
5758
6478
|
exports.MediaService = MediaService;
|
|
5759
6479
|
exports.createAuditContext = createAuditContext2;
|
|
6480
|
+
exports.createCloudinaryStorage = createCloudinaryStorage;
|
|
6481
|
+
exports.createFtpStorage = createFtpStorage;
|
|
5760
6482
|
exports.createHonoApp = createHonoApp;
|
|
5761
6483
|
exports.createLocalStorage = createLocalStorage;
|
|
5762
6484
|
exports.createRESTAPI = createRESTAPI;
|
|
6485
|
+
exports.createS3Storage = createS3Storage;
|
|
5763
6486
|
exports.getAppSecret = getAppSecret;
|
|
6487
|
+
exports.getDefaultRegistry = getDefaultRegistry;
|
|
5764
6488
|
exports.getEncryptionKey = getEncryptionKey;
|
|
5765
6489
|
exports.getSessionConfig = getSessionConfig;
|
|
5766
6490
|
exports.init_secret = init_secret;
|
|
5767
6491
|
exports.loadSecrets = loadSecrets;
|
|
5768
6492
|
exports.resolveProvider = resolveProvider;
|
|
5769
6493
|
exports.setDbAdapter = setDbAdapter;
|
|
5770
|
-
//# sourceMappingURL=chunk-
|
|
5771
|
-
//# sourceMappingURL=chunk-
|
|
6494
|
+
//# sourceMappingURL=chunk-CNKT4PME.cjs.map
|
|
6495
|
+
//# sourceMappingURL=chunk-CNKT4PME.cjs.map
|