emdash 0.31.1 → 0.32.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/api/route-utils.d.mts +3 -3
- package/dist/api/route-utils.mjs +16 -14
- package/dist/api/route-utils.mjs.map +1 -1
- package/dist/api/schemas/index.d.mts +2 -2
- package/dist/api/schemas/index.mjs +4 -4
- package/dist/{api-BkqFzIu4.mjs → api-DgPbMlng.mjs} +24 -20
- package/dist/api-DgPbMlng.mjs.map +1 -0
- package/dist/{api-tokens-uPt8UDpx.mjs → api-tokens-p-iMzvXR.mjs} +2 -2
- package/dist/{api-tokens-uPt8UDpx.mjs.map → api-tokens-p-iMzvXR.mjs.map} +1 -1
- package/dist/{apply-DuXsqll7.mjs → apply-1_6ra7NP.mjs} +19 -19
- package/dist/{apply-DuXsqll7.mjs.map → apply-1_6ra7NP.mjs.map} +1 -1
- package/dist/astro/index.d.mts +12 -12
- package/dist/astro/index.mjs +5 -1
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +10 -10
- package/dist/astro/middleware/auth.mjs +5 -5
- package/dist/astro/middleware/redirect.mjs +5 -5
- package/dist/astro/middleware/request-context.mjs +1 -1
- package/dist/astro/middleware.d.mts +10 -10
- package/dist/astro/middleware.mjs +87 -59
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +6 -6
- package/dist/astro/routes/api/admin/allowed-domains/index.mjs +6 -6
- package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +4 -4
- package/dist/astro/routes/api/admin/api-tokens/index.mjs +5 -5
- package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +4 -4
- package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +8 -8
- package/dist/astro/routes/api/admin/byline-fields/index.mjs +8 -8
- package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +8 -8
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +15 -14
- package/dist/astro/routes/api/admin/bylines/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +15 -14
- package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/admin/bylines/index.mjs +15 -15
- package/dist/astro/routes/api/admin/comments/_id_/status.mjs +11 -11
- package/dist/astro/routes/api/admin/comments/_id_.mjs +5 -5
- package/dist/astro/routes/api/admin/comments/bulk.mjs +9 -9
- package/dist/astro/routes/api/admin/comments/counts.mjs +5 -5
- package/dist/astro/routes/api/admin/comments/index.mjs +9 -9
- package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +4 -4
- package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +3 -3
- package/dist/astro/routes/api/admin/media-usage/repair.mjs +12 -10
- package/dist/astro/routes/api/admin/media-usage/repair.mjs.map +1 -1
- package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +4 -4
- package/dist/astro/routes/api/admin/oauth-clients/index.mjs +4 -4
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +36 -34
- package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +36 -34
- package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +36 -34
- package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/mcp.mjs +4 -4
- package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs +36 -34
- package/dist/astro/routes/api/admin/plugins/_id_/settings.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +36 -34
- package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +36 -34
- package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/index.mjs +36 -34
- package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +3 -3
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +36 -34
- 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 +36 -34
- package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +36 -34
- package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +36 -34
- 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 +37 -35
- package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +36 -34
- package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs +37 -35
- package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
- package/dist/astro/routes/api/admin/plugins/updates.mjs +36 -34
- package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
- package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +36 -34
- 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 +3 -3
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +36 -34
- package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
- package/dist/astro/routes/api/admin/users/_id_/disable.mjs +2 -2
- package/dist/astro/routes/api/admin/users/_id_/enable.mjs +2 -2
- package/dist/astro/routes/api/admin/users/_id_/index.mjs +6 -6
- package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +3 -3
- package/dist/astro/routes/api/admin/users/index.mjs +6 -6
- package/dist/astro/routes/api/auth/dev-bypass.mjs +7 -6
- package/dist/astro/routes/api/auth/dev-bypass.mjs.map +1 -1
- package/dist/astro/routes/api/auth/invite/accept.mjs +2 -2
- package/dist/astro/routes/api/auth/invite/complete.mjs +7 -7
- package/dist/astro/routes/api/auth/invite/index.mjs +7 -7
- package/dist/astro/routes/api/auth/invite/register-options.mjs +7 -7
- package/dist/astro/routes/api/auth/logout.mjs +3 -3
- package/dist/astro/routes/api/auth/magic-link/send.mjs +8 -8
- package/dist/astro/routes/api/auth/magic-link/verify.mjs +3 -3
- package/dist/astro/routes/api/auth/me.mjs +7 -7
- package/dist/astro/routes/api/auth/mode.mjs +2 -2
- package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +2 -2
- package/dist/astro/routes/api/auth/oauth/_provider_.mjs +1 -1
- package/dist/astro/routes/api/auth/passkey/_id_.mjs +6 -6
- package/dist/astro/routes/api/auth/passkey/index.mjs +2 -2
- package/dist/astro/routes/api/auth/passkey/options.mjs +8 -8
- package/dist/astro/routes/api/auth/passkey/register/options.mjs +7 -7
- package/dist/astro/routes/api/auth/passkey/register/verify.mjs +7 -7
- package/dist/astro/routes/api/auth/passkey/verify.mjs +7 -7
- package/dist/astro/routes/api/auth/signup/complete.mjs +7 -7
- package/dist/astro/routes/api/auth/signup/request.mjs +8 -8
- package/dist/astro/routes/api/auth/signup/verify.mjs +2 -2
- package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +11 -11
- package/dist/astro/routes/api/comments/_collection_/_contentId_/reactions.mjs +10 -10
- package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +9 -9
- package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +7 -7
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs +16 -14
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/children.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs +16 -14
- package/dist/astro/routes/api/content/_collection_/_id_/references/_relation_/parents.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +7 -7
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +14 -13
- package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/_id_.d.mts.map +1 -1
- package/dist/astro/routes/api/content/_collection_/_id_.mjs +8 -8
- package/dist/astro/routes/api/content/_collection_/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/content/_collection_/authors.mjs +3 -3
- package/dist/astro/routes/api/content/_collection_/index.mjs +7 -7
- package/dist/astro/routes/api/content/_collection_/trash.mjs +7 -7
- package/dist/astro/routes/api/dashboard.mjs +7 -7
- 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 +12 -12
- package/dist/astro/routes/api/import/wordpress/analyze.mjs +3 -3
- package/dist/astro/routes/api/import/wordpress/execute.d.mts +11 -11
- package/dist/astro/routes/api/import/wordpress/execute.mjs +12 -11
- package/dist/astro/routes/api/import/wordpress/execute.mjs.map +1 -1
- package/dist/astro/routes/api/import/wordpress/media.d.mts +10 -10
- package/dist/astro/routes/api/import/wordpress/media.mjs +7 -7
- package/dist/astro/routes/api/import/wordpress/prepare.mjs +9 -9
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.d.mts +11 -11
- package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +11 -9
- 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 +12 -12
- package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +11 -11
- package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +20 -19
- 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 +41 -41
- package/dist/astro/routes/api/media/_id_/confirm.d.mts.map +1 -1
- package/dist/astro/routes/api/media/_id_/confirm.mjs +105 -36
- package/dist/astro/routes/api/media/_id_/confirm.mjs.map +1 -1
- package/dist/astro/routes/api/media/_id_/upload.d.mts +8 -0
- package/dist/astro/routes/api/media/_id_/upload.d.mts.map +1 -0
- package/dist/astro/routes/api/media/_id_/upload.mjs +168 -0
- package/dist/astro/routes/api/media/_id_/upload.mjs.map +1 -0
- package/dist/astro/routes/api/media/_id_/usage.mjs +14 -12
- package/dist/astro/routes/api/media/_id_/usage.mjs.map +1 -1
- package/dist/astro/routes/api/media/_id_.d.mts.map +1 -1
- package/dist/astro/routes/api/media/_id_.mjs +21 -14
- package/dist/astro/routes/api/media/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/media/file/_...key_.mjs +2 -2
- package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +3 -3
- package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +3 -3
- package/dist/astro/routes/api/media/providers/index.mjs +3 -3
- package/dist/astro/routes/api/media/upload-url.d.mts.map +1 -1
- package/dist/astro/routes/api/media/upload-url.mjs +34 -23
- package/dist/astro/routes/api/media/upload-url.mjs.map +1 -1
- package/dist/astro/routes/api/media.mjs +13 -12
- package/dist/astro/routes/api/media.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +9 -8
- package/dist/astro/routes/api/menus/_name_/items/_id_.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/items.mjs +9 -8
- package/dist/astro/routes/api/menus/_name_/items.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/reorder.mjs +9 -8
- package/dist/astro/routes/api/menus/_name_/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_/translations.mjs +9 -8
- package/dist/astro/routes/api/menus/_name_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/menus/_name_.mjs +9 -8
- package/dist/astro/routes/api/menus/_name_.mjs.map +1 -1
- package/dist/astro/routes/api/menus/index.mjs +9 -8
- package/dist/astro/routes/api/menus/index.mjs.map +1 -1
- package/dist/astro/routes/api/oauth/authorize.mjs +5 -5
- package/dist/astro/routes/api/oauth/device/authorize.mjs +6 -6
- package/dist/astro/routes/api/oauth/device/code.mjs +7 -7
- package/dist/astro/routes/api/oauth/device/token.mjs +7 -7
- package/dist/astro/routes/api/oauth/register.mjs +3 -3
- package/dist/astro/routes/api/oauth/token/refresh.mjs +6 -6
- package/dist/astro/routes/api/oauth/token/revoke.mjs +6 -6
- package/dist/astro/routes/api/oauth/token.mjs +6 -6
- package/dist/astro/routes/api/openapi.json.mjs +30 -8
- package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
- package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +5 -5
- package/dist/astro/routes/api/redirects/404s/index.mjs +10 -10
- package/dist/astro/routes/api/redirects/404s/summary.mjs +10 -10
- package/dist/astro/routes/api/redirects/_id_.mjs +11 -11
- package/dist/astro/routes/api/redirects/index.mjs +11 -11
- package/dist/astro/routes/api/relations/_id_/index.mjs +16 -14
- package/dist/astro/routes/api/relations/_id_/index.mjs.map +1 -1
- package/dist/astro/routes/api/relations/_id_/translations.mjs +12 -10
- package/dist/astro/routes/api/relations/_id_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/relations/index.mjs +16 -14
- package/dist/astro/routes/api/relations/index.mjs.map +1 -1
- package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +3 -3
- package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +3 -3
- package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +36 -34
- 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 +36 -34
- 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 +36 -34
- package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +36 -34
- package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/collections/index.mjs +36 -34
- package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/index.mjs +10 -8
- package/dist/astro/routes/api/schema/index.mjs.map +1 -1
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs +36 -34
- package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/schema/orphans/index.mjs +36 -34
- package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
- package/dist/astro/routes/api/search/enable.mjs +12 -11
- package/dist/astro/routes/api/search/enable.mjs.map +1 -1
- package/dist/astro/routes/api/search/index.mjs +11 -10
- package/dist/astro/routes/api/search/index.mjs.map +1 -1
- package/dist/astro/routes/api/search/rebuild.mjs +12 -11
- package/dist/astro/routes/api/search/rebuild.mjs.map +1 -1
- package/dist/astro/routes/api/search/stats.mjs +8 -7
- package/dist/astro/routes/api/search/stats.mjs.map +1 -1
- package/dist/astro/routes/api/search/suggest.mjs +11 -10
- package/dist/astro/routes/api/search/suggest.mjs.map +1 -1
- package/dist/astro/routes/api/sections/_slug_.mjs +10 -10
- package/dist/astro/routes/api/sections/index.mjs +10 -10
- package/dist/astro/routes/api/settings/backups/archives/_name_.mjs +4 -4
- package/dist/astro/routes/api/settings/backups/archives/index.mjs +4 -4
- package/dist/astro/routes/api/settings/backups/export.mjs +4 -4
- package/dist/astro/routes/api/settings/backups/index.mjs +5 -5
- package/dist/astro/routes/api/settings/email.mjs +4 -4
- package/dist/astro/routes/api/settings.mjs +12 -12
- package/dist/astro/routes/api/setup/admin-verify.mjs +8 -8
- package/dist/astro/routes/api/setup/admin.mjs +8 -8
- package/dist/astro/routes/api/setup/dev-bypass.mjs +24 -22
- package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
- package/dist/astro/routes/api/setup/dev-reset.mjs +2 -2
- package/dist/astro/routes/api/setup/index.mjs +24 -22
- 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 -5
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +14 -13
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +14 -13
- package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.d.mts.map +1 -1
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +19 -15
- package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs.map +1 -1
- package/dist/astro/routes/api/taxonomies/index.mjs +14 -13
- package/dist/astro/routes/api/taxonomies/index.mjs.map +1 -1
- package/dist/astro/routes/api/themes/preview.mjs +4 -4
- package/dist/astro/routes/api/typegen.mjs +5 -5
- package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +1 -1
- package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +1 -1
- package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +7 -7
- package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +10 -10
- package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +10 -10
- package/dist/astro/routes/api/widget-areas/_name_.mjs +6 -6
- package/dist/astro/routes/api/widget-areas/index.mjs +10 -10
- package/dist/astro/routes/api/widget-components.mjs +2 -2
- package/dist/astro/routes/robots.txt.mjs +5 -5
- package/dist/astro/routes/sitemap-_collection_.xml.mjs +7 -7
- package/dist/astro/routes/sitemap.xml.mjs +5 -5
- package/dist/astro/types.d.mts +14 -13
- package/dist/astro/types.d.mts.map +1 -1
- package/dist/auth/providers/github.d.mts +1 -1
- package/dist/auth/providers/google.d.mts +1 -1
- package/dist/{authorize-6WPZa1xZ.mjs → authorize-R5iYYrpa.mjs} +2 -2
- package/dist/{authorize-6WPZa1xZ.mjs.map → authorize-R5iYYrpa.mjs.map} +1 -1
- package/dist/{backup-DoW-H0r4.mjs → backup-LpQ1V_sR.mjs} +2 -2
- package/dist/{backup-DoW-H0r4.mjs.map → backup-LpQ1V_sR.mjs.map} +1 -1
- package/dist/{byline-BTOxUlFF.mjs → byline-C5TAqs8N.mjs} +6 -6
- package/dist/{byline-BTOxUlFF.mjs.map → byline-C5TAqs8N.mjs.map} +1 -1
- package/dist/{byline-fields-CdU_LTF1.mjs → byline-fields-T9ZLZMxJ.mjs} +1 -1
- package/dist/{byline-fields-CdU_LTF1.mjs.map → byline-fields-T9ZLZMxJ.mjs.map} +1 -1
- package/dist/{bylines-Bc-3xtpz.mjs → bylines-B8-WGdja.mjs} +6 -6
- package/dist/{bylines-Bc-3xtpz.mjs.map → bylines-B8-WGdja.mjs.map} +1 -1
- package/dist/{bylines-oVN0iPwX.mjs → bylines-DGuqBLjV.mjs} +5 -5
- package/dist/{bylines-oVN0iPwX.mjs.map → bylines-DGuqBLjV.mjs.map} +1 -1
- package/dist/{cache-DDwy3JDW.mjs → cache-B26cufFd.mjs} +2 -2
- package/dist/{cache-DDwy3JDW.mjs.map → cache-B26cufFd.mjs.map} +1 -1
- package/dist/{chunks-Bq-5DPrD.mjs → chunks-D5dlPeRb.mjs} +2 -2
- package/dist/{chunks-Bq-5DPrD.mjs.map → chunks-D5dlPeRb.mjs.map} +1 -1
- package/dist/cli/index.mjs +20 -19
- 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 +2 -2
- package/dist/{comment-B9cUud8H.mjs → comment-DnTxxVHv.mjs} +2 -2
- package/dist/{comment-B9cUud8H.mjs.map → comment-DnTxxVHv.mjs.map} +1 -1
- package/dist/{comment-reaction-VUXO981F.mjs → comment-reaction-BUlUz2bh.mjs} +2 -2
- package/dist/{comment-reaction-VUXO981F.mjs.map → comment-reaction-BUlUz2bh.mjs.map} +1 -1
- package/dist/{comments-PXj1E9Si.mjs → comments-QPKgK3nk.mjs} +3 -3
- package/dist/{comments-PXj1E9Si.mjs.map → comments-QPKgK3nk.mjs.map} +1 -1
- package/dist/{config-B6b_BobR.mjs → config-DyF3OKg_.mjs} +15 -10
- package/dist/config-DyF3OKg_.mjs.map +1 -0
- package/dist/{content-BC43pICM.mjs → content-CpfKV9QE.mjs} +9 -9
- package/dist/content-CpfKV9QE.mjs.map +1 -0
- package/dist/{content-refresh-CPMSauD3.mjs → content-refresh-DgqUBeTv.mjs} +5 -5
- package/dist/{content-refresh-CPMSauD3.mjs.map → content-refresh-DgqUBeTv.mjs.map} +1 -1
- package/dist/{context-DOurRIrb.mjs → context-kO4YZB03.mjs} +10 -10
- package/dist/{context-DOurRIrb.mjs.map → context-kO4YZB03.mjs.map} +1 -1
- package/dist/{dashboard-CBm4zhc3.mjs → dashboard-DoBwPnON.mjs} +4 -4
- package/dist/{dashboard-CBm4zhc3.mjs.map → dashboard-DoBwPnON.mjs.map} +1 -1
- package/dist/db/index.d.mts +2 -2
- package/dist/db/index.mjs +3 -2
- package/dist/db/index.mjs.map +1 -1
- package/dist/{device-flow-BeA6lUS1.mjs → device-flow-DhUsyJU6.mjs} +4 -4
- package/dist/{device-flow-BeA6lUS1.mjs.map → device-flow-DhUsyJU6.mjs.map} +1 -1
- package/dist/{dialect-helpers-BtOwo2us.mjs → dialect-helpers-DFwyWjTM.mjs} +21 -2
- package/dist/dialect-helpers-DFwyWjTM.mjs.map +1 -0
- package/dist/{error-DJ98TRbQ.mjs → error-DmmN74gW.mjs} +2 -2
- package/dist/{error-DJ98TRbQ.mjs.map → error-DmmN74gW.mjs.map} +1 -1
- package/dist/{escape-CFedIB1C.mjs → escape-CdW31WsG.mjs} +1 -1
- package/dist/{escape-CFedIB1C.mjs.map → escape-CdW31WsG.mjs.map} +1 -1
- package/dist/{field-defs-cache-KfNJ_qLQ.mjs → field-defs-cache-QMVnzTH6.mjs} +2 -2
- package/dist/{field-defs-cache-KfNJ_qLQ.mjs.map → field-defs-cache-QMVnzTH6.mjs.map} +1 -1
- package/dist/{fts-manager-hyCU9OOp.mjs → fts-manager-vncsZIJm.mjs} +3 -3
- package/dist/{fts-manager-hyCU9OOp.mjs.map → fts-manager-vncsZIJm.mjs.map} +1 -1
- package/dist/{hash-DFFrkivP.mjs → hash-Cb7U1b5M.mjs} +8 -4
- package/dist/hash-Cb7U1b5M.mjs.map +1 -0
- package/dist/{hreflang-CbYeCzl9.mjs → hreflang-Bg-zMpkr.mjs} +7 -7
- package/dist/{hreflang-CbYeCzl9.mjs.map → hreflang-Bg-zMpkr.mjs.map} +1 -1
- package/dist/{hreflang-CIxH_LKz.d.mts → hreflang-CZbSx6Bg.d.mts} +2 -2
- package/dist/{hreflang-CIxH_LKz.d.mts.map → hreflang-CZbSx6Bg.d.mts.map} +1 -1
- package/dist/{import-oDYCgNYQ.mjs → import-PKhLeXvn.mjs} +2 -2
- package/dist/{import-oDYCgNYQ.mjs.map → import-PKhLeXvn.mjs.map} +1 -1
- package/dist/{index-BEui-cRd.d.mts → index-BeNto_Vs.d.mts} +3 -3
- package/dist/{index-BEui-cRd.d.mts.map → index-BeNto_Vs.d.mts.map} +1 -1
- package/dist/{index-BZJ_5FOX.d.mts → index-j9rTh3cp.d.mts} +52 -17
- package/dist/index-j9rTh3cp.d.mts.map +1 -0
- package/dist/index.d.mts +18 -18
- package/dist/index.mjs +49 -49
- package/dist/{load-Dg5gtWma.mjs → load-BwTdWE8B.mjs} +2 -2
- package/dist/{load-Dg5gtWma.mjs.map → load-BwTdWE8B.mjs.map} +1 -1
- package/dist/{loader-D4rigvjR.mjs → loader-C1XOLV5b.mjs} +4 -4
- package/dist/{loader-D4rigvjR.mjs.map → loader-C1XOLV5b.mjs.map} +1 -1
- package/dist/media/index.d.mts +1 -1
- package/dist/media/local-runtime.d.mts +12 -12
- package/dist/media/local-runtime.mjs +5 -5
- package/dist/{media-BJ6SRY3g.mjs → media-DWIzoVFL.mjs} +8 -4
- package/dist/{media-BJ6SRY3g.mjs.map → media-DWIzoVFL.mjs.map} +1 -1
- package/dist/{media-allowlist-jUppPWFy.mjs → media-allowlist-B7TPD6aq.mjs} +1 -1
- package/dist/{media-allowlist-jUppPWFy.mjs.map → media-allowlist-B7TPD6aq.mjs.map} +1 -1
- package/dist/{media-its1WLYv.mjs → media-tQDZEdu7.mjs} +60 -15
- package/dist/media-tQDZEdu7.mjs.map +1 -0
- package/dist/{media-upload-DNu3QySc.mjs → media-upload-Bc9Nc5Zf.mjs} +6 -6
- package/dist/{media-upload-DNu3QySc.mjs.map → media-upload-Bc9Nc5Zf.mjs.map} +1 -1
- package/dist/{media-usage-DPQ3K4xB.mjs → media-usage-DPJcNaAq.mjs} +6 -6
- package/dist/{media-usage-DPQ3K4xB.mjs.map → media-usage-DPJcNaAq.mjs.map} +1 -1
- package/dist/{menus-BMGzWyWH.mjs → menus-BxPLFfIc.mjs} +3 -3
- package/dist/{menus-BMGzWyWH.mjs.map → menus-BxPLFfIc.mjs.map} +1 -1
- package/dist/{menus-CPf_SW7u.mjs → menus-C6qhqP4X.mjs} +8 -8
- package/dist/{menus-CPf_SW7u.mjs.map → menus-C6qhqP4X.mjs.map} +1 -1
- package/dist/{oauth-authorization-oqfbBuMH.mjs → oauth-authorization-C9tFinly.mjs} +4 -4
- package/dist/{oauth-authorization-oqfbBuMH.mjs.map → oauth-authorization-C9tFinly.mjs.map} +1 -1
- package/dist/{oauth-clients-BlrUHAsf.mjs → oauth-clients-BwTxE1aE.mjs} +1 -1
- package/dist/{oauth-clients-BlrUHAsf.mjs.map → oauth-clients-BwTxE1aE.mjs.map} +1 -1
- package/dist/{oauth-state-store-DnvjRMy6.mjs → oauth-state-store-DaRUwPWO.mjs} +1 -1
- package/dist/{oauth-state-store-DnvjRMy6.mjs.map → oauth-state-store-DaRUwPWO.mjs.map} +1 -1
- package/dist/{oauth-user-lookup-oUllxvAv.mjs → oauth-user-lookup-CzuzOSlF.mjs} +1 -1
- package/dist/{oauth-user-lookup-oUllxvAv.mjs.map → oauth-user-lookup-CzuzOSlF.mjs.map} +1 -1
- package/dist/object-cache/memory.d.mts +1 -1
- package/dist/{options-BqMkWfYa.d.mts → options-aeIs8WFu.d.mts} +3 -3
- package/dist/{options-BqMkWfYa.d.mts.map → options-aeIs8WFu.d.mts.map} +1 -1
- package/dist/page/index.d.mts +2 -2
- package/dist/{parse-53zyxlpv.mjs → parse-BL49yb9D.mjs} +2 -2
- package/dist/{parse-53zyxlpv.mjs.map → parse-BL49yb9D.mjs.map} +1 -1
- package/dist/{passkey-config-C-SKpc0-.mjs → passkey-config-KMhgFjdQ.mjs} +1 -1
- package/dist/{passkey-config-C-SKpc0-.mjs.map → passkey-config-KMhgFjdQ.mjs.map} +1 -1
- package/dist/{placeholder-Dop47JMM.d.mts → placeholder-Dxo5Zt58.d.mts} +1 -1
- package/dist/{placeholder-Dop47JMM.d.mts.map → placeholder-Dxo5Zt58.d.mts.map} +1 -1
- package/dist/plugin-types.d.mts +1 -1
- package/dist/plugin-utils.d.mts +10 -10
- package/dist/plugins/adapt-sandbox-entry.d.mts +10 -10
- package/dist/{portable-text-sDj92Kxv.mjs → portable-text-CNahFfj6.mjs} +1 -1
- package/dist/{portable-text-sDj92Kxv.mjs.map → portable-text-CNahFfj6.mjs.map} +1 -1
- package/dist/{query-DdFGHzQF.mjs → query-1xOcEy18.mjs} +3 -3
- package/dist/{query-DdFGHzQF.mjs.map → query-1xOcEy18.mjs.map} +1 -1
- package/dist/{query-Bz9bWZfm.mjs → query-DCiXI7OZ.mjs} +36 -16
- package/dist/query-DCiXI7OZ.mjs.map +1 -0
- package/dist/{rate-limit-D3IEiUi2.mjs → rate-limit-Vkp9gQ1Y.mjs} +2 -2
- package/dist/{rate-limit-D3IEiUi2.mjs.map → rate-limit-Vkp9gQ1Y.mjs.map} +1 -1
- package/dist/{redirect-BoUac4NT.mjs → redirect-BJ6d1yHh.mjs} +3 -3
- package/dist/{redirect-BoUac4NT.mjs.map → redirect-BJ6d1yHh.mjs.map} +1 -1
- package/dist/{redirect-Botom7X6.mjs → redirect-CDgfv9vh.mjs} +1 -1
- package/dist/{redirect-Botom7X6.mjs.map → redirect-CDgfv9vh.mjs.map} +1 -1
- package/dist/{redirects-D457qwt0.mjs → redirects-BOn2DIUs.mjs} +8 -4
- package/dist/redirects-BOn2DIUs.mjs.map +1 -0
- package/dist/{redirects-ChbpqKiS.mjs → redirects-Cwkq2H-G.mjs} +3 -3
- package/dist/{redirects-ChbpqKiS.mjs.map → redirects-Cwkq2H-G.mjs.map} +1 -1
- package/dist/{registry-y21kjhfG.mjs → registry-BP1JK2xh.mjs} +6 -6
- package/dist/{registry-y21kjhfG.mjs.map → registry-BP1JK2xh.mjs.map} +1 -1
- package/dist/{relation-Du0r04E0.mjs → relation-DJ5DZh7B.mjs} +3 -3
- package/dist/{relation-Du0r04E0.mjs.map → relation-DJ5DZh7B.mjs.map} +1 -1
- package/dist/{relations-CZMpBqEd.mjs → relations-C4duJnwI.mjs} +2 -2
- package/dist/{relations-CZMpBqEd.mjs.map → relations-C4duJnwI.mjs.map} +1 -1
- package/dist/{relations-qKmSj9KM.d.mts → relations-DHFQGX0h.d.mts} +28 -17
- package/dist/relations-DHFQGX0h.d.mts.map +1 -0
- package/dist/{relations-DtX1UMBH.mjs → relations-DpTjftCl.mjs} +6 -6
- package/dist/{relations-DtX1UMBH.mjs.map → relations-DpTjftCl.mjs.map} +1 -1
- package/dist/{resolve-DFr4kq72.mjs → resolve-BUvFE0Lr.mjs} +2 -2
- package/dist/{resolve-DFr4kq72.mjs.map → resolve-BUvFE0Lr.mjs.map} +1 -1
- package/dist/{runner-BdeLEgad.mjs → runner-BsI18UgP.mjs} +230 -212
- package/dist/runner-BsI18UgP.mjs.map +1 -0
- package/dist/{runner-Dd8m8k9F.d.mts → runner-CHdkERbt.d.mts} +2 -2
- package/dist/{runner-Dd8m8k9F.d.mts.map → runner-CHdkERbt.d.mts.map} +1 -1
- package/dist/runtime.d.mts +11 -11
- package/dist/runtime.mjs +3 -3
- package/dist/{schema-BH8P8vaC.mjs → schema-eqZhkR0S.mjs} +3 -3
- package/dist/{schema-BH8P8vaC.mjs.map → schema-eqZhkR0S.mjs.map} +1 -1
- package/dist/{scopes-DTIkIaGA.mjs → scopes-Be2d8bSb.mjs} +3 -3
- package/dist/{scopes-DTIkIaGA.mjs.map → scopes-Be2d8bSb.mjs.map} +1 -1
- package/dist/{search-D-Yhpmvs.mjs → search-CeO678cp.mjs} +6 -6
- package/dist/{search-D-Yhpmvs.mjs.map → search-CeO678cp.mjs.map} +1 -1
- package/dist/{secrets-CQ-69kF9.mjs → secrets-870d-7yA.mjs} +1 -1
- package/dist/{secrets-CQ-69kF9.mjs.map → secrets-870d-7yA.mjs.map} +1 -1
- package/dist/{sections-CzVBAKjw.mjs → sections-DSuEfirn.mjs} +3 -3
- package/dist/{sections-CzVBAKjw.mjs.map → sections-DSuEfirn.mjs.map} +1 -1
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +18 -16
- package/dist/seo/index.d.mts +3 -3
- package/dist/seo/index.mjs +14 -12
- package/dist/seo/index.mjs.map +1 -1
- package/dist/{seo-kJzVAmmu.mjs → seo-BAPKiED8.mjs} +2 -2
- package/dist/{seo-kJzVAmmu.mjs.map → seo-BAPKiED8.mjs.map} +1 -1
- package/dist/{service-CQnc276a.mjs → service-DyFT-k-R.mjs} +3 -3
- package/dist/{service-CQnc276a.mjs.map → service-DyFT-k-R.mjs.map} +1 -1
- package/dist/{session-user-DbHqKDKe.mjs → session-user-BrK2zz7S.mjs} +1 -1
- package/dist/{session-user-DbHqKDKe.mjs.map → session-user-BrK2zz7S.mjs.map} +1 -1
- package/dist/{settings-Dtc0DU31.mjs → settings-CREKyj8I.mjs} +3 -3
- package/dist/{settings-Dtc0DU31.mjs.map → settings-CREKyj8I.mjs.map} +1 -1
- package/dist/{settings-C2PaBS49.mjs → settings-DgKouY2S.mjs} +3 -3
- package/dist/{settings-C2PaBS49.mjs.map → settings-DgKouY2S.mjs.map} +1 -1
- package/dist/{setup-complete-CdIlqloh.mjs → setup-complete-L7bMK2VI.mjs} +1 -1
- package/dist/{setup-complete-CdIlqloh.mjs.map → setup-complete-L7bMK2VI.mjs.map} +1 -1
- package/dist/{setup-nonce-DqiIPr-J.mjs → setup-nonce-Ct5tjWcq.mjs} +1 -1
- package/dist/{setup-nonce-DqiIPr-J.mjs.map → setup-nonce-Ct5tjWcq.mjs.map} +1 -1
- package/dist/{site-url-NGJT6NTU.mjs → site-url-8N3kSyVZ.mjs} +1 -1
- package/dist/{site-url-NGJT6NTU.mjs.map → site-url-8N3kSyVZ.mjs.map} +1 -1
- package/dist/storage/local.d.mts +1 -1
- package/dist/storage/s3.d.mts +1 -1
- package/dist/storage/s3.mjs +1 -1
- package/dist/storage/s3.mjs.map +1 -1
- package/dist/{taxonomies-r7pqLBCn.mjs → taxonomies-B61CRSha.mjs} +48 -22
- package/dist/taxonomies-B61CRSha.mjs.map +1 -0
- package/dist/{taxonomies-BN6M9Nrv.mjs → taxonomies-Dbdki3Un.mjs} +9 -8
- package/dist/taxonomies-Dbdki3Un.mjs.map +1 -0
- package/dist/{taxonomy-0mCI25HN.mjs → taxonomy-DvwWAPvA.mjs} +3 -3
- package/dist/{taxonomy-0mCI25HN.mjs.map → taxonomy-DvwWAPvA.mjs.map} +1 -1
- package/dist/{transport-CKrlqSpD.mjs → transport-BWQo0Foy.mjs} +1 -1
- package/dist/{transport-CKrlqSpD.mjs.map → transport-BWQo0Foy.mjs.map} +1 -1
- package/dist/{transport-Vd7VdqHv.d.mts → transport-DokJOScS.d.mts} +1 -1
- package/dist/{transport-Vd7VdqHv.d.mts.map → transport-DokJOScS.d.mts.map} +1 -1
- package/dist/{types-DPKhI5ib.d.mts → types-84Bque3-.d.mts} +9 -1
- package/dist/types-84Bque3-.d.mts.map +1 -0
- package/dist/{types-BvB7gDOD.d.mts → types-BQY7VYw6.d.mts} +2 -2
- package/dist/{types-BvB7gDOD.d.mts.map → types-BQY7VYw6.d.mts.map} +1 -1
- package/dist/{types-B4vm81VH.d.mts → types-C9P3ysxq.d.mts} +1 -1
- package/dist/{types-B4vm81VH.d.mts.map → types-C9P3ysxq.d.mts.map} +1 -1
- package/dist/{types-B97jg3aW.d.mts → types-CcSZCcF1.d.mts} +1 -1
- package/dist/{types-B97jg3aW.d.mts.map → types-CcSZCcF1.d.mts.map} +1 -1
- package/dist/{types-BNfPHfay.d.mts → types-DQLZ58GV.d.mts} +1 -1
- package/dist/{types-BNfPHfay.d.mts.map → types-DQLZ58GV.d.mts.map} +1 -1
- package/dist/{types-BfYUPJdP.d.mts → types-DqZVPILm.d.mts} +2 -2
- package/dist/{types-BfYUPJdP.d.mts.map → types-DqZVPILm.d.mts.map} +1 -1
- package/dist/{types-C7U0HlEr.d.mts → types-Dr2GLlwu.d.mts} +1 -1
- package/dist/{types-C7U0HlEr.d.mts.map → types-Dr2GLlwu.d.mts.map} +1 -1
- package/dist/{types-iVeZ5g6N.d.mts → types-DsAMyD_o.d.mts} +1 -1
- package/dist/{types-iVeZ5g6N.d.mts.map → types-DsAMyD_o.d.mts.map} +1 -1
- package/dist/{types-CrRUFdej.mjs → types-XrQQ-Aex.mjs} +2 -2
- package/dist/{types-CrRUFdej.mjs.map → types-XrQQ-Aex.mjs.map} +1 -1
- package/dist/{types-Kmaxdarz.d.mts → types-jISEalTX.d.mts} +1 -1
- package/dist/{types-Kmaxdarz.d.mts.map → types-jISEalTX.d.mts.map} +1 -1
- package/dist/upload-attempts-C5yd6Gae.mjs +27 -0
- package/dist/upload-attempts-C5yd6Gae.mjs.map +1 -0
- package/dist/{user-aTXqmXBz.mjs → user-BAumEmpA.mjs} +3 -3
- package/dist/{user-aTXqmXBz.mjs.map → user-BAumEmpA.mjs.map} +1 -1
- package/dist/{validate-CDT1jl6_.mjs → validate-Bs_wT2ul.mjs} +3 -3
- package/dist/{validate-CDT1jl6_.mjs.map → validate-Bs_wT2ul.mjs.map} +1 -1
- package/dist/{validate-DULfgpJP.d.mts → validate-CdYcY-Ln.d.mts} +5 -5
- package/dist/{validate-DULfgpJP.d.mts.map → validate-CdYcY-Ln.d.mts.map} +1 -1
- package/dist/{validation-DlD0E35W.mjs → validation-BngR0nit.mjs} +10 -8
- package/dist/{validation-DlD0E35W.mjs.map → validation-BngR0nit.mjs.map} +1 -1
- package/dist/version-Bdfb34kk.mjs +7 -0
- package/dist/{version-BRMngh9I.mjs.map → version-Bdfb34kk.mjs.map} +1 -1
- package/dist/{widgets-5fipNkMT.mjs → widgets-L-VndTKD.mjs} +2 -2
- package/dist/{widgets-5fipNkMT.mjs.map → widgets-L-VndTKD.mjs.map} +1 -1
- package/dist/{wxr-taxonomies-DrihupaC.mjs → wxr-taxonomies-BexjtNid.mjs} +4 -4
- package/dist/{wxr-taxonomies-DrihupaC.mjs.map → wxr-taxonomies-BexjtNid.mjs.map} +1 -1
- package/dist/{wxr-taxonomies-B4tMuN5T.d.mts → wxr-taxonomies-DueaLFd9.d.mts} +1 -1
- package/dist/{wxr-taxonomies-B4tMuN5T.d.mts.map → wxr-taxonomies-DueaLFd9.d.mts.map} +1 -1
- package/dist/{zod-generator-CwkHWz4f.mjs → zod-generator-DMzfga3g.mjs} +3 -3
- package/dist/{zod-generator-CwkHWz4f.mjs.map → zod-generator-DMzfga3g.mjs.map} +1 -1
- package/package.json +6 -5
- package/src/api/handlers/media.ts +4 -4
- package/src/api/handlers/taxonomies.ts +10 -9
- package/src/api/openapi/document.ts +36 -3
- package/src/api/schemas/media.ts +9 -2
- package/src/api/schemas/taxonomies.ts +16 -1
- package/src/astro/integration/routes.ts +5 -0
- package/src/astro/routes/api/content/[collection]/[id].ts +3 -1
- package/src/astro/routes/api/media/[id]/confirm.ts +148 -59
- package/src/astro/routes/api/media/[id]/upload.ts +262 -0
- package/src/astro/routes/api/media/[id].ts +19 -10
- package/src/astro/routes/api/media/upload-url.ts +28 -31
- package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +6 -3
- package/src/astro/types.ts +1 -0
- package/src/cleanup.ts +24 -1
- package/src/components/InlinePortableTextEditor.tsx +4 -0
- package/src/components/code-mark.ts +11 -0
- package/src/database/dialect-helpers.ts +38 -0
- package/src/database/migrations/054_media_upload_attempts.ts +41 -0
- package/src/database/migrations/runner.ts +2 -0
- package/src/database/repositories/content.ts +2 -5
- package/src/database/repositories/media.ts +158 -28
- package/src/database/types.ts +10 -0
- package/src/emdash-runtime.ts +13 -0
- package/src/i18n/config.ts +17 -10
- package/src/index.ts +1 -0
- package/src/media/upload-attempts.ts +33 -0
- package/src/query.ts +29 -2
- package/src/storage/s3.ts +1 -1
- package/src/taxonomies/term-counts.ts +54 -24
- package/src/utils/hash.ts +14 -3
- package/dist/api-BkqFzIu4.mjs.map +0 -1
- package/dist/config-B6b_BobR.mjs.map +0 -1
- package/dist/content-BC43pICM.mjs.map +0 -1
- package/dist/dialect-helpers-BtOwo2us.mjs.map +0 -1
- package/dist/hash-DFFrkivP.mjs.map +0 -1
- package/dist/index-BZJ_5FOX.d.mts.map +0 -1
- package/dist/media-its1WLYv.mjs.map +0 -1
- package/dist/query-Bz9bWZfm.mjs.map +0 -1
- package/dist/redirects-D457qwt0.mjs.map +0 -1
- package/dist/relations-qKmSj9KM.d.mts.map +0 -1
- package/dist/runner-BdeLEgad.mjs.map +0 -1
- package/dist/taxonomies-BN6M9Nrv.mjs.map +0 -1
- package/dist/taxonomies-r7pqLBCn.mjs.map +0 -1
- package/dist/types-DPKhI5ib.d.mts.map +0 -1
- package/dist/version-BRMngh9I.mjs +0 -7
- /package/dist/{api-tokens-Cvmixds7.mjs → api-tokens-CEsW_jCg.mjs} +0 -0
|
@@ -8,14 +8,16 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { APIRoute } from "astro";
|
|
11
|
-
import {
|
|
11
|
+
import type { DownloadResult } from "emdash";
|
|
12
12
|
|
|
13
13
|
import { requireOwnerPerm, requirePerm } from "#api/authorize.js";
|
|
14
14
|
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
15
15
|
import { isParseError, parseOptionalBody } from "#api/parse.js";
|
|
16
16
|
import { mediaConfirmBody } from "#api/schemas.js";
|
|
17
|
+
import { MediaRepository } from "#db/repositories/media.js";
|
|
17
18
|
import { enrichImageMetadata } from "#media/enrich.js";
|
|
18
19
|
import type { MediaItem } from "#types";
|
|
20
|
+
import { computeContentHash, MAX_CONTENT_HASH_BYTES } from "#utils/hash.js";
|
|
19
21
|
|
|
20
22
|
export const prerender = false;
|
|
21
23
|
|
|
@@ -26,7 +28,7 @@ export const prerender = false;
|
|
|
26
28
|
* on the very uploads that flow was designed for. LQIP is progressive
|
|
27
29
|
* enhancement: large images simply ship without a server-generated placeholder.
|
|
28
30
|
*/
|
|
29
|
-
const MAX_PLACEHOLDER_DOWNLOAD_BYTES =
|
|
31
|
+
const MAX_PLACEHOLDER_DOWNLOAD_BYTES = MAX_CONTENT_HASH_BYTES;
|
|
30
32
|
|
|
31
33
|
/**
|
|
32
34
|
* Add URL to media item (relative URL for portability)
|
|
@@ -38,6 +40,67 @@ function addUrlToMedia(item: MediaItem): MediaItem & { url: string } {
|
|
|
38
40
|
};
|
|
39
41
|
}
|
|
40
42
|
|
|
43
|
+
async function cancelDownload(download: DownloadResult): Promise<void> {
|
|
44
|
+
try {
|
|
45
|
+
await download.body.cancel();
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error("[media] confirm download cancellation failed:", error);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function forgetUploadAttempt(repo: MediaRepository, storageKey: string): Promise<void> {
|
|
52
|
+
try {
|
|
53
|
+
await repo.deleteUploadAttempt(storageKey);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error("[media] confirm upload attempt cleanup failed:", error);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function confirmationConflict(repo: MediaRepository, id: string): Promise<Response> {
|
|
60
|
+
const current = await repo.findById(id);
|
|
61
|
+
if (!current) {
|
|
62
|
+
return apiError("NOT_FOUND", `Media item not found: ${id}`, 404);
|
|
63
|
+
}
|
|
64
|
+
if (current.status === "ready") {
|
|
65
|
+
await forgetUploadAttempt(repo, current.storageKey);
|
|
66
|
+
return apiSuccess({ item: addUrlToMedia(current) });
|
|
67
|
+
}
|
|
68
|
+
if (current.status === "pending") {
|
|
69
|
+
return apiError("INVALID_STATE", "Media item changed during confirmation", 409);
|
|
70
|
+
}
|
|
71
|
+
return apiError("INVALID_STATE", `Media item is not pending: ${current.status}`, 400);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function consumeDownload(download: DownloadResult): Promise<Uint8Array> {
|
|
75
|
+
const reader = download.body.getReader();
|
|
76
|
+
try {
|
|
77
|
+
const bytes = new Uint8Array(download.size);
|
|
78
|
+
let receivedSize = 0;
|
|
79
|
+
while (true) {
|
|
80
|
+
const { done, value } = await reader.read();
|
|
81
|
+
if (done) break;
|
|
82
|
+
if (receivedSize + value.byteLength > bytes.byteLength) {
|
|
83
|
+
throw new Error("Stored file exceeds its reported size");
|
|
84
|
+
}
|
|
85
|
+
bytes.set(value, receivedSize);
|
|
86
|
+
receivedSize += value.byteLength;
|
|
87
|
+
}
|
|
88
|
+
if (receivedSize !== download.size) {
|
|
89
|
+
throw new Error("Stored file size does not match its reported size");
|
|
90
|
+
}
|
|
91
|
+
return bytes;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
try {
|
|
94
|
+
await reader.cancel(error);
|
|
95
|
+
} catch (cancelError) {
|
|
96
|
+
console.error("[media] confirm download cancellation failed:", cancelError);
|
|
97
|
+
}
|
|
98
|
+
throw error;
|
|
99
|
+
} finally {
|
|
100
|
+
reader.releaseLock();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
41
104
|
/**
|
|
42
105
|
* Confirm upload completion
|
|
43
106
|
*/
|
|
@@ -68,90 +131,116 @@ export const POST: APIRoute = async ({ params, request, locals }) => {
|
|
|
68
131
|
return apiError("NOT_FOUND", `Media item not found: ${id}`, 404);
|
|
69
132
|
}
|
|
70
133
|
|
|
71
|
-
if (existing.status !== "pending") {
|
|
72
|
-
return apiError("INVALID_STATE", `Media item is not pending: ${existing.status}`, 400);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
134
|
// Only the uploader or a user with media:edit_any can confirm/fail a pending upload
|
|
76
135
|
const ownerDenied = requireOwnerPerm(
|
|
77
136
|
user,
|
|
78
137
|
existing.authorId ?? "",
|
|
79
|
-
"media:
|
|
138
|
+
"media:upload",
|
|
80
139
|
"media:edit_any",
|
|
81
140
|
);
|
|
82
141
|
if (ownerDenied) return ownerDenied;
|
|
83
142
|
|
|
84
|
-
|
|
143
|
+
if (existing.status === "ready") {
|
|
144
|
+
await forgetUploadAttempt(repo, existing.storageKey);
|
|
145
|
+
return apiSuccess({ item: addUrlToMedia(existing) });
|
|
146
|
+
}
|
|
147
|
+
if (existing.status !== "pending") {
|
|
148
|
+
return apiError("INVALID_STATE", `Media item is not pending: ${existing.status}`, 400);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (body.size !== undefined && existing.size !== null && body.size !== existing.size) {
|
|
152
|
+
return apiError(
|
|
153
|
+
"UPLOAD_SIZE_MISMATCH",
|
|
154
|
+
"Confirmed size does not match the pending media item",
|
|
155
|
+
400,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
let confirmedSize = existing.size ?? body.size;
|
|
160
|
+
let contentHash = existing.contentHash;
|
|
161
|
+
let imageBytes: Uint8Array | undefined;
|
|
162
|
+
|
|
85
163
|
if (emdash.storage) {
|
|
86
164
|
const exists = await emdash.storage.exists(existing.storageKey);
|
|
87
165
|
if (!exists) {
|
|
88
|
-
|
|
89
|
-
await repo
|
|
166
|
+
const failed = await repo.markFailed(id, existing.storageKey);
|
|
167
|
+
if (!failed) return await confirmationConflict(repo, id);
|
|
90
168
|
return apiError("FILE_NOT_FOUND", "File was not uploaded to storage", 400);
|
|
91
169
|
}
|
|
170
|
+
|
|
171
|
+
const storedFile = await emdash.storage.download(existing.storageKey);
|
|
172
|
+
if (confirmedSize !== undefined && storedFile.size !== confirmedSize) {
|
|
173
|
+
await cancelDownload(storedFile);
|
|
174
|
+
return apiError(
|
|
175
|
+
"UPLOAD_SIZE_MISMATCH",
|
|
176
|
+
"Stored file size does not match the pending media item",
|
|
177
|
+
400,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
confirmedSize = storedFile.size;
|
|
181
|
+
|
|
182
|
+
const isImage = existing.mimeType.startsWith("image/");
|
|
183
|
+
const canBuffer = storedFile.size <= MAX_PLACEHOLDER_DOWNLOAD_BYTES;
|
|
184
|
+
const hasServerHash =
|
|
185
|
+
contentHash !== null && (await repo.hasUploadAttempt(existing.storageKey));
|
|
186
|
+
if (canBuffer && (isImage || !hasServerHash)) {
|
|
187
|
+
const bytes = await consumeDownload(storedFile);
|
|
188
|
+
contentHash = bytes.byteLength > 0 ? await computeContentHash(bytes) : null;
|
|
189
|
+
if (isImage && bytes.byteLength > 0) imageBytes = bytes;
|
|
190
|
+
} else {
|
|
191
|
+
if (!hasServerHash) contentHash = null;
|
|
192
|
+
await cancelDownload(storedFile);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (isImage && !canBuffer) {
|
|
196
|
+
console.warn(
|
|
197
|
+
`[media] confirm skipping placeholder: object ${existing.storageKey} reported size ${storedFile.size} bytes (> ${MAX_PLACEHOLDER_DOWNLOAD_BYTES})`,
|
|
198
|
+
);
|
|
199
|
+
}
|
|
92
200
|
}
|
|
93
201
|
|
|
94
|
-
//
|
|
95
|
-
// generate LQIP placeholders (and server-side dimensions as a fallback).
|
|
96
|
-
// The signed-URL flow uploads directly to storage, so this confirm is the
|
|
97
|
-
// only point at which the server sees the bytes. Best-effort: a decode
|
|
98
|
-
// failure must not block the upload from being marked ready. We also cap
|
|
99
|
-
// the download size — buffering a large original into a Worker heap to
|
|
100
|
-
// compute a 32px blurhash would OOM on the uploads the signed-URL path
|
|
101
|
-
// exists to support, so oversized files skip the server-side placeholder.
|
|
202
|
+
// LQIP is best-effort; oversized images skip server-side placeholders.
|
|
102
203
|
let blurhash: string | undefined;
|
|
103
204
|
let dominantColor: string | undefined;
|
|
104
205
|
let width = body.width;
|
|
105
206
|
let height = body.height;
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
} else {
|
|
121
|
-
const enriched = await enrichImageMetadata(bytes, existing.mimeType, {
|
|
122
|
-
knownDimensions:
|
|
123
|
-
body.width != null && body.height != null
|
|
124
|
-
? { width: body.width, height: body.height }
|
|
125
|
-
: undefined,
|
|
126
|
-
});
|
|
127
|
-
blurhash = enriched.blurhash;
|
|
128
|
-
dominantColor = enriched.dominantColor;
|
|
129
|
-
width = width ?? enriched.width;
|
|
130
|
-
height = height ?? enriched.height;
|
|
131
|
-
}
|
|
132
|
-
} catch (error) {
|
|
133
|
-
console.error("[media] confirm placeholder generation failed:", error);
|
|
134
|
-
}
|
|
135
|
-
} else {
|
|
136
|
-
console.warn(
|
|
137
|
-
`[media] confirm skipping placeholder: object ${existing.storageKey} reported size ${knownSize} bytes (> ${MAX_PLACEHOLDER_DOWNLOAD_BYTES})`,
|
|
138
|
-
);
|
|
207
|
+
if (imageBytes) {
|
|
208
|
+
try {
|
|
209
|
+
const enriched = await enrichImageMetadata(imageBytes, existing.mimeType, {
|
|
210
|
+
knownDimensions:
|
|
211
|
+
body.width != null && body.height != null
|
|
212
|
+
? { width: body.width, height: body.height }
|
|
213
|
+
: undefined,
|
|
214
|
+
});
|
|
215
|
+
blurhash = enriched.blurhash;
|
|
216
|
+
dominantColor = enriched.dominantColor;
|
|
217
|
+
width = width ?? enriched.width;
|
|
218
|
+
height = height ?? enriched.height;
|
|
219
|
+
} catch (error) {
|
|
220
|
+
console.error("[media] confirm placeholder generation failed:", error);
|
|
139
221
|
}
|
|
140
222
|
}
|
|
141
223
|
|
|
142
224
|
// Confirm the upload
|
|
143
|
-
const item = await repo.confirmUpload(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
225
|
+
const item = await repo.confirmUpload(
|
|
226
|
+
id,
|
|
227
|
+
{
|
|
228
|
+
size: confirmedSize,
|
|
229
|
+
width,
|
|
230
|
+
height,
|
|
231
|
+
blurhash,
|
|
232
|
+
dominantColor,
|
|
233
|
+
contentHash,
|
|
234
|
+
},
|
|
235
|
+
existing.storageKey,
|
|
236
|
+
);
|
|
150
237
|
|
|
151
238
|
if (!item) {
|
|
152
|
-
return
|
|
239
|
+
return await confirmationConflict(repo, id);
|
|
153
240
|
}
|
|
154
241
|
|
|
242
|
+
await forgetUploadAttempt(repo, item.storageKey);
|
|
243
|
+
|
|
155
244
|
// Add URL to the response (relative URL for portability)
|
|
156
245
|
const itemWithUrl = addUrlToMedia(item);
|
|
157
246
|
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import type { APIRoute } from "astro";
|
|
2
|
+
import type { Storage } from "emdash";
|
|
3
|
+
import { ulid } from "ulidx";
|
|
4
|
+
|
|
5
|
+
import { requireOwnerPerm, requirePerm } from "#api/authorize.js";
|
|
6
|
+
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
7
|
+
import { MediaRepository } from "#db/repositories/media.js";
|
|
8
|
+
import { normalizeMime } from "#media/mime.js";
|
|
9
|
+
import { removeUploadAttempt } from "#media/upload-attempts.js";
|
|
10
|
+
import { computeContentHash, MAX_CONTENT_HASH_BYTES } from "#utils/hash.js";
|
|
11
|
+
|
|
12
|
+
export const prerender = false;
|
|
13
|
+
|
|
14
|
+
const INITIAL_HASH_BUFFER_BYTES = 64 * 1024;
|
|
15
|
+
|
|
16
|
+
type FixedLengthStreamConstructor = new (
|
|
17
|
+
expectedLength: number | bigint,
|
|
18
|
+
) => TransformStream<ArrayBuffer | ArrayBufferView, Uint8Array>;
|
|
19
|
+
|
|
20
|
+
declare const FixedLengthStream: FixedLengthStreamConstructor | undefined;
|
|
21
|
+
|
|
22
|
+
class UploadBodyError extends Error {
|
|
23
|
+
readonly code: "PAYLOAD_TOO_LARGE" | "UPLOAD_SIZE_MISMATCH";
|
|
24
|
+
|
|
25
|
+
constructor(code: "PAYLOAD_TOO_LARGE" | "UPLOAD_SIZE_MISMATCH") {
|
|
26
|
+
super(code);
|
|
27
|
+
this.code = code;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function findUploadBodyError(error: unknown): UploadBodyError | null {
|
|
32
|
+
let current = error;
|
|
33
|
+
for (let depth = 0; depth < 5 && current instanceof Error; depth++) {
|
|
34
|
+
if (current instanceof UploadBodyError) return current;
|
|
35
|
+
current = current.cause;
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function preserveKnownLength(
|
|
41
|
+
body: ReadableStream<Uint8Array>,
|
|
42
|
+
expectedLength: number,
|
|
43
|
+
): ReadableStream<Uint8Array> {
|
|
44
|
+
if (typeof FixedLengthStream === "undefined") return body;
|
|
45
|
+
return body.pipeThrough(new FixedLengthStream(expectedLength));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function createUploadAttemptKey(key: string): string {
|
|
49
|
+
const pathSeparator = key.lastIndexOf("/");
|
|
50
|
+
const extensionSeparator = key.lastIndexOf(".");
|
|
51
|
+
if (extensionSeparator > pathSeparator) {
|
|
52
|
+
return `${key.slice(0, extensionSeparator)}.${ulid()}${key.slice(extensionSeparator)}`;
|
|
53
|
+
}
|
|
54
|
+
return `${key}.${ulid()}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function getStoredSize(storage: Storage, key: string): Promise<number | null> {
|
|
58
|
+
if (!(await storage.exists(key))) return null;
|
|
59
|
+
const download = await storage.download(key);
|
|
60
|
+
try {
|
|
61
|
+
return download.size;
|
|
62
|
+
} finally {
|
|
63
|
+
try {
|
|
64
|
+
await download.body.cancel();
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error("[media] upload download cancellation failed:", error);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const PUT: APIRoute = async ({ params, request, locals }) => {
|
|
72
|
+
const { emdash, user } = locals;
|
|
73
|
+
const { id } = params;
|
|
74
|
+
|
|
75
|
+
const denied = requirePerm(user, "media:upload");
|
|
76
|
+
if (denied) return denied;
|
|
77
|
+
|
|
78
|
+
if (!id) {
|
|
79
|
+
return apiError("INVALID_REQUEST", "Media ID is required", 400);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!emdash?.db) {
|
|
83
|
+
return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!emdash.storage) {
|
|
87
|
+
return apiError("NO_STORAGE", "Storage not configured", 500);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const repo = new MediaRepository(emdash.db);
|
|
92
|
+
const media = await repo.findById(id);
|
|
93
|
+
if (!media) {
|
|
94
|
+
return apiError("NOT_FOUND", `Media item not found: ${id}`, 404);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (media.status !== "pending") {
|
|
98
|
+
return apiError("INVALID_STATE", `Media item is not pending: ${media.status}`, 400);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const ownerDenied = requireOwnerPerm(
|
|
102
|
+
user,
|
|
103
|
+
media.authorId ?? "",
|
|
104
|
+
"media:upload",
|
|
105
|
+
"media:edit_any",
|
|
106
|
+
);
|
|
107
|
+
if (ownerDenied) return ownerDenied;
|
|
108
|
+
|
|
109
|
+
if (!Number.isSafeInteger(media.size) || media.size === null || media.size < 0) {
|
|
110
|
+
return apiError("INVALID_STATE", "Pending media item has no valid upload size", 400);
|
|
111
|
+
}
|
|
112
|
+
const expectedSize = media.size;
|
|
113
|
+
|
|
114
|
+
const contentType = request.headers.get("Content-Type");
|
|
115
|
+
if (!contentType || normalizeMime(contentType) !== media.mimeType) {
|
|
116
|
+
return apiError("INVALID_TYPE", "Upload content type does not match the media item", 400);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const requestBody =
|
|
120
|
+
request.body ??
|
|
121
|
+
(expectedSize === 0
|
|
122
|
+
? new ReadableStream<Uint8Array>({
|
|
123
|
+
start(controller) {
|
|
124
|
+
controller.close();
|
|
125
|
+
},
|
|
126
|
+
})
|
|
127
|
+
: null);
|
|
128
|
+
if (!requestBody) {
|
|
129
|
+
return apiError("NO_FILE", "No file provided", 400);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const contentLength = request.headers.get("Content-Length");
|
|
133
|
+
if (contentLength !== null) {
|
|
134
|
+
const declaredSize = Number(contentLength);
|
|
135
|
+
if (!Number.isSafeInteger(declaredSize) || declaredSize < 0) {
|
|
136
|
+
return apiError("INVALID_REQUEST", "Invalid Content-Length header", 400);
|
|
137
|
+
}
|
|
138
|
+
if (declaredSize > expectedSize) {
|
|
139
|
+
return apiError("PAYLOAD_TOO_LARGE", "Upload exceeds the expected size", 413);
|
|
140
|
+
}
|
|
141
|
+
if (declaredSize !== expectedSize) {
|
|
142
|
+
return apiError("UPLOAD_SIZE_MISMATCH", "Upload size does not match the media item", 400);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
let receivedSize = 0;
|
|
147
|
+
const shouldHash = expectedSize > 0 && expectedSize <= MAX_CONTENT_HASH_BYTES;
|
|
148
|
+
let hashBytes: Uint8Array | null = null;
|
|
149
|
+
const checkedBody = requestBody.pipeThrough(
|
|
150
|
+
new TransformStream<Uint8Array, Uint8Array>({
|
|
151
|
+
transform(chunk, controller) {
|
|
152
|
+
const offset = receivedSize;
|
|
153
|
+
receivedSize += chunk.byteLength;
|
|
154
|
+
if (receivedSize > expectedSize) {
|
|
155
|
+
controller.error(new UploadBodyError("PAYLOAD_TOO_LARGE"));
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (shouldHash && chunk.byteLength > 0) {
|
|
159
|
+
if (!hashBytes) {
|
|
160
|
+
hashBytes = new Uint8Array(
|
|
161
|
+
Math.min(expectedSize, Math.max(INITIAL_HASH_BUFFER_BYTES, receivedSize)),
|
|
162
|
+
);
|
|
163
|
+
} else if (receivedSize > hashBytes.byteLength) {
|
|
164
|
+
const grown = new Uint8Array(
|
|
165
|
+
Math.min(expectedSize, Math.max(receivedSize, hashBytes.byteLength * 2)),
|
|
166
|
+
);
|
|
167
|
+
grown.set(hashBytes);
|
|
168
|
+
hashBytes = grown;
|
|
169
|
+
}
|
|
170
|
+
hashBytes.set(chunk, offset);
|
|
171
|
+
}
|
|
172
|
+
controller.enqueue(chunk);
|
|
173
|
+
},
|
|
174
|
+
flush(controller) {
|
|
175
|
+
if (receivedSize !== expectedSize) {
|
|
176
|
+
controller.error(new UploadBodyError("UPLOAD_SIZE_MISMATCH"));
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
}),
|
|
180
|
+
);
|
|
181
|
+
const body = preserveKnownLength(checkedBody, expectedSize);
|
|
182
|
+
|
|
183
|
+
const attemptKey = createUploadAttemptKey(media.storageKey);
|
|
184
|
+
await repo.createUploadAttempt(id, attemptKey);
|
|
185
|
+
|
|
186
|
+
let attemptSize: number;
|
|
187
|
+
try {
|
|
188
|
+
const result = await emdash.storage.upload({
|
|
189
|
+
key: attemptKey,
|
|
190
|
+
body,
|
|
191
|
+
contentType: media.mimeType,
|
|
192
|
+
});
|
|
193
|
+
attemptSize = result.size;
|
|
194
|
+
} catch (error) {
|
|
195
|
+
await removeUploadAttempt(emdash.storage, repo, attemptKey);
|
|
196
|
+
const bodyError = findUploadBodyError(error);
|
|
197
|
+
if (bodyError?.code === "PAYLOAD_TOO_LARGE") {
|
|
198
|
+
return apiError("PAYLOAD_TOO_LARGE", "Upload exceeds the expected size", 413);
|
|
199
|
+
}
|
|
200
|
+
if (bodyError?.code === "UPLOAD_SIZE_MISMATCH") {
|
|
201
|
+
return apiError("UPLOAD_SIZE_MISMATCH", "Upload size does not match the media item", 400);
|
|
202
|
+
}
|
|
203
|
+
return handleError(error, "Upload failed", "UPLOAD_ERROR");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (receivedSize !== expectedSize || attemptSize !== expectedSize) {
|
|
207
|
+
await removeUploadAttempt(emdash.storage, repo, attemptKey);
|
|
208
|
+
return apiError("UPLOAD_SIZE_MISMATCH", "Upload size does not match the media item", 400);
|
|
209
|
+
}
|
|
210
|
+
const contentHash = hashBytes ? await computeContentHash(hashBytes) : undefined;
|
|
211
|
+
|
|
212
|
+
let published: boolean;
|
|
213
|
+
try {
|
|
214
|
+
published = await repo.publishPendingStorageKey(
|
|
215
|
+
id,
|
|
216
|
+
media.storageKey,
|
|
217
|
+
attemptKey,
|
|
218
|
+
contentHash,
|
|
219
|
+
);
|
|
220
|
+
} catch (error) {
|
|
221
|
+
try {
|
|
222
|
+
const current = await repo.findById(id);
|
|
223
|
+
if (
|
|
224
|
+
current?.storageKey === attemptKey &&
|
|
225
|
+
(current.status === "pending" || current.status === "ready") &&
|
|
226
|
+
current.size === expectedSize &&
|
|
227
|
+
(await getStoredSize(emdash.storage, attemptKey)) === expectedSize
|
|
228
|
+
) {
|
|
229
|
+
return apiSuccess({ uploaded: true, size: expectedSize });
|
|
230
|
+
}
|
|
231
|
+
} catch (verificationError) {
|
|
232
|
+
console.error("[media] upload publication verification failed:", verificationError);
|
|
233
|
+
}
|
|
234
|
+
return handleError(error, "Upload failed", "UPLOAD_ERROR");
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (!published) {
|
|
238
|
+
const current = await repo.findById(id);
|
|
239
|
+
if (
|
|
240
|
+
current &&
|
|
241
|
+
(current.status === "pending" || current.status === "ready") &&
|
|
242
|
+
current.size === expectedSize &&
|
|
243
|
+
(current.storageKey === attemptKey ||
|
|
244
|
+
expectedSize === 0 ||
|
|
245
|
+
(contentHash !== undefined && current.contentHash === contentHash)) &&
|
|
246
|
+
(await getStoredSize(emdash.storage, current.storageKey)) === expectedSize
|
|
247
|
+
) {
|
|
248
|
+
if (current.storageKey !== attemptKey) {
|
|
249
|
+
await removeUploadAttempt(emdash.storage, repo, attemptKey);
|
|
250
|
+
}
|
|
251
|
+
return apiSuccess({ uploaded: true, size: expectedSize });
|
|
252
|
+
}
|
|
253
|
+
await removeUploadAttempt(emdash.storage, repo, attemptKey);
|
|
254
|
+
return apiError("INVALID_STATE", "Media item is no longer pending", 400);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
await removeUploadAttempt(emdash.storage, repo, media.storageKey);
|
|
258
|
+
return apiSuccess({ uploaded: true, size: receivedSize });
|
|
259
|
+
} catch (error) {
|
|
260
|
+
return handleError(error, "Upload failed", "UPLOAD_ERROR");
|
|
261
|
+
}
|
|
262
|
+
};
|
|
@@ -13,6 +13,8 @@ import { apiError, apiSuccess, handleError, unwrapResult } from "#api/error.js";
|
|
|
13
13
|
import { handleMediaUsageSummaries } from "#api/handlers/media-usage.js";
|
|
14
14
|
import { isParseError, parseBody, parseQuery } from "#api/parse.js";
|
|
15
15
|
import { mediaGetQuery, mediaUpdateBody } from "#api/schemas.js";
|
|
16
|
+
import { MediaRepository } from "#db/repositories/media.js";
|
|
17
|
+
import { removeUploadAttempt } from "#media/upload-attempts.js";
|
|
16
18
|
|
|
17
19
|
export const prerender = false;
|
|
18
20
|
|
|
@@ -137,20 +139,27 @@ export const DELETE: APIRoute = async ({ params, locals }) => {
|
|
|
137
139
|
);
|
|
138
140
|
if (ownerDenied) return ownerDenied;
|
|
139
141
|
|
|
140
|
-
// Delete file from storage via the storage adapter
|
|
141
|
-
if (emdash.storage) {
|
|
142
|
-
try {
|
|
143
|
-
await emdash.storage.delete(media.storageKey);
|
|
144
|
-
} catch {
|
|
145
|
-
// Best-effort — continue with database deletion
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
142
|
// Delete from database — site-settings cache invalidation happens
|
|
150
143
|
// in `EmDashRuntime.handleMediaDelete` so MCP/plugin paths inherit it.
|
|
151
144
|
const result = await emdash.handleMediaDelete(id);
|
|
145
|
+
if (!result.success) return unwrapResult(result);
|
|
146
|
+
if (
|
|
147
|
+
typeof result.data !== "object" ||
|
|
148
|
+
result.data === null ||
|
|
149
|
+
!("storageKey" in result.data) ||
|
|
150
|
+
typeof result.data.storageKey !== "string"
|
|
151
|
+
) {
|
|
152
|
+
return apiError("MEDIA_DELETE_ERROR", "Failed to delete media", 500);
|
|
153
|
+
}
|
|
152
154
|
|
|
153
|
-
|
|
155
|
+
if (emdash.storage) {
|
|
156
|
+
const repo = new MediaRepository(emdash.db);
|
|
157
|
+
await removeUploadAttempt(emdash.storage, repo, result.data.storageKey, {
|
|
158
|
+
allowUntracked: true,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return apiSuccess({ deleted: true });
|
|
154
163
|
} catch (error) {
|
|
155
164
|
return handleError(error, "Failed to delete media", "MEDIA_DELETE_ERROR");
|
|
156
165
|
}
|
|
@@ -39,6 +39,10 @@ interface ExistingMediaResponse {
|
|
|
39
39
|
url: string;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
function isUnsupportedSignedUpload(error: unknown): boolean {
|
|
43
|
+
return error instanceof Error && "code" in error && error.code === "NOT_SUPPORTED";
|
|
44
|
+
}
|
|
45
|
+
|
|
42
46
|
/**
|
|
43
47
|
* Get a signed upload URL for direct-to-storage upload
|
|
44
48
|
*/
|
|
@@ -71,6 +75,7 @@ export const POST: APIRoute = async ({ request, locals }) => {
|
|
|
71
75
|
}
|
|
72
76
|
const body = await parseBody(request, mediaUploadUrlBody(maxSize));
|
|
73
77
|
if (isParseError(body)) return body;
|
|
78
|
+
const normalizedContentType = normalizeMime(body.contentType);
|
|
74
79
|
|
|
75
80
|
// Validate content type (field-aware widening)
|
|
76
81
|
const fieldAllowlist = body.fieldId
|
|
@@ -85,9 +90,9 @@ export const POST: APIRoute = async ({ request, locals }) => {
|
|
|
85
90
|
const repo = new MediaRepository(emdash.db);
|
|
86
91
|
|
|
87
92
|
// Check for existing content with same hash (deduplication)
|
|
88
|
-
if (body.contentHash) {
|
|
93
|
+
if (body.contentHash && body.size > 0) {
|
|
89
94
|
const existing = await repo.findByContentHash(body.contentHash);
|
|
90
|
-
if (existing) {
|
|
95
|
+
if (existing && existing.mimeType === normalizedContentType && existing.size === body.size) {
|
|
91
96
|
const response: ExistingMediaResponse = {
|
|
92
97
|
existing: true,
|
|
93
98
|
mediaId: existing.id,
|
|
@@ -103,49 +108,41 @@ export const POST: APIRoute = async ({ request, locals }) => {
|
|
|
103
108
|
const ext = path.extname(body.filename) || "";
|
|
104
109
|
const storageKey = `${id}${ext}`;
|
|
105
110
|
|
|
106
|
-
|
|
111
|
+
let signedUrl: Awaited<ReturnType<typeof emdash.storage.getSignedUploadUrl>> | null;
|
|
112
|
+
try {
|
|
113
|
+
signedUrl = await emdash.storage.getSignedUploadUrl({
|
|
114
|
+
key: storageKey,
|
|
115
|
+
contentType: body.contentType,
|
|
116
|
+
size: body.size,
|
|
117
|
+
expiresIn: 3600,
|
|
118
|
+
});
|
|
119
|
+
} catch (error) {
|
|
120
|
+
if (!isUnsupportedSignedUpload(error)) throw error;
|
|
121
|
+
signedUrl = null;
|
|
122
|
+
}
|
|
123
|
+
|
|
107
124
|
const mediaItem = await repo.createPending({
|
|
108
125
|
filename: body.filename,
|
|
109
|
-
mimeType:
|
|
126
|
+
mimeType: normalizedContentType,
|
|
110
127
|
size: body.size,
|
|
111
128
|
storageKey,
|
|
112
|
-
contentHash: body.contentHash,
|
|
113
129
|
authorId: user?.id,
|
|
114
130
|
});
|
|
115
131
|
|
|
116
|
-
// Get signed upload URL from storage
|
|
117
|
-
const signedUrl = await emdash.storage.getSignedUploadUrl({
|
|
118
|
-
key: storageKey,
|
|
119
|
-
contentType: body.contentType,
|
|
120
|
-
size: body.size,
|
|
121
|
-
expiresIn: 3600, // 1 hour
|
|
122
|
-
});
|
|
123
|
-
|
|
124
132
|
const response: UploadUrlResponse = {
|
|
125
|
-
uploadUrl: signedUrl.
|
|
126
|
-
method: signedUrl
|
|
127
|
-
headers: signedUrl
|
|
133
|
+
uploadUrl: signedUrl?.url ?? `/_emdash/api/media/${mediaItem.id}/upload`,
|
|
134
|
+
method: signedUrl?.method ?? "PUT",
|
|
135
|
+
headers: signedUrl?.headers ?? {
|
|
136
|
+
"Content-Type": normalizedContentType,
|
|
137
|
+
"X-EmDash-Request": "1",
|
|
138
|
+
},
|
|
128
139
|
mediaId: mediaItem.id,
|
|
129
140
|
storageKey,
|
|
130
|
-
expiresAt: signedUrl.
|
|
141
|
+
expiresAt: signedUrl?.expiresAt ?? new Date(Date.now() + 3600 * 1000).toISOString(),
|
|
131
142
|
};
|
|
132
143
|
|
|
133
144
|
return apiSuccess(response);
|
|
134
145
|
} catch (error) {
|
|
135
|
-
// Check if storage doesn't support signed URLs (e.g., local storage)
|
|
136
|
-
if (
|
|
137
|
-
error instanceof Error &&
|
|
138
|
-
"code" in error &&
|
|
139
|
-
// eslint-disable-next-line typescript/no-unsafe-type-assertion -- narrowing error to check custom code property after "code" in error guard
|
|
140
|
-
(error as { code: string }).code === "NOT_SUPPORTED"
|
|
141
|
-
) {
|
|
142
|
-
return apiError(
|
|
143
|
-
"NOT_SUPPORTED",
|
|
144
|
-
"Storage does not support signed upload URLs. Use direct upload.",
|
|
145
|
-
501,
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
146
|
return handleError(error, "Failed to generate upload URL", "UPLOAD_URL_ERROR");
|
|
150
147
|
}
|
|
151
148
|
};
|