emdash 0.0.0-a → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -1
- package/dist/adapters-BLMa4JGD.d.mts +106 -0
- package/dist/adapters-BLMa4JGD.d.mts.map +1 -0
- package/dist/apply-Bjfq_b4-.mjs +1293 -0
- package/dist/apply-Bjfq_b4-.mjs.map +1 -0
- package/dist/astro/index.d.mts +51 -0
- package/dist/astro/index.d.mts.map +1 -0
- package/dist/astro/index.mjs +1333 -0
- package/dist/astro/index.mjs.map +1 -0
- package/dist/astro/middleware/auth.d.mts +31 -0
- package/dist/astro/middleware/auth.d.mts.map +1 -0
- package/dist/astro/middleware/auth.mjs +654 -0
- package/dist/astro/middleware/auth.mjs.map +1 -0
- package/dist/astro/middleware/redirect.d.mts +22 -0
- package/dist/astro/middleware/redirect.d.mts.map +1 -0
- package/dist/astro/middleware/redirect.mjs +63 -0
- package/dist/astro/middleware/redirect.mjs.map +1 -0
- package/dist/astro/middleware/request-context.d.mts +18 -0
- package/dist/astro/middleware/request-context.d.mts.map +1 -0
- package/dist/astro/middleware/request-context.mjs +1310 -0
- package/dist/astro/middleware/request-context.mjs.map +1 -0
- package/dist/astro/middleware/setup.d.mts +20 -0
- package/dist/astro/middleware/setup.d.mts.map +1 -0
- package/dist/astro/middleware/setup.mjs +47 -0
- package/dist/astro/middleware/setup.mjs.map +1 -0
- package/dist/astro/middleware.d.mts +13 -0
- package/dist/astro/middleware.d.mts.map +1 -0
- package/dist/astro/middleware.mjs +1613 -0
- package/dist/astro/middleware.mjs.map +1 -0
- package/dist/astro/types.d.mts +250 -0
- package/dist/astro/types.d.mts.map +1 -0
- package/dist/astro/types.mjs +1 -0
- package/dist/base64-MBPo9ozB.mjs +59 -0
- package/dist/base64-MBPo9ozB.mjs.map +1 -0
- package/dist/byline-CL847F26.mjs +213 -0
- package/dist/byline-CL847F26.mjs.map +1 -0
- package/dist/bylines-C2a-2TGt.mjs +136 -0
- package/dist/bylines-C2a-2TGt.mjs.map +1 -0
- package/dist/chunk-ClPoSABd.mjs +21 -0
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/index.mjs +3909 -0
- package/dist/cli/index.mjs.map +1 -0
- package/dist/client/cf-access.d.mts +60 -0
- package/dist/client/cf-access.d.mts.map +1 -0
- package/dist/client/cf-access.mjs +179 -0
- package/dist/client/cf-access.mjs.map +1 -0
- package/dist/client/index.d.mts +398 -0
- package/dist/client/index.d.mts.map +1 -0
- package/dist/client/index.mjs +346 -0
- package/dist/client/index.mjs.map +1 -0
- package/dist/config-CKE8p9xM.mjs +55 -0
- package/dist/config-CKE8p9xM.mjs.map +1 -0
- package/dist/connection-B4zVnQIa.mjs +40 -0
- package/dist/connection-B4zVnQIa.mjs.map +1 -0
- package/dist/content-D6C2WsZC.mjs +824 -0
- package/dist/content-D6C2WsZC.mjs.map +1 -0
- package/dist/db/index.d.mts +4 -0
- package/dist/db/index.mjs +62 -0
- package/dist/db/index.mjs.map +1 -0
- package/dist/db/libsql.d.mts +11 -0
- package/dist/db/libsql.d.mts.map +1 -0
- package/dist/db/libsql.mjs +17 -0
- package/dist/db/libsql.mjs.map +1 -0
- package/dist/db/postgres.d.mts +11 -0
- package/dist/db/postgres.d.mts.map +1 -0
- package/dist/db/postgres.mjs +30 -0
- package/dist/db/postgres.mjs.map +1 -0
- package/dist/db/sqlite.d.mts +11 -0
- package/dist/db/sqlite.d.mts.map +1 -0
- package/dist/db/sqlite.mjs +16 -0
- package/dist/db/sqlite.mjs.map +1 -0
- package/dist/default-Cyi4aAxu.mjs +81 -0
- package/dist/default-Cyi4aAxu.mjs.map +1 -0
- package/dist/dialect-helpers-B9uSp2GJ.mjs +90 -0
- package/dist/dialect-helpers-B9uSp2GJ.mjs.map +1 -0
- package/dist/error-Cxz0tQeO.mjs +27 -0
- package/dist/error-Cxz0tQeO.mjs.map +1 -0
- package/dist/index-C1xF3OGh.d.mts +4527 -0
- package/dist/index-C1xF3OGh.d.mts.map +1 -0
- package/dist/index.d.mts +16 -0
- package/dist/index.mjs +30 -0
- package/dist/load-yOOlckBj.mjs +28 -0
- package/dist/load-yOOlckBj.mjs.map +1 -0
- package/dist/loader-fz8Q_3EO.mjs +447 -0
- package/dist/loader-fz8Q_3EO.mjs.map +1 -0
- package/dist/manifest-schema-Dcl0R6nM.mjs +184 -0
- package/dist/manifest-schema-Dcl0R6nM.mjs.map +1 -0
- package/dist/media/index.d.mts +26 -0
- package/dist/media/index.d.mts.map +1 -0
- package/dist/media/index.mjs +55 -0
- package/dist/media/index.mjs.map +1 -0
- package/dist/media/local-runtime.d.mts +39 -0
- package/dist/media/local-runtime.d.mts.map +1 -0
- package/dist/media/local-runtime.mjs +133 -0
- package/dist/media/local-runtime.mjs.map +1 -0
- package/dist/media-DqHVh136.mjs +200 -0
- package/dist/media-DqHVh136.mjs.map +1 -0
- package/dist/mode-C2EzN1uE.mjs +23 -0
- package/dist/mode-C2EzN1uE.mjs.map +1 -0
- package/dist/page/index.d.mts +140 -0
- package/dist/page/index.d.mts.map +1 -0
- package/dist/page/index.mjs +416 -0
- package/dist/page/index.mjs.map +1 -0
- package/dist/placeholder-CmGAmqeO.d.mts +276 -0
- package/dist/placeholder-CmGAmqeO.d.mts.map +1 -0
- package/dist/placeholder-SmpOx-_v.mjs +243 -0
- package/dist/placeholder-SmpOx-_v.mjs.map +1 -0
- package/dist/plugin-utils.d.mts +58 -0
- package/dist/plugin-utils.d.mts.map +1 -0
- package/dist/plugin-utils.mjs +78 -0
- package/dist/plugin-utils.mjs.map +1 -0
- package/dist/plugins/adapt-sandbox-entry.d.mts +22 -0
- package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -0
- package/dist/plugins/adapt-sandbox-entry.mjs +113 -0
- package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -0
- package/dist/query-CS_iSj34.mjs +460 -0
- package/dist/query-CS_iSj34.mjs.map +1 -0
- package/dist/redirect-DIfIni3r.mjs +329 -0
- package/dist/redirect-DIfIni3r.mjs.map +1 -0
- package/dist/registry-D_w5HW4G.mjs +863 -0
- package/dist/registry-D_w5HW4G.mjs.map +1 -0
- package/dist/request-context.d.mts +49 -0
- package/dist/request-context.d.mts.map +1 -0
- package/dist/request-context.mjs +43 -0
- package/dist/request-context.mjs.map +1 -0
- package/dist/runner-B-u2F2b6.mjs +1412 -0
- package/dist/runner-B-u2F2b6.mjs.map +1 -0
- package/dist/runner-EAtf0ZIe.d.mts +27 -0
- package/dist/runner-EAtf0ZIe.d.mts.map +1 -0
- package/dist/runtime.d.mts +26 -0
- package/dist/runtime.d.mts.map +1 -0
- package/dist/runtime.mjs +42 -0
- package/dist/runtime.mjs.map +1 -0
- package/dist/search-DG603UrT.mjs +9211 -0
- package/dist/search-DG603UrT.mjs.map +1 -0
- package/dist/seed/index.d.mts +3 -0
- package/dist/seed/index.mjs +15 -0
- package/dist/seo/index.d.mts +70 -0
- package/dist/seo/index.d.mts.map +1 -0
- package/dist/seo/index.mjs +70 -0
- package/dist/seo/index.mjs.map +1 -0
- package/dist/storage/local.d.mts +39 -0
- package/dist/storage/local.d.mts.map +1 -0
- package/dist/storage/local.mjs +166 -0
- package/dist/storage/local.mjs.map +1 -0
- package/dist/storage/s3.d.mts +32 -0
- package/dist/storage/s3.d.mts.map +1 -0
- package/dist/storage/s3.mjs +175 -0
- package/dist/storage/s3.mjs.map +1 -0
- package/dist/tokens-DpgrkrXK.mjs +171 -0
- package/dist/tokens-DpgrkrXK.mjs.map +1 -0
- package/dist/transport-BFGblqwG.d.mts +42 -0
- package/dist/transport-BFGblqwG.d.mts.map +1 -0
- package/dist/transport-yxiQsi8I.mjs +418 -0
- package/dist/transport-yxiQsi8I.mjs.map +1 -0
- package/dist/types-BRuPJGdV.d.mts +102 -0
- package/dist/types-BRuPJGdV.d.mts.map +1 -0
- package/dist/types-C4-fAxN3.d.mts +182 -0
- package/dist/types-C4-fAxN3.d.mts.map +1 -0
- package/dist/types-CMMN0pNg.mjs +31 -0
- package/dist/types-CMMN0pNg.mjs.map +1 -0
- package/dist/types-CUBbjgmP.mjs +16 -0
- package/dist/types-CUBbjgmP.mjs.map +1 -0
- package/dist/types-DRjfYOEv.d.mts +426 -0
- package/dist/types-DRjfYOEv.d.mts.map +1 -0
- package/dist/types-DY5zk5HN.mjs +73 -0
- package/dist/types-DY5zk5HN.mjs.map +1 -0
- package/dist/types-DaNLHo_T.d.mts +184 -0
- package/dist/types-DaNLHo_T.d.mts.map +1 -0
- package/dist/types-DvhsUmSJ.d.mts +1111 -0
- package/dist/types-DvhsUmSJ.d.mts.map +1 -0
- package/dist/validate-CpBtVMsD.d.mts +378 -0
- package/dist/validate-CpBtVMsD.d.mts.map +1 -0
- package/dist/validate-CqRJb_xU.mjs +97 -0
- package/dist/validate-CqRJb_xU.mjs.map +1 -0
- package/dist/validate-O7PWmlnq.mjs +328 -0
- package/dist/validate-O7PWmlnq.mjs.map +1 -0
- package/locals.d.ts +46 -0
- package/package.json +233 -19
- package/src/api/authorize.ts +63 -0
- package/src/api/csrf.ts +48 -0
- package/src/api/error.ts +99 -0
- package/src/api/errors.ts +445 -0
- package/src/api/escape.ts +9 -0
- package/src/api/handlers/api-tokens.ts +240 -0
- package/src/api/handlers/comments.ts +314 -0
- package/src/api/handlers/content.ts +1315 -0
- package/src/api/handlers/dashboard.ts +205 -0
- package/src/api/handlers/device-flow.ts +687 -0
- package/src/api/handlers/index.ts +163 -0
- package/src/api/handlers/manifest.ts +158 -0
- package/src/api/handlers/marketplace.ts +930 -0
- package/src/api/handlers/media.ts +207 -0
- package/src/api/handlers/menus.ts +493 -0
- package/src/api/handlers/oauth-authorization.ts +429 -0
- package/src/api/handlers/oauth-clients.ts +353 -0
- package/src/api/handlers/oauth-user-lookup.ts +39 -0
- package/src/api/handlers/plugins.ts +254 -0
- package/src/api/handlers/redirects.ts +360 -0
- package/src/api/handlers/revision.ts +145 -0
- package/src/api/handlers/schema.ts +534 -0
- package/src/api/handlers/sections.ts +289 -0
- package/src/api/handlers/seo.ts +115 -0
- package/src/api/handlers/settings.ts +49 -0
- package/src/api/handlers/snapshot.ts +350 -0
- package/src/api/handlers/taxonomies.ts +523 -0
- package/src/api/index.ts +6 -0
- package/src/api/openapi/document.ts +2368 -0
- package/src/api/openapi/index.ts +1 -0
- package/src/api/parse.ts +139 -0
- package/src/api/redirect.ts +14 -0
- package/src/api/rev.ts +67 -0
- package/src/api/schemas/auth.ts +112 -0
- package/src/api/schemas/bylines.ts +85 -0
- package/src/api/schemas/comments.ts +117 -0
- package/src/api/schemas/common.ts +89 -0
- package/src/api/schemas/content.ts +191 -0
- package/src/api/schemas/import.ts +52 -0
- package/src/api/schemas/index.ts +17 -0
- package/src/api/schemas/media.ts +116 -0
- package/src/api/schemas/menus.ts +111 -0
- package/src/api/schemas/redirects.ts +155 -0
- package/src/api/schemas/schema.ts +203 -0
- package/src/api/schemas/search.ts +63 -0
- package/src/api/schemas/sections.ts +67 -0
- package/src/api/schemas/settings.ts +63 -0
- package/src/api/schemas/setup.ts +37 -0
- package/src/api/schemas/taxonomies.ts +113 -0
- package/src/api/schemas/users.ts +96 -0
- package/src/api/schemas/widgets.ts +80 -0
- package/src/api/site-url.ts +25 -0
- package/src/api/types.ts +82 -0
- package/src/astro/index.ts +27 -0
- package/src/astro/integration/index.ts +303 -0
- package/src/astro/integration/routes.ts +834 -0
- package/src/astro/integration/runtime.ts +338 -0
- package/src/astro/integration/virtual-modules.ts +469 -0
- package/src/astro/integration/vite-config.ts +328 -0
- package/src/astro/middleware/auth.ts +743 -0
- package/src/astro/middleware/redirect.ts +89 -0
- package/src/astro/middleware/request-context.ts +129 -0
- package/src/astro/middleware/setup.ts +89 -0
- package/src/astro/middleware.ts +398 -0
- package/src/astro/routes/PluginRegistry.tsx +15 -0
- package/src/astro/routes/admin.astro +81 -0
- package/src/astro/routes/api/admin/allowed-domains/[domain].ts +112 -0
- package/src/astro/routes/api/admin/allowed-domains/index.ts +108 -0
- package/src/astro/routes/api/admin/api-tokens/[id].ts +40 -0
- package/src/astro/routes/api/admin/api-tokens/index.ts +68 -0
- package/src/astro/routes/api/admin/bylines/[id]/index.ts +87 -0
- package/src/astro/routes/api/admin/bylines/index.ts +72 -0
- package/src/astro/routes/api/admin/comments/[id]/status.ts +120 -0
- package/src/astro/routes/api/admin/comments/[id].ts +64 -0
- package/src/astro/routes/api/admin/comments/bulk.ts +42 -0
- package/src/astro/routes/api/admin/comments/counts.ts +30 -0
- package/src/astro/routes/api/admin/comments/index.ts +46 -0
- package/src/astro/routes/api/admin/hooks/exclusive/[hookName].ts +91 -0
- package/src/astro/routes/api/admin/hooks/exclusive/index.ts +51 -0
- package/src/astro/routes/api/admin/oauth-clients/[id].ts +110 -0
- package/src/astro/routes/api/admin/oauth-clients/index.ts +71 -0
- package/src/astro/routes/api/admin/plugins/[id]/disable.ts +39 -0
- package/src/astro/routes/api/admin/plugins/[id]/enable.ts +39 -0
- package/src/astro/routes/api/admin/plugins/[id]/index.ts +38 -0
- package/src/astro/routes/api/admin/plugins/[id]/uninstall.ts +48 -0
- package/src/astro/routes/api/admin/plugins/[id]/update.ts +59 -0
- package/src/astro/routes/api/admin/plugins/index.ts +32 -0
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/icon.ts +61 -0
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/index.ts +33 -0
- package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +62 -0
- package/src/astro/routes/api/admin/plugins/marketplace/index.ts +38 -0
- package/src/astro/routes/api/admin/plugins/updates.ts +28 -0
- package/src/astro/routes/api/admin/themes/marketplace/[id]/index.ts +33 -0
- package/src/astro/routes/api/admin/themes/marketplace/[id]/thumbnail.ts +61 -0
- package/src/astro/routes/api/admin/themes/marketplace/index.ts +45 -0
- package/src/astro/routes/api/admin/users/[id]/disable.ts +69 -0
- package/src/astro/routes/api/admin/users/[id]/enable.ts +48 -0
- package/src/astro/routes/api/admin/users/[id]/index.ts +146 -0
- package/src/astro/routes/api/admin/users/[id]/send-recovery.ts +72 -0
- package/src/astro/routes/api/admin/users/index.ts +66 -0
- package/src/astro/routes/api/auth/dev-bypass.ts +139 -0
- package/src/astro/routes/api/auth/invite/accept.ts +52 -0
- package/src/astro/routes/api/auth/invite/complete.ts +84 -0
- package/src/astro/routes/api/auth/invite/index.ts +99 -0
- package/src/astro/routes/api/auth/logout.ts +40 -0
- package/src/astro/routes/api/auth/magic-link/send.ts +89 -0
- package/src/astro/routes/api/auth/magic-link/verify.ts +71 -0
- package/src/astro/routes/api/auth/me.ts +60 -0
- package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +219 -0
- package/src/astro/routes/api/auth/oauth/[provider].ts +119 -0
- package/src/astro/routes/api/auth/passkey/[id].ts +124 -0
- package/src/astro/routes/api/auth/passkey/index.ts +54 -0
- package/src/astro/routes/api/auth/passkey/options.ts +82 -0
- package/src/astro/routes/api/auth/passkey/register/options.ts +86 -0
- package/src/astro/routes/api/auth/passkey/register/verify.ts +117 -0
- package/src/astro/routes/api/auth/passkey/verify.ts +66 -0
- package/src/astro/routes/api/auth/signup/complete.ts +85 -0
- package/src/astro/routes/api/auth/signup/request.ts +77 -0
- package/src/astro/routes/api/auth/signup/verify.ts +53 -0
- package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +312 -0
- package/src/astro/routes/api/content/[collection]/[id]/compare.ts +28 -0
- package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +54 -0
- package/src/astro/routes/api/content/[collection]/[id]/duplicate.ts +61 -0
- package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +33 -0
- package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +107 -0
- package/src/astro/routes/api/content/[collection]/[id]/publish.ts +56 -0
- package/src/astro/routes/api/content/[collection]/[id]/restore.ts +54 -0
- package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +31 -0
- package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +105 -0
- package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +140 -0
- package/src/astro/routes/api/content/[collection]/[id]/translations.ts +30 -0
- package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +56 -0
- package/src/astro/routes/api/content/[collection]/[id].ts +137 -0
- package/src/astro/routes/api/content/[collection]/index.ts +59 -0
- package/src/astro/routes/api/content/[collection]/trash.ts +33 -0
- package/src/astro/routes/api/dashboard.ts +32 -0
- package/src/astro/routes/api/dev/emails.ts +36 -0
- package/src/astro/routes/api/import/probe.ts +47 -0
- package/src/astro/routes/api/import/wordpress/analyze.ts +510 -0
- package/src/astro/routes/api/import/wordpress/execute.ts +283 -0
- package/src/astro/routes/api/import/wordpress/media.ts +338 -0
- package/src/astro/routes/api/import/wordpress/prepare.ts +181 -0
- package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +393 -0
- package/src/astro/routes/api/import/wordpress-plugin/analyze.ts +111 -0
- package/src/astro/routes/api/import/wordpress-plugin/callback.ts +58 -0
- package/src/astro/routes/api/import/wordpress-plugin/execute.ts +347 -0
- package/src/astro/routes/api/manifest.ts +62 -0
- package/src/astro/routes/api/mcp.ts +124 -0
- package/src/astro/routes/api/media/[id]/confirm.ts +93 -0
- package/src/astro/routes/api/media/[id].ts +145 -0
- package/src/astro/routes/api/media/file/[key].ts +79 -0
- package/src/astro/routes/api/media/providers/[providerId]/[itemId].ts +86 -0
- package/src/astro/routes/api/media/providers/[providerId]/index.ts +111 -0
- package/src/astro/routes/api/media/providers/index.ts +30 -0
- package/src/astro/routes/api/media/upload-url.ts +137 -0
- package/src/astro/routes/api/media.ts +190 -0
- package/src/astro/routes/api/menus/[name]/items.ts +87 -0
- package/src/astro/routes/api/menus/[name]/reorder.ts +33 -0
- package/src/astro/routes/api/menus/[name].ts +65 -0
- package/src/astro/routes/api/menus/index.ts +47 -0
- package/src/astro/routes/api/oauth/authorize.ts +412 -0
- package/src/astro/routes/api/oauth/device/authorize.ts +45 -0
- package/src/astro/routes/api/oauth/device/code.ts +51 -0
- package/src/astro/routes/api/oauth/device/token.ts +69 -0
- package/src/astro/routes/api/oauth/token/refresh.ts +38 -0
- package/src/astro/routes/api/oauth/token/revoke.ts +38 -0
- package/src/astro/routes/api/oauth/token.ts +184 -0
- package/src/astro/routes/api/openapi.json.ts +32 -0
- package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +92 -0
- package/src/astro/routes/api/redirects/404s/index.ts +72 -0
- package/src/astro/routes/api/redirects/404s/summary.ts +33 -0
- package/src/astro/routes/api/redirects/[id].ts +84 -0
- package/src/astro/routes/api/redirects/index.ts +52 -0
- package/src/astro/routes/api/revisions/[revisionId]/index.ts +29 -0
- package/src/astro/routes/api/revisions/[revisionId]/restore.ts +62 -0
- package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +76 -0
- package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +52 -0
- package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +32 -0
- package/src/astro/routes/api/schema/collections/[slug]/index.ts +80 -0
- package/src/astro/routes/api/schema/collections/index.ts +47 -0
- package/src/astro/routes/api/schema/index.ts +109 -0
- package/src/astro/routes/api/schema/orphans/[slug].ts +36 -0
- package/src/astro/routes/api/schema/orphans/index.ts +26 -0
- package/src/astro/routes/api/search/enable.ts +64 -0
- package/src/astro/routes/api/search/index.ts +55 -0
- package/src/astro/routes/api/search/rebuild.ts +72 -0
- package/src/astro/routes/api/search/stats.ts +35 -0
- package/src/astro/routes/api/search/suggest.ts +53 -0
- package/src/astro/routes/api/sections/[slug].ts +84 -0
- package/src/astro/routes/api/sections/index.ts +52 -0
- package/src/astro/routes/api/settings/email.ts +150 -0
- package/src/astro/routes/api/settings.ts +67 -0
- package/src/astro/routes/api/setup/admin-verify.ts +100 -0
- package/src/astro/routes/api/setup/admin.ts +94 -0
- package/src/astro/routes/api/setup/dev-bypass.ts +199 -0
- package/src/astro/routes/api/setup/dev-reset.ts +40 -0
- package/src/astro/routes/api/setup/index.ts +126 -0
- package/src/astro/routes/api/setup/status.ts +122 -0
- package/src/astro/routes/api/snapshot.ts +75 -0
- package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +95 -0
- package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +69 -0
- package/src/astro/routes/api/taxonomies/index.ts +59 -0
- package/src/astro/routes/api/themes/preview.ts +77 -0
- package/src/astro/routes/api/typegen.ts +114 -0
- package/src/astro/routes/api/well-known/auth.ts +68 -0
- package/src/astro/routes/api/well-known/oauth-authorization-server.ts +44 -0
- package/src/astro/routes/api/well-known/oauth-protected-resource.ts +37 -0
- package/src/astro/routes/api/widget-areas/[name]/reorder.ts +72 -0
- package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +127 -0
- package/src/astro/routes/api/widget-areas/[name]/widgets.ts +80 -0
- package/src/astro/routes/api/widget-areas/[name].ts +87 -0
- package/src/astro/routes/api/widget-areas/index.ts +99 -0
- package/src/astro/routes/api/widget-components.ts +22 -0
- package/src/astro/routes/robots.txt.ts +77 -0
- package/src/astro/routes/sitemap.xml.ts +97 -0
- package/src/astro/storage/adapters.ts +74 -0
- package/src/astro/storage/index.ts +19 -0
- package/src/astro/storage/types.ts +60 -0
- package/src/astro/types.ts +346 -0
- package/src/auth/api-tokens.ts +25 -0
- package/src/auth/challenge-store.ts +80 -0
- package/src/auth/mode.ts +96 -0
- package/src/auth/oauth-state-store.ts +96 -0
- package/src/auth/passkey-config.ts +27 -0
- package/src/auth/rate-limit.ts +158 -0
- package/src/auth/scopes.ts +33 -0
- package/src/auth/types.ts +104 -0
- package/src/aws-sdk.d.ts +100 -0
- package/src/bylines/index.ts +237 -0
- package/src/cleanup.ts +153 -0
- package/src/cli/client-factory.ts +100 -0
- package/src/cli/commands/auth.ts +46 -0
- package/src/cli/commands/bundle-utils.ts +247 -0
- package/src/cli/commands/bundle.ts +609 -0
- package/src/cli/commands/content.ts +442 -0
- package/src/cli/commands/dev.ts +191 -0
- package/src/cli/commands/doctor.ts +211 -0
- package/src/cli/commands/export-seed.ts +630 -0
- package/src/cli/commands/import/wordpress.ts +1056 -0
- package/src/cli/commands/init.ts +192 -0
- package/src/cli/commands/login.ts +547 -0
- package/src/cli/commands/media.ts +165 -0
- package/src/cli/commands/menu.ts +67 -0
- package/src/cli/commands/plugin-init.ts +291 -0
- package/src/cli/commands/plugin-validate.ts +31 -0
- package/src/cli/commands/plugin.ts +33 -0
- package/src/cli/commands/publish.ts +699 -0
- package/src/cli/commands/schema.ts +233 -0
- package/src/cli/commands/search-cmd.ts +54 -0
- package/src/cli/commands/seed.ts +288 -0
- package/src/cli/commands/taxonomy.ts +128 -0
- package/src/cli/commands/types.ts +68 -0
- package/src/cli/credentials.ts +236 -0
- package/src/cli/index.ts +70 -0
- package/src/cli/output.ts +75 -0
- package/src/cli/wxr/parser.ts +969 -0
- package/src/client/cf-access.ts +193 -0
- package/src/client/index.ts +854 -0
- package/src/client/portable-text.ts +413 -0
- package/src/client/transport.ts +200 -0
- package/src/comments/moderator.ts +46 -0
- package/src/comments/notifications.ts +144 -0
- package/src/comments/query.ts +105 -0
- package/src/comments/service.ts +213 -0
- package/src/components/Break.astro +45 -0
- package/src/components/Button.astro +71 -0
- package/src/components/Buttons.astro +49 -0
- package/src/components/Code.astro +59 -0
- package/src/components/Columns.astro +59 -0
- package/src/components/CommentForm.astro +315 -0
- package/src/components/Comments.astro +232 -0
- package/src/components/Cover.astro +128 -0
- package/src/components/EmDashBodyEnd.astro +32 -0
- package/src/components/EmDashBodyStart.astro +32 -0
- package/src/components/EmDashHead.astro +53 -0
- package/src/components/EmDashImage.astro +178 -0
- package/src/components/EmDashMedia.astro +167 -0
- package/src/components/Embed.astro +128 -0
- package/src/components/File.astro +122 -0
- package/src/components/Gallery.astro +93 -0
- package/src/components/HtmlBlock.astro +33 -0
- package/src/components/Image.astro +178 -0
- package/src/components/InlineEditor.astro +27 -0
- package/src/components/InlinePortableTextEditor.tsx +1905 -0
- package/src/components/LiveSearch.astro +614 -0
- package/src/components/PortableText.astro +51 -0
- package/src/components/Pullquote.astro +51 -0
- package/src/components/Table.astro +108 -0
- package/src/components/WidgetArea.astro +22 -0
- package/src/components/WidgetRenderer.astro +72 -0
- package/src/components/index.ts +116 -0
- package/src/components/marks/Link.astro +31 -0
- package/src/components/marks/StrikeThrough.astro +7 -0
- package/src/components/marks/Subscript.astro +7 -0
- package/src/components/marks/Superscript.astro +7 -0
- package/src/components/marks/Underline.astro +7 -0
- package/src/components/widgets/Archives.astro +65 -0
- package/src/components/widgets/Categories.astro +35 -0
- package/src/components/widgets/RecentPosts.astro +51 -0
- package/src/components/widgets/Search.astro +18 -0
- package/src/components/widgets/Tags.astro +38 -0
- package/src/content/converters/index.ts +9 -0
- package/src/content/converters/portable-text-to-prosemirror.ts +385 -0
- package/src/content/converters/prosemirror-to-portable-text.ts +413 -0
- package/src/content/converters/types.ts +120 -0
- package/src/content/index.ts +5 -0
- package/src/database/connection.ts +67 -0
- package/src/database/dialect-helpers.ts +138 -0
- package/src/database/index.ts +5 -0
- package/src/database/migrations/001_initial.ts +136 -0
- package/src/database/migrations/002_media_status.ts +26 -0
- package/src/database/migrations/003_schema_registry.ts +79 -0
- package/src/database/migrations/004_plugins.ts +62 -0
- package/src/database/migrations/005_menus.ts +67 -0
- package/src/database/migrations/006_taxonomy_defs.ts +51 -0
- package/src/database/migrations/007_widgets.ts +42 -0
- package/src/database/migrations/008_auth.ts +194 -0
- package/src/database/migrations/009_user_disabled.ts +27 -0
- package/src/database/migrations/011_sections.ts +65 -0
- package/src/database/migrations/012_search.ts +25 -0
- package/src/database/migrations/013_scheduled_publishing.ts +51 -0
- package/src/database/migrations/014_draft_revisions.ts +72 -0
- package/src/database/migrations/015_indexes.ts +82 -0
- package/src/database/migrations/016_api_tokens.ts +89 -0
- package/src/database/migrations/017_authorization_codes.ts +45 -0
- package/src/database/migrations/018_seo.ts +56 -0
- package/src/database/migrations/019_i18n.ts +618 -0
- package/src/database/migrations/020_collection_url_pattern.ts +23 -0
- package/src/database/migrations/021_remove_section_categories.ts +43 -0
- package/src/database/migrations/022_marketplace_plugin_state.ts +46 -0
- package/src/database/migrations/023_plugin_metadata.ts +33 -0
- package/src/database/migrations/024_media_placeholders.ts +32 -0
- package/src/database/migrations/025_oauth_clients.ts +28 -0
- package/src/database/migrations/026_cron_tasks.ts +49 -0
- package/src/database/migrations/027_comments.ts +87 -0
- package/src/database/migrations/028_drop_author_url.ts +9 -0
- package/src/database/migrations/029_redirects.ts +67 -0
- package/src/database/migrations/030_widen_scheduled_index.ts +48 -0
- package/src/database/migrations/031_bylines.ts +90 -0
- package/src/database/migrations/032_rate_limits.ts +42 -0
- package/src/database/migrations/runner.ts +170 -0
- package/src/database/repositories/audit.ts +294 -0
- package/src/database/repositories/byline.ts +387 -0
- package/src/database/repositories/comment.ts +458 -0
- package/src/database/repositories/content.ts +1144 -0
- package/src/database/repositories/index.ts +30 -0
- package/src/database/repositories/media.ts +347 -0
- package/src/database/repositories/options.ts +150 -0
- package/src/database/repositories/plugin-storage.ts +373 -0
- package/src/database/repositories/redirect.ts +480 -0
- package/src/database/repositories/revision.ts +200 -0
- package/src/database/repositories/seo.ts +176 -0
- package/src/database/repositories/taxonomy.ts +294 -0
- package/src/database/repositories/types.ts +132 -0
- package/src/database/repositories/user.ts +258 -0
- package/src/database/transaction.ts +54 -0
- package/src/database/types.ts +501 -0
- package/src/database/validate.ts +138 -0
- package/src/db/adapters.ts +125 -0
- package/src/db/index.ts +37 -0
- package/src/db/libsql.ts +23 -0
- package/src/db/postgres.ts +30 -0
- package/src/db/sqlite.ts +27 -0
- package/src/emdash-runtime.ts +2096 -0
- package/src/fields/boolean.ts +34 -0
- package/src/fields/datetime.ts +44 -0
- package/src/fields/file.ts +41 -0
- package/src/fields/image.ts +34 -0
- package/src/fields/index.ts +42 -0
- package/src/fields/integer.ts +50 -0
- package/src/fields/json.ts +37 -0
- package/src/fields/multiselect.ts +48 -0
- package/src/fields/number.ts +52 -0
- package/src/fields/portable-text.ts +33 -0
- package/src/fields/reference.ts +29 -0
- package/src/fields/richtext.ts +31 -0
- package/src/fields/select.ts +46 -0
- package/src/fields/slug.ts +38 -0
- package/src/fields/text.ts +55 -0
- package/src/fields/textarea.ts +52 -0
- package/src/fields/types.ts +64 -0
- package/src/i18n/config.ts +68 -0
- package/src/import/index.ts +90 -0
- package/src/import/menus.ts +436 -0
- package/src/import/registry.ts +111 -0
- package/src/import/sections.ts +103 -0
- package/src/import/settings.ts +281 -0
- package/src/import/sources/wordpress-plugin.ts +641 -0
- package/src/import/sources/wordpress-rest.ts +191 -0
- package/src/import/sources/wxr.ts +330 -0
- package/src/import/ssrf.ts +260 -0
- package/src/import/types.ts +418 -0
- package/src/import/utils.ts +412 -0
- package/src/index.ts +481 -0
- package/src/loader.ts +770 -0
- package/src/mcp/server.ts +1463 -0
- package/src/media/index.ts +32 -0
- package/src/media/local-runtime.ts +213 -0
- package/src/media/local.ts +46 -0
- package/src/media/normalize.ts +190 -0
- package/src/media/placeholder.ts +150 -0
- package/src/media/provider-loader.ts +78 -0
- package/src/media/types.ts +279 -0
- package/src/menus/index.ts +324 -0
- package/src/menus/types.ts +112 -0
- package/src/page/context.ts +93 -0
- package/src/page/fragments.ts +89 -0
- package/src/page/index.ts +58 -0
- package/src/page/jsonld.ts +94 -0
- package/src/page/metadata.ts +185 -0
- package/src/page/seo-contributions.ts +136 -0
- package/src/plugin-utils.ts +80 -0
- package/src/plugins/adapt-sandbox-entry.ts +207 -0
- package/src/plugins/context.ts +833 -0
- package/src/plugins/cron.ts +361 -0
- package/src/plugins/define-plugin.ts +259 -0
- package/src/plugins/email-console.ts +73 -0
- package/src/plugins/email.ts +209 -0
- package/src/plugins/hooks.ts +1273 -0
- package/src/plugins/index.ts +193 -0
- package/src/plugins/manager.ts +595 -0
- package/src/plugins/manifest-schema.ts +230 -0
- package/src/plugins/marketplace.ts +460 -0
- package/src/plugins/request-meta.ts +139 -0
- package/src/plugins/routes.ts +302 -0
- package/src/plugins/sandbox/index.ts +18 -0
- package/src/plugins/sandbox/noop.ts +76 -0
- package/src/plugins/sandbox/types.ts +173 -0
- package/src/plugins/scheduler/node.ts +122 -0
- package/src/plugins/scheduler/piggyback.ts +71 -0
- package/src/plugins/scheduler/types.ts +27 -0
- package/src/plugins/state.ts +208 -0
- package/src/plugins/storage-indexes.ts +326 -0
- package/src/plugins/storage-query.ts +240 -0
- package/src/plugins/types.ts +1284 -0
- package/src/preview/helpers.ts +27 -0
- package/src/preview/index.ts +40 -0
- package/src/preview/tokens.ts +279 -0
- package/src/preview/urls.ts +118 -0
- package/src/query.ts +674 -0
- package/src/redirects/patterns.ts +224 -0
- package/src/request-context.ts +67 -0
- package/src/runtime.ts +21 -0
- package/src/schema/index.ts +29 -0
- package/src/schema/query.ts +44 -0
- package/src/schema/registry.ts +965 -0
- package/src/schema/types.ts +276 -0
- package/src/schema/zod-generator.ts +413 -0
- package/src/search/fts-manager.ts +452 -0
- package/src/search/index.ts +26 -0
- package/src/search/query.ts +396 -0
- package/src/search/text-extraction.ts +162 -0
- package/src/search/types.ts +114 -0
- package/src/sections/index.ts +226 -0
- package/src/sections/types.ts +86 -0
- package/src/seed/apply.ts +1141 -0
- package/src/seed/default.ts +86 -0
- package/src/seed/index.ts +28 -0
- package/src/seed/load.ts +35 -0
- package/src/seed/types.ts +341 -0
- package/src/seed/validate.ts +642 -0
- package/src/seo/index.ts +179 -0
- package/src/settings/index.ts +203 -0
- package/src/settings/types.ts +58 -0
- package/src/storage/index.ts +28 -0
- package/src/storage/local.ts +253 -0
- package/src/storage/s3.ts +271 -0
- package/src/storage/types.ts +204 -0
- package/src/taxonomies/index.ts +309 -0
- package/src/taxonomies/types.ts +61 -0
- package/src/ui.ts +75 -0
- package/src/utils/base64.ts +73 -0
- package/src/utils/hash.ts +36 -0
- package/src/utils/sanitize.ts +20 -0
- package/src/utils/slugify.ts +29 -0
- package/src/utils/url.ts +48 -0
- package/src/virtual-modules.d.ts +111 -0
- package/src/visual-editing/editable.ts +108 -0
- package/src/visual-editing/toolbar.ts +1229 -0
- package/src/widgets/components.ts +105 -0
- package/src/widgets/index.ts +131 -0
- package/src/widgets/types.ts +81 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single media item endpoint
|
|
3
|
+
*
|
|
4
|
+
* GET /_emdash/api/media/:id - Get media item
|
|
5
|
+
* PUT /_emdash/api/media/:id - Update media metadata
|
|
6
|
+
* DELETE /_emdash/api/media/:id - Delete media item
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { APIRoute } from "astro";
|
|
10
|
+
|
|
11
|
+
import { requireOwnerPerm, requirePerm } from "#api/authorize.js";
|
|
12
|
+
import { apiError, handleError, unwrapResult } from "#api/error.js";
|
|
13
|
+
import { isParseError, parseBody } from "#api/parse.js";
|
|
14
|
+
import { mediaUpdateBody } from "#api/schemas.js";
|
|
15
|
+
|
|
16
|
+
export const prerender = false;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get media item
|
|
20
|
+
*/
|
|
21
|
+
export const GET: APIRoute = async ({ params, locals }) => {
|
|
22
|
+
const { emdash, user } = locals;
|
|
23
|
+
const { id } = params;
|
|
24
|
+
|
|
25
|
+
const readDenied = requirePerm(user, "media:read");
|
|
26
|
+
if (readDenied) return readDenied;
|
|
27
|
+
|
|
28
|
+
if (!id) {
|
|
29
|
+
return apiError("INVALID_REQUEST", "Media ID required", 400);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!emdash?.handleMediaGet) {
|
|
33
|
+
return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const result = await emdash.handleMediaGet(id);
|
|
37
|
+
return unwrapResult(result);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Update media metadata
|
|
42
|
+
*
|
|
43
|
+
* Authors can edit their own media; editors+ can edit any.
|
|
44
|
+
*/
|
|
45
|
+
export const PUT: APIRoute = async ({ params, request, locals }) => {
|
|
46
|
+
const { emdash, user } = locals;
|
|
47
|
+
const { id } = params;
|
|
48
|
+
|
|
49
|
+
// Minimum permission gate — ownership checked below
|
|
50
|
+
const editDenied = requirePerm(user, "media:edit_own");
|
|
51
|
+
if (editDenied) return editDenied;
|
|
52
|
+
|
|
53
|
+
if (!id) {
|
|
54
|
+
return apiError("INVALID_REQUEST", "Media ID required", 400);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!emdash?.handleMediaGet || !emdash?.handleMediaUpdate) {
|
|
58
|
+
return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
// Fetch media item for ownership check
|
|
63
|
+
const getResult = await emdash.handleMediaGet(id);
|
|
64
|
+
if (!getResult.success || !getResult.data?.item) {
|
|
65
|
+
return apiError("NOT_FOUND", "Media item not found", 404);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const media = getResult.data.item;
|
|
69
|
+
|
|
70
|
+
// Ownership check: authors can edit own, editors+ can edit any
|
|
71
|
+
const ownerDenied = requireOwnerPerm(user, media.authorId, "media:edit_own", "media:edit_any");
|
|
72
|
+
if (ownerDenied) return ownerDenied;
|
|
73
|
+
|
|
74
|
+
const body = await parseBody(request, mediaUpdateBody);
|
|
75
|
+
if (isParseError(body)) return body;
|
|
76
|
+
|
|
77
|
+
const result = await emdash.handleMediaUpdate(id, {
|
|
78
|
+
alt: body.alt,
|
|
79
|
+
caption: body.caption,
|
|
80
|
+
width: body.width,
|
|
81
|
+
height: body.height,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return unwrapResult(result);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
return handleError(error, "Failed to update media", "MEDIA_UPDATE_ERROR");
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Delete media item
|
|
92
|
+
*
|
|
93
|
+
* Authors can delete their own media; editors+ can delete any.
|
|
94
|
+
*/
|
|
95
|
+
export const DELETE: APIRoute = async ({ params, locals }) => {
|
|
96
|
+
const { emdash, user } = locals;
|
|
97
|
+
const { id } = params;
|
|
98
|
+
|
|
99
|
+
// Minimum permission gate — ownership checked below
|
|
100
|
+
const deleteDenied = requirePerm(user, "media:delete_own");
|
|
101
|
+
if (deleteDenied) return deleteDenied;
|
|
102
|
+
|
|
103
|
+
if (!id) {
|
|
104
|
+
return apiError("INVALID_REQUEST", "Media ID required", 400);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!emdash?.handleMediaGet || !emdash?.handleMediaDelete) {
|
|
108
|
+
return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
// Fetch media item for ownership check and storage key
|
|
113
|
+
const getResult = await emdash.handleMediaGet(id);
|
|
114
|
+
if (!getResult.success || !getResult.data?.item) {
|
|
115
|
+
return apiError("NOT_FOUND", "Media item not found", 404);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const media = getResult.data.item;
|
|
119
|
+
|
|
120
|
+
// Ownership check: authors can delete own, editors+ can delete any
|
|
121
|
+
const ownerDenied = requireOwnerPerm(
|
|
122
|
+
user,
|
|
123
|
+
media.authorId,
|
|
124
|
+
"media:delete_own",
|
|
125
|
+
"media:delete_any",
|
|
126
|
+
);
|
|
127
|
+
if (ownerDenied) return ownerDenied;
|
|
128
|
+
|
|
129
|
+
// Delete file from storage via the storage adapter
|
|
130
|
+
if (emdash.storage) {
|
|
131
|
+
try {
|
|
132
|
+
await emdash.storage.delete(media.storageKey);
|
|
133
|
+
} catch {
|
|
134
|
+
// Best-effort — continue with database deletion
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Delete from database
|
|
139
|
+
const result = await emdash.handleMediaDelete(id);
|
|
140
|
+
|
|
141
|
+
return unwrapResult(result);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
return handleError(error, "Failed to delete media", "MEDIA_DELETE_ERROR");
|
|
144
|
+
}
|
|
145
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serve uploaded media files
|
|
3
|
+
*
|
|
4
|
+
* GET /_emdash/api/media/file/:key - Serve file from storage
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { APIRoute } from "astro";
|
|
8
|
+
|
|
9
|
+
import { apiError, handleError } from "#api/error.js";
|
|
10
|
+
|
|
11
|
+
export const prerender = false;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Content types that are safe to display inline (simple raster/vector images, video, audio).
|
|
15
|
+
* Everything else gets Content-Disposition: attachment to prevent script execution.
|
|
16
|
+
*/
|
|
17
|
+
const SAFE_INLINE_TYPES = new Set([
|
|
18
|
+
"image/jpeg",
|
|
19
|
+
"image/png",
|
|
20
|
+
"image/gif",
|
|
21
|
+
"image/webp",
|
|
22
|
+
"image/avif",
|
|
23
|
+
"image/x-icon",
|
|
24
|
+
"video/mp4",
|
|
25
|
+
"video/webm",
|
|
26
|
+
"audio/mpeg",
|
|
27
|
+
"audio/wav",
|
|
28
|
+
"audio/ogg",
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
export const GET: APIRoute = async ({ params, locals }) => {
|
|
32
|
+
const { key } = params;
|
|
33
|
+
const { emdash } = locals;
|
|
34
|
+
|
|
35
|
+
if (!key) {
|
|
36
|
+
return apiError("NOT_FOUND", "File not found", 404);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!emdash?.storage) {
|
|
40
|
+
return apiError("NOT_CONFIGURED", "Storage not configured", 500);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const result = await emdash.storage.download(key);
|
|
45
|
+
|
|
46
|
+
const headers: Record<string, string> = {
|
|
47
|
+
"Content-Type": result.contentType,
|
|
48
|
+
"Cache-Control": "public, max-age=31536000, immutable",
|
|
49
|
+
"X-Content-Type-Options": "nosniff",
|
|
50
|
+
// Sandbox CSP on all user-uploaded content — prevents script execution
|
|
51
|
+
// even for SVGs navigated to directly or content types that support scripting.
|
|
52
|
+
"Content-Security-Policy":
|
|
53
|
+
"sandbox; default-src 'none'; img-src 'self'; style-src 'unsafe-inline'",
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
if (result.size) {
|
|
57
|
+
headers["Content-Length"] = String(result.size);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Safe image/media types can render inline; everything else (SVG, PDF,
|
|
61
|
+
// HTML, JS, etc.) must be downloaded to prevent stored XSS.
|
|
62
|
+
if (SAFE_INLINE_TYPES.has(result.contentType)) {
|
|
63
|
+
headers["Content-Disposition"] = "inline";
|
|
64
|
+
} else {
|
|
65
|
+
headers["Content-Disposition"] = "attachment";
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return new Response(result.body, { status: 200, headers });
|
|
69
|
+
} catch (error) {
|
|
70
|
+
// Check if it's a "not found" error
|
|
71
|
+
if (
|
|
72
|
+
error instanceof Error &&
|
|
73
|
+
(error.message.includes("not found") || error.message.includes("NOT_FOUND"))
|
|
74
|
+
) {
|
|
75
|
+
return apiError("NOT_FOUND", "File not found", 404);
|
|
76
|
+
}
|
|
77
|
+
return handleError(error, "Failed to serve file", "FILE_SERVE_ERROR");
|
|
78
|
+
}
|
|
79
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media Provider Item Endpoint
|
|
3
|
+
*
|
|
4
|
+
* GET /_emdash/api/media/providers/:providerId/:itemId - Get single item
|
|
5
|
+
* DELETE /_emdash/api/media/providers/:providerId/:itemId - Delete item
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { APIRoute } from "astro";
|
|
9
|
+
|
|
10
|
+
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
11
|
+
|
|
12
|
+
export const prerender = false;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get a single media item from a provider
|
|
16
|
+
*/
|
|
17
|
+
export const GET: APIRoute = async ({ params, locals }) => {
|
|
18
|
+
const { emdash } = locals;
|
|
19
|
+
const { providerId, itemId } = params;
|
|
20
|
+
|
|
21
|
+
if (!providerId || !itemId) {
|
|
22
|
+
return apiError("INVALID_REQUEST", "Provider ID and Item ID required", 400);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!emdash?.getMediaProvider) {
|
|
26
|
+
return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const provider = emdash.getMediaProvider(providerId);
|
|
30
|
+
if (!provider) {
|
|
31
|
+
return apiError("NOT_FOUND", `Provider "${providerId}" not found`, 404);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!provider.get) {
|
|
35
|
+
return apiError(
|
|
36
|
+
"NOT_SUPPORTED",
|
|
37
|
+
`Provider "${providerId}" does not support getting individual items`,
|
|
38
|
+
400,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const item = await provider.get(itemId);
|
|
44
|
+
|
|
45
|
+
if (!item) {
|
|
46
|
+
return apiError("NOT_FOUND", "Item not found", 404);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return apiSuccess({ item });
|
|
50
|
+
} catch (error) {
|
|
51
|
+
return handleError(error, "Failed to get item from provider", "PROVIDER_GET_ERROR");
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Delete a media item from a provider
|
|
57
|
+
*/
|
|
58
|
+
export const DELETE: APIRoute = async ({ params, locals }) => {
|
|
59
|
+
const { emdash } = locals;
|
|
60
|
+
const { providerId, itemId } = params;
|
|
61
|
+
|
|
62
|
+
if (!providerId || !itemId) {
|
|
63
|
+
return apiError("INVALID_REQUEST", "Provider ID and Item ID required", 400);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!emdash?.getMediaProvider) {
|
|
67
|
+
return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const provider = emdash.getMediaProvider(providerId);
|
|
71
|
+
if (!provider) {
|
|
72
|
+
return apiError("NOT_FOUND", `Provider "${providerId}" not found`, 404);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!provider.delete) {
|
|
76
|
+
return apiError("NOT_SUPPORTED", `Provider "${providerId}" does not support deletion`, 400);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
await provider.delete(itemId);
|
|
81
|
+
|
|
82
|
+
return apiSuccess({ deleted: true });
|
|
83
|
+
} catch (error) {
|
|
84
|
+
return handleError(error, "Failed to delete item from provider", "PROVIDER_DELETE_ERROR");
|
|
85
|
+
}
|
|
86
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media Provider List/Upload Endpoint
|
|
3
|
+
*
|
|
4
|
+
* GET /_emdash/api/media/providers/:providerId - List media from provider
|
|
5
|
+
* POST /_emdash/api/media/providers/:providerId - Upload to provider
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { APIRoute } from "astro";
|
|
9
|
+
|
|
10
|
+
import { requirePerm } from "#api/authorize.js";
|
|
11
|
+
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
12
|
+
|
|
13
|
+
export const prerender = false;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* List media from a specific provider
|
|
17
|
+
*/
|
|
18
|
+
export const GET: APIRoute = async ({ params, request, locals }) => {
|
|
19
|
+
const { emdash, user } = locals;
|
|
20
|
+
const { providerId } = params;
|
|
21
|
+
|
|
22
|
+
const readDenied = requirePerm(user, "media:read");
|
|
23
|
+
if (readDenied) return readDenied;
|
|
24
|
+
|
|
25
|
+
if (!providerId) {
|
|
26
|
+
return apiError("INVALID_REQUEST", "Provider ID required", 400);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!emdash?.getMediaProvider) {
|
|
30
|
+
return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const provider = emdash.getMediaProvider(providerId);
|
|
34
|
+
if (!provider) {
|
|
35
|
+
return apiError("NOT_FOUND", `Provider "${providerId}" not found`, 404);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const url = new URL(request.url);
|
|
39
|
+
const cursor = url.searchParams.get("cursor") || undefined;
|
|
40
|
+
const limit = url.searchParams.get("limit")
|
|
41
|
+
? parseInt(url.searchParams.get("limit")!, 10)
|
|
42
|
+
: undefined;
|
|
43
|
+
const query = url.searchParams.get("query") || undefined;
|
|
44
|
+
const mimeType = url.searchParams.get("mimeType") || undefined;
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const result = await provider.list({
|
|
48
|
+
cursor,
|
|
49
|
+
limit,
|
|
50
|
+
query,
|
|
51
|
+
mimeType,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return apiSuccess({
|
|
55
|
+
items: result.items,
|
|
56
|
+
nextCursor: result.nextCursor,
|
|
57
|
+
});
|
|
58
|
+
} catch (error) {
|
|
59
|
+
return handleError(error, "Failed to list media from provider", "PROVIDER_LIST_ERROR");
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Upload media to a specific provider
|
|
65
|
+
*/
|
|
66
|
+
export const POST: APIRoute = async ({ params, request, locals }) => {
|
|
67
|
+
const { emdash, user } = locals;
|
|
68
|
+
const { providerId } = params;
|
|
69
|
+
|
|
70
|
+
const uploadDenied = requirePerm(user, "media:upload");
|
|
71
|
+
if (uploadDenied) return uploadDenied;
|
|
72
|
+
|
|
73
|
+
if (!providerId) {
|
|
74
|
+
return apiError("INVALID_REQUEST", "Provider ID required", 400);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!emdash?.getMediaProvider) {
|
|
78
|
+
return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const provider = emdash.getMediaProvider(providerId);
|
|
82
|
+
if (!provider) {
|
|
83
|
+
return apiError("NOT_FOUND", `Provider "${providerId}" not found`, 404);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!provider.upload) {
|
|
87
|
+
return apiError("NOT_SUPPORTED", `Provider "${providerId}" does not support uploads`, 400);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const formData = await request.formData();
|
|
92
|
+
const fileEntry = formData.get("file");
|
|
93
|
+
const file = fileEntry instanceof File ? fileEntry : null;
|
|
94
|
+
const altEntry = formData.get("alt");
|
|
95
|
+
const alt = typeof altEntry === "string" ? altEntry : null;
|
|
96
|
+
|
|
97
|
+
if (!file) {
|
|
98
|
+
return apiError("NO_FILE", "No file provided", 400);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const item = await provider.upload({
|
|
102
|
+
file,
|
|
103
|
+
filename: file.name,
|
|
104
|
+
alt: alt || undefined,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return apiSuccess({ item }, 201);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
return handleError(error, "Failed to upload to provider", "PROVIDER_UPLOAD_ERROR");
|
|
110
|
+
}
|
|
111
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media Providers List Endpoint
|
|
3
|
+
*
|
|
4
|
+
* GET /_emdash/api/media/providers - List all configured media providers
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { APIRoute } from "astro";
|
|
8
|
+
|
|
9
|
+
import { requirePerm } from "#api/authorize.js";
|
|
10
|
+
import { apiError, apiSuccess } from "#api/error.js";
|
|
11
|
+
|
|
12
|
+
export const prerender = false;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* List all configured media providers
|
|
16
|
+
*/
|
|
17
|
+
export const GET: APIRoute = async ({ locals }) => {
|
|
18
|
+
const { emdash, user } = locals;
|
|
19
|
+
|
|
20
|
+
const denied = requirePerm(user, "media:read");
|
|
21
|
+
if (denied) return denied;
|
|
22
|
+
|
|
23
|
+
if (!emdash?.getMediaProviderList) {
|
|
24
|
+
return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const providers = emdash.getMediaProviderList();
|
|
28
|
+
|
|
29
|
+
return apiSuccess({ items: providers });
|
|
30
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media upload URL endpoint
|
|
3
|
+
*
|
|
4
|
+
* POST /_emdash/api/media/upload-url
|
|
5
|
+
*
|
|
6
|
+
* Returns a signed URL for direct upload to storage.
|
|
7
|
+
* Creates a pending media record that must be confirmed after upload.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import * as path from "node:path";
|
|
11
|
+
|
|
12
|
+
import type { APIRoute } from "astro";
|
|
13
|
+
import { MediaRepository } from "emdash";
|
|
14
|
+
import { ulid } from "ulidx";
|
|
15
|
+
|
|
16
|
+
import { requirePerm } from "#api/authorize.js";
|
|
17
|
+
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
18
|
+
import { isParseError, parseBody } from "#api/parse.js";
|
|
19
|
+
import { mediaUploadUrlBody } from "#api/schemas.js";
|
|
20
|
+
|
|
21
|
+
export const prerender = false;
|
|
22
|
+
|
|
23
|
+
interface UploadUrlResponse {
|
|
24
|
+
uploadUrl: string;
|
|
25
|
+
method: "PUT";
|
|
26
|
+
headers: Record<string, string>;
|
|
27
|
+
mediaId: string;
|
|
28
|
+
storageKey: string;
|
|
29
|
+
expiresAt: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Response when content already exists (deduplication) */
|
|
33
|
+
interface ExistingMediaResponse {
|
|
34
|
+
existing: true;
|
|
35
|
+
mediaId: string;
|
|
36
|
+
storageKey: string;
|
|
37
|
+
url: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get a signed upload URL for direct-to-storage upload
|
|
42
|
+
*/
|
|
43
|
+
export const POST: APIRoute = async ({ request, locals }) => {
|
|
44
|
+
const { emdash, user } = locals;
|
|
45
|
+
|
|
46
|
+
const denied = requirePerm(user, "media:upload");
|
|
47
|
+
if (denied) return denied;
|
|
48
|
+
|
|
49
|
+
if (!emdash?.storage) {
|
|
50
|
+
return apiError(
|
|
51
|
+
"NO_STORAGE",
|
|
52
|
+
"Storage not configured. Signed URL uploads require S3-compatible storage.",
|
|
53
|
+
501,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!emdash?.db) {
|
|
58
|
+
return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const body = await parseBody(request, mediaUploadUrlBody);
|
|
63
|
+
if (isParseError(body)) return body;
|
|
64
|
+
|
|
65
|
+
// Validate content type
|
|
66
|
+
const allowedTypes = ["image/", "video/", "audio/", "application/pdf"];
|
|
67
|
+
if (!allowedTypes.some((type) => body.contentType.startsWith(type))) {
|
|
68
|
+
return apiError("INVALID_TYPE", "File type not allowed", 400);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const repo = new MediaRepository(emdash.db);
|
|
72
|
+
|
|
73
|
+
// Check for existing content with same hash (deduplication)
|
|
74
|
+
if (body.contentHash) {
|
|
75
|
+
const existing = await repo.findByContentHash(body.contentHash);
|
|
76
|
+
if (existing) {
|
|
77
|
+
const response: ExistingMediaResponse = {
|
|
78
|
+
existing: true,
|
|
79
|
+
mediaId: existing.id,
|
|
80
|
+
storageKey: existing.storageKey,
|
|
81
|
+
url: `/_emdash/api/media/file/${existing.storageKey}`,
|
|
82
|
+
};
|
|
83
|
+
return apiSuccess(response);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Generate unique storage key
|
|
88
|
+
const id = ulid();
|
|
89
|
+
const ext = path.extname(body.filename) || "";
|
|
90
|
+
const storageKey = `${id}${ext}`;
|
|
91
|
+
|
|
92
|
+
// Create pending media record with content hash
|
|
93
|
+
const mediaItem = await repo.createPending({
|
|
94
|
+
filename: body.filename,
|
|
95
|
+
mimeType: body.contentType,
|
|
96
|
+
size: body.size,
|
|
97
|
+
storageKey,
|
|
98
|
+
contentHash: body.contentHash,
|
|
99
|
+
authorId: user?.id,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Get signed upload URL from storage
|
|
103
|
+
const signedUrl = await emdash.storage.getSignedUploadUrl({
|
|
104
|
+
key: storageKey,
|
|
105
|
+
contentType: body.contentType,
|
|
106
|
+
size: body.size,
|
|
107
|
+
expiresIn: 3600, // 1 hour
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const response: UploadUrlResponse = {
|
|
111
|
+
uploadUrl: signedUrl.url,
|
|
112
|
+
method: signedUrl.method,
|
|
113
|
+
headers: signedUrl.headers,
|
|
114
|
+
mediaId: mediaItem.id,
|
|
115
|
+
storageKey,
|
|
116
|
+
expiresAt: signedUrl.expiresAt,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
return apiSuccess(response);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
// Check if storage doesn't support signed URLs (e.g., local storage)
|
|
122
|
+
if (
|
|
123
|
+
error instanceof Error &&
|
|
124
|
+
"code" in error &&
|
|
125
|
+
// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- narrowing error to check custom code property after "code" in error guard
|
|
126
|
+
(error as { code: string }).code === "NOT_SUPPORTED"
|
|
127
|
+
) {
|
|
128
|
+
return apiError(
|
|
129
|
+
"NOT_SUPPORTED",
|
|
130
|
+
"Storage does not support signed upload URLs. Use direct upload.",
|
|
131
|
+
501,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return handleError(error, "Failed to generate upload URL", "UPLOAD_URL_ERROR");
|
|
136
|
+
}
|
|
137
|
+
};
|