emdash 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{adapters-BKSf3T9R.d.mts → adapters-DoNJiveC.d.mts} +1 -1
- package/dist/{adapters-BKSf3T9R.d.mts.map → adapters-DoNJiveC.d.mts.map} +1 -1
- package/dist/{apply-x0eMK1lX.mjs → apply-BzltprvY.mjs} +85 -135
- package/dist/apply-BzltprvY.mjs.map +1 -0
- package/dist/astro/index.d.mts +6 -6
- package/dist/astro/index.d.mts.map +1 -1
- package/dist/astro/index.mjs +110 -4
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/middleware/auth.d.mts +6 -7
- package/dist/astro/middleware/auth.d.mts.map +1 -1
- package/dist/astro/middleware/auth.mjs +16 -59
- package/dist/astro/middleware/auth.mjs.map +1 -1
- package/dist/astro/middleware/redirect.d.mts.map +1 -1
- package/dist/astro/middleware/redirect.mjs +17 -12
- package/dist/astro/middleware/redirect.mjs.map +1 -1
- package/dist/astro/middleware/request-context.d.mts.map +1 -1
- package/dist/astro/middleware/request-context.mjs +9 -6
- package/dist/astro/middleware/request-context.mjs.map +1 -1
- package/dist/astro/middleware/setup.mjs +1 -1
- package/dist/astro/middleware.d.mts.map +1 -1
- package/dist/astro/middleware.mjs +72 -124
- package/dist/astro/middleware.mjs.map +1 -1
- package/dist/astro/types.d.mts +26 -10
- package/dist/astro/types.d.mts.map +1 -1
- package/dist/{base64-MBPo9ozB.mjs → base64-BRICGH2l.mjs} +1 -1
- package/dist/{base64-MBPo9ozB.mjs.map → base64-BRICGH2l.mjs.map} +1 -1
- package/dist/{byline-Chbr2GoP.mjs → byline-BSaNL1w7.mjs} +4 -4
- package/dist/{byline-Chbr2GoP.mjs.map → byline-BSaNL1w7.mjs.map} +1 -1
- package/dist/bylines-CvJ3PYz2.mjs +113 -0
- package/dist/bylines-CvJ3PYz2.mjs.map +1 -0
- package/dist/cache-C6N_hhN7.mjs +65 -0
- package/dist/cache-C6N_hhN7.mjs.map +1 -0
- package/dist/{chunks-HGz06Soa.mjs → chunks-NBQVDOci.mjs} +8 -2
- package/dist/{chunks-HGz06Soa.mjs.map → chunks-NBQVDOci.mjs.map} +1 -1
- package/dist/cli/index.mjs +224 -30
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/cf-access.d.mts +1 -1
- package/dist/client/index.d.mts +1 -1
- package/dist/client/index.mjs +3 -3
- package/dist/client/index.mjs.map +1 -1
- package/dist/{config-BXwuX8Bx.mjs → config-BI0V3ICQ.mjs} +1 -1
- package/dist/{config-BXwuX8Bx.mjs.map → config-BI0V3ICQ.mjs.map} +1 -1
- package/dist/{content-BcQPYxdV.mjs → content-8lOYF0pr.mjs} +32 -15
- package/dist/{content-BcQPYxdV.mjs.map → content-8lOYF0pr.mjs.map} +1 -1
- package/dist/db/index.d.mts +3 -3
- package/dist/db/index.mjs +2 -2
- package/dist/db/libsql.d.mts +1 -1
- package/dist/db/libsql.d.mts.map +1 -1
- package/dist/db/libsql.mjs +7 -2
- package/dist/db/libsql.mjs.map +1 -1
- package/dist/db/postgres.d.mts +1 -1
- package/dist/db/sqlite.d.mts +1 -1
- package/dist/db/sqlite.d.mts.map +1 -1
- package/dist/db/sqlite.mjs +8 -3
- package/dist/db/sqlite.mjs.map +1 -1
- package/dist/{db-errors-l1Qh2RPR.mjs → db-errors-WRezodiz.mjs} +1 -1
- package/dist/{db-errors-l1Qh2RPR.mjs.map → db-errors-WRezodiz.mjs.map} +1 -1
- package/dist/{default-DCVqE5ib.mjs → default-D8ksjWhO.mjs} +1 -1
- package/dist/{default-DCVqE5ib.mjs.map → default-D8ksjWhO.mjs.map} +1 -1
- package/dist/{dialect-helpers-DhTzaUxP.mjs → dialect-helpers-BKCvISIQ.mjs} +19 -2
- package/dist/dialect-helpers-BKCvISIQ.mjs.map +1 -0
- package/dist/{error-zG5T1UGA.mjs → error-D_-tqP-I.mjs} +1 -1
- package/dist/{error-zG5T1UGA.mjs.map → error-D_-tqP-I.mjs.map} +1 -1
- package/dist/{index-DIb-CzNx.d.mts → index-BFRaVcD6.d.mts} +94 -34
- package/dist/index-BFRaVcD6.d.mts.map +1 -0
- package/dist/index.d.mts +11 -11
- package/dist/index.mjs +29 -27
- package/dist/{load-CyEoextb.mjs → load-DDqMMvZL.mjs} +2 -2
- package/dist/{load-CyEoextb.mjs.map → load-DDqMMvZL.mjs.map} +1 -1
- package/dist/{loader-CndGj8kM.mjs → loader-CKLbBnhK.mjs} +27 -7
- package/dist/loader-CKLbBnhK.mjs.map +1 -0
- package/dist/{manifest-schema-DH9xhc6t.mjs → manifest-schema-DqWNC3lM.mjs} +33 -3
- package/dist/manifest-schema-DqWNC3lM.mjs.map +1 -0
- package/dist/media/index.d.mts +1 -1
- package/dist/media/index.mjs +1 -1
- package/dist/media/local-runtime.d.mts +7 -7
- package/dist/media/local-runtime.mjs +3 -3
- package/dist/{media-D8FbNsl0.mjs → media-BW32b4gi.mjs} +2 -2
- package/dist/{media-D8FbNsl0.mjs.map → media-BW32b4gi.mjs.map} +1 -1
- package/dist/{mode-BnAOqItE.mjs → mode-ier8jbBk.mjs} +1 -1
- package/dist/{mode-BnAOqItE.mjs.map → mode-ier8jbBk.mjs.map} +1 -1
- package/dist/options-BVp3UsTS.mjs +117 -0
- package/dist/options-BVp3UsTS.mjs.map +1 -0
- package/dist/page/index.d.mts +2 -2
- package/dist/{placeholder-D29tWZ7o.d.mts → placeholder-BE4o_2dc.d.mts} +1 -1
- package/dist/{placeholder-D29tWZ7o.d.mts.map → placeholder-BE4o_2dc.d.mts.map} +1 -1
- package/dist/{placeholder-C-fk5hYI.mjs → placeholder-CIJejMlK.mjs} +1 -1
- package/dist/{placeholder-C-fk5hYI.mjs.map → placeholder-CIJejMlK.mjs.map} +1 -1
- package/dist/plugins/adapt-sandbox-entry.d.mts +5 -5
- package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -1
- package/dist/plugins/adapt-sandbox-entry.mjs +6 -5
- package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -1
- package/dist/public-url-DByxYjUw.mjs +51 -0
- package/dist/public-url-DByxYjUw.mjs.map +1 -0
- package/dist/{query-fqEdLFms.mjs → query-Cg9ZKRQ0.mjs} +114 -16
- package/dist/query-Cg9ZKRQ0.mjs.map +1 -0
- package/dist/{redirect-D_pshWdf.mjs → redirect-BhUBKRc1.mjs} +11 -6
- package/dist/redirect-BhUBKRc1.mjs.map +1 -0
- package/dist/{registry-C3Mr0ODu.mjs → registry-Dw70ChxB.mjs} +38 -4
- package/dist/registry-Dw70ChxB.mjs.map +1 -0
- package/dist/{request-cache-Ci7f5pBb.mjs → request-cache-B-bmkipQ.mjs} +1 -1
- package/dist/{request-cache-Ci7f5pBb.mjs.map → request-cache-B-bmkipQ.mjs.map} +1 -1
- package/dist/runner-Bnoj7vjK.d.mts +44 -0
- package/dist/runner-Bnoj7vjK.d.mts.map +1 -0
- package/dist/{runner-tQ7BJ4T7.mjs → runner-C7ADox5q.mjs} +185 -55
- package/dist/{runner-tQ7BJ4T7.mjs.map → runner-C7ADox5q.mjs.map} +1 -1
- package/dist/runtime.d.mts +6 -6
- package/dist/runtime.mjs +4 -4
- package/dist/{search-BoZYFuUk.mjs → search-dOGEccMa.mjs} +129 -83
- package/dist/search-dOGEccMa.mjs.map +1 -0
- package/dist/secrets-CW3reAnU.mjs +314 -0
- package/dist/secrets-CW3reAnU.mjs.map +1 -0
- package/dist/seed/index.d.mts +2 -2
- package/dist/seed/index.mjs +15 -14
- package/dist/seo/index.d.mts +1 -1
- package/dist/storage/local.d.mts +1 -1
- package/dist/storage/local.mjs +1 -1
- package/dist/storage/s3.d.mts +1 -1
- package/dist/storage/s3.mjs +1 -1
- package/dist/{taxonomies-B4IAshV8.mjs → taxonomies-ZlRtD6AG.mjs} +14 -7
- package/dist/taxonomies-ZlRtD6AG.mjs.map +1 -0
- package/dist/{tokens-D9vnZqYS.mjs → tokens-D7zMmWi2.mjs} +2 -2
- package/dist/{tokens-D9vnZqYS.mjs.map → tokens-D7zMmWi2.mjs.map} +1 -1
- package/dist/{transport-C9ugt2Nr.mjs → transport-BeMCmin1.mjs} +6 -5
- package/dist/{transport-C9ugt2Nr.mjs.map → transport-BeMCmin1.mjs.map} +1 -1
- package/dist/{transport-CUnEL3Vs.d.mts → transport-DNEfeMaU.d.mts} +1 -1
- package/dist/{transport-CUnEL3Vs.d.mts.map → transport-DNEfeMaU.d.mts.map} +1 -1
- package/dist/types-4fVtCIm0.mjs +68 -0
- package/dist/types-4fVtCIm0.mjs.map +1 -0
- package/dist/{types-BmPPSUEx.d.mts → types-BSyXeCFW.d.mts} +24 -2
- package/dist/{types-BmPPSUEx.d.mts.map → types-BSyXeCFW.d.mts.map} +1 -1
- package/dist/{types-i36XcA_X.d.mts → types-BuBIptGk.d.mts} +65 -134
- package/dist/types-BuBIptGk.d.mts.map +1 -0
- package/dist/{types-CgqmmMJB.mjs → types-CDbKp7ND.mjs} +1 -1
- package/dist/{types-CgqmmMJB.mjs.map → types-CDbKp7ND.mjs.map} +1 -1
- package/dist/{types-Bm1dn-q3.mjs → types-CIOg5AR8.mjs} +1 -1
- package/dist/{types-Bm1dn-q3.mjs.map → types-CIOg5AR8.mjs.map} +1 -1
- package/dist/{types-BrA0xf5I.d.mts → types-CJsYGpco.d.mts} +1 -1
- package/dist/{types-BrA0xf5I.d.mts.map → types-CJsYGpco.d.mts.map} +1 -1
- package/dist/{types-BIgulNsW.mjs → types-CRxNbK-Z.mjs} +2 -2
- package/dist/{types-BIgulNsW.mjs.map → types-CRxNbK-Z.mjs.map} +1 -1
- package/dist/{types-CS8FIX7L.d.mts → types-CrtWgIvl.d.mts} +1 -1
- package/dist/{types-CS8FIX7L.d.mts.map → types-CrtWgIvl.d.mts.map} +1 -1
- package/dist/{types-DIMwPFub.d.mts → types-M78DQ1lx.d.mts} +1 -1
- package/dist/{types-DIMwPFub.d.mts.map → types-M78DQ1lx.d.mts.map} +1 -1
- package/dist/{validate-CxVsLehf.mjs → validate-Baqf0slj.mjs} +3 -3
- package/dist/{validate-CxVsLehf.mjs.map → validate-Baqf0slj.mjs.map} +1 -1
- package/dist/{validate-DHxmpFJt.d.mts → validate-BfQh_C_y.d.mts} +4 -4
- package/dist/{validate-DHxmpFJt.d.mts.map → validate-BfQh_C_y.d.mts.map} +1 -1
- package/dist/{validation-C-ZpN2GI.mjs → validation-BfEI7tNe.mjs} +6 -6
- package/dist/{validation-C-ZpN2GI.mjs.map → validation-BfEI7tNe.mjs.map} +1 -1
- package/dist/version-DoxrVdYf.mjs +7 -0
- package/dist/{version-Bbq8TCrz.mjs.map → version-DoxrVdYf.mjs.map} +1 -1
- package/dist/{zod-generator-CpwccCIv.mjs → zod-generator-CC0xNe_K.mjs} +4 -4
- package/dist/zod-generator-CC0xNe_K.mjs.map +1 -0
- package/locals.d.ts +1 -6
- package/package.json +9 -8
- package/src/api/handlers/comments.ts +6 -4
- package/src/api/handlers/content.ts +29 -1
- package/src/api/handlers/device-flow.ts +5 -0
- package/src/api/handlers/marketplace.ts +11 -4
- package/src/api/handlers/oauth-authorization.ts +72 -33
- package/src/api/handlers/revision.ts +23 -14
- package/src/api/handlers/taxonomies.ts +3 -6
- package/src/api/public-url.ts +48 -2
- package/src/api/schemas/comments.ts +2 -2
- package/src/api/schemas/content.ts +17 -0
- package/src/api/schemas/sections.ts +3 -3
- package/src/api/schemas/users.ts +1 -1
- package/src/api/types.ts +5 -1
- package/src/astro/integration/index.ts +17 -0
- package/src/astro/integration/runtime.ts +30 -0
- package/src/astro/integration/virtual-modules.ts +32 -2
- package/src/astro/integration/vite-config.ts +6 -1
- package/src/astro/middleware/auth.ts +13 -6
- package/src/astro/middleware/redirect.ts +29 -16
- package/src/astro/middleware/request-context.ts +15 -5
- package/src/astro/middleware.ts +23 -9
- package/src/astro/routes/api/auth/invite/complete.ts +6 -1
- package/src/astro/routes/api/auth/passkey/register/verify.ts +6 -1
- package/src/astro/routes/api/auth/passkey/verify.ts +6 -1
- package/src/astro/routes/api/auth/signup/complete.ts +6 -1
- package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +2 -2
- package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +4 -2
- package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +34 -12
- package/src/astro/routes/api/content/[collection]/[id]/publish.ts +32 -2
- package/src/astro/routes/api/content/[collection]/[id]/restore.ts +4 -2
- package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +3 -2
- package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +8 -4
- package/src/astro/routes/api/content/[collection]/[id].ts +12 -0
- package/src/astro/routes/api/import/wordpress/execute.ts +3 -1
- package/src/astro/routes/api/import/wordpress/prepare.ts +7 -8
- package/src/astro/routes/api/import/wordpress-plugin/execute.ts +3 -1
- package/src/astro/routes/api/manifest.ts +62 -45
- package/src/astro/routes/api/media/[id]/confirm.ts +10 -1
- package/src/astro/routes/api/media/providers/[providerId]/index.ts +12 -3
- package/src/astro/routes/api/openapi.json.ts +27 -10
- package/src/astro/routes/api/redirects/404s/index.ts +10 -4
- package/src/astro/routes/api/redirects/404s/summary.ts +4 -2
- package/src/astro/routes/api/redirects/[id].ts +10 -4
- package/src/astro/routes/api/redirects/index.ts +7 -3
- package/src/astro/routes/api/revisions/[revisionId]/index.ts +1 -1
- package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +0 -2
- package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +0 -1
- package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +0 -1
- package/src/astro/routes/api/schema/collections/[slug]/index.ts +2 -2
- package/src/astro/routes/api/schema/collections/index.ts +1 -1
- package/src/astro/routes/api/search/index.ts +10 -2
- package/src/astro/routes/api/sections/[slug].ts +10 -4
- package/src/astro/routes/api/sections/index.ts +7 -3
- package/src/astro/routes/api/setup/admin-verify.ts +6 -1
- package/src/astro/routes/api/snapshot.ts +44 -18
- package/src/astro/routes/api/taxonomies/index.ts +0 -1
- package/src/astro/routes/api/themes/preview.ts +11 -5
- package/src/astro/types.ts +23 -3
- package/src/auth/allowed-origins.ts +168 -0
- package/src/auth/passkey-config.ts +35 -13
- package/src/bylines/index.ts +37 -88
- package/src/cli/commands/auth.ts +28 -6
- package/src/cli/commands/bundle-utils.ts +11 -2
- package/src/cli/commands/bundle.ts +28 -8
- package/src/cli/commands/content.ts +13 -0
- package/src/cli/commands/login.ts +8 -1
- package/src/cli/commands/publish.ts +24 -0
- package/src/cli/commands/secrets.ts +183 -0
- package/src/cli/credentials.ts +1 -1
- package/src/cli/index.ts +5 -1
- package/src/client/index.ts +4 -4
- package/src/client/transport.ts +17 -7
- package/src/components/Break.astro +2 -2
- package/src/components/EmDashHead.astro +18 -13
- package/src/components/Embed.astro +1 -1
- package/src/components/Gallery.astro +1 -1
- package/src/components/Image.astro +1 -1
- package/src/components/InlinePortableTextEditor.tsx +104 -18
- package/src/config/secrets.ts +528 -0
- package/src/database/dialect-helpers.ts +50 -0
- package/src/database/migrations/034_published_at_index.ts +1 -1
- package/src/database/migrations/035_bounded_404_log.ts +56 -39
- package/src/database/migrations/runner.ts +156 -23
- package/src/database/repositories/content.ts +36 -12
- package/src/database/repositories/redirect.ts +14 -3
- package/src/database/repositories/taxonomy.ts +26 -0
- package/src/db/libsql.ts +1 -3
- package/src/db/sqlite.ts +2 -5
- package/src/emdash-runtime.ts +84 -159
- package/src/index.ts +9 -0
- package/src/loader.ts +24 -1
- package/src/mcp/server.ts +103 -36
- package/src/page/site-identity.ts +58 -0
- package/src/plugins/adapt-sandbox-entry.ts +22 -10
- package/src/plugins/context.ts +13 -10
- package/src/plugins/define-plugin.ts +40 -12
- package/src/plugins/hooks.ts +23 -19
- package/src/plugins/index.ts +9 -0
- package/src/plugins/manifest-schema.ts +37 -2
- package/src/plugins/types.ts +151 -11
- package/src/preview/urls.ts +23 -3
- package/src/query.ts +148 -5
- package/src/redirects/cache.ts +38 -18
- package/src/schema/registry.ts +56 -0
- package/src/schema/zod-generator.ts +27 -5
- package/src/seed/apply.ts +2 -0
- package/src/settings/index.ts +80 -6
- package/src/settings/types.ts +23 -1
- package/src/taxonomies/index.ts +11 -1
- package/dist/apply-x0eMK1lX.mjs.map +0 -1
- package/dist/bylines-CRNsVG88.mjs +0 -157
- package/dist/bylines-CRNsVG88.mjs.map +0 -1
- package/dist/cache-BkKBuIvS.mjs +0 -56
- package/dist/cache-BkKBuIvS.mjs.map +0 -1
- package/dist/chunk-ClPoSABd.mjs +0 -21
- package/dist/dialect-helpers-DhTzaUxP.mjs.map +0 -1
- package/dist/index-DIb-CzNx.d.mts.map +0 -1
- package/dist/loader-CndGj8kM.mjs.map +0 -1
- package/dist/manifest-schema-DH9xhc6t.mjs.map +0 -1
- package/dist/query-fqEdLFms.mjs.map +0 -1
- package/dist/redirect-D_pshWdf.mjs.map +0 -1
- package/dist/registry-C3Mr0ODu.mjs.map +0 -1
- package/dist/runner-OURCaApa.d.mts +0 -34
- package/dist/runner-OURCaApa.d.mts.map +0 -1
- package/dist/search-BoZYFuUk.mjs.map +0 -1
- package/dist/taxonomies-B4IAshV8.mjs.map +0 -1
- package/dist/types-i36XcA_X.d.mts.map +0 -1
- package/dist/version-Bbq8TCrz.mjs +0 -7
- package/dist/zod-generator-CpwccCIv.mjs.map +0 -1
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import type { APIRoute } from "astro";
|
|
11
11
|
import { MediaRepository } from "emdash";
|
|
12
12
|
|
|
13
|
-
import { requirePerm } from "#api/authorize.js";
|
|
13
|
+
import { requireOwnerPerm, requirePerm } from "#api/authorize.js";
|
|
14
14
|
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
15
15
|
import { isParseError, parseOptionalBody } from "#api/parse.js";
|
|
16
16
|
import { mediaConfirmBody } from "#api/schemas.js";
|
|
@@ -62,6 +62,15 @@ export const POST: APIRoute = async ({ params, request, locals }) => {
|
|
|
62
62
|
return apiError("INVALID_STATE", `Media item is not pending: ${existing.status}`, 400);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
// Only the uploader or a user with media:edit_any can confirm/fail a pending upload
|
|
66
|
+
const ownerDenied = requireOwnerPerm(
|
|
67
|
+
user,
|
|
68
|
+
existing.authorId ?? "",
|
|
69
|
+
"media:edit_own",
|
|
70
|
+
"media:edit_any",
|
|
71
|
+
);
|
|
72
|
+
if (ownerDenied) return ownerDenied;
|
|
73
|
+
|
|
65
74
|
// Optionally verify the file exists in storage
|
|
66
75
|
if (emdash.storage) {
|
|
67
76
|
const exists = await emdash.storage.exists(existing.storageKey);
|
|
@@ -37,9 +37,8 @@ export const GET: APIRoute = async ({ params, request, locals }) => {
|
|
|
37
37
|
|
|
38
38
|
const url = new URL(request.url);
|
|
39
39
|
const cursor = url.searchParams.get("cursor") || undefined;
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
: undefined;
|
|
40
|
+
const rawLimit = url.searchParams.get("limit");
|
|
41
|
+
const limit = rawLimit ? Math.max(1, Math.min(parseInt(rawLimit, 10) || 50, 100)) : undefined;
|
|
43
42
|
const query = url.searchParams.get("query") || undefined;
|
|
44
43
|
const mimeType = url.searchParams.get("mimeType") || undefined;
|
|
45
44
|
|
|
@@ -98,6 +97,16 @@ export const POST: APIRoute = async ({ params, request, locals }) => {
|
|
|
98
97
|
return apiError("NO_FILE", "No file provided", 400);
|
|
99
98
|
}
|
|
100
99
|
|
|
100
|
+
// Basic size validation (default 50MB, configurable via maxUploadSize)
|
|
101
|
+
const maxSize = emdash.config?.maxUploadSize ?? 50 * 1024 * 1024;
|
|
102
|
+
if (file.size > maxSize) {
|
|
103
|
+
return apiError(
|
|
104
|
+
"FILE_TOO_LARGE",
|
|
105
|
+
`File exceeds maximum size of ${Math.round(maxSize / 1024 / 1024)}MB`,
|
|
106
|
+
413,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
101
110
|
const item = await provider.upload({
|
|
102
111
|
file,
|
|
103
112
|
filename: file.name,
|
|
@@ -9,33 +9,50 @@
|
|
|
9
9
|
|
|
10
10
|
import type { APIRoute } from "astro";
|
|
11
11
|
|
|
12
|
+
import { handleError } from "../../../api/error.js";
|
|
12
13
|
import { generateOpenApiDocument } from "../../../api/openapi/index.js";
|
|
13
14
|
|
|
14
15
|
export const prerender = false;
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
// Use globalThis with Symbol.for to survive Vite's SSR module duplication
|
|
18
|
+
const OPENAPI_CACHE_KEY = Symbol.for("emdash.openapi.cachedSpec");
|
|
19
|
+
|
|
20
|
+
function getCachedSpec(): string | null {
|
|
21
|
+
const val = (globalThis as Record<symbol, unknown>)[OPENAPI_CACHE_KEY];
|
|
22
|
+
return typeof val === "string" ? val : null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function setCachedSpec(spec: string): void {
|
|
26
|
+
(globalThis as Record<symbol, unknown>)[OPENAPI_CACHE_KEY] = spec;
|
|
27
|
+
}
|
|
17
28
|
|
|
18
29
|
export const GET: APIRoute = async ({ locals }) => {
|
|
19
30
|
const { emdash } = locals;
|
|
20
|
-
|
|
31
|
+
|
|
32
|
+
let spec = getCachedSpec();
|
|
33
|
+
if (!spec && emdash) {
|
|
21
34
|
try {
|
|
22
35
|
const doc = generateOpenApiDocument({ maxUploadSize: emdash.config.maxUploadSize });
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
{ status: 500, headers: { "Content-Type": "application/json" } },
|
|
28
|
-
);
|
|
36
|
+
spec = JSON.stringify(doc);
|
|
37
|
+
setCachedSpec(spec);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
return handleError(error, "Failed to generate OpenAPI document", "OPENAPI_ERROR");
|
|
29
40
|
}
|
|
30
41
|
}
|
|
31
42
|
|
|
32
|
-
|
|
43
|
+
if (!spec) {
|
|
44
|
+
try {
|
|
45
|
+
spec = JSON.stringify(generateOpenApiDocument());
|
|
46
|
+
} catch (error) {
|
|
47
|
+
return handleError(error, "Failed to generate OpenAPI document", "OPENAPI_ERROR");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
33
50
|
|
|
34
51
|
return new Response(spec, {
|
|
35
52
|
status: 200,
|
|
36
53
|
headers: {
|
|
37
54
|
"Content-Type": "application/json",
|
|
38
|
-
"Cache-Control": "
|
|
55
|
+
"Cache-Control": "private, no-store",
|
|
39
56
|
"Access-Control-Allow-Origin": "*",
|
|
40
57
|
},
|
|
41
58
|
});
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import type { APIRoute } from "astro";
|
|
10
10
|
|
|
11
11
|
import { requirePerm } from "#api/authorize.js";
|
|
12
|
-
import { handleError, unwrapResult } from "#api/error.js";
|
|
12
|
+
import { handleError, requireDb, unwrapResult } from "#api/error.js";
|
|
13
13
|
import {
|
|
14
14
|
handleNotFoundClear,
|
|
15
15
|
handleNotFoundList,
|
|
@@ -22,7 +22,9 @@ export const prerender = false;
|
|
|
22
22
|
|
|
23
23
|
export const GET: APIRoute = async ({ url, locals }) => {
|
|
24
24
|
const { emdash, user } = locals;
|
|
25
|
-
const
|
|
25
|
+
const dbErr = requireDb(emdash?.db);
|
|
26
|
+
if (dbErr) return dbErr;
|
|
27
|
+
const db = emdash!.db;
|
|
26
28
|
|
|
27
29
|
const denied = requirePerm(user, "redirects:read");
|
|
28
30
|
if (denied) return denied;
|
|
@@ -40,7 +42,9 @@ export const GET: APIRoute = async ({ url, locals }) => {
|
|
|
40
42
|
|
|
41
43
|
export const DELETE: APIRoute = async ({ locals }) => {
|
|
42
44
|
const { emdash, user } = locals;
|
|
43
|
-
const
|
|
45
|
+
const dbErr = requireDb(emdash?.db);
|
|
46
|
+
if (dbErr) return dbErr;
|
|
47
|
+
const db = emdash!.db;
|
|
44
48
|
|
|
45
49
|
const denied = requirePerm(user, "redirects:manage");
|
|
46
50
|
if (denied) return denied;
|
|
@@ -55,7 +59,9 @@ export const DELETE: APIRoute = async ({ locals }) => {
|
|
|
55
59
|
|
|
56
60
|
export const POST: APIRoute = async ({ request, locals }) => {
|
|
57
61
|
const { emdash, user } = locals;
|
|
58
|
-
const
|
|
62
|
+
const dbErr = requireDb(emdash?.db);
|
|
63
|
+
if (dbErr) return dbErr;
|
|
64
|
+
const db = emdash!.db;
|
|
59
65
|
|
|
60
66
|
const denied = requirePerm(user, "redirects:manage");
|
|
61
67
|
if (denied) return denied;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import type { APIRoute } from "astro";
|
|
8
8
|
|
|
9
9
|
import { requirePerm } from "#api/authorize.js";
|
|
10
|
-
import { handleError, unwrapResult } from "#api/error.js";
|
|
10
|
+
import { handleError, requireDb, unwrapResult } from "#api/error.js";
|
|
11
11
|
import { handleNotFoundSummary } from "#api/handlers/redirects.js";
|
|
12
12
|
import { isParseError, parseQuery } from "#api/parse.js";
|
|
13
13
|
import { notFoundSummaryQuery } from "#api/schemas.js";
|
|
@@ -16,7 +16,9 @@ export const prerender = false;
|
|
|
16
16
|
|
|
17
17
|
export const GET: APIRoute = async ({ url, locals }) => {
|
|
18
18
|
const { emdash, user } = locals;
|
|
19
|
-
const
|
|
19
|
+
const dbErr = requireDb(emdash?.db);
|
|
20
|
+
if (dbErr) return dbErr;
|
|
21
|
+
const db = emdash!.db;
|
|
20
22
|
|
|
21
23
|
const denied = requirePerm(user, "redirects:read");
|
|
22
24
|
if (denied) return denied;
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import type { APIRoute } from "astro";
|
|
10
10
|
|
|
11
11
|
import { requirePerm } from "#api/authorize.js";
|
|
12
|
-
import { apiError, handleError, unwrapResult } from "#api/error.js";
|
|
12
|
+
import { apiError, handleError, requireDb, unwrapResult } from "#api/error.js";
|
|
13
13
|
import {
|
|
14
14
|
handleRedirectDelete,
|
|
15
15
|
handleRedirectGet,
|
|
@@ -23,7 +23,9 @@ export const prerender = false;
|
|
|
23
23
|
|
|
24
24
|
export const GET: APIRoute = async ({ params, locals }) => {
|
|
25
25
|
const { emdash, user } = locals;
|
|
26
|
-
const
|
|
26
|
+
const dbErr = requireDb(emdash?.db);
|
|
27
|
+
if (dbErr) return dbErr;
|
|
28
|
+
const db = emdash!.db;
|
|
27
29
|
const { id } = params;
|
|
28
30
|
|
|
29
31
|
const denied = requirePerm(user, "redirects:read");
|
|
@@ -43,7 +45,9 @@ export const GET: APIRoute = async ({ params, locals }) => {
|
|
|
43
45
|
|
|
44
46
|
export const PUT: APIRoute = async ({ params, request, locals }) => {
|
|
45
47
|
const { emdash, user } = locals;
|
|
46
|
-
const
|
|
48
|
+
const dbErr = requireDb(emdash?.db);
|
|
49
|
+
if (dbErr) return dbErr;
|
|
50
|
+
const db = emdash!.db;
|
|
47
51
|
const { id } = params;
|
|
48
52
|
|
|
49
53
|
const denied = requirePerm(user, "redirects:manage");
|
|
@@ -67,7 +71,9 @@ export const PUT: APIRoute = async ({ params, request, locals }) => {
|
|
|
67
71
|
|
|
68
72
|
export const DELETE: APIRoute = async ({ params, locals }) => {
|
|
69
73
|
const { emdash, user } = locals;
|
|
70
|
-
const
|
|
74
|
+
const dbErr = requireDb(emdash?.db);
|
|
75
|
+
if (dbErr) return dbErr;
|
|
76
|
+
const db = emdash!.db;
|
|
71
77
|
const { id } = params;
|
|
72
78
|
|
|
73
79
|
const denied = requirePerm(user, "redirects:manage");
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import type { APIRoute } from "astro";
|
|
9
9
|
|
|
10
10
|
import { requirePerm } from "#api/authorize.js";
|
|
11
|
-
import { handleError, unwrapResult } from "#api/error.js";
|
|
11
|
+
import { handleError, requireDb, unwrapResult } from "#api/error.js";
|
|
12
12
|
import { handleRedirectCreate, handleRedirectList } from "#api/handlers/redirects.js";
|
|
13
13
|
import { isParseError, parseBody, parseQuery } from "#api/parse.js";
|
|
14
14
|
import { createRedirectBody, redirectsListQuery } from "#api/schemas.js";
|
|
@@ -18,7 +18,9 @@ export const prerender = false;
|
|
|
18
18
|
|
|
19
19
|
export const GET: APIRoute = async ({ url, locals }) => {
|
|
20
20
|
const { emdash, user } = locals;
|
|
21
|
-
const
|
|
21
|
+
const dbErr = requireDb(emdash?.db);
|
|
22
|
+
if (dbErr) return dbErr;
|
|
23
|
+
const db = emdash!.db;
|
|
22
24
|
|
|
23
25
|
const denied = requirePerm(user, "redirects:read");
|
|
24
26
|
if (denied) return denied;
|
|
@@ -36,7 +38,9 @@ export const GET: APIRoute = async ({ url, locals }) => {
|
|
|
36
38
|
|
|
37
39
|
export const POST: APIRoute = async ({ request, locals }) => {
|
|
38
40
|
const { emdash, user } = locals;
|
|
39
|
-
const
|
|
41
|
+
const dbErr = requireDb(emdash?.db);
|
|
42
|
+
if (dbErr) return dbErr;
|
|
43
|
+
const db = emdash!.db;
|
|
40
44
|
|
|
41
45
|
const denied = requirePerm(user, "redirects:manage");
|
|
42
46
|
if (denied) return denied;
|
|
@@ -16,7 +16,7 @@ export const GET: APIRoute = async ({ params, locals }) => {
|
|
|
16
16
|
const { emdash, user } = locals;
|
|
17
17
|
const revisionId = params.revisionId!;
|
|
18
18
|
|
|
19
|
-
const denied = requirePerm(user, "content:
|
|
19
|
+
const denied = requirePerm(user, "content:read_drafts");
|
|
20
20
|
if (denied) return denied;
|
|
21
21
|
|
|
22
22
|
if (!emdash?.handleRevisionGet) {
|
|
@@ -57,7 +57,6 @@ export const PUT: APIRoute = async ({ params, request, locals }) => {
|
|
|
57
57
|
fieldSlug,
|
|
58
58
|
body as UpdateFieldInput,
|
|
59
59
|
);
|
|
60
|
-
if (result.success) emdash!.invalidateManifest();
|
|
61
60
|
return unwrapResult(result);
|
|
62
61
|
};
|
|
63
62
|
|
|
@@ -73,6 +72,5 @@ export const DELETE: APIRoute = async ({ params, locals }) => {
|
|
|
73
72
|
if (denied) return denied;
|
|
74
73
|
|
|
75
74
|
const result = await handleSchemaFieldDelete(emdash!.db, collectionSlug, fieldSlug);
|
|
76
|
-
if (result.success) emdash!.invalidateManifest();
|
|
77
75
|
return unwrapResult(result);
|
|
78
76
|
};
|
|
@@ -28,6 +28,5 @@ export const POST: APIRoute = async ({ params, request, locals }) => {
|
|
|
28
28
|
if (isParseError(body)) return body;
|
|
29
29
|
|
|
30
30
|
const result = await handleSchemaFieldReorder(emdash!.db, collectionSlug, body.fieldSlugs);
|
|
31
|
-
if (result.success) emdash!.invalidateManifest();
|
|
32
31
|
return unwrapResult(result);
|
|
33
32
|
};
|
|
@@ -59,7 +59,7 @@ export const PUT: APIRoute = async ({ params, request, locals }) => {
|
|
|
59
59
|
// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- parseBody validates via Zod
|
|
60
60
|
body as UpdateCollectionInput,
|
|
61
61
|
);
|
|
62
|
-
emdash!.
|
|
62
|
+
emdash!.invalidateUrlPatternCache();
|
|
63
63
|
return unwrapResult(result);
|
|
64
64
|
};
|
|
65
65
|
|
|
@@ -77,6 +77,6 @@ export const DELETE: APIRoute = async ({ params, url, locals }) => {
|
|
|
77
77
|
const result = await handleSchemaCollectionDelete(emdash!.db, slug, {
|
|
78
78
|
force,
|
|
79
79
|
});
|
|
80
|
-
emdash!.
|
|
80
|
+
emdash!.invalidateUrlPatternCache();
|
|
81
81
|
return unwrapResult(result);
|
|
82
82
|
};
|
|
@@ -43,6 +43,6 @@ export const POST: APIRoute = async ({ request, locals }) => {
|
|
|
43
43
|
|
|
44
44
|
// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- Zod schema output narrowed to CreateCollectionInput
|
|
45
45
|
const result = await handleSchemaCollectionCreate(emdash!.db, body as CreateCollectionInput);
|
|
46
|
-
emdash!.
|
|
46
|
+
emdash!.invalidateUrlPatternCache();
|
|
47
47
|
return unwrapResult(result, 201);
|
|
48
48
|
};
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* GET /_emdash/api/search?q=query&collections=posts,pages&limit=20
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { hasPermission } from "@emdash-cms/auth";
|
|
7
8
|
import type { APIRoute } from "astro";
|
|
8
9
|
|
|
9
10
|
import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
@@ -23,7 +24,7 @@ export const prerender = false;
|
|
|
23
24
|
* - limit: Maximum results (optional, defaults to 20)
|
|
24
25
|
*/
|
|
25
26
|
export const GET: APIRoute = async ({ url, locals }) => {
|
|
26
|
-
const { emdash } = locals;
|
|
27
|
+
const { emdash, user } = locals;
|
|
27
28
|
|
|
28
29
|
if (!emdash?.db) {
|
|
29
30
|
return apiError("NOT_CONFIGURED", "EmDash not configured", 500);
|
|
@@ -36,6 +37,13 @@ export const GET: APIRoute = async ({ url, locals }) => {
|
|
|
36
37
|
? query.collections.split(",").map((c: string) => c.trim())
|
|
37
38
|
: undefined;
|
|
38
39
|
|
|
40
|
+
// Only users with content:read_drafts may search non-published statuses.
|
|
41
|
+
// Anonymous and subscriber requests are forced to "published".
|
|
42
|
+
const status =
|
|
43
|
+
query.status && query.status !== "published" && hasPermission(user, "content:read_drafts")
|
|
44
|
+
? query.status
|
|
45
|
+
: "published";
|
|
46
|
+
|
|
39
47
|
try {
|
|
40
48
|
// Verify FTS indexes are healthy on first use. At most once per worker
|
|
41
49
|
// lifetime; no-op after that. Moved off the cold-start hot path to
|
|
@@ -44,7 +52,7 @@ export const GET: APIRoute = async ({ url, locals }) => {
|
|
|
44
52
|
|
|
45
53
|
const result = await searchWithDb(emdash.db, query.q, {
|
|
46
54
|
collections,
|
|
47
|
-
status
|
|
55
|
+
status,
|
|
48
56
|
locale: query.locale,
|
|
49
57
|
limit: query.limit,
|
|
50
58
|
});
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import type { APIRoute } from "astro";
|
|
10
10
|
|
|
11
11
|
import { requirePerm } from "#api/authorize.js";
|
|
12
|
-
import { apiError, handleError, unwrapResult } from "#api/error.js";
|
|
12
|
+
import { apiError, handleError, requireDb, unwrapResult } from "#api/error.js";
|
|
13
13
|
import {
|
|
14
14
|
handleSectionDelete,
|
|
15
15
|
handleSectionGet,
|
|
@@ -22,7 +22,9 @@ export const prerender = false;
|
|
|
22
22
|
|
|
23
23
|
export const GET: APIRoute = async ({ params, locals }) => {
|
|
24
24
|
const { emdash, user } = locals;
|
|
25
|
-
const
|
|
25
|
+
const dbErr = requireDb(emdash?.db);
|
|
26
|
+
if (dbErr) return dbErr;
|
|
27
|
+
const db = emdash!.db;
|
|
26
28
|
const { slug } = params;
|
|
27
29
|
|
|
28
30
|
const denied = requirePerm(user, "sections:read");
|
|
@@ -42,7 +44,9 @@ export const GET: APIRoute = async ({ params, locals }) => {
|
|
|
42
44
|
|
|
43
45
|
export const PUT: APIRoute = async ({ params, request, locals }) => {
|
|
44
46
|
const { emdash, user } = locals;
|
|
45
|
-
const
|
|
47
|
+
const dbErr = requireDb(emdash?.db);
|
|
48
|
+
if (dbErr) return dbErr;
|
|
49
|
+
const db = emdash!.db;
|
|
46
50
|
const { slug } = params;
|
|
47
51
|
|
|
48
52
|
const denied = requirePerm(user, "sections:manage");
|
|
@@ -65,7 +69,9 @@ export const PUT: APIRoute = async ({ params, request, locals }) => {
|
|
|
65
69
|
|
|
66
70
|
export const DELETE: APIRoute = async ({ params, locals }) => {
|
|
67
71
|
const { emdash, user } = locals;
|
|
68
|
-
const
|
|
72
|
+
const dbErr = requireDb(emdash?.db);
|
|
73
|
+
if (dbErr) return dbErr;
|
|
74
|
+
const db = emdash!.db;
|
|
69
75
|
const { slug } = params;
|
|
70
76
|
|
|
71
77
|
const denied = requirePerm(user, "sections:manage");
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import type { APIRoute } from "astro";
|
|
9
9
|
|
|
10
10
|
import { requirePerm } from "#api/authorize.js";
|
|
11
|
-
import { handleError, unwrapResult } from "#api/error.js";
|
|
11
|
+
import { handleError, requireDb, unwrapResult } from "#api/error.js";
|
|
12
12
|
import { handleSectionCreate, handleSectionList } from "#api/handlers/sections.js";
|
|
13
13
|
import { isParseError, parseBody, parseQuery } from "#api/parse.js";
|
|
14
14
|
import { createSectionBody, sectionsListQuery } from "#api/schemas.js";
|
|
@@ -17,7 +17,9 @@ export const prerender = false;
|
|
|
17
17
|
|
|
18
18
|
export const GET: APIRoute = async ({ url, locals }) => {
|
|
19
19
|
const { emdash, user } = locals;
|
|
20
|
-
const
|
|
20
|
+
const dbErr = requireDb(emdash?.db);
|
|
21
|
+
if (dbErr) return dbErr;
|
|
22
|
+
const db = emdash!.db;
|
|
21
23
|
|
|
22
24
|
const denied = requirePerm(user, "sections:read");
|
|
23
25
|
if (denied) return denied;
|
|
@@ -35,7 +37,9 @@ export const GET: APIRoute = async ({ url, locals }) => {
|
|
|
35
37
|
|
|
36
38
|
export const POST: APIRoute = async ({ request, locals }) => {
|
|
37
39
|
const { emdash, user } = locals;
|
|
38
|
-
const
|
|
40
|
+
const dbErr = requireDb(emdash?.db);
|
|
41
|
+
if (dbErr) return dbErr;
|
|
42
|
+
const db = emdash!.db;
|
|
39
43
|
|
|
40
44
|
const denied = requirePerm(user, "sections:manage");
|
|
41
45
|
if (denied) return denied;
|
|
@@ -16,6 +16,7 @@ import { apiError, apiSuccess, handleError } from "#api/error.js";
|
|
|
16
16
|
import { isParseError, parseBody } from "#api/parse.js";
|
|
17
17
|
import { getPublicOrigin } from "#api/public-url.js";
|
|
18
18
|
import { setupAdminVerifyBody } from "#api/schemas.js";
|
|
19
|
+
import { getConfiguredAllowedOrigins, validateAllowedOrigins } from "#auth/allowed-origins.js";
|
|
19
20
|
import { createChallengeStore } from "#auth/challenge-store.js";
|
|
20
21
|
import { getPasskeyConfig } from "#auth/passkey-config.js";
|
|
21
22
|
import { SETUP_NONCE_COOKIE } from "#auth/setup-nonce.js";
|
|
@@ -83,7 +84,11 @@ export const POST: APIRoute = async ({ cookies, request, locals }) => {
|
|
|
83
84
|
const url = new URL(request.url);
|
|
84
85
|
const siteName = (await options.get<string>("emdash:site_title")) ?? undefined;
|
|
85
86
|
const siteUrl = getPublicOrigin(url, emdash?.config);
|
|
86
|
-
const
|
|
87
|
+
const allowedOrigins = validateAllowedOrigins(
|
|
88
|
+
siteUrl,
|
|
89
|
+
getConfiguredAllowedOrigins(emdash?.config),
|
|
90
|
+
);
|
|
91
|
+
const passkeyConfig = getPasskeyConfig(url, siteName, siteUrl, allowedOrigins);
|
|
87
92
|
|
|
88
93
|
// Verify the registration response
|
|
89
94
|
const challengeStore = createChallengeStore(emdash.db);
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* - Excludes auth/user/session/token tables
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import type { User } from "@emdash-cms/auth";
|
|
10
11
|
import type { APIRoute } from "astro";
|
|
11
12
|
|
|
12
13
|
import { requirePerm } from "#api/authorize.js";
|
|
@@ -17,11 +18,31 @@ import {
|
|
|
17
18
|
verifyPreviewSignature,
|
|
18
19
|
} from "#api/handlers/snapshot.js";
|
|
19
20
|
import { getPublicOrigin } from "#api/public-url.js";
|
|
21
|
+
import { resolveSecretsCached } from "#config/secrets.js";
|
|
20
22
|
|
|
21
23
|
export const prerender = false;
|
|
22
24
|
|
|
23
|
-
export const GET: APIRoute = async ({ request, locals, url }) => {
|
|
24
|
-
const { emdash
|
|
25
|
+
export const GET: APIRoute = async ({ request, locals, url, session }) => {
|
|
26
|
+
const { emdash } = locals;
|
|
27
|
+
// This route is in PUBLIC_API_EXACT (for preview-signature callers with no session),
|
|
28
|
+
// so auth middleware skips user resolution. Manually resolve the session user here
|
|
29
|
+
// to support session-authenticated admin users alongside preview-signature auth.
|
|
30
|
+
let user: User | undefined = (locals as { user?: User }).user;
|
|
31
|
+
if (!user && session && emdash?.db) {
|
|
32
|
+
try {
|
|
33
|
+
const { createKyselyAdapter } = await import("@emdash-cms/auth/adapters/kysely");
|
|
34
|
+
const sessionUser = await session.get("user");
|
|
35
|
+
if (sessionUser?.id) {
|
|
36
|
+
const adapter = createKyselyAdapter(emdash.db);
|
|
37
|
+
const resolved = await adapter.getUserById(sessionUser.id);
|
|
38
|
+
if (resolved && !resolved.disabled) {
|
|
39
|
+
user = resolved;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
// Session resolution failed, continue to preview-signature check
|
|
44
|
+
}
|
|
45
|
+
}
|
|
25
46
|
|
|
26
47
|
if (!emdash?.db) {
|
|
27
48
|
return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
|
|
@@ -32,24 +53,29 @@ export const GET: APIRoute = async ({ request, locals, url }) => {
|
|
|
32
53
|
let authorized = false;
|
|
33
54
|
|
|
34
55
|
if (previewSig) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
56
|
+
// Resolves env override or DB-stored value. Always non-empty after
|
|
57
|
+
// resolution, so the signature path is never silently disabled.
|
|
58
|
+
// Note: a signing process without access to this database (e.g. a
|
|
59
|
+
// remote preview Worker) must set the same `EMDASH_PREVIEW_SECRET`
|
|
60
|
+
// env var on both sides.
|
|
61
|
+
const { previewSecret: secret, previewSecretSource } = await resolveSecretsCached(emdash.db);
|
|
62
|
+
const parsed = parsePreviewSignatureHeader(previewSig);
|
|
63
|
+
if (!parsed) {
|
|
64
|
+
console.warn("[snapshot] Failed to parse X-Preview-Signature header");
|
|
40
65
|
} else {
|
|
41
|
-
|
|
42
|
-
if (!
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
66
|
+
authorized = await verifyPreviewSignature(parsed.source, parsed.exp, parsed.sig, secret);
|
|
67
|
+
if (!authorized) {
|
|
68
|
+
const fields: Record<string, unknown> = {
|
|
69
|
+
source: parsed.source,
|
|
70
|
+
exp: parsed.exp,
|
|
71
|
+
expired: parsed.exp < Date.now() / 1000,
|
|
72
|
+
secretSource: previewSecretSource,
|
|
73
|
+
};
|
|
74
|
+
if (previewSecretSource === "db") {
|
|
75
|
+
fields.hint =
|
|
76
|
+
"Set EMDASH_PREVIEW_SECRET in both this process and the signing process to share secrets across deployments";
|
|
52
77
|
}
|
|
78
|
+
console.warn("[snapshot] Preview signature verification failed", fields);
|
|
53
79
|
}
|
|
54
80
|
}
|
|
55
81
|
}
|
|
@@ -52,7 +52,6 @@ export const POST: APIRoute = async ({ request, locals }) => {
|
|
|
52
52
|
if (isParseError(body)) return body;
|
|
53
53
|
|
|
54
54
|
const result = await handleTaxonomyCreate(emdash.db, body);
|
|
55
|
-
if (result.success) emdash.invalidateManifest();
|
|
56
55
|
return unwrapResult(result, 201);
|
|
57
56
|
} catch (error) {
|
|
58
57
|
return handleError(error, "Failed to create taxonomy", "TAXONOMY_CREATE_ERROR");
|
|
@@ -4,7 +4,13 @@
|
|
|
4
4
|
* POST /_emdash/api/themes/preview
|
|
5
5
|
*
|
|
6
6
|
* Generates a signed preview URL for the "Try with my data" feature.
|
|
7
|
-
*
|
|
7
|
+
*
|
|
8
|
+
* Uses the resolved preview secret: env override (`EMDASH_PREVIEW_SECRET`)
|
|
9
|
+
* wins, otherwise an auto-generated stable per-site value persisted in the
|
|
10
|
+
* options table is used. Processes that share the same database converge on
|
|
11
|
+
* the same auto-generated value; only set `EMDASH_PREVIEW_SECRET` in both
|
|
12
|
+
* processes when the verifying side runs without access to the EmDash DB
|
|
13
|
+
* (e.g. a remote preview Worker).
|
|
8
14
|
*/
|
|
9
15
|
|
|
10
16
|
import type { APIRoute } from "astro";
|
|
@@ -12,6 +18,7 @@ import type { APIRoute } from "astro";
|
|
|
12
18
|
import { requirePerm } from "#api/authorize.js";
|
|
13
19
|
import { apiError, apiSuccess } from "#api/error.js";
|
|
14
20
|
import { getPublicOrigin } from "#api/public-url.js";
|
|
21
|
+
import { resolveSecretsCached } from "#config/secrets.js";
|
|
15
22
|
|
|
16
23
|
export const prerender = false;
|
|
17
24
|
|
|
@@ -25,10 +32,9 @@ export const POST: APIRoute = async ({ request, url, locals }) => {
|
|
|
25
32
|
const denied = requirePerm(user, "plugins:read");
|
|
26
33
|
if (denied) return denied;
|
|
27
34
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
35
|
+
// Always non-empty after resolution; env override wins, otherwise a
|
|
36
|
+
// stable DB-stored value is used.
|
|
37
|
+
const { previewSecret: secret } = await resolveSecretsCached(emdash.db);
|
|
32
38
|
|
|
33
39
|
let body: { previewUrl: string };
|
|
34
40
|
try {
|
package/src/astro/types.ts
CHANGED
|
@@ -228,6 +228,15 @@ export interface EmDashHandlers {
|
|
|
228
228
|
slug?: string;
|
|
229
229
|
status?: string;
|
|
230
230
|
authorId?: string | null;
|
|
231
|
+
bylines?: Array<{ bylineId: string; roleLabel?: string | null }>;
|
|
232
|
+
seo?: {
|
|
233
|
+
title?: string | null;
|
|
234
|
+
description?: string | null;
|
|
235
|
+
image?: string | null;
|
|
236
|
+
canonical?: string | null;
|
|
237
|
+
noIndex?: boolean;
|
|
238
|
+
};
|
|
239
|
+
publishedAt?: string | null;
|
|
231
240
|
_rev?: string;
|
|
232
241
|
},
|
|
233
242
|
) => Promise<HandlerResponse>;
|
|
@@ -255,7 +264,11 @@ export interface EmDashHandlers {
|
|
|
255
264
|
) => Promise<HandlerResponse>;
|
|
256
265
|
|
|
257
266
|
// Publishing & Scheduling handlers
|
|
258
|
-
handleContentPublish: (
|
|
267
|
+
handleContentPublish: (
|
|
268
|
+
collection: string,
|
|
269
|
+
id: string,
|
|
270
|
+
options?: { publishedAt?: string },
|
|
271
|
+
) => Promise<HandlerResponse>;
|
|
259
272
|
|
|
260
273
|
handleContentUnpublish: (collection: string, id: string) => Promise<HandlerResponse>;
|
|
261
274
|
|
|
@@ -362,8 +375,15 @@ export interface EmDashHandlers {
|
|
|
362
375
|
// Configuration (for checking database type, auth mode, etc.)
|
|
363
376
|
config: import("./integration/runtime.js").EmDashConfig;
|
|
364
377
|
|
|
365
|
-
//
|
|
366
|
-
|
|
378
|
+
// Build the admin manifest from the live database. Only used by admin
|
|
379
|
+
// routes; logged-out requests don't need it. Per-request, deduplicated
|
|
380
|
+
// by `requestCached`.
|
|
381
|
+
getManifest: () => Promise<EmDashManifest>;
|
|
382
|
+
|
|
383
|
+
// Clear the cached URL patterns used by `resolveEmDashPath`. Call after
|
|
384
|
+
// any schema mutation that creates/updates/deletes a collection's
|
|
385
|
+
// `urlPattern` so public routing picks up the change immediately.
|
|
386
|
+
invalidateUrlPatternCache: () => void;
|
|
367
387
|
|
|
368
388
|
// Sandbox runner (for marketplace plugin install/update)
|
|
369
389
|
getSandboxRunner: () => import("../plugins/sandbox/types.js").SandboxRunner | null;
|