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,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public comment endpoints
|
|
3
|
+
*
|
|
4
|
+
* GET /_emdash/api/comments/:collection/:contentId - List approved comments
|
|
5
|
+
* POST /_emdash/api/comments/:collection/:contentId - Submit a comment
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { APIRoute } from "astro";
|
|
9
|
+
|
|
10
|
+
import { apiError, apiSuccess, handleError, requireDb, unwrapResult } from "#api/error.js";
|
|
11
|
+
import { handleCommentList, checkRateLimit, hashIp } from "#api/handlers/comments.js";
|
|
12
|
+
import { isParseError, parseBody } from "#api/parse.js";
|
|
13
|
+
import { createCommentBody } from "#api/schemas.js";
|
|
14
|
+
import { getSiteBaseUrl } from "#api/site-url.js";
|
|
15
|
+
import { sendCommentNotification } from "#comments/notifications.js";
|
|
16
|
+
import { createComment, type CommentHookRunner } from "#comments/service.js";
|
|
17
|
+
import { CommentRepository } from "#db/repositories/comment.js";
|
|
18
|
+
import { extractRequestMeta } from "#plugins/request-meta.js";
|
|
19
|
+
import type { CollectionCommentSettings, ModerationDecision } from "#plugins/types.js";
|
|
20
|
+
|
|
21
|
+
export const prerender = false;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* List approved comments for a content item (public, no auth required)
|
|
25
|
+
*/
|
|
26
|
+
export const GET: APIRoute = async ({ params, url, locals }) => {
|
|
27
|
+
const { emdash } = locals;
|
|
28
|
+
const { collection, contentId } = params;
|
|
29
|
+
|
|
30
|
+
if (!collection || !contentId) {
|
|
31
|
+
return apiError("VALIDATION_ERROR", "Collection and content ID required", 400);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const dbErr = requireDb(emdash?.db);
|
|
35
|
+
if (dbErr) return dbErr;
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const limit = Math.min(Number(url.searchParams.get("limit") || 50), 100);
|
|
39
|
+
const cursor = url.searchParams.get("cursor") ?? undefined;
|
|
40
|
+
const threaded = url.searchParams.get("threaded") === "true";
|
|
41
|
+
|
|
42
|
+
// Check collection exists and has comments enabled
|
|
43
|
+
const collectionRow = await emdash.db
|
|
44
|
+
.selectFrom("_emdash_collections")
|
|
45
|
+
.select(["comments_enabled"])
|
|
46
|
+
.where("slug", "=", collection)
|
|
47
|
+
.executeTakeFirst();
|
|
48
|
+
|
|
49
|
+
if (!collectionRow) {
|
|
50
|
+
return apiError("NOT_FOUND", `Collection '${collection}' not found`, 404);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!collectionRow.comments_enabled) {
|
|
54
|
+
return apiError("COMMENTS_DISABLED", "Comments are not enabled for this collection", 403);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const result = await handleCommentList(emdash.db, collection, contentId, {
|
|
58
|
+
limit,
|
|
59
|
+
cursor,
|
|
60
|
+
threaded,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return unwrapResult(result);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
return handleError(error, "Failed to list comments", "COMMENT_LIST_ERROR");
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Submit a comment (public, gated by anti-spam checks)
|
|
71
|
+
*/
|
|
72
|
+
export const POST: APIRoute = async ({ params, request, locals }) => {
|
|
73
|
+
const { emdash, user } = locals;
|
|
74
|
+
const { collection, contentId } = params;
|
|
75
|
+
|
|
76
|
+
if (!collection || !contentId) {
|
|
77
|
+
return apiError("VALIDATION_ERROR", "Collection and content ID required", 400);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const dbErr = requireDb(emdash?.db);
|
|
81
|
+
if (dbErr) return dbErr;
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
// Parse and validate input
|
|
85
|
+
const body = await parseBody(request, createCommentBody);
|
|
86
|
+
if (isParseError(body)) return body;
|
|
87
|
+
|
|
88
|
+
// Check collection exists and has comments enabled
|
|
89
|
+
const collectionRow = await emdash.db
|
|
90
|
+
.selectFrom("_emdash_collections")
|
|
91
|
+
.select([
|
|
92
|
+
"comments_enabled",
|
|
93
|
+
"comments_moderation",
|
|
94
|
+
"comments_closed_after_days",
|
|
95
|
+
"comments_auto_approve_users",
|
|
96
|
+
])
|
|
97
|
+
.where("slug", "=", collection)
|
|
98
|
+
.executeTakeFirst();
|
|
99
|
+
|
|
100
|
+
if (!collectionRow) {
|
|
101
|
+
return apiError("NOT_FOUND", `Collection '${collection}' not found`, 404);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!collectionRow.comments_enabled) {
|
|
105
|
+
return apiError("COMMENTS_DISABLED", "Comments are not enabled for this collection", 403);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Verify the content item exists, is published, and not soft-deleted
|
|
109
|
+
const contentRow = await emdash.db
|
|
110
|
+
.selectFrom(`ec_${collection}` as never)
|
|
111
|
+
.select(["id" as never, "slug" as never, "author_id" as never, "published_at" as never])
|
|
112
|
+
.where("id" as never, "=", contentId as never)
|
|
113
|
+
.where("status" as never, "=", "published" as never)
|
|
114
|
+
.where("deleted_at" as never, "is", null as never)
|
|
115
|
+
.executeTakeFirst();
|
|
116
|
+
|
|
117
|
+
if (!contentRow) {
|
|
118
|
+
return apiError("NOT_FOUND", "Content not found", 404);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Check if comments are closed (published_at + closed_after_days)
|
|
122
|
+
if (collectionRow.comments_closed_after_days > 0) {
|
|
123
|
+
const publishedAt = (contentRow as { published_at: string | null }).published_at;
|
|
124
|
+
if (publishedAt) {
|
|
125
|
+
const closedDate = new Date(publishedAt);
|
|
126
|
+
closedDate.setDate(closedDate.getDate() + collectionRow.comments_closed_after_days);
|
|
127
|
+
if (new Date() > closedDate) {
|
|
128
|
+
return apiError("COMMENTS_CLOSED", "Comments are closed for this content", 403);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Anti-spam: Honeypot — hidden field filled only by bots
|
|
134
|
+
if (body.website_url) {
|
|
135
|
+
// Silently accept — don't reveal the honeypot to bots
|
|
136
|
+
return apiSuccess({ status: "pending", message: "Comment submitted for review" });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Anti-spam: Rate limiting
|
|
140
|
+
const meta = extractRequestMeta(request);
|
|
141
|
+
const ipSalt =
|
|
142
|
+
import.meta.env.EMDASH_AUTH_SECRET || import.meta.env.AUTH_SECRET || "emdash-ip-salt";
|
|
143
|
+
let ipHash: string;
|
|
144
|
+
if (meta.ip) {
|
|
145
|
+
ipHash = await hashIp(meta.ip, ipSalt);
|
|
146
|
+
} else if (meta.userAgent) {
|
|
147
|
+
// Fallback: hash user-agent as a rough identifier when IP is unavailable
|
|
148
|
+
ipHash = await hashIp(`ua:${meta.userAgent}`, ipSalt);
|
|
149
|
+
} else {
|
|
150
|
+
// Fail closed: all unidentifiable requests share one rate-limit bucket.
|
|
151
|
+
// Use a larger limit since this bucket is shared across all anonymous users.
|
|
152
|
+
ipHash = "unknown";
|
|
153
|
+
}
|
|
154
|
+
const unknownBucketLimit = ipHash === "unknown" ? 20 : undefined;
|
|
155
|
+
const rateLimited = await checkRateLimit(emdash.db, ipHash, unknownBucketLimit);
|
|
156
|
+
if (rateLimited) {
|
|
157
|
+
return apiError("RATE_LIMITED", "Too many comments. Please try again later.", 429);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Build collection settings
|
|
161
|
+
const collectionSettings: CollectionCommentSettings = {
|
|
162
|
+
commentsEnabled: collectionRow.comments_enabled === 1,
|
|
163
|
+
commentsModeration:
|
|
164
|
+
collectionRow.comments_moderation as CollectionCommentSettings["commentsModeration"],
|
|
165
|
+
commentsClosedAfterDays: collectionRow.comments_closed_after_days,
|
|
166
|
+
commentsAutoApproveUsers: collectionRow.comments_auto_approve_users === 1,
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// Determine author fields — authenticated user overrides form input
|
|
170
|
+
let authorName = body.authorName;
|
|
171
|
+
let authorEmail = body.authorEmail;
|
|
172
|
+
let authorUserId: string | null = null;
|
|
173
|
+
|
|
174
|
+
if (user) {
|
|
175
|
+
authorName = user.name || authorName;
|
|
176
|
+
authorEmail = user.email;
|
|
177
|
+
authorUserId = user.id;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Validate parent exists and belongs to the same content.
|
|
181
|
+
// Enforce 1-level nesting: if the parent is itself a reply, attach to its root.
|
|
182
|
+
let resolvedParentId = body.parentId ?? null;
|
|
183
|
+
if (body.parentId) {
|
|
184
|
+
const repo = new CommentRepository(emdash.db);
|
|
185
|
+
const parent = await repo.findById(body.parentId);
|
|
186
|
+
if (!parent) {
|
|
187
|
+
return apiError("VALIDATION_ERROR", "Parent comment not found", 400);
|
|
188
|
+
}
|
|
189
|
+
if (parent.collection !== collection || parent.contentId !== contentId) {
|
|
190
|
+
return apiError("VALIDATION_ERROR", "Parent comment belongs to different content", 400);
|
|
191
|
+
}
|
|
192
|
+
// Flatten: if parent is a reply, use its parent (the root) instead
|
|
193
|
+
resolvedParentId = parent.parentId ?? parent.id;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Wire the comment service to the real hook pipeline
|
|
197
|
+
const hookRunner: CommentHookRunner = {
|
|
198
|
+
async runBeforeCreate(event) {
|
|
199
|
+
return emdash.hooks.runCommentBeforeCreate(event);
|
|
200
|
+
},
|
|
201
|
+
async runModerate(event) {
|
|
202
|
+
const result = await emdash.hooks.invokeExclusiveHook("comment:moderate", event);
|
|
203
|
+
if (!result) return { status: "pending" as const, reason: "No moderator configured" };
|
|
204
|
+
if (result.error) {
|
|
205
|
+
console.error(`[comments] Moderation error (${result.pluginId}):`, result.error.message);
|
|
206
|
+
return { status: "pending" as const, reason: "Moderation error" };
|
|
207
|
+
}
|
|
208
|
+
return result.result as ModerationDecision;
|
|
209
|
+
},
|
|
210
|
+
fireAfterCreate(event) {
|
|
211
|
+
emdash.hooks
|
|
212
|
+
.runCommentAfterCreate(event)
|
|
213
|
+
.catch((err) =>
|
|
214
|
+
console.error(
|
|
215
|
+
"[comments] afterCreate error:",
|
|
216
|
+
err instanceof Error ? err.message : err,
|
|
217
|
+
),
|
|
218
|
+
);
|
|
219
|
+
},
|
|
220
|
+
fireAfterModerate(event) {
|
|
221
|
+
emdash.hooks
|
|
222
|
+
.runCommentAfterModerate(event)
|
|
223
|
+
.catch((err) =>
|
|
224
|
+
console.error(
|
|
225
|
+
"[comments] afterModerate error:",
|
|
226
|
+
err instanceof Error ? err.message : err,
|
|
227
|
+
),
|
|
228
|
+
);
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// Build content info for afterCreate hooks (e.g. email notifications)
|
|
233
|
+
const typedContent = contentRow as {
|
|
234
|
+
id: string;
|
|
235
|
+
slug: string;
|
|
236
|
+
author_id: string | null;
|
|
237
|
+
};
|
|
238
|
+
let contentAuthor: { id: string; name: string | null; email: string } | undefined;
|
|
239
|
+
if (typedContent.author_id) {
|
|
240
|
+
const authorRow = await emdash.db
|
|
241
|
+
.selectFrom("users")
|
|
242
|
+
.select(["id", "name", "email", "email_verified"])
|
|
243
|
+
.where("id", "=", typedContent.author_id)
|
|
244
|
+
.executeTakeFirst();
|
|
245
|
+
if (authorRow && authorRow.email_verified) {
|
|
246
|
+
contentAuthor = {
|
|
247
|
+
id: authorRow.id,
|
|
248
|
+
name: authorRow.name,
|
|
249
|
+
email: authorRow.email,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const result = await createComment(
|
|
255
|
+
emdash.db,
|
|
256
|
+
{
|
|
257
|
+
collection,
|
|
258
|
+
contentId,
|
|
259
|
+
parentId: resolvedParentId,
|
|
260
|
+
authorName,
|
|
261
|
+
authorEmail,
|
|
262
|
+
authorUserId,
|
|
263
|
+
body: body.body,
|
|
264
|
+
ipHash,
|
|
265
|
+
userAgent: meta.userAgent,
|
|
266
|
+
},
|
|
267
|
+
collectionSettings,
|
|
268
|
+
hookRunner,
|
|
269
|
+
{
|
|
270
|
+
id: typedContent.id,
|
|
271
|
+
collection,
|
|
272
|
+
slug: typedContent.slug,
|
|
273
|
+
author: contentAuthor,
|
|
274
|
+
},
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
if (!result) {
|
|
278
|
+
return apiError("COMMENT_REJECTED", "Comment was rejected", 403);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Send notification to content author (awaited so it completes before
|
|
282
|
+
// the response is sent — required for Cloudflare Workers where the
|
|
283
|
+
// isolate terminates after the response).
|
|
284
|
+
if (result.comment.status === "approved" && emdash.email && contentAuthor) {
|
|
285
|
+
try {
|
|
286
|
+
const adminBaseUrl = await getSiteBaseUrl(emdash.db, request);
|
|
287
|
+
await sendCommentNotification({
|
|
288
|
+
email: emdash.email,
|
|
289
|
+
comment: result.comment,
|
|
290
|
+
contentAuthor,
|
|
291
|
+
adminBaseUrl,
|
|
292
|
+
});
|
|
293
|
+
} catch (err) {
|
|
294
|
+
console.error("[comments] notification error:", err instanceof Error ? err.message : err);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return apiSuccess(
|
|
299
|
+
{
|
|
300
|
+
id: result.comment.id,
|
|
301
|
+
status: result.comment.status,
|
|
302
|
+
message:
|
|
303
|
+
result.comment.status === "approved"
|
|
304
|
+
? "Comment published"
|
|
305
|
+
: "Comment submitted for review",
|
|
306
|
+
},
|
|
307
|
+
201,
|
|
308
|
+
);
|
|
309
|
+
} catch (error) {
|
|
310
|
+
return handleError(error, "Failed to submit comment", "COMMENT_CREATE_ERROR");
|
|
311
|
+
}
|
|
312
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compare live and draft revisions
|
|
3
|
+
*
|
|
4
|
+
* GET /_emdash/api/content/{collection}/{id}/compare
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { APIRoute } from "astro";
|
|
8
|
+
|
|
9
|
+
import { requirePerm } from "#api/authorize.js";
|
|
10
|
+
import { apiError, unwrapResult } from "#api/error.js";
|
|
11
|
+
|
|
12
|
+
export const prerender = false;
|
|
13
|
+
|
|
14
|
+
export const GET: APIRoute = async ({ params, locals }) => {
|
|
15
|
+
const { emdash, user } = locals;
|
|
16
|
+
const denied = requirePerm(user, "content:read");
|
|
17
|
+
if (denied) return denied;
|
|
18
|
+
const collection = params.collection!;
|
|
19
|
+
const id = params.id!;
|
|
20
|
+
|
|
21
|
+
if (!emdash?.handleContentCompare) {
|
|
22
|
+
return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const result = await emdash.handleContentCompare(collection, id);
|
|
26
|
+
|
|
27
|
+
return unwrapResult(result);
|
|
28
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discard draft changes - reverts to live version
|
|
3
|
+
*
|
|
4
|
+
* POST /_emdash/api/content/{collection}/{id}/discard-draft
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { APIRoute } from "astro";
|
|
8
|
+
|
|
9
|
+
import { requireOwnerPerm } from "#api/authorize.js";
|
|
10
|
+
import { apiError, mapErrorStatus, unwrapResult } from "#api/error.js";
|
|
11
|
+
|
|
12
|
+
export const prerender = false;
|
|
13
|
+
|
|
14
|
+
export const POST: APIRoute = async ({ params, locals, cache }) => {
|
|
15
|
+
const { emdash, user } = locals;
|
|
16
|
+
const collection = params.collection!;
|
|
17
|
+
const id = params.id!;
|
|
18
|
+
|
|
19
|
+
if (!emdash?.handleContentDiscardDraft || !emdash?.handleContentGet) {
|
|
20
|
+
return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Fetch item to check ownership
|
|
24
|
+
const existing = await emdash.handleContentGet(collection, id);
|
|
25
|
+
if (!existing.success) {
|
|
26
|
+
return apiError(
|
|
27
|
+
existing.error?.code ?? "UNKNOWN_ERROR",
|
|
28
|
+
existing.error?.message ?? "Unknown error",
|
|
29
|
+
mapErrorStatus(existing.error?.code),
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
const existingData =
|
|
33
|
+
existing.data && typeof existing.data === "object"
|
|
34
|
+
? // eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- handler returns unknown data; narrowed by typeof check above
|
|
35
|
+
(existing.data as Record<string, unknown>)
|
|
36
|
+
: undefined;
|
|
37
|
+
// Handler returns { item, _rev } — extract the item for ownership check
|
|
38
|
+
const existingItem =
|
|
39
|
+
existingData?.item && typeof existingData.item === "object"
|
|
40
|
+
? // eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- narrowed by typeof check above
|
|
41
|
+
(existingData.item as Record<string, unknown>)
|
|
42
|
+
: existingData;
|
|
43
|
+
const authorId = typeof existingItem?.authorId === "string" ? existingItem.authorId : "";
|
|
44
|
+
const denied = requireOwnerPerm(user, authorId, "content:edit_own", "content:edit_any");
|
|
45
|
+
if (denied) return denied;
|
|
46
|
+
|
|
47
|
+
const result = await emdash.handleContentDiscardDraft(collection, id);
|
|
48
|
+
|
|
49
|
+
if (!result.success) return unwrapResult(result);
|
|
50
|
+
|
|
51
|
+
if (cache.enabled) await cache.invalidate({ tags: [collection, id] });
|
|
52
|
+
|
|
53
|
+
return unwrapResult(result);
|
|
54
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Duplicate content endpoint - injected by EmDash integration
|
|
3
|
+
*
|
|
4
|
+
* POST /_emdash/api/content/{collection}/{id}/duplicate - Create a copy
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { APIRoute } from "astro";
|
|
8
|
+
|
|
9
|
+
import { requirePerm, requireOwnerPerm } from "#api/authorize.js";
|
|
10
|
+
import { apiError, mapErrorStatus, unwrapResult } from "#api/error.js";
|
|
11
|
+
|
|
12
|
+
export const prerender = false;
|
|
13
|
+
|
|
14
|
+
export const POST: APIRoute = async ({ params, locals, cache }) => {
|
|
15
|
+
const { emdash, user } = locals;
|
|
16
|
+
const collection = params.collection!;
|
|
17
|
+
const id = params.id!;
|
|
18
|
+
|
|
19
|
+
const denied = requirePerm(user, "content:create");
|
|
20
|
+
if (denied) return denied;
|
|
21
|
+
|
|
22
|
+
if (!emdash?.handleContentDuplicate || !emdash?.handleContentGet) {
|
|
23
|
+
return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Fetch item to check ownership — duplicating requires read access to the source
|
|
27
|
+
const existing = await emdash.handleContentGet(collection, id);
|
|
28
|
+
if (!existing.success) {
|
|
29
|
+
return apiError(
|
|
30
|
+
existing.error?.code ?? "UNKNOWN_ERROR",
|
|
31
|
+
existing.error?.message ?? "Unknown error",
|
|
32
|
+
mapErrorStatus(existing.error?.code),
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const existingData =
|
|
37
|
+
existing.data && typeof existing.data === "object"
|
|
38
|
+
? // eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- handler returns unknown data; narrowed by typeof check above
|
|
39
|
+
(existing.data as Record<string, unknown>)
|
|
40
|
+
: undefined;
|
|
41
|
+
// Handler returns { item, _rev } — extract the item for ownership check
|
|
42
|
+
const existingItem =
|
|
43
|
+
existingData?.item && typeof existingData.item === "object"
|
|
44
|
+
? // eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- narrowed by typeof check above
|
|
45
|
+
(existingData.item as Record<string, unknown>)
|
|
46
|
+
: existingData;
|
|
47
|
+
const authorId = typeof existingItem?.authorId === "string" ? existingItem.authorId : "";
|
|
48
|
+
// Duplicating requires read access to the source — check ownership-based edit permissions
|
|
49
|
+
// since content:read is flat (no own/any split). This ensures authors can only duplicate their own.
|
|
50
|
+
const readDenied = requireOwnerPerm(user, authorId, "content:edit_own", "content:edit_any");
|
|
51
|
+
if (readDenied) return readDenied;
|
|
52
|
+
|
|
53
|
+
const resolvedId = typeof existingItem?.id === "string" ? existingItem.id : id;
|
|
54
|
+
const result = await emdash.handleContentDuplicate(collection, resolvedId, user?.id);
|
|
55
|
+
|
|
56
|
+
if (!result.success) return unwrapResult(result);
|
|
57
|
+
|
|
58
|
+
if (cache.enabled) await cache.invalidate({ tags: [collection] });
|
|
59
|
+
|
|
60
|
+
return unwrapResult(result, 201);
|
|
61
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permanent delete content endpoint - injected by EmDash integration
|
|
3
|
+
*
|
|
4
|
+
* DELETE /_emdash/api/content/{collection}/{id}/permanent - Permanently delete (no undo)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { APIRoute } from "astro";
|
|
8
|
+
|
|
9
|
+
import { requirePerm } from "#api/authorize.js";
|
|
10
|
+
import { apiError, unwrapResult } from "#api/error.js";
|
|
11
|
+
|
|
12
|
+
export const prerender = false;
|
|
13
|
+
|
|
14
|
+
export const DELETE: APIRoute = async ({ params, locals, cache }) => {
|
|
15
|
+
const { emdash, user } = locals;
|
|
16
|
+
const collection = params.collection!;
|
|
17
|
+
const id = params.id!;
|
|
18
|
+
|
|
19
|
+
const denied = requirePerm(user, "import:execute");
|
|
20
|
+
if (denied) return denied;
|
|
21
|
+
|
|
22
|
+
if (!emdash?.handleContentPermanentDelete) {
|
|
23
|
+
return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const result = await emdash.handleContentPermanentDelete(collection, id);
|
|
27
|
+
|
|
28
|
+
if (!result.success) return unwrapResult(result);
|
|
29
|
+
|
|
30
|
+
if (cache.enabled) await cache.invalidate({ tags: [collection, id] });
|
|
31
|
+
|
|
32
|
+
return unwrapResult(result);
|
|
33
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preview URL endpoint - generates a signed preview URL for content
|
|
3
|
+
*
|
|
4
|
+
* POST /_emdash/api/content/{collection}/{id}/preview-url
|
|
5
|
+
*
|
|
6
|
+
* Request body:
|
|
7
|
+
* {
|
|
8
|
+
* expiresIn?: string | number; // Default: "1h"
|
|
9
|
+
* pathPattern?: string; // Default: "/{collection}/{id}"
|
|
10
|
+
* }
|
|
11
|
+
*
|
|
12
|
+
* Response:
|
|
13
|
+
* {
|
|
14
|
+
* url: string; // The preview URL with token
|
|
15
|
+
* expiresAt: number; // Unix timestamp when token expires
|
|
16
|
+
* }
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { APIRoute } from "astro";
|
|
20
|
+
|
|
21
|
+
import { requirePerm } from "#api/authorize.js";
|
|
22
|
+
import { apiError, apiSuccess, handleError, unwrapResult } from "#api/error.js";
|
|
23
|
+
import { parseOptionalBody, isParseError } from "#api/parse.js";
|
|
24
|
+
import { contentPreviewUrlBody } from "#api/schemas.js";
|
|
25
|
+
import { getPreviewUrl } from "#preview/index.js";
|
|
26
|
+
|
|
27
|
+
export const prerender = false;
|
|
28
|
+
|
|
29
|
+
const DURATION_PATTERN = /^(\d+)([smhdw])$/;
|
|
30
|
+
|
|
31
|
+
export const POST: APIRoute = async ({ params, request, locals }) => {
|
|
32
|
+
const { emdash, user } = locals;
|
|
33
|
+
const denied = requirePerm(user, "content:read");
|
|
34
|
+
if (denied) return denied;
|
|
35
|
+
const collection = params.collection!;
|
|
36
|
+
const id = params.id!;
|
|
37
|
+
|
|
38
|
+
// Get the preview secret from environment
|
|
39
|
+
const previewSecret = import.meta.env.EMDASH_PREVIEW_SECRET || import.meta.env.PREVIEW_SECRET;
|
|
40
|
+
|
|
41
|
+
if (!previewSecret) {
|
|
42
|
+
return apiError(
|
|
43
|
+
"NOT_CONFIGURED",
|
|
44
|
+
"Preview not configured. Set EMDASH_PREVIEW_SECRET environment variable.",
|
|
45
|
+
500,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Verify the content exists (optional, but good for UX)
|
|
50
|
+
if (emdash?.handleContentGet) {
|
|
51
|
+
const result = await emdash.handleContentGet(collection, id);
|
|
52
|
+
if (!result.success) return unwrapResult(result);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Parse request body
|
|
56
|
+
const body = await parseOptionalBody(request, contentPreviewUrlBody, {});
|
|
57
|
+
if (isParseError(body)) return body;
|
|
58
|
+
|
|
59
|
+
const expiresIn = body.expiresIn || "1h";
|
|
60
|
+
const pathPattern = body.pathPattern;
|
|
61
|
+
|
|
62
|
+
// Calculate expiry timestamp
|
|
63
|
+
const expiresInSeconds = typeof expiresIn === "number" ? expiresIn : parseExpiresIn(expiresIn);
|
|
64
|
+
const expiresAt = Math.floor(Date.now() / 1000) + expiresInSeconds;
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const url = await getPreviewUrl({
|
|
68
|
+
collection,
|
|
69
|
+
id,
|
|
70
|
+
secret: previewSecret,
|
|
71
|
+
expiresIn,
|
|
72
|
+
pathPattern,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return apiSuccess({ url, expiresAt });
|
|
76
|
+
} catch (error) {
|
|
77
|
+
return handleError(error, "Failed to generate preview URL", "TOKEN_ERROR");
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Parse duration string to seconds
|
|
83
|
+
*/
|
|
84
|
+
function parseExpiresIn(duration: string): number {
|
|
85
|
+
const match = duration.match(DURATION_PATTERN);
|
|
86
|
+
if (!match) {
|
|
87
|
+
return 3600; // Default 1 hour
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const value = parseInt(match[1], 10);
|
|
91
|
+
const unit = match[2];
|
|
92
|
+
|
|
93
|
+
switch (unit) {
|
|
94
|
+
case "s":
|
|
95
|
+
return value;
|
|
96
|
+
case "m":
|
|
97
|
+
return value * 60;
|
|
98
|
+
case "h":
|
|
99
|
+
return value * 60 * 60;
|
|
100
|
+
case "d":
|
|
101
|
+
return value * 60 * 60 * 24;
|
|
102
|
+
case "w":
|
|
103
|
+
return value * 60 * 60 * 24 * 7;
|
|
104
|
+
default:
|
|
105
|
+
return 3600;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Publish content - promotes draft to live
|
|
3
|
+
*
|
|
4
|
+
* POST /_emdash/api/content/{collection}/{id}/publish
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { APIRoute } from "astro";
|
|
8
|
+
|
|
9
|
+
import { requireOwnerPerm } from "#api/authorize.js";
|
|
10
|
+
import { apiError, mapErrorStatus, unwrapResult } from "#api/error.js";
|
|
11
|
+
|
|
12
|
+
export const prerender = false;
|
|
13
|
+
|
|
14
|
+
export const POST: APIRoute = async ({ params, locals, cache }) => {
|
|
15
|
+
const { emdash, user } = locals;
|
|
16
|
+
const collection = params.collection!;
|
|
17
|
+
const id = params.id!;
|
|
18
|
+
|
|
19
|
+
if (!emdash?.handleContentPublish || !emdash?.handleContentGet) {
|
|
20
|
+
return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Fetch item to check ownership
|
|
24
|
+
const existing = await emdash.handleContentGet(collection, id);
|
|
25
|
+
if (!existing.success) {
|
|
26
|
+
return apiError(
|
|
27
|
+
existing.error?.code ?? "UNKNOWN_ERROR",
|
|
28
|
+
existing.error?.message ?? "Unknown error",
|
|
29
|
+
mapErrorStatus(existing.error?.code),
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const existingData =
|
|
34
|
+
existing.data && typeof existing.data === "object"
|
|
35
|
+
? // eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- handler returns unknown data; narrowed by typeof check above
|
|
36
|
+
(existing.data as Record<string, unknown>)
|
|
37
|
+
: undefined;
|
|
38
|
+
const existingItem =
|
|
39
|
+
existingData?.item && typeof existingData.item === "object"
|
|
40
|
+
? // eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- narrowed by typeof check above
|
|
41
|
+
(existingData.item as Record<string, unknown>)
|
|
42
|
+
: existingData;
|
|
43
|
+
const authorId = typeof existingItem?.authorId === "string" ? existingItem.authorId : "";
|
|
44
|
+
const denied = requireOwnerPerm(user, authorId, "content:publish_own", "content:publish_any");
|
|
45
|
+
if (denied) return denied;
|
|
46
|
+
|
|
47
|
+
const resolvedId = typeof existingItem?.id === "string" ? existingItem.id : id;
|
|
48
|
+
|
|
49
|
+
const result = await emdash.handleContentPublish(collection, resolvedId);
|
|
50
|
+
|
|
51
|
+
if (!result.success) return unwrapResult(result);
|
|
52
|
+
|
|
53
|
+
if (cache.enabled) await cache.invalidate({ tags: [collection, resolvedId] });
|
|
54
|
+
|
|
55
|
+
return unwrapResult(result);
|
|
56
|
+
};
|