emdash 0.19.0 → 0.21.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/dist/{adapters-C5AWLJSD.d.mts → adapters-BxSmgtbF.d.mts} +1 -1
- package/dist/{adapters-C5AWLJSD.d.mts.map → adapters-BxSmgtbF.d.mts.map} +1 -1
- package/dist/{allowed-origins-CyYLEJkp.mjs → allowed-origins-BqC8cul8.mjs} +2 -2
- package/dist/{allowed-origins-CyYLEJkp.mjs.map → allowed-origins-BqC8cul8.mjs.map} +1 -1
- package/dist/api/route-utils.d.mts +3 -3
- package/dist/api/route-utils.mjs +13 -12
- package/dist/api/route-utils.mjs.map +1 -1
- package/dist/api/schemas/index.d.mts +1 -1
- package/dist/api/schemas/index.mjs +3 -2
- package/dist/{api-BZ6bhjYs.mjs → api-DxjIV2o8.mjs} +46 -15
- package/dist/api-DxjIV2o8.mjs.map +1 -0
- package/dist/{api-tokens-VrXNiNvV.mjs → api-tokens-BFFkB0jB.mjs} +2 -2
- package/dist/{api-tokens-VrXNiNvV.mjs.map → api-tokens-BFFkB0jB.mjs.map} +1 -1
- package/dist/{apply-hQkKKBCf.mjs → apply-CLjxheyb.mjs} +12 -12
- package/dist/{apply-hQkKKBCf.mjs.map → apply-CLjxheyb.mjs.map} +1 -1
- package/dist/astro/index.d.mts +10 -10
- package/dist/astro/index.d.mts.map +1 -1
- package/dist/astro/index.mjs +50 -15
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +9 -9
- package/dist/astro/middleware/auth.mjs +5 -5
- package/dist/astro/middleware/redirect.d.mts.map +1 -1
- package/dist/astro/middleware/redirect.mjs +11 -2
- package/dist/astro/middleware/redirect.mjs.map +1 -1
- package/dist/astro/middleware/request-context.mjs +3 -2
- package/dist/astro/middleware/request-context.mjs.map +1 -1
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.d.mts +1 -1
- package/dist/astro/middleware.d.mts.map +1 -1
- package/dist/astro/middleware.mjs +91 -137
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +5 -4
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs.map +1 -1
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs +5 -4
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +3 -3
- package/dist/astro/routes/api/admin/api-tokens/index.mjs +4 -4
- package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +4 -4
- package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +8 -7
- package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/admin/byline-fields/index.mjs +8 -7
- package/dist/astro/routes/api/admin/byline-fields/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +8 -7
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +14 -12
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +14 -12
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/admin/bylines/index.mjs +14 -12
- package/dist/astro/routes/api/admin/bylines/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs +9 -8
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs.map +1 -1
- package/dist/astro/routes/api/admin/comments/_id_.mjs +3 -3
- package/dist/astro/routes/api/admin/comments/bulk.mjs +7 -6
- package/dist/astro/routes/api/admin/comments/bulk.mjs.map +1 -1
- package/dist/astro/routes/api/admin/comments/counts.mjs +3 -3
- package/dist/astro/routes/api/admin/comments/index.mjs +7 -6
- package/dist/astro/routes/api/admin/comments/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +3 -3
- package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +2 -2
- package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +3 -3
- package/dist/astro/routes/api/admin/oauth-clients/index.mjs +3 -3
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +29 -27
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +29 -27
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +28 -26
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +28 -26
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +28 -26
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/index.mjs +28 -26
- package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +2 -2
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +28 -26
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +28 -26
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +28 -26
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +28 -26
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +29 -27
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +28 -26
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs +29 -27
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/updates.mjs +28 -26
- package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +28 -26
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +2 -2
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +28 -26
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/users/_id_/disable.mjs +1 -1
- package/dist/astro/routes/api/admin/users/_id_/enable.mjs +1 -1
- package/dist/astro/routes/api/admin/users/_id_/index.mjs +5 -4
- package/dist/astro/routes/api/admin/users/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +2 -2
- package/dist/astro/routes/api/admin/users/index.mjs +5 -4
- package/dist/astro/routes/api/admin/users/index.mjs.map +1 -1
- package/dist/astro/routes/api/auth/dev-bypass.mjs +3 -3
- package/dist/astro/routes/api/auth/invite/accept.mjs +1 -1
- package/dist/astro/routes/api/auth/invite/complete.mjs +9 -8
- package/dist/astro/routes/api/auth/invite/complete.mjs.map +1 -1
- package/dist/astro/routes/api/auth/invite/index.mjs +6 -5
- package/dist/astro/routes/api/auth/invite/index.mjs.map +1 -1
- package/dist/astro/routes/api/auth/invite/register-options.mjs +8 -7
- package/dist/astro/routes/api/auth/invite/register-options.mjs.map +1 -1
- package/dist/astro/routes/api/auth/logout.mjs +2 -2
- package/dist/astro/routes/api/auth/magic-link/send.mjs +8 -7
- package/dist/astro/routes/api/auth/magic-link/send.mjs.map +1 -1
- package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
- package/dist/astro/routes/api/auth/me.mjs +5 -4
- package/dist/astro/routes/api/auth/me.mjs.map +1 -1
- package/dist/astro/routes/api/auth/mode.mjs +1 -1
- package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +3 -3
- package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
- package/dist/astro/routes/api/auth/passkey/_id_.mjs +5 -4
- package/dist/astro/routes/api/auth/passkey/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/index.mjs +1 -1
- package/dist/astro/routes/api/auth/passkey/options.mjs +10 -9
- package/dist/astro/routes/api/auth/passkey/options.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/register/options.mjs +8 -7
- package/dist/astro/routes/api/auth/passkey/register/options.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs +9 -8
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs.map +1 -1
- package/dist/astro/routes/api/auth/passkey/verify.mjs +9 -8
- package/dist/astro/routes/api/auth/passkey/verify.mjs.map +1 -1
- package/dist/astro/routes/api/auth/signup/complete.mjs +9 -8
- package/dist/astro/routes/api/auth/signup/complete.mjs.map +1 -1
- package/dist/astro/routes/api/auth/signup/request.mjs +8 -7
- package/dist/astro/routes/api/auth/signup/request.mjs.map +1 -1
- package/dist/astro/routes/api/auth/signup/verify.mjs +1 -1
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +11 -9
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +10 -8
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +6 -5
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +6 -5
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +10 -9
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/_id_.mjs +6 -5
- package/dist/astro/routes/api/content/_collection_/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/authors.mjs +2 -2
- package/dist/astro/routes/api/content/_collection_/index.mjs +6 -5
- package/dist/astro/routes/api/content/_collection_/index.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/trash.mjs +6 -5
- package/dist/astro/routes/api/content/_collection_/trash.mjs.map +1 -1
- package/dist/astro/routes/api/dashboard.mjs +3 -3
- package/dist/astro/routes/api/dev/emails.mjs +2 -2
- package/dist/astro/routes/api/import/probe.d.mts +3 -3
- package/dist/astro/routes/api/import/probe.mjs +10 -9
- package/dist/astro/routes/api/import/probe.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/analyze.mjs +3 -3
- package/dist/astro/routes/api/import/wordpress/execute.d.mts +9 -9
- package/dist/astro/routes/api/import/wordpress/execute.mjs +10 -9
- package/dist/astro/routes/api/import/wordpress/execute.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/media.mjs +8 -7
- package/dist/astro/routes/api/import/wordpress/media.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/prepare.mjs +9 -8
- package/dist/astro/routes/api/import/wordpress/prepare.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +8 -7
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +10 -9
- package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +1 -1
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +14 -12
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs.map +1 -1
- package/dist/astro/routes/api/manifest.mjs +3 -3
- package/dist/astro/routes/api/mcp.mjs +20 -19
- package/dist/astro/routes/api/mcp.mjs.map +1 -1
- package/dist/astro/routes/api/media/_id_/confirm.mjs +6 -5
- package/dist/astro/routes/api/media/_id_/confirm.mjs.map +1 -1
- package/dist/astro/routes/api/media/_id_.mjs +6 -5
- package/dist/astro/routes/api/media/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/media/file/_...key_.mjs +1 -1
- package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +2 -2
- package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +2 -2
- package/dist/astro/routes/api/media/providers/index.mjs +2 -2
- package/dist/astro/routes/api/media/upload-url.mjs +8 -7
- package/dist/astro/routes/api/media/upload-url.mjs.map +1 -1
- package/dist/astro/routes/api/media.mjs +10 -9
- package/dist/astro/routes/api/media.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +6 -5
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/items.mjs +6 -5
- package/dist/astro/routes/api/menus/_name_/items.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/reorder.mjs +6 -5
- package/dist/astro/routes/api/menus/_name_/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/translations.mjs +6 -5
- package/dist/astro/routes/api/menus/_name_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_.mjs +6 -5
- package/dist/astro/routes/api/menus/_name_.mjs.map +1 -1
- package/dist/astro/routes/api/menus/index.mjs +6 -5
- package/dist/astro/routes/api/menus/index.mjs.map +1 -1
- package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
- package/dist/astro/routes/api/oauth/device/authorize.mjs +5 -5
- package/dist/astro/routes/api/oauth/device/code.mjs +8 -8
- package/dist/astro/routes/api/oauth/device/token.mjs +7 -7
- package/dist/astro/routes/api/oauth/register.mjs +2 -2
- package/dist/astro/routes/api/oauth/token/refresh.mjs +5 -5
- package/dist/astro/routes/api/oauth/token/revoke.mjs +5 -5
- package/dist/astro/routes/api/oauth/token.mjs +5 -5
- package/dist/astro/routes/api/openapi.json.mjs +3 -2
- package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +3 -3
- package/dist/astro/routes/api/redirects/404s/index.mjs +7 -6
- package/dist/astro/routes/api/redirects/404s/index.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/404s/summary.mjs +7 -6
- package/dist/astro/routes/api/redirects/404s/summary.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/_id_.mjs +8 -7
- package/dist/astro/routes/api/redirects/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/redirects/index.mjs +8 -7
- package/dist/astro/routes/api/redirects/index.mjs.map +1 -1
- package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +2 -2
- package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +2 -2
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +28 -26
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +28 -26
- package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +28 -26
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +28 -26
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/index.mjs +28 -26
- package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/index.mjs +8 -13
- package/dist/astro/routes/api/schema/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs +28 -26
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/schema/orphans/index.mjs +28 -26
- package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
- package/dist/astro/routes/api/search/enable.mjs +9 -8
- package/dist/astro/routes/api/search/enable.mjs.map +1 -1
- package/dist/astro/routes/api/search/index.mjs +8 -7
- package/dist/astro/routes/api/search/index.mjs.map +1 -1
- package/dist/astro/routes/api/search/rebuild.mjs +9 -8
- package/dist/astro/routes/api/search/rebuild.mjs.map +1 -1
- package/dist/astro/routes/api/search/stats.mjs +5 -5
- package/dist/astro/routes/api/search/suggest.mjs +8 -7
- package/dist/astro/routes/api/search/suggest.mjs.map +1 -1
- package/dist/astro/routes/api/sections/_slug_.mjs +8 -7
- package/dist/astro/routes/api/sections/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/sections/index.mjs +8 -7
- package/dist/astro/routes/api/sections/index.mjs.map +1 -1
- package/dist/astro/routes/api/settings/email.mjs +3 -3
- package/dist/astro/routes/api/settings.mjs +11 -9
- package/dist/astro/routes/api/settings.mjs.map +1 -1
- package/dist/astro/routes/api/setup/admin-verify.mjs +10 -9
- package/dist/astro/routes/api/setup/admin-verify.mjs.map +1 -1
- package/dist/astro/routes/api/setup/admin.mjs +9 -8
- package/dist/astro/routes/api/setup/admin.mjs.map +1 -1
- package/dist/astro/routes/api/setup/dev-bypass.mjs +19 -18
- package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
- package/dist/astro/routes/api/setup/dev-reset.mjs +1 -1
- package/dist/astro/routes/api/setup/index.mjs +20 -18
- package/dist/astro/routes/api/setup/index.mjs.map +1 -1
- package/dist/astro/routes/api/setup/status.mjs +3 -3
- package/dist/astro/routes/api/snapshot.mjs +5 -4
- package/dist/astro/routes/api/snapshot.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +11 -10
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +11 -10
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +11 -10
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/index.mjs +11 -10
- package/dist/astro/routes/api/taxonomies/index.mjs.map +1 -1
- package/dist/astro/routes/api/themes/preview.mjs +5 -4
- package/dist/astro/routes/api/themes/preview.mjs.map +1 -1
- package/dist/astro/routes/api/typegen.mjs +4 -4
- package/dist/astro/routes/api/well-known/auth.mjs +1 -1
- package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
- package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
- package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +6 -5
- package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +9 -7
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +9 -7
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/_name_.mjs +5 -4
- package/dist/astro/routes/api/widget-areas/_name_.mjs.map +1 -1
- package/dist/astro/routes/api/widget-areas/index.mjs +9 -7
- package/dist/astro/routes/api/widget-areas/index.mjs.map +1 -1
- package/dist/astro/routes/api/widget-components.mjs +2 -2
- package/dist/astro/routes/robots.txt.mjs +5 -4
- package/dist/astro/routes/robots.txt.mjs.map +1 -1
- package/dist/astro/routes/sitemap-_collection_.xml.d.mts.map +1 -1
- package/dist/astro/routes/sitemap-_collection_.xml.mjs +15 -7
- package/dist/astro/routes/sitemap-_collection_.xml.mjs.map +1 -1
- package/dist/astro/routes/sitemap.xml.mjs +6 -5
- package/dist/astro/routes/sitemap.xml.mjs.map +1 -1
- package/dist/astro/types.d.mts +12 -12
- package/dist/auth/providers/github.d.mts +1 -1
- package/dist/auth/providers/google.d.mts +1 -1
- package/dist/{authorize-C_8t2KGa.mjs → authorize-D5gfBVU5.mjs} +2 -2
- package/dist/{authorize-C_8t2KGa.mjs.map → authorize-D5gfBVU5.mjs.map} +1 -1
- package/dist/{byline-DUx48sJp.mjs → byline-V_Qp1Ziw.mjs} +27 -14
- package/dist/byline-V_Qp1Ziw.mjs.map +1 -0
- package/dist/{byline-fields-51kg6Vuv.mjs → byline-fields-B0NO1yUB.mjs} +3 -3
- package/dist/{byline-fields-51kg6Vuv.mjs.map → byline-fields-B0NO1yUB.mjs.map} +1 -1
- package/dist/{byline-fields-DYXKDuNX.d.mts → byline-fields-CQJRIQkn.d.mts} +36 -32
- package/dist/byline-fields-CQJRIQkn.d.mts.map +1 -0
- package/dist/{byline-fields-C_OsR-KF.mjs → byline-fields-nBVqK_Ff.mjs} +2 -2
- package/dist/{byline-fields-C_OsR-KF.mjs.map → byline-fields-nBVqK_Ff.mjs.map} +1 -1
- package/dist/{byline-registry-CWP7I71B.mjs → byline-registry-DedidtqC.mjs} +2 -2
- package/dist/{byline-registry-CWP7I71B.mjs.map → byline-registry-DedidtqC.mjs.map} +1 -1
- package/dist/{bylines-Cx5n-WqP.mjs → bylines-B2NWnIwS.mjs} +2 -2
- package/dist/{bylines-Cx5n-WqP.mjs.map → bylines-B2NWnIwS.mjs.map} +1 -1
- package/dist/{bylines-wurS258E.mjs → bylines-DfGDnred.mjs} +7 -7
- package/dist/{bylines-wurS258E.mjs.map → bylines-DfGDnred.mjs.map} +1 -1
- package/dist/{cache-B_HzASVT.mjs → cache-DTTHWD8n.mjs} +1 -1
- package/dist/{cache-B_HzASVT.mjs.map → cache-DTTHWD8n.mjs.map} +1 -1
- package/dist/{challenge-store-DGwuCc4R.mjs → challenge-store-woE0bbCf.mjs} +1 -1
- package/dist/{challenge-store-DGwuCc4R.mjs.map → challenge-store-woE0bbCf.mjs.map} +1 -1
- package/dist/cli/index.mjs +22 -20
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/cf-access.d.mts +1 -1
- package/dist/client/index.d.mts +1 -1
- package/dist/client/index.mjs +1 -1
- package/dist/{comments-CJ0RZsYR.mjs → comments-D2hNuxNa.mjs} +1 -1
- package/dist/{comments-CJ0RZsYR.mjs.map → comments-D2hNuxNa.mjs.map} +1 -1
- package/dist/{components-CTfpu3PZ.mjs → components-DYKp2gmo.mjs} +1 -1
- package/dist/{components-CTfpu3PZ.mjs.map → components-DYKp2gmo.mjs.map} +1 -1
- package/dist/{context-GG52SPgh.mjs → context-Cm4pt1Ws.mjs} +5 -5
- package/dist/{context-GG52SPgh.mjs.map → context-Cm4pt1Ws.mjs.map} +1 -1
- package/dist/{cron-BJ2ClIlj.mjs → cron-DdEVrQ2Y.mjs} +1 -1
- package/dist/{cron-BJ2ClIlj.mjs.map → cron-DdEVrQ2Y.mjs.map} +1 -1
- package/dist/{dashboard-2JgAMWxK.mjs → dashboard-C-UYpps0.mjs} +1 -1
- package/dist/{dashboard-2JgAMWxK.mjs.map → dashboard-C-UYpps0.mjs.map} +1 -1
- package/dist/database/instrumentation.d.mts +10 -1
- package/dist/database/instrumentation.d.mts.map +1 -1
- package/dist/database/instrumentation.mjs +13 -1
- package/dist/database/instrumentation.mjs.map +1 -1
- package/dist/db/index.d.mts +3 -3
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/{db-errors-CtzxKBxe.mjs → db-errors-BluWkwGI.mjs} +1 -1
- package/dist/{db-errors-CtzxKBxe.mjs.map → db-errors-BluWkwGI.mjs.map} +1 -1
- package/dist/{default-xLFNSsZ9.mjs → default-NHGuJzQ3.mjs} +1 -1
- package/dist/{default-xLFNSsZ9.mjs.map → default-NHGuJzQ3.mjs.map} +1 -1
- package/dist/{device-flow-s6_q3T7A.mjs → device-flow-BQApWgnW.mjs} +4 -4
- package/dist/{device-flow-s6_q3T7A.mjs.map → device-flow-BQApWgnW.mjs.map} +1 -1
- package/dist/{email-console-DHT2Fbpj.mjs → email-console-BbU3RbWv.mjs} +1 -1
- package/dist/{email-console-DHT2Fbpj.mjs.map → email-console-BbU3RbWv.mjs.map} +1 -1
- package/dist/{error-RwM4dD35.mjs → error-CNn_w7jf.mjs} +1 -1
- package/dist/{error-RwM4dD35.mjs.map → error-CNn_w7jf.mjs.map} +1 -1
- package/dist/{escape-bIyGoW5W.mjs → escape-DPgcxcpL.mjs} +1 -1
- package/dist/{escape-bIyGoW5W.mjs.map → escape-DPgcxcpL.mjs.map} +1 -1
- package/dist/{fts-manager-1RgHmopc.mjs → fts-manager-Cx5z8jdA.mjs} +1 -1
- package/dist/{fts-manager-1RgHmopc.mjs.map → fts-manager-Cx5z8jdA.mjs.map} +1 -1
- package/dist/{hash-9w3pd3-m.mjs → hash-DlvIFn0b.mjs} +1 -1
- package/dist/{hash-9w3pd3-m.mjs.map → hash-DlvIFn0b.mjs.map} +1 -1
- package/dist/{import-Dh8bWmyq.mjs → import-KyxT1Mbs.mjs} +3 -3
- package/dist/{import-Dh8bWmyq.mjs.map → import-KyxT1Mbs.mjs.map} +1 -1
- package/dist/{index-FfiTQJq2.d.mts → index-D2VAiumu.d.mts} +46 -15
- package/dist/{index-FfiTQJq2.d.mts.map → index-D2VAiumu.d.mts.map} +1 -1
- package/dist/{index-BpYeJO1E.d.mts → index-uT2yR66F.d.mts} +3 -3
- package/dist/{index-BpYeJO1E.d.mts.map → index-uT2yR66F.d.mts.map} +1 -1
- package/dist/index.d.mts +16 -16
- package/dist/index.mjs +48 -46
- package/dist/init-lock-DlBHjf9-.mjs +83 -0
- package/dist/init-lock-DlBHjf9-.mjs.map +1 -0
- package/dist/{load-B84ohfBk.mjs → load-Dq91b_DK.mjs} +1 -1
- package/dist/{load-B84ohfBk.mjs.map → load-Dq91b_DK.mjs.map} +1 -1
- package/dist/{loader-CpZKpFz0.mjs → loader-BqWjcH3h.mjs} +12 -15
- package/dist/loader-BqWjcH3h.mjs.map +1 -0
- package/dist/{manifest-schema-Cj-YrzrF.mjs → manifest-schema-DFPeqMAn.mjs} +55 -2
- package/dist/manifest-schema-DFPeqMAn.mjs.map +1 -0
- package/dist/media/index.d.mts +1 -1
- package/dist/media/index.mjs +2 -2
- package/dist/media/local-runtime.d.mts +11 -11
- package/dist/media/local-runtime.mjs +4 -3
- package/dist/media/local-runtime.mjs.map +1 -1
- package/dist/{media-allowlist-CMcoYIjQ.mjs → media-allowlist-_A0SuDn4.mjs} +2 -2
- package/dist/{media-allowlist-CMcoYIjQ.mjs.map → media-allowlist-_A0SuDn4.mjs.map} +1 -1
- package/dist/media-url-CqLd69IO.mjs +26 -0
- package/dist/media-url-CqLd69IO.mjs.map +1 -0
- package/dist/{menus-Dp9xporj.mjs → menus-Ryk9L7fT.mjs} +10 -37
- package/dist/menus-Ryk9L7fT.mjs.map +1 -0
- package/dist/{mime-CCEzze7W.mjs → mime-YbtlEtvS.mjs} +1 -1
- package/dist/{mime-CCEzze7W.mjs.map → mime-YbtlEtvS.mjs.map} +1 -1
- package/dist/{mode-BjlXswIw.mjs → mode-CGXzIbD8.mjs} +1 -1
- package/dist/{mode-BjlXswIw.mjs.map → mode-CGXzIbD8.mjs.map} +1 -1
- package/dist/{normalize-CK5o04zr.mjs → normalize-DKsg36ty.mjs} +1 -1
- package/dist/{normalize-CK5o04zr.mjs.map → normalize-DKsg36ty.mjs.map} +1 -1
- package/dist/{oauth-authorization-1aPAYjiC.mjs → oauth-authorization-C2kVyjXI.mjs} +4 -4
- package/dist/{oauth-authorization-1aPAYjiC.mjs.map → oauth-authorization-C2kVyjXI.mjs.map} +1 -1
- package/dist/{oauth-clients-8mPDStMv.mjs → oauth-clients-BC873NCV.mjs} +1 -1
- package/dist/{oauth-clients-8mPDStMv.mjs.map → oauth-clients-BC873NCV.mjs.map} +1 -1
- package/dist/{oauth-state-store-BJ7YtrfD.mjs → oauth-state-store-Cd--TUaq.mjs} +1 -1
- package/dist/{oauth-state-store-BJ7YtrfD.mjs.map → oauth-state-store-Cd--TUaq.mjs.map} +1 -1
- package/dist/{oauth-user-lookup-BdDSDvjF.mjs → oauth-user-lookup-e4wOvDud.mjs} +1 -1
- package/dist/{oauth-user-lookup-BdDSDvjF.mjs.map → oauth-user-lookup-e4wOvDud.mjs.map} +1 -1
- package/dist/{options-D4MnavW_.d.mts → options-9kLgkE8m.d.mts} +3 -3
- package/dist/{options-D4MnavW_.d.mts.map → options-9kLgkE8m.d.mts.map} +1 -1
- package/dist/page/index.d.mts +2 -2
- package/dist/{parse-CrGndy1A.mjs → parse-DzSrk1t8.mjs} +2 -2
- package/dist/{parse-CrGndy1A.mjs.map → parse-DzSrk1t8.mjs.map} +1 -1
- package/dist/{passkey-config-BDVM86Tj.mjs → passkey-config-BpjbE_Uv.mjs} +1 -1
- package/dist/{passkey-config-BDVM86Tj.mjs.map → passkey-config-BpjbE_Uv.mjs.map} +1 -1
- package/dist/{placeholder-BZxr8W1j.mjs → placeholder-2xumZh4g.mjs} +1 -1
- package/dist/{placeholder-BZxr8W1j.mjs.map → placeholder-2xumZh4g.mjs.map} +1 -1
- package/dist/{placeholder-B9lUUEmj.d.mts → placeholder-BevVKfay.d.mts} +1 -1
- package/dist/{placeholder-B9lUUEmj.d.mts.map → placeholder-BevVKfay.d.mts.map} +1 -1
- package/dist/plugin-types.d.mts +1 -1
- package/dist/plugin-utils.d.mts +9 -9
- package/dist/plugins/adapt-sandbox-entry.d.mts +9 -9
- package/dist/plugins/adapt-sandbox-entry.mjs +2 -2
- package/dist/{preview-BfuRkVKW.mjs → preview-Dqv2hwXr.mjs} +2 -2
- package/dist/{preview-BfuRkVKW.mjs.map → preview-Dqv2hwXr.mjs.map} +1 -1
- package/dist/{public-url-egRHCy1m.mjs → public-url-D_zARuvZ.mjs} +1 -1
- package/dist/{public-url-egRHCy1m.mjs.map → public-url-D_zARuvZ.mjs.map} +1 -1
- package/dist/{query-BFQ029Ts.mjs → query-Crm038Mc.mjs} +21 -11
- package/dist/query-Crm038Mc.mjs.map +1 -0
- package/dist/{rate-limit-ClFFUga6.mjs → rate-limit-hRTBqmw1.mjs} +2 -2
- package/dist/{rate-limit-ClFFUga6.mjs.map → rate-limit-hRTBqmw1.mjs.map} +1 -1
- package/dist/{redirect-Cw3JTlmj.mjs → redirect-C-OOkyku.mjs} +1 -1
- package/dist/{redirect-Cw3JTlmj.mjs.map → redirect-C-OOkyku.mjs.map} +1 -1
- package/dist/{redirects-DEygMrRO.mjs → redirects-6Zg2SoYo.mjs} +11 -10
- package/dist/redirects-6Zg2SoYo.mjs.map +1 -0
- package/dist/{redirects-OIu6vQ2i.mjs → redirects-CP3TnTLO.mjs} +20 -14
- package/dist/redirects-CP3TnTLO.mjs.map +1 -0
- package/dist/{registry-brYh-rAT.mjs → registry-diMzD1Wf.mjs} +3 -3
- package/dist/{registry-brYh-rAT.mjs.map → registry-diMzD1Wf.mjs.map} +1 -1
- package/dist/{request-cache-D32LpnmI.mjs → request-cache-UwmBAiUK.mjs} +1 -1
- package/dist/{request-cache-D32LpnmI.mjs.map → request-cache-UwmBAiUK.mjs.map} +1 -1
- package/dist/request-context.d.mts +7 -0
- package/dist/request-context.d.mts.map +1 -1
- package/dist/request-context.mjs +2 -1
- package/dist/request-context.mjs.map +1 -1
- package/dist/{request-meta-7ByVLxB-.mjs → request-meta-DPechd0W.mjs} +2 -2
- package/dist/{request-meta-7ByVLxB-.mjs.map → request-meta-DPechd0W.mjs.map} +1 -1
- package/dist/{resolve-BqYMVG0D.mjs → resolve-B3NUUtVY.mjs} +1 -1
- package/dist/{resolve-BqYMVG0D.mjs.map → resolve-B3NUUtVY.mjs.map} +1 -1
- package/dist/{runner-BcRuXq_h.d.mts → runner-C8vcbvCe.d.mts} +2 -2
- package/dist/{runner-BcRuXq_h.d.mts.map → runner-C8vcbvCe.d.mts.map} +1 -1
- package/dist/runtime.d.mts +10 -10
- package/dist/runtime.mjs +1 -1
- package/dist/{schema-CS7Eg5gh.mjs → schema-BDOkd3OU.mjs} +4 -4
- package/dist/{schema-CS7Eg5gh.mjs.map → schema-BDOkd3OU.mjs.map} +1 -1
- package/dist/{search-o-aQzHI1.mjs → search-Bs_J_EW-.mjs} +3 -3
- package/dist/{search-o-aQzHI1.mjs.map → search-Bs_J_EW-.mjs.map} +1 -1
- package/dist/{secrets-C_ZtRos3.mjs → secrets-C8xmE6mR.mjs} +21 -11
- package/dist/secrets-C8xmE6mR.mjs.map +1 -0
- package/dist/{sections-DhsZ0ns9.mjs → sections-P0zuBlyz.mjs} +2 -2
- package/dist/{sections-DhsZ0ns9.mjs.map → sections-P0zuBlyz.mjs.map} +1 -1
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +14 -13
- package/dist/seo/index.d.mts +1 -1
- package/dist/seo/index.d.mts.map +1 -1
- package/dist/seo/index.mjs +3 -12
- package/dist/seo/index.mjs.map +1 -1
- package/dist/{seo-DfjLvu8i.mjs → seo-CLhm-Fmb.mjs} +4 -3
- package/dist/seo-CLhm-Fmb.mjs.map +1 -0
- package/dist/{seo-B5e6y9Wk.mjs → seo-DpNgGQjF.mjs} +1 -1
- package/dist/{seo-B5e6y9Wk.mjs.map → seo-DpNgGQjF.mjs.map} +1 -1
- package/dist/{service-DAxg8RPR.mjs → service-CDQQnT8W.mjs} +2 -2
- package/dist/{service-DAxg8RPR.mjs.map → service-CDQQnT8W.mjs.map} +1 -1
- package/dist/{settings-B1p-gPUK.mjs → settings-BjBsmVAo.mjs} +32 -30
- package/dist/settings-BjBsmVAo.mjs.map +1 -0
- package/dist/{settings-DIsbHTRE.mjs → settings-sO0Fif4p.mjs} +2 -2
- package/dist/{settings-DIsbHTRE.mjs.map → settings-sO0Fif4p.mjs.map} +1 -1
- package/dist/{setup-complete-Yuv78yua.mjs → setup-complete-CMMr-oZU.mjs} +1 -1
- package/dist/{setup-complete-Yuv78yua.mjs.map → setup-complete-CMMr-oZU.mjs.map} +1 -1
- package/dist/{setup-nonce-Bm0uKqmf.mjs → setup-nonce-169xl4fV.mjs} +1 -1
- package/dist/{setup-nonce-Bm0uKqmf.mjs.map → setup-nonce-169xl4fV.mjs.map} +1 -1
- package/dist/single-flight-cache-C0UV1Npg.mjs +104 -0
- package/dist/single-flight-cache-C0UV1Npg.mjs.map +1 -0
- package/dist/{site-url-mEVmwIFi.mjs → site-url-vtsuOvSD.mjs} +1 -1
- package/dist/{site-url-mEVmwIFi.mjs.map → site-url-vtsuOvSD.mjs.map} +1 -1
- package/dist/{ssrf-BsVGIE0Z.mjs → ssrf-XO05Voq6.mjs} +1 -1
- package/dist/{ssrf-BsVGIE0Z.mjs.map → ssrf-XO05Voq6.mjs.map} +1 -1
- package/dist/status-2gZklYuj.mjs +30 -0
- package/dist/status-2gZklYuj.mjs.map +1 -0
- package/dist/storage/local.d.mts +1 -1
- package/dist/storage/local.mjs +2 -2
- package/dist/storage/s3.d.mts +1 -1
- package/dist/storage/s3.mjs +1 -1
- package/dist/{taxonomies-BEW7S5AI.mjs → taxonomies-BBxYA38v.mjs} +49 -12
- package/dist/taxonomies-BBxYA38v.mjs.map +1 -0
- package/dist/{taxonomies-UusDXv3C.mjs → taxonomies-DuESHWKI.mjs} +2 -2
- package/dist/{taxonomies-UusDXv3C.mjs.map → taxonomies-DuESHWKI.mjs.map} +1 -1
- package/dist/{tokens-Bx2afeT-.mjs → tokens-DMkVjxrx.mjs} +1 -1
- package/dist/{tokens-Bx2afeT-.mjs.map → tokens-DMkVjxrx.mjs.map} +1 -1
- package/dist/{transport--Ck3RBin.mjs → transport-1cIrOb1Y.mjs} +1 -1
- package/dist/{transport--Ck3RBin.mjs.map → transport-1cIrOb1Y.mjs.map} +1 -1
- package/dist/{transport-BwQeeY2p.d.mts → transport-jdvsZEIt.d.mts} +1 -1
- package/dist/{transport-BwQeeY2p.d.mts.map → transport-jdvsZEIt.d.mts.map} +1 -1
- package/dist/{trusted-proxy-B4AfnoAp.mjs → trusted-proxy-CHp41Fjj.mjs} +1 -1
- package/dist/{trusted-proxy-B4AfnoAp.mjs.map → trusted-proxy-CHp41Fjj.mjs.map} +1 -1
- package/dist/{types-DWnN7weG.d.mts → types-BFgYtuKd.d.mts} +1 -1
- package/dist/{types-DWnN7weG.d.mts.map → types-BFgYtuKd.d.mts.map} +1 -1
- package/dist/{types-DZk_y-MU.mjs → types-BIduXPJk.mjs} +1 -1
- package/dist/types-BIduXPJk.mjs.map +1 -0
- package/dist/{types-WVmpZBJV.d.mts → types-BTnnBYVX.d.mts} +2 -2
- package/dist/{types-WVmpZBJV.d.mts.map → types-BTnnBYVX.d.mts.map} +1 -1
- package/dist/types-BoRm8-pp.mjs +3 -0
- package/dist/{types-DbCWhHet.d.mts → types-Bzfk2yC8.d.mts} +2 -2
- package/dist/types-Bzfk2yC8.d.mts.map +1 -0
- package/dist/{types-Qa7-HJJC.d.mts → types-CkEuk-Zr.d.mts} +1 -1
- package/dist/{types-Qa7-HJJC.d.mts.map → types-CkEuk-Zr.d.mts.map} +1 -1
- package/dist/{types-DMwSpvcw.d.mts → types-DO7whVYU.d.mts} +9 -3
- package/dist/{types-DMwSpvcw.d.mts.map → types-DO7whVYU.d.mts.map} +1 -1
- package/dist/{types-DX6v9KzJ.d.mts → types-DdkL6fyv.d.mts} +1 -1
- package/dist/{types-DX6v9KzJ.d.mts.map → types-DdkL6fyv.d.mts.map} +1 -1
- package/dist/{types-DpFmlNyB.mjs → types-DejCHqWT.mjs} +1 -1
- package/dist/{types-DpFmlNyB.mjs.map → types-DejCHqWT.mjs.map} +1 -1
- package/dist/{types-OT_Es5mp.d.mts → types-Del0VMij.d.mts} +1 -1
- package/dist/{types-OT_Es5mp.d.mts.map → types-Del0VMij.d.mts.map} +1 -1
- package/dist/{types-kwqCOUxj.d.mts → types-u_XxjbS8.d.mts} +1 -1
- package/dist/{types-kwqCOUxj.d.mts.map → types-u_XxjbS8.d.mts.map} +1 -1
- package/dist/{utils-C4Ih4DML.mjs → utils-C4M981Br.mjs} +1 -1
- package/dist/{utils-C4Ih4DML.mjs.map → utils-C4M981Br.mjs.map} +1 -1
- package/dist/{validate-ZP9Dvg0P.mjs → validate-DGhQPXzI.mjs} +2 -2
- package/dist/{validate-ZP9Dvg0P.mjs.map → validate-DGhQPXzI.mjs.map} +1 -1
- package/dist/{validate-BPAHUSge.d.mts → validate-cJOiOvT2.d.mts} +5 -5
- package/dist/{validate-BPAHUSge.d.mts.map → validate-cJOiOvT2.d.mts.map} +1 -1
- package/dist/{validation-CE5i4q0c.mjs → validation-DVHjPM1M.mjs} +5 -5
- package/dist/{validation-CE5i4q0c.mjs.map → validation-DVHjPM1M.mjs.map} +1 -1
- package/dist/version-BOjj_cfz.mjs +7 -0
- package/dist/{version-Dw0JXu45.mjs.map → version-BOjj_cfz.mjs.map} +1 -1
- package/dist/{widgets-ClEnYQCH.mjs → widgets-Ci6hLwfO.mjs} +47 -44
- package/dist/widgets-Ci6hLwfO.mjs.map +1 -0
- package/dist/{zod-generator-Djo_VHCt.mjs → zod-generator-CarzgPAu.mjs} +2 -2
- package/dist/{zod-generator-Djo_VHCt.mjs.map → zod-generator-CarzgPAu.mjs.map} +1 -1
- package/package.json +10 -10
- package/src/api/handlers/marketplace.ts +2 -5
- package/src/api/handlers/redirects.ts +24 -13
- package/src/api/handlers/registry.ts +70 -0
- package/src/api/handlers/seo.ts +9 -1
- package/src/api/schemas/redirects.ts +11 -4
- package/src/api/schemas/schema.ts +13 -1
- package/src/astro/integration/index.ts +44 -8
- package/src/astro/integration/routes.ts +46 -9
- package/src/astro/middleware/redirect.ts +12 -0
- package/src/astro/middleware.ts +20 -6
- package/src/astro/routes/api/schema/index.ts +7 -15
- package/src/astro/routes/sitemap-[collection].xml.ts +13 -2
- package/src/bylines/field-defs-cache.ts +70 -20
- package/src/cli/commands/bundle-utils.ts +2 -0
- package/src/cli/commands/doctor.ts +1 -1
- package/src/cli/commands/secrets.ts +2 -2
- package/src/config/secrets.ts +28 -14
- package/src/database/instrumentation.ts +13 -0
- package/src/emdash-runtime.ts +31 -25
- package/src/loader.ts +24 -15
- package/src/plugins/manifest-schema.ts +75 -0
- package/src/plugins/marketplace.ts +2 -5
- package/src/plugins/types.ts +12 -0
- package/src/query.ts +13 -2
- package/src/redirects/status.ts +27 -0
- package/src/request-context.ts +8 -0
- package/src/schema/types.ts +11 -1
- package/src/seo/index.ts +2 -28
- package/src/seo/media-url.ts +32 -0
- package/src/settings/index.ts +32 -40
- package/src/taxonomies/index.ts +78 -12
- package/src/utils/single-flight-cache.ts +194 -0
- package/src/widgets/index.ts +57 -54
- package/dist/api-BZ6bhjYs.mjs.map +0 -1
- package/dist/byline-DUx48sJp.mjs.map +0 -1
- package/dist/byline-fields-DYXKDuNX.d.mts.map +0 -1
- package/dist/loader-CpZKpFz0.mjs.map +0 -1
- package/dist/manifest-schema-Cj-YrzrF.mjs.map +0 -1
- package/dist/menus-Dp9xporj.mjs.map +0 -1
- package/dist/query-BFQ029Ts.mjs.map +0 -1
- package/dist/redirects-DEygMrRO.mjs.map +0 -1
- package/dist/redirects-OIu6vQ2i.mjs.map +0 -1
- package/dist/secrets-C_ZtRos3.mjs.map +0 -1
- package/dist/seo-DfjLvu8i.mjs.map +0 -1
- package/dist/settings-B1p-gPUK.mjs.map +0 -1
- package/dist/taxonomies-BEW7S5AI.mjs.map +0 -1
- package/dist/types-Cj2S6FuC.mjs +0 -3
- package/dist/types-DZk_y-MU.mjs.map +0 -1
- package/dist/types-DbCWhHet.d.mts.map +0 -1
- package/dist/version-Dw0JXu45.mjs +0 -7
- package/dist/widgets-ClEnYQCH.mjs.map +0 -1
- /package/dist/{api-tokens-B6VgoE6M.mjs → api-tokens-C7ywRx7l.mjs} +0 -0
- /package/dist/{ssrf-BvgVcfNQ.mjs → ssrf-CRZGzjdL.mjs} +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import "../../../../base64-CqR-7kqF.mjs";
|
|
2
2
|
import "../../../../types-BXSUSAjt.mjs";
|
|
3
3
|
import { t as OptionsRepository } from "../../../../options-BPCVnesz.mjs";
|
|
4
|
-
import { n as apiSuccess, r as handleError, t as apiError } from "../../../../error-
|
|
5
|
-
import { n as parseBody, t as isParseError } from "../../../../parse-
|
|
6
|
-
import { n as requirePerm } from "../../../../authorize-
|
|
4
|
+
import { n as apiSuccess, r as handleError, t as apiError } from "../../../../error-CNn_w7jf.mjs";
|
|
5
|
+
import { n as parseBody, t as isParseError } from "../../../../parse-DzSrk1t8.mjs";
|
|
6
|
+
import { n as requirePerm } from "../../../../authorize-D5gfBVU5.mjs";
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
import { escapeHtml } from "@emdash-cms/auth";
|
|
9
9
|
|
|
@@ -3,16 +3,18 @@ import "../../../base64-CqR-7kqF.mjs";
|
|
|
3
3
|
import "../../../types-BXSUSAjt.mjs";
|
|
4
4
|
import "../../../media-JOf3pNkw.mjs";
|
|
5
5
|
import "../../../options-BPCVnesz.mjs";
|
|
6
|
-
import "../../../
|
|
7
|
-
import "../../../
|
|
8
|
-
import "../../../
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import "../../../
|
|
6
|
+
import "../../../init-lock-DlBHjf9-.mjs";
|
|
7
|
+
import "../../../request-cache-UwmBAiUK.mjs";
|
|
8
|
+
import "../../../loader-BqWjcH3h.mjs";
|
|
9
|
+
import "../../../settings-BjBsmVAo.mjs";
|
|
10
|
+
import { n as handleSettingsUpdate, t as handleSettingsGet } from "../../../settings-sO0Fif4p.mjs";
|
|
11
|
+
import { a as unwrapResult, r as handleError, t as apiError } from "../../../error-CNn_w7jf.mjs";
|
|
12
|
+
import { n as parseBody, t as isParseError } from "../../../parse-DzSrk1t8.mjs";
|
|
13
|
+
import { P as settingsUpdateBody } from "../../../redirects-6Zg2SoYo.mjs";
|
|
14
|
+
import "../../../byline-fields-B0NO1yUB.mjs";
|
|
15
|
+
import "../../../status-2gZklYuj.mjs";
|
|
14
16
|
import "../../../api/schemas/index.mjs";
|
|
15
|
-
import { n as requirePerm } from "../../../authorize-
|
|
17
|
+
import { n as requirePerm } from "../../../authorize-D5gfBVU5.mjs";
|
|
16
18
|
|
|
17
19
|
//#region src/astro/routes/api/settings.ts
|
|
18
20
|
const prerender = false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings.mjs","names":[],"sources":["../../../../src/astro/routes/api/settings.ts"],"sourcesContent":["/**\n * Site Settings API endpoint\n *\n * GET /_emdash/api/settings - Get all site settings\n * POST /_emdash/api/settings - Update site settings\n */\n\nimport type { APIRoute } from \"astro\";\n\nimport { requirePerm } from \"#api/authorize.js\";\nimport { apiError, handleError, unwrapResult } from \"#api/error.js\";\nimport { handleSettingsGet, handleSettingsUpdate } from \"#api/handlers/settings.js\";\nimport { isParseError, parseBody } from \"#api/parse.js\";\nimport { settingsUpdateBody } from \"#api/schemas.js\";\n\nexport const prerender = false;\n\n/**\n * GET /_emdash/api/settings\n *\n * Returns all site settings as a JSON object.\n * Unset values are undefined. Media references include resolved URLs.\n */\nexport const GET: APIRoute = async ({ locals }) => {\n\tconst { emdash, user } = locals;\n\n\tif (!emdash?.db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\tconst denied = requirePerm(user, \"settings:read\");\n\tif (denied) return denied;\n\n\ttry {\n\t\tconst result = await handleSettingsGet(emdash.db, emdash.storage);\n\t\treturn unwrapResult(result);\n\t} catch (error) {\n\t\treturn handleError(error, \"Failed to get settings\", \"SETTINGS_READ_ERROR\");\n\t}\n};\n\n/**\n * POST /_emdash/api/settings\n *\n * Updates site settings. Accepts a partial settings object.\n * Merges with existing settings and returns the updated settings.\n */\nexport const POST: APIRoute = async ({ request, locals }) => {\n\tconst { emdash, user } = locals;\n\n\tif (!emdash?.db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\tconst denied = requirePerm(user, \"settings:manage\");\n\tif (denied) return denied;\n\n\ttry {\n\t\tconst body = await parseBody(request, settingsUpdateBody);\n\t\tif (isParseError(body)) return body;\n\n\t\tconst result = await handleSettingsUpdate(emdash.db, emdash.storage, body);\n\t\treturn unwrapResult(result);\n\t} catch (error) {\n\t\treturn handleError(error, \"Failed to update settings\", \"SETTINGS_UPDATE_ERROR\");\n\t}\n};\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"settings.mjs","names":[],"sources":["../../../../src/astro/routes/api/settings.ts"],"sourcesContent":["/**\n * Site Settings API endpoint\n *\n * GET /_emdash/api/settings - Get all site settings\n * POST /_emdash/api/settings - Update site settings\n */\n\nimport type { APIRoute } from \"astro\";\n\nimport { requirePerm } from \"#api/authorize.js\";\nimport { apiError, handleError, unwrapResult } from \"#api/error.js\";\nimport { handleSettingsGet, handleSettingsUpdate } from \"#api/handlers/settings.js\";\nimport { isParseError, parseBody } from \"#api/parse.js\";\nimport { settingsUpdateBody } from \"#api/schemas.js\";\n\nexport const prerender = false;\n\n/**\n * GET /_emdash/api/settings\n *\n * Returns all site settings as a JSON object.\n * Unset values are undefined. Media references include resolved URLs.\n */\nexport const GET: APIRoute = async ({ locals }) => {\n\tconst { emdash, user } = locals;\n\n\tif (!emdash?.db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\tconst denied = requirePerm(user, \"settings:read\");\n\tif (denied) return denied;\n\n\ttry {\n\t\tconst result = await handleSettingsGet(emdash.db, emdash.storage);\n\t\treturn unwrapResult(result);\n\t} catch (error) {\n\t\treturn handleError(error, \"Failed to get settings\", \"SETTINGS_READ_ERROR\");\n\t}\n};\n\n/**\n * POST /_emdash/api/settings\n *\n * Updates site settings. Accepts a partial settings object.\n * Merges with existing settings and returns the updated settings.\n */\nexport const POST: APIRoute = async ({ request, locals }) => {\n\tconst { emdash, user } = locals;\n\n\tif (!emdash?.db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\tconst denied = requirePerm(user, \"settings:manage\");\n\tif (denied) return denied;\n\n\ttry {\n\t\tconst body = await parseBody(request, settingsUpdateBody);\n\t\tif (isParseError(body)) return body;\n\n\t\tconst result = await handleSettingsUpdate(emdash.db, emdash.storage, body);\n\t\treturn unwrapResult(result);\n\t} catch (error) {\n\t\treturn handleError(error, \"Failed to update settings\", \"SETTINGS_UPDATE_ERROR\");\n\t}\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAeA,MAAa,YAAY;;;;;;;AAQzB,MAAa,MAAgB,OAAO,EAAE,aAAa;CAClD,MAAM,EAAE,QAAQ,SAAS;AAEzB,KAAI,CAAC,QAAQ,GACZ,QAAO,SAAS,kBAAkB,6BAA6B,IAAI;CAGpE,MAAM,SAAS,YAAY,MAAM,gBAAgB;AACjD,KAAI,OAAQ,QAAO;AAEnB,KAAI;AAEH,SAAO,aADQ,MAAM,kBAAkB,OAAO,IAAI,OAAO,QAAQ,CACtC;UACnB,OAAO;AACf,SAAO,YAAY,OAAO,0BAA0B,sBAAsB;;;;;;;;;AAU5E,MAAa,OAAiB,OAAO,EAAE,SAAS,aAAa;CAC5D,MAAM,EAAE,QAAQ,SAAS;AAEzB,KAAI,CAAC,QAAQ,GACZ,QAAO,SAAS,kBAAkB,6BAA6B,IAAI;CAGpE,MAAM,SAAS,YAAY,MAAM,kBAAkB;AACnD,KAAI,OAAQ,QAAO;AAEnB,KAAI;EACH,MAAM,OAAO,MAAM,UAAU,SAAS,mBAAmB;AACzD,MAAI,aAAa,KAAK,CAAE,QAAO;AAG/B,SAAO,aADQ,MAAM,qBAAqB,OAAO,IAAI,OAAO,SAAS,KAAK,CAC/C;UACnB,OAAO;AACf,SAAO,YAAY,OAAO,6BAA6B,wBAAwB"}
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import "../../../../base64-CqR-7kqF.mjs";
|
|
2
2
|
import "../../../../types-BXSUSAjt.mjs";
|
|
3
3
|
import { t as OptionsRepository } from "../../../../options-BPCVnesz.mjs";
|
|
4
|
-
import { n as apiSuccess, r as handleError, t as apiError } from "../../../../error-
|
|
5
|
-
import { n as parseBody, t as isParseError } from "../../../../parse-
|
|
6
|
-
import "../../../../redirects-
|
|
7
|
-
import { l as setupAdminVerifyBody } from "../../../../byline-fields-
|
|
4
|
+
import { n as apiSuccess, r as handleError, t as apiError } from "../../../../error-CNn_w7jf.mjs";
|
|
5
|
+
import { n as parseBody, t as isParseError } from "../../../../parse-DzSrk1t8.mjs";
|
|
6
|
+
import "../../../../redirects-6Zg2SoYo.mjs";
|
|
7
|
+
import { l as setupAdminVerifyBody } from "../../../../byline-fields-B0NO1yUB.mjs";
|
|
8
|
+
import "../../../../status-2gZklYuj.mjs";
|
|
8
9
|
import "../../../../api/schemas/index.mjs";
|
|
9
|
-
import { n as getPublicOrigin } from "../../../../public-url-
|
|
10
|
-
import { n as validateAllowedOrigins, t as getConfiguredAllowedOrigins } from "../../../../allowed-origins-
|
|
11
|
-
import { n as createChallengeStore } from "../../../../challenge-store-
|
|
12
|
-
import { t as getPasskeyConfig } from "../../../../passkey-config-
|
|
13
|
-
import { t as SETUP_NONCE_COOKIE } from "../../../../setup-nonce-
|
|
10
|
+
import { n as getPublicOrigin } from "../../../../public-url-D_zARuvZ.mjs";
|
|
11
|
+
import { n as validateAllowedOrigins, t as getConfiguredAllowedOrigins } from "../../../../allowed-origins-BqC8cul8.mjs";
|
|
12
|
+
import { n as createChallengeStore } from "../../../../challenge-store-woE0bbCf.mjs";
|
|
13
|
+
import { t as getPasskeyConfig } from "../../../../passkey-config-BpjbE_Uv.mjs";
|
|
14
|
+
import { t as SETUP_NONCE_COOKIE } from "../../../../setup-nonce-169xl4fV.mjs";
|
|
14
15
|
import { createKyselyAdapter } from "@emdash-cms/auth/adapters/kysely";
|
|
15
16
|
import { Role, secureCompare } from "@emdash-cms/auth";
|
|
16
17
|
import { registerPasskey, verifyRegistrationResponse } from "@emdash-cms/auth/passkey";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin-verify.mjs","names":[],"sources":["../../../../../src/astro/routes/api/setup/admin-verify.ts"],"sourcesContent":["/**\n * POST /_emdash/api/setup/admin/verify\n *\n * Complete admin creation by verifying the passkey registration\n */\n\nimport type { APIRoute } from \"astro\";\n\nexport const prerender = false;\n\nimport { Role, secureCompare } from \"@emdash-cms/auth\";\nimport { createKyselyAdapter } from \"@emdash-cms/auth/adapters/kysely\";\nimport { verifyRegistrationResponse, registerPasskey } from \"@emdash-cms/auth/passkey\";\n\nimport { apiError, apiSuccess, handleError } from \"#api/error.js\";\nimport { isParseError, parseBody } from \"#api/parse.js\";\nimport { getPublicOrigin } from \"#api/public-url.js\";\nimport { setupAdminVerifyBody } from \"#api/schemas.js\";\nimport { getConfiguredAllowedOrigins, validateAllowedOrigins } from \"#auth/allowed-origins.js\";\nimport { createChallengeStore } from \"#auth/challenge-store.js\";\nimport { getPasskeyConfig } from \"#auth/passkey-config.js\";\nimport { SETUP_NONCE_COOKIE } from \"#auth/setup-nonce.js\";\nimport { OptionsRepository } from \"#db/repositories/options.js\";\n\nexport const POST: APIRoute = async ({ cookies, request, locals }) => {\n\tconst { emdash } = locals;\n\n\tif (!emdash?.db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\ttry {\n\t\t// Check if setup is already complete\n\t\tconst options = new OptionsRepository(emdash.db);\n\t\tconst setupComplete = await options.get(\"emdash:setup_complete\");\n\n\t\tif (setupComplete === true || setupComplete === \"true\") {\n\t\t\treturn apiError(\"SETUP_COMPLETE\", \"Setup already complete\", 400);\n\t\t}\n\n\t\t// Check if any users exist\n\t\tconst adapter = createKyselyAdapter(emdash.db);\n\t\tconst userCount = await adapter.countUsers();\n\n\t\tif (userCount > 0) {\n\t\t\treturn apiError(\"ADMIN_EXISTS\", \"Admin user already exists\", 400);\n\t\t}\n\n\t\t// Get setup state\n\t\tconst setupState = await options.get<{\n\t\t\tstep?: string;\n\t\t\temail?: string;\n\t\t\tname?: string | null;\n\t\t\tnonce?: string;\n\t\t}>(\"emdash:setup_state\");\n\n\t\tif (!setupState || setupState.step !== \"admin\") {\n\t\t\treturn apiError(\"INVALID_STATE\", \"Invalid setup state. Please restart setup.\", 400);\n\t\t}\n\n\t\t// Verify the session nonce. The cookie was minted by POST /setup/admin\n\t\t// and stored alongside setup_state; presenting a matching cookie is\n\t\t// proof that this verify call comes from the same browser that\n\t\t// started the admin step. Constant-time compare to avoid leaking the\n\t\t// stored value through timing.\n\t\tconst cookieNonce = cookies.get(SETUP_NONCE_COOKIE)?.value;\n\t\tif (!setupState.nonce || !cookieNonce || !secureCompare(cookieNonce, setupState.nonce)) {\n\t\t\treturn apiError(\n\t\t\t\t\"INVALID_STATE\",\n\t\t\t\t\"Setup session expired or tampered with. Please restart the admin step.\",\n\t\t\t\t400,\n\t\t\t);\n\t\t}\n\n\t\tif (!setupState.email) {\n\t\t\treturn apiError(\"INVALID_STATE\", \"Invalid setup state. Please restart setup.\", 400);\n\t\t}\n\n\t\t// Parse request body\n\t\tconst body = await parseBody(request, setupAdminVerifyBody);\n\t\tif (isParseError(body)) return body;\n\n\t\t// Get passkey config\n\t\tconst url = new URL(request.url);\n\t\tconst siteName = (await options.get<string>(\"emdash:site_title\")) ?? undefined;\n\t\tconst siteUrl = getPublicOrigin(url, emdash?.config);\n\t\tconst allowedOrigins = validateAllowedOrigins(\n\t\t\tsiteUrl,\n\t\t\tgetConfiguredAllowedOrigins(emdash?.config),\n\t\t);\n\t\tconst passkeyConfig = getPasskeyConfig(url, siteName, siteUrl, allowedOrigins);\n\n\t\t// Verify the registration response\n\t\tconst challengeStore = createChallengeStore(emdash.db);\n\n\t\tconst verified = await verifyRegistrationResponse(\n\t\t\tpasskeyConfig,\n\t\t\tbody.credential,\n\t\t\tchallengeStore,\n\t\t);\n\n\t\t// Create the admin user\n\t\tconst user = await adapter.createUser({\n\t\t\temail: setupState.email,\n\t\t\tname: setupState.name ?? null,\n\t\t\trole: Role.ADMIN,\n\t\t\temailVerified: false, // No email verification for first user\n\t\t});\n\n\t\t// Register the passkey\n\t\tawait registerPasskey(adapter, user.id, verified, \"Setup passkey\");\n\n\t\t// Mark setup as complete\n\t\tawait options.set(\"emdash:setup_complete\", true);\n\n\t\t// Clean up setup state and the session nonce cookie\n\t\tawait options.delete(\"emdash:setup_state\");\n\t\tcookies.delete(SETUP_NONCE_COOKIE, { path: \"/_emdash/\" });\n\n\t\treturn apiSuccess({\n\t\t\tsuccess: true,\n\t\t\tuser: {\n\t\t\t\tid: user.id,\n\t\t\t\temail: user.email,\n\t\t\t\tname: user.name,\n\t\t\t\trole: user.role,\n\t\t\t},\n\t\t});\n\t} catch (error) {\n\t\treturn handleError(error, \"Failed to verify admin setup\", \"SETUP_VERIFY_ERROR\");\n\t}\n};\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"admin-verify.mjs","names":[],"sources":["../../../../../src/astro/routes/api/setup/admin-verify.ts"],"sourcesContent":["/**\n * POST /_emdash/api/setup/admin/verify\n *\n * Complete admin creation by verifying the passkey registration\n */\n\nimport type { APIRoute } from \"astro\";\n\nexport const prerender = false;\n\nimport { Role, secureCompare } from \"@emdash-cms/auth\";\nimport { createKyselyAdapter } from \"@emdash-cms/auth/adapters/kysely\";\nimport { verifyRegistrationResponse, registerPasskey } from \"@emdash-cms/auth/passkey\";\n\nimport { apiError, apiSuccess, handleError } from \"#api/error.js\";\nimport { isParseError, parseBody } from \"#api/parse.js\";\nimport { getPublicOrigin } from \"#api/public-url.js\";\nimport { setupAdminVerifyBody } from \"#api/schemas.js\";\nimport { getConfiguredAllowedOrigins, validateAllowedOrigins } from \"#auth/allowed-origins.js\";\nimport { createChallengeStore } from \"#auth/challenge-store.js\";\nimport { getPasskeyConfig } from \"#auth/passkey-config.js\";\nimport { SETUP_NONCE_COOKIE } from \"#auth/setup-nonce.js\";\nimport { OptionsRepository } from \"#db/repositories/options.js\";\n\nexport const POST: APIRoute = async ({ cookies, request, locals }) => {\n\tconst { emdash } = locals;\n\n\tif (!emdash?.db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\ttry {\n\t\t// Check if setup is already complete\n\t\tconst options = new OptionsRepository(emdash.db);\n\t\tconst setupComplete = await options.get(\"emdash:setup_complete\");\n\n\t\tif (setupComplete === true || setupComplete === \"true\") {\n\t\t\treturn apiError(\"SETUP_COMPLETE\", \"Setup already complete\", 400);\n\t\t}\n\n\t\t// Check if any users exist\n\t\tconst adapter = createKyselyAdapter(emdash.db);\n\t\tconst userCount = await adapter.countUsers();\n\n\t\tif (userCount > 0) {\n\t\t\treturn apiError(\"ADMIN_EXISTS\", \"Admin user already exists\", 400);\n\t\t}\n\n\t\t// Get setup state\n\t\tconst setupState = await options.get<{\n\t\t\tstep?: string;\n\t\t\temail?: string;\n\t\t\tname?: string | null;\n\t\t\tnonce?: string;\n\t\t}>(\"emdash:setup_state\");\n\n\t\tif (!setupState || setupState.step !== \"admin\") {\n\t\t\treturn apiError(\"INVALID_STATE\", \"Invalid setup state. Please restart setup.\", 400);\n\t\t}\n\n\t\t// Verify the session nonce. The cookie was minted by POST /setup/admin\n\t\t// and stored alongside setup_state; presenting a matching cookie is\n\t\t// proof that this verify call comes from the same browser that\n\t\t// started the admin step. Constant-time compare to avoid leaking the\n\t\t// stored value through timing.\n\t\tconst cookieNonce = cookies.get(SETUP_NONCE_COOKIE)?.value;\n\t\tif (!setupState.nonce || !cookieNonce || !secureCompare(cookieNonce, setupState.nonce)) {\n\t\t\treturn apiError(\n\t\t\t\t\"INVALID_STATE\",\n\t\t\t\t\"Setup session expired or tampered with. Please restart the admin step.\",\n\t\t\t\t400,\n\t\t\t);\n\t\t}\n\n\t\tif (!setupState.email) {\n\t\t\treturn apiError(\"INVALID_STATE\", \"Invalid setup state. Please restart setup.\", 400);\n\t\t}\n\n\t\t// Parse request body\n\t\tconst body = await parseBody(request, setupAdminVerifyBody);\n\t\tif (isParseError(body)) return body;\n\n\t\t// Get passkey config\n\t\tconst url = new URL(request.url);\n\t\tconst siteName = (await options.get<string>(\"emdash:site_title\")) ?? undefined;\n\t\tconst siteUrl = getPublicOrigin(url, emdash?.config);\n\t\tconst allowedOrigins = validateAllowedOrigins(\n\t\t\tsiteUrl,\n\t\t\tgetConfiguredAllowedOrigins(emdash?.config),\n\t\t);\n\t\tconst passkeyConfig = getPasskeyConfig(url, siteName, siteUrl, allowedOrigins);\n\n\t\t// Verify the registration response\n\t\tconst challengeStore = createChallengeStore(emdash.db);\n\n\t\tconst verified = await verifyRegistrationResponse(\n\t\t\tpasskeyConfig,\n\t\t\tbody.credential,\n\t\t\tchallengeStore,\n\t\t);\n\n\t\t// Create the admin user\n\t\tconst user = await adapter.createUser({\n\t\t\temail: setupState.email,\n\t\t\tname: setupState.name ?? null,\n\t\t\trole: Role.ADMIN,\n\t\t\temailVerified: false, // No email verification for first user\n\t\t});\n\n\t\t// Register the passkey\n\t\tawait registerPasskey(adapter, user.id, verified, \"Setup passkey\");\n\n\t\t// Mark setup as complete\n\t\tawait options.set(\"emdash:setup_complete\", true);\n\n\t\t// Clean up setup state and the session nonce cookie\n\t\tawait options.delete(\"emdash:setup_state\");\n\t\tcookies.delete(SETUP_NONCE_COOKIE, { path: \"/_emdash/\" });\n\n\t\treturn apiSuccess({\n\t\t\tsuccess: true,\n\t\t\tuser: {\n\t\t\t\tid: user.id,\n\t\t\t\temail: user.email,\n\t\t\t\tname: user.name,\n\t\t\t\trole: user.role,\n\t\t\t},\n\t\t});\n\t} catch (error) {\n\t\treturn handleError(error, \"Failed to verify admin setup\", \"SETUP_VERIFY_ERROR\");\n\t}\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAQA,MAAa,YAAY;AAgBzB,MAAa,OAAiB,OAAO,EAAE,SAAS,SAAS,aAAa;CACrE,MAAM,EAAE,WAAW;AAEnB,KAAI,CAAC,QAAQ,GACZ,QAAO,SAAS,kBAAkB,6BAA6B,IAAI;AAGpE,KAAI;EAEH,MAAM,UAAU,IAAI,kBAAkB,OAAO,GAAG;EAChD,MAAM,gBAAgB,MAAM,QAAQ,IAAI,wBAAwB;AAEhE,MAAI,kBAAkB,QAAQ,kBAAkB,OAC/C,QAAO,SAAS,kBAAkB,0BAA0B,IAAI;EAIjE,MAAM,UAAU,oBAAoB,OAAO,GAAG;AAG9C,MAFkB,MAAM,QAAQ,YAAY,GAE5B,EACf,QAAO,SAAS,gBAAgB,6BAA6B,IAAI;EAIlE,MAAM,aAAa,MAAM,QAAQ,IAK9B,qBAAqB;AAExB,MAAI,CAAC,cAAc,WAAW,SAAS,QACtC,QAAO,SAAS,iBAAiB,8CAA8C,IAAI;EAQpF,MAAM,cAAc,QAAQ,IAAI,mBAAmB,EAAE;AACrD,MAAI,CAAC,WAAW,SAAS,CAAC,eAAe,CAAC,cAAc,aAAa,WAAW,MAAM,CACrF,QAAO,SACN,iBACA,0EACA,IACA;AAGF,MAAI,CAAC,WAAW,MACf,QAAO,SAAS,iBAAiB,8CAA8C,IAAI;EAIpF,MAAM,OAAO,MAAM,UAAU,SAAS,qBAAqB;AAC3D,MAAI,aAAa,KAAK,CAAE,QAAO;EAG/B,MAAM,MAAM,IAAI,IAAI,QAAQ,IAAI;EAChC,MAAM,WAAY,MAAM,QAAQ,IAAY,oBAAoB,IAAK;EACrE,MAAM,UAAU,gBAAgB,KAAK,QAAQ,OAAO;EAKpD,MAAM,gBAAgB,iBAAiB,KAAK,UAAU,SAJ/B,uBACtB,SACA,4BAA4B,QAAQ,OAAO,CAC3C,CAC6E;EAG9E,MAAM,iBAAiB,qBAAqB,OAAO,GAAG;EAEtD,MAAM,WAAW,MAAM,2BACtB,eACA,KAAK,YACL,eACA;EAGD,MAAM,OAAO,MAAM,QAAQ,WAAW;GACrC,OAAO,WAAW;GAClB,MAAM,WAAW,QAAQ;GACzB,MAAM,KAAK;GACX,eAAe;GACf,CAAC;AAGF,QAAM,gBAAgB,SAAS,KAAK,IAAI,UAAU,gBAAgB;AAGlE,QAAM,QAAQ,IAAI,yBAAyB,KAAK;AAGhD,QAAM,QAAQ,OAAO,qBAAqB;AAC1C,UAAQ,OAAO,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEzD,SAAO,WAAW;GACjB,SAAS;GACT,MAAM;IACL,IAAI,KAAK;IACT,OAAO,KAAK;IACZ,MAAM,KAAK;IACX,MAAM,KAAK;IACX;GACD,CAAC;UACM,OAAO;AACf,SAAO,YAAY,OAAO,gCAAgC,qBAAqB"}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import "../../../../base64-CqR-7kqF.mjs";
|
|
2
2
|
import "../../../../types-BXSUSAjt.mjs";
|
|
3
3
|
import { t as OptionsRepository } from "../../../../options-BPCVnesz.mjs";
|
|
4
|
-
import { n as apiSuccess, r as handleError, t as apiError } from "../../../../error-
|
|
5
|
-
import { n as parseBody, t as isParseError } from "../../../../parse-
|
|
6
|
-
import "../../../../redirects-
|
|
7
|
-
import { c as setupAdminBody } from "../../../../byline-fields-
|
|
4
|
+
import { n as apiSuccess, r as handleError, t as apiError } from "../../../../error-CNn_w7jf.mjs";
|
|
5
|
+
import { n as parseBody, t as isParseError } from "../../../../parse-DzSrk1t8.mjs";
|
|
6
|
+
import "../../../../redirects-6Zg2SoYo.mjs";
|
|
7
|
+
import { c as setupAdminBody } from "../../../../byline-fields-B0NO1yUB.mjs";
|
|
8
|
+
import "../../../../status-2gZklYuj.mjs";
|
|
8
9
|
import "../../../../api/schemas/index.mjs";
|
|
9
|
-
import { n as getPublicOrigin } from "../../../../public-url-
|
|
10
|
-
import { n as createChallengeStore } from "../../../../challenge-store-
|
|
11
|
-
import { t as getPasskeyConfig } from "../../../../passkey-config-
|
|
12
|
-
import { n as SETUP_NONCE_MAX_AGE_SECONDS, t as SETUP_NONCE_COOKIE } from "../../../../setup-nonce-
|
|
10
|
+
import { n as getPublicOrigin } from "../../../../public-url-D_zARuvZ.mjs";
|
|
11
|
+
import { n as createChallengeStore } from "../../../../challenge-store-woE0bbCf.mjs";
|
|
12
|
+
import { t as getPasskeyConfig } from "../../../../passkey-config-BpjbE_Uv.mjs";
|
|
13
|
+
import { n as SETUP_NONCE_MAX_AGE_SECONDS, t as SETUP_NONCE_COOKIE } from "../../../../setup-nonce-169xl4fV.mjs";
|
|
13
14
|
import { createKyselyAdapter } from "@emdash-cms/auth/adapters/kysely";
|
|
14
15
|
import { generateToken } from "@emdash-cms/auth";
|
|
15
16
|
import { generateRegistrationOptions } from "@emdash-cms/auth/passkey";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin.mjs","names":[],"sources":["../../../../../src/astro/routes/api/setup/admin.ts"],"sourcesContent":["/**\n * POST /_emdash/api/setup/admin\n *\n * Step 3 of setup: Start admin creation by returning passkey registration options\n */\n\nimport type { APIRoute } from \"astro\";\n\nexport const prerender = false;\n\nimport { generateToken } from \"@emdash-cms/auth\";\nimport { createKyselyAdapter } from \"@emdash-cms/auth/adapters/kysely\";\nimport { generateRegistrationOptions } from \"@emdash-cms/auth/passkey\";\n\nimport { apiError, apiSuccess, handleError } from \"#api/error.js\";\nimport { isParseError, parseBody } from \"#api/parse.js\";\nimport { getPublicOrigin } from \"#api/public-url.js\";\nimport { setupAdminBody } from \"#api/schemas.js\";\nimport { createChallengeStore } from \"#auth/challenge-store.js\";\nimport { getPasskeyConfig } from \"#auth/passkey-config.js\";\nimport { SETUP_NONCE_COOKIE, SETUP_NONCE_MAX_AGE_SECONDS } from \"#auth/setup-nonce.js\";\nimport { OptionsRepository } from \"#db/repositories/options.js\";\n\nexport const POST: APIRoute = async ({ cookies, request, locals }) => {\n\tconst { emdash } = locals;\n\n\tif (!emdash?.db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\ttry {\n\t\t// Check if setup is already complete\n\t\tconst options = new OptionsRepository(emdash.db);\n\t\tconst setupComplete = await options.get(\"emdash:setup_complete\");\n\n\t\tif (setupComplete === true || setupComplete === \"true\") {\n\t\t\treturn apiError(\"SETUP_COMPLETE\", \"Setup already complete\", 400);\n\t\t}\n\n\t\t// Check if any users exist\n\t\tconst adapter = createKyselyAdapter(emdash.db);\n\t\tconst userCount = await adapter.countUsers();\n\n\t\tif (userCount > 0) {\n\t\t\treturn apiError(\"ADMIN_EXISTS\", \"Admin user already exists\", 400);\n\t\t}\n\n\t\t// Parse request body\n\t\tconst body = await parseBody(request, setupAdminBody);\n\t\tif (isParseError(body)) return body;\n\n\t\t// Preserve title/tagline from step 1 by reading existing setup state\n\t\t// before we overwrite it below.\n\t\tconst existingState = await options.get<Record<string, unknown>>(\"emdash:setup_state\");\n\n\t\t// Mint a fresh session nonce. This binds the follow-up\n\t\t// /setup/admin/verify call to the same browser that made this\n\t\t// request, so an unauthenticated attacker on another host cannot\n\t\t// substitute their own email into the setup state during the\n\t\t// setup window. Rotates on every call so a legitimate retry\n\t\t// always gets a working session.\n\t\tconst nonce = generateToken();\n\n\t\t// Get passkey config\n\t\tconst url = new URL(request.url);\n\t\tconst siteName = (await options.get<string>(\"emdash:site_title\")) ?? undefined;\n\t\tconst siteUrl = getPublicOrigin(url, emdash?.config);\n\t\tconst passkeyConfig = getPasskeyConfig(url, siteName, siteUrl);\n\n\t\t// Generate registration options\n\t\tconst challengeStore = createChallengeStore(emdash.db);\n\n\t\t// Create a temporary user object for registration options\n\t\t// (not persisted until passkey is verified)\n\t\tconst tempUser = {\n\t\t\tid: `setup-${Date.now()}`, // Temporary ID\n\t\t\temail: body.email.toLowerCase(),\n\t\t\tname: body.name || null,\n\t\t};\n\n\t\tconst registrationOptions = await generateRegistrationOptions(\n\t\t\tpasskeyConfig,\n\t\t\ttempUser,\n\t\t\t[], // No existing credentials\n\t\t\tchallengeStore,\n\t\t);\n\n\t\t// Store the nonce alongside the rest of the setup state, preserving\n\t\t// title/tagline from step 1. The verify endpoint will constant-time\n\t\t// compare the nonce with the incoming cookie.\n\t\tawait options.set(\"emdash:setup_state\", {\n\t\t\t...existingState,\n\t\t\tstep: \"admin\",\n\t\t\temail: body.email.toLowerCase(),\n\t\t\tname: body.name || null,\n\t\t\ttempUserId: tempUser.id,\n\t\t\tnonce,\n\t\t});\n\n\t\t// HttpOnly + SameSite=Strict + path-scoped. The cookie must not be\n\t\t// accessible to JS (nothing in the admin UI needs to read it) and\n\t\t// must not be sent on cross-site navigations. The /_emdash/ path\n\t\t// scope keeps it away from user-authored frontend code.\n\t\t//\n\t\t// Derive `secure` from the public origin, not the internal request\n\t\t// URL. Behind a TLS-terminating reverse proxy the internal hop is\n\t\t// often `http:` while the browser-facing origin is `https:` —\n\t\t// using `url.protocol` there would drop the Secure flag on a\n\t\t// sensitive cookie over the public HTTPS connection.\n\t\tconst publicOrigin = new URL(siteUrl);\n\t\tcookies.set(SETUP_NONCE_COOKIE, nonce, {\n\t\t\tpath: \"/_emdash/\",\n\t\t\thttpOnly: true,\n\t\t\tsameSite: \"strict\",\n\t\t\tsecure: publicOrigin.protocol === \"https:\",\n\t\t\tmaxAge: SETUP_NONCE_MAX_AGE_SECONDS,\n\t\t});\n\n\t\treturn apiSuccess({\n\t\t\tsuccess: true,\n\t\t\toptions: registrationOptions,\n\t\t});\n\t} catch (error) {\n\t\treturn handleError(error, \"Failed to create admin\", \"SETUP_ADMIN_ERROR\");\n\t}\n};\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"admin.mjs","names":[],"sources":["../../../../../src/astro/routes/api/setup/admin.ts"],"sourcesContent":["/**\n * POST /_emdash/api/setup/admin\n *\n * Step 3 of setup: Start admin creation by returning passkey registration options\n */\n\nimport type { APIRoute } from \"astro\";\n\nexport const prerender = false;\n\nimport { generateToken } from \"@emdash-cms/auth\";\nimport { createKyselyAdapter } from \"@emdash-cms/auth/adapters/kysely\";\nimport { generateRegistrationOptions } from \"@emdash-cms/auth/passkey\";\n\nimport { apiError, apiSuccess, handleError } from \"#api/error.js\";\nimport { isParseError, parseBody } from \"#api/parse.js\";\nimport { getPublicOrigin } from \"#api/public-url.js\";\nimport { setupAdminBody } from \"#api/schemas.js\";\nimport { createChallengeStore } from \"#auth/challenge-store.js\";\nimport { getPasskeyConfig } from \"#auth/passkey-config.js\";\nimport { SETUP_NONCE_COOKIE, SETUP_NONCE_MAX_AGE_SECONDS } from \"#auth/setup-nonce.js\";\nimport { OptionsRepository } from \"#db/repositories/options.js\";\n\nexport const POST: APIRoute = async ({ cookies, request, locals }) => {\n\tconst { emdash } = locals;\n\n\tif (!emdash?.db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\ttry {\n\t\t// Check if setup is already complete\n\t\tconst options = new OptionsRepository(emdash.db);\n\t\tconst setupComplete = await options.get(\"emdash:setup_complete\");\n\n\t\tif (setupComplete === true || setupComplete === \"true\") {\n\t\t\treturn apiError(\"SETUP_COMPLETE\", \"Setup already complete\", 400);\n\t\t}\n\n\t\t// Check if any users exist\n\t\tconst adapter = createKyselyAdapter(emdash.db);\n\t\tconst userCount = await adapter.countUsers();\n\n\t\tif (userCount > 0) {\n\t\t\treturn apiError(\"ADMIN_EXISTS\", \"Admin user already exists\", 400);\n\t\t}\n\n\t\t// Parse request body\n\t\tconst body = await parseBody(request, setupAdminBody);\n\t\tif (isParseError(body)) return body;\n\n\t\t// Preserve title/tagline from step 1 by reading existing setup state\n\t\t// before we overwrite it below.\n\t\tconst existingState = await options.get<Record<string, unknown>>(\"emdash:setup_state\");\n\n\t\t// Mint a fresh session nonce. This binds the follow-up\n\t\t// /setup/admin/verify call to the same browser that made this\n\t\t// request, so an unauthenticated attacker on another host cannot\n\t\t// substitute their own email into the setup state during the\n\t\t// setup window. Rotates on every call so a legitimate retry\n\t\t// always gets a working session.\n\t\tconst nonce = generateToken();\n\n\t\t// Get passkey config\n\t\tconst url = new URL(request.url);\n\t\tconst siteName = (await options.get<string>(\"emdash:site_title\")) ?? undefined;\n\t\tconst siteUrl = getPublicOrigin(url, emdash?.config);\n\t\tconst passkeyConfig = getPasskeyConfig(url, siteName, siteUrl);\n\n\t\t// Generate registration options\n\t\tconst challengeStore = createChallengeStore(emdash.db);\n\n\t\t// Create a temporary user object for registration options\n\t\t// (not persisted until passkey is verified)\n\t\tconst tempUser = {\n\t\t\tid: `setup-${Date.now()}`, // Temporary ID\n\t\t\temail: body.email.toLowerCase(),\n\t\t\tname: body.name || null,\n\t\t};\n\n\t\tconst registrationOptions = await generateRegistrationOptions(\n\t\t\tpasskeyConfig,\n\t\t\ttempUser,\n\t\t\t[], // No existing credentials\n\t\t\tchallengeStore,\n\t\t);\n\n\t\t// Store the nonce alongside the rest of the setup state, preserving\n\t\t// title/tagline from step 1. The verify endpoint will constant-time\n\t\t// compare the nonce with the incoming cookie.\n\t\tawait options.set(\"emdash:setup_state\", {\n\t\t\t...existingState,\n\t\t\tstep: \"admin\",\n\t\t\temail: body.email.toLowerCase(),\n\t\t\tname: body.name || null,\n\t\t\ttempUserId: tempUser.id,\n\t\t\tnonce,\n\t\t});\n\n\t\t// HttpOnly + SameSite=Strict + path-scoped. The cookie must not be\n\t\t// accessible to JS (nothing in the admin UI needs to read it) and\n\t\t// must not be sent on cross-site navigations. The /_emdash/ path\n\t\t// scope keeps it away from user-authored frontend code.\n\t\t//\n\t\t// Derive `secure` from the public origin, not the internal request\n\t\t// URL. Behind a TLS-terminating reverse proxy the internal hop is\n\t\t// often `http:` while the browser-facing origin is `https:` —\n\t\t// using `url.protocol` there would drop the Secure flag on a\n\t\t// sensitive cookie over the public HTTPS connection.\n\t\tconst publicOrigin = new URL(siteUrl);\n\t\tcookies.set(SETUP_NONCE_COOKIE, nonce, {\n\t\t\tpath: \"/_emdash/\",\n\t\t\thttpOnly: true,\n\t\t\tsameSite: \"strict\",\n\t\t\tsecure: publicOrigin.protocol === \"https:\",\n\t\t\tmaxAge: SETUP_NONCE_MAX_AGE_SECONDS,\n\t\t});\n\n\t\treturn apiSuccess({\n\t\t\tsuccess: true,\n\t\t\toptions: registrationOptions,\n\t\t});\n\t} catch (error) {\n\t\treturn handleError(error, \"Failed to create admin\", \"SETUP_ADMIN_ERROR\");\n\t}\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAQA,MAAa,YAAY;AAezB,MAAa,OAAiB,OAAO,EAAE,SAAS,SAAS,aAAa;CACrE,MAAM,EAAE,WAAW;AAEnB,KAAI,CAAC,QAAQ,GACZ,QAAO,SAAS,kBAAkB,6BAA6B,IAAI;AAGpE,KAAI;EAEH,MAAM,UAAU,IAAI,kBAAkB,OAAO,GAAG;EAChD,MAAM,gBAAgB,MAAM,QAAQ,IAAI,wBAAwB;AAEhE,MAAI,kBAAkB,QAAQ,kBAAkB,OAC/C,QAAO,SAAS,kBAAkB,0BAA0B,IAAI;AAOjE,MAFkB,MADF,oBAAoB,OAAO,GAAG,CACd,YAAY,GAE5B,EACf,QAAO,SAAS,gBAAgB,6BAA6B,IAAI;EAIlE,MAAM,OAAO,MAAM,UAAU,SAAS,eAAe;AACrD,MAAI,aAAa,KAAK,CAAE,QAAO;EAI/B,MAAM,gBAAgB,MAAM,QAAQ,IAA6B,qBAAqB;EAQtF,MAAM,QAAQ,eAAe;EAG7B,MAAM,MAAM,IAAI,IAAI,QAAQ,IAAI;EAChC,MAAM,WAAY,MAAM,QAAQ,IAAY,oBAAoB,IAAK;EACrE,MAAM,UAAU,gBAAgB,KAAK,QAAQ,OAAO;EACpD,MAAM,gBAAgB,iBAAiB,KAAK,UAAU,QAAQ;EAG9D,MAAM,iBAAiB,qBAAqB,OAAO,GAAG;EAItD,MAAM,WAAW;GAChB,IAAI,SAAS,KAAK,KAAK;GACvB,OAAO,KAAK,MAAM,aAAa;GAC/B,MAAM,KAAK,QAAQ;GACnB;EAED,MAAM,sBAAsB,MAAM,4BACjC,eACA,UACA,EAAE,EACF,eACA;AAKD,QAAM,QAAQ,IAAI,sBAAsB;GACvC,GAAG;GACH,MAAM;GACN,OAAO,KAAK,MAAM,aAAa;GAC/B,MAAM,KAAK,QAAQ;GACnB,YAAY,SAAS;GACrB;GACA,CAAC;EAYF,MAAM,eAAe,IAAI,IAAI,QAAQ;AACrC,UAAQ,IAAI,oBAAoB,OAAO;GACtC,MAAM;GACN,UAAU;GACV,UAAU;GACV,QAAQ,aAAa,aAAa;GAClC,QAAQ;GACR,CAAC;AAEF,SAAO,WAAW;GACjB,SAAS;GACT,SAAS;GACT,CAAC;UACM,OAAO;AACf,SAAO,YAAY,OAAO,0BAA0B,oBAAoB"}
|
|
@@ -7,24 +7,25 @@ import "../../../../media-JOf3pNkw.mjs";
|
|
|
7
7
|
import "../../../../taxonomy-CdllE4oq.mjs";
|
|
8
8
|
import { t as OptionsRepository } from "../../../../options-BPCVnesz.mjs";
|
|
9
9
|
import "../../../../redirect-CRWIt8Zj.mjs";
|
|
10
|
-
import "../../../../
|
|
11
|
-
import "../../../../
|
|
12
|
-
import "../../../../byline-
|
|
13
|
-
import "../../../../
|
|
14
|
-
import "../../../../
|
|
15
|
-
import "../../../../
|
|
16
|
-
import "../../../../
|
|
17
|
-
import "../../../../
|
|
18
|
-
import
|
|
19
|
-
import "../../../../
|
|
20
|
-
import
|
|
21
|
-
import { t as
|
|
22
|
-
import { t as
|
|
23
|
-
import {
|
|
24
|
-
import "../../../../
|
|
25
|
-
import
|
|
26
|
-
import { t as
|
|
27
|
-
import { t as
|
|
10
|
+
import "../../../../init-lock-DlBHjf9-.mjs";
|
|
11
|
+
import "../../../../request-cache-UwmBAiUK.mjs";
|
|
12
|
+
import "../../../../byline-registry-DedidtqC.mjs";
|
|
13
|
+
import "../../../../byline-V_Qp1Ziw.mjs";
|
|
14
|
+
import "../../../../fts-manager-Cx5z8jdA.mjs";
|
|
15
|
+
import "../../../../registry-diMzD1Wf.mjs";
|
|
16
|
+
import "../../../../loader-BqWjcH3h.mjs";
|
|
17
|
+
import "../../../../settings-BjBsmVAo.mjs";
|
|
18
|
+
import "../../../../ssrf-XO05Voq6.mjs";
|
|
19
|
+
import { n as apiSuccess, r as handleError, t as apiError } from "../../../../error-CNn_w7jf.mjs";
|
|
20
|
+
import "../../../../ssrf-CRZGzjdL.mjs";
|
|
21
|
+
import { t as validateSeed } from "../../../../validate-DGhQPXzI.mjs";
|
|
22
|
+
import { t as applySeed } from "../../../../apply-CLjxheyb.mjs";
|
|
23
|
+
import { t as loadSeed } from "../../../../load-Dq91b_DK.mjs";
|
|
24
|
+
import { n as getPublicOrigin } from "../../../../public-url-D_zARuvZ.mjs";
|
|
25
|
+
import "../../../../api-tokens-C7ywRx7l.mjs";
|
|
26
|
+
import { t as handleApiTokenCreate } from "../../../../api-tokens-BFFkB0jB.mjs";
|
|
27
|
+
import { t as escapeHtml } from "../../../../escape-DPgcxcpL.mjs";
|
|
28
|
+
import { t as isSafeRedirect } from "../../../../redirect-C-OOkyku.mjs";
|
|
28
29
|
import { ulid } from "ulidx";
|
|
29
30
|
|
|
30
31
|
//#region src/astro/routes/api/setup/dev-bypass.ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev-bypass.mjs","names":[],"sources":["../../../../../src/astro/routes/api/setup/dev-bypass.ts"],"sourcesContent":["/**\n * POST /_emdash/api/setup/dev-bypass\n * GET /_emdash/api/setup/dev-bypass\n *\n * Development-only endpoint to bypass the setup wizard.\n * Runs migrations, creates a dev admin user, and marks setup complete.\n *\n * ONLY available when import.meta.env.DEV is true.\n *\n * Usage:\n * - GET with redirect: /_emdash/api/setup/dev-bypass?redirect=/_emdash/admin\n * - POST for API: Returns JSON with setup info\n *\n * For agent/browser testing, navigate to:\n * /_emdash/api/setup/dev-bypass?redirect=/_emdash/admin\n */\n\nimport type { APIRoute } from \"astro\";\n\nexport const prerender = false;\n\nimport { ulid } from \"ulidx\";\n\nimport { apiError, apiSuccess, handleError } from \"#api/error.js\";\nimport { escapeHtml } from \"#api/escape.js\";\nimport { handleApiTokenCreate } from \"#api/handlers/api-tokens.js\";\nimport { getPublicOrigin } from \"#api/public-url.js\";\nimport { isSafeRedirect } from \"#api/redirect.js\";\nimport { runMigrations } from \"#db/migrations/runner.js\";\nimport { OptionsRepository } from \"#db/repositories/options.js\";\nimport { applySeed } from \"#seed/apply.js\";\nimport { loadSeed } from \"#seed/load.js\";\nimport { validateSeed } from \"#seed/validate.js\";\n\n// RBAC role levels (matching @emdash-cms/auth)\nconst ROLE_ADMIN = 50;\n\nconst DEV_USER_EMAIL = \"dev@emdash.local\";\nconst DEV_USER_NAME = \"Dev Admin\";\nconst DEV_SITE_TITLE = \"EmDash Dev Site\";\n\nasync function handleDevBypass(context: Parameters<APIRoute>[0]): Promise<Response> {\n\t// CRITICAL: Only allow in development mode\n\tif (!import.meta.env.DEV) {\n\t\treturn apiError(\"FORBIDDEN\", \"Dev bypass is only available in development mode\", 403);\n\t}\n\n\tconst { locals, url, session } = context;\n\tconst { emdash } = locals;\n\n\tif (!emdash?.db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\ttry {\n\t\t// Run migrations\n\t\tconst migrations = await runMigrations(emdash.db);\n\t\tconsole.log(\"[setup-dev-bypass] Migrations applied:\", migrations.applied);\n\n\t\t// Apply seed (user seed or built-in default)\n\t\tconst seed = await loadSeed();\n\t\tconst validation = validateSeed(seed);\n\t\tif (validation.valid) {\n\t\t\tconst seedResult = await applySeed(emdash.db, seed, {\n\t\t\t\tincludeContent: true,\n\t\t\t\tonConflict: \"skip\",\n\t\t\t\tstorage: emdash.storage ?? undefined,\n\t\t\t});\n\t\t\tconsole.log(\n\t\t\t\t`[setup-dev-bypass] Seed applied: ${seedResult.collections.created} collections, ${seedResult.fields.created} fields`,\n\t\t\t);\n\t\t}\n\n\t\tconst options = new OptionsRepository(emdash.db);\n\n\t\t// Find or create dev user (direct DB access to avoid @emdash-cms/auth import issues in dev)\n\t\tconst existingUser = await emdash.db\n\t\t\t.selectFrom(\"users\")\n\t\t\t.selectAll()\n\t\t\t.where(\"email\", \"=\", DEV_USER_EMAIL)\n\t\t\t.executeTakeFirst();\n\n\t\tlet user: { id: string; email: string; name: string; role: number };\n\t\tlet userCreated = false;\n\n\t\tif (!existingUser) {\n\t\t\tconst now = new Date().toISOString();\n\t\t\tconst newUser = {\n\t\t\t\tid: ulid(),\n\t\t\t\temail: DEV_USER_EMAIL,\n\t\t\t\tname: DEV_USER_NAME,\n\t\t\t\trole: ROLE_ADMIN,\n\t\t\t\temail_verified: 1,\n\t\t\t\tcreated_at: now,\n\t\t\t\tupdated_at: now,\n\t\t\t};\n\n\t\t\tawait emdash.db.insertInto(\"users\").values(newUser).execute();\n\n\t\t\tuser = {\n\t\t\t\tid: newUser.id,\n\t\t\t\temail: newUser.email,\n\t\t\t\tname: newUser.name,\n\t\t\t\trole: newUser.role,\n\t\t\t};\n\t\t\tuserCreated = true;\n\t\t\tconsole.log(\"[setup-dev-bypass] Created dev admin user:\", user.email);\n\t\t} else {\n\t\t\tuser = {\n\t\t\t\tid: existingUser.id,\n\t\t\t\temail: existingUser.email,\n\t\t\t\tname: existingUser.name || DEV_USER_NAME,\n\t\t\t\trole: existingUser.role,\n\t\t\t};\n\t\t}\n\n\t\t// Set site title if not already set\n\t\tconst existingTitle = await options.get(\"emdash:site_title\");\n\t\tif (!existingTitle) {\n\t\t\tawait options.set(\"emdash:site_title\", DEV_SITE_TITLE);\n\t\t}\n\n\t\t// Store canonical site URL (used by magic-link/recovery emails)\n\t\tawait options.set(\"emdash:site_url\", getPublicOrigin(url, emdash?.config));\n\n\t\t// Mark setup complete\n\t\tawait options.set(\"emdash:setup_complete\", true);\n\n\t\t// Create session\n\t\tif (session) {\n\t\t\tsession.set(\"user\", { id: user.id });\n\t\t}\n\n\t\t// Optionally create a PAT token (?token=1) for headless/CLI testing.\n\t\tlet token: string | undefined;\n\t\tif (url.searchParams.has(\"token\")) {\n\t\t\tconst result = await handleApiTokenCreate(emdash.db, user.id, {\n\t\t\t\tname: \"dev-bypass-token\",\n\t\t\t\tscopes: [\n\t\t\t\t\t\"content:read\",\n\t\t\t\t\t\"content:write\",\n\t\t\t\t\t\"media:read\",\n\t\t\t\t\t\"media:write\",\n\t\t\t\t\t\"schema:read\",\n\t\t\t\t\t\"schema:write\",\n\t\t\t\t\t\"admin\",\n\t\t\t\t],\n\t\t\t});\n\t\t\tif (result.success) {\n\t\t\t\ttoken = result.data.token;\n\t\t\t}\n\t\t}\n\n\t\t// Check for redirect parameter\n\t\tconst redirect = url.searchParams.get(\"redirect\");\n\n\t\tif (redirect) {\n\t\t\t// Validate redirect is a safe local path (prevent open redirect via //evil.com or /\\evil.com)\n\t\t\tif (!isSafeRedirect(redirect)) {\n\t\t\t\treturn apiError(\"INVALID_REDIRECT\", \"Redirect must be a local path\", 400);\n\t\t\t}\n\n\t\t\t// Return an HTML page with meta-refresh redirect\n\t\t\t// This ensures the session is fully saved before redirect\n\t\t\tconst safeRedirect = escapeHtml(redirect);\n\t\t\tconst html = `<!DOCTYPE html>\n<html>\n<head>\n\t<meta http-equiv=\"refresh\" content=\"0;url=${safeRedirect}\">\n</head>\n<body>Redirecting...</body>\n</html>`;\n\t\t\treturn new Response(html, {\n\t\t\t\tstatus: 200,\n\t\t\t\theaders: { \"Content-Type\": \"text/html\" },\n\t\t\t});\n\t\t}\n\n\t\t// Return JSON response\n\t\treturn apiSuccess({\n\t\t\tsuccess: true,\n\t\t\tmessage: \"Dev setup complete\",\n\t\t\tmigrations: migrations.applied,\n\t\t\tuserCreated,\n\t\t\tuser: {\n\t\t\t\tid: user.id,\n\t\t\t\temail: user.email,\n\t\t\t\tname: user.name,\n\t\t\t\trole: user.role,\n\t\t\t},\n\t\t\t...(token ? { token } : {}),\n\t\t});\n\t} catch (error) {\n\t\treturn handleError(error, \"Dev bypass failed\", \"DEV_BYPASS_ERROR\");\n\t}\n}\n\n// Support both GET and POST\nexport const GET: APIRoute = handleDevBypass;\nexport const POST: APIRoute = handleDevBypass;\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"dev-bypass.mjs","names":[],"sources":["../../../../../src/astro/routes/api/setup/dev-bypass.ts"],"sourcesContent":["/**\n * POST /_emdash/api/setup/dev-bypass\n * GET /_emdash/api/setup/dev-bypass\n *\n * Development-only endpoint to bypass the setup wizard.\n * Runs migrations, creates a dev admin user, and marks setup complete.\n *\n * ONLY available when import.meta.env.DEV is true.\n *\n * Usage:\n * - GET with redirect: /_emdash/api/setup/dev-bypass?redirect=/_emdash/admin\n * - POST for API: Returns JSON with setup info\n *\n * For agent/browser testing, navigate to:\n * /_emdash/api/setup/dev-bypass?redirect=/_emdash/admin\n */\n\nimport type { APIRoute } from \"astro\";\n\nexport const prerender = false;\n\nimport { ulid } from \"ulidx\";\n\nimport { apiError, apiSuccess, handleError } from \"#api/error.js\";\nimport { escapeHtml } from \"#api/escape.js\";\nimport { handleApiTokenCreate } from \"#api/handlers/api-tokens.js\";\nimport { getPublicOrigin } from \"#api/public-url.js\";\nimport { isSafeRedirect } from \"#api/redirect.js\";\nimport { runMigrations } from \"#db/migrations/runner.js\";\nimport { OptionsRepository } from \"#db/repositories/options.js\";\nimport { applySeed } from \"#seed/apply.js\";\nimport { loadSeed } from \"#seed/load.js\";\nimport { validateSeed } from \"#seed/validate.js\";\n\n// RBAC role levels (matching @emdash-cms/auth)\nconst ROLE_ADMIN = 50;\n\nconst DEV_USER_EMAIL = \"dev@emdash.local\";\nconst DEV_USER_NAME = \"Dev Admin\";\nconst DEV_SITE_TITLE = \"EmDash Dev Site\";\n\nasync function handleDevBypass(context: Parameters<APIRoute>[0]): Promise<Response> {\n\t// CRITICAL: Only allow in development mode\n\tif (!import.meta.env.DEV) {\n\t\treturn apiError(\"FORBIDDEN\", \"Dev bypass is only available in development mode\", 403);\n\t}\n\n\tconst { locals, url, session } = context;\n\tconst { emdash } = locals;\n\n\tif (!emdash?.db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\ttry {\n\t\t// Run migrations\n\t\tconst migrations = await runMigrations(emdash.db);\n\t\tconsole.log(\"[setup-dev-bypass] Migrations applied:\", migrations.applied);\n\n\t\t// Apply seed (user seed or built-in default)\n\t\tconst seed = await loadSeed();\n\t\tconst validation = validateSeed(seed);\n\t\tif (validation.valid) {\n\t\t\tconst seedResult = await applySeed(emdash.db, seed, {\n\t\t\t\tincludeContent: true,\n\t\t\t\tonConflict: \"skip\",\n\t\t\t\tstorage: emdash.storage ?? undefined,\n\t\t\t});\n\t\t\tconsole.log(\n\t\t\t\t`[setup-dev-bypass] Seed applied: ${seedResult.collections.created} collections, ${seedResult.fields.created} fields`,\n\t\t\t);\n\t\t}\n\n\t\tconst options = new OptionsRepository(emdash.db);\n\n\t\t// Find or create dev user (direct DB access to avoid @emdash-cms/auth import issues in dev)\n\t\tconst existingUser = await emdash.db\n\t\t\t.selectFrom(\"users\")\n\t\t\t.selectAll()\n\t\t\t.where(\"email\", \"=\", DEV_USER_EMAIL)\n\t\t\t.executeTakeFirst();\n\n\t\tlet user: { id: string; email: string; name: string; role: number };\n\t\tlet userCreated = false;\n\n\t\tif (!existingUser) {\n\t\t\tconst now = new Date().toISOString();\n\t\t\tconst newUser = {\n\t\t\t\tid: ulid(),\n\t\t\t\temail: DEV_USER_EMAIL,\n\t\t\t\tname: DEV_USER_NAME,\n\t\t\t\trole: ROLE_ADMIN,\n\t\t\t\temail_verified: 1,\n\t\t\t\tcreated_at: now,\n\t\t\t\tupdated_at: now,\n\t\t\t};\n\n\t\t\tawait emdash.db.insertInto(\"users\").values(newUser).execute();\n\n\t\t\tuser = {\n\t\t\t\tid: newUser.id,\n\t\t\t\temail: newUser.email,\n\t\t\t\tname: newUser.name,\n\t\t\t\trole: newUser.role,\n\t\t\t};\n\t\t\tuserCreated = true;\n\t\t\tconsole.log(\"[setup-dev-bypass] Created dev admin user:\", user.email);\n\t\t} else {\n\t\t\tuser = {\n\t\t\t\tid: existingUser.id,\n\t\t\t\temail: existingUser.email,\n\t\t\t\tname: existingUser.name || DEV_USER_NAME,\n\t\t\t\trole: existingUser.role,\n\t\t\t};\n\t\t}\n\n\t\t// Set site title if not already set\n\t\tconst existingTitle = await options.get(\"emdash:site_title\");\n\t\tif (!existingTitle) {\n\t\t\tawait options.set(\"emdash:site_title\", DEV_SITE_TITLE);\n\t\t}\n\n\t\t// Store canonical site URL (used by magic-link/recovery emails)\n\t\tawait options.set(\"emdash:site_url\", getPublicOrigin(url, emdash?.config));\n\n\t\t// Mark setup complete\n\t\tawait options.set(\"emdash:setup_complete\", true);\n\n\t\t// Create session\n\t\tif (session) {\n\t\t\tsession.set(\"user\", { id: user.id });\n\t\t}\n\n\t\t// Optionally create a PAT token (?token=1) for headless/CLI testing.\n\t\tlet token: string | undefined;\n\t\tif (url.searchParams.has(\"token\")) {\n\t\t\tconst result = await handleApiTokenCreate(emdash.db, user.id, {\n\t\t\t\tname: \"dev-bypass-token\",\n\t\t\t\tscopes: [\n\t\t\t\t\t\"content:read\",\n\t\t\t\t\t\"content:write\",\n\t\t\t\t\t\"media:read\",\n\t\t\t\t\t\"media:write\",\n\t\t\t\t\t\"schema:read\",\n\t\t\t\t\t\"schema:write\",\n\t\t\t\t\t\"admin\",\n\t\t\t\t],\n\t\t\t});\n\t\t\tif (result.success) {\n\t\t\t\ttoken = result.data.token;\n\t\t\t}\n\t\t}\n\n\t\t// Check for redirect parameter\n\t\tconst redirect = url.searchParams.get(\"redirect\");\n\n\t\tif (redirect) {\n\t\t\t// Validate redirect is a safe local path (prevent open redirect via //evil.com or /\\evil.com)\n\t\t\tif (!isSafeRedirect(redirect)) {\n\t\t\t\treturn apiError(\"INVALID_REDIRECT\", \"Redirect must be a local path\", 400);\n\t\t\t}\n\n\t\t\t// Return an HTML page with meta-refresh redirect\n\t\t\t// This ensures the session is fully saved before redirect\n\t\t\tconst safeRedirect = escapeHtml(redirect);\n\t\t\tconst html = `<!DOCTYPE html>\n<html>\n<head>\n\t<meta http-equiv=\"refresh\" content=\"0;url=${safeRedirect}\">\n</head>\n<body>Redirecting...</body>\n</html>`;\n\t\t\treturn new Response(html, {\n\t\t\t\tstatus: 200,\n\t\t\t\theaders: { \"Content-Type\": \"text/html\" },\n\t\t\t});\n\t\t}\n\n\t\t// Return JSON response\n\t\treturn apiSuccess({\n\t\t\tsuccess: true,\n\t\t\tmessage: \"Dev setup complete\",\n\t\t\tmigrations: migrations.applied,\n\t\t\tuserCreated,\n\t\t\tuser: {\n\t\t\t\tid: user.id,\n\t\t\t\temail: user.email,\n\t\t\t\tname: user.name,\n\t\t\t\trole: user.role,\n\t\t\t},\n\t\t\t...(token ? { token } : {}),\n\t\t});\n\t} catch (error) {\n\t\treturn handleError(error, \"Dev bypass failed\", \"DEV_BYPASS_ERROR\");\n\t}\n}\n\n// Support both GET and POST\nexport const GET: APIRoute = handleDevBypass;\nexport const POST: APIRoute = handleDevBypass;\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "../../../../base64-CqR-7kqF.mjs";
|
|
2
2
|
import "../../../../types-BXSUSAjt.mjs";
|
|
3
3
|
import { t as OptionsRepository } from "../../../../options-BPCVnesz.mjs";
|
|
4
|
-
import { n as apiSuccess, r as handleError, t as apiError } from "../../../../error-
|
|
4
|
+
import { n as apiSuccess, r as handleError, t as apiError } from "../../../../error-CNn_w7jf.mjs";
|
|
5
5
|
|
|
6
6
|
//#region src/astro/routes/api/setup/dev-reset.ts
|
|
7
7
|
const prerender = false;
|
|
@@ -7,25 +7,27 @@ import "../../../../media-JOf3pNkw.mjs";
|
|
|
7
7
|
import "../../../../taxonomy-CdllE4oq.mjs";
|
|
8
8
|
import { t as OptionsRepository } from "../../../../options-BPCVnesz.mjs";
|
|
9
9
|
import "../../../../redirect-CRWIt8Zj.mjs";
|
|
10
|
-
import "../../../../
|
|
11
|
-
import "../../../../
|
|
12
|
-
import "../../../../byline-
|
|
13
|
-
import "../../../../
|
|
14
|
-
import "../../../../
|
|
15
|
-
import "../../../../
|
|
16
|
-
import "../../../../
|
|
17
|
-
import "../../../../
|
|
18
|
-
import
|
|
19
|
-
import { n as
|
|
20
|
-
import "../../../../
|
|
21
|
-
import
|
|
10
|
+
import "../../../../init-lock-DlBHjf9-.mjs";
|
|
11
|
+
import "../../../../request-cache-UwmBAiUK.mjs";
|
|
12
|
+
import "../../../../byline-registry-DedidtqC.mjs";
|
|
13
|
+
import "../../../../byline-V_Qp1Ziw.mjs";
|
|
14
|
+
import "../../../../fts-manager-Cx5z8jdA.mjs";
|
|
15
|
+
import "../../../../registry-diMzD1Wf.mjs";
|
|
16
|
+
import "../../../../loader-BqWjcH3h.mjs";
|
|
17
|
+
import "../../../../settings-BjBsmVAo.mjs";
|
|
18
|
+
import "../../../../ssrf-XO05Voq6.mjs";
|
|
19
|
+
import { n as apiSuccess, r as handleError, t as apiError } from "../../../../error-CNn_w7jf.mjs";
|
|
20
|
+
import { n as parseBody, t as isParseError } from "../../../../parse-DzSrk1t8.mjs";
|
|
21
|
+
import "../../../../redirects-6Zg2SoYo.mjs";
|
|
22
|
+
import { d as setupBody } from "../../../../byline-fields-B0NO1yUB.mjs";
|
|
23
|
+
import "../../../../status-2gZklYuj.mjs";
|
|
22
24
|
import "../../../../api/schemas/index.mjs";
|
|
23
|
-
import "../../../../ssrf-
|
|
24
|
-
import { t as validateSeed } from "../../../../validate-
|
|
25
|
-
import { t as applySeed } from "../../../../apply-
|
|
26
|
-
import { t as loadSeed } from "../../../../load-
|
|
27
|
-
import { n as getPublicOrigin } from "../../../../public-url-
|
|
28
|
-
import { t as getAuthMode } from "../../../../mode-
|
|
25
|
+
import "../../../../ssrf-CRZGzjdL.mjs";
|
|
26
|
+
import { t as validateSeed } from "../../../../validate-DGhQPXzI.mjs";
|
|
27
|
+
import { t as applySeed } from "../../../../apply-CLjxheyb.mjs";
|
|
28
|
+
import { t as loadSeed } from "../../../../load-Dq91b_DK.mjs";
|
|
29
|
+
import { n as getPublicOrigin } from "../../../../public-url-D_zARuvZ.mjs";
|
|
30
|
+
import { t as getAuthMode } from "../../../../mode-CGXzIbD8.mjs";
|
|
29
31
|
|
|
30
32
|
//#region src/astro/routes/api/setup/index.ts
|
|
31
33
|
const prerender = false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../../src/astro/routes/api/setup/index.ts"],"sourcesContent":["/**\n * POST /_emdash/api/setup\n *\n * Executes the setup wizard - applies seed file and marks setup complete\n */\n\nimport type { APIRoute } from \"astro\";\n\nexport const prerender = false;\n\nimport { apiError, apiSuccess, handleError } from \"#api/error.js\";\nimport { isParseError, parseBody } from \"#api/parse.js\";\nimport { getPublicOrigin } from \"#api/public-url.js\";\nimport { setupBody } from \"#api/schemas.js\";\nimport { getAuthMode } from \"#auth/mode.js\";\nimport { runMigrations } from \"#db/migrations/runner.js\";\nimport { OptionsRepository } from \"#db/repositories/options.js\";\nimport { applySeed } from \"#seed/apply.js\";\nimport { loadSeed } from \"#seed/load.js\";\nimport { validateSeed } from \"#seed/validate.js\";\n\nexport const POST: APIRoute = async ({ request, url, locals }) => {\n\tconst { emdash } = locals;\n\n\tif (!emdash?.db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\ttry {\n\t\t// Guard: reject if setup has already been completed.\n\t\t// The options table may not exist on first-ever setup (pre-migration),\n\t\t// so a query failure means setup hasn't run yet — allow it to proceed.\n\t\ttry {\n\t\t\tconst options = new OptionsRepository(emdash.db);\n\t\t\tconst setupComplete = await options.get(\"emdash:setup_complete\");\n\n\t\t\tif (setupComplete === true || setupComplete === \"true\") {\n\t\t\t\treturn apiError(\"ALREADY_CONFIGURED\", \"Setup has already been completed\", 409);\n\t\t\t}\n\t\t} catch {\n\t\t\t// Options table doesn't exist yet — first-ever setup, allow it\n\t\t}\n\n\t\t// Parse request body\n\t\tconst body = await parseBody(request, setupBody);\n\t\tif (isParseError(body)) return body;\n\n\t\t// 1. Run core migrations\n\t\ttry {\n\t\t\tawait runMigrations(emdash.db);\n\t\t} catch (error) {\n\t\t\treturn handleError(error, \"Failed to run database migrations\", \"MIGRATION_ERROR\");\n\t\t}\n\n\t\t// 2. Load seed file (user seed or built-in default)\n\t\tconst seed = await loadSeed();\n\n\t\t// 3. Override seed settings with form values\n\t\tseed.settings = {\n\t\t\t...seed.settings,\n\t\t\ttitle: body.title,\n\t\t\ttagline: body.tagline,\n\t\t};\n\n\t\t// 4. Apply seed\n\t\tconst validation = validateSeed(seed);\n\t\tif (!validation.valid) {\n\t\t\treturn apiError(\"INVALID_SEED\", `Invalid seed file: ${validation.errors.join(\", \")}`, 400);\n\t\t}\n\n\t\tlet result;\n\t\ttry {\n\t\t\tresult = await applySeed(emdash.db, seed, {\n\t\t\t\tincludeContent: body.includeContent,\n\t\t\t\tonConflict: \"skip\",\n\t\t\t\tstorage: emdash.storage ?? undefined,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\treturn handleError(error, \"Failed to apply seed\", \"SEED_ERROR\");\n\t\t}\n\n\t\t// 5. Store setup state\n\t\t// In external auth mode, mark setup complete immediately (first user to login becomes admin)\n\t\t// Otherwise, setup_complete is set after admin user is created (passkey or auth provider)\n\t\tconst authMode = getAuthMode(emdash.config);\n\t\tconst useExternalAuth = authMode.type === \"external\";\n\n\t\ttry {\n\t\t\tconst options = new OptionsRepository(emdash.db);\n\n\t\t\t// Store the canonical site URL from the setup request.\n\t\t\t// Write-once at the DB level so concurrent setup POSTs can't both\n\t\t\t// observe an empty value and race to write. A spoofed Host header\n\t\t\t// on a later call during the wizard window must not be able to\n\t\t\t// replace the first value.\n\t\t\tconst siteUrl = getPublicOrigin(url, emdash.config);\n\t\t\tawait options.setIfAbsent(\"emdash:site_url\", siteUrl);\n\n\t\t\tif (useExternalAuth) {\n\t\t\t\t// External auth mode: mark setup complete now\n\t\t\t\t// First user to log in via external provider will become admin\n\t\t\t\tawait options.set(\"emdash:setup_complete\", true);\n\t\t\t\tawait options.set(\"emdash:site_title\", body.title);\n\t\t\t\tif (body.tagline) {\n\t\t\t\t\tawait options.set(\"emdash:site_tagline\", body.tagline);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Passkey/provider mode: store state for next step (admin creation)\n\t\t\t\tawait options.set(\"emdash:setup_state\", {\n\t\t\t\t\tstep: \"site_complete\",\n\t\t\t\t\ttitle: body.title,\n\t\t\t\t\ttagline: body.tagline,\n\t\t\t\t});\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to save setup state:\", error);\n\t\t\t// Non-fatal - continue anyway\n\t\t}\n\n\t\t// 6. Return success with result\n\t\treturn apiSuccess({\n\t\t\tsuccess: true,\n\t\t\t// In external auth mode, setup is complete - redirect to admin\n\t\t\tsetupComplete: useExternalAuth,\n\t\t\tresult,\n\t\t});\n\t} catch (error) {\n\t\treturn handleError(error, \"Setup failed\", \"SETUP_ERROR\");\n\t}\n};\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../../src/astro/routes/api/setup/index.ts"],"sourcesContent":["/**\n * POST /_emdash/api/setup\n *\n * Executes the setup wizard - applies seed file and marks setup complete\n */\n\nimport type { APIRoute } from \"astro\";\n\nexport const prerender = false;\n\nimport { apiError, apiSuccess, handleError } from \"#api/error.js\";\nimport { isParseError, parseBody } from \"#api/parse.js\";\nimport { getPublicOrigin } from \"#api/public-url.js\";\nimport { setupBody } from \"#api/schemas.js\";\nimport { getAuthMode } from \"#auth/mode.js\";\nimport { runMigrations } from \"#db/migrations/runner.js\";\nimport { OptionsRepository } from \"#db/repositories/options.js\";\nimport { applySeed } from \"#seed/apply.js\";\nimport { loadSeed } from \"#seed/load.js\";\nimport { validateSeed } from \"#seed/validate.js\";\n\nexport const POST: APIRoute = async ({ request, url, locals }) => {\n\tconst { emdash } = locals;\n\n\tif (!emdash?.db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\ttry {\n\t\t// Guard: reject if setup has already been completed.\n\t\t// The options table may not exist on first-ever setup (pre-migration),\n\t\t// so a query failure means setup hasn't run yet — allow it to proceed.\n\t\ttry {\n\t\t\tconst options = new OptionsRepository(emdash.db);\n\t\t\tconst setupComplete = await options.get(\"emdash:setup_complete\");\n\n\t\t\tif (setupComplete === true || setupComplete === \"true\") {\n\t\t\t\treturn apiError(\"ALREADY_CONFIGURED\", \"Setup has already been completed\", 409);\n\t\t\t}\n\t\t} catch {\n\t\t\t// Options table doesn't exist yet — first-ever setup, allow it\n\t\t}\n\n\t\t// Parse request body\n\t\tconst body = await parseBody(request, setupBody);\n\t\tif (isParseError(body)) return body;\n\n\t\t// 1. Run core migrations\n\t\ttry {\n\t\t\tawait runMigrations(emdash.db);\n\t\t} catch (error) {\n\t\t\treturn handleError(error, \"Failed to run database migrations\", \"MIGRATION_ERROR\");\n\t\t}\n\n\t\t// 2. Load seed file (user seed or built-in default)\n\t\tconst seed = await loadSeed();\n\n\t\t// 3. Override seed settings with form values\n\t\tseed.settings = {\n\t\t\t...seed.settings,\n\t\t\ttitle: body.title,\n\t\t\ttagline: body.tagline,\n\t\t};\n\n\t\t// 4. Apply seed\n\t\tconst validation = validateSeed(seed);\n\t\tif (!validation.valid) {\n\t\t\treturn apiError(\"INVALID_SEED\", `Invalid seed file: ${validation.errors.join(\", \")}`, 400);\n\t\t}\n\n\t\tlet result;\n\t\ttry {\n\t\t\tresult = await applySeed(emdash.db, seed, {\n\t\t\t\tincludeContent: body.includeContent,\n\t\t\t\tonConflict: \"skip\",\n\t\t\t\tstorage: emdash.storage ?? undefined,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\treturn handleError(error, \"Failed to apply seed\", \"SEED_ERROR\");\n\t\t}\n\n\t\t// 5. Store setup state\n\t\t// In external auth mode, mark setup complete immediately (first user to login becomes admin)\n\t\t// Otherwise, setup_complete is set after admin user is created (passkey or auth provider)\n\t\tconst authMode = getAuthMode(emdash.config);\n\t\tconst useExternalAuth = authMode.type === \"external\";\n\n\t\ttry {\n\t\t\tconst options = new OptionsRepository(emdash.db);\n\n\t\t\t// Store the canonical site URL from the setup request.\n\t\t\t// Write-once at the DB level so concurrent setup POSTs can't both\n\t\t\t// observe an empty value and race to write. A spoofed Host header\n\t\t\t// on a later call during the wizard window must not be able to\n\t\t\t// replace the first value.\n\t\t\tconst siteUrl = getPublicOrigin(url, emdash.config);\n\t\t\tawait options.setIfAbsent(\"emdash:site_url\", siteUrl);\n\n\t\t\tif (useExternalAuth) {\n\t\t\t\t// External auth mode: mark setup complete now\n\t\t\t\t// First user to log in via external provider will become admin\n\t\t\t\tawait options.set(\"emdash:setup_complete\", true);\n\t\t\t\tawait options.set(\"emdash:site_title\", body.title);\n\t\t\t\tif (body.tagline) {\n\t\t\t\t\tawait options.set(\"emdash:site_tagline\", body.tagline);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Passkey/provider mode: store state for next step (admin creation)\n\t\t\t\tawait options.set(\"emdash:setup_state\", {\n\t\t\t\t\tstep: \"site_complete\",\n\t\t\t\t\ttitle: body.title,\n\t\t\t\t\ttagline: body.tagline,\n\t\t\t\t});\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to save setup state:\", error);\n\t\t\t// Non-fatal - continue anyway\n\t\t}\n\n\t\t// 6. Return success with result\n\t\treturn apiSuccess({\n\t\t\tsuccess: true,\n\t\t\t// In external auth mode, setup is complete - redirect to admin\n\t\t\tsetupComplete: useExternalAuth,\n\t\t\tresult,\n\t\t});\n\t} catch (error) {\n\t\treturn handleError(error, \"Setup failed\", \"SETUP_ERROR\");\n\t}\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAQA,MAAa,YAAY;AAazB,MAAa,OAAiB,OAAO,EAAE,SAAS,KAAK,aAAa;CACjE,MAAM,EAAE,WAAW;AAEnB,KAAI,CAAC,QAAQ,GACZ,QAAO,SAAS,kBAAkB,6BAA6B,IAAI;AAGpE,KAAI;AAIH,MAAI;GAEH,MAAM,gBAAgB,MADN,IAAI,kBAAkB,OAAO,GAAG,CACZ,IAAI,wBAAwB;AAEhE,OAAI,kBAAkB,QAAQ,kBAAkB,OAC/C,QAAO,SAAS,sBAAsB,oCAAoC,IAAI;UAExE;EAKR,MAAM,OAAO,MAAM,UAAU,SAAS,UAAU;AAChD,MAAI,aAAa,KAAK,CAAE,QAAO;AAG/B,MAAI;AACH,SAAM,cAAc,OAAO,GAAG;WACtB,OAAO;AACf,UAAO,YAAY,OAAO,qCAAqC,kBAAkB;;EAIlF,MAAM,OAAO,MAAM,UAAU;AAG7B,OAAK,WAAW;GACf,GAAG,KAAK;GACR,OAAO,KAAK;GACZ,SAAS,KAAK;GACd;EAGD,MAAM,aAAa,aAAa,KAAK;AACrC,MAAI,CAAC,WAAW,MACf,QAAO,SAAS,gBAAgB,sBAAsB,WAAW,OAAO,KAAK,KAAK,IAAI,IAAI;EAG3F,IAAI;AACJ,MAAI;AACH,YAAS,MAAM,UAAU,OAAO,IAAI,MAAM;IACzC,gBAAgB,KAAK;IACrB,YAAY;IACZ,SAAS,OAAO,WAAW;IAC3B,CAAC;WACM,OAAO;AACf,UAAO,YAAY,OAAO,wBAAwB,aAAa;;EAOhE,MAAM,kBADW,YAAY,OAAO,OAAO,CACV,SAAS;AAE1C,MAAI;GACH,MAAM,UAAU,IAAI,kBAAkB,OAAO,GAAG;GAOhD,MAAM,UAAU,gBAAgB,KAAK,OAAO,OAAO;AACnD,SAAM,QAAQ,YAAY,mBAAmB,QAAQ;AAErD,OAAI,iBAAiB;AAGpB,UAAM,QAAQ,IAAI,yBAAyB,KAAK;AAChD,UAAM,QAAQ,IAAI,qBAAqB,KAAK,MAAM;AAClD,QAAI,KAAK,QACR,OAAM,QAAQ,IAAI,uBAAuB,KAAK,QAAQ;SAIvD,OAAM,QAAQ,IAAI,sBAAsB;IACvC,MAAM;IACN,OAAO,KAAK;IACZ,SAAS,KAAK;IACd,CAAC;WAEK,OAAO;AACf,WAAQ,MAAM,+BAA+B,MAAM;;AAKpD,SAAO,WAAW;GACjB,SAAS;GAET,eAAe;GACf;GACA,CAAC;UACM,OAAO;AACf,SAAO,YAAY,OAAO,gBAAgB,cAAc"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import "../../../../base64-CqR-7kqF.mjs";
|
|
2
2
|
import "../../../../types-BXSUSAjt.mjs";
|
|
3
|
-
import { n as apiSuccess, r as handleError, t as apiError } from "../../../../error-
|
|
4
|
-
import { n as loadUserSeed } from "../../../../load-
|
|
5
|
-
import { t as getAuthMode } from "../../../../mode-
|
|
3
|
+
import { n as apiSuccess, r as handleError, t as apiError } from "../../../../error-CNn_w7jf.mjs";
|
|
4
|
+
import { n as loadUserSeed } from "../../../../load-Dq91b_DK.mjs";
|
|
5
|
+
import { t as getAuthMode } from "../../../../mode-CGXzIbD8.mjs";
|
|
6
6
|
|
|
7
7
|
//#region src/astro/routes/api/setup/status.ts
|
|
8
8
|
const prerender = false;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import "../../../base64-CqR-7kqF.mjs";
|
|
2
2
|
import "../../../types-BXSUSAjt.mjs";
|
|
3
3
|
import "../../../options-BPCVnesz.mjs";
|
|
4
|
-
import
|
|
5
|
-
import { n as
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
4
|
+
import "../../../init-lock-DlBHjf9-.mjs";
|
|
5
|
+
import { n as apiSuccess, r as handleError, t as apiError } from "../../../error-CNn_w7jf.mjs";
|
|
6
|
+
import { n as getPublicOrigin } from "../../../public-url-D_zARuvZ.mjs";
|
|
7
|
+
import { i as resolveSecretsCached } from "../../../secrets-C8xmE6mR.mjs";
|
|
8
|
+
import { n as requirePerm } from "../../../authorize-D5gfBVU5.mjs";
|
|
8
9
|
import { sql } from "kysely";
|
|
9
10
|
|
|
10
11
|
//#region src/api/handlers/snapshot.ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot.mjs","names":[],"sources":["../../../../src/api/handlers/snapshot.ts","../../../../src/astro/routes/api/snapshot.ts"],"sourcesContent":["/**\n * Snapshot handler — generates a portable database snapshot.\n *\n * Returns all content tables, schema definitions, and supporting data\n * needed to render content in an isolated preview database.\n *\n * Used by:\n * - DO preview database (EmDashPreviewDB.populateFromSnapshot)\n * - Future: CLI export, backup, site migration\n */\n\nimport type { Kysely } from \"kysely\";\nimport { sql } from \"kysely\";\n\nimport type { Database } from \"../../database/types.js\";\n\n// ─�� Preview signature verification ──────────────────────────────\n\n/**\n * Verify HMAC-SHA256 preview signature using crypto.subtle.\n * Returns true if the signature is valid and not expired.\n */\nexport async function verifyPreviewSignature(\n\tsource: string,\n\texp: number,\n\tsig: string,\n\tsecret: string,\n): Promise<boolean> {\n\tif (exp < Date.now() / 1000) return false;\n\n\tconst encoder = new TextEncoder();\n\tconst key = await crypto.subtle.importKey(\n\t\t\"raw\",\n\t\tencoder.encode(secret),\n\t\t{ name: \"HMAC\", hash: \"SHA-256\" },\n\t\tfalse,\n\t\t[\"verify\"],\n\t);\n\n\tconst sigBytes = new Uint8Array(sig.length / 2);\n\tfor (let i = 0; i < sig.length; i += 2) {\n\t\tsigBytes[i / 2] = parseInt(sig.substring(i, i + 2), 16);\n\t}\n\n\treturn crypto.subtle.verify(\"HMAC\", key, sigBytes, encoder.encode(`${source}:${exp}`));\n}\n\n/**\n * Parse an X-Preview-Signature header value into its components.\n *\n * Format: \"source:exp:sig\" where source is a URL (contains colons),\n * exp is a unix timestamp, and sig is 64 hex chars.\n *\n * Parses from the right since source URLs contain colons.\n *\n * @returns Parsed components, or null if the format is invalid\n */\nexport function parsePreviewSignatureHeader(\n\theader: string,\n): { source: string; exp: number; sig: string } | null {\n\tconst lastColon = header.lastIndexOf(\":\");\n\tif (lastColon <= 0) return null;\n\n\tconst sig = header.substring(lastColon + 1);\n\tif (sig.length !== 64) return null;\n\n\tconst rest = header.substring(0, lastColon);\n\tconst secondLastColon = rest.lastIndexOf(\":\");\n\tif (secondLastColon <= 0) return null;\n\n\tconst source = rest.substring(0, secondLastColon);\n\tconst exp = parseInt(rest.substring(secondLastColon + 1), 10);\n\n\tif (isNaN(exp) || source.length === 0) return null;\n\n\treturn { source, exp, sig };\n}\n\n// ── Media URL rewriting ─────────────────────────────────────────\n\nconst MEDIA_FILE_PREFIX = \"/_emdash/api/media/file/\";\n\n/**\n * Parse a JSON string value and inject `src` for local media objects.\n * Returns the original string if it's not a local media value.\n */\nfunction injectMediaSrc(jsonStr: string, origin: string): string {\n\ttry {\n\t\tconst obj = JSON.parse(jsonStr);\n\t\tif (typeof obj !== \"object\" || obj === null || Array.isArray(obj)) return jsonStr;\n\t\tif (injectMediaSrcInto(obj, origin)) {\n\t\t\treturn JSON.stringify(obj);\n\t\t}\n\t\treturn jsonStr;\n\t} catch {\n\t\treturn jsonStr;\n\t}\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Recursively walk an object and inject `src` into local media values.\n * Returns true if any modifications were made.\n */\nfunction injectMediaSrcInto(obj: Record<string, unknown>, origin: string): boolean {\n\tlet modified = false;\n\n\t// Check if this object itself is a local media value\n\tif ((obj.provider === \"local\" || (!obj.provider && obj.id && obj.meta)) && !obj.src) {\n\t\tconst meta = isRecord(obj.meta) ? obj.meta : undefined;\n\t\tconst storageKey = meta?.storageKey ?? obj.id;\n\t\tif (typeof storageKey === \"string\" && storageKey) {\n\t\t\tobj.src = `${origin}${MEDIA_FILE_PREFIX}${storageKey}`;\n\t\t\tmodified = true;\n\t\t}\n\t}\n\n\t// Recurse into nested objects/arrays (e.g. Portable Text with image blocks)\n\tfor (const value of Object.values(obj)) {\n\t\tif (Array.isArray(value)) {\n\t\t\tfor (const item of value) {\n\t\t\t\tif (isRecord(item)) {\n\t\t\t\t\tif (injectMediaSrcInto(item, origin)) {\n\t\t\t\t\t\tmodified = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (isRecord(value)) {\n\t\t\tif (injectMediaSrcInto(value, origin)) {\n\t\t\t\tmodified = true;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn modified;\n}\n\n// ── Snapshot generation ─────────────────────────────────────────\n\n/**\n * Safe identifier pattern for snapshot table names.\n * More permissive than validateIdentifier() — allows leading underscores\n * (needed for system tables like _emdash_collections).\n */\nconst SAFE_TABLE_NAME = /^[a-z_][a-z0-9_]*$/;\n\n/** Snapshot shape consumed by the DO preview database */\nexport interface Snapshot {\n\ttables: Record<string, Record<string, unknown>[]>;\n\tschema: Record<\n\t\tstring,\n\t\t{\n\t\t\tcolumns: string[];\n\t\t\ttypes?: Record<string, string>;\n\t\t}\n\t>;\n\tgeneratedAt: string;\n}\n\n/**\n * System tables included in snapshots.\n * Content tables (ec_*) are discovered dynamically.\n */\nconst SYSTEM_TABLES = [\n\t\"_emdash_collections\",\n\t\"_emdash_fields\",\n\t\"_emdash_taxonomy_defs\",\n\t\"_emdash_menus\",\n\t\"_emdash_menu_items\",\n\t\"_emdash_sections\",\n\t\"_emdash_widget_areas\",\n\t\"_emdash_widgets\",\n\t\"_emdash_seo\",\n\t\"_emdash_migrations\",\n\t\"taxonomies\",\n\t\"content_taxonomies\",\n\t\"media\",\n\t\"options\",\n\t\"revisions\",\n];\n\n/**\n * Table name prefixes excluded from snapshots (auth/security data).\n */\nconst EXCLUDED_PREFIXES = [\n\t\"_emdash_api_tokens\",\n\t\"_emdash_oauth_tokens\",\n\t\"_emdash_authorization_codes\",\n\t\"_emdash_device_codes\",\n\t\"_emdash_migrations_lock\",\n\t\"_plugin_\",\n\t\"users\",\n\t\"sessions\",\n\t\"credentials\",\n\t\"challenges\",\n];\n\n/**\n * Options key prefixes safe for inclusion in snapshots.\n *\n * The options table contains plugin secrets (plugin:*), passkey challenges\n * (emdash:passkey_pending:*), and setup state that must not leak to\n * preview databases. Only site-level rendering settings are needed.\n */\nconst SAFE_OPTIONS_PREFIXES = [\"site:\"];\n\nfunction isExcluded(tableName: string): boolean {\n\treturn EXCLUDED_PREFIXES.some((prefix) => tableName.startsWith(prefix));\n}\n\n/** Column info from PRAGMA table_info */\ninterface ColumnInfo {\n\tname: string;\n\ttype: string;\n}\n\nexport interface GenerateSnapshotOptions {\n\t/** Include draft and trashed content (default: false) */\n\tincludeDrafts?: boolean;\n\t/** Origin URL for absolutizing local media URLs (e.g. \"https://mysite.com\") */\n\torigin?: string;\n}\n\n/**\n * Generate a portable database snapshot.\n *\n * Discovers ec_* content tables dynamically, exports system tables\n * needed for rendering, and includes schema info for table recreation.\n */\nexport async function generateSnapshot(\n\tdb: Kysely<Database>,\n\toptions?: GenerateSnapshotOptions,\n): Promise<Snapshot> {\n\tconst includeDrafts = options?.includeDrafts ?? false;\n\n\t// Discover all ec_* content tables\n\tconst tableResult = await sql<{ name: string }>`\n\t\tSELECT name FROM sqlite_master\n\t\tWHERE type = 'table'\n\t\tAND name LIKE 'ec_%'\n\t\tORDER BY name\n\t`.execute(db);\n\n\tconst contentTables = tableResult.rows.map((r) => r.name);\n\n\t// Build list of all tables to export\n\tconst allTables = [...contentTables, ...SYSTEM_TABLES];\n\n\tconst tables: Record<string, Record<string, unknown>[]> = {};\n\tconst schema: Record<string, { columns: string[]; types?: Record<string, string> }> = {};\n\n\tfor (const tableName of allTables) {\n\t\tif (isExcluded(tableName)) continue;\n\n\t\t// Validate identifier before interpolating into sql.raw().\n\t\t// SYSTEM_TABLES are hardcoded and safe, but ec_* names come from\n\t\t// sqlite_master and must be validated.\n\t\tif (!SAFE_TABLE_NAME.test(tableName)) continue;\n\n\t\ttry {\n\t\t\t// Get column info via PRAGMA\n\t\t\tconst pragmaResult = await sql<ColumnInfo>`\n\t\t\t\tPRAGMA table_info(${sql.raw(`\"${tableName}\"`)})\n\t\t\t`.execute(db);\n\n\t\t\tif (pragmaResult.rows.length === 0) continue;\n\n\t\t\tconst columns = pragmaResult.rows.map((r) => r.name);\n\t\t\tconst types: Record<string, string> = {};\n\t\t\tfor (const row of pragmaResult.rows) {\n\t\t\t\ttypes[row.name] = row.type || \"TEXT\";\n\t\t\t}\n\n\t\t\tschema[tableName] = { columns, types };\n\n\t\t\t// Fetch rows\n\t\t\tlet rows: Record<string, unknown>[];\n\n\t\t\tif (tableName.startsWith(\"ec_\")) {\n\t\t\t\tif (includeDrafts) {\n\t\t\t\t\t// Include all non-deleted content (published, draft, scheduled)\n\t\t\t\t\trows = (\n\t\t\t\t\t\tawait sql<Record<string, unknown>>`\n\t\t\t\t\t\tSELECT * FROM ${sql.raw(`\"${tableName}\"`)}\n\t\t\t\t\t\tWHERE deleted_at IS NULL\n\t\t\t\t\t`.execute(db)\n\t\t\t\t\t).rows;\n\t\t\t\t} else {\n\t\t\t\t\t// Only export published content\n\t\t\t\t\trows = (\n\t\t\t\t\t\tawait sql<Record<string, unknown>>`\n\t\t\t\t\t\tSELECT * FROM ${sql.raw(`\"${tableName}\"`)}\n\t\t\t\t\t\tWHERE deleted_at IS NULL\n\t\t\t\t\t\tAND (status = 'published' OR (status = 'scheduled' AND scheduled_at <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now')))\n\t\t\t\t\t`.execute(db)\n\t\t\t\t\t).rows;\n\t\t\t\t}\n\t\t\t} else if (tableName === \"options\") {\n\t\t\t\t// Filter options to safe rendering-only prefixes.\n\t\t\t\t// Excludes plugin secrets, passkey challenges, and setup state.\n\t\t\t\trows = (\n\t\t\t\t\tawait sql<Record<string, unknown>>`\n\t\t\t\t\tSELECT * FROM ${sql.raw(`\"${tableName}\"`)}\n\t\t\t\t`.execute(db)\n\t\t\t\t).rows.filter((row) => {\n\t\t\t\t\tconst name = typeof row.name === \"string\" ? row.name : \"\";\n\t\t\t\t\treturn SAFE_OPTIONS_PREFIXES.some((prefix) => name.startsWith(prefix));\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\trows = (\n\t\t\t\t\tawait sql<Record<string, unknown>>`\n\t\t\t\t\tSELECT * FROM ${sql.raw(`\"${tableName}\"`)}\n\t\t\t\t`.execute(db)\n\t\t\t\t).rows;\n\t\t\t}\n\n\t\t\tif (rows.length > 0) {\n\t\t\t\ttables[tableName] = rows;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Table might not exist yet (e.g. pre-migration) — skip silently\n\t\t}\n\t}\n\n\t// Absolutize local media URLs in content tables so snapshots are portable.\n\t// Local image fields are stored as JSON with provider:\"local\" and\n\t// meta.storageKey but no src — the URL is derived at render time.\n\t// For snapshots consumed by external preview services, inject src now.\n\tif (options?.origin) {\n\t\tconst origin = options.origin;\n\t\tfor (const [tableName, rows] of Object.entries(tables)) {\n\t\t\tif (!tableName.startsWith(\"ec_\")) continue;\n\t\t\tfor (const row of rows) {\n\t\t\t\tfor (const [col, value] of Object.entries(row)) {\n\t\t\t\t\tif (typeof value !== \"string\" || !value.startsWith(\"{\")) continue;\n\t\t\t\t\trow[col] = injectMediaSrc(value, origin);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\ttables,\n\t\tschema,\n\t\tgeneratedAt: new Date().toISOString(),\n\t};\n}\n","/**\n * Snapshot endpoint — exports a portable database snapshot for preview mode.\n *\n * Security:\n * - Authenticated users: requires content:read + schema:read permissions\n * - Preview services: requires valid X-Preview-Signature header (HMAC-SHA256)\n * - Excludes auth/user/session/token tables\n */\n\nimport type { User } from \"@emdash-cms/auth\";\nimport type { APIRoute } from \"astro\";\n\nimport { requirePerm } from \"#api/authorize.js\";\nimport { apiError, apiSuccess, handleError } from \"#api/error.js\";\nimport {\n\tgenerateSnapshot,\n\tparsePreviewSignatureHeader,\n\tverifyPreviewSignature,\n} from \"#api/handlers/snapshot.js\";\nimport { getPublicOrigin } from \"#api/public-url.js\";\nimport { resolveSecretsCached } from \"#config/secrets.js\";\n\nexport const prerender = false;\n\nexport const GET: APIRoute = async ({ request, locals, url, session }) => {\n\tconst { emdash } = locals;\n\t// This route is in PUBLIC_API_EXACT (for preview-signature callers with no session),\n\t// so auth middleware skips user resolution. Manually resolve the session user here\n\t// to support session-authenticated admin users alongside preview-signature auth.\n\tlet user: User | undefined = (locals as { user?: User }).user;\n\tif (!user && session && emdash?.db) {\n\t\ttry {\n\t\t\tconst { createKyselyAdapter } = await import(\"@emdash-cms/auth/adapters/kysely\");\n\t\t\tconst sessionUser = await session.get(\"user\");\n\t\t\tif (sessionUser?.id) {\n\t\t\t\tconst adapter = createKyselyAdapter(emdash.db);\n\t\t\t\tconst resolved = await adapter.getUserById(sessionUser.id);\n\t\t\t\tif (resolved && !resolved.disabled) {\n\t\t\t\t\tuser = resolved;\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Session resolution failed, continue to preview-signature check\n\t\t}\n\t}\n\n\tif (!emdash?.db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\t// Check for preview signature auth (used by DO preview services)\n\tconst previewSig = request.headers.get(\"X-Preview-Signature\");\n\tlet authorized = false;\n\n\tif (previewSig) {\n\t\t// Resolves env override or DB-stored value. Always non-empty after\n\t\t// resolution, so the signature path is never silently disabled.\n\t\t// Note: a signing process without access to this database (e.g. a\n\t\t// remote preview Worker) must set the same `EMDASH_PREVIEW_SECRET`\n\t\t// env var on both sides.\n\t\tconst { previewSecret: secret, previewSecretSource } = await resolveSecretsCached(emdash.db);\n\t\tconst parsed = parsePreviewSignatureHeader(previewSig);\n\t\tif (!parsed) {\n\t\t\tconsole.warn(\"[snapshot] Failed to parse X-Preview-Signature header\");\n\t\t} else {\n\t\t\tauthorized = await verifyPreviewSignature(parsed.source, parsed.exp, parsed.sig, secret);\n\t\t\tif (!authorized) {\n\t\t\t\tconst fields: Record<string, unknown> = {\n\t\t\t\t\tsource: parsed.source,\n\t\t\t\t\texp: parsed.exp,\n\t\t\t\t\texpired: parsed.exp < Date.now() / 1000,\n\t\t\t\t\tsecretSource: previewSecretSource,\n\t\t\t\t};\n\t\t\t\tif (previewSecretSource === \"db\") {\n\t\t\t\t\tfields.hint =\n\t\t\t\t\t\t\"Set EMDASH_PREVIEW_SECRET in both this process and the signing process to share secrets across deployments\";\n\t\t\t\t}\n\t\t\t\tconsole.warn(\"[snapshot] Preview signature verification failed\", fields);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!authorized) {\n\t\t// Fall back to standard user auth\n\t\tconst contentDenied = requirePerm(user, \"content:read\");\n\t\tif (contentDenied) return contentDenied;\n\t\tconst schemaDenied = requirePerm(user, \"schema:read\");\n\t\tif (schemaDenied) return schemaDenied;\n\t}\n\n\ttry {\n\t\tconst includeDrafts = url.searchParams.get(\"drafts\") === \"true\";\n\t\tconst snapshot = await generateSnapshot(emdash.db, {\n\t\t\tincludeDrafts,\n\t\t\torigin: getPublicOrigin(url, emdash.config),\n\t\t});\n\n\t\treturn apiSuccess(snapshot);\n\t} catch (error) {\n\t\treturn handleError(error, \"Failed to generate snapshot\", \"SNAPSHOT_ERROR\");\n\t}\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"snapshot.mjs","names":[],"sources":["../../../../src/api/handlers/snapshot.ts","../../../../src/astro/routes/api/snapshot.ts"],"sourcesContent":["/**\n * Snapshot handler — generates a portable database snapshot.\n *\n * Returns all content tables, schema definitions, and supporting data\n * needed to render content in an isolated preview database.\n *\n * Used by:\n * - DO preview database (EmDashPreviewDB.populateFromSnapshot)\n * - Future: CLI export, backup, site migration\n */\n\nimport type { Kysely } from \"kysely\";\nimport { sql } from \"kysely\";\n\nimport type { Database } from \"../../database/types.js\";\n\n// ─�� Preview signature verification ──────────────────────────────\n\n/**\n * Verify HMAC-SHA256 preview signature using crypto.subtle.\n * Returns true if the signature is valid and not expired.\n */\nexport async function verifyPreviewSignature(\n\tsource: string,\n\texp: number,\n\tsig: string,\n\tsecret: string,\n): Promise<boolean> {\n\tif (exp < Date.now() / 1000) return false;\n\n\tconst encoder = new TextEncoder();\n\tconst key = await crypto.subtle.importKey(\n\t\t\"raw\",\n\t\tencoder.encode(secret),\n\t\t{ name: \"HMAC\", hash: \"SHA-256\" },\n\t\tfalse,\n\t\t[\"verify\"],\n\t);\n\n\tconst sigBytes = new Uint8Array(sig.length / 2);\n\tfor (let i = 0; i < sig.length; i += 2) {\n\t\tsigBytes[i / 2] = parseInt(sig.substring(i, i + 2), 16);\n\t}\n\n\treturn crypto.subtle.verify(\"HMAC\", key, sigBytes, encoder.encode(`${source}:${exp}`));\n}\n\n/**\n * Parse an X-Preview-Signature header value into its components.\n *\n * Format: \"source:exp:sig\" where source is a URL (contains colons),\n * exp is a unix timestamp, and sig is 64 hex chars.\n *\n * Parses from the right since source URLs contain colons.\n *\n * @returns Parsed components, or null if the format is invalid\n */\nexport function parsePreviewSignatureHeader(\n\theader: string,\n): { source: string; exp: number; sig: string } | null {\n\tconst lastColon = header.lastIndexOf(\":\");\n\tif (lastColon <= 0) return null;\n\n\tconst sig = header.substring(lastColon + 1);\n\tif (sig.length !== 64) return null;\n\n\tconst rest = header.substring(0, lastColon);\n\tconst secondLastColon = rest.lastIndexOf(\":\");\n\tif (secondLastColon <= 0) return null;\n\n\tconst source = rest.substring(0, secondLastColon);\n\tconst exp = parseInt(rest.substring(secondLastColon + 1), 10);\n\n\tif (isNaN(exp) || source.length === 0) return null;\n\n\treturn { source, exp, sig };\n}\n\n// ── Media URL rewriting ─────────────────────────────────────────\n\nconst MEDIA_FILE_PREFIX = \"/_emdash/api/media/file/\";\n\n/**\n * Parse a JSON string value and inject `src` for local media objects.\n * Returns the original string if it's not a local media value.\n */\nfunction injectMediaSrc(jsonStr: string, origin: string): string {\n\ttry {\n\t\tconst obj = JSON.parse(jsonStr);\n\t\tif (typeof obj !== \"object\" || obj === null || Array.isArray(obj)) return jsonStr;\n\t\tif (injectMediaSrcInto(obj, origin)) {\n\t\t\treturn JSON.stringify(obj);\n\t\t}\n\t\treturn jsonStr;\n\t} catch {\n\t\treturn jsonStr;\n\t}\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Recursively walk an object and inject `src` into local media values.\n * Returns true if any modifications were made.\n */\nfunction injectMediaSrcInto(obj: Record<string, unknown>, origin: string): boolean {\n\tlet modified = false;\n\n\t// Check if this object itself is a local media value\n\tif ((obj.provider === \"local\" || (!obj.provider && obj.id && obj.meta)) && !obj.src) {\n\t\tconst meta = isRecord(obj.meta) ? obj.meta : undefined;\n\t\tconst storageKey = meta?.storageKey ?? obj.id;\n\t\tif (typeof storageKey === \"string\" && storageKey) {\n\t\t\tobj.src = `${origin}${MEDIA_FILE_PREFIX}${storageKey}`;\n\t\t\tmodified = true;\n\t\t}\n\t}\n\n\t// Recurse into nested objects/arrays (e.g. Portable Text with image blocks)\n\tfor (const value of Object.values(obj)) {\n\t\tif (Array.isArray(value)) {\n\t\t\tfor (const item of value) {\n\t\t\t\tif (isRecord(item)) {\n\t\t\t\t\tif (injectMediaSrcInto(item, origin)) {\n\t\t\t\t\t\tmodified = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (isRecord(value)) {\n\t\t\tif (injectMediaSrcInto(value, origin)) {\n\t\t\t\tmodified = true;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn modified;\n}\n\n// ── Snapshot generation ─────────────────────────────────────────\n\n/**\n * Safe identifier pattern for snapshot table names.\n * More permissive than validateIdentifier() — allows leading underscores\n * (needed for system tables like _emdash_collections).\n */\nconst SAFE_TABLE_NAME = /^[a-z_][a-z0-9_]*$/;\n\n/** Snapshot shape consumed by the DO preview database */\nexport interface Snapshot {\n\ttables: Record<string, Record<string, unknown>[]>;\n\tschema: Record<\n\t\tstring,\n\t\t{\n\t\t\tcolumns: string[];\n\t\t\ttypes?: Record<string, string>;\n\t\t}\n\t>;\n\tgeneratedAt: string;\n}\n\n/**\n * System tables included in snapshots.\n * Content tables (ec_*) are discovered dynamically.\n */\nconst SYSTEM_TABLES = [\n\t\"_emdash_collections\",\n\t\"_emdash_fields\",\n\t\"_emdash_taxonomy_defs\",\n\t\"_emdash_menus\",\n\t\"_emdash_menu_items\",\n\t\"_emdash_sections\",\n\t\"_emdash_widget_areas\",\n\t\"_emdash_widgets\",\n\t\"_emdash_seo\",\n\t\"_emdash_migrations\",\n\t\"taxonomies\",\n\t\"content_taxonomies\",\n\t\"media\",\n\t\"options\",\n\t\"revisions\",\n];\n\n/**\n * Table name prefixes excluded from snapshots (auth/security data).\n */\nconst EXCLUDED_PREFIXES = [\n\t\"_emdash_api_tokens\",\n\t\"_emdash_oauth_tokens\",\n\t\"_emdash_authorization_codes\",\n\t\"_emdash_device_codes\",\n\t\"_emdash_migrations_lock\",\n\t\"_plugin_\",\n\t\"users\",\n\t\"sessions\",\n\t\"credentials\",\n\t\"challenges\",\n];\n\n/**\n * Options key prefixes safe for inclusion in snapshots.\n *\n * The options table contains plugin secrets (plugin:*), passkey challenges\n * (emdash:passkey_pending:*), and setup state that must not leak to\n * preview databases. Only site-level rendering settings are needed.\n */\nconst SAFE_OPTIONS_PREFIXES = [\"site:\"];\n\nfunction isExcluded(tableName: string): boolean {\n\treturn EXCLUDED_PREFIXES.some((prefix) => tableName.startsWith(prefix));\n}\n\n/** Column info from PRAGMA table_info */\ninterface ColumnInfo {\n\tname: string;\n\ttype: string;\n}\n\nexport interface GenerateSnapshotOptions {\n\t/** Include draft and trashed content (default: false) */\n\tincludeDrafts?: boolean;\n\t/** Origin URL for absolutizing local media URLs (e.g. \"https://mysite.com\") */\n\torigin?: string;\n}\n\n/**\n * Generate a portable database snapshot.\n *\n * Discovers ec_* content tables dynamically, exports system tables\n * needed for rendering, and includes schema info for table recreation.\n */\nexport async function generateSnapshot(\n\tdb: Kysely<Database>,\n\toptions?: GenerateSnapshotOptions,\n): Promise<Snapshot> {\n\tconst includeDrafts = options?.includeDrafts ?? false;\n\n\t// Discover all ec_* content tables\n\tconst tableResult = await sql<{ name: string }>`\n\t\tSELECT name FROM sqlite_master\n\t\tWHERE type = 'table'\n\t\tAND name LIKE 'ec_%'\n\t\tORDER BY name\n\t`.execute(db);\n\n\tconst contentTables = tableResult.rows.map((r) => r.name);\n\n\t// Build list of all tables to export\n\tconst allTables = [...contentTables, ...SYSTEM_TABLES];\n\n\tconst tables: Record<string, Record<string, unknown>[]> = {};\n\tconst schema: Record<string, { columns: string[]; types?: Record<string, string> }> = {};\n\n\tfor (const tableName of allTables) {\n\t\tif (isExcluded(tableName)) continue;\n\n\t\t// Validate identifier before interpolating into sql.raw().\n\t\t// SYSTEM_TABLES are hardcoded and safe, but ec_* names come from\n\t\t// sqlite_master and must be validated.\n\t\tif (!SAFE_TABLE_NAME.test(tableName)) continue;\n\n\t\ttry {\n\t\t\t// Get column info via PRAGMA\n\t\t\tconst pragmaResult = await sql<ColumnInfo>`\n\t\t\t\tPRAGMA table_info(${sql.raw(`\"${tableName}\"`)})\n\t\t\t`.execute(db);\n\n\t\t\tif (pragmaResult.rows.length === 0) continue;\n\n\t\t\tconst columns = pragmaResult.rows.map((r) => r.name);\n\t\t\tconst types: Record<string, string> = {};\n\t\t\tfor (const row of pragmaResult.rows) {\n\t\t\t\ttypes[row.name] = row.type || \"TEXT\";\n\t\t\t}\n\n\t\t\tschema[tableName] = { columns, types };\n\n\t\t\t// Fetch rows\n\t\t\tlet rows: Record<string, unknown>[];\n\n\t\t\tif (tableName.startsWith(\"ec_\")) {\n\t\t\t\tif (includeDrafts) {\n\t\t\t\t\t// Include all non-deleted content (published, draft, scheduled)\n\t\t\t\t\trows = (\n\t\t\t\t\t\tawait sql<Record<string, unknown>>`\n\t\t\t\t\t\tSELECT * FROM ${sql.raw(`\"${tableName}\"`)}\n\t\t\t\t\t\tWHERE deleted_at IS NULL\n\t\t\t\t\t`.execute(db)\n\t\t\t\t\t).rows;\n\t\t\t\t} else {\n\t\t\t\t\t// Only export published content\n\t\t\t\t\trows = (\n\t\t\t\t\t\tawait sql<Record<string, unknown>>`\n\t\t\t\t\t\tSELECT * FROM ${sql.raw(`\"${tableName}\"`)}\n\t\t\t\t\t\tWHERE deleted_at IS NULL\n\t\t\t\t\t\tAND (status = 'published' OR (status = 'scheduled' AND scheduled_at <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now')))\n\t\t\t\t\t`.execute(db)\n\t\t\t\t\t).rows;\n\t\t\t\t}\n\t\t\t} else if (tableName === \"options\") {\n\t\t\t\t// Filter options to safe rendering-only prefixes.\n\t\t\t\t// Excludes plugin secrets, passkey challenges, and setup state.\n\t\t\t\trows = (\n\t\t\t\t\tawait sql<Record<string, unknown>>`\n\t\t\t\t\tSELECT * FROM ${sql.raw(`\"${tableName}\"`)}\n\t\t\t\t`.execute(db)\n\t\t\t\t).rows.filter((row) => {\n\t\t\t\t\tconst name = typeof row.name === \"string\" ? row.name : \"\";\n\t\t\t\t\treturn SAFE_OPTIONS_PREFIXES.some((prefix) => name.startsWith(prefix));\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\trows = (\n\t\t\t\t\tawait sql<Record<string, unknown>>`\n\t\t\t\t\tSELECT * FROM ${sql.raw(`\"${tableName}\"`)}\n\t\t\t\t`.execute(db)\n\t\t\t\t).rows;\n\t\t\t}\n\n\t\t\tif (rows.length > 0) {\n\t\t\t\ttables[tableName] = rows;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Table might not exist yet (e.g. pre-migration) — skip silently\n\t\t}\n\t}\n\n\t// Absolutize local media URLs in content tables so snapshots are portable.\n\t// Local image fields are stored as JSON with provider:\"local\" and\n\t// meta.storageKey but no src — the URL is derived at render time.\n\t// For snapshots consumed by external preview services, inject src now.\n\tif (options?.origin) {\n\t\tconst origin = options.origin;\n\t\tfor (const [tableName, rows] of Object.entries(tables)) {\n\t\t\tif (!tableName.startsWith(\"ec_\")) continue;\n\t\t\tfor (const row of rows) {\n\t\t\t\tfor (const [col, value] of Object.entries(row)) {\n\t\t\t\t\tif (typeof value !== \"string\" || !value.startsWith(\"{\")) continue;\n\t\t\t\t\trow[col] = injectMediaSrc(value, origin);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\ttables,\n\t\tschema,\n\t\tgeneratedAt: new Date().toISOString(),\n\t};\n}\n","/**\n * Snapshot endpoint — exports a portable database snapshot for preview mode.\n *\n * Security:\n * - Authenticated users: requires content:read + schema:read permissions\n * - Preview services: requires valid X-Preview-Signature header (HMAC-SHA256)\n * - Excludes auth/user/session/token tables\n */\n\nimport type { User } from \"@emdash-cms/auth\";\nimport type { APIRoute } from \"astro\";\n\nimport { requirePerm } from \"#api/authorize.js\";\nimport { apiError, apiSuccess, handleError } from \"#api/error.js\";\nimport {\n\tgenerateSnapshot,\n\tparsePreviewSignatureHeader,\n\tverifyPreviewSignature,\n} from \"#api/handlers/snapshot.js\";\nimport { getPublicOrigin } from \"#api/public-url.js\";\nimport { resolveSecretsCached } from \"#config/secrets.js\";\n\nexport const prerender = false;\n\nexport const GET: APIRoute = async ({ request, locals, url, session }) => {\n\tconst { emdash } = locals;\n\t// This route is in PUBLIC_API_EXACT (for preview-signature callers with no session),\n\t// so auth middleware skips user resolution. Manually resolve the session user here\n\t// to support session-authenticated admin users alongside preview-signature auth.\n\tlet user: User | undefined = (locals as { user?: User }).user;\n\tif (!user && session && emdash?.db) {\n\t\ttry {\n\t\t\tconst { createKyselyAdapter } = await import(\"@emdash-cms/auth/adapters/kysely\");\n\t\t\tconst sessionUser = await session.get(\"user\");\n\t\t\tif (sessionUser?.id) {\n\t\t\t\tconst adapter = createKyselyAdapter(emdash.db);\n\t\t\t\tconst resolved = await adapter.getUserById(sessionUser.id);\n\t\t\t\tif (resolved && !resolved.disabled) {\n\t\t\t\t\tuser = resolved;\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Session resolution failed, continue to preview-signature check\n\t\t}\n\t}\n\n\tif (!emdash?.db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\t// Check for preview signature auth (used by DO preview services)\n\tconst previewSig = request.headers.get(\"X-Preview-Signature\");\n\tlet authorized = false;\n\n\tif (previewSig) {\n\t\t// Resolves env override or DB-stored value. Always non-empty after\n\t\t// resolution, so the signature path is never silently disabled.\n\t\t// Note: a signing process without access to this database (e.g. a\n\t\t// remote preview Worker) must set the same `EMDASH_PREVIEW_SECRET`\n\t\t// env var on both sides.\n\t\tconst { previewSecret: secret, previewSecretSource } = await resolveSecretsCached(emdash.db);\n\t\tconst parsed = parsePreviewSignatureHeader(previewSig);\n\t\tif (!parsed) {\n\t\t\tconsole.warn(\"[snapshot] Failed to parse X-Preview-Signature header\");\n\t\t} else {\n\t\t\tauthorized = await verifyPreviewSignature(parsed.source, parsed.exp, parsed.sig, secret);\n\t\t\tif (!authorized) {\n\t\t\t\tconst fields: Record<string, unknown> = {\n\t\t\t\t\tsource: parsed.source,\n\t\t\t\t\texp: parsed.exp,\n\t\t\t\t\texpired: parsed.exp < Date.now() / 1000,\n\t\t\t\t\tsecretSource: previewSecretSource,\n\t\t\t\t};\n\t\t\t\tif (previewSecretSource === \"db\") {\n\t\t\t\t\tfields.hint =\n\t\t\t\t\t\t\"Set EMDASH_PREVIEW_SECRET in both this process and the signing process to share secrets across deployments\";\n\t\t\t\t}\n\t\t\t\tconsole.warn(\"[snapshot] Preview signature verification failed\", fields);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!authorized) {\n\t\t// Fall back to standard user auth\n\t\tconst contentDenied = requirePerm(user, \"content:read\");\n\t\tif (contentDenied) return contentDenied;\n\t\tconst schemaDenied = requirePerm(user, \"schema:read\");\n\t\tif (schemaDenied) return schemaDenied;\n\t}\n\n\ttry {\n\t\tconst includeDrafts = url.searchParams.get(\"drafts\") === \"true\";\n\t\tconst snapshot = await generateSnapshot(emdash.db, {\n\t\t\tincludeDrafts,\n\t\t\torigin: getPublicOrigin(url, emdash.config),\n\t\t});\n\n\t\treturn apiSuccess(snapshot);\n\t} catch (error) {\n\t\treturn handleError(error, \"Failed to generate snapshot\", \"SNAPSHOT_ERROR\");\n\t}\n};\n"],"mappings":"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"}
|
|
@@ -2,17 +2,18 @@ import "../../../../../../../dialect-helpers-DRI5pyY3.mjs";
|
|
|
2
2
|
import "../../../../../../../base64-CqR-7kqF.mjs";
|
|
3
3
|
import "../../../../../../../types-BXSUSAjt.mjs";
|
|
4
4
|
import "../../../../../../../taxonomy-CdllE4oq.mjs";
|
|
5
|
-
import "../../../../../../../request-cache-
|
|
6
|
-
import "../../../../../../../loader-
|
|
7
|
-
import "../../../../../../../resolve-
|
|
8
|
-
import "../../../../../../../taxonomies-
|
|
9
|
-
import { a as handleTermGet, r as handleTermCreate, s as handleTermTranslations } from "../../../../../../../taxonomies-
|
|
10
|
-
import { a as unwrapResult, i as requireDb, r as handleError, t as apiError } from "../../../../../../../error-
|
|
11
|
-
import { i as parseQuery, n as parseBody, t as isParseError } from "../../../../../../../parse-
|
|
12
|
-
import { Pn as localeFilterQuery } from "../../../../../../../redirects-
|
|
13
|
-
import "../../../../../../../byline-fields-
|
|
5
|
+
import "../../../../../../../request-cache-UwmBAiUK.mjs";
|
|
6
|
+
import "../../../../../../../loader-BqWjcH3h.mjs";
|
|
7
|
+
import "../../../../../../../resolve-B3NUUtVY.mjs";
|
|
8
|
+
import "../../../../../../../taxonomies-BBxYA38v.mjs";
|
|
9
|
+
import { a as handleTermGet, r as handleTermCreate, s as handleTermTranslations } from "../../../../../../../taxonomies-DuESHWKI.mjs";
|
|
10
|
+
import { a as unwrapResult, i as requireDb, r as handleError, t as apiError } from "../../../../../../../error-CNn_w7jf.mjs";
|
|
11
|
+
import { i as parseQuery, n as parseBody, t as isParseError } from "../../../../../../../parse-DzSrk1t8.mjs";
|
|
12
|
+
import { Pn as localeFilterQuery } from "../../../../../../../redirects-6Zg2SoYo.mjs";
|
|
13
|
+
import "../../../../../../../byline-fields-B0NO1yUB.mjs";
|
|
14
|
+
import "../../../../../../../status-2gZklYuj.mjs";
|
|
14
15
|
import "../../../../../../../api/schemas/index.mjs";
|
|
15
|
-
import { n as requirePerm } from "../../../../../../../authorize-
|
|
16
|
+
import { n as requirePerm } from "../../../../../../../authorize-D5gfBVU5.mjs";
|
|
16
17
|
import { z } from "zod";
|
|
17
18
|
|
|
18
19
|
//#region src/astro/routes/api/taxonomies/[name]/terms/[slug]/translations.ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translations.mjs","names":[],"sources":["../../../../../../../../src/astro/routes/api/taxonomies/[name]/terms/[slug]/translations.ts"],"sourcesContent":["/**\n * Term translation endpoints\n *\n * GET /_emdash/api/taxonomies/:name/terms/:slug/translations[?locale=xx]\n * POST /_emdash/api/taxonomies/:name/terms/:slug/translations\n * body: { locale, label?, slug? }\n */\n\nimport type { APIRoute } from \"astro\";\nimport { z } from \"zod\";\n\nimport { requirePerm } from \"#api/authorize.js\";\nimport { apiError, handleError, requireDb, unwrapResult } from \"#api/error.js\";\nimport {\n\thandleTermCreate,\n\thandleTermGet,\n\thandleTermTranslations,\n} from \"#api/handlers/taxonomies.js\";\nimport { isParseError, parseBody, parseQuery } from \"#api/parse.js\";\nimport { localeFilterQuery } from \"#api/schemas.js\";\n\nexport const prerender = false;\n\nconst createTermTranslationBody = z\n\t.object({\n\t\tlocale: z.string().min(1),\n\t\tlabel: z.string().min(1).optional(),\n\t\tslug: z.string().min(1).optional(),\n\t})\n\t.meta({ id: \"CreateTermTranslationBody\" });\n\nexport const GET: APIRoute = async ({ params, request, locals }) => {\n\tconst { emdash, user } = locals;\n\tconst { name, slug } = params;\n\tif (!name || !slug) return apiError(\"VALIDATION_ERROR\", \"Taxonomy name and slug required\", 400);\n\n\tconst dbErr = requireDb(emdash?.db);\n\tif (dbErr) return dbErr;\n\n\tconst denied = requirePerm(user, \"taxonomies:read\");\n\tif (denied) return denied;\n\n\tconst query = parseQuery(new URL(request.url), localeFilterQuery);\n\tif (isParseError(query)) return query;\n\n\ttry {\n\t\tconst anchor = await handleTermGet(emdash.db, name, slug, { locale: query.locale });\n\t\tif (!anchor.success) return unwrapResult(anchor);\n\t\tconst result = await handleTermTranslations(emdash.db, anchor.data.term.id);\n\t\treturn unwrapResult(result);\n\t} catch (error) {\n\t\treturn handleError(error, \"Failed to list term translations\", \"TERM_TRANSLATIONS_ERROR\");\n\t}\n};\n\nexport const POST: APIRoute = async ({ params, request, locals }) => {\n\tconst { emdash, user } = locals;\n\tconst { name, slug } = params;\n\tif (!name || !slug) return apiError(\"VALIDATION_ERROR\", \"Taxonomy name and slug required\", 400);\n\n\tconst dbErr = requireDb(emdash?.db);\n\tif (dbErr) return dbErr;\n\n\tconst denied = requirePerm(user, \"taxonomies:manage\");\n\tif (denied) return denied;\n\n\tconst query = parseQuery(new URL(request.url), localeFilterQuery);\n\tif (isParseError(query)) return query;\n\n\ttry {\n\t\tconst body = await parseBody(request, createTermTranslationBody);\n\t\tif (isParseError(body)) return body;\n\n\t\tconst source = await handleTermGet(emdash.db, name, slug, { locale: query.locale });\n\t\tif (!source.success) return unwrapResult(source);\n\n\t\tconst result = await handleTermCreate(emdash.db, name, {\n\t\t\tslug: body.slug ?? source.data.term.slug,\n\t\t\tlabel: body.label ?? source.data.term.label,\n\t\t\tparentId: source.data.term.parentId,\n\t\t\tdescription: source.data.term.description,\n\t\t\tlocale: body.locale,\n\t\t\ttranslationOf: source.data.term.id,\n\t\t});\n\t\treturn unwrapResult(result, 201);\n\t} catch (error) {\n\t\treturn handleError(error, \"Failed to create term translation\", \"TERM_TRANSLATION_CREATE_ERROR\");\n\t}\n};\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"translations.mjs","names":[],"sources":["../../../../../../../../src/astro/routes/api/taxonomies/[name]/terms/[slug]/translations.ts"],"sourcesContent":["/**\n * Term translation endpoints\n *\n * GET /_emdash/api/taxonomies/:name/terms/:slug/translations[?locale=xx]\n * POST /_emdash/api/taxonomies/:name/terms/:slug/translations\n * body: { locale, label?, slug? }\n */\n\nimport type { APIRoute } from \"astro\";\nimport { z } from \"zod\";\n\nimport { requirePerm } from \"#api/authorize.js\";\nimport { apiError, handleError, requireDb, unwrapResult } from \"#api/error.js\";\nimport {\n\thandleTermCreate,\n\thandleTermGet,\n\thandleTermTranslations,\n} from \"#api/handlers/taxonomies.js\";\nimport { isParseError, parseBody, parseQuery } from \"#api/parse.js\";\nimport { localeFilterQuery } from \"#api/schemas.js\";\n\nexport const prerender = false;\n\nconst createTermTranslationBody = z\n\t.object({\n\t\tlocale: z.string().min(1),\n\t\tlabel: z.string().min(1).optional(),\n\t\tslug: z.string().min(1).optional(),\n\t})\n\t.meta({ id: \"CreateTermTranslationBody\" });\n\nexport const GET: APIRoute = async ({ params, request, locals }) => {\n\tconst { emdash, user } = locals;\n\tconst { name, slug } = params;\n\tif (!name || !slug) return apiError(\"VALIDATION_ERROR\", \"Taxonomy name and slug required\", 400);\n\n\tconst dbErr = requireDb(emdash?.db);\n\tif (dbErr) return dbErr;\n\n\tconst denied = requirePerm(user, \"taxonomies:read\");\n\tif (denied) return denied;\n\n\tconst query = parseQuery(new URL(request.url), localeFilterQuery);\n\tif (isParseError(query)) return query;\n\n\ttry {\n\t\tconst anchor = await handleTermGet(emdash.db, name, slug, { locale: query.locale });\n\t\tif (!anchor.success) return unwrapResult(anchor);\n\t\tconst result = await handleTermTranslations(emdash.db, anchor.data.term.id);\n\t\treturn unwrapResult(result);\n\t} catch (error) {\n\t\treturn handleError(error, \"Failed to list term translations\", \"TERM_TRANSLATIONS_ERROR\");\n\t}\n};\n\nexport const POST: APIRoute = async ({ params, request, locals }) => {\n\tconst { emdash, user } = locals;\n\tconst { name, slug } = params;\n\tif (!name || !slug) return apiError(\"VALIDATION_ERROR\", \"Taxonomy name and slug required\", 400);\n\n\tconst dbErr = requireDb(emdash?.db);\n\tif (dbErr) return dbErr;\n\n\tconst denied = requirePerm(user, \"taxonomies:manage\");\n\tif (denied) return denied;\n\n\tconst query = parseQuery(new URL(request.url), localeFilterQuery);\n\tif (isParseError(query)) return query;\n\n\ttry {\n\t\tconst body = await parseBody(request, createTermTranslationBody);\n\t\tif (isParseError(body)) return body;\n\n\t\tconst source = await handleTermGet(emdash.db, name, slug, { locale: query.locale });\n\t\tif (!source.success) return unwrapResult(source);\n\n\t\tconst result = await handleTermCreate(emdash.db, name, {\n\t\t\tslug: body.slug ?? source.data.term.slug,\n\t\t\tlabel: body.label ?? source.data.term.label,\n\t\t\tparentId: source.data.term.parentId,\n\t\t\tdescription: source.data.term.description,\n\t\t\tlocale: body.locale,\n\t\t\ttranslationOf: source.data.term.id,\n\t\t});\n\t\treturn unwrapResult(result, 201);\n\t} catch (error) {\n\t\treturn handleError(error, \"Failed to create term translation\", \"TERM_TRANSLATION_CREATE_ERROR\");\n\t}\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAqBA,MAAa,YAAY;AAEzB,MAAM,4BAA4B,EAChC,OAAO;CACP,QAAQ,EAAE,QAAQ,CAAC,IAAI,EAAE;CACzB,OAAO,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,UAAU;CACnC,MAAM,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,UAAU;CAClC,CAAC,CACD,KAAK,EAAE,IAAI,6BAA6B,CAAC;AAE3C,MAAa,MAAgB,OAAO,EAAE,QAAQ,SAAS,aAAa;CACnE,MAAM,EAAE,QAAQ,SAAS;CACzB,MAAM,EAAE,MAAM,SAAS;AACvB,KAAI,CAAC,QAAQ,CAAC,KAAM,QAAO,SAAS,oBAAoB,mCAAmC,IAAI;CAE/F,MAAM,QAAQ,UAAU,QAAQ,GAAG;AACnC,KAAI,MAAO,QAAO;CAElB,MAAM,SAAS,YAAY,MAAM,kBAAkB;AACnD,KAAI,OAAQ,QAAO;CAEnB,MAAM,QAAQ,WAAW,IAAI,IAAI,QAAQ,IAAI,EAAE,kBAAkB;AACjE,KAAI,aAAa,MAAM,CAAE,QAAO;AAEhC,KAAI;EACH,MAAM,SAAS,MAAM,cAAc,OAAO,IAAI,MAAM,MAAM,EAAE,QAAQ,MAAM,QAAQ,CAAC;AACnF,MAAI,CAAC,OAAO,QAAS,QAAO,aAAa,OAAO;AAEhD,SAAO,aADQ,MAAM,uBAAuB,OAAO,IAAI,OAAO,KAAK,KAAK,GAAG,CAChD;UACnB,OAAO;AACf,SAAO,YAAY,OAAO,oCAAoC,0BAA0B;;;AAI1F,MAAa,OAAiB,OAAO,EAAE,QAAQ,SAAS,aAAa;CACpE,MAAM,EAAE,QAAQ,SAAS;CACzB,MAAM,EAAE,MAAM,SAAS;AACvB,KAAI,CAAC,QAAQ,CAAC,KAAM,QAAO,SAAS,oBAAoB,mCAAmC,IAAI;CAE/F,MAAM,QAAQ,UAAU,QAAQ,GAAG;AACnC,KAAI,MAAO,QAAO;CAElB,MAAM,SAAS,YAAY,MAAM,oBAAoB;AACrD,KAAI,OAAQ,QAAO;CAEnB,MAAM,QAAQ,WAAW,IAAI,IAAI,QAAQ,IAAI,EAAE,kBAAkB;AACjE,KAAI,aAAa,MAAM,CAAE,QAAO;AAEhC,KAAI;EACH,MAAM,OAAO,MAAM,UAAU,SAAS,0BAA0B;AAChE,MAAI,aAAa,KAAK,CAAE,QAAO;EAE/B,MAAM,SAAS,MAAM,cAAc,OAAO,IAAI,MAAM,MAAM,EAAE,QAAQ,MAAM,QAAQ,CAAC;AACnF,MAAI,CAAC,OAAO,QAAS,QAAO,aAAa,OAAO;AAUhD,SAAO,aARQ,MAAM,iBAAiB,OAAO,IAAI,MAAM;GACtD,MAAM,KAAK,QAAQ,OAAO,KAAK,KAAK;GACpC,OAAO,KAAK,SAAS,OAAO,KAAK,KAAK;GACtC,UAAU,OAAO,KAAK,KAAK;GAC3B,aAAa,OAAO,KAAK,KAAK;GAC9B,QAAQ,KAAK;GACb,eAAe,OAAO,KAAK,KAAK;GAChC,CAAC,EAC0B,IAAI;UACxB,OAAO;AACf,SAAO,YAAY,OAAO,qCAAqC,gCAAgC"}
|