@kyro-cms/core 0.8.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -589
- package/dist/{WebhookService-118ZTFis.d.ts → WebhookService-CUTb9XOy.d.ts} +1 -1
- package/dist/{WebhookService-AefJfqX0.d.cts → WebhookService-Yg2UEOB4.d.cts} +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 +31 -97
- 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 +19 -95
- package/dist/api-handler.js.map +1 -1
- package/dist/{tenant-B1YB0Jy8.d.ts → base-B71y_EAF.d.cts} +6 -12
- package/dist/{tenant-Cpeveji6.d.cts → base-DaqY2GhA.d.ts} +6 -12
- package/dist/bootstrap-5NLASFOG.cjs +32 -0
- package/dist/{bootstrap-AKAUP6F6.cjs.map → bootstrap-5NLASFOG.cjs.map} +1 -1
- package/dist/bootstrap-T5BK77LD.js +7 -0
- package/dist/{bootstrap-JCML6NFO.js.map → bootstrap-T5BK77LD.js.map} +1 -1
- package/dist/{chunk-35U3FROB.js → chunk-22M4O4ZJ.js} +607 -63
- package/dist/chunk-22M4O4ZJ.js.map +1 -0
- package/dist/chunk-2HZRBATX.cjs +253 -0
- package/dist/chunk-2HZRBATX.cjs.map +1 -0
- 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-4M7X5HAB.cjs +173 -0
- package/dist/chunk-4M7X5HAB.cjs.map +1 -0
- package/dist/chunk-5EPFQUQD.js +3243 -0
- package/dist/chunk-5EPFQUQD.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-WOWUL7ZY.js → chunk-AL5KX63J.js} +4 -3
- package/dist/chunk-AL5KX63J.js.map +1 -0
- package/dist/{chunk-2OL4O2TH.cjs → chunk-C36TMDTY.cjs} +66 -61
- package/dist/chunk-C36TMDTY.cjs.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-2KVHZE6O.cjs → chunk-COIASRDK.cjs} +202 -46
- package/dist/chunk-COIASRDK.cjs.map +1 -0
- package/dist/chunk-DEVFAKCQ.cjs +3291 -0
- package/dist/chunk-DEVFAKCQ.cjs.map +1 -0
- package/dist/{chunk-3ZFYL34R.js → chunk-DYTZ6FQ7.js} +12 -185
- package/dist/chunk-DYTZ6FQ7.js.map +1 -0
- package/dist/{chunk-QPPDLRNR.js → chunk-EJN2PAOE.js} +197 -41
- package/dist/chunk-EJN2PAOE.js.map +1 -0
- package/dist/chunk-FAXU7BMP.js +220 -0
- package/dist/chunk-FAXU7BMP.js.map +1 -0
- package/dist/{chunk-OHVB4AJ7.js → chunk-FOPGUM27.js} +22 -17
- package/dist/chunk-FOPGUM27.js.map +1 -0
- package/dist/chunk-GAOXD3XT.js +175 -0
- package/dist/chunk-GAOXD3XT.js.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-WQBRWOQT.cjs → chunk-JOPVMWTM.cjs} +3 -2
- package/dist/chunk-JOPVMWTM.cjs.map +1 -0
- package/dist/chunk-KC2GDBLS.cjs +84 -0
- package/dist/chunk-KC2GDBLS.cjs.map +1 -0
- package/dist/{chunk-K7JPTH3G.cjs → chunk-KNRSROWB.cjs} +132 -74
- package/dist/chunk-KNRSROWB.cjs.map +1 -0
- package/dist/{chunk-3AJE4SEG.js → chunk-KPA4AN4R.js} +125 -67
- package/dist/chunk-KPA4AN4R.js.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-Y3QQN7PN.js → chunk-P2HKJ7P5.js} +13 -4
- package/dist/chunk-P2HKJ7P5.js.map +1 -0
- package/dist/{chunk-SA7NSSIQ.cjs → chunk-PI73NNOK.cjs} +13 -187
- package/dist/chunk-PI73NNOK.cjs.map +1 -0
- package/dist/{chunk-HXRD4B37.js → chunk-PU2Z5VWF.js} +1279 -556
- package/dist/chunk-PU2Z5VWF.js.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-YVUJBEXE.cjs → chunk-RAMGUDJN.cjs} +16 -7
- package/dist/chunk-RAMGUDJN.cjs.map +1 -0
- package/dist/{chunk-LINKCEG4.cjs → chunk-ROJHKAQ4.cjs} +617 -73
- package/dist/chunk-ROJHKAQ4.cjs.map +1 -0
- package/dist/{chunk-5KVM3WEY.cjs → chunk-RSF3UU7H.cjs} +1330 -602
- package/dist/chunk-RSF3UU7H.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-SPBTLUN6.js +92 -0
- package/dist/chunk-SPBTLUN6.js.map +1 -0
- package/dist/{chunk-57P6MJKC.js → chunk-TXSZFA4G.js} +3 -3
- package/dist/chunk-TXSZFA4G.js.map +1 -0
- package/dist/chunk-UERVXYVK.cjs +99 -0
- package/dist/chunk-UERVXYVK.cjs.map +1 -0
- package/dist/{chunk-PDYFVNUX.cjs → chunk-V2TVSCV5.cjs} +16 -23
- package/dist/chunk-V2TVSCV5.cjs.map +1 -0
- package/dist/{chunk-DXHRBMGB.js → chunk-VO35MNPH.js} +12 -19
- package/dist/chunk-VO35MNPH.js.map +1 -0
- package/dist/{chunk-IA6AU5PI.cjs → chunk-WNCYAKF3.cjs} +3 -3
- package/dist/chunk-WNCYAKF3.cjs.map +1 -0
- package/dist/chunk-XEB7PH2E.js +81 -0
- package/dist/chunk-XEB7PH2E.js.map +1 -0
- package/dist/cli/index.cjs +5 -5
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +5 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/client.cjs +3 -3
- package/dist/client.d.cts +3 -3
- package/dist/client.d.ts +3 -3
- package/dist/client.js +1 -1
- package/dist/drizzle/index.cjs +14 -13
- package/dist/drizzle/index.d.cts +9 -7
- package/dist/drizzle/index.d.ts +9 -7
- package/dist/drizzle/index.js +5 -4
- package/dist/fields/index.cjs +21 -37
- package/dist/fields/index.d.cts +2 -22
- package/dist/fields/index.d.ts +2 -22
- package/dist/fields/index.js +1 -1
- package/dist/graphql/index.cjs +5 -4
- package/dist/graphql/index.d.cts +5 -3
- package/dist/graphql/index.d.ts +5 -3
- package/dist/graphql/index.js +3 -2
- package/dist/index-CJXPB_ot.d.ts +276 -0
- package/dist/index-CaTNnLGd.d.cts +276 -0
- package/dist/index.cjs +304 -162
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +129 -205
- package/dist/index.d.ts +129 -205
- package/dist/index.js +172 -33
- package/dist/index.js.map +1 -1
- package/dist/integration.cjs +2 -2
- package/dist/integration.js +1 -1
- package/dist/mongo-auth-adapter-ISOM7FSS.cjs +17 -0
- package/dist/{mongo-auth-adapter-NHHUJHVH.cjs.map → mongo-auth-adapter-ISOM7FSS.cjs.map} +1 -1
- package/dist/mongo-auth-adapter-MO6STCV3.js +4 -0
- package/dist/{mongo-auth-adapter-NJQUUCTP.js.map → mongo-auth-adapter-MO6STCV3.js.map} +1 -1
- package/dist/mongodb/index.cjs +8 -7
- package/dist/mongodb/index.d.cts +5 -7
- package/dist/mongodb/index.d.ts +5 -7
- package/dist/mongodb/index.js +4 -3
- package/dist/postgres-auth-adapter-DWDR7P5G.js +5 -0
- package/dist/{postgres-auth-adapter-3T2NKTSE.js.map → postgres-auth-adapter-DWDR7P5G.js.map} +1 -1
- package/dist/postgres-auth-adapter-WRWSJD4E.cjs +14 -0
- package/dist/{postgres-auth-adapter-7IEENCKQ.cjs.map → postgres-auth-adapter-WRWSJD4E.cjs.map} +1 -1
- package/dist/redis-adapter-HGTPWIGV.js +4 -0
- package/dist/{redis-adapter-VQXD7ESY.js.map → redis-adapter-HGTPWIGV.js.map} +1 -1
- package/dist/redis-adapter-KJ3YOOT6.cjs +13 -0
- package/dist/{redis-adapter-D2E2S3GB.cjs.map → redis-adapter-KJ3YOOT6.cjs.map} +1 -1
- package/dist/rest/index.cjs +15 -14
- package/dist/rest/index.d.cts +4 -4
- package/dist/rest/index.d.ts +4 -4
- package/dist/rest/index.js +13 -12
- package/dist/{schema-5PHL5IVB.js → schema-6I5OFR4Z.js} +3 -3
- package/dist/{schema-5PHL5IVB.js.map → schema-6I5OFR4Z.js.map} +1 -1
- package/dist/{schema-37SE2F4B.cjs → schema-TTFE4467.cjs} +14 -14
- package/dist/{schema-37SE2F4B.cjs.map → schema-TTFE4467.cjs.map} +1 -1
- package/dist/sqlite-adapter-6GEUSVXQ.js +4 -0
- package/dist/{sqlite-adapter-TR3U3W6Q.js.map → sqlite-adapter-6GEUSVXQ.js.map} +1 -1
- package/dist/sqlite-adapter-CSIZE5SX.cjs +13 -0
- package/dist/{sqlite-adapter-LVK5PS4T.cjs.map → sqlite-adapter-CSIZE5SX.cjs.map} +1 -1
- package/dist/templates/index.cjs +133 -31
- package/dist/templates/index.d.cts +52 -9
- package/dist/templates/index.d.ts +52 -9
- package/dist/templates/index.js +3 -1
- package/dist/trpc/index.cjs +13 -12
- package/dist/trpc/index.d.cts +55 -49
- package/dist/trpc/index.d.ts +55 -49
- package/dist/trpc/index.js +4 -3
- 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-Bs1up4yP.d.ts → types-CyCQ6SAI.d.ts} +28 -2
- package/dist/{types-J3R9nVsZ.d.cts → types-DJxD9394.d.cts} +28 -2
- package/dist/{types-VtjUxIMp.d.cts → types-Z6FBiqa2.d.cts} +35 -14
- package/dist/{types-VtjUxIMp.d.ts → types-Z6FBiqa2.d.ts} +35 -14
- 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-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-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/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,16 +1,16 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
3
|
+
var chunkWNCYAKF3_cjs = require('./chunk-WNCYAKF3.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 chunkSA7NSSIQ_cjs = require('./chunk-SA7NSSIQ.cjs');
|
|
7
|
+
var chunkQFLB4EIJ_cjs = require('./chunk-QFLB4EIJ.cjs');
|
|
8
|
+
var chunk4M7X5HAB_cjs = require('./chunk-4M7X5HAB.cjs');
|
|
9
|
+
var chunkCOIASRDK_cjs = require('./chunk-COIASRDK.cjs');
|
|
10
|
+
var chunkC36TMDTY_cjs = require('./chunk-C36TMDTY.cjs');
|
|
11
|
+
var chunkV2TVSCV5_cjs = require('./chunk-V2TVSCV5.cjs');
|
|
12
|
+
var chunkGXFOGU7N_cjs = require('./chunk-GXFOGU7N.cjs');
|
|
13
|
+
var chunkNKPKR5BW_cjs = require('./chunk-NKPKR5BW.cjs');
|
|
14
14
|
var chunkG7VZBCD6_cjs = require('./chunk-G7VZBCD6.cjs');
|
|
15
15
|
var crypto = require('crypto');
|
|
16
16
|
var unstorage = require('unstorage');
|
|
@@ -19,11 +19,12 @@ 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
25
|
var stream = require('stream');
|
|
26
26
|
var basicFtp = require('basic-ftp');
|
|
27
|
+
var zodToJsonSchema = require('zod-to-json-schema');
|
|
27
28
|
|
|
28
29
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
29
30
|
|
|
@@ -31,6 +32,7 @@ var crypto__default = /*#__PURE__*/_interopDefault(crypto);
|
|
|
31
32
|
var fsDriver__default = /*#__PURE__*/_interopDefault(fsDriver);
|
|
32
33
|
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
33
34
|
var sharp__default = /*#__PURE__*/_interopDefault(sharp);
|
|
35
|
+
var process2__default = /*#__PURE__*/_interopDefault(process2);
|
|
34
36
|
|
|
35
37
|
function setDbAdapter(adapter) {
|
|
36
38
|
dbAdapter = adapter;
|
|
@@ -388,14 +390,14 @@ function createAuthMiddleware(config) {
|
|
|
388
390
|
userLookup
|
|
389
391
|
} = config;
|
|
390
392
|
return async function authMiddleware(req) {
|
|
391
|
-
const apiKeyRaw =
|
|
393
|
+
const apiKeyRaw = chunk4M7X5HAB_cjs.extractApiKeyFromRequest(req);
|
|
392
394
|
if (apiKeyRaw && db) {
|
|
393
|
-
const result = await
|
|
395
|
+
const result = await chunk4M7X5HAB_cjs.validateApiKey(apiKeyRaw, db, userLookup);
|
|
394
396
|
if (result.valid && result.user) {
|
|
395
397
|
return {
|
|
396
398
|
user: result.user,
|
|
397
399
|
tenantContext: createTenantContextFromUser(result.user),
|
|
398
|
-
apiKeyContext:
|
|
400
|
+
apiKeyContext: chunk4M7X5HAB_cjs.createApiKeyContext(result),
|
|
399
401
|
status: 200,
|
|
400
402
|
authType: "apikey"
|
|
401
403
|
};
|
|
@@ -782,7 +784,7 @@ var AuditLogger = class {
|
|
|
782
784
|
serializeLog(log) {
|
|
783
785
|
const result = {
|
|
784
786
|
id: log.id,
|
|
785
|
-
timestamp: log.timestamp.toISOString(),
|
|
787
|
+
timestamp: new Date(log.timestamp).toISOString(),
|
|
786
788
|
action: log.action,
|
|
787
789
|
resource: log.resource,
|
|
788
790
|
success: log.success ? "1" : "0"
|
|
@@ -957,7 +959,7 @@ var AuthRoutes = class {
|
|
|
957
959
|
constructor(config) {
|
|
958
960
|
this.authAdapter = config.redis;
|
|
959
961
|
this.email = config.email;
|
|
960
|
-
this.passwordPolicy = config.passwordPolicy || new
|
|
962
|
+
this.passwordPolicy = config.passwordPolicy || new chunkWNCYAKF3_cjs.PasswordPolicy();
|
|
961
963
|
this.lockout = config.lockout;
|
|
962
964
|
this.rateLimiter = config.rateLimiter;
|
|
963
965
|
this.auditLogger = config.auditLogger;
|
|
@@ -1177,23 +1179,19 @@ var AuthRoutes = class {
|
|
|
1177
1179
|
}
|
|
1178
1180
|
}
|
|
1179
1181
|
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);
|
|
1182
|
+
const session = await getCurrentUser(req);
|
|
1183
|
+
if (!session) {
|
|
1184
|
+
return this.errorResponse("Not authenticated", 401);
|
|
1196
1185
|
}
|
|
1186
|
+
return this.jsonResponse({
|
|
1187
|
+
success: true,
|
|
1188
|
+
user: {
|
|
1189
|
+
id: session.userId,
|
|
1190
|
+
email: session.email,
|
|
1191
|
+
role: session.role,
|
|
1192
|
+
tenantId: session.tenantId
|
|
1193
|
+
}
|
|
1194
|
+
});
|
|
1197
1195
|
}
|
|
1198
1196
|
async changePassword(req) {
|
|
1199
1197
|
const session = await getCurrentUser(req);
|
|
@@ -1303,7 +1301,7 @@ var AuthRoutes = class {
|
|
|
1303
1301
|
}
|
|
1304
1302
|
if (this.auditLogger) {
|
|
1305
1303
|
await this.auditLogger.log({
|
|
1306
|
-
action: "
|
|
1304
|
+
action: "password_reset",
|
|
1307
1305
|
userId: user.id,
|
|
1308
1306
|
userEmail: user.email,
|
|
1309
1307
|
resource: "auth",
|
|
@@ -1491,7 +1489,7 @@ var AuthRoutes = class {
|
|
|
1491
1489
|
}
|
|
1492
1490
|
};
|
|
1493
1491
|
function createLocalStorage(config) {
|
|
1494
|
-
const { uploadDir, baseUrl = "/uploads" } = config;
|
|
1492
|
+
const { uploadDir = path.join(process2__default.default.cwd(), "public", "uploads"), baseUrl = "/uploads" } = config;
|
|
1495
1493
|
async function ensureDir(dir) {
|
|
1496
1494
|
if (!fs.existsSync(dir)) {
|
|
1497
1495
|
await promises.mkdir(dir, { recursive: true });
|
|
@@ -1679,6 +1677,467 @@ function createLocalStorage(config) {
|
|
|
1679
1677
|
}
|
|
1680
1678
|
};
|
|
1681
1679
|
}
|
|
1680
|
+
|
|
1681
|
+
// src/storage/imgix.ts
|
|
1682
|
+
function createImgixStorage(config) {
|
|
1683
|
+
const signUrl = (path3, params) => {
|
|
1684
|
+
if (!config.signKey) return path3;
|
|
1685
|
+
const signer = new TextEncoder();
|
|
1686
|
+
const key = signer.encode(config.signKey);
|
|
1687
|
+
const data = signer.encode(path3 + params.toString());
|
|
1688
|
+
let hash = 0;
|
|
1689
|
+
const combined = new Uint8Array(key.length + data.length);
|
|
1690
|
+
combined.set(key);
|
|
1691
|
+
combined.set(data, key.length);
|
|
1692
|
+
for (let i = 0; i < combined.length; i++) {
|
|
1693
|
+
hash = (hash << 5) - hash + combined[i] | 0;
|
|
1694
|
+
}
|
|
1695
|
+
params.set("s", Math.abs(hash).toString(16));
|
|
1696
|
+
return path3;
|
|
1697
|
+
};
|
|
1698
|
+
return {
|
|
1699
|
+
name: "imgix",
|
|
1700
|
+
displayName: "Imgix",
|
|
1701
|
+
supportsDynamicResize: true,
|
|
1702
|
+
async upload(_file, _options) {
|
|
1703
|
+
throw new Error(
|
|
1704
|
+
"Imgix is a transformation service. Use another provider for uploads."
|
|
1705
|
+
);
|
|
1706
|
+
},
|
|
1707
|
+
async uploadFromUrl(url, options) {
|
|
1708
|
+
const filename = options?.filename || url.split("/").pop() || "file";
|
|
1709
|
+
const response = await fetch(url);
|
|
1710
|
+
if (!response.ok) {
|
|
1711
|
+
throw new Error(`Failed to fetch: ${response.statusText}`);
|
|
1712
|
+
}
|
|
1713
|
+
const blob = await response.blob();
|
|
1714
|
+
new File([blob], filename, { type: blob.type });
|
|
1715
|
+
return {
|
|
1716
|
+
id: Buffer.from(url).toString("base64").slice(0, 20),
|
|
1717
|
+
filename,
|
|
1718
|
+
originalName: filename,
|
|
1719
|
+
mimeType: blob.type,
|
|
1720
|
+
size: blob.size,
|
|
1721
|
+
url: this.getImageUrl(url),
|
|
1722
|
+
thumbnailUrl: this.getImageUrl(url, {
|
|
1723
|
+
width: 200,
|
|
1724
|
+
height: 200,
|
|
1725
|
+
fit: "crop"
|
|
1726
|
+
}),
|
|
1727
|
+
provider: "imgix",
|
|
1728
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1729
|
+
};
|
|
1730
|
+
},
|
|
1731
|
+
async delete(_url) {
|
|
1732
|
+
},
|
|
1733
|
+
async rename(_oldUrl, newKey) {
|
|
1734
|
+
return `https://${config.domain}/${newKey}`;
|
|
1735
|
+
},
|
|
1736
|
+
getImageUrl(url, transforms) {
|
|
1737
|
+
const parsed = new URL(url);
|
|
1738
|
+
const params = new URLSearchParams(parsed.search);
|
|
1739
|
+
if (config.defaultParameters) {
|
|
1740
|
+
Object.entries(config.defaultParameters).forEach(([key, value]) => {
|
|
1741
|
+
if (!params.has(key)) {
|
|
1742
|
+
params.set(key, value);
|
|
1743
|
+
}
|
|
1744
|
+
});
|
|
1745
|
+
}
|
|
1746
|
+
if (transforms) {
|
|
1747
|
+
if (transforms.width) params.set("w", String(transforms.width));
|
|
1748
|
+
if (transforms.height) params.set("h", String(transforms.height));
|
|
1749
|
+
if (transforms.quality) params.set("q", String(transforms.quality));
|
|
1750
|
+
if (transforms.format) params.set("fm", transforms.format);
|
|
1751
|
+
if (transforms.fit) params.set("fit", transforms.fit);
|
|
1752
|
+
if (transforms.blur) params.set("blur", String(transforms.blur));
|
|
1753
|
+
if (transforms.sharpen) params.set("sharp", String(transforms.sharpen));
|
|
1754
|
+
}
|
|
1755
|
+
params.set("auto", "compress,format");
|
|
1756
|
+
parsed.pathname + "?" + params.toString();
|
|
1757
|
+
if (config.signKey) {
|
|
1758
|
+
signUrl(parsed.pathname + params.toString(), params);
|
|
1759
|
+
}
|
|
1760
|
+
return `https://${config.domain}${parsed.pathname}${params.toString() ? "?" + params.toString() : ""}`;
|
|
1761
|
+
},
|
|
1762
|
+
async generateThumbnail(file) {
|
|
1763
|
+
return this.getImageUrl(file.url, {
|
|
1764
|
+
width: 200,
|
|
1765
|
+
height: 200,
|
|
1766
|
+
fit: "crop"
|
|
1767
|
+
});
|
|
1768
|
+
},
|
|
1769
|
+
async list() {
|
|
1770
|
+
return [];
|
|
1771
|
+
},
|
|
1772
|
+
async exists(url) {
|
|
1773
|
+
try {
|
|
1774
|
+
const response = await fetch(url, { method: "HEAD" });
|
|
1775
|
+
return response.ok;
|
|
1776
|
+
} catch {
|
|
1777
|
+
return false;
|
|
1778
|
+
}
|
|
1779
|
+
},
|
|
1780
|
+
async createFolder() {
|
|
1781
|
+
},
|
|
1782
|
+
async deleteFolder() {
|
|
1783
|
+
}
|
|
1784
|
+
};
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
// src/storage/bunny.ts
|
|
1788
|
+
function getUrl(key, config) {
|
|
1789
|
+
if (config.cdnUrl) {
|
|
1790
|
+
return `${config.cdnUrl.replace(/\/$/, "")}/${key}`;
|
|
1791
|
+
}
|
|
1792
|
+
return `https://${config.storageZone}.b-cdn.net/${key}`;
|
|
1793
|
+
}
|
|
1794
|
+
function getUrlPrefix(config) {
|
|
1795
|
+
if (config.cdnUrl) {
|
|
1796
|
+
return config.cdnUrl.replace(/\/$/, "") + "/";
|
|
1797
|
+
}
|
|
1798
|
+
return `https://${config.storageZone}.b-cdn.net/`;
|
|
1799
|
+
}
|
|
1800
|
+
function createBunnyStorage(config) {
|
|
1801
|
+
const baseUrl = `https://storage.bunnycdn.com/${config.storageZone}`;
|
|
1802
|
+
const getKey = (path3) => {
|
|
1803
|
+
const prefix = config.prefix ? `${config.prefix}/` : "";
|
|
1804
|
+
return `${prefix}${path3}`.replace(/\/+/g, "/");
|
|
1805
|
+
};
|
|
1806
|
+
return {
|
|
1807
|
+
name: "bunny",
|
|
1808
|
+
displayName: "Bunny.net Storage",
|
|
1809
|
+
supportsDynamicResize: true,
|
|
1810
|
+
async upload(file, options) {
|
|
1811
|
+
const key = getKey(
|
|
1812
|
+
`${options?.folder ? `${options.folder}/` : ""}${options?.filename || file.name}`
|
|
1813
|
+
);
|
|
1814
|
+
const buffer = Buffer.from(await file.arrayBuffer());
|
|
1815
|
+
const response = await fetch(`${baseUrl}/${key}`, {
|
|
1816
|
+
method: "PUT",
|
|
1817
|
+
headers: {
|
|
1818
|
+
AccessKey: config.apiKey,
|
|
1819
|
+
"Content-Type": file.type
|
|
1820
|
+
},
|
|
1821
|
+
body: buffer
|
|
1822
|
+
});
|
|
1823
|
+
if (!response.ok) {
|
|
1824
|
+
const errorText = await response.text();
|
|
1825
|
+
throw new Error(
|
|
1826
|
+
`Bunny.net upload failed: ${response.status} ${errorText}`
|
|
1827
|
+
);
|
|
1828
|
+
}
|
|
1829
|
+
return {
|
|
1830
|
+
id: Buffer.from(key).toString("base64url"),
|
|
1831
|
+
filename: options?.filename || file.name,
|
|
1832
|
+
originalName: file.name,
|
|
1833
|
+
mimeType: file.type,
|
|
1834
|
+
size: buffer.length,
|
|
1835
|
+
url: getUrl(key, config),
|
|
1836
|
+
thumbnailUrl: file.type.startsWith("image/") ? getUrl(key, config) : void 0,
|
|
1837
|
+
folder: options?.folder,
|
|
1838
|
+
provider: "bunny",
|
|
1839
|
+
metadata: options?.metadata,
|
|
1840
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1841
|
+
};
|
|
1842
|
+
},
|
|
1843
|
+
async uploadFromUrl(url) {
|
|
1844
|
+
const response = await fetch(url);
|
|
1845
|
+
if (!response.ok) {
|
|
1846
|
+
throw new Error(`Failed to fetch: ${response.statusText}`);
|
|
1847
|
+
}
|
|
1848
|
+
const blob = await response.blob();
|
|
1849
|
+
const filename = url.split("/").pop() || "file";
|
|
1850
|
+
const file = new File([blob], filename, { type: blob.type });
|
|
1851
|
+
return this.upload(file);
|
|
1852
|
+
},
|
|
1853
|
+
async delete(url) {
|
|
1854
|
+
const key = url.replace(getUrlPrefix(config), "");
|
|
1855
|
+
const response = await fetch(`${baseUrl}/${key}`, {
|
|
1856
|
+
method: "DELETE",
|
|
1857
|
+
headers: {
|
|
1858
|
+
AccessKey: config.apiKey
|
|
1859
|
+
}
|
|
1860
|
+
});
|
|
1861
|
+
if (!response.ok && response.status !== 404) {
|
|
1862
|
+
const errorText = await response.text();
|
|
1863
|
+
throw new Error(
|
|
1864
|
+
`Bunny.net delete failed: ${response.status} ${errorText}`
|
|
1865
|
+
);
|
|
1866
|
+
}
|
|
1867
|
+
},
|
|
1868
|
+
async rename(oldUrl, newKey) {
|
|
1869
|
+
const oldKey = oldUrl.replace(getUrlPrefix(config), "");
|
|
1870
|
+
const fullPath = config.prefix ? `${config.prefix}/${newKey}` : newKey;
|
|
1871
|
+
const response = await fetch(`${baseUrl}/${oldKey}`, {
|
|
1872
|
+
method: "GET",
|
|
1873
|
+
headers: {
|
|
1874
|
+
AccessKey: config.apiKey
|
|
1875
|
+
}
|
|
1876
|
+
});
|
|
1877
|
+
if (!response.ok) {
|
|
1878
|
+
throw new Error(`Bunny.net rename failed: could not read old file`);
|
|
1879
|
+
}
|
|
1880
|
+
const content = await response.arrayBuffer();
|
|
1881
|
+
await fetch(`${baseUrl}/${fullPath}`, {
|
|
1882
|
+
method: "PUT",
|
|
1883
|
+
headers: {
|
|
1884
|
+
AccessKey: config.apiKey,
|
|
1885
|
+
"Content-Type": response.headers.get("Content-Type") || "application/octet-stream"
|
|
1886
|
+
},
|
|
1887
|
+
body: content
|
|
1888
|
+
});
|
|
1889
|
+
await this.delete(oldUrl);
|
|
1890
|
+
return getUrl(fullPath, config);
|
|
1891
|
+
},
|
|
1892
|
+
getImageUrl(url, transforms) {
|
|
1893
|
+
if (!transforms || Object.keys(transforms).length === 0) return url;
|
|
1894
|
+
const params = new URLSearchParams({ url });
|
|
1895
|
+
if (transforms.width) params.set("w", String(transforms.width));
|
|
1896
|
+
if (transforms.height) params.set("h", String(transforms.height));
|
|
1897
|
+
if (transforms.quality) params.set("q", String(transforms.quality));
|
|
1898
|
+
if (transforms.format) params.set("f", transforms.format);
|
|
1899
|
+
return `/api/media/resize?${params.toString()}`;
|
|
1900
|
+
},
|
|
1901
|
+
async generateThumbnail(file) {
|
|
1902
|
+
return this.getImageUrl(file.url, { width: 400, height: 400 });
|
|
1903
|
+
},
|
|
1904
|
+
async list(prefix) {
|
|
1905
|
+
const key = getKey(prefix || "");
|
|
1906
|
+
const response = await fetch(`${baseUrl}/${key}`, {
|
|
1907
|
+
method: "GET",
|
|
1908
|
+
headers: {
|
|
1909
|
+
AccessKey: config.apiKey
|
|
1910
|
+
}
|
|
1911
|
+
});
|
|
1912
|
+
if (!response.ok) {
|
|
1913
|
+
const errorText = await response.text();
|
|
1914
|
+
throw new Error(
|
|
1915
|
+
`Bunny.net list failed: ${response.status} ${errorText}`
|
|
1916
|
+
);
|
|
1917
|
+
}
|
|
1918
|
+
const items = await response.json();
|
|
1919
|
+
return items.map((item) => ({
|
|
1920
|
+
id: Buffer.from(item.ObjectName || "").toString("base64url"),
|
|
1921
|
+
filename: item.ObjectName?.split("/").pop() || "",
|
|
1922
|
+
originalName: item.ObjectName?.split("/").pop() || "",
|
|
1923
|
+
mimeType: "application/octet-stream",
|
|
1924
|
+
size: item.Length || 0,
|
|
1925
|
+
url: getUrl(item.ObjectName || "", config),
|
|
1926
|
+
provider: "bunny",
|
|
1927
|
+
createdAt: item.LastChanged || (/* @__PURE__ */ new Date()).toISOString()
|
|
1928
|
+
}));
|
|
1929
|
+
},
|
|
1930
|
+
async exists(url) {
|
|
1931
|
+
const key = url.replace(getUrlPrefix(config), "");
|
|
1932
|
+
const response = await fetch(`${baseUrl}/${key}`, {
|
|
1933
|
+
method: "HEAD",
|
|
1934
|
+
headers: {
|
|
1935
|
+
AccessKey: config.apiKey
|
|
1936
|
+
}
|
|
1937
|
+
});
|
|
1938
|
+
return response.ok;
|
|
1939
|
+
},
|
|
1940
|
+
async createFolder(folder) {
|
|
1941
|
+
const key = getKey(`${folder}/`);
|
|
1942
|
+
await fetch(`${baseUrl}/${key}`, {
|
|
1943
|
+
method: "PUT",
|
|
1944
|
+
headers: {
|
|
1945
|
+
AccessKey: config.apiKey,
|
|
1946
|
+
"Content-Length": "0"
|
|
1947
|
+
},
|
|
1948
|
+
body: ""
|
|
1949
|
+
});
|
|
1950
|
+
},
|
|
1951
|
+
async deleteFolder(folder) {
|
|
1952
|
+
const key = getKey(`${folder}/`);
|
|
1953
|
+
await fetch(`${baseUrl}/${key}`, {
|
|
1954
|
+
method: "DELETE",
|
|
1955
|
+
headers: {
|
|
1956
|
+
AccessKey: config.apiKey
|
|
1957
|
+
}
|
|
1958
|
+
});
|
|
1959
|
+
}
|
|
1960
|
+
};
|
|
1961
|
+
}
|
|
1962
|
+
var StorageProviderRegistry = class {
|
|
1963
|
+
providers = /* @__PURE__ */ new Map();
|
|
1964
|
+
providerToPlugin = /* @__PURE__ */ new Map();
|
|
1965
|
+
disabledPlugins = /* @__PURE__ */ new Set();
|
|
1966
|
+
constructor() {
|
|
1967
|
+
this.registerLocal();
|
|
1968
|
+
this.registerImgix();
|
|
1969
|
+
this.registerBunny();
|
|
1970
|
+
}
|
|
1971
|
+
registerLocal() {
|
|
1972
|
+
this.register({
|
|
1973
|
+
type: "local",
|
|
1974
|
+
displayName: "Local Server",
|
|
1975
|
+
configKey: "local",
|
|
1976
|
+
configFields: [
|
|
1977
|
+
{
|
|
1978
|
+
name: "uploadDir",
|
|
1979
|
+
type: "text",
|
|
1980
|
+
label: "Upload Directory",
|
|
1981
|
+
defaultValue: "./public/uploads"
|
|
1982
|
+
},
|
|
1983
|
+
{
|
|
1984
|
+
name: "baseUrl",
|
|
1985
|
+
type: "text",
|
|
1986
|
+
label: "Base URL",
|
|
1987
|
+
defaultValue: "/uploads"
|
|
1988
|
+
}
|
|
1989
|
+
],
|
|
1990
|
+
extractConfig: (sc, key) => sc[key] || {},
|
|
1991
|
+
extractRawConfig: (c) => {
|
|
1992
|
+
const localConfig = c?.local || c;
|
|
1993
|
+
const savedUploadDir = (localConfig?.uploadDir || "").trim();
|
|
1994
|
+
let uploadDir;
|
|
1995
|
+
if (savedUploadDir) {
|
|
1996
|
+
if (path__default.default.isAbsolute(savedUploadDir)) {
|
|
1997
|
+
uploadDir = savedUploadDir;
|
|
1998
|
+
} else if (savedUploadDir.includes("/") || savedUploadDir.includes("\\")) {
|
|
1999
|
+
uploadDir = path__default.default.resolve(process.cwd(), savedUploadDir);
|
|
2000
|
+
} else {
|
|
2001
|
+
uploadDir = path__default.default.join(process.cwd(), "public", savedUploadDir);
|
|
2002
|
+
}
|
|
2003
|
+
} else {
|
|
2004
|
+
uploadDir = path__default.default.join(process.cwd(), "public", "uploads");
|
|
2005
|
+
}
|
|
2006
|
+
const savedBaseUrl = (localConfig?.baseUrl || "").trim();
|
|
2007
|
+
let baseUrl;
|
|
2008
|
+
if (savedBaseUrl) {
|
|
2009
|
+
baseUrl = savedBaseUrl.startsWith("/") ? savedBaseUrl : `/${savedBaseUrl}`;
|
|
2010
|
+
} else {
|
|
2011
|
+
baseUrl = "/uploads";
|
|
2012
|
+
}
|
|
2013
|
+
return { uploadDir, baseUrl };
|
|
2014
|
+
},
|
|
2015
|
+
factory: (c) => createLocalStorage(c)
|
|
2016
|
+
});
|
|
2017
|
+
}
|
|
2018
|
+
registerImgix() {
|
|
2019
|
+
this.register({
|
|
2020
|
+
type: "imgix",
|
|
2021
|
+
displayName: "Imgix",
|
|
2022
|
+
configKey: "imgix",
|
|
2023
|
+
configFields: [
|
|
2024
|
+
{ name: "domain", type: "text", label: "Domain", required: true },
|
|
2025
|
+
{ name: "signKey", type: "password", label: "Sign Key" }
|
|
2026
|
+
],
|
|
2027
|
+
extractConfig: (sc, key) => sc[key] || {},
|
|
2028
|
+
extractRawConfig: (c) => c?.imgix || c,
|
|
2029
|
+
factory: (c) => createImgixStorage(c)
|
|
2030
|
+
});
|
|
2031
|
+
}
|
|
2032
|
+
registerBunny() {
|
|
2033
|
+
this.register({
|
|
2034
|
+
type: "bunny",
|
|
2035
|
+
displayName: "Bunny.net",
|
|
2036
|
+
configKey: "bunny",
|
|
2037
|
+
configFields: [
|
|
2038
|
+
{
|
|
2039
|
+
name: "storageZone",
|
|
2040
|
+
type: "text",
|
|
2041
|
+
label: "Storage Zone",
|
|
2042
|
+
required: true
|
|
2043
|
+
},
|
|
2044
|
+
{ name: "apiKey", type: "password", label: "API Key", required: true },
|
|
2045
|
+
{ name: "cdnUrl", type: "text", label: "CDN URL" },
|
|
2046
|
+
{ name: "prefix", type: "text", label: "Path Prefix" }
|
|
2047
|
+
],
|
|
2048
|
+
extractConfig: (sc, key) => sc[key] || {},
|
|
2049
|
+
extractRawConfig: (c) => c?.bunny || c,
|
|
2050
|
+
factory: (c) => createBunnyStorage(c)
|
|
2051
|
+
});
|
|
2052
|
+
}
|
|
2053
|
+
register(registration) {
|
|
2054
|
+
if (this.providers.has(registration.type)) {
|
|
2055
|
+
console.warn(
|
|
2056
|
+
`[StorageRegistry] Provider "${registration.type}" already registered, skipping`
|
|
2057
|
+
);
|
|
2058
|
+
return;
|
|
2059
|
+
}
|
|
2060
|
+
if (registration.pluginName) {
|
|
2061
|
+
this.providerToPlugin.set(registration.type, registration.pluginName);
|
|
2062
|
+
}
|
|
2063
|
+
this.providers.set(registration.type, registration);
|
|
2064
|
+
}
|
|
2065
|
+
unregister(type) {
|
|
2066
|
+
this.providers.delete(type);
|
|
2067
|
+
this.providerToPlugin.delete(type);
|
|
2068
|
+
}
|
|
2069
|
+
get(type) {
|
|
2070
|
+
return this.providers.get(type);
|
|
2071
|
+
}
|
|
2072
|
+
getAll() {
|
|
2073
|
+
return Array.from(this.providers.values());
|
|
2074
|
+
}
|
|
2075
|
+
getAllAvailable(isPluginEnabled) {
|
|
2076
|
+
const all = this.getAll();
|
|
2077
|
+
if (!isPluginEnabled) return all;
|
|
2078
|
+
return all.filter((p) => {
|
|
2079
|
+
if (!p.pluginName) return true;
|
|
2080
|
+
return isPluginEnabled(p.pluginName);
|
|
2081
|
+
});
|
|
2082
|
+
}
|
|
2083
|
+
has(type) {
|
|
2084
|
+
return this.providers.has(type);
|
|
2085
|
+
}
|
|
2086
|
+
async resolve(type, storageConfig) {
|
|
2087
|
+
const reg = this.providers.get(type);
|
|
2088
|
+
if (!reg) {
|
|
2089
|
+
throw new Error(
|
|
2090
|
+
`Unknown storage provider type: "${type}". Is the plugin that provides it enabled?`
|
|
2091
|
+
);
|
|
2092
|
+
}
|
|
2093
|
+
if (reg.pluginName && this.disabledPlugins.has(reg.pluginName)) {
|
|
2094
|
+
throw new Error(
|
|
2095
|
+
`Storage provider "${type}" is not available (plugin "${reg.pluginName}" is disabled)`
|
|
2096
|
+
);
|
|
2097
|
+
}
|
|
2098
|
+
const configKey = reg.configKey || type;
|
|
2099
|
+
const config = reg.extractConfig(storageConfig, configKey);
|
|
2100
|
+
return reg.factory(config);
|
|
2101
|
+
}
|
|
2102
|
+
async resolveWithConfig(type, config) {
|
|
2103
|
+
const reg = this.providers.get(type);
|
|
2104
|
+
if (!reg) {
|
|
2105
|
+
throw new Error(
|
|
2106
|
+
`Unknown storage provider type: "${type}". Is the plugin that provides it enabled?`
|
|
2107
|
+
);
|
|
2108
|
+
}
|
|
2109
|
+
if (reg.pluginName && this.disabledPlugins.has(reg.pluginName)) {
|
|
2110
|
+
throw new Error(
|
|
2111
|
+
`Storage provider "${type}" is not available (plugin "${reg.pluginName}" is disabled)`
|
|
2112
|
+
);
|
|
2113
|
+
}
|
|
2114
|
+
const providerConfig = reg.extractRawConfig(config);
|
|
2115
|
+
return reg.factory(providerConfig);
|
|
2116
|
+
}
|
|
2117
|
+
getProviderPluginName(type) {
|
|
2118
|
+
return this.providerToPlugin.get(type);
|
|
2119
|
+
}
|
|
2120
|
+
getAllPluginNames() {
|
|
2121
|
+
return Array.from(new Set(this.providerToPlugin.values()));
|
|
2122
|
+
}
|
|
2123
|
+
setPluginEnabled(name, enabled) {
|
|
2124
|
+
if (enabled) {
|
|
2125
|
+
this.disabledPlugins.delete(name);
|
|
2126
|
+
} else {
|
|
2127
|
+
this.disabledPlugins.add(name);
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
isPluginEnabled(name) {
|
|
2131
|
+
return !this.disabledPlugins.has(name);
|
|
2132
|
+
}
|
|
2133
|
+
};
|
|
2134
|
+
var instance = null;
|
|
2135
|
+
function getDefaultRegistry() {
|
|
2136
|
+
if (!instance) {
|
|
2137
|
+
instance = new StorageProviderRegistry();
|
|
2138
|
+
}
|
|
2139
|
+
return instance;
|
|
2140
|
+
}
|
|
1682
2141
|
function extractPublicDevUrlId(url) {
|
|
1683
2142
|
if (!url) return "";
|
|
1684
2143
|
if (url.startsWith("pub-")) return url;
|
|
@@ -1712,7 +2171,7 @@ function getPublicUrl(key, config) {
|
|
|
1712
2171
|
return `https://${config.bucket}.s3.${config.region}.amazonaws.com/${normalizedKey}`;
|
|
1713
2172
|
}
|
|
1714
2173
|
}
|
|
1715
|
-
function
|
|
2174
|
+
function getUrlPrefix2(config) {
|
|
1716
2175
|
if (config.cdnUrl) {
|
|
1717
2176
|
return config.cdnUrl.replace(/\/$/, "") + "/";
|
|
1718
2177
|
}
|
|
@@ -1780,11 +2239,11 @@ function createS3Storage(config) {
|
|
|
1780
2239
|
})
|
|
1781
2240
|
}
|
|
1782
2241
|
});
|
|
1783
|
-
const getKey = (
|
|
2242
|
+
const getKey = (path3) => {
|
|
1784
2243
|
const prefix = config.prefix ? `${config.prefix}/` : "";
|
|
1785
|
-
return `${prefix}${
|
|
2244
|
+
return `${prefix}${path3}`.replace(/\/+/g, "/");
|
|
1786
2245
|
};
|
|
1787
|
-
const
|
|
2246
|
+
const getUrl2 = (key) => getPublicUrl(key, config);
|
|
1788
2247
|
return {
|
|
1789
2248
|
name: config.provider,
|
|
1790
2249
|
displayName: getDisplayName(config.provider),
|
|
@@ -1815,8 +2274,8 @@ function createS3Storage(config) {
|
|
|
1815
2274
|
originalName: file.name,
|
|
1816
2275
|
mimeType: file.type,
|
|
1817
2276
|
size: buffer.length,
|
|
1818
|
-
url:
|
|
1819
|
-
thumbnailUrl: file.type.startsWith("image/") ?
|
|
2277
|
+
url: getUrl2(key),
|
|
2278
|
+
thumbnailUrl: file.type.startsWith("image/") ? getUrl2(key) : void 0,
|
|
1820
2279
|
folder: options?.folder,
|
|
1821
2280
|
provider: config.provider,
|
|
1822
2281
|
metadata: {
|
|
@@ -1837,7 +2296,7 @@ function createS3Storage(config) {
|
|
|
1837
2296
|
return this.upload(file);
|
|
1838
2297
|
},
|
|
1839
2298
|
async delete(url) {
|
|
1840
|
-
const key = url.replace(
|
|
2299
|
+
const key = url.replace(getUrlPrefix2(config), "");
|
|
1841
2300
|
await client.send(
|
|
1842
2301
|
new clientS3.DeleteObjectCommand({
|
|
1843
2302
|
Bucket: config.bucket,
|
|
@@ -1846,7 +2305,7 @@ function createS3Storage(config) {
|
|
|
1846
2305
|
);
|
|
1847
2306
|
},
|
|
1848
2307
|
async rename(oldUrl, newKey) {
|
|
1849
|
-
const oldKey = oldUrl.replace(
|
|
2308
|
+
const oldKey = oldUrl.replace(getUrlPrefix2(config), "");
|
|
1850
2309
|
const newKeyWithPrefix = config.prefix ? `${config.prefix}/${newKey}` : newKey;
|
|
1851
2310
|
await client.send(
|
|
1852
2311
|
new clientS3.CopyObjectCommand({
|
|
@@ -1861,7 +2320,7 @@ function createS3Storage(config) {
|
|
|
1861
2320
|
Key: oldKey
|
|
1862
2321
|
})
|
|
1863
2322
|
);
|
|
1864
|
-
return
|
|
2323
|
+
return getUrl2(newKeyWithPrefix);
|
|
1865
2324
|
},
|
|
1866
2325
|
getImageUrl(url, transforms) {
|
|
1867
2326
|
if (!transforms || Object.keys(transforms).length === 0) return url;
|
|
@@ -1889,14 +2348,14 @@ function createS3Storage(config) {
|
|
|
1889
2348
|
originalName: item.Key?.split("/").pop() || "",
|
|
1890
2349
|
mimeType: "application/octet-stream",
|
|
1891
2350
|
size: item.Size || 0,
|
|
1892
|
-
url:
|
|
2351
|
+
url: getUrl2(item.Key || ""),
|
|
1893
2352
|
provider: config.provider,
|
|
1894
2353
|
createdAt: item.LastModified?.toISOString() || (/* @__PURE__ */ new Date()).toISOString()
|
|
1895
2354
|
}));
|
|
1896
2355
|
},
|
|
1897
2356
|
async exists(url) {
|
|
1898
2357
|
try {
|
|
1899
|
-
const key = url.replace(
|
|
2358
|
+
const key = url.replace(getUrlPrefix2(config), "");
|
|
1900
2359
|
await client.send(
|
|
1901
2360
|
new clientS3.HeadObjectCommand({
|
|
1902
2361
|
Bucket: config.bucket,
|
|
@@ -1956,9 +2415,9 @@ function createS3Storage(config) {
|
|
|
1956
2415
|
function createCloudinaryStorage(config) {
|
|
1957
2416
|
const getBaseUrl = () => `https://api.cloudinary.com/v1_1/${config.cloudName}/upload`;
|
|
1958
2417
|
const generateSignature = async (params) => {
|
|
1959
|
-
const
|
|
2418
|
+
const crypto4 = await import('crypto');
|
|
1960
2419
|
const sortedParams = Object.keys(params).sort().map((key) => `${key}=${params[key]}`).join("&");
|
|
1961
|
-
return
|
|
2420
|
+
return crypto4.createHash("sha256").update(sortedParams + config.apiSecret).digest("hex");
|
|
1962
2421
|
};
|
|
1963
2422
|
return {
|
|
1964
2423
|
name: "cloudinary",
|
|
@@ -2060,8 +2519,8 @@ function createCloudinaryStorage(config) {
|
|
|
2060
2519
|
public_id: publicId
|
|
2061
2520
|
};
|
|
2062
2521
|
const sortedParams = Object.keys(signatureParams).sort().map((key) => `${key}=${signatureParams[key]}`).join("&");
|
|
2063
|
-
const
|
|
2064
|
-
const signature =
|
|
2522
|
+
const crypto4 = await import('crypto');
|
|
2523
|
+
const signature = crypto4.createHash("sha256").update(sortedParams + config.apiSecret).digest("hex");
|
|
2065
2524
|
const deleteUrl = `https://api.cloudinary.com/v1_1/${config.cloudName}/image/destroy`;
|
|
2066
2525
|
const formData = new FormData();
|
|
2067
2526
|
formData.append("public_id", publicId);
|
|
@@ -2112,8 +2571,8 @@ function createCloudinaryStorage(config) {
|
|
|
2112
2571
|
timestamp: String(timestamp)
|
|
2113
2572
|
};
|
|
2114
2573
|
const sortedParams = Object.keys(signatureParams).sort().map((key) => `${key}=${signatureParams[key]}`).join("&");
|
|
2115
|
-
const
|
|
2116
|
-
const signature =
|
|
2574
|
+
const crypto4 = await import('crypto');
|
|
2575
|
+
const signature = crypto4.createHash("sha256").update(sortedParams + config.apiSecret).digest("hex");
|
|
2117
2576
|
const formData = new FormData();
|
|
2118
2577
|
formData.append("from_public_id", publicIdWithoutVersion);
|
|
2119
2578
|
formData.append("to_public_id", newPublicId);
|
|
@@ -2184,112 +2643,6 @@ function createCloudinaryStorage(config) {
|
|
|
2184
2643
|
}
|
|
2185
2644
|
};
|
|
2186
2645
|
}
|
|
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}`);
|
|
2219
|
-
}
|
|
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
|
-
},
|
|
2243
|
-
getImageUrl(url, transforms) {
|
|
2244
|
-
const parsed = new URL(url);
|
|
2245
|
-
const params = new URLSearchParams(parsed.search);
|
|
2246
|
-
if (config.defaultParameters) {
|
|
2247
|
-
Object.entries(config.defaultParameters).forEach(([key, value]) => {
|
|
2248
|
-
if (!params.has(key)) {
|
|
2249
|
-
params.set(key, value);
|
|
2250
|
-
}
|
|
2251
|
-
});
|
|
2252
|
-
}
|
|
2253
|
-
if (transforms) {
|
|
2254
|
-
if (transforms.width) params.set("w", String(transforms.width));
|
|
2255
|
-
if (transforms.height) params.set("h", String(transforms.height));
|
|
2256
|
-
if (transforms.quality) params.set("q", String(transforms.quality));
|
|
2257
|
-
if (transforms.format) params.set("fm", transforms.format);
|
|
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);
|
|
2266
|
-
}
|
|
2267
|
-
return `https://${config.domain}${parsed.pathname}${params.toString() ? "?" + params.toString() : ""}`;
|
|
2268
|
-
},
|
|
2269
|
-
async generateThumbnail(file) {
|
|
2270
|
-
return this.getImageUrl(file.url, {
|
|
2271
|
-
width: 200,
|
|
2272
|
-
height: 200,
|
|
2273
|
-
fit: "crop"
|
|
2274
|
-
});
|
|
2275
|
-
},
|
|
2276
|
-
async list() {
|
|
2277
|
-
return [];
|
|
2278
|
-
},
|
|
2279
|
-
async exists(url) {
|
|
2280
|
-
try {
|
|
2281
|
-
const response = await fetch(url, { method: "HEAD" });
|
|
2282
|
-
return response.ok;
|
|
2283
|
-
} catch {
|
|
2284
|
-
return false;
|
|
2285
|
-
}
|
|
2286
|
-
},
|
|
2287
|
-
async createFolder() {
|
|
2288
|
-
},
|
|
2289
|
-
async deleteFolder() {
|
|
2290
|
-
}
|
|
2291
|
-
};
|
|
2292
|
-
}
|
|
2293
2646
|
function createFtpStorage(config) {
|
|
2294
2647
|
let client = null;
|
|
2295
2648
|
async function getClient() {
|
|
@@ -2307,15 +2660,15 @@ function createFtpStorage(config) {
|
|
|
2307
2660
|
}
|
|
2308
2661
|
return client;
|
|
2309
2662
|
}
|
|
2310
|
-
const getKey = (
|
|
2663
|
+
const getKey = (path3) => {
|
|
2311
2664
|
const prefix = config.prefix ? `${config.prefix}/` : "";
|
|
2312
|
-
return `${prefix}${
|
|
2665
|
+
return `${prefix}${path3}`.replace(/\/+/g, "/");
|
|
2313
2666
|
};
|
|
2314
|
-
const
|
|
2667
|
+
const getUrl2 = (key) => {
|
|
2315
2668
|
const base = config.baseUrl.replace(/\/$/, "");
|
|
2316
2669
|
return `${base}/${key}`;
|
|
2317
2670
|
};
|
|
2318
|
-
const
|
|
2671
|
+
const getUrlPrefix3 = () => {
|
|
2319
2672
|
const base = config.baseUrl.replace(/\/$/, "");
|
|
2320
2673
|
return base + "/";
|
|
2321
2674
|
};
|
|
@@ -2346,8 +2699,8 @@ function createFtpStorage(config) {
|
|
|
2346
2699
|
originalName: file.name,
|
|
2347
2700
|
mimeType: file.type,
|
|
2348
2701
|
size: buffer.length,
|
|
2349
|
-
url:
|
|
2350
|
-
thumbnailUrl: file.type.startsWith("image/") ?
|
|
2702
|
+
url: getUrl2(key),
|
|
2703
|
+
thumbnailUrl: file.type.startsWith("image/") ? getUrl2(key) : void 0,
|
|
2351
2704
|
folder: options?.folder,
|
|
2352
2705
|
provider: config.type,
|
|
2353
2706
|
metadata: options?.metadata,
|
|
@@ -2366,15 +2719,15 @@ function createFtpStorage(config) {
|
|
|
2366
2719
|
},
|
|
2367
2720
|
async delete(url) {
|
|
2368
2721
|
const ftp = await getClient();
|
|
2369
|
-
const key = url.replace(
|
|
2722
|
+
const key = url.replace(getUrlPrefix3(), "");
|
|
2370
2723
|
await ftp.remove(key);
|
|
2371
2724
|
},
|
|
2372
2725
|
async rename(oldUrl, newKey) {
|
|
2373
2726
|
const ftp = await getClient();
|
|
2374
|
-
const oldKey = oldUrl.replace(
|
|
2727
|
+
const oldKey = oldUrl.replace(getUrlPrefix3(), "");
|
|
2375
2728
|
const fullPath = config.prefix ? `${config.prefix}/${newKey}` : newKey;
|
|
2376
2729
|
await ftp.rename(oldKey, fullPath);
|
|
2377
|
-
return
|
|
2730
|
+
return getUrl2(fullPath);
|
|
2378
2731
|
},
|
|
2379
2732
|
getImageUrl(url, transforms) {
|
|
2380
2733
|
if (!transforms || Object.keys(transforms).length === 0) return url;
|
|
@@ -2403,14 +2756,14 @@ function createFtpStorage(config) {
|
|
|
2403
2756
|
originalName: item.name,
|
|
2404
2757
|
mimeType: "application/octet-stream",
|
|
2405
2758
|
size: Number(item.size) || 0,
|
|
2406
|
-
url:
|
|
2759
|
+
url: getUrl2(`${key}/${item.name}`.replace(/\/+/g, "/")),
|
|
2407
2760
|
provider: config.type,
|
|
2408
2761
|
createdAt: item.modifiedAt ? item.modifiedAt.toISOString() : (/* @__PURE__ */ new Date()).toISOString()
|
|
2409
2762
|
}));
|
|
2410
2763
|
},
|
|
2411
2764
|
async exists(url) {
|
|
2412
2765
|
const ftp = await getClient();
|
|
2413
|
-
const key = url.replace(
|
|
2766
|
+
const key = url.replace(getUrlPrefix3(), "");
|
|
2414
2767
|
try {
|
|
2415
2768
|
await ftp.size(key);
|
|
2416
2769
|
return true;
|
|
@@ -2434,105 +2787,17 @@ function createFtpStorage(config) {
|
|
|
2434
2787
|
// src/storage/index.ts
|
|
2435
2788
|
async function resolveProvider(configService) {
|
|
2436
2789
|
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
|
-
});
|
|
2790
|
+
const registry = getDefaultRegistry();
|
|
2791
|
+
try {
|
|
2792
|
+
return await registry.resolve(config.type, config);
|
|
2793
|
+
} catch (err) {
|
|
2794
|
+
console.warn(
|
|
2795
|
+
`[resolveProvider] ${err.message} \u2014 falling back to local storage`
|
|
2796
|
+
);
|
|
2797
|
+
return createLocalStorage({
|
|
2798
|
+
uploadDir: path__default.default.join(process.cwd(), "public", "uploads"),
|
|
2799
|
+
baseUrl: "/uploads"
|
|
2800
|
+
});
|
|
2536
2801
|
}
|
|
2537
2802
|
}
|
|
2538
2803
|
async function resolveProviderWithConfig(config) {
|
|
@@ -2543,121 +2808,18 @@ async function resolveProviderWithConfig(config) {
|
|
|
2543
2808
|
baseUrl: "/uploads"
|
|
2544
2809
|
});
|
|
2545
2810
|
}
|
|
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
|
-
}
|
|
2811
|
+
const type = config.type || "local";
|
|
2812
|
+
const registry = getDefaultRegistry();
|
|
2813
|
+
try {
|
|
2814
|
+
return await registry.resolveWithConfig(type, config);
|
|
2815
|
+
} catch (err) {
|
|
2816
|
+
console.warn(
|
|
2817
|
+
`[resolveProviderWithConfig] ${err.message} \u2014 falling back to local storage`
|
|
2818
|
+
);
|
|
2819
|
+
return createLocalStorage({
|
|
2820
|
+
uploadDir: path__default.default.join(process.cwd(), "public", "uploads"),
|
|
2821
|
+
baseUrl: "/uploads"
|
|
2822
|
+
});
|
|
2661
2823
|
}
|
|
2662
2824
|
}
|
|
2663
2825
|
async function processImage(buffer) {
|
|
@@ -2686,14 +2848,14 @@ var MediaService = class _MediaService {
|
|
|
2686
2848
|
this.db = db;
|
|
2687
2849
|
this.storage = storage2;
|
|
2688
2850
|
this.dialect = options?.dialect || "sqlite";
|
|
2689
|
-
this.genId = options?.genId ||
|
|
2851
|
+
this.genId = options?.genId || chunkCOIASRDK_cjs.genId;
|
|
2690
2852
|
}
|
|
2691
2853
|
static async init(db, options) {
|
|
2692
2854
|
let storage2;
|
|
2693
2855
|
if (options?.storageConfig) {
|
|
2694
2856
|
storage2 = await resolveProviderWithConfig(options.storageConfig);
|
|
2695
2857
|
} else {
|
|
2696
|
-
const configService = new
|
|
2858
|
+
const configService = new chunkWNCYAKF3_cjs.ConfigService(db);
|
|
2697
2859
|
if (typeof db?.select === "function") {
|
|
2698
2860
|
await configService.load();
|
|
2699
2861
|
}
|
|
@@ -3261,13 +3423,38 @@ var MediaService = class _MediaService {
|
|
|
3261
3423
|
}
|
|
3262
3424
|
}
|
|
3263
3425
|
};
|
|
3264
|
-
|
|
3265
|
-
|
|
3426
|
+
function formatZodErrors(errors) {
|
|
3427
|
+
return errors.map((e) => `${e.path.join(".")}: ${e.message}`).join(", ");
|
|
3428
|
+
}
|
|
3429
|
+
function convertRichtextFields(fields, data) {
|
|
3430
|
+
if (!data || typeof data !== "object") return;
|
|
3431
|
+
for (const field of fields) {
|
|
3432
|
+
if (field.type === "richtext" && field.name) {
|
|
3433
|
+
const val = data[field.name];
|
|
3434
|
+
if (typeof val === "string") {
|
|
3435
|
+
data[field.name] = [{ type: "paragraph", children: [{ text: val }] }];
|
|
3436
|
+
} else if (val && typeof val === "object" && !Array.isArray(val) && val.type === "doc" && Array.isArray(val.content)) {
|
|
3437
|
+
data[field.name] = val.content;
|
|
3438
|
+
}
|
|
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)) convertRichtextFields(tab.fields, data[field.name]);
|
|
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
|
+
convertRichtextFields(field.fields, data[field.name]);
|
|
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") convertRichtextFields(field.fields, item);
|
|
3449
|
+
}
|
|
3450
|
+
}
|
|
3451
|
+
}
|
|
3452
|
+
}
|
|
3266
3453
|
var COLLECTION_EVENT_MAP = {
|
|
3267
3454
|
_media: {
|
|
3268
|
-
create:
|
|
3269
|
-
update:
|
|
3270
|
-
delete:
|
|
3455
|
+
create: chunkQFLB4EIJ_cjs.WEBHOOK_EVENTS.MEDIA_UPLOAD,
|
|
3456
|
+
update: chunkQFLB4EIJ_cjs.WEBHOOK_EVENTS.MEDIA_UPLOAD,
|
|
3457
|
+
delete: chunkQFLB4EIJ_cjs.WEBHOOK_EVENTS.MEDIA_DELETE
|
|
3271
3458
|
}
|
|
3272
3459
|
};
|
|
3273
3460
|
function getWebhookEvent(collection, operation) {
|
|
@@ -3326,7 +3513,7 @@ async function checkCollectionAccess(collection, operation, req, ctxUser, ctxTen
|
|
|
3326
3513
|
};
|
|
3327
3514
|
const isDefaultAllowed = accessLevels[defaultCollectionAccess] || false;
|
|
3328
3515
|
if (accessRule) {
|
|
3329
|
-
const allowed = await
|
|
3516
|
+
const allowed = await chunk4M7X5HAB_cjs.evaluateAccess(accessRule, {
|
|
3330
3517
|
req,
|
|
3331
3518
|
user: ctxUser,
|
|
3332
3519
|
tenantID: ctxTenantID
|
|
@@ -3348,7 +3535,7 @@ async function checkCollectionAccess(collection, operation, req, ctxUser, ctxTen
|
|
|
3348
3535
|
const resource = collection.slug;
|
|
3349
3536
|
const action = operation === "read" ? "read" : operation === "create" ? "create" : "update";
|
|
3350
3537
|
const permission = `${resource}:${action}`;
|
|
3351
|
-
if (!
|
|
3538
|
+
if (!chunk4M7X5HAB_cjs.hasApiKeyPermission(apiKeyContext.permissions, permission) && !chunk4M7X5HAB_cjs.hasApiKeyPermission(apiKeyContext.permissions, `${resource}:admin`)) {
|
|
3352
3539
|
return {
|
|
3353
3540
|
allowed: false,
|
|
3354
3541
|
error: `Missing permission: ${permission}`,
|
|
@@ -3362,14 +3549,14 @@ async function checkCollectionAccess(collection, operation, req, ctxUser, ctxTen
|
|
|
3362
3549
|
const permission = `${resource}:${action}`;
|
|
3363
3550
|
let rbacAllowed = false;
|
|
3364
3551
|
if (ctxUser.role) {
|
|
3365
|
-
const userHasPermission =
|
|
3552
|
+
const userHasPermission = chunkNKPKR5BW_cjs.hasPermission(
|
|
3366
3553
|
{ id: ctxUser.id, email: ctxUser.email, role: ctxUser.role },
|
|
3367
3554
|
permission
|
|
3368
3555
|
);
|
|
3369
3556
|
if (userHasPermission) {
|
|
3370
3557
|
rbacAllowed = true;
|
|
3371
3558
|
} else {
|
|
3372
|
-
const adminPermission =
|
|
3559
|
+
const adminPermission = chunkNKPKR5BW_cjs.hasPermission(
|
|
3373
3560
|
{ id: ctxUser.id, email: ctxUser.email, role: ctxUser.role },
|
|
3374
3561
|
`${resource}:admin`
|
|
3375
3562
|
);
|
|
@@ -3389,7 +3576,7 @@ async function checkCollectionAccess(collection, operation, req, ctxUser, ctxTen
|
|
|
3389
3576
|
async function checkGlobalAccess(global, operation, req, ctxUser, ctxTenantID, enablePublicAccess = true) {
|
|
3390
3577
|
const accessRule = global.access?.[operation];
|
|
3391
3578
|
if (accessRule) {
|
|
3392
|
-
const allowed = await
|
|
3579
|
+
const allowed = await chunk4M7X5HAB_cjs.evaluateAccess(accessRule, {
|
|
3393
3580
|
req,
|
|
3394
3581
|
user: ctxUser,
|
|
3395
3582
|
tenantID: ctxTenantID
|
|
@@ -3415,42 +3602,41 @@ async function checkGlobalAccess(global, operation, req, ctxUser, ctxTenantID, e
|
|
|
3415
3602
|
return { allowed: true };
|
|
3416
3603
|
}
|
|
3417
3604
|
async function resolveAuthContext(req, authMw, staticUser, staticTenantID) {
|
|
3418
|
-
if (
|
|
3605
|
+
if (staticUser) {
|
|
3419
3606
|
return {
|
|
3420
3607
|
user: staticUser,
|
|
3421
3608
|
tenantID: staticTenantID,
|
|
3422
|
-
apiKeyContext: void 0
|
|
3609
|
+
apiKeyContext: void 0,
|
|
3610
|
+
authType: "static"
|
|
3423
3611
|
};
|
|
3424
3612
|
}
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
if (result.status === 401) {
|
|
3436
|
-
return { user: void 0, tenantID: void 0, apiKeyContext: void 0 };
|
|
3613
|
+
if (authMw) {
|
|
3614
|
+
const res = await authMw(req);
|
|
3615
|
+
if (res.status === 200 && res.user) {
|
|
3616
|
+
return {
|
|
3617
|
+
user: res.user,
|
|
3618
|
+
tenantID: res.tenantContext?.tenantId,
|
|
3619
|
+
apiKeyContext: res.apiKeyContext,
|
|
3620
|
+
authType: res.authType
|
|
3621
|
+
};
|
|
3622
|
+
}
|
|
3437
3623
|
}
|
|
3438
3624
|
return {
|
|
3439
|
-
user:
|
|
3440
|
-
tenantID:
|
|
3441
|
-
apiKeyContext:
|
|
3442
|
-
authType:
|
|
3625
|
+
user: void 0,
|
|
3626
|
+
tenantID: void 0,
|
|
3627
|
+
apiKeyContext: void 0,
|
|
3628
|
+
authType: void 0
|
|
3443
3629
|
};
|
|
3444
3630
|
}
|
|
3445
3631
|
function createDefaultAuthAdapter(db, rootDir) {
|
|
3446
|
-
if (db instanceof
|
|
3447
|
-
return new
|
|
3632
|
+
if (db instanceof chunkCOIASRDK_cjs.DrizzleAdapter && db.dialect === "postgres") {
|
|
3633
|
+
return new chunkC36TMDTY_cjs.PostgresAuthAdapter({ db: db.client });
|
|
3448
3634
|
}
|
|
3449
|
-
if (db instanceof
|
|
3450
|
-
return new
|
|
3635
|
+
if (db instanceof chunkV2TVSCV5_cjs.MongoDBAdapter) {
|
|
3636
|
+
return new chunkGXFOGU7N_cjs.MongoDBAuthAdapter({ db: db.db });
|
|
3451
3637
|
}
|
|
3452
3638
|
const defaultAuthDbPath = path.resolve(rootDir, "data", "kyro.db");
|
|
3453
|
-
return new
|
|
3639
|
+
return new chunkIDVRRRAK_cjs.SQLiteAuthAdapter({
|
|
3454
3640
|
path: process.env.KYRO_AUTH_DB_PATH || defaultAuthDbPath
|
|
3455
3641
|
});
|
|
3456
3642
|
}
|
|
@@ -3554,78 +3740,15 @@ function createHonoApp(options) {
|
|
|
3554
3740
|
rateLimiter
|
|
3555
3741
|
});
|
|
3556
3742
|
app.post("/api/auth/login", async (c) => authRoutes.login(c.req.raw));
|
|
3557
|
-
app.post("/api/auth/register", async (c) => authRoutes.register(c.req.raw));
|
|
3558
|
-
app.post("/api/auth/logout", async (c) => authRoutes.logout(c.req.raw));
|
|
3559
|
-
app.post("/api/auth/refresh", async (c) => authRoutes.refresh(c.req.raw));
|
|
3560
|
-
app.get("/api/auth/me", async (c) => authRoutes.me(c.req.raw));
|
|
3561
|
-
app.get("/api/auth/sessions", async (c) => authRoutes.listSessions(c.req.raw));
|
|
3562
|
-
app.post("/api/auth/sessions/refresh", async (c) => authRoutes.refreshSession(c.req.raw));
|
|
3563
|
-
app.delete("/api/auth/sessions", async (c) => authRoutes.revokeOtherSessions(c.req.raw));
|
|
3564
|
-
app.delete("/api/auth/sessions/:id", async (c) => authRoutes.revokeSession(c.req.raw, c.req.param("id")));
|
|
3565
|
-
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
|
-
});
|
|
3743
|
+
app.post("/api/auth/register", async (c) => authRoutes.register(c.req.raw));
|
|
3744
|
+
app.post("/api/auth/logout", async (c) => authRoutes.logout(c.req.raw));
|
|
3745
|
+
app.post("/api/auth/refresh", async (c) => authRoutes.refresh(c.req.raw));
|
|
3746
|
+
app.get("/api/auth/me", async (c) => authRoutes.me(c.req.raw));
|
|
3747
|
+
app.get("/api/auth/sessions", async (c) => authRoutes.listSessions(c.req.raw));
|
|
3748
|
+
app.post("/api/auth/sessions/refresh", async (c) => authRoutes.refreshSession(c.req.raw));
|
|
3749
|
+
app.delete("/api/auth/sessions", async (c) => authRoutes.revokeOtherSessions(c.req.raw));
|
|
3750
|
+
app.delete("/api/auth/sessions/:id", async (c) => authRoutes.revokeSession(c.req.raw, c.req.param("id")));
|
|
3751
|
+
app.put("/api/auth/sessions/:id/name", async (c) => authRoutes.renameSession(c.req.raw, c.req.param("id")));
|
|
3629
3752
|
app.get("/api/auth/access", async (c) => {
|
|
3630
3753
|
try {
|
|
3631
3754
|
const { user: ctxUser, tenantID: ctxTenantID } = await resolveAuthContext(
|
|
@@ -3710,7 +3833,13 @@ function createHonoApp(options) {
|
|
|
3710
3833
|
return c.json({ error: error.message }, 500);
|
|
3711
3834
|
}
|
|
3712
3835
|
});
|
|
3713
|
-
const
|
|
3836
|
+
const usersCollection2 = typeof registry.hasCollection === "function" && registry.hasCollection("users") ? registry.getCollection("users") : (() => {
|
|
3837
|
+
try {
|
|
3838
|
+
return registry.getCollection("users");
|
|
3839
|
+
} catch {
|
|
3840
|
+
return chunkKC2GDBLS_cjs.usersCollection;
|
|
3841
|
+
}
|
|
3842
|
+
})();
|
|
3714
3843
|
app.get("/api/users", async (c) => {
|
|
3715
3844
|
try {
|
|
3716
3845
|
const { user: ctxUser, tenantID: ctxTenantID } = await resolveAuthContext(
|
|
@@ -3720,7 +3849,7 @@ function createHonoApp(options) {
|
|
|
3720
3849
|
tenantID
|
|
3721
3850
|
);
|
|
3722
3851
|
const access = await checkCollectionAccess(
|
|
3723
|
-
|
|
3852
|
+
usersCollection2,
|
|
3724
3853
|
"read",
|
|
3725
3854
|
c.req.raw,
|
|
3726
3855
|
ctxUser,
|
|
@@ -3765,19 +3894,6 @@ function createHonoApp(options) {
|
|
|
3765
3894
|
user,
|
|
3766
3895
|
tenantID
|
|
3767
3896
|
);
|
|
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
3897
|
const id = c.req.param("id");
|
|
3782
3898
|
const found = await sessionAuthAdapter.findUserById(id);
|
|
3783
3899
|
if (!found) {
|
|
@@ -3797,7 +3913,7 @@ function createHonoApp(options) {
|
|
|
3797
3913
|
tenantID
|
|
3798
3914
|
);
|
|
3799
3915
|
const access = await checkCollectionAccess(
|
|
3800
|
-
|
|
3916
|
+
usersCollection2,
|
|
3801
3917
|
"create",
|
|
3802
3918
|
c.req.raw,
|
|
3803
3919
|
ctxUser,
|
|
@@ -3822,8 +3938,18 @@ function createHonoApp(options) {
|
|
|
3822
3938
|
password: body.password,
|
|
3823
3939
|
name: body.name,
|
|
3824
3940
|
role: body.role || "customer",
|
|
3941
|
+
avatar: body.avatar,
|
|
3825
3942
|
tenantId: body.tenantId
|
|
3826
3943
|
});
|
|
3944
|
+
if (ctxUser) {
|
|
3945
|
+
sessionAuthAdapter?.createAuditLog({
|
|
3946
|
+
action: "user_create",
|
|
3947
|
+
userId: ctxUser.id,
|
|
3948
|
+
resource: "users",
|
|
3949
|
+
resourceId: created.id,
|
|
3950
|
+
success: true
|
|
3951
|
+
});
|
|
3952
|
+
}
|
|
3827
3953
|
return c.json(
|
|
3828
3954
|
{ data: created, message: "User created successfully" },
|
|
3829
3955
|
201
|
|
@@ -3841,7 +3967,7 @@ function createHonoApp(options) {
|
|
|
3841
3967
|
tenantID
|
|
3842
3968
|
);
|
|
3843
3969
|
const access = await checkCollectionAccess(
|
|
3844
|
-
|
|
3970
|
+
usersCollection2,
|
|
3845
3971
|
"update",
|
|
3846
3972
|
c.req.raw,
|
|
3847
3973
|
ctxUser,
|
|
@@ -3863,6 +3989,9 @@ function createHonoApp(options) {
|
|
|
3863
3989
|
if (body.name !== void 0) updateData.name = body.name;
|
|
3864
3990
|
if (body.email !== void 0) updateData.email = body.email;
|
|
3865
3991
|
if (body.role !== void 0) updateData.role = body.role;
|
|
3992
|
+
if (body.avatar !== void 0) {
|
|
3993
|
+
updateData.avatar = typeof body.avatar === "object" && body.avatar !== null ? body.avatar.id || String(body.avatar) : body.avatar;
|
|
3994
|
+
}
|
|
3866
3995
|
if (body.tenantId !== void 0) updateData.tenantId = body.tenantId;
|
|
3867
3996
|
if (body.emailVerified !== void 0)
|
|
3868
3997
|
updateData.emailVerified = body.emailVerified;
|
|
@@ -3874,6 +4003,25 @@ function createHonoApp(options) {
|
|
|
3874
4003
|
if (!updated) {
|
|
3875
4004
|
return c.json({ error: "User update failed" }, 500);
|
|
3876
4005
|
}
|
|
4006
|
+
if (ctxUser) {
|
|
4007
|
+
sessionAuthAdapter?.createAuditLog({
|
|
4008
|
+
action: "user_update",
|
|
4009
|
+
userId: ctxUser.id,
|
|
4010
|
+
resource: "users",
|
|
4011
|
+
resourceId: id,
|
|
4012
|
+
success: true
|
|
4013
|
+
});
|
|
4014
|
+
if (body.role && existing.role !== body.role) {
|
|
4015
|
+
sessionAuthAdapter?.createAuditLog({
|
|
4016
|
+
action: "role_change",
|
|
4017
|
+
userId: ctxUser.id,
|
|
4018
|
+
resource: "users",
|
|
4019
|
+
resourceId: id,
|
|
4020
|
+
success: true,
|
|
4021
|
+
metadata: { oldRole: existing.role, newRole: body.role }
|
|
4022
|
+
});
|
|
4023
|
+
}
|
|
4024
|
+
}
|
|
3877
4025
|
return c.json({ data: updated, message: "User updated successfully" });
|
|
3878
4026
|
} catch (error) {
|
|
3879
4027
|
return c.json({ error: error.message }, 500);
|
|
@@ -3888,7 +4036,7 @@ function createHonoApp(options) {
|
|
|
3888
4036
|
tenantID
|
|
3889
4037
|
);
|
|
3890
4038
|
const access = await checkCollectionAccess(
|
|
3891
|
-
|
|
4039
|
+
usersCollection2,
|
|
3892
4040
|
"delete",
|
|
3893
4041
|
c.req.raw,
|
|
3894
4042
|
ctxUser,
|
|
@@ -3912,6 +4060,15 @@ function createHonoApp(options) {
|
|
|
3912
4060
|
if (!deleted) {
|
|
3913
4061
|
return c.json({ error: "User deletion failed" }, 500);
|
|
3914
4062
|
}
|
|
4063
|
+
if (ctxUser) {
|
|
4064
|
+
sessionAuthAdapter?.createAuditLog({
|
|
4065
|
+
action: "user_delete",
|
|
4066
|
+
userId: ctxUser.id,
|
|
4067
|
+
resource: "users",
|
|
4068
|
+
resourceId: id,
|
|
4069
|
+
success: true
|
|
4070
|
+
});
|
|
4071
|
+
}
|
|
3915
4072
|
return c.json({ data: existing, message: "User deleted successfully" });
|
|
3916
4073
|
} catch (error) {
|
|
3917
4074
|
return c.json({ error: error.message }, 500);
|
|
@@ -3980,8 +4137,8 @@ function createHonoApp(options) {
|
|
|
3980
4137
|
}
|
|
3981
4138
|
if (!mediaService) {
|
|
3982
4139
|
try {
|
|
3983
|
-
const dialect = db instanceof
|
|
3984
|
-
const mediaDb = dialect === "postgres" && db instanceof
|
|
4140
|
+
const dialect = db instanceof chunkCOIASRDK_cjs.DrizzleAdapter ? db.dialect : "sqlite";
|
|
4141
|
+
const mediaDb = dialect === "postgres" && db instanceof chunkCOIASRDK_cjs.DrizzleAdapter ? db.client : db;
|
|
3985
4142
|
mediaService = await MediaService.init(mediaDb, { dialect });
|
|
3986
4143
|
} catch (error) {
|
|
3987
4144
|
console.error("[getMedia] Init error:", error);
|
|
@@ -4068,11 +4225,11 @@ function createHonoApp(options) {
|
|
|
4068
4225
|
const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4069
4226
|
if (!ctxUser) return c.json({ error: "Authentication required" }, 401);
|
|
4070
4227
|
const service = await getMedia();
|
|
4071
|
-
const
|
|
4072
|
-
if (!
|
|
4228
|
+
const path3 = c.req.query("path");
|
|
4229
|
+
if (!path3) {
|
|
4073
4230
|
return c.json({ error: "Path is required" }, 400);
|
|
4074
4231
|
}
|
|
4075
|
-
await service.deleteFolder(
|
|
4232
|
+
await service.deleteFolder(path3);
|
|
4076
4233
|
return c.json({ message: "Folder deleted" });
|
|
4077
4234
|
} catch (error) {
|
|
4078
4235
|
console.error("[Media] delete folder error:", error);
|
|
@@ -4225,6 +4382,119 @@ function createHonoApp(options) {
|
|
|
4225
4382
|
return c.json({ error: error.message }, 500);
|
|
4226
4383
|
}
|
|
4227
4384
|
});
|
|
4385
|
+
app.get("/api/plugins", async (c) => {
|
|
4386
|
+
try {
|
|
4387
|
+
const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4388
|
+
if (!ctxUser) return c.json({ error: "Authentication required" }, 401);
|
|
4389
|
+
const plugins = registry.getPlugins();
|
|
4390
|
+
const storageRegistry = registry.storageProviders;
|
|
4391
|
+
const pluginList = await Promise.all(
|
|
4392
|
+
plugins.map(async (p) => {
|
|
4393
|
+
let enabled = true;
|
|
4394
|
+
const pluginName = p.name;
|
|
4395
|
+
let states = {};
|
|
4396
|
+
try {
|
|
4397
|
+
const doc = await db.findOne({
|
|
4398
|
+
collection: "_globals_plugin-settings",
|
|
4399
|
+
where: {}
|
|
4400
|
+
});
|
|
4401
|
+
if (doc && doc.states) states = doc.states;
|
|
4402
|
+
} catch {
|
|
4403
|
+
}
|
|
4404
|
+
if (states[pluginName] !== void 0) {
|
|
4405
|
+
enabled = states[pluginName];
|
|
4406
|
+
}
|
|
4407
|
+
storageRegistry.setPluginEnabled(pluginName, enabled);
|
|
4408
|
+
return {
|
|
4409
|
+
id: pluginName,
|
|
4410
|
+
name: pluginName,
|
|
4411
|
+
version: p.version || "1.0.0",
|
|
4412
|
+
description: p.description || "",
|
|
4413
|
+
enabled,
|
|
4414
|
+
status: enabled ? "active" : "disabled"
|
|
4415
|
+
};
|
|
4416
|
+
})
|
|
4417
|
+
);
|
|
4418
|
+
return c.json(pluginList);
|
|
4419
|
+
} catch (error) {
|
|
4420
|
+
console.error("[Plugins] list error:", error);
|
|
4421
|
+
return c.json({ error: error.message }, 500);
|
|
4422
|
+
}
|
|
4423
|
+
});
|
|
4424
|
+
app.put("/api/plugins/:name/toggle", async (c) => {
|
|
4425
|
+
try {
|
|
4426
|
+
const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4427
|
+
if (!ctxUser) return c.json({ error: "Authentication required" }, 401);
|
|
4428
|
+
const pluginName = c.req.param("name");
|
|
4429
|
+
const storageRegistry = registry.storageProviders;
|
|
4430
|
+
const currentEnabled = storageRegistry.isPluginEnabled(pluginName);
|
|
4431
|
+
const newEnabled = !currentEnabled;
|
|
4432
|
+
const affectedProviders = [];
|
|
4433
|
+
if (!newEnabled) {
|
|
4434
|
+
for (const p of storageRegistry.getAll()) {
|
|
4435
|
+
if (p.pluginName === pluginName) {
|
|
4436
|
+
affectedProviders.push(p.type);
|
|
4437
|
+
}
|
|
4438
|
+
}
|
|
4439
|
+
}
|
|
4440
|
+
const force = c.req.query("force") === "1";
|
|
4441
|
+
if (!force && !newEnabled && affectedProviders.length > 0) {
|
|
4442
|
+
let activeProvider = "local";
|
|
4443
|
+
try {
|
|
4444
|
+
const row = db?.prepare?.(`SELECT provider FROM "_globals_storage-settings" LIMIT 1`)?.get();
|
|
4445
|
+
if (row?.provider) activeProvider = row.provider;
|
|
4446
|
+
} catch {
|
|
4447
|
+
try {
|
|
4448
|
+
const result = await db?.findOne?.({
|
|
4449
|
+
collection: "_globals_storage-settings",
|
|
4450
|
+
where: {}
|
|
4451
|
+
});
|
|
4452
|
+
if (result?.provider) activeProvider = result.provider;
|
|
4453
|
+
} catch {
|
|
4454
|
+
}
|
|
4455
|
+
}
|
|
4456
|
+
if (affectedProviders.includes(activeProvider)) {
|
|
4457
|
+
return c.json({
|
|
4458
|
+
error: `Cannot disable "${pluginName}" \u2014 storage provider "${activeProvider}" is currently active. Switch to Local storage first.`,
|
|
4459
|
+
requiresAction: true,
|
|
4460
|
+
activeProvider
|
|
4461
|
+
}, 409);
|
|
4462
|
+
}
|
|
4463
|
+
}
|
|
4464
|
+
try {
|
|
4465
|
+
let states = {};
|
|
4466
|
+
let docId = "global";
|
|
4467
|
+
const doc = await db.findOne({
|
|
4468
|
+
collection: "_globals_plugin-settings",
|
|
4469
|
+
where: {}
|
|
4470
|
+
});
|
|
4471
|
+
if (doc) {
|
|
4472
|
+
states = doc.states || {};
|
|
4473
|
+
docId = doc.id;
|
|
4474
|
+
}
|
|
4475
|
+
states[pluginName] = newEnabled;
|
|
4476
|
+
if (doc) {
|
|
4477
|
+
await db.update({
|
|
4478
|
+
collection: "_globals_plugin-settings",
|
|
4479
|
+
id: docId,
|
|
4480
|
+
data: { states }
|
|
4481
|
+
});
|
|
4482
|
+
} else {
|
|
4483
|
+
await db.create({
|
|
4484
|
+
collection: "_globals_plugin-settings",
|
|
4485
|
+
data: { states, id: "global" }
|
|
4486
|
+
});
|
|
4487
|
+
}
|
|
4488
|
+
} catch (e) {
|
|
4489
|
+
console.warn(`[Plugins] Could not persist state for "${pluginName}":`, e);
|
|
4490
|
+
}
|
|
4491
|
+
storageRegistry.setPluginEnabled(pluginName, newEnabled);
|
|
4492
|
+
return c.json({ name: pluginName, enabled: newEnabled });
|
|
4493
|
+
} catch (error) {
|
|
4494
|
+
console.error("[Plugins] toggle error:", error);
|
|
4495
|
+
return c.json({ error: error.message }, 500);
|
|
4496
|
+
}
|
|
4497
|
+
});
|
|
4228
4498
|
app.get("/api/health", (c) => {
|
|
4229
4499
|
return c.json({
|
|
4230
4500
|
status: "ok",
|
|
@@ -4233,6 +4503,102 @@ function createHonoApp(options) {
|
|
|
4233
4503
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4234
4504
|
});
|
|
4235
4505
|
});
|
|
4506
|
+
app.get("/api/kyro/schema", async (c) => {
|
|
4507
|
+
const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4508
|
+
if (!ctxUser) return c.json({ error: "Authentication required" }, 401);
|
|
4509
|
+
const extractFields = (fields) => fields.map((f) => {
|
|
4510
|
+
const meta = {
|
|
4511
|
+
name: f.name,
|
|
4512
|
+
type: f.type,
|
|
4513
|
+
label: f.label,
|
|
4514
|
+
required: f.required ?? false,
|
|
4515
|
+
unique: f.unique ?? false,
|
|
4516
|
+
indexed: f.indexed ?? false,
|
|
4517
|
+
defaultValue: f.defaultValue,
|
|
4518
|
+
admin: f.admin ? { ...f.admin } : void 0
|
|
4519
|
+
};
|
|
4520
|
+
if (f.minLength !== void 0) meta.minLength = f.minLength;
|
|
4521
|
+
if (f.maxLength !== void 0) meta.maxLength = f.maxLength;
|
|
4522
|
+
if (f.pattern !== void 0) meta.pattern = f.pattern;
|
|
4523
|
+
if (f.variant !== void 0) meta.variant = f.variant;
|
|
4524
|
+
if (f.hasMany !== void 0) meta.hasMany = f.hasMany;
|
|
4525
|
+
if (f.min !== void 0) meta.min = f.min;
|
|
4526
|
+
if (f.max !== void 0) meta.max = f.max;
|
|
4527
|
+
if (f.step !== void 0) meta.step = f.step;
|
|
4528
|
+
if (f.integer !== void 0) meta.integer = f.integer;
|
|
4529
|
+
if (f.options) meta.options = f.options;
|
|
4530
|
+
if (f.relationTo) meta.relationTo = f.relationTo;
|
|
4531
|
+
if (f.maxDepth !== void 0) meta.maxDepth = f.maxDepth;
|
|
4532
|
+
if (f.minRows !== void 0) meta.minRows = f.minRows;
|
|
4533
|
+
if (f.maxRows !== void 0) meta.maxRows = f.maxRows;
|
|
4534
|
+
if (f.localized !== void 0) meta.localized = f.localized;
|
|
4535
|
+
if (f.language) meta.language = f.language;
|
|
4536
|
+
if (f.format) meta.format = f.format;
|
|
4537
|
+
if (f.allowedTypes) meta.allowedTypes = f.allowedTypes;
|
|
4538
|
+
if (f.maxSize) meta.maxSize = f.maxSize;
|
|
4539
|
+
if (f.type === "group" || f.type === "row" || f.type === "collapsible" && f.fields) {
|
|
4540
|
+
meta.fields = extractFields(f.fields);
|
|
4541
|
+
}
|
|
4542
|
+
if (f.type === "array" && f.fields) {
|
|
4543
|
+
meta.fields = extractFields(f.fields);
|
|
4544
|
+
}
|
|
4545
|
+
if (f.type === "blocks" && f.blocks) {
|
|
4546
|
+
meta.blocks = f.blocks.map((b) => ({
|
|
4547
|
+
slug: b.slug,
|
|
4548
|
+
label: b.label,
|
|
4549
|
+
fields: extractFields(b.fields)
|
|
4550
|
+
}));
|
|
4551
|
+
}
|
|
4552
|
+
if (f.type === "tabs" && f.tabs) {
|
|
4553
|
+
meta.tabs = f.tabs.map((t) => ({
|
|
4554
|
+
label: t.label,
|
|
4555
|
+
name: t.name,
|
|
4556
|
+
fields: extractFields(t.fields)
|
|
4557
|
+
}));
|
|
4558
|
+
}
|
|
4559
|
+
return meta;
|
|
4560
|
+
});
|
|
4561
|
+
const data = { collections: {}, globals: {} };
|
|
4562
|
+
for (const col of registry.getCollections()) {
|
|
4563
|
+
const slug = col.slug;
|
|
4564
|
+
try {
|
|
4565
|
+
data.collections[slug] = {
|
|
4566
|
+
slug,
|
|
4567
|
+
label: col.label || slug,
|
|
4568
|
+
fields: extractFields(col.fields),
|
|
4569
|
+
jsonSchema: zodToJsonSchema.zodToJsonSchema(registry.getZodSchema(slug), { target: "openApi3" }),
|
|
4570
|
+
createSchema: zodToJsonSchema.zodToJsonSchema(registry.getCreateZodSchema(slug), { target: "openApi3" }),
|
|
4571
|
+
updateSchema: zodToJsonSchema.zodToJsonSchema(registry.getUpdateZodSchema(slug), { target: "openApi3" }),
|
|
4572
|
+
procedures: {
|
|
4573
|
+
find: { collection: slug, where: "Record<string,any>", sort: "string", limit: "number", page: "number", depth: "number", select: "string[]", draft: "boolean" },
|
|
4574
|
+
findByID: { collection: slug, id: "string", depth: "number", select: "string[]", draft: "boolean" },
|
|
4575
|
+
create: { collection: slug, data: "Record<string,any>", depth: "number", select: "string[]" },
|
|
4576
|
+
update: { collection: slug, id: "string", data: "Record<string,any>", depth: "number", select: "string[]", baseUpdatedAt: "string" },
|
|
4577
|
+
delete: { collection: slug, id: "string" },
|
|
4578
|
+
count: { collection: slug, where: "Record<string,any>" }
|
|
4579
|
+
}
|
|
4580
|
+
};
|
|
4581
|
+
} catch {
|
|
4582
|
+
}
|
|
4583
|
+
}
|
|
4584
|
+
for (const global of registry.getGlobals()) {
|
|
4585
|
+
const slug = global.slug;
|
|
4586
|
+
try {
|
|
4587
|
+
data.globals[slug] = {
|
|
4588
|
+
slug,
|
|
4589
|
+
label: global.label || slug,
|
|
4590
|
+
fields: extractFields(global.fields),
|
|
4591
|
+
jsonSchema: zodToJsonSchema.zodToJsonSchema(registry.getZodSchema(slug), { target: "openApi3" }),
|
|
4592
|
+
procedures: {
|
|
4593
|
+
get: {},
|
|
4594
|
+
update: { data: "Record<string,any>" }
|
|
4595
|
+
}
|
|
4596
|
+
};
|
|
4597
|
+
} catch {
|
|
4598
|
+
}
|
|
4599
|
+
}
|
|
4600
|
+
return c.json(data);
|
|
4601
|
+
});
|
|
4236
4602
|
app.get("/api/collections", async (c) => {
|
|
4237
4603
|
const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4238
4604
|
if (!ctxUser) return c.json({ error: "Authentication required" }, 401);
|
|
@@ -4248,6 +4614,129 @@ function createHonoApp(options) {
|
|
|
4248
4614
|
}));
|
|
4249
4615
|
return c.json(collections2);
|
|
4250
4616
|
});
|
|
4617
|
+
function resolveDocField(fields, doc, fieldName) {
|
|
4618
|
+
if (fieldName in doc) return doc[fieldName];
|
|
4619
|
+
for (const field of fields) {
|
|
4620
|
+
if (!field.name) continue;
|
|
4621
|
+
if (field.type === "tabs" && field.tabs) {
|
|
4622
|
+
const data = doc[field.name];
|
|
4623
|
+
if (data && typeof data === "object" && fieldName in data) return data[fieldName];
|
|
4624
|
+
}
|
|
4625
|
+
if ((field.type === "group" || field.type === "collapsible") && field.fields) {
|
|
4626
|
+
const data = doc[field.name];
|
|
4627
|
+
if (data && typeof data === "object") {
|
|
4628
|
+
if (fieldName in data) return data[fieldName];
|
|
4629
|
+
const nested = resolveDocField(field.fields, data, fieldName);
|
|
4630
|
+
if (nested !== void 0) return nested;
|
|
4631
|
+
}
|
|
4632
|
+
}
|
|
4633
|
+
}
|
|
4634
|
+
return void 0;
|
|
4635
|
+
}
|
|
4636
|
+
function flattenRelationshipFields(fields) {
|
|
4637
|
+
const relFields = [];
|
|
4638
|
+
for (const field of fields) {
|
|
4639
|
+
if (field.type === "relationship") {
|
|
4640
|
+
relFields.push(field);
|
|
4641
|
+
} else if (field.type === "tabs" && field.tabs) {
|
|
4642
|
+
for (const tab of field.tabs) {
|
|
4643
|
+
relFields.push(...flattenRelationshipFields(tab.fields || []));
|
|
4644
|
+
}
|
|
4645
|
+
} else if ((field.type === "group" || field.type === "collapsible") && field.fields) {
|
|
4646
|
+
relFields.push(...flattenRelationshipFields(field.fields || []));
|
|
4647
|
+
}
|
|
4648
|
+
}
|
|
4649
|
+
return relFields;
|
|
4650
|
+
}
|
|
4651
|
+
function extractRelValue(value) {
|
|
4652
|
+
if (!value) return [];
|
|
4653
|
+
if (typeof value === "string") return [value];
|
|
4654
|
+
if (Array.isArray(value)) return value.map((v) => typeof v === "object" ? v.value ?? v : v);
|
|
4655
|
+
if (typeof value === "object") {
|
|
4656
|
+
const v = value.value ?? value;
|
|
4657
|
+
return typeof v === "string" ? [v] : Array.isArray(v) ? v : [];
|
|
4658
|
+
}
|
|
4659
|
+
return [];
|
|
4660
|
+
}
|
|
4661
|
+
async function populateRelationships(docs, collection, db2, registry2) {
|
|
4662
|
+
if (docs.length === 0) return;
|
|
4663
|
+
const relFields = flattenRelationshipFields(collection.fields);
|
|
4664
|
+
if (relFields.length === 0) return;
|
|
4665
|
+
for (const relField of relFields) {
|
|
4666
|
+
const targetSlugs = Array.isArray(relField.relationTo) ? relField.relationTo : [relField.relationTo];
|
|
4667
|
+
const idsBySlug = {};
|
|
4668
|
+
for (const slug of targetSlugs) {
|
|
4669
|
+
idsBySlug[slug] = /* @__PURE__ */ new Set();
|
|
4670
|
+
}
|
|
4671
|
+
for (const doc of docs) {
|
|
4672
|
+
const raw = resolveDocField(collection.fields, doc, relField.name);
|
|
4673
|
+
const ids = extractRelValue(raw);
|
|
4674
|
+
for (const id of ids) {
|
|
4675
|
+
if (!id) continue;
|
|
4676
|
+
if (targetSlugs.length === 1) {
|
|
4677
|
+
idsBySlug[targetSlugs[0]].add(id);
|
|
4678
|
+
} else {
|
|
4679
|
+
for (const slug of targetSlugs) {
|
|
4680
|
+
idsBySlug[slug].add(id);
|
|
4681
|
+
}
|
|
4682
|
+
}
|
|
4683
|
+
}
|
|
4684
|
+
}
|
|
4685
|
+
for (const [targetSlug, idSet] of Object.entries(idsBySlug)) {
|
|
4686
|
+
if (idSet.size === 0) continue;
|
|
4687
|
+
const targetCollection = registry2.getCollection(targetSlug);
|
|
4688
|
+
if (!targetCollection) continue;
|
|
4689
|
+
const titleField = targetCollection.admin?.useAsTitle || "title";
|
|
4690
|
+
const idArr = Array.from(idSet);
|
|
4691
|
+
const relatedDocs = [];
|
|
4692
|
+
for (const id of idArr) {
|
|
4693
|
+
try {
|
|
4694
|
+
const relDoc = await db2.findByID({ collection: targetSlug, id, draft: true });
|
|
4695
|
+
if (relDoc) {
|
|
4696
|
+
const title = resolveDocField(targetCollection.fields, relDoc, titleField) ?? id;
|
|
4697
|
+
relatedDocs.push({ id, title: String(title) });
|
|
4698
|
+
}
|
|
4699
|
+
} catch {
|
|
4700
|
+
relatedDocs.push({ id, title: id });
|
|
4701
|
+
}
|
|
4702
|
+
}
|
|
4703
|
+
const titleMap = new Map(relatedDocs.map((d) => [d.id, d.title]));
|
|
4704
|
+
for (const doc of docs) {
|
|
4705
|
+
const raw = resolveDocField(collection.fields, doc, relField.name);
|
|
4706
|
+
if (!raw) continue;
|
|
4707
|
+
const setValue = (val) => {
|
|
4708
|
+
if (relField.name in doc) {
|
|
4709
|
+
doc[relField.name] = val;
|
|
4710
|
+
} else {
|
|
4711
|
+
for (const f of collection.fields) {
|
|
4712
|
+
if (f.type === "tabs" && f.tabs) {
|
|
4713
|
+
for (const tab of f.tabs) {
|
|
4714
|
+
if (tab.fields?.some((tf) => tf.name === relField.name)) {
|
|
4715
|
+
const tabData = doc[f.name];
|
|
4716
|
+
if (tabData && typeof tabData === "object") {
|
|
4717
|
+
tabData[relField.name] = val;
|
|
4718
|
+
}
|
|
4719
|
+
}
|
|
4720
|
+
}
|
|
4721
|
+
}
|
|
4722
|
+
}
|
|
4723
|
+
}
|
|
4724
|
+
};
|
|
4725
|
+
if (typeof raw === "string") {
|
|
4726
|
+
setValue({ id: raw, title: titleMap.get(raw) || raw });
|
|
4727
|
+
} else if (Array.isArray(raw)) {
|
|
4728
|
+
setValue(raw.map((v) => {
|
|
4729
|
+
const id = typeof v === "object" ? v.value ?? v.id : v;
|
|
4730
|
+
return { id, title: titleMap.get(id) || id };
|
|
4731
|
+
}));
|
|
4732
|
+
} else if (typeof raw === "object") {
|
|
4733
|
+
const id = raw.value ?? raw.id;
|
|
4734
|
+
setValue({ id, title: titleMap.get(id) || id });
|
|
4735
|
+
}
|
|
4736
|
+
}
|
|
4737
|
+
}
|
|
4738
|
+
}
|
|
4739
|
+
}
|
|
4251
4740
|
app.get("/api/search", async (c) => {
|
|
4252
4741
|
try {
|
|
4253
4742
|
const query = c.req.query("q") || "";
|
|
@@ -4299,11 +4788,12 @@ function createHonoApp(options) {
|
|
|
4299
4788
|
limit,
|
|
4300
4789
|
tenantID: ctxTenantID
|
|
4301
4790
|
});
|
|
4791
|
+
await populateRelationships(searchResult.docs, collection, db, registry);
|
|
4302
4792
|
for (const doc of searchResult.docs) {
|
|
4303
4793
|
const titleField = collection.admin?.useAsTitle || searchableFields.find(
|
|
4304
4794
|
(f) => f === "title" || f === "name" || f === "heading" || f === "slug"
|
|
4305
4795
|
);
|
|
4306
|
-
const title = titleField ? doc
|
|
4796
|
+
const title = titleField ? resolveDocField(collection.fields, doc, titleField) ?? doc.id : doc.id;
|
|
4307
4797
|
results.push({
|
|
4308
4798
|
collection: collection.slug,
|
|
4309
4799
|
label: collection.label || collection.slug,
|
|
@@ -4325,13 +4815,13 @@ function createHonoApp(options) {
|
|
|
4325
4815
|
app.get("/api/keys", async (c) => {
|
|
4326
4816
|
try {
|
|
4327
4817
|
const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4328
|
-
if (!ctxUser || !
|
|
4818
|
+
if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:read")) {
|
|
4329
4819
|
return c.json({ error: "Forbidden" }, 403);
|
|
4330
4820
|
}
|
|
4331
4821
|
const page = parseInt(c.req.query("page") || "1");
|
|
4332
4822
|
const limit = Math.min(parseInt(c.req.query("limit") || "50"), 100);
|
|
4333
|
-
console.log("[ApiKeys] Querying",
|
|
4334
|
-
const result = await db.find({ collection:
|
|
4823
|
+
console.log("[ApiKeys] Querying", chunk4M7X5HAB_cjs.API_KEY_COLLECTION, { page, limit });
|
|
4824
|
+
const result = await db.find({ collection: chunk4M7X5HAB_cjs.API_KEY_COLLECTION, where: {}, page, limit });
|
|
4335
4825
|
console.log("[ApiKeys] Result:", result);
|
|
4336
4826
|
const docs = (result.docs || []).map((doc) => ({
|
|
4337
4827
|
id: doc.id,
|
|
@@ -4350,21 +4840,21 @@ function createHonoApp(options) {
|
|
|
4350
4840
|
app.post("/api/keys", async (c) => {
|
|
4351
4841
|
try {
|
|
4352
4842
|
const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4353
|
-
if (!ctxUser || !
|
|
4843
|
+
if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:admin")) {
|
|
4354
4844
|
return c.json({ error: "Forbidden" }, 403);
|
|
4355
4845
|
}
|
|
4356
4846
|
const body = await c.req.json();
|
|
4357
4847
|
if (!body.name || typeof body.name !== "string") {
|
|
4358
4848
|
return c.json({ error: "name is required" }, 400);
|
|
4359
4849
|
}
|
|
4360
|
-
const rawKey =
|
|
4850
|
+
const rawKey = chunk4M7X5HAB_cjs.generateApiKey();
|
|
4361
4851
|
const doc = await db.create({
|
|
4362
|
-
collection:
|
|
4852
|
+
collection: chunk4M7X5HAB_cjs.API_KEY_COLLECTION,
|
|
4363
4853
|
data: {
|
|
4364
4854
|
userId: ctxUser.id,
|
|
4365
4855
|
name: body.name,
|
|
4366
4856
|
key: rawKey,
|
|
4367
|
-
keyPrefix:
|
|
4857
|
+
keyPrefix: chunk4M7X5HAB_cjs.generateApiKeyPrefix(rawKey),
|
|
4368
4858
|
permissions: Array.isArray(body.permissions) ? body.permissions : ["*"],
|
|
4369
4859
|
expiresAt: body.expiresAt || null,
|
|
4370
4860
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -4391,13 +4881,13 @@ function createHonoApp(options) {
|
|
|
4391
4881
|
app.delete("/api/keys/:id", async (c) => {
|
|
4392
4882
|
try {
|
|
4393
4883
|
const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4394
|
-
if (!ctxUser || !
|
|
4884
|
+
if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:admin")) {
|
|
4395
4885
|
return c.json({ error: "Forbidden" }, 403);
|
|
4396
4886
|
}
|
|
4397
4887
|
const id = c.req.param("id");
|
|
4398
|
-
const existing = await db.findByID({ collection:
|
|
4888
|
+
const existing = await db.findByID({ collection: chunk4M7X5HAB_cjs.API_KEY_COLLECTION, id });
|
|
4399
4889
|
if (!existing) return c.json({ error: "API key not found" }, 404);
|
|
4400
|
-
await db.delete({ collection:
|
|
4890
|
+
await db.delete({ collection: chunk4M7X5HAB_cjs.API_KEY_COLLECTION, id });
|
|
4401
4891
|
await sessionAuthAdapter?.createAuditLog({
|
|
4402
4892
|
action: "api_key_delete",
|
|
4403
4893
|
userId: ctxUser.id,
|
|
@@ -4415,19 +4905,19 @@ function createHonoApp(options) {
|
|
|
4415
4905
|
app.patch("/api/keys/:id", async (c) => {
|
|
4416
4906
|
try {
|
|
4417
4907
|
const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4418
|
-
if (!ctxUser || !
|
|
4908
|
+
if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:admin")) {
|
|
4419
4909
|
return c.json({ error: "Forbidden" }, 403);
|
|
4420
4910
|
}
|
|
4421
4911
|
const id = c.req.param("id");
|
|
4422
4912
|
const body = await c.req.json();
|
|
4423
|
-
const existing = await db.findByID({ collection:
|
|
4913
|
+
const existing = await db.findByID({ collection: chunk4M7X5HAB_cjs.API_KEY_COLLECTION, id });
|
|
4424
4914
|
if (!existing) return c.json({ error: "API key not found" }, 404);
|
|
4425
4915
|
const updateData = {};
|
|
4426
4916
|
if (typeof body.name === "string" && body.name.trim()) updateData.name = body.name.trim();
|
|
4427
4917
|
if (Array.isArray(body.permissions)) updateData.permissions = body.permissions;
|
|
4428
4918
|
if (body.expiresAt !== void 0) updateData.expiresAt = body.expiresAt || null;
|
|
4429
4919
|
if (Object.keys(updateData).length === 0) return c.json({ error: "Nothing to update" }, 400);
|
|
4430
|
-
const updated = await db.update({ collection:
|
|
4920
|
+
const updated = await db.update({ collection: chunk4M7X5HAB_cjs.API_KEY_COLLECTION, id, data: updateData });
|
|
4431
4921
|
return c.json({ ...updated, keyPrefix: existing.keyPrefix });
|
|
4432
4922
|
} catch (error) {
|
|
4433
4923
|
console.error("[ApiKeys] PATCH error:", error);
|
|
@@ -4437,19 +4927,19 @@ function createHonoApp(options) {
|
|
|
4437
4927
|
app.post("/api/keys/:id/rotate", async (c) => {
|
|
4438
4928
|
try {
|
|
4439
4929
|
const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4440
|
-
if (!ctxUser || !
|
|
4930
|
+
if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:admin")) {
|
|
4441
4931
|
return c.json({ error: "Forbidden" }, 403);
|
|
4442
4932
|
}
|
|
4443
4933
|
const id = c.req.param("id");
|
|
4444
|
-
const existing = await db.findByID({ collection:
|
|
4934
|
+
const existing = await db.findByID({ collection: chunk4M7X5HAB_cjs.API_KEY_COLLECTION, id });
|
|
4445
4935
|
if (!existing) return c.json({ error: "API key not found" }, 404);
|
|
4446
|
-
const rawKey =
|
|
4936
|
+
const rawKey = chunk4M7X5HAB_cjs.generateApiKey();
|
|
4447
4937
|
const updated = await db.update({
|
|
4448
|
-
collection:
|
|
4938
|
+
collection: chunk4M7X5HAB_cjs.API_KEY_COLLECTION,
|
|
4449
4939
|
id,
|
|
4450
4940
|
data: {
|
|
4451
4941
|
key: rawKey,
|
|
4452
|
-
keyPrefix:
|
|
4942
|
+
keyPrefix: chunk4M7X5HAB_cjs.generateApiKeyPrefix(rawKey),
|
|
4453
4943
|
lastUsedAt: null
|
|
4454
4944
|
}
|
|
4455
4945
|
});
|
|
@@ -4475,7 +4965,7 @@ function createHonoApp(options) {
|
|
|
4475
4965
|
app.get("/api/webhooks", async (c) => {
|
|
4476
4966
|
try {
|
|
4477
4967
|
const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4478
|
-
if (!ctxUser || !
|
|
4968
|
+
if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:read")) {
|
|
4479
4969
|
return c.json({ error: "Forbidden" }, 403);
|
|
4480
4970
|
}
|
|
4481
4971
|
if (!webhookService) return c.json({ error: "Webhook service not available" }, 503);
|
|
@@ -4489,7 +4979,7 @@ function createHonoApp(options) {
|
|
|
4489
4979
|
app.post("/api/webhooks", async (c) => {
|
|
4490
4980
|
try {
|
|
4491
4981
|
const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4492
|
-
if (!ctxUser || !
|
|
4982
|
+
if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:admin")) {
|
|
4493
4983
|
return c.json({ error: "Forbidden" }, 403);
|
|
4494
4984
|
}
|
|
4495
4985
|
if (!webhookService) return c.json({ error: "Webhook service not available" }, 503);
|
|
@@ -4512,7 +5002,7 @@ function createHonoApp(options) {
|
|
|
4512
5002
|
app.get("/api/webhooks/:id", async (c) => {
|
|
4513
5003
|
try {
|
|
4514
5004
|
const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4515
|
-
if (!ctxUser || !
|
|
5005
|
+
if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:read")) {
|
|
4516
5006
|
return c.json({ error: "Forbidden" }, 403);
|
|
4517
5007
|
}
|
|
4518
5008
|
if (!webhookService) return c.json({ error: "Webhook service not available" }, 503);
|
|
@@ -4528,7 +5018,7 @@ function createHonoApp(options) {
|
|
|
4528
5018
|
app.patch("/api/webhooks/:id", async (c) => {
|
|
4529
5019
|
try {
|
|
4530
5020
|
const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4531
|
-
if (!ctxUser || !
|
|
5021
|
+
if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:admin")) {
|
|
4532
5022
|
return c.json({ error: "Forbidden" }, 403);
|
|
4533
5023
|
}
|
|
4534
5024
|
if (!webhookService) return c.json({ error: "Webhook service not available" }, 503);
|
|
@@ -4553,7 +5043,7 @@ function createHonoApp(options) {
|
|
|
4553
5043
|
app.delete("/api/webhooks/:id", async (c) => {
|
|
4554
5044
|
try {
|
|
4555
5045
|
const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4556
|
-
if (!ctxUser || !
|
|
5046
|
+
if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:admin")) {
|
|
4557
5047
|
return c.json({ error: "Forbidden" }, 403);
|
|
4558
5048
|
}
|
|
4559
5049
|
if (!webhookService) return c.json({ error: "Webhook service not available" }, 503);
|
|
@@ -4578,7 +5068,7 @@ function createHonoApp(options) {
|
|
|
4578
5068
|
app.post("/api/webhooks/:id/test", async (c) => {
|
|
4579
5069
|
try {
|
|
4580
5070
|
const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4581
|
-
if (!ctxUser || !
|
|
5071
|
+
if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:admin")) {
|
|
4582
5072
|
return c.json({ error: "Forbidden" }, 403);
|
|
4583
5073
|
}
|
|
4584
5074
|
if (!webhookService) return c.json({ error: "Webhook service not available" }, 503);
|
|
@@ -4594,7 +5084,7 @@ function createHonoApp(options) {
|
|
|
4594
5084
|
app.get("/api/webhooks/:id/history", async (c) => {
|
|
4595
5085
|
try {
|
|
4596
5086
|
const { user: ctxUser } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
4597
|
-
if (!ctxUser || !
|
|
5087
|
+
if (!ctxUser || !chunkNKPKR5BW_cjs.hasPermission(ctxUser, "users:read")) {
|
|
4598
5088
|
return c.json({ error: "Forbidden" }, 403);
|
|
4599
5089
|
}
|
|
4600
5090
|
if (!webhookService) return c.json({ error: "Webhook service not available" }, 503);
|
|
@@ -4643,37 +5133,31 @@ function createHonoApp(options) {
|
|
|
4643
5133
|
if (!access.allowed) {
|
|
4644
5134
|
return c.json({ error: access.error }, access.status || 403);
|
|
4645
5135
|
}
|
|
4646
|
-
|
|
5136
|
+
if (ctxTenantID) {
|
|
5137
|
+
db.setTenantContext({ tenantId: ctxTenantID, userId: ctxUser?.id ?? "", role: ctxUser?.role, isSuperAdmin: ctxUser?.role === "super_admin" });
|
|
5138
|
+
}
|
|
4647
5139
|
const url = new URL(c.req.url);
|
|
4648
5140
|
const page = parseInt(url.searchParams.get("page") || "1");
|
|
4649
|
-
const limit = Math.min(
|
|
4650
|
-
parseInt(url.searchParams.get("limit") || "10"),
|
|
4651
|
-
100
|
|
4652
|
-
);
|
|
5141
|
+
const limit = Math.min(parseInt(url.searchParams.get("limit") || "10"), 100);
|
|
4653
5142
|
const sort = url.searchParams.get("sort") || void 0;
|
|
4654
5143
|
const depth = parseInt(url.searchParams.get("depth") || "0");
|
|
4655
5144
|
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
|
-
}
|
|
5145
|
+
const isDraftRequest = !!ctxUser;
|
|
4664
5146
|
const result = await db.find({
|
|
4665
5147
|
collection: slug,
|
|
4666
|
-
where,
|
|
5148
|
+
where: {},
|
|
4667
5149
|
sort,
|
|
4668
5150
|
limit,
|
|
4669
5151
|
page,
|
|
4670
5152
|
depth,
|
|
4671
5153
|
tenantID: ctxTenantID,
|
|
4672
|
-
select
|
|
5154
|
+
select,
|
|
5155
|
+
draft: isDraftRequest
|
|
4673
5156
|
});
|
|
5157
|
+
await populateRelationships(result.docs, collection, db, registry);
|
|
4674
5158
|
return c.json(result);
|
|
4675
5159
|
} catch (error) {
|
|
4676
|
-
console.error(
|
|
5160
|
+
console.error("[API] list error:", error);
|
|
4677
5161
|
return c.json({ error: error.message }, 500);
|
|
4678
5162
|
}
|
|
4679
5163
|
});
|
|
@@ -4698,6 +5182,9 @@ function createHonoApp(options) {
|
|
|
4698
5182
|
return c.json({ error: access.error }, access.status || 403);
|
|
4699
5183
|
}
|
|
4700
5184
|
const id = c.req.param("id");
|
|
5185
|
+
if (!id) {
|
|
5186
|
+
return c.json({ error: "Missing document ID" }, 400);
|
|
5187
|
+
}
|
|
4701
5188
|
const url = new URL(c.req.url);
|
|
4702
5189
|
const compareA = url.searchParams.get("compareA");
|
|
4703
5190
|
const compareB = url.searchParams.get("compareB");
|
|
@@ -4781,23 +5268,39 @@ function createHonoApp(options) {
|
|
|
4781
5268
|
const id = c.req.param("id");
|
|
4782
5269
|
const body = await c.req.json();
|
|
4783
5270
|
const baseUpdatedAt = readBaseUpdatedAt(body);
|
|
4784
|
-
const data = body.data ?? omitRevisionFields(body);
|
|
4785
5271
|
const originalDoc = await db.findByID({
|
|
4786
5272
|
collection: slug,
|
|
4787
5273
|
id,
|
|
4788
|
-
tenantID: ctxTenantID
|
|
5274
|
+
tenantID: ctxTenantID,
|
|
5275
|
+
draft: true
|
|
4789
5276
|
});
|
|
4790
5277
|
if (!originalDoc) {
|
|
4791
5278
|
return c.json({ error: "Document not found" }, 404);
|
|
4792
5279
|
}
|
|
5280
|
+
let finalData;
|
|
5281
|
+
if (body.delta) {
|
|
5282
|
+
finalData = { ...originalDoc, ...body.delta };
|
|
5283
|
+
} else {
|
|
5284
|
+
finalData = body.data ?? omitRevisionFields(body);
|
|
5285
|
+
}
|
|
4793
5286
|
const draft = await db.upsertDraft({
|
|
4794
5287
|
collection: slug,
|
|
4795
5288
|
documentId: id,
|
|
4796
5289
|
tenantID: ctxTenantID,
|
|
4797
|
-
data,
|
|
5290
|
+
data: finalData,
|
|
4798
5291
|
baseUpdatedAt,
|
|
4799
5292
|
draftUpdatedAt: body.draftUpdatedAt
|
|
4800
5293
|
});
|
|
5294
|
+
if (ctxUser) {
|
|
5295
|
+
sessionAuthAdapter?.createAuditLog({
|
|
5296
|
+
action: "document_update",
|
|
5297
|
+
userId: ctxUser.id,
|
|
5298
|
+
resource: slug,
|
|
5299
|
+
resourceId: id,
|
|
5300
|
+
success: true,
|
|
5301
|
+
metadata: { type: "draft_save" }
|
|
5302
|
+
});
|
|
5303
|
+
}
|
|
4801
5304
|
return c.json({ data: draft, message: "Draft saved successfully" });
|
|
4802
5305
|
} catch (error) {
|
|
4803
5306
|
return c.json({ error: error.message }, 500);
|
|
@@ -4828,6 +5331,16 @@ function createHonoApp(options) {
|
|
|
4828
5331
|
documentId: c.req.param("id"),
|
|
4829
5332
|
tenantID: ctxTenantID
|
|
4830
5333
|
});
|
|
5334
|
+
if (ctxUser) {
|
|
5335
|
+
sessionAuthAdapter?.createAuditLog({
|
|
5336
|
+
action: "document_update",
|
|
5337
|
+
userId: ctxUser.id,
|
|
5338
|
+
resource: slug,
|
|
5339
|
+
resourceId: c.req.param("id"),
|
|
5340
|
+
success: true,
|
|
5341
|
+
metadata: { type: "draft_discard" }
|
|
5342
|
+
});
|
|
5343
|
+
}
|
|
4831
5344
|
return c.json({ message: "Draft discarded successfully" });
|
|
4832
5345
|
} catch (error) {
|
|
4833
5346
|
return c.json({ error: error.message }, 500);
|
|
@@ -4870,12 +5383,14 @@ function createHonoApp(options) {
|
|
|
4870
5383
|
doc = await db.findOne({
|
|
4871
5384
|
collection: slug,
|
|
4872
5385
|
where: { slug: id },
|
|
4873
|
-
tenantID: ctxTenantID
|
|
5386
|
+
tenantID: ctxTenantID,
|
|
5387
|
+
draft: isDraftRequest
|
|
4874
5388
|
});
|
|
4875
5389
|
}
|
|
4876
5390
|
if (!doc) {
|
|
4877
5391
|
return c.json({ error: "Document not found" }, 404);
|
|
4878
5392
|
}
|
|
5393
|
+
await populateRelationships([doc], collection, db, registry);
|
|
4879
5394
|
return c.json({ data: doc });
|
|
4880
5395
|
} catch (error) {
|
|
4881
5396
|
return c.json({ error: error.message }, 500);
|
|
@@ -4901,23 +5416,80 @@ function createHonoApp(options) {
|
|
|
4901
5416
|
if (!access.allowed) {
|
|
4902
5417
|
return c.json({ error: access.error }, access.status || 403);
|
|
4903
5418
|
}
|
|
5419
|
+
if (ctxTenantID) {
|
|
5420
|
+
db.setTenantContext({ tenantId: ctxTenantID, userId: ctxUser?.id ?? "", role: ctxUser?.role, isSuperAdmin: ctxUser?.role === "super_admin" });
|
|
5421
|
+
}
|
|
4904
5422
|
auditApiKeyUsage(sessionAuthAdapter, apiKeyContext, basePath, "POST", c.req.raw);
|
|
4905
5423
|
const body = await c.req.json();
|
|
5424
|
+
let validated = body;
|
|
5425
|
+
for (const field of collection.fields) {
|
|
5426
|
+
if (field.name && validated[field.name] === "") {
|
|
5427
|
+
const isTextual = field.type === "text" || field.type === "textarea" || field.type === "code" || field.type === "markdown" || field.type === "email" || field.type === "password" || field.type === "color";
|
|
5428
|
+
if (!isTextual) {
|
|
5429
|
+
validated[field.name] = null;
|
|
5430
|
+
}
|
|
5431
|
+
}
|
|
5432
|
+
}
|
|
5433
|
+
convertRichtextFields(collection.fields, validated);
|
|
5434
|
+
const hookReq = c.req.raw;
|
|
5435
|
+
if (collection.hooks?.beforeValidate) {
|
|
5436
|
+
for (const hook of collection.hooks.beforeValidate) {
|
|
5437
|
+
const hookResult = await hook({
|
|
5438
|
+
collection: slug,
|
|
5439
|
+
data: validated,
|
|
5440
|
+
req: hookReq,
|
|
5441
|
+
user: ctxUser,
|
|
5442
|
+
tenantID: ctxTenantID,
|
|
5443
|
+
operation: "create"
|
|
5444
|
+
});
|
|
5445
|
+
if (hookResult) Object.assign(validated, hookResult);
|
|
5446
|
+
}
|
|
5447
|
+
}
|
|
4906
5448
|
const schema = registry.getCreateZodSchema(slug);
|
|
4907
|
-
let validated;
|
|
4908
5449
|
try {
|
|
4909
|
-
validated = schema.parse(
|
|
5450
|
+
validated = schema.parse(validated);
|
|
4910
5451
|
} catch (zodErr) {
|
|
4911
|
-
return c.json({ error:
|
|
5452
|
+
return c.json({ error: `Validation failed: ${formatZodErrors(zodErr.errors)}`, details: zodErr.errors }, 400);
|
|
4912
5453
|
}
|
|
4913
5454
|
if (collection.tenantScoped && ctxTenantID) {
|
|
4914
5455
|
validated.tenantID = ctxTenantID;
|
|
4915
5456
|
}
|
|
5457
|
+
const isDraftEnabled = collection.versions?.drafts === true;
|
|
5458
|
+
if (isDraftEnabled) {
|
|
5459
|
+
validated.publishStatus = "draft";
|
|
5460
|
+
validated.hasDraft = false;
|
|
5461
|
+
}
|
|
5462
|
+
if (collection.hooks?.beforeChange) {
|
|
5463
|
+
for (const hook of collection.hooks.beforeChange) {
|
|
5464
|
+
const hookResult = await hook({
|
|
5465
|
+
collection: slug,
|
|
5466
|
+
data: validated,
|
|
5467
|
+
req: hookReq,
|
|
5468
|
+
user: ctxUser,
|
|
5469
|
+
tenantID: ctxTenantID,
|
|
5470
|
+
operation: "create"
|
|
5471
|
+
});
|
|
5472
|
+
if (hookResult) Object.assign(validated, hookResult);
|
|
5473
|
+
}
|
|
5474
|
+
}
|
|
4916
5475
|
const doc = await db.create({
|
|
4917
5476
|
collection: slug,
|
|
4918
5477
|
data: validated,
|
|
4919
5478
|
tenantID: ctxTenantID
|
|
4920
5479
|
});
|
|
5480
|
+
if (collection.hooks?.afterChange) {
|
|
5481
|
+
for (const hook of collection.hooks.afterChange) {
|
|
5482
|
+
await hook({
|
|
5483
|
+
collection: slug,
|
|
5484
|
+
doc,
|
|
5485
|
+
data: validated,
|
|
5486
|
+
req: hookReq,
|
|
5487
|
+
user: ctxUser,
|
|
5488
|
+
tenantID: ctxTenantID,
|
|
5489
|
+
operation: "create"
|
|
5490
|
+
});
|
|
5491
|
+
}
|
|
5492
|
+
}
|
|
4921
5493
|
if (webhookService) {
|
|
4922
5494
|
webhookService.trigger(getWebhookEvent(slug, "create"), {
|
|
4923
5495
|
collection: slug,
|
|
@@ -4927,11 +5499,20 @@ function createHonoApp(options) {
|
|
|
4927
5499
|
tenantId: ctxTenantID
|
|
4928
5500
|
}).catch((err) => console.error(`[Webhook] Failed to trigger:`, err));
|
|
4929
5501
|
}
|
|
5502
|
+
if (ctxUser) {
|
|
5503
|
+
sessionAuthAdapter?.createAuditLog({
|
|
5504
|
+
action: "document_create",
|
|
5505
|
+
userId: ctxUser.id,
|
|
5506
|
+
resource: slug,
|
|
5507
|
+
resourceId: doc.id,
|
|
5508
|
+
success: true
|
|
5509
|
+
});
|
|
5510
|
+
}
|
|
4930
5511
|
return c.json({ data: doc, message: "Created successfully" }, 201);
|
|
4931
5512
|
} catch (error) {
|
|
4932
5513
|
if (error.name === "ZodError") {
|
|
4933
5514
|
return c.json(
|
|
4934
|
-
{ error:
|
|
5515
|
+
{ error: `Validation failed: ${formatZodErrors(error.errors)}`, details: error.errors },
|
|
4935
5516
|
400
|
|
4936
5517
|
);
|
|
4937
5518
|
}
|
|
@@ -4966,32 +5547,66 @@ function createHonoApp(options) {
|
|
|
4966
5547
|
bodyKeys: Object.keys(body),
|
|
4967
5548
|
tenantID: ctxTenantID
|
|
4968
5549
|
});
|
|
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
5550
|
const originalDoc = await db.findByID({
|
|
4978
5551
|
collection: slug,
|
|
4979
5552
|
id,
|
|
4980
5553
|
tenantID: ctxTenantID,
|
|
4981
5554
|
draft: true
|
|
4982
|
-
// Always fetch current doc regardless of status
|
|
4983
5555
|
});
|
|
4984
|
-
if (originalDoc) {
|
|
4985
|
-
console.log(`[PATCH] Original doc updatedAt:`, originalDoc.updatedAt);
|
|
4986
|
-
}
|
|
4987
5556
|
if (!originalDoc) {
|
|
4988
5557
|
return c.json({ error: "Document not found" }, 404);
|
|
4989
5558
|
}
|
|
4990
5559
|
if (baseUpdatedAt && originalDoc.updatedAt && baseUpdatedAt !== originalDoc.updatedAt) {
|
|
4991
5560
|
return c.json(buildConflictResponse(baseUpdatedAt, originalDoc), 409);
|
|
4992
5561
|
}
|
|
5562
|
+
let validated = Object.fromEntries(
|
|
5563
|
+
Object.entries(omitRevisionFields(body)).filter(
|
|
5564
|
+
([_, v]) => v !== "null" && v !== void 0
|
|
5565
|
+
)
|
|
5566
|
+
);
|
|
5567
|
+
for (const field of collection.fields) {
|
|
5568
|
+
if (field.name && validated[field.name] === "") {
|
|
5569
|
+
const isTextual = field.type === "text" || field.type === "textarea" || field.type === "code" || field.type === "markdown" || field.type === "email" || field.type === "password" || field.type === "color";
|
|
5570
|
+
if (!isTextual) {
|
|
5571
|
+
validated[field.name] = null;
|
|
5572
|
+
}
|
|
5573
|
+
}
|
|
5574
|
+
}
|
|
5575
|
+
convertRichtextFields(collection.fields, validated);
|
|
5576
|
+
const hookReq = c.req.raw;
|
|
5577
|
+
if (collection.hooks?.beforeValidate) {
|
|
5578
|
+
for (const hook of collection.hooks.beforeValidate) {
|
|
5579
|
+
const hookResult = await hook({
|
|
5580
|
+
collection: slug,
|
|
5581
|
+
data: validated,
|
|
5582
|
+
originalDoc,
|
|
5583
|
+
req: hookReq,
|
|
5584
|
+
user: ctxUser,
|
|
5585
|
+
tenantID: ctxTenantID,
|
|
5586
|
+
operation: "update"
|
|
5587
|
+
});
|
|
5588
|
+
if (hookResult) Object.assign(validated, hookResult);
|
|
5589
|
+
}
|
|
5590
|
+
}
|
|
5591
|
+
const schema = registry.getUpdateZodSchema(slug);
|
|
5592
|
+
validated = schema.parse(validated);
|
|
5593
|
+
if (collection.hooks?.beforeChange) {
|
|
5594
|
+
for (const hook of collection.hooks.beforeChange) {
|
|
5595
|
+
const hookResult = await hook({
|
|
5596
|
+
collection: slug,
|
|
5597
|
+
data: validated,
|
|
5598
|
+
originalDoc,
|
|
5599
|
+
req: hookReq,
|
|
5600
|
+
user: ctxUser,
|
|
5601
|
+
tenantID: ctxTenantID,
|
|
5602
|
+
operation: "update"
|
|
5603
|
+
});
|
|
5604
|
+
if (hookResult) Object.assign(validated, hookResult);
|
|
5605
|
+
}
|
|
5606
|
+
}
|
|
5607
|
+
console.log(`[PATCH] Validated data:`, Object.keys(validated));
|
|
4993
5608
|
const isDraftEnabled = collection.versions?.drafts === true;
|
|
4994
|
-
const isAlreadyPublished = originalDoc.
|
|
5609
|
+
const isAlreadyPublished = originalDoc.publishStatus === "published";
|
|
4995
5610
|
let doc;
|
|
4996
5611
|
if (isDraftEnabled && isAlreadyPublished) {
|
|
4997
5612
|
await db.createVersion({
|
|
@@ -5006,18 +5621,20 @@ function createHonoApp(options) {
|
|
|
5006
5621
|
await db.update({
|
|
5007
5622
|
collection: slug,
|
|
5008
5623
|
id,
|
|
5009
|
-
data: {
|
|
5624
|
+
data: { hasDraft: true },
|
|
5010
5625
|
// Keep old data, just set flag
|
|
5011
5626
|
tenantID: ctxTenantID
|
|
5012
5627
|
});
|
|
5013
5628
|
} else {
|
|
5014
|
-
const saveData = isDraftEnabled ? { ...validated,
|
|
5629
|
+
const saveData = isDraftEnabled ? { ...validated, publishStatus: "draft", hasDraft: false } : validated;
|
|
5630
|
+
console.log(`[PATCH] About to call db.update for ${slug}/${id} with keys:`, Object.keys(saveData));
|
|
5015
5631
|
await db.update({
|
|
5016
5632
|
collection: slug,
|
|
5017
5633
|
id,
|
|
5018
5634
|
data: saveData,
|
|
5019
5635
|
tenantID: ctxTenantID
|
|
5020
5636
|
});
|
|
5637
|
+
console.log(`[PATCH] db.update SUCCEEDED for ${slug}/${id}`);
|
|
5021
5638
|
}
|
|
5022
5639
|
doc = await db.findByID({
|
|
5023
5640
|
collection: slug,
|
|
@@ -5044,6 +5661,20 @@ function createHonoApp(options) {
|
|
|
5044
5661
|
tenantID: ctxTenantID
|
|
5045
5662
|
});
|
|
5046
5663
|
}
|
|
5664
|
+
if (collection.hooks?.afterChange) {
|
|
5665
|
+
for (const hook of collection.hooks.afterChange) {
|
|
5666
|
+
await hook({
|
|
5667
|
+
collection: slug,
|
|
5668
|
+
doc,
|
|
5669
|
+
data: validated,
|
|
5670
|
+
originalDoc,
|
|
5671
|
+
req: hookReq,
|
|
5672
|
+
user: ctxUser,
|
|
5673
|
+
tenantID: ctxTenantID,
|
|
5674
|
+
operation: "update"
|
|
5675
|
+
});
|
|
5676
|
+
}
|
|
5677
|
+
}
|
|
5047
5678
|
if (webhookService) {
|
|
5048
5679
|
webhookService.trigger(getWebhookEvent(slug, "update"), {
|
|
5049
5680
|
collection: slug,
|
|
@@ -5054,16 +5685,26 @@ function createHonoApp(options) {
|
|
|
5054
5685
|
tenantId: ctxTenantID
|
|
5055
5686
|
}).catch((err) => console.error(`[Webhook] Failed to trigger:`, err));
|
|
5056
5687
|
}
|
|
5057
|
-
|
|
5688
|
+
auditApiKeyUsage(sessionAuthAdapter, apiKeyContext, `${basePath}/${id}`, "PATCH", c.req.raw);
|
|
5689
|
+
if (ctxUser) {
|
|
5690
|
+
sessionAuthAdapter?.createAuditLog({
|
|
5691
|
+
action: "document_update",
|
|
5692
|
+
userId: ctxUser.id,
|
|
5693
|
+
resource: slug,
|
|
5694
|
+
resourceId: id,
|
|
5695
|
+
success: true
|
|
5696
|
+
});
|
|
5697
|
+
}
|
|
5058
5698
|
return c.json({ data: doc, message: isDraftEnabled ? "Draft saved" : "Updated successfully" });
|
|
5059
5699
|
} catch (error) {
|
|
5060
5700
|
if (error.name === "ZodError") {
|
|
5061
5701
|
console.error(`[PATCH ${basePath}/:id] Validation failed:`, error.errors);
|
|
5062
5702
|
return c.json(
|
|
5063
|
-
{ error:
|
|
5703
|
+
{ error: `Validation failed: ${formatZodErrors(error.errors)}`, details: error.errors },
|
|
5064
5704
|
400
|
|
5065
5705
|
);
|
|
5066
5706
|
}
|
|
5707
|
+
console.error(`[PATCH ${basePath}/:id] ERROR:`, error.message, `CAUSE:`, error.cause?.message || error.cause, `QUERY:`, error.query);
|
|
5067
5708
|
return c.json({ error: error.message }, 500);
|
|
5068
5709
|
}
|
|
5069
5710
|
});
|
|
@@ -5087,13 +5728,33 @@ function createHonoApp(options) {
|
|
|
5087
5728
|
if (!access.allowed) {
|
|
5088
5729
|
return c.json({ error: access.error }, access.status || 403);
|
|
5089
5730
|
}
|
|
5731
|
+
if (ctxTenantID) {
|
|
5732
|
+
db.setTenantContext({ tenantId: ctxTenantID, userId: ctxUser?.id ?? "", role: ctxUser?.role, isSuperAdmin: ctxUser?.role === "super_admin" });
|
|
5733
|
+
}
|
|
5090
5734
|
const id = c.req.param("id");
|
|
5091
5735
|
console.log(`[DELETE] Deleting ${slug}/${id}`);
|
|
5736
|
+
const hookReq = c.req.raw;
|
|
5092
5737
|
const originalDoc = await db.findByID({
|
|
5093
5738
|
collection: slug,
|
|
5094
5739
|
id,
|
|
5095
|
-
tenantID: ctxTenantID
|
|
5740
|
+
tenantID: ctxTenantID,
|
|
5741
|
+
draft: true
|
|
5096
5742
|
});
|
|
5743
|
+
if (!originalDoc) {
|
|
5744
|
+
return c.json({ error: "Document not found" }, 404);
|
|
5745
|
+
}
|
|
5746
|
+
if (collection.hooks?.beforeDelete) {
|
|
5747
|
+
for (const hook of collection.hooks.beforeDelete) {
|
|
5748
|
+
await hook({
|
|
5749
|
+
collection: slug,
|
|
5750
|
+
doc: originalDoc,
|
|
5751
|
+
req: hookReq,
|
|
5752
|
+
user: ctxUser,
|
|
5753
|
+
tenantID: ctxTenantID,
|
|
5754
|
+
operation: "delete"
|
|
5755
|
+
});
|
|
5756
|
+
}
|
|
5757
|
+
}
|
|
5097
5758
|
const doc = await db.delete({
|
|
5098
5759
|
collection: slug,
|
|
5099
5760
|
id,
|
|
@@ -5104,6 +5765,19 @@ function createHonoApp(options) {
|
|
|
5104
5765
|
documentId: id,
|
|
5105
5766
|
tenantID: ctxTenantID
|
|
5106
5767
|
});
|
|
5768
|
+
if (collection.hooks?.afterDelete) {
|
|
5769
|
+
for (const hook of collection.hooks.afterDelete) {
|
|
5770
|
+
await hook({
|
|
5771
|
+
collection: slug,
|
|
5772
|
+
doc,
|
|
5773
|
+
originalDoc,
|
|
5774
|
+
req: hookReq,
|
|
5775
|
+
user: ctxUser,
|
|
5776
|
+
tenantID: ctxTenantID,
|
|
5777
|
+
operation: "delete"
|
|
5778
|
+
});
|
|
5779
|
+
}
|
|
5780
|
+
}
|
|
5107
5781
|
if (webhookService) {
|
|
5108
5782
|
webhookService.trigger(getWebhookEvent(slug, "delete"), {
|
|
5109
5783
|
collection: slug,
|
|
@@ -5114,6 +5788,16 @@ function createHonoApp(options) {
|
|
|
5114
5788
|
tenantId: ctxTenantID
|
|
5115
5789
|
}).catch((err) => console.error(`[Webhook] Failed to trigger:`, err));
|
|
5116
5790
|
}
|
|
5791
|
+
auditApiKeyUsage(sessionAuthAdapter, apiKeyContext, `${basePath}/${id}`, "DELETE", c.req.raw);
|
|
5792
|
+
if (ctxUser) {
|
|
5793
|
+
sessionAuthAdapter?.createAuditLog({
|
|
5794
|
+
action: "document_delete",
|
|
5795
|
+
userId: ctxUser.id,
|
|
5796
|
+
resource: slug,
|
|
5797
|
+
resourceId: id,
|
|
5798
|
+
success: true
|
|
5799
|
+
});
|
|
5800
|
+
}
|
|
5117
5801
|
return c.json({ data: doc, message: "Deleted successfully" });
|
|
5118
5802
|
} catch (error) {
|
|
5119
5803
|
console.error(`[DELETE] Error deleting ${slug}:`, error);
|
|
@@ -5147,7 +5831,8 @@ function createHonoApp(options) {
|
|
|
5147
5831
|
const originalDoc = await db.findByID({
|
|
5148
5832
|
collection: slug,
|
|
5149
5833
|
id,
|
|
5150
|
-
tenantID: ctxTenantID
|
|
5834
|
+
tenantID: ctxTenantID,
|
|
5835
|
+
draft: true
|
|
5151
5836
|
});
|
|
5152
5837
|
if (!originalDoc) {
|
|
5153
5838
|
console.log("[Duplicate] Document not found");
|
|
@@ -5207,7 +5892,7 @@ function createHonoApp(options) {
|
|
|
5207
5892
|
const doc = await db.update({
|
|
5208
5893
|
collection: slug,
|
|
5209
5894
|
id,
|
|
5210
|
-
data: { ...version.data,
|
|
5895
|
+
data: { ...version.data, publishStatus: "draft", hasDraft: false },
|
|
5211
5896
|
tenantID: ctxTenantID
|
|
5212
5897
|
});
|
|
5213
5898
|
return c.json({
|
|
@@ -5252,7 +5937,7 @@ function createHonoApp(options) {
|
|
|
5252
5937
|
const doc = await db.update({
|
|
5253
5938
|
collection: slug,
|
|
5254
5939
|
id: c.req.param("id"),
|
|
5255
|
-
data: { ...version.data,
|
|
5940
|
+
data: { ...version.data, publishStatus: "draft", hasDraft: false },
|
|
5256
5941
|
tenantID: ctxTenantID
|
|
5257
5942
|
});
|
|
5258
5943
|
return c.json({
|
|
@@ -5285,6 +5970,9 @@ function createHonoApp(options) {
|
|
|
5285
5970
|
if (!access.allowed) {
|
|
5286
5971
|
return c.json({ error: access.error }, access.status || 403);
|
|
5287
5972
|
}
|
|
5973
|
+
if (ctxTenantID) {
|
|
5974
|
+
db.setTenantContext({ tenantId: ctxTenantID, userId: ctxUser?.id ?? "", role: ctxUser?.role, isSuperAdmin: ctxUser?.role === "super_admin" });
|
|
5975
|
+
}
|
|
5288
5976
|
const id = c.req.param("id");
|
|
5289
5977
|
const body = await c.req.json().catch(() => ({}));
|
|
5290
5978
|
const baseUpdatedAt = readBaseUpdatedAt(body);
|
|
@@ -5300,9 +5988,9 @@ function createHonoApp(options) {
|
|
|
5300
5988
|
if (baseUpdatedAt && originalDoc.updatedAt && baseUpdatedAt !== originalDoc.updatedAt) {
|
|
5301
5989
|
return c.json(buildConflictResponse(baseUpdatedAt, originalDoc), 409);
|
|
5302
5990
|
}
|
|
5303
|
-
let publishData = {
|
|
5991
|
+
let publishData = { publishStatus: "published", hasDraft: false };
|
|
5304
5992
|
let finalContent = originalDoc;
|
|
5305
|
-
if (originalDoc.
|
|
5993
|
+
if (originalDoc.hasDraft) {
|
|
5306
5994
|
const versions = await db.findVersions({
|
|
5307
5995
|
collection: slug,
|
|
5308
5996
|
documentId: id,
|
|
@@ -5325,7 +6013,7 @@ function createHonoApp(options) {
|
|
|
5325
6013
|
await db.createVersion({
|
|
5326
6014
|
collection: slug,
|
|
5327
6015
|
documentId: id,
|
|
5328
|
-
data: { ...finalContent,
|
|
6016
|
+
data: { ...finalContent, publishStatus: "published" },
|
|
5329
6017
|
status: "published",
|
|
5330
6018
|
createdBy: ctxUser?.id,
|
|
5331
6019
|
changeDescription: "Published",
|
|
@@ -5384,7 +6072,7 @@ function createHonoApp(options) {
|
|
|
5384
6072
|
const doc = await db.update({
|
|
5385
6073
|
collection: slug,
|
|
5386
6074
|
id,
|
|
5387
|
-
data: {
|
|
6075
|
+
data: { publishStatus: "draft" },
|
|
5388
6076
|
tenantID: ctxTenantID
|
|
5389
6077
|
});
|
|
5390
6078
|
if (webhookService) {
|
|
@@ -5420,12 +6108,30 @@ function createHonoApp(options) {
|
|
|
5420
6108
|
return c.json({ error: access.error }, access.status || 403);
|
|
5421
6109
|
}
|
|
5422
6110
|
const isDraftRequest = c.req.query("draft") === "true" && !!ctxUser;
|
|
5423
|
-
|
|
6111
|
+
let doc = await db.findOne({
|
|
5424
6112
|
collection: `_globals_${slug}`,
|
|
5425
6113
|
where: {},
|
|
5426
6114
|
tenantID: ctxTenantID,
|
|
5427
6115
|
draft: isDraftRequest
|
|
5428
6116
|
});
|
|
6117
|
+
if (slug === "system") {
|
|
6118
|
+
const newSecret = crypto__default.default.randomBytes(32).toString("hex");
|
|
6119
|
+
if (!doc) {
|
|
6120
|
+
doc = await db.create({
|
|
6121
|
+
collection: `_globals_${slug}`,
|
|
6122
|
+
data: { id: slug, appSecret: newSecret },
|
|
6123
|
+
tenantID: ctxTenantID
|
|
6124
|
+
});
|
|
6125
|
+
} else if (!doc.appSecret) {
|
|
6126
|
+
await db.update({
|
|
6127
|
+
collection: `_globals_${slug}`,
|
|
6128
|
+
id: slug,
|
|
6129
|
+
data: { appSecret: newSecret },
|
|
6130
|
+
tenantID: ctxTenantID
|
|
6131
|
+
});
|
|
6132
|
+
doc.appSecret = newSecret;
|
|
6133
|
+
}
|
|
6134
|
+
}
|
|
5429
6135
|
return c.json({ data: doc || {} });
|
|
5430
6136
|
} catch (error) {
|
|
5431
6137
|
return c.json({ error: error.message }, 500);
|
|
@@ -5445,18 +6151,27 @@ function createHonoApp(options) {
|
|
|
5445
6151
|
if (!access.allowed) {
|
|
5446
6152
|
return c.json({ error: access.error }, access.status || 403);
|
|
5447
6153
|
}
|
|
5448
|
-
const body = await c.req.json();
|
|
6154
|
+
const body = omitRevisionFields(await c.req.json());
|
|
5449
6155
|
const cleaned = Object.fromEntries(
|
|
5450
6156
|
Object.entries(body).filter(([_, v]) => v !== null && v !== "null" && v !== void 0)
|
|
5451
6157
|
);
|
|
6158
|
+
for (const field of globalConfig.fields) {
|
|
6159
|
+
if (field.name && cleaned[field.name] === "") {
|
|
6160
|
+
const isTextual = field.type === "text" || field.type === "textarea" || field.type === "code" || field.type === "markdown" || field.type === "email" || field.type === "password" || field.type === "color";
|
|
6161
|
+
if (!isTextual) {
|
|
6162
|
+
cleaned[field.name] = null;
|
|
6163
|
+
}
|
|
6164
|
+
}
|
|
6165
|
+
}
|
|
6166
|
+
convertRichtextFields(globalConfig.fields, cleaned);
|
|
5452
6167
|
const schema = registry.getZodSchema(slug);
|
|
5453
6168
|
let validated;
|
|
5454
6169
|
try {
|
|
5455
6170
|
validated = schema.parse(cleaned);
|
|
5456
6171
|
} catch (zodErr) {
|
|
5457
|
-
return c.json({ error:
|
|
6172
|
+
return c.json({ error: `Validation failed: ${formatZodErrors(zodErr.errors)}`, details: zodErr.errors }, 400);
|
|
5458
6173
|
}
|
|
5459
|
-
const SYSTEM_FIELDS = /* @__PURE__ */ new Set(["id", "createdAt", "updatedAt", "
|
|
6174
|
+
const SYSTEM_FIELDS = /* @__PURE__ */ new Set(["id", "createdAt", "updatedAt", "publishStatus", "hasDraft", "baseUpdatedAt", "_baseUpdatedAt"]);
|
|
5460
6175
|
const userData = Object.fromEntries(
|
|
5461
6176
|
Object.entries(validated).filter(([k]) => !SYSTEM_FIELDS.has(k))
|
|
5462
6177
|
);
|
|
@@ -5468,7 +6183,7 @@ function createHonoApp(options) {
|
|
|
5468
6183
|
draft: true
|
|
5469
6184
|
});
|
|
5470
6185
|
const isDraftEnabled = globalConfig.versions?.drafts === true;
|
|
5471
|
-
const isAlreadyPublished = originalDoc?.
|
|
6186
|
+
const isAlreadyPublished = originalDoc?.publishStatus === "published";
|
|
5472
6187
|
let doc;
|
|
5473
6188
|
if (isDraftEnabled && isAlreadyPublished) {
|
|
5474
6189
|
await db.createVersion({
|
|
@@ -5483,11 +6198,11 @@ function createHonoApp(options) {
|
|
|
5483
6198
|
doc = await db.update({
|
|
5484
6199
|
collection: collectionSlug,
|
|
5485
6200
|
id: slug,
|
|
5486
|
-
data: {
|
|
6201
|
+
data: { hasDraft: true },
|
|
5487
6202
|
tenantID: ctxTenantID
|
|
5488
6203
|
});
|
|
5489
6204
|
} else {
|
|
5490
|
-
const saveData = isDraftEnabled ? { ...userData,
|
|
6205
|
+
const saveData = isDraftEnabled ? { ...userData, publishStatus: "draft", hasDraft: false } : { ...userData, publishStatus: "published", hasDraft: false };
|
|
5491
6206
|
if (originalDoc) {
|
|
5492
6207
|
doc = await db.update({
|
|
5493
6208
|
collection: collectionSlug,
|
|
@@ -5518,6 +6233,15 @@ function createHonoApp(options) {
|
|
|
5518
6233
|
mediaService = null;
|
|
5519
6234
|
mediaServiceInitError = null;
|
|
5520
6235
|
}
|
|
6236
|
+
if (ctxUser) {
|
|
6237
|
+
sessionAuthAdapter?.createAuditLog({
|
|
6238
|
+
action: "settings_change",
|
|
6239
|
+
userId: ctxUser.id,
|
|
6240
|
+
resource: `global:${slug}`,
|
|
6241
|
+
resourceId: slug,
|
|
6242
|
+
success: true
|
|
6243
|
+
});
|
|
6244
|
+
}
|
|
5521
6245
|
return c.json({ data: doc, message: "Updated successfully" });
|
|
5522
6246
|
} catch (error) {
|
|
5523
6247
|
console.error(`[API] Save global "${slug}" failed:`, error);
|
|
@@ -5542,9 +6266,9 @@ function createHonoApp(options) {
|
|
|
5542
6266
|
draft: true
|
|
5543
6267
|
});
|
|
5544
6268
|
if (!originalDoc) return c.json({ error: "Global not found" }, 404);
|
|
5545
|
-
let publishData = {
|
|
6269
|
+
let publishData = { publishStatus: "published", hasDraft: false };
|
|
5546
6270
|
let finalContent = originalDoc;
|
|
5547
|
-
if (originalDoc.
|
|
6271
|
+
if (originalDoc.hasDraft) {
|
|
5548
6272
|
const versions = await db.findVersions({
|
|
5549
6273
|
collection: collectionSlug,
|
|
5550
6274
|
documentId: slug,
|
|
@@ -5567,7 +6291,7 @@ function createHonoApp(options) {
|
|
|
5567
6291
|
await db.createVersion({
|
|
5568
6292
|
collection: collectionSlug,
|
|
5569
6293
|
documentId: slug,
|
|
5570
|
-
data: { ...finalContent,
|
|
6294
|
+
data: { ...finalContent, publishStatus: "published" },
|
|
5571
6295
|
status: "published",
|
|
5572
6296
|
createdBy: ctxUser?.id,
|
|
5573
6297
|
changeDescription: "Published",
|
|
@@ -5587,7 +6311,7 @@ function createHonoApp(options) {
|
|
|
5587
6311
|
const doc = await db.update({
|
|
5588
6312
|
collection: `_globals_${slug}`,
|
|
5589
6313
|
id: slug,
|
|
5590
|
-
data: {
|
|
6314
|
+
data: { publishStatus: "draft", hasDraft: false },
|
|
5591
6315
|
tenantID: ctxTenantID
|
|
5592
6316
|
});
|
|
5593
6317
|
return c.json({ data: doc, message: "Unpublished successfully" });
|
|
@@ -5647,7 +6371,7 @@ function createHonoApp(options) {
|
|
|
5647
6371
|
const doc = await db.update({
|
|
5648
6372
|
collection: collectionSlug,
|
|
5649
6373
|
id: slug,
|
|
5650
|
-
data: { ...version.data,
|
|
6374
|
+
data: { ...version.data, publishStatus: "draft", hasDraft: false },
|
|
5651
6375
|
tenantID: ctxTenantID
|
|
5652
6376
|
});
|
|
5653
6377
|
return c.json({ data: doc, message: "Restored successfully" });
|
|
@@ -5693,7 +6417,7 @@ function createHonoApp(options) {
|
|
|
5693
6417
|
mailgun: body.mailgun,
|
|
5694
6418
|
ses: body.ses
|
|
5695
6419
|
};
|
|
5696
|
-
const transport = new
|
|
6420
|
+
const transport = new chunkWNCYAKF3_cjs.EmailTransport(transportConfig);
|
|
5697
6421
|
const recipient = body.testEmail || body.testEmailSection && body.testEmailSection.testEmail;
|
|
5698
6422
|
if (!recipient) {
|
|
5699
6423
|
return c.json({ error: "No test recipient email provided" }, 400);
|
|
@@ -5757,15 +6481,19 @@ exports.InMemoryAuditLogger = InMemoryAuditLogger;
|
|
|
5757
6481
|
exports.InMemoryRateLimiter = InMemoryRateLimiter;
|
|
5758
6482
|
exports.MediaService = MediaService;
|
|
5759
6483
|
exports.createAuditContext = createAuditContext2;
|
|
6484
|
+
exports.createCloudinaryStorage = createCloudinaryStorage;
|
|
6485
|
+
exports.createFtpStorage = createFtpStorage;
|
|
5760
6486
|
exports.createHonoApp = createHonoApp;
|
|
5761
6487
|
exports.createLocalStorage = createLocalStorage;
|
|
5762
6488
|
exports.createRESTAPI = createRESTAPI;
|
|
6489
|
+
exports.createS3Storage = createS3Storage;
|
|
5763
6490
|
exports.getAppSecret = getAppSecret;
|
|
6491
|
+
exports.getDefaultRegistry = getDefaultRegistry;
|
|
5764
6492
|
exports.getEncryptionKey = getEncryptionKey;
|
|
5765
6493
|
exports.getSessionConfig = getSessionConfig;
|
|
5766
6494
|
exports.init_secret = init_secret;
|
|
5767
6495
|
exports.loadSecrets = loadSecrets;
|
|
5768
6496
|
exports.resolveProvider = resolveProvider;
|
|
5769
6497
|
exports.setDbAdapter = setDbAdapter;
|
|
5770
|
-
//# sourceMappingURL=chunk-
|
|
5771
|
-
//# sourceMappingURL=chunk-
|
|
6498
|
+
//# sourceMappingURL=chunk-RSF3UU7H.cjs.map
|
|
6499
|
+
//# sourceMappingURL=chunk-RSF3UU7H.cjs.map
|