nextly 0.0.1 → 0.0.2-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +122 -0
- package/dist/_dts-chunks/collections-handler.d-DjgO74Wt.d.ts +20540 -0
- package/dist/_dts-chunks/config.d-DNwsDnjs.d.ts +2589 -0
- package/dist/_dts-chunks/define-component.d-BUgTHmt3.d.ts +1149 -0
- package/dist/_dts-chunks/image-processor.d-OO1PmMrv.d.ts +335 -0
- package/dist/_dts-chunks/index.d-axCAzZ7m.d.ts +17842 -0
- package/dist/_dts-chunks/media.d-DjDOZo4B.d.ts +117 -0
- package/dist/_dts-chunks/on-error.d-CHIKWNxd.d.ts +38 -0
- package/dist/_dts-chunks/storage.d-BUhQ2we_.d.ts +404 -0
- package/dist/actions/index.d.ts +239 -0
- package/dist/actions/index.mjs +281 -0
- package/dist/api/auth-state.d.ts +5 -0
- package/dist/api/auth-state.mjs +131 -0
- package/dist/api/collections-schema-detail.d.ts +56 -0
- package/dist/api/collections-schema-detail.mjs +244 -0
- package/dist/api/collections-schema-export.d.ts +56 -0
- package/dist/api/collections-schema-export.mjs +129 -0
- package/dist/api/collections-schema.d.ts +59 -0
- package/dist/api/collections-schema.mjs +207 -0
- package/dist/api/components-detail.d.ts +50 -0
- package/dist/api/components-detail.mjs +132 -0
- package/dist/api/components.d.ts +69 -0
- package/dist/api/components.mjs +144 -0
- package/dist/api/email-providers-default.d.ts +40 -0
- package/dist/api/email-providers-default.mjs +75 -0
- package/dist/api/email-providers-detail.d.ts +81 -0
- package/dist/api/email-providers-detail.mjs +109 -0
- package/dist/api/email-providers-test.d.ts +43 -0
- package/dist/api/email-providers-test.mjs +114 -0
- package/dist/api/email-providers.d.ts +69 -0
- package/dist/api/email-providers.mjs +110 -0
- package/dist/api/email-send-template.d.ts +41 -0
- package/dist/api/email-send-template.mjs +58 -0
- package/dist/api/email-send.d.ts +42 -0
- package/dist/api/email-send.mjs +58 -0
- package/dist/api/email-templates-detail.d.ts +74 -0
- package/dist/api/email-templates-detail.mjs +112 -0
- package/dist/api/email-templates-layout.d.ts +55 -0
- package/dist/api/email-templates-layout.mjs +92 -0
- package/dist/api/email-templates-preview.d.ts +48 -0
- package/dist/api/email-templates-preview.mjs +93 -0
- package/dist/api/email-templates.d.ts +61 -0
- package/dist/api/email-templates.mjs +118 -0
- package/dist/api/health.d.ts +68 -0
- package/dist/api/health.mjs +67 -0
- package/dist/api/index.d.ts +54 -0
- package/dist/api/index.mjs +16 -0
- package/dist/api/media-bulk.d.ts +74 -0
- package/dist/api/media-bulk.mjs +196 -0
- package/dist/api/media-folders.d.ts +112 -0
- package/dist/api/media-folders.mjs +187 -0
- package/dist/api/media-handlers.d.ts +102 -0
- package/dist/api/media-handlers.mjs +437 -0
- package/dist/api/media.d.ts +117 -0
- package/dist/api/media.mjs +242 -0
- package/dist/api/singles-detail.d.ts +87 -0
- package/dist/api/singles-detail.mjs +170 -0
- package/dist/api/singles-schema-detail.d.ts +54 -0
- package/dist/api/singles-schema-detail.mjs +182 -0
- package/dist/api/singles.d.ts +34 -0
- package/dist/api/singles.mjs +94 -0
- package/dist/api/storage-upload-url.d.ts +48 -0
- package/dist/api/storage-upload-url.mjs +202 -0
- package/dist/api/uploads.d.ts +109 -0
- package/dist/api/uploads.mjs +359 -0
- package/dist/auth/index.d.ts +425 -0
- package/dist/auth/index.mjs +199 -0
- package/dist/boot-apply-PQSYLDIN.mjs +7 -0
- package/dist/chunk-2OALJTK6.mjs +489 -0
- package/dist/chunk-2Q2SX2CS.mjs +365 -0
- package/dist/chunk-2TFX4ND3.mjs +13 -0
- package/dist/chunk-2TWPDSYD.mjs +87 -0
- package/dist/chunk-2W3DVD7S.mjs +647 -0
- package/dist/chunk-2ZFKXPQM.mjs +88 -0
- package/dist/chunk-3FA7FKAV.mjs +832 -0
- package/dist/chunk-3NZ2KMBL.mjs +58 -0
- package/dist/chunk-4MJLT6PZ.mjs +0 -0
- package/dist/chunk-56WO4WX7.mjs +0 -0
- package/dist/chunk-5APFUGAD.mjs +89 -0
- package/dist/chunk-5HMZ644B.mjs +108 -0
- package/dist/chunk-67GXH6PR.mjs +32 -0
- package/dist/chunk-6JNEPWRW.mjs +14368 -0
- package/dist/chunk-6NFHQIJD.mjs +45 -0
- package/dist/chunk-7P6ASYW6.mjs +9 -0
- package/dist/chunk-A3WPLSDT.mjs +1364 -0
- package/dist/chunk-AGJ6F2T3.mjs +144 -0
- package/dist/chunk-AK6Z23OX.mjs +1464 -0
- package/dist/chunk-APKKRD2G.mjs +102 -0
- package/dist/chunk-B2GV2BWH.mjs +73 -0
- package/dist/chunk-D5HQBNUB.mjs +74 -0
- package/dist/chunk-DNNG377Z.mjs +204 -0
- package/dist/chunk-DP3G27G5.mjs +135 -0
- package/dist/chunk-DV6WVX2Q.mjs +0 -0
- package/dist/chunk-DXGGXIUZ.mjs +57 -0
- package/dist/chunk-EGXBZCGC.mjs +943 -0
- package/dist/chunk-ERCNLX3V.mjs +176 -0
- package/dist/chunk-FQULBZ53.mjs +850 -0
- package/dist/chunk-G2AA4QLC.mjs +262 -0
- package/dist/chunk-GDBJ5JCU.mjs +488 -0
- package/dist/chunk-GJNSJU4S.mjs +19 -0
- package/dist/chunk-GZ6DCQKC.mjs +69 -0
- package/dist/chunk-H26B4FYG.mjs +167 -0
- package/dist/chunk-I4JMR3UR.mjs +21 -0
- package/dist/chunk-INV7QKLG.mjs +508 -0
- package/dist/chunk-IUDOC7N7.mjs +46 -0
- package/dist/chunk-IZWPRDC3.mjs +206 -0
- package/dist/chunk-KIMNCZGV.mjs +15 -0
- package/dist/chunk-L6HW2DA7.mjs +15 -0
- package/dist/chunk-LAZXX4HR.mjs +100 -0
- package/dist/chunk-LDKCUMHK.mjs +95 -0
- package/dist/chunk-LRXMECUA.mjs +0 -0
- package/dist/chunk-M52VMPGA.mjs +119 -0
- package/dist/chunk-MGUWEEI6.mjs +160 -0
- package/dist/chunk-NRUWQ5Z7.mjs +419 -0
- package/dist/chunk-NSEFNNU4.mjs +25360 -0
- package/dist/chunk-NTHVDFGO.mjs +138 -0
- package/dist/chunk-O3QHXMOX.mjs +3166 -0
- package/dist/chunk-P7NH2OSC.mjs +2605 -0
- package/dist/chunk-PKMABBB5.mjs +184 -0
- package/dist/chunk-PWS6XGJK.mjs +76 -0
- package/dist/chunk-R6JJQHFC.mjs +20 -0
- package/dist/chunk-RJLLGGPG.mjs +0 -0
- package/dist/chunk-SBACDPNX.mjs +689 -0
- package/dist/chunk-TO5AFLVQ.mjs +124 -0
- package/dist/chunk-TS7GHTG2.mjs +5436 -0
- package/dist/chunk-UJ2IMJ4W.mjs +133 -0
- package/dist/chunk-UOP63Q54.mjs +102 -0
- package/dist/chunk-UUOFWCM6.mjs +78 -0
- package/dist/chunk-V4EQTOA4.mjs +893 -0
- package/dist/chunk-VJ66NCL4.mjs +193 -0
- package/dist/chunk-VQJQHVEV.mjs +29 -0
- package/dist/chunk-VTJADRO3.mjs +141 -0
- package/dist/chunk-VWF3JO32.mjs +0 -0
- package/dist/chunk-W4MGXIRR.mjs +27 -0
- package/dist/chunk-W5KKPZT5.mjs +1204 -0
- package/dist/chunk-WD34YQ6T.mjs +381 -0
- package/dist/chunk-WZBYMYVW.mjs +14 -0
- package/dist/chunk-X23WKS3Z.mjs +50 -0
- package/dist/chunk-X7TXCYYN.mjs +6496 -0
- package/dist/chunk-XGI4EMS3.mjs +140 -0
- package/dist/chunk-XZKLBMN6.mjs +1153 -0
- package/dist/chunk-YB7INWPY.mjs +0 -0
- package/dist/chunk-YV4Y7SDL.mjs +83 -0
- package/dist/chunk-YZNBLFIW.mjs +1688 -0
- package/dist/chunk-YZZCTONM.mjs +263 -0
- package/dist/chunk-ZE6A3FYH.mjs +289 -0
- package/dist/cli/nextly.mjs +68 -0
- package/dist/cli/utils/index.d.ts +449 -0
- package/dist/cli/utils/index.mjs +49 -0
- package/dist/component-schema-service-5577KVW6.mjs +11 -0
- package/dist/config-loader-23YEMC3Z.mjs +23 -0
- package/dist/config.d.ts +44 -0
- package/dist/config.mjs +109 -0
- package/dist/container-ORGFGYSZ.mjs +9 -0
- package/dist/database/index.d.ts +12 -0
- package/dist/database/index.mjs +40 -0
- package/dist/database/seeders/index.d.ts +93 -0
- package/dist/database/seeders/index.mjs +47 -0
- package/dist/db-sync-demote-LJGKLB3S.mjs +117 -0
- package/dist/db-sync-promote-B26VSYQF.mjs +113 -0
- package/dist/dev-reload-broadcaster-B73IQ53V.mjs +25 -0
- package/dist/dist-M2NOU37V.mjs +19 -0
- package/dist/drizzle-kit-lazy-D2M2PXR2.mjs +13 -0
- package/dist/dynamic-collection-schema-service-IEXTPIZ7.mjs +8 -0
- package/dist/errors/index.d.ts +159 -0
- package/dist/errors/index.mjs +10 -0
- package/dist/factory-IWMBKUJM.mjs +15 -0
- package/dist/first-run-QIVKWJIF.mjs +63 -0
- package/dist/fresh-push-NR67DC3R.mjs +8 -0
- package/dist/index.d.ts +4175 -0
- package/dist/index.mjs +1336 -0
- package/dist/local-plugin-PTET4NAT.mjs +7 -0
- package/dist/logger-NU46DXNY.mjs +15 -0
- package/dist/logger-YE4TC7ZN.mjs +9 -0
- package/dist/migration-journal-EP532Y4L.mjs +139 -0
- package/dist/migrations/mysql/0000_eager_sentry.sql +174 -0
- package/dist/migrations/mysql/0001_soft_giant_girl.sql +27 -0
- package/dist/migrations/mysql/0002_media_table.sql +24 -0
- package/dist/migrations/mysql/0003_dynamic_singles.sql +37 -0
- package/dist/migrations/mysql/0004_dynamic_components.sql +35 -0
- package/dist/migrations/mysql/0005_user_management_tables.sql +92 -0
- package/dist/migrations/mysql/0006_api_keys.sql +36 -0
- package/dist/migrations/mysql/0007_general_settings.sql +20 -0
- package/dist/migrations/mysql/0008_site_settings_logo_url.sql +9 -0
- package/dist/migrations/mysql/0009_activity_log.sql +30 -0
- package/dist/migrations/mysql/0010_site_settings_sidebar.sql +13 -0
- package/dist/migrations/mysql/0011_missing_tables_and_columns.sql +54 -0
- package/dist/migrations/mysql/0012_image_sizes_and_focal_point.sql +30 -0
- package/dist/migrations/mysql/0012_media_folders.sql +43 -0
- package/dist/migrations/mysql/0013_user_brute_force_protection.sql +31 -0
- package/dist/migrations/mysql/0014_email_template_attachments.sql +12 -0
- package/dist/migrations/mysql/0015_media_uploaded_by_nullable.sql +15 -0
- package/dist/migrations/mysql/20260429_000000_000_initial_journal.sql +22 -0
- package/dist/migrations/mysql/20260501_000000_journal_batch.sql +17 -0
- package/dist/migrations/mysql/20260501_000001_audit_log.sql +24 -0
- package/dist/migrations/mysql/20260504_000000_nextly_meta.sql +21 -0
- package/dist/migrations/mysql/meta/0000_snapshot.json +1005 -0
- package/dist/migrations/mysql/meta/0001_snapshot.json +1099 -0
- package/dist/migrations/mysql/meta/_journal.json +41 -0
- package/dist/migrations/postgresql/0000_misty_king_bedlam.sql +169 -0
- package/dist/migrations/postgresql/0001_perpetual_captain_marvel.sql +8 -0
- package/dist/migrations/postgresql/0002_sad_spectrum.sql +16 -0
- package/dist/migrations/postgresql/0003_hesitant_ultron.sql +17 -0
- package/dist/migrations/postgresql/0004_media_table.sql +24 -0
- package/dist/migrations/postgresql/0005_media_folders.sql +36 -0
- package/dist/migrations/postgresql/0006_dynamic_collections_update.sql +50 -0
- package/dist/migrations/postgresql/0007_dynamic_singles.sql +38 -0
- package/dist/migrations/postgresql/0008_dynamic_components.sql +37 -0
- package/dist/migrations/postgresql/0009_user_management_tables.sql +95 -0
- package/dist/migrations/postgresql/0010_api_keys.sql +34 -0
- package/dist/migrations/postgresql/0011_general_settings.sql +20 -0
- package/dist/migrations/postgresql/0012_site_settings_logo_url.sql +9 -0
- package/dist/migrations/postgresql/0013_activity_log.sql +29 -0
- package/dist/migrations/postgresql/0014_image_sizes_and_focal_point.sql +33 -0
- package/dist/migrations/postgresql/0014_site_settings_sidebar.sql +13 -0
- package/dist/migrations/postgresql/0015_user_brute_force_protection.sql +29 -0
- package/dist/migrations/postgresql/0016_email_template_attachments.sql +12 -0
- package/dist/migrations/postgresql/0017_media_uploaded_by_nullable.sql +15 -0
- package/dist/migrations/postgresql/20260429_000000_000_initial_journal.sql +24 -0
- package/dist/migrations/postgresql/20260501_000000_journal_batch.sql +17 -0
- package/dist/migrations/postgresql/20260501_000001_audit_log.sql +24 -0
- package/dist/migrations/postgresql/20260504_000000_nextly_meta.sql +22 -0
- package/dist/migrations/postgresql/meta/0000_snapshot.json +1286 -0
- package/dist/migrations/postgresql/meta/0001_snapshot.json +1407 -0
- package/dist/migrations/postgresql/meta/0002_snapshot.json +1552 -0
- package/dist/migrations/postgresql/meta/0003_snapshot.json +1695 -0
- package/dist/migrations/postgresql/meta/0010_snapshot.json +2345 -0
- package/dist/migrations/postgresql/meta/_journal.json +90 -0
- package/dist/migrations/sqlite/0000_api_keys.sql +34 -0
- package/dist/migrations/sqlite/0001_general_settings.sql +20 -0
- package/dist/migrations/sqlite/0002_site_settings_logo_url.sql +9 -0
- package/dist/migrations/sqlite/0003_activity_log.sql +29 -0
- package/dist/migrations/sqlite/0004_image_sizes_and_focal_point.sql +29 -0
- package/dist/migrations/sqlite/0004_site_settings_sidebar.sql +11 -0
- package/dist/migrations/sqlite/0005_user_brute_force_protection.sql +29 -0
- package/dist/migrations/sqlite/0006_email_template_attachments.sql +12 -0
- package/dist/migrations/sqlite/0007_media_uploaded_by_nullable.sql +111 -0
- package/dist/migrations/sqlite/20260429_000000_000_initial_journal.sql +24 -0
- package/dist/migrations/sqlite/20260501_000000_journal_batch.sql +19 -0
- package/dist/migrations/sqlite/20260501_000001_audit_log.sql +24 -0
- package/dist/migrations/sqlite/20260504_000000_nextly_meta.sql +21 -0
- package/dist/migrations/sqlite/20260505_000000_user_management_tables.sql +77 -0
- package/dist/next.d.ts +57 -0
- package/dist/next.mjs +55 -0
- package/dist/observability/index.d.ts +87 -0
- package/dist/observability/index.mjs +57 -0
- package/dist/permissions-3DZZQZMI.mjs +39 -0
- package/dist/pipeline-YOML7SWF.mjs +29 -0
- package/dist/preview-ZZTR3QGS.mjs +9 -0
- package/dist/program-PW6UB2ZC.mjs +5934 -0
- package/dist/reconcile-single-tables-7ENVXJGB.mjs +7 -0
- package/dist/register-SF6E6FVU.mjs +49 -0
- package/dist/reload-config-HWQ4G5MM.mjs +23 -0
- package/dist/resolve-single-table-name-JSOMUB3R.mjs +7 -0
- package/dist/routeHandler-UNMMJIBM.mjs +77 -0
- package/dist/runtime-schema-generator-NRA6A6Z6.mjs +8 -0
- package/dist/runtime.d.ts +120 -0
- package/dist/runtime.mjs +73 -0
- package/dist/schema-hash-FMMG6VPJ.mjs +13 -0
- package/dist/schema-registry-EQ36FZDP.mjs +7 -0
- package/dist/scripts/load-env.mjs +42 -0
- package/dist/storage/index.d.ts +566 -0
- package/dist/storage/index.mjs +45 -0
- package/dist/super-admin-G5ZK5F4T.mjs +39 -0
- package/dist/system-table-service-WGSRVEGT.mjs +17 -0
- package/dist/users-7KELGRYJ.mjs +38 -0
- package/package.json +308 -9
|
@@ -0,0 +1,3166 @@
|
|
|
1
|
+
import {
|
|
2
|
+
routeAuthRequest
|
|
3
|
+
} from "./chunk-A3WPLSDT.mjs";
|
|
4
|
+
import {
|
|
5
|
+
POST
|
|
6
|
+
} from "./chunk-YV4Y7SDL.mjs";
|
|
7
|
+
import {
|
|
8
|
+
readJsonBody
|
|
9
|
+
} from "./chunk-VQJQHVEV.mjs";
|
|
10
|
+
import {
|
|
11
|
+
POST as POST2
|
|
12
|
+
} from "./chunk-LDKCUMHK.mjs";
|
|
13
|
+
import {
|
|
14
|
+
createJsonErrorResponse,
|
|
15
|
+
isErrorResponse,
|
|
16
|
+
requireAnyPermission,
|
|
17
|
+
requireAuthentication,
|
|
18
|
+
requireCollectionAccess,
|
|
19
|
+
requirePermission,
|
|
20
|
+
toNextlyAuthError
|
|
21
|
+
} from "./chunk-2Q2SX2CS.mjs";
|
|
22
|
+
import {
|
|
23
|
+
nextlyValidationFromZod
|
|
24
|
+
} from "./chunk-GJNSJU4S.mjs";
|
|
25
|
+
import {
|
|
26
|
+
getForegroundForBackground,
|
|
27
|
+
hexToHslTriplet,
|
|
28
|
+
isValidHex
|
|
29
|
+
} from "./chunk-PWS6XGJK.mjs";
|
|
30
|
+
import {
|
|
31
|
+
CreateApiKeySchema,
|
|
32
|
+
UpdateApiKeySchema,
|
|
33
|
+
createCorsMiddleware,
|
|
34
|
+
createSecurityHeadersMiddleware
|
|
35
|
+
} from "./chunk-YZZCTONM.mjs";
|
|
36
|
+
import {
|
|
37
|
+
createRateLimiter
|
|
38
|
+
} from "./chunk-ERCNLX3V.mjs";
|
|
39
|
+
import {
|
|
40
|
+
parseTrustedProxyIpsEnv
|
|
41
|
+
} from "./chunk-APKKRD2G.mjs";
|
|
42
|
+
import {
|
|
43
|
+
withErrorHandler
|
|
44
|
+
} from "./chunk-TO5AFLVQ.mjs";
|
|
45
|
+
import {
|
|
46
|
+
ensureHmrListener,
|
|
47
|
+
getCachedNextly
|
|
48
|
+
} from "./chunk-P7NH2OSC.mjs";
|
|
49
|
+
import {
|
|
50
|
+
getService,
|
|
51
|
+
isServicesRegistered,
|
|
52
|
+
registerServices
|
|
53
|
+
} from "./chunk-X7TXCYYN.mjs";
|
|
54
|
+
import {
|
|
55
|
+
readOrGenerateRequestId
|
|
56
|
+
} from "./chunk-67GXH6PR.mjs";
|
|
57
|
+
import {
|
|
58
|
+
getNextlyLogger
|
|
59
|
+
} from "./chunk-W4MGXIRR.mjs";
|
|
60
|
+
import {
|
|
61
|
+
ImageSizeService,
|
|
62
|
+
ServiceDispatcher,
|
|
63
|
+
parseWhereQuery
|
|
64
|
+
} from "./chunk-NSEFNNU4.mjs";
|
|
65
|
+
import {
|
|
66
|
+
containsSuperAdminRole,
|
|
67
|
+
hasSuperAdminExcluding,
|
|
68
|
+
isSuperAdmin,
|
|
69
|
+
listEffectivePermissions
|
|
70
|
+
} from "./chunk-W5KKPZT5.mjs";
|
|
71
|
+
import {
|
|
72
|
+
BaseService,
|
|
73
|
+
withTimezoneFormatting
|
|
74
|
+
} from "./chunk-2W3DVD7S.mjs";
|
|
75
|
+
import {
|
|
76
|
+
getDialectTables
|
|
77
|
+
} from "./chunk-TS7GHTG2.mjs";
|
|
78
|
+
import {
|
|
79
|
+
nextlyMigrationJournalMysql,
|
|
80
|
+
nextlyMigrationJournalPg,
|
|
81
|
+
nextlyMigrationJournalSqlite
|
|
82
|
+
} from "./chunk-H26B4FYG.mjs";
|
|
83
|
+
import {
|
|
84
|
+
env
|
|
85
|
+
} from "./chunk-UJ2IMJ4W.mjs";
|
|
86
|
+
import {
|
|
87
|
+
getImageProcessor,
|
|
88
|
+
getMediaStorage
|
|
89
|
+
} from "./chunk-EGXBZCGC.mjs";
|
|
90
|
+
import {
|
|
91
|
+
respondAction,
|
|
92
|
+
respondData,
|
|
93
|
+
respondDoc,
|
|
94
|
+
respondList,
|
|
95
|
+
respondMutation
|
|
96
|
+
} from "./chunk-IUDOC7N7.mjs";
|
|
97
|
+
import {
|
|
98
|
+
container
|
|
99
|
+
} from "./chunk-D5HQBNUB.mjs";
|
|
100
|
+
import {
|
|
101
|
+
NextlyError
|
|
102
|
+
} from "./chunk-NRUWQ5Z7.mjs";
|
|
103
|
+
|
|
104
|
+
// src/api/api-keys.ts
|
|
105
|
+
import { z } from "zod";
|
|
106
|
+
async function getApiKeyService() {
|
|
107
|
+
await getCachedNextly();
|
|
108
|
+
return container.get("apiKeyService");
|
|
109
|
+
}
|
|
110
|
+
async function requireApiKeyPermission(req, action) {
|
|
111
|
+
return requireAnyPermission(req, [
|
|
112
|
+
{ action, resource: "api-keys" },
|
|
113
|
+
{ action: "update", resource: "api-keys" }
|
|
114
|
+
]);
|
|
115
|
+
}
|
|
116
|
+
function denySessionOnly(action) {
|
|
117
|
+
throw NextlyError.forbidden({
|
|
118
|
+
logContext: { reason: "session-only", action }
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
var listApiKeys = withErrorHandler(
|
|
122
|
+
async (req) => {
|
|
123
|
+
const authResult = await requireApiKeyPermission(req, "read");
|
|
124
|
+
if (isErrorResponse(authResult)) throw toNextlyAuthError(authResult);
|
|
125
|
+
const service = await getApiKeyService();
|
|
126
|
+
const allUsers = await isSuperAdmin(authResult.userId);
|
|
127
|
+
const keys = await service.listApiKeys(authResult.userId, { allUsers });
|
|
128
|
+
return respondList(keys, {
|
|
129
|
+
total: keys.length,
|
|
130
|
+
page: 1,
|
|
131
|
+
limit: keys.length,
|
|
132
|
+
totalPages: 1,
|
|
133
|
+
hasNext: false,
|
|
134
|
+
hasPrev: false
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
function getApiKeyById(req, id) {
|
|
139
|
+
return withErrorHandler(async (request) => {
|
|
140
|
+
const authResult = await requireApiKeyPermission(request, "read");
|
|
141
|
+
if (isErrorResponse(authResult)) throw toNextlyAuthError(authResult);
|
|
142
|
+
const service = await getApiKeyService();
|
|
143
|
+
const allUsers = await isSuperAdmin(authResult.userId);
|
|
144
|
+
const key = await service.getApiKeyById(id, authResult.userId, {
|
|
145
|
+
allUsers
|
|
146
|
+
});
|
|
147
|
+
if (!key) {
|
|
148
|
+
throw NextlyError.notFound({
|
|
149
|
+
logContext: { entity: "api-key", id, callerId: authResult.userId }
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return respondDoc(key);
|
|
153
|
+
})(req);
|
|
154
|
+
}
|
|
155
|
+
var createApiKey = withErrorHandler(
|
|
156
|
+
async (req) => {
|
|
157
|
+
const authResult = await requireApiKeyPermission(req, "create");
|
|
158
|
+
if (isErrorResponse(authResult)) throw toNextlyAuthError(authResult);
|
|
159
|
+
if (authResult.authMethod !== "session") denySessionOnly("create");
|
|
160
|
+
const body = await readJsonBody(req);
|
|
161
|
+
let validated;
|
|
162
|
+
try {
|
|
163
|
+
validated = CreateApiKeySchema.parse(body);
|
|
164
|
+
} catch (err) {
|
|
165
|
+
if (err instanceof z.ZodError) throw nextlyValidationFromZod(err);
|
|
166
|
+
throw err;
|
|
167
|
+
}
|
|
168
|
+
const service = await getApiKeyService();
|
|
169
|
+
const { meta, key } = await service.createApiKey(
|
|
170
|
+
authResult.userId,
|
|
171
|
+
validated
|
|
172
|
+
);
|
|
173
|
+
return respondMutation(
|
|
174
|
+
"API key created.",
|
|
175
|
+
{ doc: meta, key },
|
|
176
|
+
{ status: 201 }
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
);
|
|
180
|
+
function updateApiKey(req, id) {
|
|
181
|
+
return withErrorHandler(async (request) => {
|
|
182
|
+
const authResult = await requireApiKeyPermission(request, "update");
|
|
183
|
+
if (isErrorResponse(authResult)) throw toNextlyAuthError(authResult);
|
|
184
|
+
if (authResult.authMethod !== "session") denySessionOnly("update");
|
|
185
|
+
const body = await readJsonBody(request);
|
|
186
|
+
let validated;
|
|
187
|
+
try {
|
|
188
|
+
validated = UpdateApiKeySchema.parse(body);
|
|
189
|
+
} catch (err) {
|
|
190
|
+
if (err instanceof z.ZodError) throw nextlyValidationFromZod(err);
|
|
191
|
+
throw err;
|
|
192
|
+
}
|
|
193
|
+
const service = await getApiKeyService();
|
|
194
|
+
const updated = await service.updateApiKey(
|
|
195
|
+
id,
|
|
196
|
+
authResult.userId,
|
|
197
|
+
validated
|
|
198
|
+
);
|
|
199
|
+
return respondMutation("API key updated.", updated);
|
|
200
|
+
})(req);
|
|
201
|
+
}
|
|
202
|
+
function revokeApiKey(req, id) {
|
|
203
|
+
return withErrorHandler(async (request) => {
|
|
204
|
+
const authResult = await requireApiKeyPermission(request, "delete");
|
|
205
|
+
if (isErrorResponse(authResult)) throw toNextlyAuthError(authResult);
|
|
206
|
+
if (authResult.authMethod !== "session") denySessionOnly("delete");
|
|
207
|
+
const service = await getApiKeyService();
|
|
208
|
+
await service.revokeApiKey(id, authResult.userId);
|
|
209
|
+
return respondAction("API key revoked.", { id });
|
|
210
|
+
})(req);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// src/api/dashboard.ts
|
|
214
|
+
var PRIVATE_NO_STORE_HEADERS = {
|
|
215
|
+
"Cache-Control": "private, no-store",
|
|
216
|
+
Vary: "Cookie"
|
|
217
|
+
};
|
|
218
|
+
async function getDashboardService() {
|
|
219
|
+
await getCachedNextly();
|
|
220
|
+
return container.get("dashboardService");
|
|
221
|
+
}
|
|
222
|
+
async function getActivityLogService() {
|
|
223
|
+
await getCachedNextly();
|
|
224
|
+
return container.get("activityLogService");
|
|
225
|
+
}
|
|
226
|
+
async function resolveReadableResources(userId) {
|
|
227
|
+
if (await isSuperAdmin(userId)) return void 0;
|
|
228
|
+
const permissionPairs = await listEffectivePermissions(userId);
|
|
229
|
+
return new Set(
|
|
230
|
+
permissionPairs.filter((pair) => pair.endsWith(":read")).map((pair) => pair.split(":")[0])
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
var getDashboardStats = withErrorHandler(async (req) => {
|
|
234
|
+
const auth = await requireAuthentication(req);
|
|
235
|
+
if (isErrorResponse(auth)) throw toNextlyAuthError(auth);
|
|
236
|
+
const service = await getDashboardService();
|
|
237
|
+
const readableResources = await resolveReadableResources(auth.userId);
|
|
238
|
+
const stats = await service.getStats({ readableResources });
|
|
239
|
+
return respondData({ ...stats }, { headers: PRIVATE_NO_STORE_HEADERS });
|
|
240
|
+
});
|
|
241
|
+
var getDashboardRecentEntries = withErrorHandler(
|
|
242
|
+
async (req) => {
|
|
243
|
+
const auth = await requireAuthentication(req);
|
|
244
|
+
if (isErrorResponse(auth)) throw toNextlyAuthError(auth);
|
|
245
|
+
const { searchParams } = new URL(req.url);
|
|
246
|
+
const limitParam = searchParams.get("limit");
|
|
247
|
+
const limit = limitParam ? Math.min(Math.max(Number(limitParam) || 5, 1), 20) : 5;
|
|
248
|
+
const service = await getDashboardService();
|
|
249
|
+
const readableResources = await resolveReadableResources(auth.userId);
|
|
250
|
+
const entries = await service.getRecentEntries(limit, readableResources);
|
|
251
|
+
return respondData(
|
|
252
|
+
{ ...entries },
|
|
253
|
+
{
|
|
254
|
+
headers: PRIVATE_NO_STORE_HEADERS
|
|
255
|
+
}
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
);
|
|
259
|
+
var getDashboardActivity = withErrorHandler(async (req) => {
|
|
260
|
+
const auth = await requireAuthentication(req);
|
|
261
|
+
if (isErrorResponse(auth)) throw toNextlyAuthError(auth);
|
|
262
|
+
const { searchParams } = new URL(req.url);
|
|
263
|
+
const limitParam = searchParams.get("limit");
|
|
264
|
+
const limit = limitParam ? Math.min(Math.max(Number(limitParam) || 5, 1), 50) : 5;
|
|
265
|
+
const service = await getActivityLogService();
|
|
266
|
+
const result = await service.getRecentActivity({ limit });
|
|
267
|
+
return respondData({ ...result }, { headers: PRIVATE_NO_STORE_HEADERS });
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// src/api/general-settings.ts
|
|
271
|
+
import { z as z2 } from "zod";
|
|
272
|
+
async function getGeneralSettingsService() {
|
|
273
|
+
await getCachedNextly();
|
|
274
|
+
return container.get("generalSettingsService");
|
|
275
|
+
}
|
|
276
|
+
var updateSettingsSchema = z2.object({
|
|
277
|
+
applicationName: z2.string().max(255).nullable().optional(),
|
|
278
|
+
siteUrl: z2.string().url("Site URL must be a valid URL").max(2048).nullable().optional(),
|
|
279
|
+
adminEmail: z2.string().email("Admin email must be a valid email address").max(255).nullable().optional(),
|
|
280
|
+
timezone: z2.string().max(100).nullable().optional(),
|
|
281
|
+
dateFormat: z2.string().max(50).nullable().optional(),
|
|
282
|
+
timeFormat: z2.string().max(50).nullable().optional(),
|
|
283
|
+
logoUrl: z2.string().url("Logo URL must be a valid URL").max(2048).nullable().optional()
|
|
284
|
+
});
|
|
285
|
+
var getGeneralSettings = withErrorHandler(async (req) => {
|
|
286
|
+
const authResult = await requireAnyPermission(req, [
|
|
287
|
+
{ action: "read", resource: "settings" },
|
|
288
|
+
{ action: "manage", resource: "settings" }
|
|
289
|
+
]);
|
|
290
|
+
if (isErrorResponse(authResult)) throw toNextlyAuthError(authResult);
|
|
291
|
+
const service = await getGeneralSettingsService();
|
|
292
|
+
const settings = await service.getSettings();
|
|
293
|
+
return withTimezoneFormatting(respondData({ ...settings }));
|
|
294
|
+
});
|
|
295
|
+
var updateGeneralSettings = withErrorHandler(async (req) => {
|
|
296
|
+
const authResult = await requireAnyPermission(req, [
|
|
297
|
+
{ action: "update", resource: "settings" },
|
|
298
|
+
{ action: "manage", resource: "settings" }
|
|
299
|
+
]);
|
|
300
|
+
if (isErrorResponse(authResult)) throw toNextlyAuthError(authResult);
|
|
301
|
+
const text = await req.text();
|
|
302
|
+
let body;
|
|
303
|
+
try {
|
|
304
|
+
body = text ? JSON.parse(text) : {};
|
|
305
|
+
} catch {
|
|
306
|
+
throw NextlyError.validation({
|
|
307
|
+
errors: [
|
|
308
|
+
{
|
|
309
|
+
path: "",
|
|
310
|
+
code: "invalid_json",
|
|
311
|
+
message: "Request body is not valid JSON."
|
|
312
|
+
}
|
|
313
|
+
]
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
const parsed = updateSettingsSchema.safeParse(body);
|
|
317
|
+
if (!parsed.success) {
|
|
318
|
+
throw nextlyValidationFromZod(parsed.error);
|
|
319
|
+
}
|
|
320
|
+
const service = await getGeneralSettingsService();
|
|
321
|
+
const updated = await service.updateSettings(parsed.data);
|
|
322
|
+
return withTimezoneFormatting(
|
|
323
|
+
respondMutation("General settings updated.", updated)
|
|
324
|
+
);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// src/api/image-sizes.ts
|
|
328
|
+
import { z as z3 } from "zod";
|
|
329
|
+
|
|
330
|
+
// src/services/media-regeneration.ts
|
|
331
|
+
import { and, sql, gt } from "drizzle-orm";
|
|
332
|
+
var MediaRegenerationService = class extends BaseService {
|
|
333
|
+
imageSizeService;
|
|
334
|
+
constructor(adapter, logger) {
|
|
335
|
+
super(adapter, logger);
|
|
336
|
+
this.imageSizeService = new ImageSizeService(adapter, logger);
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Check how many images need regeneration.
|
|
340
|
+
*
|
|
341
|
+
* An image needs regeneration if:
|
|
342
|
+
* 1. A configured size is missing from its `sizes` JSONB
|
|
343
|
+
* 2. A configured size has different dimensions than what's stored
|
|
344
|
+
*/
|
|
345
|
+
async getRegenerationStatus() {
|
|
346
|
+
const { media } = this.tables;
|
|
347
|
+
const allImages = await this.db.select({ count: sql`count(*)` }).from(media).where(sql`${media.mimeType} LIKE 'image/%'`);
|
|
348
|
+
const total = Number(allImages[0]?.count ?? 0);
|
|
349
|
+
const sizeConfigs = await this.imageSizeService.getActiveSizeConfigs();
|
|
350
|
+
const configNames = sizeConfigs.map((s) => s.name).sort();
|
|
351
|
+
if (configNames.length === 0) {
|
|
352
|
+
return { pending: 0, total, inProgress: false };
|
|
353
|
+
}
|
|
354
|
+
const images = await this.db.select({
|
|
355
|
+
id: media.id,
|
|
356
|
+
sizes: media.sizes
|
|
357
|
+
}).from(media).where(sql`${media.mimeType} LIKE 'image/%'`);
|
|
358
|
+
let pending = 0;
|
|
359
|
+
for (const img of images) {
|
|
360
|
+
if (this.needsRegeneration(img.sizes, configNames)) {
|
|
361
|
+
pending++;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return { pending, total, inProgress: false };
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Process a batch of images that need regeneration.
|
|
368
|
+
*
|
|
369
|
+
* @param options.batchSize - Number of images to process (default: 10)
|
|
370
|
+
* @param options.cursor - ID of last processed image (for pagination)
|
|
371
|
+
*/
|
|
372
|
+
async regenerateBatch(options = {}) {
|
|
373
|
+
const batchSize = options.batchSize ?? 10;
|
|
374
|
+
const { media } = this.tables;
|
|
375
|
+
const sizeConfigs = await this.imageSizeService.getActiveSizeConfigs();
|
|
376
|
+
const configNames = sizeConfigs.map((s) => s.name).sort();
|
|
377
|
+
if (configNames.length === 0) {
|
|
378
|
+
return { processed: 0, remaining: 0, nextCursor: null, failures: [] };
|
|
379
|
+
}
|
|
380
|
+
const conditions = [sql`${media.mimeType} LIKE 'image/%'`];
|
|
381
|
+
if (options.cursor) {
|
|
382
|
+
conditions.push(gt(media.id, options.cursor));
|
|
383
|
+
}
|
|
384
|
+
const images = await this.db.select().from(media).where(and(...conditions)).orderBy(media.id).limit(batchSize + 1);
|
|
385
|
+
const hasMore = images.length > batchSize;
|
|
386
|
+
const batch = hasMore ? images.slice(0, batchSize) : images;
|
|
387
|
+
const _storage = getMediaStorage();
|
|
388
|
+
const failures = [];
|
|
389
|
+
let processed = 0;
|
|
390
|
+
for (const img of batch) {
|
|
391
|
+
if (!this.needsRegeneration(img.sizes, configNames)) {
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
try {
|
|
395
|
+
console.log(
|
|
396
|
+
`[Regeneration] Image ${img.id} needs regeneration (${img.filename})`
|
|
397
|
+
);
|
|
398
|
+
processed++;
|
|
399
|
+
} catch (error) {
|
|
400
|
+
failures.push({
|
|
401
|
+
mediaId: img.id,
|
|
402
|
+
error: error?.message ?? "Unknown error"
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
const status = await this.getRegenerationStatus();
|
|
407
|
+
return {
|
|
408
|
+
processed,
|
|
409
|
+
remaining: status.pending - processed,
|
|
410
|
+
nextCursor: hasMore ? batch[batch.length - 1]?.id ?? null : null,
|
|
411
|
+
failures
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
needsRegeneration(sizes, configNames) {
|
|
415
|
+
if (!sizes || configNames.length === 0) return configNames.length > 0;
|
|
416
|
+
const parsed = typeof sizes === "string" ? JSON.parse(sizes) : sizes;
|
|
417
|
+
if (!parsed || typeof parsed !== "object") return true;
|
|
418
|
+
const existingNames = Object.keys(parsed).sort();
|
|
419
|
+
for (const name of configNames) {
|
|
420
|
+
if (!existingNames.includes(name)) return true;
|
|
421
|
+
}
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
// src/api/image-sizes.ts
|
|
427
|
+
var imageSizeServiceInstance = null;
|
|
428
|
+
async function getImageSizeService() {
|
|
429
|
+
if (!imageSizeServiceInstance) {
|
|
430
|
+
await getCachedNextly();
|
|
431
|
+
const adapter = container.get("adapter");
|
|
432
|
+
imageSizeServiceInstance = new ImageSizeService(adapter, console);
|
|
433
|
+
}
|
|
434
|
+
return imageSizeServiceInstance;
|
|
435
|
+
}
|
|
436
|
+
var createImageSizeSchema = z3.object({
|
|
437
|
+
name: z3.string().min(1).max(50),
|
|
438
|
+
width: z3.number().int().positive().max(1e4).nullable().optional(),
|
|
439
|
+
height: z3.number().int().positive().max(1e4).nullable().optional(),
|
|
440
|
+
fit: z3.enum(["cover", "inside", "contain", "fill"]).optional().default("inside"),
|
|
441
|
+
quality: z3.number().int().min(1).max(100).optional().default(80),
|
|
442
|
+
format: z3.enum(["auto", "webp", "jpeg", "png", "avif"]).optional().default("auto")
|
|
443
|
+
});
|
|
444
|
+
var updateImageSizeSchema = z3.object({
|
|
445
|
+
name: z3.string().min(1).max(50).optional(),
|
|
446
|
+
width: z3.number().int().positive().max(1e4).nullable().optional(),
|
|
447
|
+
height: z3.number().int().positive().max(1e4).nullable().optional(),
|
|
448
|
+
fit: z3.enum(["cover", "inside", "contain", "fill"]).optional(),
|
|
449
|
+
quality: z3.number().int().min(1).max(100).optional(),
|
|
450
|
+
format: z3.enum(["auto", "webp", "jpeg", "png", "avif"]).optional()
|
|
451
|
+
});
|
|
452
|
+
var listImageSizes = withErrorHandler(async (req) => {
|
|
453
|
+
const authResult = await requireAnyPermission(req, [
|
|
454
|
+
{ action: "read", resource: "settings" },
|
|
455
|
+
{ action: "manage", resource: "settings" }
|
|
456
|
+
]);
|
|
457
|
+
if (isErrorResponse(authResult)) throw toNextlyAuthError(authResult);
|
|
458
|
+
const service = await getImageSizeService();
|
|
459
|
+
const sizes = await service.list();
|
|
460
|
+
return respondList(sizes, {
|
|
461
|
+
total: sizes.length,
|
|
462
|
+
page: 1,
|
|
463
|
+
limit: sizes.length,
|
|
464
|
+
totalPages: 1,
|
|
465
|
+
hasNext: false,
|
|
466
|
+
hasPrev: false
|
|
467
|
+
});
|
|
468
|
+
});
|
|
469
|
+
var getImageSizeById = withErrorHandler(
|
|
470
|
+
async (req, id) => {
|
|
471
|
+
const authResult = await requireAnyPermission(req, [
|
|
472
|
+
{ action: "read", resource: "settings" },
|
|
473
|
+
{ action: "manage", resource: "settings" }
|
|
474
|
+
]);
|
|
475
|
+
if (isErrorResponse(authResult)) throw toNextlyAuthError(authResult);
|
|
476
|
+
const service = await getImageSizeService();
|
|
477
|
+
const size = await service.getById(id);
|
|
478
|
+
if (!size) {
|
|
479
|
+
throw NextlyError.notFound({ logContext: { resource: "imageSize", id } });
|
|
480
|
+
}
|
|
481
|
+
return respondDoc(size);
|
|
482
|
+
}
|
|
483
|
+
);
|
|
484
|
+
var createImageSize = withErrorHandler(async (req) => {
|
|
485
|
+
const authResult = await requireAnyPermission(req, [
|
|
486
|
+
{ action: "manage", resource: "settings" }
|
|
487
|
+
]);
|
|
488
|
+
if (isErrorResponse(authResult)) throw toNextlyAuthError(authResult);
|
|
489
|
+
const text = await req.text();
|
|
490
|
+
let body;
|
|
491
|
+
try {
|
|
492
|
+
body = text ? JSON.parse(text) : {};
|
|
493
|
+
} catch {
|
|
494
|
+
throw NextlyError.validation({
|
|
495
|
+
errors: [
|
|
496
|
+
{
|
|
497
|
+
path: "",
|
|
498
|
+
code: "invalid_json",
|
|
499
|
+
message: "Request body is not valid JSON."
|
|
500
|
+
}
|
|
501
|
+
]
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
const parsed = createImageSizeSchema.safeParse(body);
|
|
505
|
+
if (!parsed.success) {
|
|
506
|
+
throw nextlyValidationFromZod(parsed.error);
|
|
507
|
+
}
|
|
508
|
+
const data = parsed.data;
|
|
509
|
+
if (!data.width && !data.height) {
|
|
510
|
+
throw NextlyError.validation({
|
|
511
|
+
errors: [
|
|
512
|
+
{
|
|
513
|
+
path: "",
|
|
514
|
+
code: "missing_dimension",
|
|
515
|
+
message: "At least one dimension (width or height) is required."
|
|
516
|
+
}
|
|
517
|
+
]
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
const service = await getImageSizeService();
|
|
521
|
+
const created = await service.create({
|
|
522
|
+
name: data.name,
|
|
523
|
+
width: data.width ?? null,
|
|
524
|
+
height: data.height ?? null,
|
|
525
|
+
fit: data.fit,
|
|
526
|
+
quality: data.quality,
|
|
527
|
+
format: data.format,
|
|
528
|
+
isDefault: false
|
|
529
|
+
// UI-created sizes are not "default" (code-defined)
|
|
530
|
+
});
|
|
531
|
+
return respondMutation("Image size created.", created, { status: 201 });
|
|
532
|
+
});
|
|
533
|
+
var updateImageSize = withErrorHandler(
|
|
534
|
+
async (req, id) => {
|
|
535
|
+
const authResult = await requireAnyPermission(req, [
|
|
536
|
+
{ action: "manage", resource: "settings" }
|
|
537
|
+
]);
|
|
538
|
+
if (isErrorResponse(authResult)) throw toNextlyAuthError(authResult);
|
|
539
|
+
const text = await req.text();
|
|
540
|
+
let body;
|
|
541
|
+
try {
|
|
542
|
+
body = text ? JSON.parse(text) : {};
|
|
543
|
+
} catch {
|
|
544
|
+
throw NextlyError.validation({
|
|
545
|
+
errors: [
|
|
546
|
+
{
|
|
547
|
+
path: "",
|
|
548
|
+
code: "invalid_json",
|
|
549
|
+
message: "Request body is not valid JSON."
|
|
550
|
+
}
|
|
551
|
+
]
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
const parsed = updateImageSizeSchema.safeParse(body);
|
|
555
|
+
if (!parsed.success) {
|
|
556
|
+
throw nextlyValidationFromZod(parsed.error);
|
|
557
|
+
}
|
|
558
|
+
const service = await getImageSizeService();
|
|
559
|
+
const updated = await service.update(id, parsed.data);
|
|
560
|
+
return respondMutation("Image size updated.", updated);
|
|
561
|
+
}
|
|
562
|
+
);
|
|
563
|
+
var deleteImageSize = withErrorHandler(
|
|
564
|
+
async (req, id) => {
|
|
565
|
+
const authResult = await requireAnyPermission(req, [
|
|
566
|
+
{ action: "manage", resource: "settings" }
|
|
567
|
+
]);
|
|
568
|
+
if (isErrorResponse(authResult)) throw toNextlyAuthError(authResult);
|
|
569
|
+
const service = await getImageSizeService();
|
|
570
|
+
await service.delete(id);
|
|
571
|
+
return respondAction("Image size deleted.", { id });
|
|
572
|
+
}
|
|
573
|
+
);
|
|
574
|
+
var getRegenerationStatus = withErrorHandler(async (req) => {
|
|
575
|
+
const authResult = await requireAnyPermission(req, [
|
|
576
|
+
{ action: "manage", resource: "settings" }
|
|
577
|
+
]);
|
|
578
|
+
if (isErrorResponse(authResult)) throw toNextlyAuthError(authResult);
|
|
579
|
+
await getCachedNextly();
|
|
580
|
+
const adapter = container.get("adapter");
|
|
581
|
+
const regenService = new MediaRegenerationService(adapter, console);
|
|
582
|
+
const status = await regenService.getRegenerationStatus();
|
|
583
|
+
return respondData(status);
|
|
584
|
+
});
|
|
585
|
+
var regenerateBatch = withErrorHandler(async (req) => {
|
|
586
|
+
const authResult = await requireAnyPermission(req, [
|
|
587
|
+
{ action: "manage", resource: "settings" }
|
|
588
|
+
]);
|
|
589
|
+
if (isErrorResponse(authResult)) throw toNextlyAuthError(authResult);
|
|
590
|
+
const text = await req.text();
|
|
591
|
+
let body;
|
|
592
|
+
try {
|
|
593
|
+
body = text ? JSON.parse(text) : {};
|
|
594
|
+
} catch {
|
|
595
|
+
throw NextlyError.validation({
|
|
596
|
+
errors: [
|
|
597
|
+
{
|
|
598
|
+
path: "",
|
|
599
|
+
code: "invalid_json",
|
|
600
|
+
message: "Request body is not valid JSON."
|
|
601
|
+
}
|
|
602
|
+
]
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
const opts = body && typeof body === "object" ? body : {};
|
|
606
|
+
const batchSize = typeof opts.batchSize === "number" ? opts.batchSize : 10;
|
|
607
|
+
const cursor = typeof opts.cursor === "string" ? opts.cursor : void 0;
|
|
608
|
+
await getCachedNextly();
|
|
609
|
+
const adapter = container.get("adapter");
|
|
610
|
+
const regenService = new MediaRegenerationService(adapter, console);
|
|
611
|
+
const result = await regenService.regenerateBatch({ batchSize, cursor });
|
|
612
|
+
return respondAction("Regeneration batch processed.", {
|
|
613
|
+
processed: result.processed,
|
|
614
|
+
remaining: result.remaining,
|
|
615
|
+
nextCursor: result.nextCursor,
|
|
616
|
+
failures: result.failures
|
|
617
|
+
});
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
// src/domains/schema/journal/read-journal.ts
|
|
621
|
+
import { desc, lt } from "drizzle-orm";
|
|
622
|
+
var MIN_LIMIT = 1;
|
|
623
|
+
var MAX_LIMIT = 100;
|
|
624
|
+
async function readJournal(args) {
|
|
625
|
+
const table = tableForDialect(args.dialect);
|
|
626
|
+
const limit = clamp(args.limit, MIN_LIMIT, MAX_LIMIT);
|
|
627
|
+
const beforeDate = args.before ? new Date(args.before) : void 0;
|
|
628
|
+
const db = args.db;
|
|
629
|
+
const startedAtCol = table.startedAt;
|
|
630
|
+
let chain = db.select().from(table);
|
|
631
|
+
if (beforeDate) {
|
|
632
|
+
chain = chain.where(lt(startedAtCol, beforeDate));
|
|
633
|
+
}
|
|
634
|
+
chain = chain.orderBy(desc(startedAtCol));
|
|
635
|
+
const raw = await chain.limit(limit + 1);
|
|
636
|
+
const trimmed = raw.slice(0, limit);
|
|
637
|
+
const hasMore = raw.length > limit;
|
|
638
|
+
return { rows: trimmed.map(mapRow), hasMore };
|
|
639
|
+
}
|
|
640
|
+
function clamp(n, lo, hi) {
|
|
641
|
+
return Math.max(lo, Math.min(hi, n));
|
|
642
|
+
}
|
|
643
|
+
function mapRow(r) {
|
|
644
|
+
const scopeKind = r.scopeKind;
|
|
645
|
+
const scopeSlug = r.scopeSlug;
|
|
646
|
+
let scope = null;
|
|
647
|
+
if (scopeKind === "fresh-push") {
|
|
648
|
+
scope = { kind: "fresh-push" };
|
|
649
|
+
} else if (scopeKind === "global") {
|
|
650
|
+
scope = scopeSlug ? { kind: "global", slug: scopeSlug } : { kind: "global" };
|
|
651
|
+
} else if ((scopeKind === "collection" || scopeKind === "single") && typeof scopeSlug === "string") {
|
|
652
|
+
scope = { kind: scopeKind, slug: scopeSlug };
|
|
653
|
+
}
|
|
654
|
+
const sa = r.summaryAdded;
|
|
655
|
+
const sr = r.summaryRemoved;
|
|
656
|
+
const srn = r.summaryRenamed;
|
|
657
|
+
const sc = r.summaryChanged;
|
|
658
|
+
const summary = sa !== null && sr !== null && srn !== null && sc !== null ? { added: sa, removed: sr, renamed: srn, changed: sc } : null;
|
|
659
|
+
return {
|
|
660
|
+
id: String(r.id),
|
|
661
|
+
source: r.source,
|
|
662
|
+
status: r.status,
|
|
663
|
+
scope,
|
|
664
|
+
summary,
|
|
665
|
+
startedAt: toIso(r.startedAt),
|
|
666
|
+
endedAt: r.endedAt != null ? toIso(r.endedAt) : null,
|
|
667
|
+
durationMs: r.durationMs ?? null,
|
|
668
|
+
errorCode: r.errorCode ?? null,
|
|
669
|
+
errorMessage: r.errorMessage ?? null
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
function toIso(v) {
|
|
673
|
+
if (v instanceof Date) return v.toISOString();
|
|
674
|
+
if (typeof v === "number") return new Date(v).toISOString();
|
|
675
|
+
if (typeof v === "string") return new Date(v).toISOString();
|
|
676
|
+
return String(v);
|
|
677
|
+
}
|
|
678
|
+
function tableForDialect(dialect) {
|
|
679
|
+
switch (dialect) {
|
|
680
|
+
case "postgresql":
|
|
681
|
+
return nextlyMigrationJournalPg;
|
|
682
|
+
case "mysql":
|
|
683
|
+
return nextlyMigrationJournalMysql;
|
|
684
|
+
case "sqlite":
|
|
685
|
+
return nextlyMigrationJournalSqlite;
|
|
686
|
+
default: {
|
|
687
|
+
const exhaustive = dialect;
|
|
688
|
+
throw new Error(`Unsupported dialect: ${String(exhaustive)}`);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// src/api/schema-journal.ts
|
|
694
|
+
var PRIVATE_NO_STORE_HEADERS2 = {
|
|
695
|
+
"Cache-Control": "private, no-store",
|
|
696
|
+
Vary: "Cookie"
|
|
697
|
+
};
|
|
698
|
+
var DEFAULT_LIMIT = 20;
|
|
699
|
+
var MIN_LIMIT2 = 1;
|
|
700
|
+
var MAX_LIMIT2 = 100;
|
|
701
|
+
async function getAdapter() {
|
|
702
|
+
await getCachedNextly();
|
|
703
|
+
return container.get("adapter");
|
|
704
|
+
}
|
|
705
|
+
var getSchemaJournal = withErrorHandler(async (req) => {
|
|
706
|
+
const auth = await requireAuthentication(req);
|
|
707
|
+
if (isErrorResponse(auth)) throw toNextlyAuthError(auth);
|
|
708
|
+
if (!await isSuperAdmin(auth.userId)) {
|
|
709
|
+
throw NextlyError.forbidden({
|
|
710
|
+
logContext: { reason: "schema-journal-super-admin-required" }
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
const { searchParams } = new URL(req.url);
|
|
714
|
+
const limitParam = searchParams.get("limit");
|
|
715
|
+
let limit = DEFAULT_LIMIT;
|
|
716
|
+
if (limitParam !== null) {
|
|
717
|
+
const parsed = Number(limitParam);
|
|
718
|
+
if (!Number.isFinite(parsed) || parsed < MIN_LIMIT2 || parsed > MAX_LIMIT2) {
|
|
719
|
+
throw NextlyError.validation({
|
|
720
|
+
errors: [
|
|
721
|
+
{
|
|
722
|
+
path: "limit",
|
|
723
|
+
code: "out_of_range",
|
|
724
|
+
message: `limit must be a number between ${MIN_LIMIT2} and ${MAX_LIMIT2}`
|
|
725
|
+
}
|
|
726
|
+
]
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
limit = Math.floor(parsed);
|
|
730
|
+
}
|
|
731
|
+
const beforeParam = searchParams.get("before");
|
|
732
|
+
let before;
|
|
733
|
+
if (beforeParam !== null) {
|
|
734
|
+
const parsed = new Date(beforeParam);
|
|
735
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
736
|
+
throw NextlyError.validation({
|
|
737
|
+
errors: [
|
|
738
|
+
{
|
|
739
|
+
path: "before",
|
|
740
|
+
code: "invalid_date",
|
|
741
|
+
message: "before must be a valid ISO 8601 timestamp"
|
|
742
|
+
}
|
|
743
|
+
]
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
before = beforeParam;
|
|
747
|
+
}
|
|
748
|
+
const adapter = await getAdapter();
|
|
749
|
+
const db = adapter.getDrizzle();
|
|
750
|
+
const result = await readJournal({
|
|
751
|
+
db,
|
|
752
|
+
dialect: adapter.dialect,
|
|
753
|
+
limit,
|
|
754
|
+
before
|
|
755
|
+
});
|
|
756
|
+
return respondData({ ...result }, { headers: PRIVATE_NO_STORE_HEADERS2 });
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
// src/route-handler/route-parser.ts
|
|
760
|
+
function getActionFromMethod(httpMethod) {
|
|
761
|
+
switch (httpMethod) {
|
|
762
|
+
case "GET":
|
|
763
|
+
return "read";
|
|
764
|
+
case "POST":
|
|
765
|
+
return "create";
|
|
766
|
+
case "PATCH":
|
|
767
|
+
case "PUT":
|
|
768
|
+
return "update";
|
|
769
|
+
case "DELETE":
|
|
770
|
+
return "delete";
|
|
771
|
+
default:
|
|
772
|
+
return "read";
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
function isPublicEndpoint(service, method) {
|
|
776
|
+
if (service === "auth") {
|
|
777
|
+
return [
|
|
778
|
+
"register",
|
|
779
|
+
"generatePasswordResetToken",
|
|
780
|
+
"resetPasswordWithToken",
|
|
781
|
+
"verifyEmail"
|
|
782
|
+
].includes(method);
|
|
783
|
+
}
|
|
784
|
+
if (service === "forms") {
|
|
785
|
+
return true;
|
|
786
|
+
}
|
|
787
|
+
if (service === "components" && method === "listComponents") {
|
|
788
|
+
return true;
|
|
789
|
+
}
|
|
790
|
+
return false;
|
|
791
|
+
}
|
|
792
|
+
function requiresAuthOnly(service, method) {
|
|
793
|
+
if (service === "auth") {
|
|
794
|
+
return ["changePassword", "generateEmailVerificationToken"].includes(
|
|
795
|
+
method
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
if (service === "collections" && ["listCollections"].includes(method)) {
|
|
799
|
+
return true;
|
|
800
|
+
}
|
|
801
|
+
if (service === "singles" && ["listSingles"].includes(method)) {
|
|
802
|
+
return true;
|
|
803
|
+
}
|
|
804
|
+
if (service === "components" && method === "getComponent") {
|
|
805
|
+
return true;
|
|
806
|
+
}
|
|
807
|
+
return false;
|
|
808
|
+
}
|
|
809
|
+
function getActionFromOperation(operation) {
|
|
810
|
+
switch (operation) {
|
|
811
|
+
case "create":
|
|
812
|
+
return "create";
|
|
813
|
+
case "update":
|
|
814
|
+
return "update";
|
|
815
|
+
case "delete":
|
|
816
|
+
return "delete";
|
|
817
|
+
default:
|
|
818
|
+
return "read";
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
function parseMeRoutes(httpMethod, subresource, routeParams) {
|
|
822
|
+
if (subresource === "permissions" && httpMethod === "GET") {
|
|
823
|
+
return {
|
|
824
|
+
service: "users",
|
|
825
|
+
operation: "list",
|
|
826
|
+
method: "getCurrentUserPermissions",
|
|
827
|
+
routeParams
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
if (subresource) {
|
|
831
|
+
return null;
|
|
832
|
+
}
|
|
833
|
+
if (httpMethod === "GET") {
|
|
834
|
+
return {
|
|
835
|
+
service: "users",
|
|
836
|
+
operation: "single",
|
|
837
|
+
method: "getCurrentUser",
|
|
838
|
+
routeParams
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
if (httpMethod === "PATCH") {
|
|
842
|
+
return {
|
|
843
|
+
service: "users",
|
|
844
|
+
operation: "update",
|
|
845
|
+
method: "updateCurrentUser",
|
|
846
|
+
routeParams
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
return null;
|
|
850
|
+
}
|
|
851
|
+
function parseUserRoutes(id, subresource, subId, additionalParams, httpMethod, routeParams) {
|
|
852
|
+
if (!id && httpMethod === "POST") {
|
|
853
|
+
return {
|
|
854
|
+
service: "users",
|
|
855
|
+
operation: "create",
|
|
856
|
+
method: "createLocalUser",
|
|
857
|
+
routeParams
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
if (!id && httpMethod === "GET") {
|
|
861
|
+
return {
|
|
862
|
+
service: "users",
|
|
863
|
+
operation: "list",
|
|
864
|
+
method: "listUsers",
|
|
865
|
+
routeParams
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
if (id && !subresource && httpMethod === "GET") {
|
|
869
|
+
routeParams.userId = id;
|
|
870
|
+
return {
|
|
871
|
+
service: "users",
|
|
872
|
+
operation: "single",
|
|
873
|
+
method: "getUserById",
|
|
874
|
+
routeParams
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
if (id && !subresource && httpMethod === "PATCH") {
|
|
878
|
+
routeParams.userId = id;
|
|
879
|
+
return {
|
|
880
|
+
service: "users",
|
|
881
|
+
operation: "update",
|
|
882
|
+
method: "updateUser",
|
|
883
|
+
routeParams
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
if (id && !subresource && httpMethod === "DELETE") {
|
|
887
|
+
routeParams.userId = id;
|
|
888
|
+
return {
|
|
889
|
+
service: "users",
|
|
890
|
+
operation: "delete",
|
|
891
|
+
method: "deleteUser",
|
|
892
|
+
routeParams
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
if (id && subresource === "password" && httpMethod === "PATCH") {
|
|
896
|
+
routeParams.userId = id;
|
|
897
|
+
return {
|
|
898
|
+
service: "users",
|
|
899
|
+
operation: "update",
|
|
900
|
+
method: "updatePasswordHash",
|
|
901
|
+
routeParams
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
if (id && subresource === "accounts" && !subId && httpMethod === "GET") {
|
|
905
|
+
routeParams.userId = id;
|
|
906
|
+
return {
|
|
907
|
+
service: "users",
|
|
908
|
+
operation: "single",
|
|
909
|
+
method: "getAccounts",
|
|
910
|
+
routeParams
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
if (id && subresource === "accounts" && subId && additionalParams[0] && httpMethod === "DELETE") {
|
|
914
|
+
routeParams.userId = id;
|
|
915
|
+
routeParams.provider = subId;
|
|
916
|
+
routeParams.providerAccountId = additionalParams[0];
|
|
917
|
+
return {
|
|
918
|
+
service: "users",
|
|
919
|
+
operation: "update",
|
|
920
|
+
method: "unlinkAccountForUser",
|
|
921
|
+
routeParams
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
if (id && subresource === "roles" && !subId && httpMethod === "POST") {
|
|
925
|
+
routeParams.userId = id;
|
|
926
|
+
return {
|
|
927
|
+
service: "rbac",
|
|
928
|
+
operation: "update",
|
|
929
|
+
method: "assignRoleToUser",
|
|
930
|
+
routeParams
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
if (id && subresource === "roles" && !subId && httpMethod === "GET") {
|
|
934
|
+
routeParams.userId = id;
|
|
935
|
+
return {
|
|
936
|
+
service: "rbac",
|
|
937
|
+
operation: "list",
|
|
938
|
+
method: "listUserRoles",
|
|
939
|
+
routeParams
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
if (id && subresource === "roles" && subId && httpMethod === "DELETE") {
|
|
943
|
+
routeParams.userId = id;
|
|
944
|
+
routeParams.roleId = subId;
|
|
945
|
+
return {
|
|
946
|
+
service: "rbac",
|
|
947
|
+
operation: "update",
|
|
948
|
+
method: "unassignRoleFromUser",
|
|
949
|
+
routeParams
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
return null;
|
|
953
|
+
}
|
|
954
|
+
function parseRoleRoutes(id, subresource, subId, httpMethod, routeParams) {
|
|
955
|
+
if (!id && httpMethod === "POST") {
|
|
956
|
+
return {
|
|
957
|
+
service: "rbac",
|
|
958
|
+
operation: "create",
|
|
959
|
+
method: "createRole",
|
|
960
|
+
routeParams
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
if (!id && httpMethod === "GET") {
|
|
964
|
+
return {
|
|
965
|
+
service: "rbac",
|
|
966
|
+
operation: "list",
|
|
967
|
+
method: "listRoles",
|
|
968
|
+
routeParams
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
if (id && !subresource && httpMethod === "GET") {
|
|
972
|
+
routeParams.roleId = id;
|
|
973
|
+
return {
|
|
974
|
+
service: "rbac",
|
|
975
|
+
operation: "single",
|
|
976
|
+
method: "getRoleById",
|
|
977
|
+
routeParams
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
if (id && !subresource && httpMethod === "PATCH") {
|
|
981
|
+
routeParams.roleId = id;
|
|
982
|
+
return {
|
|
983
|
+
service: "rbac",
|
|
984
|
+
operation: "update",
|
|
985
|
+
method: "updateRole",
|
|
986
|
+
routeParams
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
if (id && !subresource && httpMethod === "DELETE") {
|
|
990
|
+
routeParams.roleId = id;
|
|
991
|
+
return {
|
|
992
|
+
service: "rbac",
|
|
993
|
+
operation: "delete",
|
|
994
|
+
method: "deleteRole",
|
|
995
|
+
routeParams
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
if (id && subresource === "children" && !subId && httpMethod === "POST") {
|
|
999
|
+
routeParams.parentRoleId = id;
|
|
1000
|
+
return {
|
|
1001
|
+
service: "rbac",
|
|
1002
|
+
operation: "update",
|
|
1003
|
+
method: "addRoleInheritance",
|
|
1004
|
+
routeParams
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
if (id && subresource === "children" && !subId && httpMethod === "GET") {
|
|
1008
|
+
routeParams.roleId = id;
|
|
1009
|
+
return {
|
|
1010
|
+
service: "rbac",
|
|
1011
|
+
operation: "list",
|
|
1012
|
+
method: "listDescendantRoles",
|
|
1013
|
+
routeParams
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
if (id && subresource === "children" && subId && httpMethod === "DELETE") {
|
|
1017
|
+
routeParams.parentRoleId = id;
|
|
1018
|
+
routeParams.childRoleId = subId;
|
|
1019
|
+
return {
|
|
1020
|
+
service: "rbac",
|
|
1021
|
+
operation: "update",
|
|
1022
|
+
method: "removeRoleInheritance",
|
|
1023
|
+
routeParams
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
if (id && subresource === "parents" && httpMethod === "GET") {
|
|
1027
|
+
routeParams.roleId = id;
|
|
1028
|
+
return {
|
|
1029
|
+
service: "rbac",
|
|
1030
|
+
operation: "list",
|
|
1031
|
+
method: "listAncestorRoles",
|
|
1032
|
+
routeParams
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
if (id && subresource === "permissions" && !subId && httpMethod === "PATCH") {
|
|
1036
|
+
routeParams.roleId = id;
|
|
1037
|
+
return {
|
|
1038
|
+
service: "rbac",
|
|
1039
|
+
operation: "update",
|
|
1040
|
+
method: "setRolePermissions",
|
|
1041
|
+
routeParams
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
if (id && subresource === "permissions" && !subId && httpMethod === "POST") {
|
|
1045
|
+
routeParams.roleId = id;
|
|
1046
|
+
return {
|
|
1047
|
+
service: "rbac",
|
|
1048
|
+
operation: "update",
|
|
1049
|
+
method: "addPermissionToRole",
|
|
1050
|
+
routeParams
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
if (id && subresource === "permissions" && !subId && httpMethod === "GET") {
|
|
1054
|
+
routeParams.roleId = id;
|
|
1055
|
+
return {
|
|
1056
|
+
service: "rbac",
|
|
1057
|
+
operation: "list",
|
|
1058
|
+
method: "listRolePermissions",
|
|
1059
|
+
routeParams
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
if (id && subresource === "permissions" && subId && httpMethod === "DELETE") {
|
|
1063
|
+
routeParams.roleId = id;
|
|
1064
|
+
routeParams.permissionId = subId;
|
|
1065
|
+
return {
|
|
1066
|
+
service: "rbac",
|
|
1067
|
+
operation: "update",
|
|
1068
|
+
method: "removePermissionFromRole",
|
|
1069
|
+
routeParams
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
return null;
|
|
1073
|
+
}
|
|
1074
|
+
function parseCollectionRoutes(id, subresource, subId, httpMethod, routeParams, additionalParams = []) {
|
|
1075
|
+
const bulkDeleteRoute = parseCollectionEntryBulkDeleteRoute(
|
|
1076
|
+
id,
|
|
1077
|
+
subresource,
|
|
1078
|
+
subId,
|
|
1079
|
+
httpMethod,
|
|
1080
|
+
routeParams
|
|
1081
|
+
);
|
|
1082
|
+
if (bulkDeleteRoute) return bulkDeleteRoute;
|
|
1083
|
+
const bulkUpdateRoute = parseCollectionEntryBulkUpdateRoute(
|
|
1084
|
+
id,
|
|
1085
|
+
subresource,
|
|
1086
|
+
subId,
|
|
1087
|
+
httpMethod,
|
|
1088
|
+
routeParams
|
|
1089
|
+
);
|
|
1090
|
+
if (bulkUpdateRoute) return bulkUpdateRoute;
|
|
1091
|
+
const bulkUpdateByQueryRoute = parseCollectionEntryBulkUpdateByQueryRoute(
|
|
1092
|
+
id,
|
|
1093
|
+
subresource,
|
|
1094
|
+
subId,
|
|
1095
|
+
httpMethod,
|
|
1096
|
+
routeParams
|
|
1097
|
+
);
|
|
1098
|
+
if (bulkUpdateByQueryRoute) return bulkUpdateByQueryRoute;
|
|
1099
|
+
const duplicateRoute = parseCollectionEntryDuplicateRoute(
|
|
1100
|
+
id,
|
|
1101
|
+
subresource,
|
|
1102
|
+
subId,
|
|
1103
|
+
additionalParams,
|
|
1104
|
+
httpMethod,
|
|
1105
|
+
routeParams
|
|
1106
|
+
);
|
|
1107
|
+
if (duplicateRoute) return duplicateRoute;
|
|
1108
|
+
const countRoute = parseCollectionEntryCountRoute(
|
|
1109
|
+
id,
|
|
1110
|
+
subresource,
|
|
1111
|
+
subId,
|
|
1112
|
+
httpMethod,
|
|
1113
|
+
routeParams
|
|
1114
|
+
);
|
|
1115
|
+
if (countRoute) return countRoute;
|
|
1116
|
+
if (!id && httpMethod === "POST") {
|
|
1117
|
+
return {
|
|
1118
|
+
service: "collections",
|
|
1119
|
+
operation: "create",
|
|
1120
|
+
method: "createCollection",
|
|
1121
|
+
routeParams
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
if (!id && httpMethod === "GET") {
|
|
1125
|
+
return {
|
|
1126
|
+
service: "collections",
|
|
1127
|
+
operation: "list",
|
|
1128
|
+
method: "listCollections",
|
|
1129
|
+
routeParams
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
if (id === "schema" && subresource && subId === "preview" && httpMethod === "POST") {
|
|
1133
|
+
routeParams.collectionName = subresource;
|
|
1134
|
+
return {
|
|
1135
|
+
service: "collections",
|
|
1136
|
+
operation: "single",
|
|
1137
|
+
method: "previewSchemaChanges",
|
|
1138
|
+
routeParams
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
if (id === "schema" && subresource && subId === "apply" && httpMethod === "POST") {
|
|
1142
|
+
routeParams.collectionName = subresource;
|
|
1143
|
+
return {
|
|
1144
|
+
service: "collections",
|
|
1145
|
+
operation: "update",
|
|
1146
|
+
method: "applySchemaChanges",
|
|
1147
|
+
routeParams
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
if (id === "schema" && subresource && httpMethod === "GET") {
|
|
1151
|
+
routeParams.collectionName = subresource;
|
|
1152
|
+
return {
|
|
1153
|
+
service: "collections",
|
|
1154
|
+
operation: "single",
|
|
1155
|
+
method: "getCollection",
|
|
1156
|
+
routeParams
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
if (id && !subresource && httpMethod === "GET") {
|
|
1160
|
+
routeParams.collectionName = id;
|
|
1161
|
+
return {
|
|
1162
|
+
service: "collections",
|
|
1163
|
+
operation: "single",
|
|
1164
|
+
method: "getCollection",
|
|
1165
|
+
routeParams
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
if (id && !subresource && httpMethod === "PATCH") {
|
|
1169
|
+
routeParams.collectionName = id;
|
|
1170
|
+
return {
|
|
1171
|
+
service: "collections",
|
|
1172
|
+
operation: "update",
|
|
1173
|
+
method: "updateCollection",
|
|
1174
|
+
routeParams
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1177
|
+
if (id && !subresource && httpMethod === "DELETE") {
|
|
1178
|
+
routeParams.collectionName = id;
|
|
1179
|
+
return {
|
|
1180
|
+
service: "collections",
|
|
1181
|
+
operation: "delete",
|
|
1182
|
+
method: "deleteCollection",
|
|
1183
|
+
routeParams
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
if (id && subresource === "entries" && !subId && httpMethod === "GET") {
|
|
1187
|
+
routeParams.collectionName = id;
|
|
1188
|
+
return {
|
|
1189
|
+
service: "collections",
|
|
1190
|
+
operation: "list",
|
|
1191
|
+
method: "listEntries",
|
|
1192
|
+
routeParams
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
if (id && subresource === "entries" && !subId && httpMethod === "POST") {
|
|
1196
|
+
routeParams.collectionName = id;
|
|
1197
|
+
return {
|
|
1198
|
+
service: "collections",
|
|
1199
|
+
operation: "create",
|
|
1200
|
+
method: "createEntry",
|
|
1201
|
+
routeParams
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
if (id && subresource === "entries" && subId && httpMethod === "GET") {
|
|
1205
|
+
routeParams.collectionName = id;
|
|
1206
|
+
routeParams.entryId = subId;
|
|
1207
|
+
return {
|
|
1208
|
+
service: "collections",
|
|
1209
|
+
operation: "single",
|
|
1210
|
+
method: "getEntry",
|
|
1211
|
+
routeParams
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
if (id && subresource === "entries" && subId && httpMethod === "PATCH") {
|
|
1215
|
+
routeParams.collectionName = id;
|
|
1216
|
+
routeParams.entryId = subId;
|
|
1217
|
+
return {
|
|
1218
|
+
service: "collections",
|
|
1219
|
+
operation: "update",
|
|
1220
|
+
method: "updateEntry",
|
|
1221
|
+
routeParams
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1224
|
+
if (id && subresource === "entries" && subId && httpMethod === "DELETE") {
|
|
1225
|
+
routeParams.collectionName = id;
|
|
1226
|
+
routeParams.entryId = subId;
|
|
1227
|
+
return {
|
|
1228
|
+
service: "collections",
|
|
1229
|
+
operation: "delete",
|
|
1230
|
+
method: "deleteEntry",
|
|
1231
|
+
routeParams
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
return null;
|
|
1235
|
+
}
|
|
1236
|
+
function parseCollectionEntryDuplicateRoute(id, subresource, subId, additionalParams, httpMethod, routeParams) {
|
|
1237
|
+
if (id && subresource === "entries" && subId && additionalParams[0] === "duplicate" && httpMethod === "POST") {
|
|
1238
|
+
routeParams.collectionName = id;
|
|
1239
|
+
routeParams.entryId = subId;
|
|
1240
|
+
return {
|
|
1241
|
+
service: "collections",
|
|
1242
|
+
operation: "create",
|
|
1243
|
+
method: "duplicateEntry",
|
|
1244
|
+
routeParams
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
return null;
|
|
1248
|
+
}
|
|
1249
|
+
function parseCollectionEntryBulkDeleteRoute(id, subresource, subId, httpMethod, routeParams) {
|
|
1250
|
+
if (id && subresource === "entries" && subId === "bulk-delete" && httpMethod === "POST") {
|
|
1251
|
+
routeParams.collectionName = id;
|
|
1252
|
+
return {
|
|
1253
|
+
service: "collections",
|
|
1254
|
+
operation: "delete",
|
|
1255
|
+
method: "bulkDeleteEntries",
|
|
1256
|
+
routeParams
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
return null;
|
|
1260
|
+
}
|
|
1261
|
+
function parseCollectionEntryBulkUpdateRoute(id, subresource, subId, httpMethod, routeParams) {
|
|
1262
|
+
if (id && subresource === "entries" && subId === "bulk-update" && httpMethod === "POST") {
|
|
1263
|
+
routeParams.collectionName = id;
|
|
1264
|
+
return {
|
|
1265
|
+
service: "collections",
|
|
1266
|
+
operation: "update",
|
|
1267
|
+
method: "bulkUpdateEntries",
|
|
1268
|
+
routeParams
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
return null;
|
|
1272
|
+
}
|
|
1273
|
+
function parseCollectionEntryBulkUpdateByQueryRoute(id, subresource, subId, httpMethod, routeParams) {
|
|
1274
|
+
if (id && subresource === "entries" && !subId && httpMethod === "PATCH") {
|
|
1275
|
+
routeParams.collectionName = id;
|
|
1276
|
+
return {
|
|
1277
|
+
service: "collections",
|
|
1278
|
+
operation: "update",
|
|
1279
|
+
method: "bulkUpdateByQuery",
|
|
1280
|
+
routeParams
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
return null;
|
|
1284
|
+
}
|
|
1285
|
+
function parseCollectionEntryCountRoute(id, subresource, subId, httpMethod, routeParams) {
|
|
1286
|
+
if (id && subresource === "entries" && subId === "count" && httpMethod === "GET") {
|
|
1287
|
+
routeParams.collectionName = id;
|
|
1288
|
+
return {
|
|
1289
|
+
service: "collections",
|
|
1290
|
+
operation: "count",
|
|
1291
|
+
method: "countEntries",
|
|
1292
|
+
routeParams
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
return null;
|
|
1296
|
+
}
|
|
1297
|
+
function parsePermissionRoutes(id, httpMethod, routeParams) {
|
|
1298
|
+
if (!id && httpMethod === "POST") {
|
|
1299
|
+
return {
|
|
1300
|
+
service: "rbac",
|
|
1301
|
+
operation: "create",
|
|
1302
|
+
method: "ensurePermission",
|
|
1303
|
+
routeParams
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
if (!id && httpMethod === "GET") {
|
|
1307
|
+
return {
|
|
1308
|
+
service: "rbac",
|
|
1309
|
+
operation: "list",
|
|
1310
|
+
method: "listPermissions",
|
|
1311
|
+
routeParams
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
if (id && httpMethod === "GET") {
|
|
1315
|
+
routeParams.permissionId = id;
|
|
1316
|
+
return {
|
|
1317
|
+
service: "rbac",
|
|
1318
|
+
operation: "single",
|
|
1319
|
+
method: "getPermissionById",
|
|
1320
|
+
routeParams
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
if (id && httpMethod === "PATCH") {
|
|
1324
|
+
routeParams.permissionId = id;
|
|
1325
|
+
return {
|
|
1326
|
+
service: "rbac",
|
|
1327
|
+
operation: "update",
|
|
1328
|
+
method: "updatePermission",
|
|
1329
|
+
routeParams
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
if (id && httpMethod === "DELETE") {
|
|
1333
|
+
routeParams.permissionId = id;
|
|
1334
|
+
return {
|
|
1335
|
+
service: "rbac",
|
|
1336
|
+
operation: "delete",
|
|
1337
|
+
method: "deletePermissionById",
|
|
1338
|
+
routeParams
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
return null;
|
|
1342
|
+
}
|
|
1343
|
+
function parseSingleRoutes(id, subresource, subId, httpMethod, routeParams) {
|
|
1344
|
+
if (!id && httpMethod === "GET") {
|
|
1345
|
+
return {
|
|
1346
|
+
service: "singles",
|
|
1347
|
+
operation: "list",
|
|
1348
|
+
method: "listSingles",
|
|
1349
|
+
routeParams
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
if (!id && httpMethod === "POST") {
|
|
1353
|
+
return {
|
|
1354
|
+
service: "singles",
|
|
1355
|
+
operation: "create",
|
|
1356
|
+
method: "createSingle",
|
|
1357
|
+
routeParams
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
if (id && !subresource && httpMethod === "GET") {
|
|
1361
|
+
routeParams.slug = id;
|
|
1362
|
+
return {
|
|
1363
|
+
service: "singles",
|
|
1364
|
+
operation: "single",
|
|
1365
|
+
method: "getSingleDocument",
|
|
1366
|
+
routeParams
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
if (id && !subresource && httpMethod === "PATCH") {
|
|
1370
|
+
routeParams.slug = id;
|
|
1371
|
+
return {
|
|
1372
|
+
service: "singles",
|
|
1373
|
+
operation: "update",
|
|
1374
|
+
method: "updateSingleDocument",
|
|
1375
|
+
routeParams
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1378
|
+
if (id && !subresource && httpMethod === "DELETE") {
|
|
1379
|
+
routeParams.slug = id;
|
|
1380
|
+
return {
|
|
1381
|
+
service: "singles",
|
|
1382
|
+
operation: "delete",
|
|
1383
|
+
method: "deleteSingle",
|
|
1384
|
+
routeParams
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
if (id && subresource === "schema" && httpMethod === "GET") {
|
|
1388
|
+
routeParams.slug = id;
|
|
1389
|
+
return {
|
|
1390
|
+
service: "singles",
|
|
1391
|
+
operation: "single",
|
|
1392
|
+
method: "getSingleSchema",
|
|
1393
|
+
routeParams
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
if (id && subresource === "schema" && httpMethod === "PATCH") {
|
|
1397
|
+
routeParams.slug = id;
|
|
1398
|
+
return {
|
|
1399
|
+
service: "singles",
|
|
1400
|
+
operation: "update",
|
|
1401
|
+
method: "updateSingleSchema",
|
|
1402
|
+
routeParams
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
if (id === "schema" && subresource && subId === "preview" && httpMethod === "POST") {
|
|
1406
|
+
routeParams.slug = subresource;
|
|
1407
|
+
return {
|
|
1408
|
+
service: "singles",
|
|
1409
|
+
operation: "single",
|
|
1410
|
+
method: "previewSingleSchemaChanges",
|
|
1411
|
+
routeParams
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
if (id === "schema" && subresource && subId === "apply" && httpMethod === "POST") {
|
|
1415
|
+
routeParams.slug = subresource;
|
|
1416
|
+
return {
|
|
1417
|
+
service: "singles",
|
|
1418
|
+
operation: "update",
|
|
1419
|
+
method: "applySingleSchemaChanges",
|
|
1420
|
+
routeParams
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
return null;
|
|
1424
|
+
}
|
|
1425
|
+
function parseComponentRoutes(id, httpMethod, routeParams, subresource, subId) {
|
|
1426
|
+
if (id === "schema" && subresource && subId === "preview" && httpMethod === "POST") {
|
|
1427
|
+
routeParams.slug = subresource;
|
|
1428
|
+
return {
|
|
1429
|
+
service: "components",
|
|
1430
|
+
operation: "single",
|
|
1431
|
+
method: "previewComponentSchemaChanges",
|
|
1432
|
+
routeParams
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1435
|
+
if (id === "schema" && subresource && subId === "apply" && httpMethod === "POST") {
|
|
1436
|
+
routeParams.slug = subresource;
|
|
1437
|
+
return {
|
|
1438
|
+
service: "components",
|
|
1439
|
+
operation: "update",
|
|
1440
|
+
method: "applyComponentSchemaChanges",
|
|
1441
|
+
routeParams
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
1444
|
+
const slug = id;
|
|
1445
|
+
if (!slug && httpMethod === "GET") {
|
|
1446
|
+
return {
|
|
1447
|
+
service: "components",
|
|
1448
|
+
operation: "list",
|
|
1449
|
+
method: "listComponents",
|
|
1450
|
+
routeParams
|
|
1451
|
+
};
|
|
1452
|
+
}
|
|
1453
|
+
if (!slug && httpMethod === "POST") {
|
|
1454
|
+
return {
|
|
1455
|
+
service: "components",
|
|
1456
|
+
operation: "create",
|
|
1457
|
+
method: "createComponent",
|
|
1458
|
+
routeParams
|
|
1459
|
+
};
|
|
1460
|
+
}
|
|
1461
|
+
if (slug && httpMethod === "GET") {
|
|
1462
|
+
routeParams.slug = slug;
|
|
1463
|
+
return {
|
|
1464
|
+
service: "components",
|
|
1465
|
+
operation: "single",
|
|
1466
|
+
method: "getComponent",
|
|
1467
|
+
routeParams
|
|
1468
|
+
};
|
|
1469
|
+
}
|
|
1470
|
+
if (slug && httpMethod === "PATCH") {
|
|
1471
|
+
routeParams.slug = slug;
|
|
1472
|
+
return {
|
|
1473
|
+
service: "components",
|
|
1474
|
+
operation: "update",
|
|
1475
|
+
method: "updateComponent",
|
|
1476
|
+
routeParams
|
|
1477
|
+
};
|
|
1478
|
+
}
|
|
1479
|
+
if (slug && httpMethod === "DELETE") {
|
|
1480
|
+
routeParams.slug = slug;
|
|
1481
|
+
return {
|
|
1482
|
+
service: "components",
|
|
1483
|
+
operation: "delete",
|
|
1484
|
+
method: "deleteComponent",
|
|
1485
|
+
routeParams
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
return null;
|
|
1489
|
+
}
|
|
1490
|
+
function parseFormsRoutes(slug, action, httpMethod, routeParams) {
|
|
1491
|
+
if (!slug && httpMethod === "GET") {
|
|
1492
|
+
return {
|
|
1493
|
+
service: "forms",
|
|
1494
|
+
operation: "list",
|
|
1495
|
+
method: "listForms",
|
|
1496
|
+
routeParams
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
1499
|
+
if (slug && !action && httpMethod === "GET") {
|
|
1500
|
+
routeParams.slug = slug;
|
|
1501
|
+
return {
|
|
1502
|
+
service: "forms",
|
|
1503
|
+
operation: "single",
|
|
1504
|
+
method: "getFormBySlug",
|
|
1505
|
+
routeParams
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1508
|
+
if (slug && action === "submit" && httpMethod === "POST") {
|
|
1509
|
+
routeParams.slug = slug;
|
|
1510
|
+
return {
|
|
1511
|
+
service: "forms",
|
|
1512
|
+
operation: "create",
|
|
1513
|
+
method: "submitForm",
|
|
1514
|
+
routeParams
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
return null;
|
|
1518
|
+
}
|
|
1519
|
+
function parseEmailProviderRoutes(id, subresource, httpMethod, routeParams) {
|
|
1520
|
+
if (!id && httpMethod === "GET") {
|
|
1521
|
+
return {
|
|
1522
|
+
service: "emailProviders",
|
|
1523
|
+
operation: "list",
|
|
1524
|
+
method: "listProviders",
|
|
1525
|
+
routeParams
|
|
1526
|
+
};
|
|
1527
|
+
}
|
|
1528
|
+
if (!id && httpMethod === "POST") {
|
|
1529
|
+
return {
|
|
1530
|
+
service: "emailProviders",
|
|
1531
|
+
operation: "create",
|
|
1532
|
+
method: "createProvider",
|
|
1533
|
+
routeParams
|
|
1534
|
+
};
|
|
1535
|
+
}
|
|
1536
|
+
if (id && subresource === "default" && httpMethod === "PATCH") {
|
|
1537
|
+
routeParams.providerId = id;
|
|
1538
|
+
return {
|
|
1539
|
+
service: "emailProviders",
|
|
1540
|
+
operation: "update",
|
|
1541
|
+
method: "setDefault",
|
|
1542
|
+
routeParams
|
|
1543
|
+
};
|
|
1544
|
+
}
|
|
1545
|
+
if (id && subresource === "test" && httpMethod === "POST") {
|
|
1546
|
+
routeParams.providerId = id;
|
|
1547
|
+
return {
|
|
1548
|
+
service: "emailProviders",
|
|
1549
|
+
operation: "single",
|
|
1550
|
+
method: "testProvider",
|
|
1551
|
+
routeParams
|
|
1552
|
+
};
|
|
1553
|
+
}
|
|
1554
|
+
if (id && !subresource && httpMethod === "GET") {
|
|
1555
|
+
routeParams.providerId = id;
|
|
1556
|
+
return {
|
|
1557
|
+
service: "emailProviders",
|
|
1558
|
+
operation: "single",
|
|
1559
|
+
method: "getProvider",
|
|
1560
|
+
routeParams
|
|
1561
|
+
};
|
|
1562
|
+
}
|
|
1563
|
+
if (id && !subresource && httpMethod === "PATCH") {
|
|
1564
|
+
routeParams.providerId = id;
|
|
1565
|
+
return {
|
|
1566
|
+
service: "emailProviders",
|
|
1567
|
+
operation: "update",
|
|
1568
|
+
method: "updateProvider",
|
|
1569
|
+
routeParams
|
|
1570
|
+
};
|
|
1571
|
+
}
|
|
1572
|
+
if (id && !subresource && httpMethod === "DELETE") {
|
|
1573
|
+
routeParams.providerId = id;
|
|
1574
|
+
return {
|
|
1575
|
+
service: "emailProviders",
|
|
1576
|
+
operation: "delete",
|
|
1577
|
+
method: "deleteProvider",
|
|
1578
|
+
routeParams
|
|
1579
|
+
};
|
|
1580
|
+
}
|
|
1581
|
+
return null;
|
|
1582
|
+
}
|
|
1583
|
+
function parseEmailTemplateRoutes(id, subresource, httpMethod, routeParams) {
|
|
1584
|
+
if (!id && httpMethod === "GET") {
|
|
1585
|
+
return {
|
|
1586
|
+
service: "emailTemplates",
|
|
1587
|
+
operation: "list",
|
|
1588
|
+
method: "listTemplates",
|
|
1589
|
+
routeParams
|
|
1590
|
+
};
|
|
1591
|
+
}
|
|
1592
|
+
if (!id && httpMethod === "POST") {
|
|
1593
|
+
return {
|
|
1594
|
+
service: "emailTemplates",
|
|
1595
|
+
operation: "create",
|
|
1596
|
+
method: "createTemplate",
|
|
1597
|
+
routeParams
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1600
|
+
if (id === "layout" && !subresource && httpMethod === "GET") {
|
|
1601
|
+
return {
|
|
1602
|
+
service: "emailTemplates",
|
|
1603
|
+
operation: "single",
|
|
1604
|
+
method: "getLayout",
|
|
1605
|
+
routeParams
|
|
1606
|
+
};
|
|
1607
|
+
}
|
|
1608
|
+
if (id === "layout" && !subresource && httpMethod === "PATCH") {
|
|
1609
|
+
return {
|
|
1610
|
+
service: "emailTemplates",
|
|
1611
|
+
operation: "update",
|
|
1612
|
+
method: "updateLayout",
|
|
1613
|
+
routeParams
|
|
1614
|
+
};
|
|
1615
|
+
}
|
|
1616
|
+
if (id && subresource === "preview" && httpMethod === "POST") {
|
|
1617
|
+
routeParams.templateId = id;
|
|
1618
|
+
return {
|
|
1619
|
+
service: "emailTemplates",
|
|
1620
|
+
operation: "single",
|
|
1621
|
+
method: "previewTemplate",
|
|
1622
|
+
routeParams
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1625
|
+
if (id && !subresource && httpMethod === "GET") {
|
|
1626
|
+
routeParams.templateId = id;
|
|
1627
|
+
return {
|
|
1628
|
+
service: "emailTemplates",
|
|
1629
|
+
operation: "single",
|
|
1630
|
+
method: "getTemplate",
|
|
1631
|
+
routeParams
|
|
1632
|
+
};
|
|
1633
|
+
}
|
|
1634
|
+
if (id && !subresource && httpMethod === "PATCH") {
|
|
1635
|
+
routeParams.templateId = id;
|
|
1636
|
+
return {
|
|
1637
|
+
service: "emailTemplates",
|
|
1638
|
+
operation: "update",
|
|
1639
|
+
method: "updateTemplate",
|
|
1640
|
+
routeParams
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1643
|
+
if (id && !subresource && httpMethod === "DELETE") {
|
|
1644
|
+
routeParams.templateId = id;
|
|
1645
|
+
return {
|
|
1646
|
+
service: "emailTemplates",
|
|
1647
|
+
operation: "delete",
|
|
1648
|
+
method: "deleteTemplate",
|
|
1649
|
+
routeParams
|
|
1650
|
+
};
|
|
1651
|
+
}
|
|
1652
|
+
return null;
|
|
1653
|
+
}
|
|
1654
|
+
function parseUserFieldRoutes(id, subresource, httpMethod, routeParams) {
|
|
1655
|
+
if (!id && httpMethod === "GET") {
|
|
1656
|
+
return {
|
|
1657
|
+
service: "userFields",
|
|
1658
|
+
operation: "list",
|
|
1659
|
+
method: "listUserFields",
|
|
1660
|
+
routeParams
|
|
1661
|
+
};
|
|
1662
|
+
}
|
|
1663
|
+
if (!id && httpMethod === "POST") {
|
|
1664
|
+
return {
|
|
1665
|
+
service: "userFields",
|
|
1666
|
+
operation: "create",
|
|
1667
|
+
method: "createField",
|
|
1668
|
+
routeParams
|
|
1669
|
+
};
|
|
1670
|
+
}
|
|
1671
|
+
if (id === "reorder" && !subresource && httpMethod === "PATCH") {
|
|
1672
|
+
return {
|
|
1673
|
+
service: "userFields",
|
|
1674
|
+
operation: "update",
|
|
1675
|
+
method: "reorderFields",
|
|
1676
|
+
routeParams
|
|
1677
|
+
};
|
|
1678
|
+
}
|
|
1679
|
+
if (id && !subresource && httpMethod === "GET") {
|
|
1680
|
+
routeParams.fieldId = id;
|
|
1681
|
+
return {
|
|
1682
|
+
service: "userFields",
|
|
1683
|
+
operation: "single",
|
|
1684
|
+
method: "getField",
|
|
1685
|
+
routeParams
|
|
1686
|
+
};
|
|
1687
|
+
}
|
|
1688
|
+
if (id && !subresource && httpMethod === "PATCH") {
|
|
1689
|
+
routeParams.fieldId = id;
|
|
1690
|
+
return {
|
|
1691
|
+
service: "userFields",
|
|
1692
|
+
operation: "update",
|
|
1693
|
+
method: "updateField",
|
|
1694
|
+
routeParams
|
|
1695
|
+
};
|
|
1696
|
+
}
|
|
1697
|
+
if (id && !subresource && httpMethod === "DELETE") {
|
|
1698
|
+
routeParams.fieldId = id;
|
|
1699
|
+
return {
|
|
1700
|
+
service: "userFields",
|
|
1701
|
+
operation: "delete",
|
|
1702
|
+
method: "deleteField",
|
|
1703
|
+
routeParams
|
|
1704
|
+
};
|
|
1705
|
+
}
|
|
1706
|
+
return null;
|
|
1707
|
+
}
|
|
1708
|
+
function parseApiKeyRoutes(id, httpMethod, routeParams) {
|
|
1709
|
+
if (!id && httpMethod === "GET") {
|
|
1710
|
+
return {
|
|
1711
|
+
service: "apiKeys",
|
|
1712
|
+
operation: "list",
|
|
1713
|
+
method: "listApiKeys",
|
|
1714
|
+
routeParams
|
|
1715
|
+
};
|
|
1716
|
+
}
|
|
1717
|
+
if (!id && httpMethod === "POST") {
|
|
1718
|
+
return {
|
|
1719
|
+
service: "apiKeys",
|
|
1720
|
+
operation: "create",
|
|
1721
|
+
method: "createApiKey",
|
|
1722
|
+
routeParams
|
|
1723
|
+
};
|
|
1724
|
+
}
|
|
1725
|
+
if (id && httpMethod === "GET") {
|
|
1726
|
+
routeParams.apiKeyId = id;
|
|
1727
|
+
return {
|
|
1728
|
+
service: "apiKeys",
|
|
1729
|
+
operation: "single",
|
|
1730
|
+
method: "getApiKeyById",
|
|
1731
|
+
routeParams
|
|
1732
|
+
};
|
|
1733
|
+
}
|
|
1734
|
+
if (id && httpMethod === "PATCH") {
|
|
1735
|
+
routeParams.apiKeyId = id;
|
|
1736
|
+
return {
|
|
1737
|
+
service: "apiKeys",
|
|
1738
|
+
operation: "update",
|
|
1739
|
+
method: "updateApiKey",
|
|
1740
|
+
routeParams
|
|
1741
|
+
};
|
|
1742
|
+
}
|
|
1743
|
+
if (id && httpMethod === "DELETE") {
|
|
1744
|
+
routeParams.apiKeyId = id;
|
|
1745
|
+
return {
|
|
1746
|
+
service: "apiKeys",
|
|
1747
|
+
operation: "delete",
|
|
1748
|
+
method: "revokeApiKey",
|
|
1749
|
+
routeParams
|
|
1750
|
+
};
|
|
1751
|
+
}
|
|
1752
|
+
return null;
|
|
1753
|
+
}
|
|
1754
|
+
function parseDashboardRoutes(id, httpMethod, routeParams) {
|
|
1755
|
+
if (httpMethod !== "GET") return null;
|
|
1756
|
+
if (id === "stats") {
|
|
1757
|
+
return {
|
|
1758
|
+
service: "dashboard",
|
|
1759
|
+
operation: "list",
|
|
1760
|
+
method: "getDashboardStats",
|
|
1761
|
+
routeParams
|
|
1762
|
+
};
|
|
1763
|
+
}
|
|
1764
|
+
if (id === "recent-entries") {
|
|
1765
|
+
return {
|
|
1766
|
+
service: "dashboard",
|
|
1767
|
+
operation: "list",
|
|
1768
|
+
method: "getDashboardRecentEntries",
|
|
1769
|
+
routeParams
|
|
1770
|
+
};
|
|
1771
|
+
}
|
|
1772
|
+
if (id === "activity") {
|
|
1773
|
+
return {
|
|
1774
|
+
service: "dashboard",
|
|
1775
|
+
operation: "list",
|
|
1776
|
+
method: "getDashboardActivity",
|
|
1777
|
+
routeParams
|
|
1778
|
+
};
|
|
1779
|
+
}
|
|
1780
|
+
return null;
|
|
1781
|
+
}
|
|
1782
|
+
function parseSchemaRoutes(id, httpMethod, routeParams) {
|
|
1783
|
+
if (httpMethod !== "GET") return null;
|
|
1784
|
+
if (id === "journal") {
|
|
1785
|
+
return {
|
|
1786
|
+
service: "schema",
|
|
1787
|
+
operation: "list",
|
|
1788
|
+
method: "getSchemaJournal",
|
|
1789
|
+
routeParams
|
|
1790
|
+
};
|
|
1791
|
+
}
|
|
1792
|
+
return null;
|
|
1793
|
+
}
|
|
1794
|
+
function parseEmailRoutes(id, httpMethod, routeParams) {
|
|
1795
|
+
if (id === "send" && httpMethod === "POST") {
|
|
1796
|
+
return {
|
|
1797
|
+
service: "email",
|
|
1798
|
+
operation: "create",
|
|
1799
|
+
method: "send",
|
|
1800
|
+
routeParams
|
|
1801
|
+
};
|
|
1802
|
+
}
|
|
1803
|
+
if (id === "send-with-template" && httpMethod === "POST") {
|
|
1804
|
+
return {
|
|
1805
|
+
service: "email",
|
|
1806
|
+
operation: "create",
|
|
1807
|
+
method: "sendWithTemplate",
|
|
1808
|
+
routeParams
|
|
1809
|
+
};
|
|
1810
|
+
}
|
|
1811
|
+
return null;
|
|
1812
|
+
}
|
|
1813
|
+
function parseRestRoute(params, httpMethod, searchParams) {
|
|
1814
|
+
if (params.length === 0) return {};
|
|
1815
|
+
const [resource, id, subresource, subId, ...additionalParams] = params;
|
|
1816
|
+
const routeParams = {};
|
|
1817
|
+
if (searchParams) {
|
|
1818
|
+
let hasBracketWhere = false;
|
|
1819
|
+
for (const [key, value] of searchParams.entries()) {
|
|
1820
|
+
if (key.startsWith("where[")) {
|
|
1821
|
+
hasBracketWhere = true;
|
|
1822
|
+
} else {
|
|
1823
|
+
routeParams[key] = value;
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
if (hasBracketWhere && !routeParams.where) {
|
|
1827
|
+
const parsed = parseWhereQuery(searchParams);
|
|
1828
|
+
if (parsed) {
|
|
1829
|
+
routeParams.where = JSON.stringify(parsed);
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
if (resource === "me") {
|
|
1834
|
+
const result = parseMeRoutes(httpMethod, id, routeParams);
|
|
1835
|
+
if (result) return result;
|
|
1836
|
+
}
|
|
1837
|
+
if (resource === "users") {
|
|
1838
|
+
const result = parseUserRoutes(
|
|
1839
|
+
id,
|
|
1840
|
+
subresource,
|
|
1841
|
+
subId,
|
|
1842
|
+
additionalParams,
|
|
1843
|
+
httpMethod,
|
|
1844
|
+
routeParams
|
|
1845
|
+
);
|
|
1846
|
+
if (result) return result;
|
|
1847
|
+
}
|
|
1848
|
+
if (resource === "roles") {
|
|
1849
|
+
const result = parseRoleRoutes(
|
|
1850
|
+
id,
|
|
1851
|
+
subresource,
|
|
1852
|
+
subId,
|
|
1853
|
+
httpMethod,
|
|
1854
|
+
routeParams
|
|
1855
|
+
);
|
|
1856
|
+
if (result) return result;
|
|
1857
|
+
}
|
|
1858
|
+
if (resource === "collections") {
|
|
1859
|
+
const result = parseCollectionRoutes(
|
|
1860
|
+
id,
|
|
1861
|
+
subresource,
|
|
1862
|
+
subId,
|
|
1863
|
+
httpMethod,
|
|
1864
|
+
routeParams,
|
|
1865
|
+
additionalParams
|
|
1866
|
+
);
|
|
1867
|
+
if (result) return result;
|
|
1868
|
+
}
|
|
1869
|
+
if (resource === "permissions") {
|
|
1870
|
+
const result = parsePermissionRoutes(id, httpMethod, routeParams);
|
|
1871
|
+
if (result) return result;
|
|
1872
|
+
}
|
|
1873
|
+
if (resource === "singles") {
|
|
1874
|
+
const result = parseSingleRoutes(id, subresource, subId, httpMethod, routeParams);
|
|
1875
|
+
if (result) return result;
|
|
1876
|
+
}
|
|
1877
|
+
if (resource === "forms") {
|
|
1878
|
+
const result = parseFormsRoutes(id, subresource, httpMethod, routeParams);
|
|
1879
|
+
if (result) return result;
|
|
1880
|
+
}
|
|
1881
|
+
if (resource === "components") {
|
|
1882
|
+
const result = parseComponentRoutes(id, httpMethod, routeParams, subresource, subId);
|
|
1883
|
+
if (result) return result;
|
|
1884
|
+
}
|
|
1885
|
+
if (resource === "email") {
|
|
1886
|
+
const result = parseEmailRoutes(id, httpMethod, routeParams);
|
|
1887
|
+
if (result) return result;
|
|
1888
|
+
}
|
|
1889
|
+
if (resource === "email-providers") {
|
|
1890
|
+
const result = parseEmailProviderRoutes(
|
|
1891
|
+
id,
|
|
1892
|
+
subresource,
|
|
1893
|
+
httpMethod,
|
|
1894
|
+
routeParams
|
|
1895
|
+
);
|
|
1896
|
+
if (result) return result;
|
|
1897
|
+
}
|
|
1898
|
+
if (resource === "email-templates") {
|
|
1899
|
+
const result = parseEmailTemplateRoutes(
|
|
1900
|
+
id,
|
|
1901
|
+
subresource,
|
|
1902
|
+
httpMethod,
|
|
1903
|
+
routeParams
|
|
1904
|
+
);
|
|
1905
|
+
if (result) return result;
|
|
1906
|
+
}
|
|
1907
|
+
if (resource === "user-fields") {
|
|
1908
|
+
const result = parseUserFieldRoutes(
|
|
1909
|
+
id,
|
|
1910
|
+
subresource,
|
|
1911
|
+
httpMethod,
|
|
1912
|
+
routeParams
|
|
1913
|
+
);
|
|
1914
|
+
if (result) return result;
|
|
1915
|
+
}
|
|
1916
|
+
if (resource === "api-keys") {
|
|
1917
|
+
const result = parseApiKeyRoutes(id, httpMethod, routeParams);
|
|
1918
|
+
if (result) return result;
|
|
1919
|
+
}
|
|
1920
|
+
if (resource === "dashboard") {
|
|
1921
|
+
const result = parseDashboardRoutes(id, httpMethod, routeParams);
|
|
1922
|
+
if (result) return result;
|
|
1923
|
+
}
|
|
1924
|
+
if (resource === "schema") {
|
|
1925
|
+
const result = parseSchemaRoutes(id, httpMethod, routeParams);
|
|
1926
|
+
if (result) return result;
|
|
1927
|
+
}
|
|
1928
|
+
if (resource === "general-settings") {
|
|
1929
|
+
const method = httpMethod === "GET" ? "getGeneralSettings" : "updateGeneralSettings";
|
|
1930
|
+
const operation = httpMethod === "GET" ? "single" : "update";
|
|
1931
|
+
return { service: "generalSettings", operation, method, routeParams };
|
|
1932
|
+
}
|
|
1933
|
+
if (resource === "image-sizes") {
|
|
1934
|
+
if (id === "regeneration-status") {
|
|
1935
|
+
return {
|
|
1936
|
+
service: "imageSizes",
|
|
1937
|
+
operation: "single",
|
|
1938
|
+
method: "regenerationStatus",
|
|
1939
|
+
routeParams
|
|
1940
|
+
};
|
|
1941
|
+
}
|
|
1942
|
+
if (id === "regenerate") {
|
|
1943
|
+
return {
|
|
1944
|
+
service: "imageSizes",
|
|
1945
|
+
operation: "single",
|
|
1946
|
+
method: "regenerate",
|
|
1947
|
+
routeParams
|
|
1948
|
+
};
|
|
1949
|
+
}
|
|
1950
|
+
if (id) routeParams.imageId = id;
|
|
1951
|
+
return {
|
|
1952
|
+
service: "imageSizes",
|
|
1953
|
+
operation: "list",
|
|
1954
|
+
method: "imageSizes",
|
|
1955
|
+
routeParams
|
|
1956
|
+
};
|
|
1957
|
+
}
|
|
1958
|
+
return {};
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
// src/domains/audit/audit-log-writer.ts
|
|
1962
|
+
import { randomUUID } from "crypto";
|
|
1963
|
+
function buildAuditLogWriter(getService2) {
|
|
1964
|
+
return {
|
|
1965
|
+
async write(event) {
|
|
1966
|
+
try {
|
|
1967
|
+
const adapter = getService2("adapter");
|
|
1968
|
+
const db = adapter.getDrizzle();
|
|
1969
|
+
const schema = getDialectTables();
|
|
1970
|
+
const table = schema.auditLog;
|
|
1971
|
+
if (!table) {
|
|
1972
|
+
return;
|
|
1973
|
+
}
|
|
1974
|
+
await db.insert(table).values({
|
|
1975
|
+
id: randomUUID(),
|
|
1976
|
+
kind: event.kind,
|
|
1977
|
+
actorUserId: event.actorUserId ?? null,
|
|
1978
|
+
targetUserId: event.targetUserId ?? null,
|
|
1979
|
+
ipAddress: event.ipAddress ?? null,
|
|
1980
|
+
userAgent: event.userAgent ?? null,
|
|
1981
|
+
metadata: event.metadata ?? null,
|
|
1982
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
1983
|
+
});
|
|
1984
|
+
} catch (err) {
|
|
1985
|
+
getNextlyLogger().warn({
|
|
1986
|
+
kind: "audit-log-write-failed",
|
|
1987
|
+
eventKind: event.kind,
|
|
1988
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1989
|
+
});
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
};
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
// src/auth/handlers/deps-bridge.ts
|
|
1996
|
+
function buildAuthRouterDeps(getService2) {
|
|
1997
|
+
return {
|
|
1998
|
+
secret: env.NEXTLY_SECRET || "",
|
|
1999
|
+
isProduction: env.NODE_ENV === "production",
|
|
2000
|
+
accessTokenTTL: 900,
|
|
2001
|
+
// 15 minutes
|
|
2002
|
+
refreshTokenTTL: 7 * 24 * 60 * 60,
|
|
2003
|
+
// 7 days
|
|
2004
|
+
maxLoginAttempts: 5,
|
|
2005
|
+
lockoutDurationSeconds: 15 * 60,
|
|
2006
|
+
// 15 minutes
|
|
2007
|
+
loginStallTimeMs: 500,
|
|
2008
|
+
requireEmailVerification: true,
|
|
2009
|
+
// Spec §13.2: read the host-app's auth.revealRegistrationConflict flag
|
|
2010
|
+
// from the registered NextlyConfig. Defaults to false (silent-success on
|
|
2011
|
+
// email conflict) when config is not yet initialised or the flag is
|
|
2012
|
+
// unset. The schema is already populated by sanitizeConfig.
|
|
2013
|
+
revealRegistrationConflict: readRevealRegistrationConflict(getService2),
|
|
2014
|
+
devAutoLogin: readDevAutoLogin(getService2),
|
|
2015
|
+
allowedOrigins: env.NEXTLY_ALLOWED_ORIGINS_PARSED || [],
|
|
2016
|
+
trustProxy: readTrustProxy(getService2),
|
|
2017
|
+
trustedProxyIps: parseTrustedProxyIpsEnv(process.env.TRUSTED_PROXY_IPS),
|
|
2018
|
+
authRateLimit: readAuthRateLimit(getService2),
|
|
2019
|
+
auditLog: buildAuditLogWriter(getService2),
|
|
2020
|
+
findUserByEmail: async (email) => {
|
|
2021
|
+
try {
|
|
2022
|
+
const adapter = getService2("adapter");
|
|
2023
|
+
const db = adapter.getDrizzle();
|
|
2024
|
+
const schema = getDialectTables();
|
|
2025
|
+
const { eq } = await import("drizzle-orm");
|
|
2026
|
+
const result = await db.select().from(schema.users).where(eq(schema.users.email, email.trim().toLowerCase())).limit(1);
|
|
2027
|
+
return result[0] || null;
|
|
2028
|
+
} catch {
|
|
2029
|
+
return null;
|
|
2030
|
+
}
|
|
2031
|
+
},
|
|
2032
|
+
findUserById: async (userId) => {
|
|
2033
|
+
try {
|
|
2034
|
+
const adapter = getService2("adapter");
|
|
2035
|
+
const db = adapter.getDrizzle();
|
|
2036
|
+
const schema = getDialectTables();
|
|
2037
|
+
const { eq } = await import("drizzle-orm");
|
|
2038
|
+
const result = await db.select().from(schema.users).where(eq(schema.users.id, userId)).limit(1);
|
|
2039
|
+
return result[0] || null;
|
|
2040
|
+
} catch {
|
|
2041
|
+
return null;
|
|
2042
|
+
}
|
|
2043
|
+
},
|
|
2044
|
+
incrementFailedAttempts: async (userId) => {
|
|
2045
|
+
const adapter = getService2("adapter");
|
|
2046
|
+
const db = adapter.getDrizzle();
|
|
2047
|
+
const schema = getDialectTables();
|
|
2048
|
+
const { eq, sql: sql2 } = await import("drizzle-orm");
|
|
2049
|
+
await db.update(schema.users).set({
|
|
2050
|
+
failedLoginAttempts: sql2`${schema.users.failedLoginAttempts} + 1`
|
|
2051
|
+
}).where(eq(schema.users.id, userId));
|
|
2052
|
+
},
|
|
2053
|
+
lockAccount: async (userId, lockedUntil) => {
|
|
2054
|
+
const adapter = getService2("adapter");
|
|
2055
|
+
const db = adapter.getDrizzle();
|
|
2056
|
+
const schema = getDialectTables();
|
|
2057
|
+
const { eq } = await import("drizzle-orm");
|
|
2058
|
+
await db.update(schema.users).set({ lockedUntil, failedLoginAttempts: 0 }).where(eq(schema.users.id, userId));
|
|
2059
|
+
},
|
|
2060
|
+
resetFailedAttempts: async (userId) => {
|
|
2061
|
+
const adapter = getService2("adapter");
|
|
2062
|
+
const db = adapter.getDrizzle();
|
|
2063
|
+
const schema = getDialectTables();
|
|
2064
|
+
const { eq } = await import("drizzle-orm");
|
|
2065
|
+
await db.update(schema.users).set({ failedLoginAttempts: 0, lockedUntil: null }).where(eq(schema.users.id, userId));
|
|
2066
|
+
},
|
|
2067
|
+
fetchRoleIds: async (userId) => {
|
|
2068
|
+
try {
|
|
2069
|
+
const adapter = getService2("adapter");
|
|
2070
|
+
const db = adapter.getDrizzle();
|
|
2071
|
+
const schema = getDialectTables();
|
|
2072
|
+
const { eq, isNull, or, gt: gt2 } = await import("drizzle-orm");
|
|
2073
|
+
const rows = await db.select({ roleId: schema.userRoles.roleId }).from(schema.userRoles).where(
|
|
2074
|
+
eq(schema.userRoles.userId, userId),
|
|
2075
|
+
// Only non-expired roles
|
|
2076
|
+
or(
|
|
2077
|
+
isNull(schema.userRoles.expiresAt),
|
|
2078
|
+
gt2(schema.userRoles.expiresAt, /* @__PURE__ */ new Date())
|
|
2079
|
+
)
|
|
2080
|
+
);
|
|
2081
|
+
return rows.map((r) => r.roleId);
|
|
2082
|
+
} catch {
|
|
2083
|
+
return [];
|
|
2084
|
+
}
|
|
2085
|
+
},
|
|
2086
|
+
fetchCustomFields: async (userId) => {
|
|
2087
|
+
try {
|
|
2088
|
+
const { container: container2 } = await import("./container-ORGFGYSZ.mjs");
|
|
2089
|
+
if (!container2.has("config") || !container2.has("userExtSchemaService")) {
|
|
2090
|
+
return {};
|
|
2091
|
+
}
|
|
2092
|
+
const config = container2.get("config");
|
|
2093
|
+
const userFields = config?.users?.fields;
|
|
2094
|
+
if (!userFields?.length) return {};
|
|
2095
|
+
const userExtSchemaService = getService2("userExtSchemaService");
|
|
2096
|
+
if (!userExtSchemaService?.hasMergedFields()) return {};
|
|
2097
|
+
const extTable = userExtSchemaService.generateRuntimeSchema(userFields);
|
|
2098
|
+
const { eq } = await import("drizzle-orm");
|
|
2099
|
+
const adapter = getService2("adapter");
|
|
2100
|
+
const db = adapter.getDrizzle();
|
|
2101
|
+
const rows = await db.select().from(extTable).where(eq(extTable.user_id, userId)).limit(1);
|
|
2102
|
+
if (!rows[0]) return {};
|
|
2103
|
+
const { id: _id, user_id: _uid, ...customFields } = rows[0];
|
|
2104
|
+
return customFields;
|
|
2105
|
+
} catch {
|
|
2106
|
+
return {};
|
|
2107
|
+
}
|
|
2108
|
+
},
|
|
2109
|
+
storeRefreshToken: async (record) => {
|
|
2110
|
+
const adapter = getService2("adapter");
|
|
2111
|
+
const db = adapter.getDrizzle();
|
|
2112
|
+
const schema = getDialectTables();
|
|
2113
|
+
await db.insert(schema.refreshTokens).values(record);
|
|
2114
|
+
},
|
|
2115
|
+
findRefreshTokenByHash: async (tokenHash) => {
|
|
2116
|
+
const adapter = getService2("adapter");
|
|
2117
|
+
const db = adapter.getDrizzle();
|
|
2118
|
+
const schema = getDialectTables();
|
|
2119
|
+
const { eq } = await import("drizzle-orm");
|
|
2120
|
+
const rows = await db.select().from(schema.refreshTokens).where(eq(schema.refreshTokens.tokenHash, tokenHash)).limit(1);
|
|
2121
|
+
return rows[0] || null;
|
|
2122
|
+
},
|
|
2123
|
+
deleteRefreshToken: async (id) => {
|
|
2124
|
+
const adapter = getService2("adapter");
|
|
2125
|
+
const db = adapter.getDrizzle();
|
|
2126
|
+
const schema = getDialectTables();
|
|
2127
|
+
const { eq } = await import("drizzle-orm");
|
|
2128
|
+
await db.delete(schema.refreshTokens).where(eq(schema.refreshTokens.id, id));
|
|
2129
|
+
},
|
|
2130
|
+
deleteRefreshTokenByHash: async (tokenHash) => {
|
|
2131
|
+
const adapter = getService2("adapter");
|
|
2132
|
+
const db = adapter.getDrizzle();
|
|
2133
|
+
const schema = getDialectTables();
|
|
2134
|
+
const { eq } = await import("drizzle-orm");
|
|
2135
|
+
await db.delete(schema.refreshTokens).where(eq(schema.refreshTokens.tokenHash, tokenHash));
|
|
2136
|
+
},
|
|
2137
|
+
deleteAllRefreshTokensForUser: async (userId) => {
|
|
2138
|
+
const adapter = getService2("adapter");
|
|
2139
|
+
const db = adapter.getDrizzle();
|
|
2140
|
+
const schema = getDialectTables();
|
|
2141
|
+
const { eq } = await import("drizzle-orm");
|
|
2142
|
+
await db.delete(schema.refreshTokens).where(eq(schema.refreshTokens.userId, userId));
|
|
2143
|
+
},
|
|
2144
|
+
getUserCount: async () => {
|
|
2145
|
+
try {
|
|
2146
|
+
const adapter = getService2("adapter");
|
|
2147
|
+
const db = adapter.getDrizzle();
|
|
2148
|
+
const schema = getDialectTables();
|
|
2149
|
+
const { count } = await import("drizzle-orm");
|
|
2150
|
+
const result = await db.select({ count: count() }).from(schema.users);
|
|
2151
|
+
return Number(result[0]?.count || 0);
|
|
2152
|
+
} catch {
|
|
2153
|
+
return 0;
|
|
2154
|
+
}
|
|
2155
|
+
},
|
|
2156
|
+
createSuperAdmin: async (data) => {
|
|
2157
|
+
const { seedPermissions } = await import("./permissions-3DZZQZMI.mjs");
|
|
2158
|
+
const { seedSuperAdmin } = await import("./super-admin-G5ZK5F4T.mjs");
|
|
2159
|
+
const adapter = getService2("adapter");
|
|
2160
|
+
await seedPermissions(adapter, { silent: true });
|
|
2161
|
+
const result = await seedSuperAdmin(adapter, {
|
|
2162
|
+
email: data.email,
|
|
2163
|
+
password: data.password,
|
|
2164
|
+
name: data.name,
|
|
2165
|
+
silent: true
|
|
2166
|
+
});
|
|
2167
|
+
if (!result.success) {
|
|
2168
|
+
throw new Error(
|
|
2169
|
+
result.errorMessages?.[0] || "Failed to create admin account"
|
|
2170
|
+
);
|
|
2171
|
+
}
|
|
2172
|
+
const db = adapter.getDrizzle();
|
|
2173
|
+
const schema = getDialectTables();
|
|
2174
|
+
const { eq } = await import("drizzle-orm");
|
|
2175
|
+
const users = await db.select({ id: schema.users.id }).from(schema.users).where(eq(schema.users.email, data.email.trim().toLowerCase())).limit(1);
|
|
2176
|
+
return {
|
|
2177
|
+
id: users[0]?.id || "",
|
|
2178
|
+
email: data.email,
|
|
2179
|
+
name: data.name
|
|
2180
|
+
};
|
|
2181
|
+
},
|
|
2182
|
+
seedPermissions: async () => {
|
|
2183
|
+
const { seedPermissions } = await import("./permissions-3DZZQZMI.mjs");
|
|
2184
|
+
const adapter = getService2("adapter");
|
|
2185
|
+
await seedPermissions(adapter, { silent: true });
|
|
2186
|
+
},
|
|
2187
|
+
registerUser: async (data) => {
|
|
2188
|
+
const authService = getService2("authService");
|
|
2189
|
+
const user = await authService.registerUser(data);
|
|
2190
|
+
return { id: user.id, email: user.email, name: user.name };
|
|
2191
|
+
},
|
|
2192
|
+
generatePasswordResetToken: async (email, redirectPath) => {
|
|
2193
|
+
const authService = getService2("authService");
|
|
2194
|
+
const result = await authService.generatePasswordResetToken(email, {
|
|
2195
|
+
redirectPath
|
|
2196
|
+
});
|
|
2197
|
+
return { token: result.token };
|
|
2198
|
+
},
|
|
2199
|
+
resetPasswordWithToken: async (token, newPassword) => {
|
|
2200
|
+
const authService = getService2("authService");
|
|
2201
|
+
const result = await authService.resetPasswordWithToken(
|
|
2202
|
+
token,
|
|
2203
|
+
newPassword
|
|
2204
|
+
);
|
|
2205
|
+
return { email: result.email };
|
|
2206
|
+
},
|
|
2207
|
+
changePassword: async (userId, currentPassword, newPassword) => {
|
|
2208
|
+
const authService = getService2("authService");
|
|
2209
|
+
try {
|
|
2210
|
+
await authService.changePassword(userId, currentPassword, newPassword);
|
|
2211
|
+
return { success: true };
|
|
2212
|
+
} catch (err) {
|
|
2213
|
+
return {
|
|
2214
|
+
success: false,
|
|
2215
|
+
error: NextlyError.is(err) ? err.publicMessage : "Failed to change password"
|
|
2216
|
+
};
|
|
2217
|
+
}
|
|
2218
|
+
},
|
|
2219
|
+
verifyEmail: async (token) => {
|
|
2220
|
+
const authService = getService2("authService");
|
|
2221
|
+
try {
|
|
2222
|
+
const result = await authService.verifyEmail(token);
|
|
2223
|
+
return {
|
|
2224
|
+
success: true,
|
|
2225
|
+
email: result.email
|
|
2226
|
+
};
|
|
2227
|
+
} catch (err) {
|
|
2228
|
+
return {
|
|
2229
|
+
success: false,
|
|
2230
|
+
error: NextlyError.is(err) ? err.publicMessage : "Failed to verify email"
|
|
2231
|
+
};
|
|
2232
|
+
}
|
|
2233
|
+
},
|
|
2234
|
+
resendVerificationEmail: async (email) => {
|
|
2235
|
+
try {
|
|
2236
|
+
const authService = getService2("authService");
|
|
2237
|
+
await authService.generateEmailVerificationToken(email);
|
|
2238
|
+
return { success: true };
|
|
2239
|
+
} catch {
|
|
2240
|
+
return { success: true };
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
};
|
|
2244
|
+
}
|
|
2245
|
+
function readRevealRegistrationConflict(getService2) {
|
|
2246
|
+
try {
|
|
2247
|
+
const config = getService2("config");
|
|
2248
|
+
if (config && typeof config === "object" && "auth" in config) {
|
|
2249
|
+
const auth = config.auth;
|
|
2250
|
+
if (auth && typeof auth === "object" && "revealRegistrationConflict" in auth) {
|
|
2251
|
+
return auth.revealRegistrationConflict === true;
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
return false;
|
|
2255
|
+
} catch {
|
|
2256
|
+
return false;
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
function readDevAutoLogin(getService2) {
|
|
2260
|
+
try {
|
|
2261
|
+
const config = getService2("config");
|
|
2262
|
+
if (config && typeof config === "object" && "admin" in config) {
|
|
2263
|
+
const admin = config.admin;
|
|
2264
|
+
if (admin && typeof admin === "object" && "devAutoLogin" in admin) {
|
|
2265
|
+
const dal = admin.devAutoLogin;
|
|
2266
|
+
if (dal && typeof dal === "object" && "email" in dal && typeof dal.email === "string") {
|
|
2267
|
+
const typed = dal;
|
|
2268
|
+
return {
|
|
2269
|
+
email: typed.email,
|
|
2270
|
+
password: typed.password
|
|
2271
|
+
};
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
return false;
|
|
2276
|
+
} catch {
|
|
2277
|
+
return false;
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
function readTrustProxy(getService2) {
|
|
2281
|
+
try {
|
|
2282
|
+
const config = getService2("config");
|
|
2283
|
+
if (config && typeof config === "object" && "security" in config) {
|
|
2284
|
+
const security = config.security;
|
|
2285
|
+
if (security && typeof security === "object" && "trustProxy" in security) {
|
|
2286
|
+
return security.trustProxy === true;
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
return false;
|
|
2290
|
+
} catch {
|
|
2291
|
+
return false;
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
function readAuthRateLimit(getService2) {
|
|
2295
|
+
const fallback = { requestsPerHour: 30, windowMs: 36e5 };
|
|
2296
|
+
try {
|
|
2297
|
+
const config = getService2("config");
|
|
2298
|
+
if (config && typeof config === "object" && "security" in config) {
|
|
2299
|
+
const security = config.security;
|
|
2300
|
+
if (security && typeof security === "object" && "authRateLimit" in security) {
|
|
2301
|
+
const arl = security.authRateLimit;
|
|
2302
|
+
return {
|
|
2303
|
+
requestsPerHour: typeof arl?.requestsPerHour === "number" ? arl.requestsPerHour : fallback.requestsPerHour,
|
|
2304
|
+
windowMs: typeof arl?.windowMs === "number" ? arl.windowMs : fallback.windowMs
|
|
2305
|
+
};
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
return fallback;
|
|
2309
|
+
} catch {
|
|
2310
|
+
return fallback;
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
// src/route-handler/auth-handler.ts
|
|
2315
|
+
var _dispatcher = null;
|
|
2316
|
+
var _storedConfig = null;
|
|
2317
|
+
function setHandlerConfig(config) {
|
|
2318
|
+
_storedConfig = config;
|
|
2319
|
+
}
|
|
2320
|
+
function getHandlerConfig() {
|
|
2321
|
+
return _storedConfig;
|
|
2322
|
+
}
|
|
2323
|
+
async function ensureServicesInitialized() {
|
|
2324
|
+
if (!isServicesRegistered()) {
|
|
2325
|
+
const nextlyConfig = _storedConfig;
|
|
2326
|
+
const serviceConfig = {
|
|
2327
|
+
imageProcessor: getImageProcessor()
|
|
2328
|
+
};
|
|
2329
|
+
if (nextlyConfig) {
|
|
2330
|
+
if (nextlyConfig.plugins) serviceConfig.plugins = nextlyConfig.plugins;
|
|
2331
|
+
if (nextlyConfig.collections)
|
|
2332
|
+
serviceConfig.collections = nextlyConfig.collections;
|
|
2333
|
+
if (nextlyConfig.storage)
|
|
2334
|
+
serviceConfig.storagePlugins = nextlyConfig.storage;
|
|
2335
|
+
if (nextlyConfig.email) serviceConfig.email = nextlyConfig.email;
|
|
2336
|
+
if (nextlyConfig.users) serviceConfig.users = nextlyConfig.users;
|
|
2337
|
+
if (nextlyConfig.admin) serviceConfig.admin = nextlyConfig.admin;
|
|
2338
|
+
if (nextlyConfig.auth) serviceConfig.auth = nextlyConfig.auth;
|
|
2339
|
+
if (nextlyConfig.security)
|
|
2340
|
+
serviceConfig.security = nextlyConfig.security;
|
|
2341
|
+
if (nextlyConfig.apiKeys)
|
|
2342
|
+
serviceConfig.apiKeys = nextlyConfig.apiKeys;
|
|
2343
|
+
if (nextlyConfig.db) {
|
|
2344
|
+
const dbConfig = nextlyConfig.db;
|
|
2345
|
+
if (dbConfig.schemasDir) serviceConfig.schemasDir = dbConfig.schemasDir;
|
|
2346
|
+
if (dbConfig.migrationsDir)
|
|
2347
|
+
serviceConfig.migrationsDir = dbConfig.migrationsDir;
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
await registerServices(serviceConfig);
|
|
2351
|
+
try {
|
|
2352
|
+
const emailTemplateService = getService("emailTemplateService");
|
|
2353
|
+
await emailTemplateService.ensureBuiltInTemplates();
|
|
2354
|
+
} catch {
|
|
2355
|
+
}
|
|
2356
|
+
try {
|
|
2357
|
+
const permissionSeedService = getService("permissionSeedService");
|
|
2358
|
+
const systemResult = await permissionSeedService.seedSystemPermissions();
|
|
2359
|
+
const collectionResult = await permissionSeedService.seedAllCollectionPermissions();
|
|
2360
|
+
const singleResult = await permissionSeedService.seedAllSinglePermissions();
|
|
2361
|
+
const allNewIds = [
|
|
2362
|
+
...systemResult.newPermissionIds,
|
|
2363
|
+
...collectionResult.newPermissionIds,
|
|
2364
|
+
...singleResult.newPermissionIds
|
|
2365
|
+
];
|
|
2366
|
+
if (allNewIds.length > 0) {
|
|
2367
|
+
await permissionSeedService.assignNewPermissionsToSuperAdmin(allNewIds);
|
|
2368
|
+
}
|
|
2369
|
+
} catch {
|
|
2370
|
+
}
|
|
2371
|
+
try {
|
|
2372
|
+
const userExtSchemaService = getService("userExtSchemaService");
|
|
2373
|
+
await userExtSchemaService.loadMergedFields();
|
|
2374
|
+
if (userExtSchemaService.hasMergedFields()) {
|
|
2375
|
+
const adapter = getService("adapter");
|
|
2376
|
+
const drizzleDb = adapter.getDrizzle();
|
|
2377
|
+
await userExtSchemaService.ensureUserExtSchema(drizzleDb);
|
|
2378
|
+
}
|
|
2379
|
+
} catch (err) {
|
|
2380
|
+
console.error(
|
|
2381
|
+
"[Auth Handler] Error during user_ext schema setup:",
|
|
2382
|
+
err instanceof Error ? err.message : String(err)
|
|
2383
|
+
);
|
|
2384
|
+
}
|
|
2385
|
+
if (nextlyConfig) {
|
|
2386
|
+
const pluginCount = Array.isArray(nextlyConfig.plugins) ? nextlyConfig.plugins.length : 0;
|
|
2387
|
+
const collectionCount = Array.isArray(nextlyConfig.collections) ? nextlyConfig.collections.length : 0;
|
|
2388
|
+
const storageCount = Array.isArray(nextlyConfig.storage) ? nextlyConfig.storage.length : 0;
|
|
2389
|
+
console.log(
|
|
2390
|
+
`[Auth Handler] Services auto-initialized with config (${pluginCount} plugin(s), ${collectionCount} collection(s), ${storageCount} storage adapter(s))`
|
|
2391
|
+
);
|
|
2392
|
+
} else {
|
|
2393
|
+
console.log("[Auth Handler] Services auto-initialized with defaults");
|
|
2394
|
+
}
|
|
2395
|
+
const { runBootTimeApplyIfDev } = await import("./boot-apply-PQSYLDIN.mjs");
|
|
2396
|
+
await runBootTimeApplyIfDev({ caller: "auth-handler" });
|
|
2397
|
+
ensureHmrListener();
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
async function getDispatcherInstance() {
|
|
2401
|
+
await ensureServicesInitialized();
|
|
2402
|
+
if (!_dispatcher) {
|
|
2403
|
+
_dispatcher = new ServiceDispatcher();
|
|
2404
|
+
}
|
|
2405
|
+
return _dispatcher;
|
|
2406
|
+
}
|
|
2407
|
+
async function handleAuthRequest(req, params, _httpMethod) {
|
|
2408
|
+
await ensureServicesInitialized();
|
|
2409
|
+
const authPath = params.slice(1).join("/");
|
|
2410
|
+
const deps = buildAuthRouterDeps(getService);
|
|
2411
|
+
const response = await routeAuthRequest(req, authPath, deps);
|
|
2412
|
+
if (response) {
|
|
2413
|
+
return response;
|
|
2414
|
+
}
|
|
2415
|
+
return new Response(
|
|
2416
|
+
JSON.stringify({
|
|
2417
|
+
error: { code: "NOT_FOUND", message: "Auth endpoint not found" }
|
|
2418
|
+
}),
|
|
2419
|
+
{ status: 404, headers: { "Content-Type": "application/json" } }
|
|
2420
|
+
);
|
|
2421
|
+
}
|
|
2422
|
+
async function getDispatcher() {
|
|
2423
|
+
return getDispatcherInstance();
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2426
|
+
// src/routeHandler.ts
|
|
2427
|
+
var globalSchemaVersion = 0;
|
|
2428
|
+
function bumpSchemaVersion() {
|
|
2429
|
+
globalSchemaVersion++;
|
|
2430
|
+
return globalSchemaVersion;
|
|
2431
|
+
}
|
|
2432
|
+
function getSchemaVersionHeader() {
|
|
2433
|
+
return globalSchemaVersion;
|
|
2434
|
+
}
|
|
2435
|
+
async function applyGlobalDateFormatting(response, req) {
|
|
2436
|
+
const contentType = response.headers.get("content-type") || "";
|
|
2437
|
+
if (!contentType.toLowerCase().includes("application/json")) {
|
|
2438
|
+
return response;
|
|
2439
|
+
}
|
|
2440
|
+
if (req) {
|
|
2441
|
+
const url = new URL(req.url);
|
|
2442
|
+
if (url.pathname.includes("/auth/")) {
|
|
2443
|
+
return response;
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
return withTimezoneFormatting(response);
|
|
2447
|
+
}
|
|
2448
|
+
async function guardSuperAdminRoleAssignment(requestingUserId, method, body) {
|
|
2449
|
+
let roleIdsToCheck = [];
|
|
2450
|
+
if (method === "createLocalUser" || method === "updateUser") {
|
|
2451
|
+
const b = body;
|
|
2452
|
+
if (Array.isArray(b?.roles) && b.roles.length > 0) {
|
|
2453
|
+
roleIdsToCheck = b.roles;
|
|
2454
|
+
}
|
|
2455
|
+
} else if (method === "assignRoleToUser") {
|
|
2456
|
+
const b = body;
|
|
2457
|
+
const roleId = b?.roleId;
|
|
2458
|
+
if (roleId) roleIdsToCheck = [roleId];
|
|
2459
|
+
}
|
|
2460
|
+
if (roleIdsToCheck.length === 0) return null;
|
|
2461
|
+
const hasSuperAdmin = await containsSuperAdminRole(roleIdsToCheck);
|
|
2462
|
+
if (!hasSuperAdmin) return null;
|
|
2463
|
+
const callerIsSuperAdmin = await isSuperAdmin(requestingUserId);
|
|
2464
|
+
if (callerIsSuperAdmin) return null;
|
|
2465
|
+
return new Response(
|
|
2466
|
+
JSON.stringify({
|
|
2467
|
+
error: "Only super-admins can assign the super_admin role"
|
|
2468
|
+
}),
|
|
2469
|
+
{ status: 403, headers: { "Content-Type": "application/json" } }
|
|
2470
|
+
);
|
|
2471
|
+
}
|
|
2472
|
+
async function guardLastSuperAdminRemoval(targetUserId, method, body) {
|
|
2473
|
+
if (method !== "updateUser") return null;
|
|
2474
|
+
const b = body;
|
|
2475
|
+
if (!Array.isArray(b?.roles)) return null;
|
|
2476
|
+
const newRoles = b.roles;
|
|
2477
|
+
const newRolesContainSuperAdmin = await containsSuperAdminRole(newRoles);
|
|
2478
|
+
if (newRolesContainSuperAdmin) return null;
|
|
2479
|
+
const targetIsSuperAdmin = await isSuperAdmin(targetUserId);
|
|
2480
|
+
if (!targetIsSuperAdmin) return null;
|
|
2481
|
+
const othersExist = await hasSuperAdminExcluding(targetUserId);
|
|
2482
|
+
if (othersExist) return null;
|
|
2483
|
+
return new Response(
|
|
2484
|
+
JSON.stringify({
|
|
2485
|
+
error: "Cannot remove the super_admin role: this user is the last super-admin. Assign the super_admin role to another user first."
|
|
2486
|
+
}),
|
|
2487
|
+
{ status: 400, headers: { "Content-Type": "application/json" } }
|
|
2488
|
+
);
|
|
2489
|
+
}
|
|
2490
|
+
async function handleApiKeyRequest(req, method, routeParams) {
|
|
2491
|
+
const id = routeParams?.apiKeyId ?? "";
|
|
2492
|
+
switch (method) {
|
|
2493
|
+
case "listApiKeys":
|
|
2494
|
+
return listApiKeys(req);
|
|
2495
|
+
case "getApiKeyById":
|
|
2496
|
+
return getApiKeyById(req, id);
|
|
2497
|
+
case "createApiKey":
|
|
2498
|
+
return createApiKey(req);
|
|
2499
|
+
case "updateApiKey":
|
|
2500
|
+
return updateApiKey(req, id);
|
|
2501
|
+
case "revokeApiKey":
|
|
2502
|
+
return revokeApiKey(req, id);
|
|
2503
|
+
default:
|
|
2504
|
+
return new Response(
|
|
2505
|
+
JSON.stringify({ error: "Unknown API key operation" }),
|
|
2506
|
+
{ status: 400, headers: { "Content-Type": "application/json" } }
|
|
2507
|
+
);
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
async function handleGeneralSettingsRequest(req, httpMethod) {
|
|
2511
|
+
switch (httpMethod) {
|
|
2512
|
+
case "GET":
|
|
2513
|
+
return getGeneralSettings(req);
|
|
2514
|
+
case "PATCH": {
|
|
2515
|
+
return updateGeneralSettings(req);
|
|
2516
|
+
}
|
|
2517
|
+
default:
|
|
2518
|
+
return new Response(JSON.stringify({ error: "Method not allowed" }), {
|
|
2519
|
+
status: 405,
|
|
2520
|
+
headers: { "Content-Type": "application/json" }
|
|
2521
|
+
});
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
async function handleImageSizesRequest(req, httpMethod, imageId) {
|
|
2525
|
+
switch (httpMethod) {
|
|
2526
|
+
case "GET":
|
|
2527
|
+
return imageId ? getImageSizeById(req, imageId) : listImageSizes(req);
|
|
2528
|
+
case "POST":
|
|
2529
|
+
return createImageSize(req);
|
|
2530
|
+
case "PATCH":
|
|
2531
|
+
if (!imageId)
|
|
2532
|
+
return new Response(JSON.stringify({ error: "ID required" }), {
|
|
2533
|
+
status: 400
|
|
2534
|
+
});
|
|
2535
|
+
return updateImageSize(req, imageId);
|
|
2536
|
+
case "DELETE":
|
|
2537
|
+
if (!imageId)
|
|
2538
|
+
return new Response(JSON.stringify({ error: "ID required" }), {
|
|
2539
|
+
status: 400
|
|
2540
|
+
});
|
|
2541
|
+
return deleteImageSize(req, imageId);
|
|
2542
|
+
default:
|
|
2543
|
+
return new Response(JSON.stringify({ error: "Method not allowed" }), {
|
|
2544
|
+
status: 405,
|
|
2545
|
+
headers: { "Content-Type": "application/json" }
|
|
2546
|
+
});
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
async function handleDashboardRequest(req, method) {
|
|
2550
|
+
switch (method) {
|
|
2551
|
+
case "getDashboardStats":
|
|
2552
|
+
return getDashboardStats(req);
|
|
2553
|
+
case "getDashboardRecentEntries":
|
|
2554
|
+
return getDashboardRecentEntries(req);
|
|
2555
|
+
case "getDashboardActivity":
|
|
2556
|
+
return getDashboardActivity(req);
|
|
2557
|
+
default:
|
|
2558
|
+
return new Response(
|
|
2559
|
+
JSON.stringify({ error: "Unknown dashboard operation" }),
|
|
2560
|
+
{ status: 400, headers: { "Content-Type": "application/json" } }
|
|
2561
|
+
);
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
async function handleSchemaRequest(req, method) {
|
|
2565
|
+
switch (method) {
|
|
2566
|
+
case "getSchemaJournal":
|
|
2567
|
+
return getSchemaJournal(req);
|
|
2568
|
+
default:
|
|
2569
|
+
return new Response(
|
|
2570
|
+
JSON.stringify({ error: "Unknown schema operation" }),
|
|
2571
|
+
{ status: 400, headers: { "Content-Type": "application/json" } }
|
|
2572
|
+
);
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
async function handleEmailRequest(req, method) {
|
|
2576
|
+
switch (method) {
|
|
2577
|
+
case "send":
|
|
2578
|
+
return POST(req);
|
|
2579
|
+
case "sendWithTemplate":
|
|
2580
|
+
return POST2(req);
|
|
2581
|
+
default:
|
|
2582
|
+
return new Response(
|
|
2583
|
+
JSON.stringify({ error: "Unknown email operation" }),
|
|
2584
|
+
{ status: 400, headers: { "Content-Type": "application/json" } }
|
|
2585
|
+
);
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
var COLLECTION_ENTRY_METHODS = /* @__PURE__ */ new Set([
|
|
2589
|
+
"listEntries",
|
|
2590
|
+
"createEntry",
|
|
2591
|
+
"getEntry",
|
|
2592
|
+
"updateEntry",
|
|
2593
|
+
"deleteEntry",
|
|
2594
|
+
"bulkDeleteEntries",
|
|
2595
|
+
"bulkUpdateEntries",
|
|
2596
|
+
"bulkUpdateByQuery",
|
|
2597
|
+
"countEntries",
|
|
2598
|
+
"duplicateEntry"
|
|
2599
|
+
]);
|
|
2600
|
+
var SINGLE_DOCUMENT_METHODS = /* @__PURE__ */ new Set([
|
|
2601
|
+
"getSingleDocument",
|
|
2602
|
+
"updateSingleDocument"
|
|
2603
|
+
]);
|
|
2604
|
+
async function resolveAuthorization(req, service, method, operation, routeParams, httpMethod) {
|
|
2605
|
+
if (service === "collections") {
|
|
2606
|
+
if (COLLECTION_ENTRY_METHODS.has(method)) {
|
|
2607
|
+
const action2 = getActionFromOperation(operation);
|
|
2608
|
+
const slug = routeParams?.collectionName || "";
|
|
2609
|
+
return requireCollectionAccess(req, action2, slug);
|
|
2610
|
+
}
|
|
2611
|
+
if (method === "getCollection") {
|
|
2612
|
+
const slug = routeParams?.collectionName || "";
|
|
2613
|
+
return requireCollectionAccess(req, "read", slug);
|
|
2614
|
+
}
|
|
2615
|
+
return requirePermission(req, "manage", "settings");
|
|
2616
|
+
}
|
|
2617
|
+
if (service === "singles") {
|
|
2618
|
+
if (SINGLE_DOCUMENT_METHODS.has(method)) {
|
|
2619
|
+
const action2 = method === "getSingleDocument" ? "read" : "update";
|
|
2620
|
+
const slug = routeParams?.slug || "";
|
|
2621
|
+
return requireCollectionAccess(req, action2, slug);
|
|
2622
|
+
}
|
|
2623
|
+
if (method === "getSingleSchema") {
|
|
2624
|
+
const slug = routeParams?.slug || "";
|
|
2625
|
+
return requireCollectionAccess(req, "read", slug);
|
|
2626
|
+
}
|
|
2627
|
+
return requirePermission(req, "manage", "settings");
|
|
2628
|
+
}
|
|
2629
|
+
if (service === "emailProviders") {
|
|
2630
|
+
const action2 = getActionFromMethod(httpMethod);
|
|
2631
|
+
return requireAnyPermission(req, [
|
|
2632
|
+
{ action: action2, resource: "email-providers" },
|
|
2633
|
+
{ action: "manage", resource: "email-providers" }
|
|
2634
|
+
]);
|
|
2635
|
+
}
|
|
2636
|
+
if (service === "emailTemplates") {
|
|
2637
|
+
const action2 = getActionFromMethod(httpMethod);
|
|
2638
|
+
return requireAnyPermission(req, [
|
|
2639
|
+
{ action: action2, resource: "email-templates" },
|
|
2640
|
+
{ action: "manage", resource: "email-templates" }
|
|
2641
|
+
]);
|
|
2642
|
+
}
|
|
2643
|
+
if (service === "userFields") {
|
|
2644
|
+
const action2 = getActionFromMethod(httpMethod);
|
|
2645
|
+
return requireAnyPermission(req, [
|
|
2646
|
+
{ action: action2, resource: "settings" },
|
|
2647
|
+
{ action: "manage", resource: "settings" }
|
|
2648
|
+
]);
|
|
2649
|
+
}
|
|
2650
|
+
if (service === "components") {
|
|
2651
|
+
const action2 = getActionFromMethod(httpMethod);
|
|
2652
|
+
return requireAnyPermission(req, [
|
|
2653
|
+
{ action: action2, resource: "settings" },
|
|
2654
|
+
{ action: "manage", resource: "settings" }
|
|
2655
|
+
]);
|
|
2656
|
+
}
|
|
2657
|
+
if (service === "rbac") {
|
|
2658
|
+
if (method === "listRolePermissions") {
|
|
2659
|
+
return requireAnyPermission(req, [
|
|
2660
|
+
{ action: "read", resource: "roles" },
|
|
2661
|
+
{ action: "read", resource: "permissions" }
|
|
2662
|
+
]);
|
|
2663
|
+
}
|
|
2664
|
+
if (method === "listPermissions") {
|
|
2665
|
+
return requireAnyPermission(req, [{ action: "read", resource: "roles" }]);
|
|
2666
|
+
}
|
|
2667
|
+
if (method === "addPermissionToRole" || method === "removePermissionFromRole") {
|
|
2668
|
+
return requirePermission(req, "update", "roles");
|
|
2669
|
+
}
|
|
2670
|
+
if (method === "addRoleInheritance" || method === "removeRoleInheritance") {
|
|
2671
|
+
return requirePermission(req, "update", "roles");
|
|
2672
|
+
}
|
|
2673
|
+
if (method === "assignRoleToUser" || method === "unassignRoleFromUser") {
|
|
2674
|
+
return requirePermission(req, "update", "users");
|
|
2675
|
+
}
|
|
2676
|
+
if (method === "getPermissionById") {
|
|
2677
|
+
return requireAnyPermission(req, [
|
|
2678
|
+
{ action: "read", resource: "roles" },
|
|
2679
|
+
{ action: "read", resource: "permissions" }
|
|
2680
|
+
]);
|
|
2681
|
+
}
|
|
2682
|
+
if (method === "ensurePermission" || method === "updatePermission" || method === "deletePermissionById") {
|
|
2683
|
+
const action3 = getActionFromMethod(httpMethod);
|
|
2684
|
+
return requireAnyPermission(req, [
|
|
2685
|
+
{ action: action3, resource: "permissions" },
|
|
2686
|
+
{ action: "manage", resource: "permissions" }
|
|
2687
|
+
]);
|
|
2688
|
+
}
|
|
2689
|
+
const action2 = getActionFromMethod(httpMethod);
|
|
2690
|
+
return requirePermission(req, action2, "roles");
|
|
2691
|
+
}
|
|
2692
|
+
const action = getActionFromMethod(httpMethod);
|
|
2693
|
+
return requirePermission(req, action, service);
|
|
2694
|
+
}
|
|
2695
|
+
async function handleServiceRequest(req, params, httpMethod) {
|
|
2696
|
+
const url = new URL(req.url);
|
|
2697
|
+
const searchParams = url.searchParams;
|
|
2698
|
+
const { service, operation, method, routeParams } = parseRestRoute(
|
|
2699
|
+
params,
|
|
2700
|
+
httpMethod,
|
|
2701
|
+
searchParams
|
|
2702
|
+
);
|
|
2703
|
+
if (!service || !operation || !method) {
|
|
2704
|
+
return new Response(
|
|
2705
|
+
JSON.stringify({
|
|
2706
|
+
error: "Invalid REST route format. Check supported endpoints: /api/users, /api/roles, /api/permissions"
|
|
2707
|
+
}),
|
|
2708
|
+
{
|
|
2709
|
+
status: 400,
|
|
2710
|
+
headers: { "Content-Type": "application/json" }
|
|
2711
|
+
}
|
|
2712
|
+
);
|
|
2713
|
+
}
|
|
2714
|
+
if (service === "apiKeys") {
|
|
2715
|
+
return handleApiKeyRequest(req, method, routeParams);
|
|
2716
|
+
}
|
|
2717
|
+
if (service === "generalSettings") {
|
|
2718
|
+
return handleGeneralSettingsRequest(req, httpMethod);
|
|
2719
|
+
}
|
|
2720
|
+
if (service === "imageSizes") {
|
|
2721
|
+
if (method === "regenerationStatus" || method === "regenerate") {
|
|
2722
|
+
return Response.json(
|
|
2723
|
+
{
|
|
2724
|
+
data: {
|
|
2725
|
+
pending: 0,
|
|
2726
|
+
total: 0,
|
|
2727
|
+
inProgress: false,
|
|
2728
|
+
message: "Batch regeneration coming soon"
|
|
2729
|
+
}
|
|
2730
|
+
},
|
|
2731
|
+
{ status: 200 }
|
|
2732
|
+
);
|
|
2733
|
+
}
|
|
2734
|
+
const imageId = routeParams?.imageId;
|
|
2735
|
+
return handleImageSizesRequest(req, httpMethod, imageId);
|
|
2736
|
+
}
|
|
2737
|
+
if (service === "dashboard") {
|
|
2738
|
+
return handleDashboardRequest(req, method);
|
|
2739
|
+
}
|
|
2740
|
+
if (service === "schema") {
|
|
2741
|
+
return handleSchemaRequest(req, method);
|
|
2742
|
+
}
|
|
2743
|
+
if (service === "email") {
|
|
2744
|
+
return handleEmailRequest(req, method);
|
|
2745
|
+
}
|
|
2746
|
+
const isPublic = isPublicEndpoint(service, method);
|
|
2747
|
+
let authorizedUser;
|
|
2748
|
+
if (!isPublic) {
|
|
2749
|
+
if (params[0] === "me") {
|
|
2750
|
+
const authResult = await requireAuthentication(req);
|
|
2751
|
+
if (isErrorResponse(authResult)) {
|
|
2752
|
+
return createJsonErrorResponse(authResult);
|
|
2753
|
+
}
|
|
2754
|
+
if (routeParams) {
|
|
2755
|
+
routeParams.userId = authResult.userId;
|
|
2756
|
+
}
|
|
2757
|
+
authorizedUser = authResult;
|
|
2758
|
+
} else if (requiresAuthOnly(service, method)) {
|
|
2759
|
+
const authResult = await requireAuthentication(req);
|
|
2760
|
+
if (isErrorResponse(authResult)) {
|
|
2761
|
+
return createJsonErrorResponse(authResult);
|
|
2762
|
+
}
|
|
2763
|
+
authorizedUser = authResult;
|
|
2764
|
+
} else {
|
|
2765
|
+
const authResult = await resolveAuthorization(
|
|
2766
|
+
req,
|
|
2767
|
+
service,
|
|
2768
|
+
method,
|
|
2769
|
+
operation,
|
|
2770
|
+
routeParams,
|
|
2771
|
+
httpMethod
|
|
2772
|
+
);
|
|
2773
|
+
if (isErrorResponse(authResult)) {
|
|
2774
|
+
return createJsonErrorResponse(authResult);
|
|
2775
|
+
}
|
|
2776
|
+
authorizedUser = authResult;
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
let body = void 0;
|
|
2780
|
+
if (httpMethod === "POST" || httpMethod === "PUT" || httpMethod === "PATCH") {
|
|
2781
|
+
try {
|
|
2782
|
+
const text = await req.text();
|
|
2783
|
+
if (text) {
|
|
2784
|
+
body = JSON.parse(text);
|
|
2785
|
+
}
|
|
2786
|
+
} catch {
|
|
2787
|
+
return new Response(
|
|
2788
|
+
JSON.stringify({ error: "Invalid JSON in request body" }),
|
|
2789
|
+
{
|
|
2790
|
+
status: 400,
|
|
2791
|
+
headers: { "Content-Type": "application/json" }
|
|
2792
|
+
}
|
|
2793
|
+
);
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
if (authorizedUser) {
|
|
2797
|
+
const guardResult = await guardSuperAdminRoleAssignment(
|
|
2798
|
+
authorizedUser.userId,
|
|
2799
|
+
method,
|
|
2800
|
+
body
|
|
2801
|
+
);
|
|
2802
|
+
if (guardResult) return guardResult;
|
|
2803
|
+
const targetUserId = routeParams?.userId;
|
|
2804
|
+
if (targetUserId) {
|
|
2805
|
+
const lastSuperAdminGuard = await guardLastSuperAdminRemoval(
|
|
2806
|
+
targetUserId,
|
|
2807
|
+
method,
|
|
2808
|
+
body
|
|
2809
|
+
);
|
|
2810
|
+
if (lastSuperAdminGuard) return lastSuperAdminGuard;
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
if (authorizedUser && routeParams) {
|
|
2814
|
+
routeParams._authenticatedUserId = authorizedUser.userId;
|
|
2815
|
+
if (authorizedUser.userName)
|
|
2816
|
+
routeParams._authenticatedUserName = authorizedUser.userName;
|
|
2817
|
+
if (authorizedUser.userEmail)
|
|
2818
|
+
routeParams._authenticatedUserEmail = authorizedUser.userEmail;
|
|
2819
|
+
}
|
|
2820
|
+
const dispatchRequest = {
|
|
2821
|
+
service,
|
|
2822
|
+
operation,
|
|
2823
|
+
method,
|
|
2824
|
+
params: routeParams,
|
|
2825
|
+
body,
|
|
2826
|
+
userId: authorizedUser?.userId,
|
|
2827
|
+
request: req
|
|
2828
|
+
// Pass request for accessing headers (IP, user-agent, etc.)
|
|
2829
|
+
};
|
|
2830
|
+
const dispatcher = await getDispatcher();
|
|
2831
|
+
const result = await dispatcher.dispatch(dispatchRequest);
|
|
2832
|
+
if (result.status === 204 || result.status === 205 || result.status === 304) {
|
|
2833
|
+
return new Response(null, { status: result.status });
|
|
2834
|
+
}
|
|
2835
|
+
const headers = {
|
|
2836
|
+
"Content-Type": "application/json"
|
|
2837
|
+
};
|
|
2838
|
+
const schemaVersion = getSchemaVersionHeader();
|
|
2839
|
+
if (schemaVersion !== void 0) {
|
|
2840
|
+
headers["X-Nextly-Schema-Version"] = String(schemaVersion);
|
|
2841
|
+
}
|
|
2842
|
+
if (!result.success) {
|
|
2843
|
+
const requestId = readOrGenerateRequestId(req);
|
|
2844
|
+
const nextlyErr = NextlyError.is(result.error) ? result.error : NextlyError.internal();
|
|
2845
|
+
const benignCodes = /* @__PURE__ */ new Set([
|
|
2846
|
+
"NOT_FOUND",
|
|
2847
|
+
"RATE_LIMITED",
|
|
2848
|
+
"AUTH_REQUIRED"
|
|
2849
|
+
]);
|
|
2850
|
+
if (!benignCodes.has(String(nextlyErr.code))) {
|
|
2851
|
+
try {
|
|
2852
|
+
const { getNextlyLogger: getNextlyLogger2 } = await import("./logger-YE4TC7ZN.mjs");
|
|
2853
|
+
getNextlyLogger2().error({
|
|
2854
|
+
kind: "dispatcher-error",
|
|
2855
|
+
...nextlyErr.toLogJSON(requestId),
|
|
2856
|
+
route: new URL(req.url).pathname,
|
|
2857
|
+
method: req.method,
|
|
2858
|
+
service: dispatchRequest.service,
|
|
2859
|
+
operation: dispatchRequest.operation,
|
|
2860
|
+
dispatchMethod: dispatchRequest.method
|
|
2861
|
+
});
|
|
2862
|
+
} catch {
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
const errorHeaders = {
|
|
2866
|
+
"content-type": "application/problem+json",
|
|
2867
|
+
"x-request-id": requestId
|
|
2868
|
+
};
|
|
2869
|
+
if (schemaVersion !== void 0) {
|
|
2870
|
+
errorHeaders["X-Nextly-Schema-Version"] = String(schemaVersion);
|
|
2871
|
+
}
|
|
2872
|
+
if (nextlyErr.code === "RATE_LIMITED") {
|
|
2873
|
+
const data = nextlyErr.publicData;
|
|
2874
|
+
if (data && typeof data === "object" && "retryAfterSeconds" in data && typeof data.retryAfterSeconds === "number") {
|
|
2875
|
+
errorHeaders["retry-after"] = String(
|
|
2876
|
+
data.retryAfterSeconds
|
|
2877
|
+
);
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
return new Response(
|
|
2881
|
+
JSON.stringify({ error: nextlyErr.toResponseJSON(requestId) }),
|
|
2882
|
+
{
|
|
2883
|
+
status: nextlyErr.statusCode,
|
|
2884
|
+
headers: errorHeaders
|
|
2885
|
+
}
|
|
2886
|
+
);
|
|
2887
|
+
}
|
|
2888
|
+
if (result.data instanceof Response) {
|
|
2889
|
+
const response = result.data;
|
|
2890
|
+
if (schemaVersion !== void 0) {
|
|
2891
|
+
response.headers.set("X-Nextly-Schema-Version", String(schemaVersion));
|
|
2892
|
+
}
|
|
2893
|
+
return response;
|
|
2894
|
+
}
|
|
2895
|
+
const successBody = { data: result.data };
|
|
2896
|
+
if (result.meta !== void 0) {
|
|
2897
|
+
successBody.meta = result.meta;
|
|
2898
|
+
}
|
|
2899
|
+
return new Response(JSON.stringify(successBody), {
|
|
2900
|
+
status: result.status,
|
|
2901
|
+
headers
|
|
2902
|
+
});
|
|
2903
|
+
}
|
|
2904
|
+
async function handleAdminMetaRequest() {
|
|
2905
|
+
const config = getHandlerConfig();
|
|
2906
|
+
const branding = config?.admin?.branding;
|
|
2907
|
+
const payload = {};
|
|
2908
|
+
if (branding?.logoUrl) payload.logoUrl = branding.logoUrl;
|
|
2909
|
+
if (branding?.logoUrlLight) payload.logoUrlLight = branding.logoUrlLight;
|
|
2910
|
+
if (branding?.logoUrlDark) payload.logoUrlDark = branding.logoUrlDark;
|
|
2911
|
+
if (branding?.logoText) payload.logoText = branding.logoText;
|
|
2912
|
+
if (branding?.favicon?.trim()) payload.favicon = branding.favicon.trim();
|
|
2913
|
+
const resolvedShowBuilder = typeof branding?.showBuilder === "boolean" ? branding.showBuilder : process.env.NODE_ENV !== "production";
|
|
2914
|
+
payload.showBuilder = resolvedShowBuilder;
|
|
2915
|
+
const colors = branding?.colors;
|
|
2916
|
+
if (colors) {
|
|
2917
|
+
const resolved = {};
|
|
2918
|
+
if (colors.primary && isValidHex(colors.primary)) {
|
|
2919
|
+
resolved.primary = hexToHslTriplet(colors.primary);
|
|
2920
|
+
resolved.primaryForeground = getForegroundForBackground(colors.primary);
|
|
2921
|
+
}
|
|
2922
|
+
if (colors.accent && isValidHex(colors.accent)) {
|
|
2923
|
+
resolved.accent = hexToHslTriplet(colors.accent);
|
|
2924
|
+
resolved.accentForeground = getForegroundForBackground(colors.accent);
|
|
2925
|
+
}
|
|
2926
|
+
if (Object.keys(resolved).length > 0) {
|
|
2927
|
+
payload.colors = resolved;
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2930
|
+
const pluginOverrides = config?.admin?.pluginOverrides;
|
|
2931
|
+
const plugins = (config?.plugins ?? []).map((plugin) => {
|
|
2932
|
+
const pluginSlug = plugin.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
2933
|
+
const hostOverride = pluginOverrides?.[pluginSlug];
|
|
2934
|
+
const effectiveAppearance = hostOverride?.appearance ? { ...plugin.admin?.appearance, ...hostOverride.appearance } : plugin.admin?.appearance;
|
|
2935
|
+
const pluginCollections = plugin.collections ?? [];
|
|
2936
|
+
return {
|
|
2937
|
+
name: plugin.name,
|
|
2938
|
+
version: plugin.version,
|
|
2939
|
+
description: plugin.admin?.description,
|
|
2940
|
+
placement: hostOverride?.placement ?? plugin.admin?.placement ?? "plugins",
|
|
2941
|
+
order: hostOverride?.order ?? plugin.admin?.order,
|
|
2942
|
+
after: hostOverride?.after ?? plugin.admin?.after,
|
|
2943
|
+
appearance: effectiveAppearance,
|
|
2944
|
+
collections: pluginCollections.map((c) => c.slug)
|
|
2945
|
+
};
|
|
2946
|
+
});
|
|
2947
|
+
if (plugins.length > 0) {
|
|
2948
|
+
payload.plugins = plugins;
|
|
2949
|
+
}
|
|
2950
|
+
try {
|
|
2951
|
+
if (container.has("generalSettingsService")) {
|
|
2952
|
+
const svc = container.get(
|
|
2953
|
+
"generalSettingsService"
|
|
2954
|
+
);
|
|
2955
|
+
const settings = await svc.getSettings();
|
|
2956
|
+
if (settings.applicationName) payload.logoText = settings.applicationName;
|
|
2957
|
+
if (settings.logoUrl) payload.logoUrl = settings.logoUrl;
|
|
2958
|
+
const customGroups = svc.getCustomSidebarGroups(settings);
|
|
2959
|
+
console.log(
|
|
2960
|
+
"[ADMIN-META] customGroups from DB:",
|
|
2961
|
+
JSON.stringify(customGroups)
|
|
2962
|
+
);
|
|
2963
|
+
if (customGroups.length > 0) {
|
|
2964
|
+
payload.customGroups = customGroups;
|
|
2965
|
+
}
|
|
2966
|
+
}
|
|
2967
|
+
} catch (err) {
|
|
2968
|
+
console.error("[ADMIN-META] Error fetching settings from DB:", err);
|
|
2969
|
+
}
|
|
2970
|
+
return respondData(payload);
|
|
2971
|
+
}
|
|
2972
|
+
async function handleAdminMetaSidebarGroups(req) {
|
|
2973
|
+
try {
|
|
2974
|
+
const authResult = await requirePermission(req, "manage", "settings");
|
|
2975
|
+
if (isErrorResponse(authResult)) return createJsonErrorResponse(authResult);
|
|
2976
|
+
const text = await req.text();
|
|
2977
|
+
const body = text ? JSON.parse(text) : {};
|
|
2978
|
+
const groups = Array.isArray(body.groups) ? body.groups : [];
|
|
2979
|
+
const validated = [];
|
|
2980
|
+
for (const g of groups) {
|
|
2981
|
+
if (typeof g !== "object" || g === null) continue;
|
|
2982
|
+
const rec = g;
|
|
2983
|
+
if (typeof rec.slug === "string" && typeof rec.name === "string") {
|
|
2984
|
+
validated.push({
|
|
2985
|
+
slug: rec.slug,
|
|
2986
|
+
name: rec.name,
|
|
2987
|
+
...typeof rec.icon === "string" && { icon: rec.icon }
|
|
2988
|
+
});
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
if (!container.has("generalSettingsService")) {
|
|
2992
|
+
return new Response(
|
|
2993
|
+
JSON.stringify({ error: "Settings service not available" }),
|
|
2994
|
+
{ status: 503, headers: { "Content-Type": "application/json" } }
|
|
2995
|
+
);
|
|
2996
|
+
}
|
|
2997
|
+
const svc = container.get("generalSettingsService");
|
|
2998
|
+
const updated = await svc.updateCustomSidebarGroups(validated);
|
|
2999
|
+
return respondMutation("Sidebar groups updated.", updated);
|
|
3000
|
+
} catch (error) {
|
|
3001
|
+
const message = error instanceof Error ? error.message : "Failed to update sidebar groups";
|
|
3002
|
+
return new Response(JSON.stringify({ error: message }), {
|
|
3003
|
+
status: 500,
|
|
3004
|
+
headers: { "Content-Type": "application/json" }
|
|
3005
|
+
});
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
async function handleGet(req, params) {
|
|
3009
|
+
if (params[0] === "admin-meta") {
|
|
3010
|
+
return handleAdminMetaRequest();
|
|
3011
|
+
}
|
|
3012
|
+
if (params[0] === "dev-reload" && process.env.NODE_ENV === "development") {
|
|
3013
|
+
const { subscribeDevReload } = await import("./dev-reload-broadcaster-B73IQ53V.mjs");
|
|
3014
|
+
let unsub;
|
|
3015
|
+
const stream = new ReadableStream({
|
|
3016
|
+
start(ctrl) {
|
|
3017
|
+
unsub = subscribeDevReload(ctrl);
|
|
3018
|
+
ctrl.enqueue(": keepalive\n\n");
|
|
3019
|
+
},
|
|
3020
|
+
cancel() {
|
|
3021
|
+
unsub?.();
|
|
3022
|
+
}
|
|
3023
|
+
});
|
|
3024
|
+
return new Response(stream, {
|
|
3025
|
+
status: 200,
|
|
3026
|
+
headers: {
|
|
3027
|
+
"Content-Type": "text/event-stream",
|
|
3028
|
+
"Cache-Control": "no-cache, no-transform",
|
|
3029
|
+
Connection: "keep-alive",
|
|
3030
|
+
"X-Accel-Buffering": "no"
|
|
3031
|
+
}
|
|
3032
|
+
});
|
|
3033
|
+
}
|
|
3034
|
+
return handleServiceRequest(req, params, "GET");
|
|
3035
|
+
}
|
|
3036
|
+
async function handlePost(req, params) {
|
|
3037
|
+
return handleServiceRequest(req, params, "POST");
|
|
3038
|
+
}
|
|
3039
|
+
async function handlePut(req, params) {
|
|
3040
|
+
return handleServiceRequest(req, params, "PUT");
|
|
3041
|
+
}
|
|
3042
|
+
async function handlePatch(req, params) {
|
|
3043
|
+
if (params[0] === "admin-meta" && params[1] === "sidebar-groups") {
|
|
3044
|
+
return handleAdminMetaSidebarGroups(req);
|
|
3045
|
+
}
|
|
3046
|
+
return handleServiceRequest(req, params, "PATCH");
|
|
3047
|
+
}
|
|
3048
|
+
async function handleDelete(req, params) {
|
|
3049
|
+
return handleServiceRequest(req, params, "DELETE");
|
|
3050
|
+
}
|
|
3051
|
+
function isAuthRoute(params) {
|
|
3052
|
+
return params.length > 0 && params[0] === "auth";
|
|
3053
|
+
}
|
|
3054
|
+
function createDynamicHandlers(options) {
|
|
3055
|
+
if (options?.config) {
|
|
3056
|
+
setHandlerConfig(options.config);
|
|
3057
|
+
}
|
|
3058
|
+
const securityConfig = options?.config?.security;
|
|
3059
|
+
const applySecurityHeaders = createSecurityHeadersMiddleware(
|
|
3060
|
+
securityConfig?.headers
|
|
3061
|
+
);
|
|
3062
|
+
const cors = createCorsMiddleware(securityConfig?.cors);
|
|
3063
|
+
const rateLimitConfig = options?.config?.rateLimit;
|
|
3064
|
+
const trustedProxyIpsFromEnv = (process.env.TRUSTED_PROXY_IPS ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
3065
|
+
const checkRateLimit = createRateLimiter({
|
|
3066
|
+
enabled: true,
|
|
3067
|
+
...rateLimitConfig ?? {},
|
|
3068
|
+
trustProxy: rateLimitConfig?.trustProxy ?? securityConfig?.trustProxy ?? false,
|
|
3069
|
+
trustedProxyIps: rateLimitConfig?.trustedProxyIps ?? trustedProxyIpsFromEnv
|
|
3070
|
+
});
|
|
3071
|
+
async function withSecurity(req, handler) {
|
|
3072
|
+
const preflightResponse = cors.handlePreflight(req);
|
|
3073
|
+
if (preflightResponse) {
|
|
3074
|
+
return applySecurityHeaders(preflightResponse);
|
|
3075
|
+
}
|
|
3076
|
+
const rateLimitResponse = await checkRateLimit(req);
|
|
3077
|
+
if (rateLimitResponse) {
|
|
3078
|
+
const corsRateLimited = cors.applyHeaders(req, rateLimitResponse);
|
|
3079
|
+
return applySecurityHeaders(corsRateLimited);
|
|
3080
|
+
}
|
|
3081
|
+
const response = await handler();
|
|
3082
|
+
const formattedResponse = await applyGlobalDateFormatting(response, req);
|
|
3083
|
+
const corsResponse = cors.applyHeaders(req, formattedResponse);
|
|
3084
|
+
return applySecurityHeaders(corsResponse);
|
|
3085
|
+
}
|
|
3086
|
+
return {
|
|
3087
|
+
GET: async (req, ctx) => {
|
|
3088
|
+
const resolvedParams = await ctx.params;
|
|
3089
|
+
const paramsList = resolvedParams.params || [];
|
|
3090
|
+
return withSecurity(req, async () => {
|
|
3091
|
+
if (isAuthRoute(paramsList))
|
|
3092
|
+
return handleAuthRequest(req, paramsList, "GET");
|
|
3093
|
+
return handleGet(req, paramsList);
|
|
3094
|
+
});
|
|
3095
|
+
},
|
|
3096
|
+
POST: async (req, ctx) => {
|
|
3097
|
+
const resolvedParams = await ctx.params;
|
|
3098
|
+
const paramsList = resolvedParams.params || [];
|
|
3099
|
+
return withSecurity(req, async () => {
|
|
3100
|
+
if (isAuthRoute(paramsList))
|
|
3101
|
+
return handleAuthRequest(req, paramsList, "POST");
|
|
3102
|
+
return handlePost(req, paramsList);
|
|
3103
|
+
});
|
|
3104
|
+
},
|
|
3105
|
+
PUT: async (req, ctx) => {
|
|
3106
|
+
const resolvedParams = await ctx.params;
|
|
3107
|
+
const paramsList = resolvedParams.params || [];
|
|
3108
|
+
return withSecurity(req, async () => {
|
|
3109
|
+
return handlePut(req, paramsList);
|
|
3110
|
+
});
|
|
3111
|
+
},
|
|
3112
|
+
PATCH: async (req, ctx) => {
|
|
3113
|
+
const resolvedParams = await ctx.params;
|
|
3114
|
+
const paramsList = resolvedParams.params || [];
|
|
3115
|
+
return withSecurity(req, async () => {
|
|
3116
|
+
if (isAuthRoute(paramsList))
|
|
3117
|
+
return handleAuthRequest(req, paramsList, "PATCH");
|
|
3118
|
+
return handlePatch(req, paramsList);
|
|
3119
|
+
});
|
|
3120
|
+
},
|
|
3121
|
+
DELETE: async (req, ctx) => {
|
|
3122
|
+
const resolvedParams = await ctx.params;
|
|
3123
|
+
const paramsList = resolvedParams.params || [];
|
|
3124
|
+
return withSecurity(req, async () => {
|
|
3125
|
+
return handleDelete(req, paramsList);
|
|
3126
|
+
});
|
|
3127
|
+
},
|
|
3128
|
+
OPTIONS: async (req) => {
|
|
3129
|
+
return withSecurity(req, async () => {
|
|
3130
|
+
return new Response(null, {
|
|
3131
|
+
status: 204,
|
|
3132
|
+
headers: { Allow: "GET, POST, PUT, PATCH, DELETE, OPTIONS" }
|
|
3133
|
+
});
|
|
3134
|
+
});
|
|
3135
|
+
}
|
|
3136
|
+
};
|
|
3137
|
+
}
|
|
3138
|
+
function getCollectionsService() {
|
|
3139
|
+
try {
|
|
3140
|
+
if (container.has("collectionService")) {
|
|
3141
|
+
return container.get("collectionService");
|
|
3142
|
+
}
|
|
3143
|
+
} catch {
|
|
3144
|
+
}
|
|
3145
|
+
return void 0;
|
|
3146
|
+
}
|
|
3147
|
+
function getCollectionsHandler() {
|
|
3148
|
+
try {
|
|
3149
|
+
if (container.has("collectionsHandler")) {
|
|
3150
|
+
return container.get("collectionsHandler");
|
|
3151
|
+
}
|
|
3152
|
+
} catch {
|
|
3153
|
+
}
|
|
3154
|
+
return void 0;
|
|
3155
|
+
}
|
|
3156
|
+
var _handleAdminMetaRequestForTest = handleAdminMetaRequest;
|
|
3157
|
+
var _handleAdminMetaSidebarGroupsForTest = handleAdminMetaSidebarGroups;
|
|
3158
|
+
|
|
3159
|
+
export {
|
|
3160
|
+
bumpSchemaVersion,
|
|
3161
|
+
createDynamicHandlers,
|
|
3162
|
+
getCollectionsService,
|
|
3163
|
+
getCollectionsHandler,
|
|
3164
|
+
_handleAdminMetaRequestForTest,
|
|
3165
|
+
_handleAdminMetaSidebarGroupsForTest
|
|
3166
|
+
};
|