emdash 0.8.0 → 0.10.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.
Files changed (317) hide show
  1. package/dist/{adapters-BKSf3T9R.d.mts → adapters-BktHA7EO.d.mts} +1 -1
  2. package/dist/{adapters-BKSf3T9R.d.mts.map → adapters-BktHA7EO.d.mts.map} +1 -1
  3. package/dist/{apply-x0eMK1lX.mjs → apply-UsrFuO7l.mjs} +207 -355
  4. package/dist/apply-UsrFuO7l.mjs.map +1 -0
  5. package/dist/astro/index.d.mts +6 -6
  6. package/dist/astro/index.d.mts.map +1 -1
  7. package/dist/astro/index.mjs +118 -4
  8. package/dist/astro/index.mjs.map +1 -1
  9. package/dist/astro/middleware/auth.d.mts +6 -7
  10. package/dist/astro/middleware/auth.d.mts.map +1 -1
  11. package/dist/astro/middleware/auth.mjs +14 -57
  12. package/dist/astro/middleware/auth.mjs.map +1 -1
  13. package/dist/astro/middleware/redirect.d.mts.map +1 -1
  14. package/dist/astro/middleware/redirect.mjs +15 -10
  15. package/dist/astro/middleware/redirect.mjs.map +1 -1
  16. package/dist/astro/middleware/request-context.d.mts.map +1 -1
  17. package/dist/astro/middleware/request-context.mjs +8 -5
  18. package/dist/astro/middleware/request-context.mjs.map +1 -1
  19. package/dist/astro/middleware/setup.mjs +1 -1
  20. package/dist/astro/middleware.d.mts.map +1 -1
  21. package/dist/astro/middleware.mjs +70 -121
  22. package/dist/astro/middleware.mjs.map +1 -1
  23. package/dist/astro/types.d.mts +25 -10
  24. package/dist/astro/types.d.mts.map +1 -1
  25. package/dist/{byline-Chbr2GoP.mjs → byline-C3vnhIpU.mjs} +4 -4
  26. package/dist/{byline-Chbr2GoP.mjs.map → byline-C3vnhIpU.mjs.map} +1 -1
  27. package/dist/bylines-esI7ioa9.mjs +113 -0
  28. package/dist/bylines-esI7ioa9.mjs.map +1 -0
  29. package/dist/cache-fTzxgMFJ.mjs +65 -0
  30. package/dist/cache-fTzxgMFJ.mjs.map +1 -0
  31. package/dist/{chunks-HGz06Soa.mjs → chunks-Da2-b-oA.mjs} +8 -2
  32. package/dist/{chunks-HGz06Soa.mjs.map → chunks-Da2-b-oA.mjs.map} +1 -1
  33. package/dist/cli/index.mjs +456 -90
  34. package/dist/cli/index.mjs.map +1 -1
  35. package/dist/client/cf-access.d.mts +1 -1
  36. package/dist/client/index.d.mts +1 -1
  37. package/dist/client/index.mjs +3 -3
  38. package/dist/client/index.mjs.map +1 -1
  39. package/dist/{config-BXwuX8Bx.mjs → config-CVssduLe.mjs} +1 -1
  40. package/dist/{config-BXwuX8Bx.mjs.map → config-CVssduLe.mjs.map} +1 -1
  41. package/dist/{content-BcQPYxdV.mjs → content-C7G4QXkK.mjs} +42 -14
  42. package/dist/content-C7G4QXkK.mjs.map +1 -0
  43. package/dist/db/index.d.mts +3 -3
  44. package/dist/db/index.mjs +2 -2
  45. package/dist/db/libsql.d.mts +1 -1
  46. package/dist/db/libsql.d.mts.map +1 -1
  47. package/dist/db/libsql.mjs +7 -2
  48. package/dist/db/libsql.mjs.map +1 -1
  49. package/dist/db/postgres.d.mts +1 -1
  50. package/dist/db/sqlite.d.mts +1 -1
  51. package/dist/db/sqlite.d.mts.map +1 -1
  52. package/dist/db/sqlite.mjs +8 -3
  53. package/dist/db/sqlite.mjs.map +1 -1
  54. package/dist/{db-errors-l1Qh2RPR.mjs → db-errors-B7P2pSCn.mjs} +1 -1
  55. package/dist/{db-errors-l1Qh2RPR.mjs.map → db-errors-B7P2pSCn.mjs.map} +1 -1
  56. package/dist/{default-DCVqE5ib.mjs → default-pHuz9WF6.mjs} +1 -1
  57. package/dist/{default-DCVqE5ib.mjs.map → default-pHuz9WF6.mjs.map} +1 -1
  58. package/dist/{dialect-helpers-DhTzaUxP.mjs → dialect-helpers-BKCvISIQ.mjs} +19 -2
  59. package/dist/dialect-helpers-BKCvISIQ.mjs.map +1 -0
  60. package/dist/{error-zG5T1UGA.mjs → error-DqnRMM5z.mjs} +1 -1
  61. package/dist/{error-zG5T1UGA.mjs.map → error-DqnRMM5z.mjs.map} +1 -1
  62. package/dist/{index-DIb-CzNx.d.mts → index-DjPMOfO0.d.mts} +162 -87
  63. package/dist/index-DjPMOfO0.d.mts.map +1 -0
  64. package/dist/index.d.mts +11 -11
  65. package/dist/index.mjs +27 -24
  66. package/dist/{load-CyEoextb.mjs → load-sXRuM7Us.mjs} +2 -2
  67. package/dist/{load-CyEoextb.mjs.map → load-sXRuM7Us.mjs.map} +1 -1
  68. package/dist/{loader-CndGj8kM.mjs → loader-Bx2_9-5e.mjs} +53 -8
  69. package/dist/loader-Bx2_9-5e.mjs.map +1 -0
  70. package/dist/{manifest-schema-DH9xhc6t.mjs → manifest-schema-CXAbd1vH.mjs} +33 -3
  71. package/dist/manifest-schema-CXAbd1vH.mjs.map +1 -0
  72. package/dist/media/index.d.mts +1 -1
  73. package/dist/media/index.mjs +1 -1
  74. package/dist/media/local-runtime.d.mts +7 -7
  75. package/dist/{mode-BnAOqItE.mjs → mode-YhqNVef_.mjs} +1 -1
  76. package/dist/{mode-BnAOqItE.mjs.map → mode-YhqNVef_.mjs.map} +1 -1
  77. package/dist/options-nPxWnrya.mjs +117 -0
  78. package/dist/options-nPxWnrya.mjs.map +1 -0
  79. package/dist/page/index.d.mts +2 -2
  80. package/dist/{patterns-CrCYkMBb.mjs → patterns-DsUZ4uxI.mjs} +1 -1
  81. package/dist/{patterns-CrCYkMBb.mjs.map → patterns-DsUZ4uxI.mjs.map} +1 -1
  82. package/dist/{placeholder-D29tWZ7o.d.mts → placeholder-CDPtkelt.d.mts} +1 -1
  83. package/dist/{placeholder-D29tWZ7o.d.mts.map → placeholder-CDPtkelt.d.mts.map} +1 -1
  84. package/dist/{placeholder-C-fk5hYI.mjs → placeholder-Ci0RLeCk.mjs} +1 -1
  85. package/dist/{placeholder-C-fk5hYI.mjs.map → placeholder-Ci0RLeCk.mjs.map} +1 -1
  86. package/dist/plugins/adapt-sandbox-entry.d.mts +5 -5
  87. package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -1
  88. package/dist/plugins/adapt-sandbox-entry.mjs +6 -5
  89. package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -1
  90. package/dist/public-url-B1AxbbbQ.mjs +51 -0
  91. package/dist/public-url-B1AxbbbQ.mjs.map +1 -0
  92. package/dist/{query-fqEdLFms.mjs → query-Bo-msrmu.mjs} +114 -16
  93. package/dist/query-Bo-msrmu.mjs.map +1 -0
  94. package/dist/{redirect-D_pshWdf.mjs → redirect-C5H7VGIX.mjs} +11 -6
  95. package/dist/redirect-C5H7VGIX.mjs.map +1 -0
  96. package/dist/{registry-C3Mr0ODu.mjs → registry-Beb7wxFc.mjs} +39 -5
  97. package/dist/registry-Beb7wxFc.mjs.map +1 -0
  98. package/dist/{request-cache-Ci7f5pBb.mjs → request-cache-C-tIpYIw.mjs} +1 -1
  99. package/dist/{request-cache-Ci7f5pBb.mjs.map → request-cache-C-tIpYIw.mjs.map} +1 -1
  100. package/dist/runner-Clwe4Mme.d.mts +44 -0
  101. package/dist/runner-Clwe4Mme.d.mts.map +1 -0
  102. package/dist/{runner-tQ7BJ4T7.mjs → runner-DMnlIkh4.mjs} +616 -191
  103. package/dist/runner-DMnlIkh4.mjs.map +1 -0
  104. package/dist/runtime.d.mts +6 -6
  105. package/dist/runtime.mjs +2 -2
  106. package/dist/{search-BoZYFuUk.mjs → search-DkN-BqsS.mjs} +270 -152
  107. package/dist/search-DkN-BqsS.mjs.map +1 -0
  108. package/dist/secrets-CZ8rxLX3.mjs +314 -0
  109. package/dist/secrets-CZ8rxLX3.mjs.map +1 -0
  110. package/dist/seed/index.d.mts +2 -2
  111. package/dist/seed/index.mjs +13 -11
  112. package/dist/seo/index.d.mts +1 -1
  113. package/dist/storage/local.d.mts +1 -1
  114. package/dist/storage/local.mjs +1 -1
  115. package/dist/storage/s3.d.mts +1 -1
  116. package/dist/storage/s3.mjs +1 -1
  117. package/dist/taxonomies-CTtewrSQ.mjs +407 -0
  118. package/dist/taxonomies-CTtewrSQ.mjs.map +1 -0
  119. package/dist/taxonomy-DSxx2K2L.mjs +218 -0
  120. package/dist/taxonomy-DSxx2K2L.mjs.map +1 -0
  121. package/dist/{tokens-D9vnZqYS.mjs → tokens-CyRDPVW2.mjs} +1 -1
  122. package/dist/{tokens-D9vnZqYS.mjs.map → tokens-CyRDPVW2.mjs.map} +1 -1
  123. package/dist/{transaction-Cn2rjY78.mjs → transaction-D44LBXvU.mjs} +1 -1
  124. package/dist/{transaction-Cn2rjY78.mjs.map → transaction-D44LBXvU.mjs.map} +1 -1
  125. package/dist/{transport-CUnEL3Vs.d.mts → transport-DX_5rpsq.d.mts} +1 -1
  126. package/dist/{transport-CUnEL3Vs.d.mts.map → transport-DX_5rpsq.d.mts.map} +1 -1
  127. package/dist/{transport-C9ugt2Nr.mjs → transport-xpzIjCIB.mjs} +6 -5
  128. package/dist/{transport-C9ugt2Nr.mjs.map → transport-xpzIjCIB.mjs.map} +1 -1
  129. package/dist/{types-BrA0xf5I.d.mts → types-B_CXXnzh.d.mts} +1 -1
  130. package/dist/{types-BrA0xf5I.d.mts.map → types-B_CXXnzh.d.mts.map} +1 -1
  131. package/dist/{types-DIMwPFub.d.mts → types-C-aFbqmA.d.mts} +1 -1
  132. package/dist/{types-DIMwPFub.d.mts.map → types-C-aFbqmA.d.mts.map} +1 -1
  133. package/dist/types-CoO6mpV3.mjs +68 -0
  134. package/dist/types-CoO6mpV3.mjs.map +1 -0
  135. package/dist/{types-i36XcA_X.d.mts → types-D19uBYWn.d.mts} +83 -7
  136. package/dist/types-D19uBYWn.d.mts.map +1 -0
  137. package/dist/{types-BmPPSUEx.d.mts → types-Dl1fgFjn.d.mts} +24 -2
  138. package/dist/{types-BmPPSUEx.d.mts.map → types-Dl1fgFjn.d.mts.map} +1 -1
  139. package/dist/{types-CS8FIX7L.d.mts → types-Dtx1mSMX.d.mts} +9 -1
  140. package/dist/types-Dtx1mSMX.d.mts.map +1 -0
  141. package/dist/{types-Bm1dn-q3.mjs → types-Eg829jj9.mjs} +1 -1
  142. package/dist/{types-Bm1dn-q3.mjs.map → types-Eg829jj9.mjs.map} +1 -1
  143. package/dist/{types-CgqmmMJB.mjs → types-K-EkEQCI.mjs} +1 -1
  144. package/dist/{types-CgqmmMJB.mjs.map → types-K-EkEQCI.mjs.map} +1 -1
  145. package/dist/{validate-CxVsLehf.mjs → validate-CBIbxM3L.mjs} +14 -10
  146. package/dist/validate-CBIbxM3L.mjs.map +1 -0
  147. package/dist/{validate-DHxmpFJt.d.mts → validate-DHGwADqO.d.mts} +18 -5
  148. package/dist/validate-DHGwADqO.d.mts.map +1 -0
  149. package/dist/{validation-C-ZpN2GI.mjs → validation-B1NYiEos.mjs} +6 -6
  150. package/dist/{validation-C-ZpN2GI.mjs.map → validation-B1NYiEos.mjs.map} +1 -1
  151. package/dist/version-CMD42IRC.mjs +7 -0
  152. package/dist/{version-Bbq8TCrz.mjs.map → version-CMD42IRC.mjs.map} +1 -1
  153. package/dist/{zod-generator-CpwccCIv.mjs → zod-generator-BNJDQBSZ.mjs} +11 -6
  154. package/dist/{zod-generator-CpwccCIv.mjs.map → zod-generator-BNJDQBSZ.mjs.map} +1 -1
  155. package/locals.d.ts +1 -6
  156. package/package.json +9 -8
  157. package/src/api/handlers/comments.ts +6 -4
  158. package/src/api/handlers/content.ts +40 -1
  159. package/src/api/handlers/dashboard.ts +29 -36
  160. package/src/api/handlers/device-flow.ts +5 -0
  161. package/src/api/handlers/marketplace.ts +11 -4
  162. package/src/api/handlers/menus.ts +256 -75
  163. package/src/api/handlers/oauth-authorization.ts +72 -33
  164. package/src/api/handlers/revision.ts +23 -14
  165. package/src/api/handlers/taxonomies.ts +273 -100
  166. package/src/api/public-url.ts +48 -2
  167. package/src/api/schemas/comments.ts +2 -2
  168. package/src/api/schemas/common.ts +7 -0
  169. package/src/api/schemas/content.ts +17 -0
  170. package/src/api/schemas/menus.ts +23 -0
  171. package/src/api/schemas/sections.ts +3 -3
  172. package/src/api/schemas/taxonomies.ts +39 -0
  173. package/src/api/schemas/users.ts +1 -1
  174. package/src/api/types.ts +5 -1
  175. package/src/astro/integration/index.ts +17 -0
  176. package/src/astro/integration/routes.ts +10 -0
  177. package/src/astro/integration/runtime.ts +30 -0
  178. package/src/astro/integration/virtual-modules.ts +32 -2
  179. package/src/astro/integration/vite-config.ts +6 -1
  180. package/src/astro/middleware/auth.ts +13 -6
  181. package/src/astro/middleware/redirect.ts +29 -16
  182. package/src/astro/middleware/request-context.ts +15 -5
  183. package/src/astro/middleware.ts +23 -9
  184. package/src/astro/routes/api/auth/invite/complete.ts +6 -1
  185. package/src/astro/routes/api/auth/passkey/register/verify.ts +6 -1
  186. package/src/astro/routes/api/auth/passkey/verify.ts +6 -1
  187. package/src/astro/routes/api/auth/signup/complete.ts +6 -1
  188. package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +2 -2
  189. package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +4 -2
  190. package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +1 -1
  191. package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +34 -12
  192. package/src/astro/routes/api/content/[collection]/[id]/publish.ts +32 -2
  193. package/src/astro/routes/api/content/[collection]/[id]/restore.ts +4 -2
  194. package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +3 -2
  195. package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +8 -4
  196. package/src/astro/routes/api/content/[collection]/[id].ts +12 -0
  197. package/src/astro/routes/api/import/wordpress/execute.ts +3 -1
  198. package/src/astro/routes/api/import/wordpress/prepare.ts +7 -8
  199. package/src/astro/routes/api/import/wordpress/rewrite-url-helpers.ts +196 -0
  200. package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +9 -177
  201. package/src/astro/routes/api/import/wordpress-plugin/execute.ts +3 -1
  202. package/src/astro/routes/api/manifest.ts +62 -45
  203. package/src/astro/routes/api/media/[id]/confirm.ts +10 -1
  204. package/src/astro/routes/api/media/providers/[providerId]/index.ts +12 -3
  205. package/src/astro/routes/api/menus/[name]/items.ts +16 -6
  206. package/src/astro/routes/api/menus/[name]/reorder.ts +8 -3
  207. package/src/astro/routes/api/menus/[name]/translations.ts +82 -0
  208. package/src/astro/routes/api/menus/[name].ts +19 -10
  209. package/src/astro/routes/api/menus/index.ts +9 -6
  210. package/src/astro/routes/api/openapi.json.ts +27 -10
  211. package/src/astro/routes/api/redirects/404s/index.ts +10 -4
  212. package/src/astro/routes/api/redirects/404s/summary.ts +4 -2
  213. package/src/astro/routes/api/redirects/[id].ts +10 -4
  214. package/src/astro/routes/api/redirects/index.ts +7 -3
  215. package/src/astro/routes/api/revisions/[revisionId]/index.ts +1 -1
  216. package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +0 -2
  217. package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +0 -1
  218. package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +0 -1
  219. package/src/astro/routes/api/schema/collections/[slug]/index.ts +2 -2
  220. package/src/astro/routes/api/schema/collections/index.ts +1 -1
  221. package/src/astro/routes/api/search/index.ts +10 -2
  222. package/src/astro/routes/api/sections/[slug].ts +10 -4
  223. package/src/astro/routes/api/sections/index.ts +7 -3
  224. package/src/astro/routes/api/setup/admin-verify.ts +6 -1
  225. package/src/astro/routes/api/snapshot.ts +44 -18
  226. package/src/astro/routes/api/taxonomies/[name]/terms/[slug]/translations.ts +89 -0
  227. package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +22 -22
  228. package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +11 -14
  229. package/src/astro/routes/api/taxonomies/index.ts +9 -7
  230. package/src/astro/routes/api/themes/preview.ts +11 -5
  231. package/src/astro/types.ts +23 -3
  232. package/src/auth/allowed-origins.ts +168 -0
  233. package/src/auth/passkey-config.ts +35 -13
  234. package/src/bylines/index.ts +37 -88
  235. package/src/cli/commands/auth.ts +28 -6
  236. package/src/cli/commands/bundle-utils.ts +11 -2
  237. package/src/cli/commands/bundle.ts +28 -8
  238. package/src/cli/commands/content.ts +13 -0
  239. package/src/cli/commands/export-seed.ts +82 -21
  240. package/src/cli/commands/login.ts +8 -1
  241. package/src/cli/commands/plugin-init.ts +216 -90
  242. package/src/cli/commands/publish.ts +24 -0
  243. package/src/cli/commands/secrets.ts +183 -0
  244. package/src/cli/credentials.ts +1 -1
  245. package/src/cli/index.ts +5 -1
  246. package/src/client/index.ts +4 -4
  247. package/src/client/transport.ts +17 -7
  248. package/src/components/Break.astro +2 -2
  249. package/src/components/EmDashHead.astro +18 -13
  250. package/src/components/Embed.astro +1 -1
  251. package/src/components/Gallery.astro +1 -1
  252. package/src/components/Image.astro +1 -1
  253. package/src/components/InlinePortableTextEditor.tsx +104 -18
  254. package/src/config/secrets.ts +528 -0
  255. package/src/database/dialect-helpers.ts +50 -0
  256. package/src/database/migrations/034_published_at_index.ts +1 -1
  257. package/src/database/migrations/035_bounded_404_log.ts +56 -39
  258. package/src/database/migrations/036_i18n_menus_and_taxonomies.ts +477 -0
  259. package/src/database/migrations/runner.ts +158 -23
  260. package/src/database/repositories/content.ts +47 -12
  261. package/src/database/repositories/redirect.ts +14 -3
  262. package/src/database/repositories/taxonomy.ts +212 -82
  263. package/src/database/types.ts +10 -2
  264. package/src/db/libsql.ts +1 -3
  265. package/src/db/sqlite.ts +2 -5
  266. package/src/emdash-runtime.ts +84 -159
  267. package/src/i18n/resolve.ts +37 -0
  268. package/src/index.ts +9 -0
  269. package/src/loader.ts +73 -3
  270. package/src/mcp/server.ts +180 -54
  271. package/src/menus/index.ts +143 -124
  272. package/src/menus/types.ts +15 -1
  273. package/src/page/site-identity.ts +58 -0
  274. package/src/plugins/adapt-sandbox-entry.ts +22 -10
  275. package/src/plugins/context.ts +13 -10
  276. package/src/plugins/define-plugin.ts +40 -12
  277. package/src/plugins/hooks.ts +23 -19
  278. package/src/plugins/index.ts +9 -0
  279. package/src/plugins/manifest-schema.ts +37 -2
  280. package/src/plugins/types.ts +151 -11
  281. package/src/preview/urls.ts +23 -3
  282. package/src/query.ts +148 -5
  283. package/src/redirects/cache.ts +38 -18
  284. package/src/schema/registry.ts +56 -0
  285. package/src/schema/zod-generator.ts +39 -7
  286. package/src/seed/apply.ts +142 -54
  287. package/src/seed/types.ts +14 -1
  288. package/src/seed/validate.ts +27 -13
  289. package/src/settings/index.ts +80 -6
  290. package/src/settings/types.ts +23 -1
  291. package/src/taxonomies/index.ts +237 -210
  292. package/src/taxonomies/types.ts +10 -0
  293. package/dist/apply-x0eMK1lX.mjs.map +0 -1
  294. package/dist/bylines-CRNsVG88.mjs +0 -157
  295. package/dist/bylines-CRNsVG88.mjs.map +0 -1
  296. package/dist/cache-BkKBuIvS.mjs +0 -56
  297. package/dist/cache-BkKBuIvS.mjs.map +0 -1
  298. package/dist/chunk-ClPoSABd.mjs +0 -21
  299. package/dist/content-BcQPYxdV.mjs.map +0 -1
  300. package/dist/dialect-helpers-DhTzaUxP.mjs.map +0 -1
  301. package/dist/index-DIb-CzNx.d.mts.map +0 -1
  302. package/dist/loader-CndGj8kM.mjs.map +0 -1
  303. package/dist/manifest-schema-DH9xhc6t.mjs.map +0 -1
  304. package/dist/query-fqEdLFms.mjs.map +0 -1
  305. package/dist/redirect-D_pshWdf.mjs.map +0 -1
  306. package/dist/registry-C3Mr0ODu.mjs.map +0 -1
  307. package/dist/runner-OURCaApa.d.mts +0 -34
  308. package/dist/runner-OURCaApa.d.mts.map +0 -1
  309. package/dist/runner-tQ7BJ4T7.mjs.map +0 -1
  310. package/dist/search-BoZYFuUk.mjs.map +0 -1
  311. package/dist/taxonomies-B4IAshV8.mjs +0 -308
  312. package/dist/taxonomies-B4IAshV8.mjs.map +0 -1
  313. package/dist/types-CS8FIX7L.d.mts.map +0 -1
  314. package/dist/types-i36XcA_X.d.mts.map +0 -1
  315. package/dist/validate-CxVsLehf.mjs.map +0 -1
  316. package/dist/validate-DHxmpFJt.d.mts.map +0 -1
  317. package/dist/version-Bbq8TCrz.mjs +0 -7
@@ -9,8 +9,8 @@ import type { APIRoute } from "astro";
9
9
  import { requirePerm } from "#api/authorize.js";
10
10
  import { handleError, unwrapResult } from "#api/error.js";
11
11
  import { handleMenuItemReorder } from "#api/handlers/menus.js";
12
- import { isParseError, parseBody } from "#api/parse.js";
13
- import { reorderMenuItemsBody } from "#api/schemas.js";
12
+ import { isParseError, parseBody, parseQuery } from "#api/parse.js";
13
+ import { localeFilterQuery, reorderMenuItemsBody } from "#api/schemas.js";
14
14
 
15
15
  export const prerender = false;
16
16
 
@@ -21,11 +21,16 @@ export const POST: APIRoute = async ({ params, request, locals }) => {
21
21
  const denied = requirePerm(user, "menus:manage");
22
22
  if (denied) return denied;
23
23
 
24
+ const localeQ = parseQuery(new URL(request.url), localeFilterQuery);
25
+ if (isParseError(localeQ)) return localeQ;
26
+
24
27
  try {
25
28
  const body = await parseBody(request, reorderMenuItemsBody);
26
29
  if (isParseError(body)) return body;
27
30
 
28
- const result = await handleMenuItemReorder(emdash.db, name, body.items);
31
+ const result = await handleMenuItemReorder(emdash.db, name, body.items, {
32
+ locale: localeQ.locale,
33
+ });
29
34
  return unwrapResult(result);
30
35
  } catch (error) {
31
36
  return handleError(error, "Failed to reorder menu items", "MENU_REORDER_ERROR");
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Menu translation endpoints
3
+ *
4
+ * GET /_emdash/api/menus/:name/translations — list translations for a menu (uses any locale row)
5
+ * POST /_emdash/api/menus/:name/translations — create a new locale translation (body: { locale, label })
6
+ */
7
+
8
+ import type { APIRoute } from "astro";
9
+ import { z } from "zod";
10
+
11
+ import { requirePerm } from "#api/authorize.js";
12
+ import { handleError, requireDb, unwrapResult } from "#api/error.js";
13
+ import { handleMenuCreate, handleMenuGet, handleMenuTranslations } from "#api/handlers/menus.js";
14
+ import { isParseError, parseBody, parseQuery } from "#api/parse.js";
15
+ import { localeFilterQuery } from "#api/schemas.js";
16
+
17
+ export const prerender = false;
18
+
19
+ const createTranslationBody = z
20
+ .object({
21
+ locale: z.string().min(1),
22
+ label: z.string().min(1).optional(),
23
+ })
24
+ .meta({ id: "CreateMenuTranslationBody" });
25
+
26
+ export const GET: APIRoute = async ({ params, request, locals }) => {
27
+ const { emdash, user } = locals;
28
+ const name = params.name!;
29
+
30
+ const dbErr = requireDb(emdash?.db);
31
+ if (dbErr) return dbErr;
32
+
33
+ const denied = requirePerm(user, "menus:read");
34
+ if (denied) return denied;
35
+
36
+ const localeQ = parseQuery(new URL(request.url), localeFilterQuery);
37
+ if (isParseError(localeQ)) return localeQ;
38
+
39
+ try {
40
+ // Look up any menu row matching the name so we can get its translation_group.
41
+ const anchor = await handleMenuGet(emdash.db, name, { locale: localeQ.locale });
42
+ if (!anchor.success) return unwrapResult(anchor);
43
+ const result = await handleMenuTranslations(emdash.db, anchor.data.id);
44
+ return unwrapResult(result);
45
+ } catch (error) {
46
+ return handleError(error, "Failed to fetch menu translations", "MENU_TRANSLATIONS_ERROR");
47
+ }
48
+ };
49
+
50
+ export const POST: APIRoute = async ({ params, request, locals }) => {
51
+ const { emdash, user } = locals;
52
+ const name = params.name!;
53
+
54
+ const dbErr = requireDb(emdash?.db);
55
+ if (dbErr) return dbErr;
56
+
57
+ const denied = requirePerm(user, "menus:manage");
58
+ if (denied) return denied;
59
+
60
+ const localeQ = parseQuery(new URL(request.url), localeFilterQuery);
61
+ if (isParseError(localeQ)) return localeQ;
62
+
63
+ try {
64
+ const body = await parseBody(request, createTranslationBody);
65
+ if (isParseError(body)) return body;
66
+
67
+ // Resolve the source menu (either by explicit locale in query, or the
68
+ // first matching row). Its id becomes the `translationOf` for the new row.
69
+ const source = await handleMenuGet(emdash.db, name, { locale: localeQ.locale });
70
+ if (!source.success) return unwrapResult(source);
71
+
72
+ const result = await handleMenuCreate(emdash.db, {
73
+ name,
74
+ label: body.label ?? source.data.label,
75
+ locale: body.locale,
76
+ translationOf: source.data.id,
77
+ });
78
+ return unwrapResult(result, 201);
79
+ } catch (error) {
80
+ return handleError(error, "Failed to create menu translation", "MENU_TRANSLATION_CREATE_ERROR");
81
+ }
82
+ };
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Single menu endpoint
3
3
  *
4
- * GET /_emdash/api/menus/:name - Get menu with items
5
- * PUT /_emdash/api/menus/:name - Update menu metadata
6
- * DELETE /_emdash/api/menus/:name - Delete menu
4
+ * GET /_emdash/api/menus/:name[?locale=xx]
5
+ * PUT /_emdash/api/menus/:name[?locale=xx]
6
+ * DELETE /_emdash/api/menus/:name[?locale=xx]
7
7
  */
8
8
 
9
9
  import type { APIRoute } from "astro";
@@ -11,20 +11,23 @@ import type { APIRoute } from "astro";
11
11
  import { requirePerm } from "#api/authorize.js";
12
12
  import { handleError, unwrapResult } from "#api/error.js";
13
13
  import { handleMenuDelete, handleMenuGet, handleMenuUpdate } from "#api/handlers/menus.js";
14
- import { isParseError, parseBody } from "#api/parse.js";
15
- import { updateMenuBody } from "#api/schemas.js";
14
+ import { isParseError, parseBody, parseQuery } from "#api/parse.js";
15
+ import { localeFilterQuery, updateMenuBody } from "#api/schemas.js";
16
16
 
17
17
  export const prerender = false;
18
18
 
19
- export const GET: APIRoute = async ({ params, locals }) => {
19
+ export const GET: APIRoute = async ({ params, request, locals }) => {
20
20
  const { emdash, user } = locals;
21
21
  const name = params.name!;
22
22
 
23
23
  const denied = requirePerm(user, "menus:read");
24
24
  if (denied) return denied;
25
25
 
26
+ const query = parseQuery(new URL(request.url), localeFilterQuery);
27
+ if (isParseError(query)) return query;
28
+
26
29
  try {
27
- const result = await handleMenuGet(emdash.db, name);
30
+ const result = await handleMenuGet(emdash.db, name, { locale: query.locale });
28
31
  return unwrapResult(result);
29
32
  } catch (error) {
30
33
  return handleError(error, "Failed to fetch menu", "MENU_GET_ERROR");
@@ -38,26 +41,32 @@ export const PUT: APIRoute = async ({ params, request, locals }) => {
38
41
  const denied = requirePerm(user, "menus:manage");
39
42
  if (denied) return denied;
40
43
 
44
+ const query = parseQuery(new URL(request.url), localeFilterQuery);
45
+ if (isParseError(query)) return query;
46
+
41
47
  try {
42
48
  const body = await parseBody(request, updateMenuBody);
43
49
  if (isParseError(body)) return body;
44
50
 
45
- const result = await handleMenuUpdate(emdash.db, name, body);
51
+ const result = await handleMenuUpdate(emdash.db, name, { ...body, locale: query.locale });
46
52
  return unwrapResult(result);
47
53
  } catch (error) {
48
54
  return handleError(error, "Failed to update menu", "MENU_UPDATE_ERROR");
49
55
  }
50
56
  };
51
57
 
52
- export const DELETE: APIRoute = async ({ params, locals }) => {
58
+ export const DELETE: APIRoute = async ({ params, request, locals }) => {
53
59
  const { emdash, user } = locals;
54
60
  const name = params.name!;
55
61
 
56
62
  const denied = requirePerm(user, "menus:manage");
57
63
  if (denied) return denied;
58
64
 
65
+ const query = parseQuery(new URL(request.url), localeFilterQuery);
66
+ if (isParseError(query)) return query;
67
+
59
68
  try {
60
- const result = await handleMenuDelete(emdash.db, name);
69
+ const result = await handleMenuDelete(emdash.db, name, { locale: query.locale });
61
70
  return unwrapResult(result);
62
71
  } catch (error) {
63
72
  return handleError(error, "Failed to delete menu", "MENU_DELETE_ERROR");
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Menus list and create endpoints
3
3
  *
4
- * GET /_emdash/api/menus - List all menus
5
- * POST /_emdash/api/menus - Create menu
4
+ * GET /_emdash/api/menus[?locale=xx] - List menus (optionally filtered by locale)
5
+ * POST /_emdash/api/menus - Create menu (body may include locale & translationOf)
6
6
  */
7
7
 
8
8
  import type { APIRoute } from "astro";
@@ -10,19 +10,22 @@ import type { APIRoute } from "astro";
10
10
  import { requirePerm } from "#api/authorize.js";
11
11
  import { handleError, unwrapResult } from "#api/error.js";
12
12
  import { handleMenuCreate, handleMenuList } from "#api/handlers/menus.js";
13
- import { isParseError, parseBody } from "#api/parse.js";
14
- import { createMenuBody } from "#api/schemas.js";
13
+ import { isParseError, parseBody, parseQuery } from "#api/parse.js";
14
+ import { createMenuBody, localeFilterQuery } from "#api/schemas.js";
15
15
 
16
16
  export const prerender = false;
17
17
 
18
- export const GET: APIRoute = async ({ locals }) => {
18
+ export const GET: APIRoute = async ({ request, locals }) => {
19
19
  const { emdash, user } = locals;
20
20
 
21
21
  const denied = requirePerm(user, "menus:read");
22
22
  if (denied) return denied;
23
23
 
24
+ const query = parseQuery(new URL(request.url), localeFilterQuery);
25
+ if (isParseError(query)) return query;
26
+
24
27
  try {
25
- const result = await handleMenuList(emdash.db);
28
+ const result = await handleMenuList(emdash.db, { locale: query.locale });
26
29
  return unwrapResult(result);
27
30
  } catch (error) {
28
31
  return handleError(error, "Failed to fetch menus", "MENU_LIST_ERROR");
@@ -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
- let cachedSpec: string | null = null;
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
- if (!cachedSpec && emdash) {
31
+
32
+ let spec = getCachedSpec();
33
+ if (!spec && emdash) {
21
34
  try {
22
35
  const doc = generateOpenApiDocument({ maxUploadSize: emdash.config.maxUploadSize });
23
- cachedSpec = JSON.stringify(doc);
24
- } catch {
25
- return new Response(
26
- JSON.stringify({ error: "Failed to generate OpenAPI document: invalid configuration" }),
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
- const spec = cachedSpec ?? JSON.stringify(generateOpenApiDocument());
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": "public, max-age=3600",
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 db = emdash.db;
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 db = emdash.db;
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 db = emdash.db;
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 db = emdash.db;
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 db = emdash.db;
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 db = emdash.db;
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 db = emdash.db;
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 db = emdash.db;
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 db = emdash.db;
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:read");
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
  };
@@ -48,6 +48,5 @@ export const POST: APIRoute = async ({ params, request, locals }) => {
48
48
  collectionSlug,
49
49
  body as CreateFieldInput,
50
50
  );
51
- if (result.success) emdash!.invalidateManifest();
52
51
  return unwrapResult(result, 201);
53
52
  };
@@ -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!.invalidateManifest();
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!.invalidateManifest();
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!.invalidateManifest();
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: query.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 db = emdash.db;
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 db = emdash.db;
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 db = emdash.db;
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 db = emdash.db;
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 db = emdash.db;
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 passkeyConfig = getPasskeyConfig(url, siteName, siteUrl);
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);