emdash 0.20.0 → 0.21.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 (547) hide show
  1. package/dist/{adapters-BzIHV3sw.d.mts → adapters-BxSmgtbF.d.mts} +1 -1
  2. package/dist/{adapters-BzIHV3sw.d.mts.map → adapters-BxSmgtbF.d.mts.map} +1 -1
  3. package/dist/{allowed-origins-B1u7Qnvg.mjs → allowed-origins-BqC8cul8.mjs} +2 -2
  4. package/dist/{allowed-origins-B1u7Qnvg.mjs.map → allowed-origins-BqC8cul8.mjs.map} +1 -1
  5. package/dist/api/route-utils.d.mts +3 -3
  6. package/dist/api/route-utils.mjs +13 -12
  7. package/dist/api/route-utils.mjs.map +1 -1
  8. package/dist/api/schemas/index.d.mts +1 -1
  9. package/dist/api/schemas/index.mjs +3 -2
  10. package/dist/{api-DStv36ik.mjs → api-DxjIV2o8.mjs} +13 -13
  11. package/dist/{api-DStv36ik.mjs.map → api-DxjIV2o8.mjs.map} +1 -1
  12. package/dist/{api-tokens-DPfhPu5V.mjs → api-tokens-BFFkB0jB.mjs} +2 -2
  13. package/dist/{api-tokens-DPfhPu5V.mjs.map → api-tokens-BFFkB0jB.mjs.map} +1 -1
  14. package/dist/{apply-Dr7snAMT.mjs → apply-CLjxheyb.mjs} +12 -12
  15. package/dist/{apply-Dr7snAMT.mjs.map → apply-CLjxheyb.mjs.map} +1 -1
  16. package/dist/astro/index.d.mts +10 -10
  17. package/dist/astro/index.d.mts.map +1 -1
  18. package/dist/astro/index.mjs +50 -15
  19. package/dist/astro/index.mjs.map +1 -1
  20. package/dist/astro/middleware/auth.d.mts +9 -9
  21. package/dist/astro/middleware/auth.mjs +5 -5
  22. package/dist/astro/middleware/redirect.d.mts.map +1 -1
  23. package/dist/astro/middleware/redirect.mjs +11 -2
  24. package/dist/astro/middleware/redirect.mjs.map +1 -1
  25. package/dist/astro/middleware/request-context.mjs +3 -2
  26. package/dist/astro/middleware/request-context.mjs.map +1 -1
  27. package/dist/astro/middleware/setup.mjs +1 -1
  28. package/dist/astro/middleware.d.mts +1 -1
  29. package/dist/astro/middleware.mjs +63 -60
  30. package/dist/astro/middleware.mjs.map +1 -1
  31. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs +5 -4
  32. package/dist/astro/routes/api/admin/allowed-domains/_domain_.mjs.map +1 -1
  33. package/dist/astro/routes/api/admin/allowed-domains/index.mjs +5 -4
  34. package/dist/astro/routes/api/admin/allowed-domains/index.mjs.map +1 -1
  35. package/dist/astro/routes/api/admin/api-tokens/_id_.mjs +3 -3
  36. package/dist/astro/routes/api/admin/api-tokens/index.mjs +4 -4
  37. package/dist/astro/routes/api/admin/byline-fields/_slug_/usage.mjs +4 -4
  38. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs +8 -7
  39. package/dist/astro/routes/api/admin/byline-fields/_slug_.mjs.map +1 -1
  40. package/dist/astro/routes/api/admin/byline-fields/index.mjs +8 -7
  41. package/dist/astro/routes/api/admin/byline-fields/index.mjs.map +1 -1
  42. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs +8 -7
  43. package/dist/astro/routes/api/admin/byline-fields/reorder.mjs.map +1 -1
  44. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs +14 -12
  45. package/dist/astro/routes/api/admin/bylines/_id_/index.mjs.map +1 -1
  46. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs +14 -12
  47. package/dist/astro/routes/api/admin/bylines/_id_/translations.mjs.map +1 -1
  48. package/dist/astro/routes/api/admin/bylines/index.mjs +14 -12
  49. package/dist/astro/routes/api/admin/bylines/index.mjs.map +1 -1
  50. package/dist/astro/routes/api/admin/comments/_id_/status.mjs +9 -8
  51. package/dist/astro/routes/api/admin/comments/_id_/status.mjs.map +1 -1
  52. package/dist/astro/routes/api/admin/comments/_id_.mjs +3 -3
  53. package/dist/astro/routes/api/admin/comments/bulk.mjs +7 -6
  54. package/dist/astro/routes/api/admin/comments/bulk.mjs.map +1 -1
  55. package/dist/astro/routes/api/admin/comments/counts.mjs +3 -3
  56. package/dist/astro/routes/api/admin/comments/index.mjs +7 -6
  57. package/dist/astro/routes/api/admin/comments/index.mjs.map +1 -1
  58. package/dist/astro/routes/api/admin/hooks/exclusive/_hookName_.mjs +3 -3
  59. package/dist/astro/routes/api/admin/hooks/exclusive/index.mjs +2 -2
  60. package/dist/astro/routes/api/admin/oauth-clients/_id_.mjs +3 -3
  61. package/dist/astro/routes/api/admin/oauth-clients/index.mjs +3 -3
  62. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs +29 -27
  63. package/dist/astro/routes/api/admin/plugins/_id_/disable.mjs.map +1 -1
  64. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs +29 -27
  65. package/dist/astro/routes/api/admin/plugins/_id_/enable.mjs.map +1 -1
  66. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs +28 -26
  67. package/dist/astro/routes/api/admin/plugins/_id_/index.mjs.map +1 -1
  68. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs +28 -26
  69. package/dist/astro/routes/api/admin/plugins/_id_/uninstall.mjs.map +1 -1
  70. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs +28 -26
  71. package/dist/astro/routes/api/admin/plugins/_id_/update.mjs.map +1 -1
  72. package/dist/astro/routes/api/admin/plugins/index.mjs +28 -26
  73. package/dist/astro/routes/api/admin/plugins/index.mjs.map +1 -1
  74. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/icon.mjs +2 -2
  75. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs +28 -26
  76. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/index.mjs.map +1 -1
  77. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs +28 -26
  78. package/dist/astro/routes/api/admin/plugins/marketplace/_id_/install.mjs.map +1 -1
  79. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs +28 -26
  80. package/dist/astro/routes/api/admin/plugins/marketplace/index.mjs.map +1 -1
  81. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs +28 -26
  82. package/dist/astro/routes/api/admin/plugins/registry/_id_/uninstall.mjs.map +1 -1
  83. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs +29 -27
  84. package/dist/astro/routes/api/admin/plugins/registry/_id_/update.mjs.map +1 -1
  85. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs +28 -26
  86. package/dist/astro/routes/api/admin/plugins/registry/artifact.mjs.map +1 -1
  87. package/dist/astro/routes/api/admin/plugins/registry/install.mjs +29 -27
  88. package/dist/astro/routes/api/admin/plugins/registry/install.mjs.map +1 -1
  89. package/dist/astro/routes/api/admin/plugins/updates.mjs +28 -26
  90. package/dist/astro/routes/api/admin/plugins/updates.mjs.map +1 -1
  91. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs +28 -26
  92. package/dist/astro/routes/api/admin/themes/marketplace/_id_/index.mjs.map +1 -1
  93. package/dist/astro/routes/api/admin/themes/marketplace/_id_/thumbnail.mjs +2 -2
  94. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs +28 -26
  95. package/dist/astro/routes/api/admin/themes/marketplace/index.mjs.map +1 -1
  96. package/dist/astro/routes/api/admin/users/_id_/disable.mjs +1 -1
  97. package/dist/astro/routes/api/admin/users/_id_/enable.mjs +1 -1
  98. package/dist/astro/routes/api/admin/users/_id_/index.mjs +5 -4
  99. package/dist/astro/routes/api/admin/users/_id_/index.mjs.map +1 -1
  100. package/dist/astro/routes/api/admin/users/_id_/send-recovery.mjs +2 -2
  101. package/dist/astro/routes/api/admin/users/index.mjs +5 -4
  102. package/dist/astro/routes/api/admin/users/index.mjs.map +1 -1
  103. package/dist/astro/routes/api/auth/dev-bypass.mjs +3 -3
  104. package/dist/astro/routes/api/auth/invite/accept.mjs +1 -1
  105. package/dist/astro/routes/api/auth/invite/complete.mjs +9 -8
  106. package/dist/astro/routes/api/auth/invite/complete.mjs.map +1 -1
  107. package/dist/astro/routes/api/auth/invite/index.mjs +6 -5
  108. package/dist/astro/routes/api/auth/invite/index.mjs.map +1 -1
  109. package/dist/astro/routes/api/auth/invite/register-options.mjs +8 -7
  110. package/dist/astro/routes/api/auth/invite/register-options.mjs.map +1 -1
  111. package/dist/astro/routes/api/auth/logout.mjs +2 -2
  112. package/dist/astro/routes/api/auth/magic-link/send.mjs +8 -7
  113. package/dist/astro/routes/api/auth/magic-link/send.mjs.map +1 -1
  114. package/dist/astro/routes/api/auth/magic-link/verify.mjs +2 -2
  115. package/dist/astro/routes/api/auth/me.mjs +5 -4
  116. package/dist/astro/routes/api/auth/me.mjs.map +1 -1
  117. package/dist/astro/routes/api/auth/mode.mjs +1 -1
  118. package/dist/astro/routes/api/auth/oauth/_provider_/callback.mjs +3 -3
  119. package/dist/astro/routes/api/auth/oauth/_provider_.mjs +2 -2
  120. package/dist/astro/routes/api/auth/passkey/_id_.mjs +5 -4
  121. package/dist/astro/routes/api/auth/passkey/_id_.mjs.map +1 -1
  122. package/dist/astro/routes/api/auth/passkey/index.mjs +1 -1
  123. package/dist/astro/routes/api/auth/passkey/options.mjs +10 -9
  124. package/dist/astro/routes/api/auth/passkey/options.mjs.map +1 -1
  125. package/dist/astro/routes/api/auth/passkey/register/options.mjs +8 -7
  126. package/dist/astro/routes/api/auth/passkey/register/options.mjs.map +1 -1
  127. package/dist/astro/routes/api/auth/passkey/register/verify.mjs +9 -8
  128. package/dist/astro/routes/api/auth/passkey/register/verify.mjs.map +1 -1
  129. package/dist/astro/routes/api/auth/passkey/verify.mjs +9 -8
  130. package/dist/astro/routes/api/auth/passkey/verify.mjs.map +1 -1
  131. package/dist/astro/routes/api/auth/signup/complete.mjs +9 -8
  132. package/dist/astro/routes/api/auth/signup/complete.mjs.map +1 -1
  133. package/dist/astro/routes/api/auth/signup/request.mjs +8 -7
  134. package/dist/astro/routes/api/auth/signup/request.mjs.map +1 -1
  135. package/dist/astro/routes/api/auth/signup/verify.mjs +1 -1
  136. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs +11 -9
  137. package/dist/astro/routes/api/comments/_collection_/_contentId_/index.mjs.map +1 -1
  138. package/dist/astro/routes/api/content/_collection_/_id_/compare.mjs +2 -2
  139. package/dist/astro/routes/api/content/_collection_/_id_/discard-draft.mjs +2 -2
  140. package/dist/astro/routes/api/content/_collection_/_id_/duplicate.mjs +2 -2
  141. package/dist/astro/routes/api/content/_collection_/_id_/permanent.mjs +2 -2
  142. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs +10 -8
  143. package/dist/astro/routes/api/content/_collection_/_id_/preview-url.mjs.map +1 -1
  144. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs +6 -5
  145. package/dist/astro/routes/api/content/_collection_/_id_/publish.mjs.map +1 -1
  146. package/dist/astro/routes/api/content/_collection_/_id_/restore.mjs +2 -2
  147. package/dist/astro/routes/api/content/_collection_/_id_/revisions.mjs +2 -2
  148. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs +6 -5
  149. package/dist/astro/routes/api/content/_collection_/_id_/schedule.mjs.map +1 -1
  150. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs +10 -9
  151. package/dist/astro/routes/api/content/_collection_/_id_/terms/_taxonomy_.mjs.map +1 -1
  152. package/dist/astro/routes/api/content/_collection_/_id_/translations.mjs +2 -2
  153. package/dist/astro/routes/api/content/_collection_/_id_/unpublish.mjs +2 -2
  154. package/dist/astro/routes/api/content/_collection_/_id_.mjs +6 -5
  155. package/dist/astro/routes/api/content/_collection_/_id_.mjs.map +1 -1
  156. package/dist/astro/routes/api/content/_collection_/authors.mjs +2 -2
  157. package/dist/astro/routes/api/content/_collection_/index.mjs +6 -5
  158. package/dist/astro/routes/api/content/_collection_/index.mjs.map +1 -1
  159. package/dist/astro/routes/api/content/_collection_/trash.mjs +6 -5
  160. package/dist/astro/routes/api/content/_collection_/trash.mjs.map +1 -1
  161. package/dist/astro/routes/api/dashboard.mjs +3 -3
  162. package/dist/astro/routes/api/dev/emails.mjs +2 -2
  163. package/dist/astro/routes/api/import/probe.d.mts +3 -3
  164. package/dist/astro/routes/api/import/probe.mjs +10 -9
  165. package/dist/astro/routes/api/import/probe.mjs.map +1 -1
  166. package/dist/astro/routes/api/import/wordpress/analyze.mjs +3 -3
  167. package/dist/astro/routes/api/import/wordpress/execute.d.mts +9 -9
  168. package/dist/astro/routes/api/import/wordpress/execute.mjs +10 -9
  169. package/dist/astro/routes/api/import/wordpress/execute.mjs.map +1 -1
  170. package/dist/astro/routes/api/import/wordpress/media.mjs +8 -7
  171. package/dist/astro/routes/api/import/wordpress/media.mjs.map +1 -1
  172. package/dist/astro/routes/api/import/wordpress/prepare.mjs +9 -8
  173. package/dist/astro/routes/api/import/wordpress/prepare.mjs.map +1 -1
  174. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs +8 -7
  175. package/dist/astro/routes/api/import/wordpress/rewrite-urls.mjs.map +1 -1
  176. package/dist/astro/routes/api/import/wordpress-plugin/analyze.d.mts +1 -1
  177. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs +10 -9
  178. package/dist/astro/routes/api/import/wordpress-plugin/analyze.mjs.map +1 -1
  179. package/dist/astro/routes/api/import/wordpress-plugin/execute.d.mts +1 -1
  180. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs +14 -12
  181. package/dist/astro/routes/api/import/wordpress-plugin/execute.mjs.map +1 -1
  182. package/dist/astro/routes/api/manifest.mjs +3 -3
  183. package/dist/astro/routes/api/mcp.mjs +20 -19
  184. package/dist/astro/routes/api/mcp.mjs.map +1 -1
  185. package/dist/astro/routes/api/media/_id_/confirm.mjs +6 -5
  186. package/dist/astro/routes/api/media/_id_/confirm.mjs.map +1 -1
  187. package/dist/astro/routes/api/media/_id_.mjs +6 -5
  188. package/dist/astro/routes/api/media/_id_.mjs.map +1 -1
  189. package/dist/astro/routes/api/media/file/_...key_.mjs +1 -1
  190. package/dist/astro/routes/api/media/providers/_providerId_/_itemId_.mjs +2 -2
  191. package/dist/astro/routes/api/media/providers/_providerId_/index.mjs +2 -2
  192. package/dist/astro/routes/api/media/providers/index.mjs +2 -2
  193. package/dist/astro/routes/api/media/upload-url.mjs +8 -7
  194. package/dist/astro/routes/api/media/upload-url.mjs.map +1 -1
  195. package/dist/astro/routes/api/media.mjs +10 -9
  196. package/dist/astro/routes/api/media.mjs.map +1 -1
  197. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs +6 -5
  198. package/dist/astro/routes/api/menus/_name_/items/_id_.mjs.map +1 -1
  199. package/dist/astro/routes/api/menus/_name_/items.mjs +6 -5
  200. package/dist/astro/routes/api/menus/_name_/items.mjs.map +1 -1
  201. package/dist/astro/routes/api/menus/_name_/reorder.mjs +6 -5
  202. package/dist/astro/routes/api/menus/_name_/reorder.mjs.map +1 -1
  203. package/dist/astro/routes/api/menus/_name_/translations.mjs +6 -5
  204. package/dist/astro/routes/api/menus/_name_/translations.mjs.map +1 -1
  205. package/dist/astro/routes/api/menus/_name_.mjs +6 -5
  206. package/dist/astro/routes/api/menus/_name_.mjs.map +1 -1
  207. package/dist/astro/routes/api/menus/index.mjs +6 -5
  208. package/dist/astro/routes/api/menus/index.mjs.map +1 -1
  209. package/dist/astro/routes/api/oauth/authorize.mjs +6 -6
  210. package/dist/astro/routes/api/oauth/device/authorize.mjs +5 -5
  211. package/dist/astro/routes/api/oauth/device/code.mjs +8 -8
  212. package/dist/astro/routes/api/oauth/device/token.mjs +7 -7
  213. package/dist/astro/routes/api/oauth/register.mjs +2 -2
  214. package/dist/astro/routes/api/oauth/token/refresh.mjs +5 -5
  215. package/dist/astro/routes/api/oauth/token/revoke.mjs +5 -5
  216. package/dist/astro/routes/api/oauth/token.mjs +5 -5
  217. package/dist/astro/routes/api/openapi.json.mjs +3 -2
  218. package/dist/astro/routes/api/openapi.json.mjs.map +1 -1
  219. package/dist/astro/routes/api/plugins/_pluginId_/_...path_.mjs +3 -3
  220. package/dist/astro/routes/api/redirects/404s/index.mjs +7 -6
  221. package/dist/astro/routes/api/redirects/404s/index.mjs.map +1 -1
  222. package/dist/astro/routes/api/redirects/404s/summary.mjs +7 -6
  223. package/dist/astro/routes/api/redirects/404s/summary.mjs.map +1 -1
  224. package/dist/astro/routes/api/redirects/_id_.mjs +8 -7
  225. package/dist/astro/routes/api/redirects/_id_.mjs.map +1 -1
  226. package/dist/astro/routes/api/redirects/index.mjs +8 -7
  227. package/dist/astro/routes/api/redirects/index.mjs.map +1 -1
  228. package/dist/astro/routes/api/revisions/_revisionId_/index.mjs +2 -2
  229. package/dist/astro/routes/api/revisions/_revisionId_/restore.mjs +2 -2
  230. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs +28 -26
  231. package/dist/astro/routes/api/schema/collections/_slug_/fields/_fieldSlug_.mjs.map +1 -1
  232. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs +28 -26
  233. package/dist/astro/routes/api/schema/collections/_slug_/fields/index.mjs.map +1 -1
  234. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs +28 -26
  235. package/dist/astro/routes/api/schema/collections/_slug_/fields/reorder.mjs.map +1 -1
  236. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs +28 -26
  237. package/dist/astro/routes/api/schema/collections/_slug_/index.mjs.map +1 -1
  238. package/dist/astro/routes/api/schema/collections/index.mjs +28 -26
  239. package/dist/astro/routes/api/schema/collections/index.mjs.map +1 -1
  240. package/dist/astro/routes/api/schema/index.mjs +5 -5
  241. package/dist/astro/routes/api/schema/orphans/_slug_.mjs +28 -26
  242. package/dist/astro/routes/api/schema/orphans/_slug_.mjs.map +1 -1
  243. package/dist/astro/routes/api/schema/orphans/index.mjs +28 -26
  244. package/dist/astro/routes/api/schema/orphans/index.mjs.map +1 -1
  245. package/dist/astro/routes/api/search/enable.mjs +9 -8
  246. package/dist/astro/routes/api/search/enable.mjs.map +1 -1
  247. package/dist/astro/routes/api/search/index.mjs +8 -7
  248. package/dist/astro/routes/api/search/index.mjs.map +1 -1
  249. package/dist/astro/routes/api/search/rebuild.mjs +9 -8
  250. package/dist/astro/routes/api/search/rebuild.mjs.map +1 -1
  251. package/dist/astro/routes/api/search/stats.mjs +5 -5
  252. package/dist/astro/routes/api/search/suggest.mjs +8 -7
  253. package/dist/astro/routes/api/search/suggest.mjs.map +1 -1
  254. package/dist/astro/routes/api/sections/_slug_.mjs +8 -7
  255. package/dist/astro/routes/api/sections/_slug_.mjs.map +1 -1
  256. package/dist/astro/routes/api/sections/index.mjs +8 -7
  257. package/dist/astro/routes/api/sections/index.mjs.map +1 -1
  258. package/dist/astro/routes/api/settings/email.mjs +3 -3
  259. package/dist/astro/routes/api/settings.mjs +11 -9
  260. package/dist/astro/routes/api/settings.mjs.map +1 -1
  261. package/dist/astro/routes/api/setup/admin-verify.mjs +10 -9
  262. package/dist/astro/routes/api/setup/admin-verify.mjs.map +1 -1
  263. package/dist/astro/routes/api/setup/admin.mjs +9 -8
  264. package/dist/astro/routes/api/setup/admin.mjs.map +1 -1
  265. package/dist/astro/routes/api/setup/dev-bypass.mjs +19 -18
  266. package/dist/astro/routes/api/setup/dev-bypass.mjs.map +1 -1
  267. package/dist/astro/routes/api/setup/dev-reset.mjs +1 -1
  268. package/dist/astro/routes/api/setup/index.mjs +20 -18
  269. package/dist/astro/routes/api/setup/index.mjs.map +1 -1
  270. package/dist/astro/routes/api/setup/status.mjs +3 -3
  271. package/dist/astro/routes/api/snapshot.mjs +5 -4
  272. package/dist/astro/routes/api/snapshot.mjs.map +1 -1
  273. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs +11 -10
  274. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_/translations.mjs.map +1 -1
  275. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs +11 -10
  276. package/dist/astro/routes/api/taxonomies/_name_/terms/_slug_.mjs.map +1 -1
  277. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs +11 -10
  278. package/dist/astro/routes/api/taxonomies/_name_/terms/index.mjs.map +1 -1
  279. package/dist/astro/routes/api/taxonomies/index.mjs +11 -10
  280. package/dist/astro/routes/api/taxonomies/index.mjs.map +1 -1
  281. package/dist/astro/routes/api/themes/preview.mjs +5 -4
  282. package/dist/astro/routes/api/themes/preview.mjs.map +1 -1
  283. package/dist/astro/routes/api/typegen.mjs +4 -4
  284. package/dist/astro/routes/api/well-known/auth.mjs +1 -1
  285. package/dist/astro/routes/api/well-known/oauth-authorization-server.mjs +2 -2
  286. package/dist/astro/routes/api/well-known/oauth-protected-resource.mjs +2 -2
  287. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs +6 -5
  288. package/dist/astro/routes/api/widget-areas/_name_/reorder.mjs.map +1 -1
  289. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs +9 -8
  290. package/dist/astro/routes/api/widget-areas/_name_/widgets/_id_.mjs.map +1 -1
  291. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs +9 -8
  292. package/dist/astro/routes/api/widget-areas/_name_/widgets.mjs.map +1 -1
  293. package/dist/astro/routes/api/widget-areas/_name_.mjs +5 -5
  294. package/dist/astro/routes/api/widget-areas/index.mjs +9 -8
  295. package/dist/astro/routes/api/widget-areas/index.mjs.map +1 -1
  296. package/dist/astro/routes/api/widget-components.mjs +2 -2
  297. package/dist/astro/routes/robots.txt.mjs +5 -4
  298. package/dist/astro/routes/robots.txt.mjs.map +1 -1
  299. package/dist/astro/routes/sitemap-_collection_.xml.mjs +8 -7
  300. package/dist/astro/routes/sitemap-_collection_.xml.mjs.map +1 -1
  301. package/dist/astro/routes/sitemap.xml.mjs +6 -5
  302. package/dist/astro/routes/sitemap.xml.mjs.map +1 -1
  303. package/dist/astro/types.d.mts +12 -12
  304. package/dist/auth/providers/github.d.mts +1 -1
  305. package/dist/auth/providers/google.d.mts +1 -1
  306. package/dist/{authorize-DsMSVSaY.mjs → authorize-D5gfBVU5.mjs} +2 -2
  307. package/dist/{authorize-DsMSVSaY.mjs.map → authorize-D5gfBVU5.mjs.map} +1 -1
  308. package/dist/{byline-DUx48sJp.mjs → byline-V_Qp1Ziw.mjs} +27 -14
  309. package/dist/byline-V_Qp1Ziw.mjs.map +1 -0
  310. package/dist/{byline-fields-8TMtkBnH.mjs → byline-fields-B0NO1yUB.mjs} +3 -3
  311. package/dist/{byline-fields-8TMtkBnH.mjs.map → byline-fields-B0NO1yUB.mjs.map} +1 -1
  312. package/dist/{byline-fields-DbibsvTl.d.mts → byline-fields-CQJRIQkn.d.mts} +32 -32
  313. package/dist/{byline-fields-DbibsvTl.d.mts.map → byline-fields-CQJRIQkn.d.mts.map} +1 -1
  314. package/dist/{byline-fields--WxSNS79.mjs → byline-fields-nBVqK_Ff.mjs} +2 -2
  315. package/dist/{byline-fields--WxSNS79.mjs.map → byline-fields-nBVqK_Ff.mjs.map} +1 -1
  316. package/dist/{byline-registry-CWP7I71B.mjs → byline-registry-DedidtqC.mjs} +2 -2
  317. package/dist/{byline-registry-CWP7I71B.mjs.map → byline-registry-DedidtqC.mjs.map} +1 -1
  318. package/dist/{bylines-BdxWCnPL.mjs → bylines-B2NWnIwS.mjs} +2 -2
  319. package/dist/{bylines-BdxWCnPL.mjs.map → bylines-B2NWnIwS.mjs.map} +1 -1
  320. package/dist/{bylines-s8c2DXbH.mjs → bylines-DfGDnred.mjs} +7 -7
  321. package/dist/{bylines-s8c2DXbH.mjs.map → bylines-DfGDnred.mjs.map} +1 -1
  322. package/dist/{cache-B_HzASVT.mjs → cache-DTTHWD8n.mjs} +1 -1
  323. package/dist/{cache-B_HzASVT.mjs.map → cache-DTTHWD8n.mjs.map} +1 -1
  324. package/dist/{challenge-store-DXX3rfdI.mjs → challenge-store-woE0bbCf.mjs} +1 -1
  325. package/dist/{challenge-store-DXX3rfdI.mjs.map → challenge-store-woE0bbCf.mjs.map} +1 -1
  326. package/dist/cli/index.mjs +19 -18
  327. package/dist/cli/index.mjs.map +1 -1
  328. package/dist/client/cf-access.d.mts +1 -1
  329. package/dist/client/index.d.mts +1 -1
  330. package/dist/client/index.mjs +1 -1
  331. package/dist/{comments-Vkivawyl.mjs → comments-D2hNuxNa.mjs} +1 -1
  332. package/dist/{comments-Vkivawyl.mjs.map → comments-D2hNuxNa.mjs.map} +1 -1
  333. package/dist/{components-CK0cuUoH.mjs → components-DYKp2gmo.mjs} +1 -1
  334. package/dist/{components-CK0cuUoH.mjs.map → components-DYKp2gmo.mjs.map} +1 -1
  335. package/dist/{context-Y7BRkWes.mjs → context-Cm4pt1Ws.mjs} +5 -5
  336. package/dist/{context-Y7BRkWes.mjs.map → context-Cm4pt1Ws.mjs.map} +1 -1
  337. package/dist/{cron-BJ2ClIlj.mjs → cron-DdEVrQ2Y.mjs} +1 -1
  338. package/dist/{cron-BJ2ClIlj.mjs.map → cron-DdEVrQ2Y.mjs.map} +1 -1
  339. package/dist/{dashboard-2JgAMWxK.mjs → dashboard-C-UYpps0.mjs} +1 -1
  340. package/dist/{dashboard-2JgAMWxK.mjs.map → dashboard-C-UYpps0.mjs.map} +1 -1
  341. package/dist/db/index.d.mts +3 -3
  342. package/dist/db/libsql.d.mts +1 -1
  343. package/dist/db/postgres.d.mts +1 -1
  344. package/dist/db/sqlite.d.mts +1 -1
  345. package/dist/{db-errors-CtzxKBxe.mjs → db-errors-BluWkwGI.mjs} +1 -1
  346. package/dist/{db-errors-CtzxKBxe.mjs.map → db-errors-BluWkwGI.mjs.map} +1 -1
  347. package/dist/{default-IlBaTFxM.mjs → default-NHGuJzQ3.mjs} +1 -1
  348. package/dist/{default-IlBaTFxM.mjs.map → default-NHGuJzQ3.mjs.map} +1 -1
  349. package/dist/{device-flow-R23SIbQ2.mjs → device-flow-BQApWgnW.mjs} +4 -4
  350. package/dist/{device-flow-R23SIbQ2.mjs.map → device-flow-BQApWgnW.mjs.map} +1 -1
  351. package/dist/{email-console-DHT2Fbpj.mjs → email-console-BbU3RbWv.mjs} +1 -1
  352. package/dist/{email-console-DHT2Fbpj.mjs.map → email-console-BbU3RbWv.mjs.map} +1 -1
  353. package/dist/{error-RwM4dD35.mjs → error-CNn_w7jf.mjs} +1 -1
  354. package/dist/{error-RwM4dD35.mjs.map → error-CNn_w7jf.mjs.map} +1 -1
  355. package/dist/{escape-Ds07EEyu.mjs → escape-DPgcxcpL.mjs} +1 -1
  356. package/dist/{escape-Ds07EEyu.mjs.map → escape-DPgcxcpL.mjs.map} +1 -1
  357. package/dist/{fts-manager-1RgHmopc.mjs → fts-manager-Cx5z8jdA.mjs} +1 -1
  358. package/dist/{fts-manager-1RgHmopc.mjs.map → fts-manager-Cx5z8jdA.mjs.map} +1 -1
  359. package/dist/{hash-9w3pd3-m.mjs → hash-DlvIFn0b.mjs} +1 -1
  360. package/dist/{hash-9w3pd3-m.mjs.map → hash-DlvIFn0b.mjs.map} +1 -1
  361. package/dist/{import-Dh8bWmyq.mjs → import-KyxT1Mbs.mjs} +3 -3
  362. package/dist/{import-Dh8bWmyq.mjs.map → import-KyxT1Mbs.mjs.map} +1 -1
  363. package/dist/{index-B1keaX5Y.d.mts → index-D2VAiumu.d.mts} +15 -15
  364. package/dist/{index-B1keaX5Y.d.mts.map → index-D2VAiumu.d.mts.map} +1 -1
  365. package/dist/{index-DR56od45.d.mts → index-uT2yR66F.d.mts} +3 -3
  366. package/dist/{index-DR56od45.d.mts.map → index-uT2yR66F.d.mts.map} +1 -1
  367. package/dist/index.d.mts +16 -16
  368. package/dist/index.mjs +48 -46
  369. package/dist/init-lock-DlBHjf9-.mjs +83 -0
  370. package/dist/init-lock-DlBHjf9-.mjs.map +1 -0
  371. package/dist/{load-BBetCvLC.mjs → load-Dq91b_DK.mjs} +1 -1
  372. package/dist/{load-BBetCvLC.mjs.map → load-Dq91b_DK.mjs.map} +1 -1
  373. package/dist/{loader-ZN1ll-d-.mjs → loader-BqWjcH3h.mjs} +2 -2
  374. package/dist/{loader-ZN1ll-d-.mjs.map → loader-BqWjcH3h.mjs.map} +1 -1
  375. package/dist/{manifest-schema-BtwbL_vj.mjs → manifest-schema-DFPeqMAn.mjs} +1 -1
  376. package/dist/{manifest-schema-BtwbL_vj.mjs.map → manifest-schema-DFPeqMAn.mjs.map} +1 -1
  377. package/dist/media/index.d.mts +1 -1
  378. package/dist/media/index.mjs +2 -2
  379. package/dist/media/local-runtime.d.mts +11 -11
  380. package/dist/media/local-runtime.mjs +4 -3
  381. package/dist/media/local-runtime.mjs.map +1 -1
  382. package/dist/{media-allowlist-Dknq-OFY.mjs → media-allowlist-_A0SuDn4.mjs} +2 -2
  383. package/dist/{media-allowlist-Dknq-OFY.mjs.map → media-allowlist-_A0SuDn4.mjs.map} +1 -1
  384. package/dist/{media-url-VClf8glU.mjs → media-url-CqLd69IO.mjs} +1 -1
  385. package/dist/{media-url-VClf8glU.mjs.map → media-url-CqLd69IO.mjs.map} +1 -1
  386. package/dist/{menus-DrQLusqj.mjs → menus-Ryk9L7fT.mjs} +9 -9
  387. package/dist/{menus-DrQLusqj.mjs.map → menus-Ryk9L7fT.mjs.map} +1 -1
  388. package/dist/{mime-CCEzze7W.mjs → mime-YbtlEtvS.mjs} +1 -1
  389. package/dist/{mime-CCEzze7W.mjs.map → mime-YbtlEtvS.mjs.map} +1 -1
  390. package/dist/{mode-CO2vQHfq.mjs → mode-CGXzIbD8.mjs} +1 -1
  391. package/dist/{mode-CO2vQHfq.mjs.map → mode-CGXzIbD8.mjs.map} +1 -1
  392. package/dist/{normalize-CK5o04zr.mjs → normalize-DKsg36ty.mjs} +1 -1
  393. package/dist/{normalize-CK5o04zr.mjs.map → normalize-DKsg36ty.mjs.map} +1 -1
  394. package/dist/{oauth-authorization-Bw4NdF_S.mjs → oauth-authorization-C2kVyjXI.mjs} +4 -4
  395. package/dist/{oauth-authorization-Bw4NdF_S.mjs.map → oauth-authorization-C2kVyjXI.mjs.map} +1 -1
  396. package/dist/{oauth-clients-BGGFp57s.mjs → oauth-clients-BC873NCV.mjs} +1 -1
  397. package/dist/{oauth-clients-BGGFp57s.mjs.map → oauth-clients-BC873NCV.mjs.map} +1 -1
  398. package/dist/{oauth-state-store-97x0xtN2.mjs → oauth-state-store-Cd--TUaq.mjs} +1 -1
  399. package/dist/{oauth-state-store-97x0xtN2.mjs.map → oauth-state-store-Cd--TUaq.mjs.map} +1 -1
  400. package/dist/{oauth-user-lookup-B_vnZHKO.mjs → oauth-user-lookup-e4wOvDud.mjs} +1 -1
  401. package/dist/{oauth-user-lookup-B_vnZHKO.mjs.map → oauth-user-lookup-e4wOvDud.mjs.map} +1 -1
  402. package/dist/{options-DyYIYpPd.d.mts → options-9kLgkE8m.d.mts} +3 -3
  403. package/dist/{options-DyYIYpPd.d.mts.map → options-9kLgkE8m.d.mts.map} +1 -1
  404. package/dist/page/index.d.mts +2 -2
  405. package/dist/{parse-CrGndy1A.mjs → parse-DzSrk1t8.mjs} +2 -2
  406. package/dist/{parse-CrGndy1A.mjs.map → parse-DzSrk1t8.mjs.map} +1 -1
  407. package/dist/{passkey-config-C3QgnQnU.mjs → passkey-config-BpjbE_Uv.mjs} +1 -1
  408. package/dist/{passkey-config-C3QgnQnU.mjs.map → passkey-config-BpjbE_Uv.mjs.map} +1 -1
  409. package/dist/{placeholder-BZxr8W1j.mjs → placeholder-2xumZh4g.mjs} +1 -1
  410. package/dist/{placeholder-BZxr8W1j.mjs.map → placeholder-2xumZh4g.mjs.map} +1 -1
  411. package/dist/{placeholder-CVBv5z8k.d.mts → placeholder-BevVKfay.d.mts} +1 -1
  412. package/dist/{placeholder-CVBv5z8k.d.mts.map → placeholder-BevVKfay.d.mts.map} +1 -1
  413. package/dist/plugin-types.d.mts +1 -1
  414. package/dist/plugin-utils.d.mts +9 -9
  415. package/dist/plugins/adapt-sandbox-entry.d.mts +9 -9
  416. package/dist/plugins/adapt-sandbox-entry.mjs +2 -2
  417. package/dist/{preview-BfuRkVKW.mjs → preview-Dqv2hwXr.mjs} +2 -2
  418. package/dist/{preview-BfuRkVKW.mjs.map → preview-Dqv2hwXr.mjs.map} +1 -1
  419. package/dist/{public-url-BFVC2OTJ.mjs → public-url-D_zARuvZ.mjs} +1 -1
  420. package/dist/{public-url-BFVC2OTJ.mjs.map → public-url-D_zARuvZ.mjs.map} +1 -1
  421. package/dist/{query-CbUcI4Xk.mjs → query-Crm038Mc.mjs} +9 -9
  422. package/dist/{query-CbUcI4Xk.mjs.map → query-Crm038Mc.mjs.map} +1 -1
  423. package/dist/{rate-limit-C7hjdkS5.mjs → rate-limit-hRTBqmw1.mjs} +2 -2
  424. package/dist/{rate-limit-C7hjdkS5.mjs.map → rate-limit-hRTBqmw1.mjs.map} +1 -1
  425. package/dist/{redirect-B_q19j4v.mjs → redirect-C-OOkyku.mjs} +1 -1
  426. package/dist/{redirect-B_q19j4v.mjs.map → redirect-C-OOkyku.mjs.map} +1 -1
  427. package/dist/{redirects-CCbCqCCd.mjs → redirects-6Zg2SoYo.mjs} +8 -9
  428. package/dist/{redirects-CCbCqCCd.mjs.map → redirects-6Zg2SoYo.mjs.map} +1 -1
  429. package/dist/{redirects-DxVoR7PI.mjs → redirects-CP3TnTLO.mjs} +20 -14
  430. package/dist/redirects-CP3TnTLO.mjs.map +1 -0
  431. package/dist/{registry-brYh-rAT.mjs → registry-diMzD1Wf.mjs} +3 -3
  432. package/dist/{registry-brYh-rAT.mjs.map → registry-diMzD1Wf.mjs.map} +1 -1
  433. package/dist/{request-cache-D32LpnmI.mjs → request-cache-UwmBAiUK.mjs} +1 -1
  434. package/dist/{request-cache-D32LpnmI.mjs.map → request-cache-UwmBAiUK.mjs.map} +1 -1
  435. package/dist/{request-meta-7ByVLxB-.mjs → request-meta-DPechd0W.mjs} +2 -2
  436. package/dist/{request-meta-7ByVLxB-.mjs.map → request-meta-DPechd0W.mjs.map} +1 -1
  437. package/dist/{resolve-BqYMVG0D.mjs → resolve-B3NUUtVY.mjs} +1 -1
  438. package/dist/{resolve-BqYMVG0D.mjs.map → resolve-B3NUUtVY.mjs.map} +1 -1
  439. package/dist/{runner-DTdhuI9i.d.mts → runner-C8vcbvCe.d.mts} +2 -2
  440. package/dist/{runner-DTdhuI9i.d.mts.map → runner-C8vcbvCe.d.mts.map} +1 -1
  441. package/dist/runtime.d.mts +10 -10
  442. package/dist/runtime.mjs +1 -1
  443. package/dist/{schema-C1E70ug_.mjs → schema-BDOkd3OU.mjs} +4 -4
  444. package/dist/{schema-C1E70ug_.mjs.map → schema-BDOkd3OU.mjs.map} +1 -1
  445. package/dist/{search-B3SGZw91.mjs → search-Bs_J_EW-.mjs} +3 -3
  446. package/dist/{search-B3SGZw91.mjs.map → search-Bs_J_EW-.mjs.map} +1 -1
  447. package/dist/{secrets-ChPTmy9x.mjs → secrets-C8xmE6mR.mjs} +21 -11
  448. package/dist/secrets-C8xmE6mR.mjs.map +1 -0
  449. package/dist/{sections-D_lVzwRZ.mjs → sections-P0zuBlyz.mjs} +2 -2
  450. package/dist/{sections-D_lVzwRZ.mjs.map → sections-P0zuBlyz.mjs.map} +1 -1
  451. package/dist/seed/index.d.mts +2 -2
  452. package/dist/seed/index.mjs +14 -13
  453. package/dist/seo/index.d.mts +1 -1
  454. package/dist/seo/index.mjs +1 -1
  455. package/dist/{seo-D_LPkOtu.mjs → seo-CLhm-Fmb.mjs} +1 -1
  456. package/dist/{seo-D_LPkOtu.mjs.map → seo-CLhm-Fmb.mjs.map} +1 -1
  457. package/dist/{seo-B5e6y9Wk.mjs → seo-DpNgGQjF.mjs} +1 -1
  458. package/dist/{seo-B5e6y9Wk.mjs.map → seo-DpNgGQjF.mjs.map} +1 -1
  459. package/dist/{service-ChDcsTBs.mjs → service-CDQQnT8W.mjs} +2 -2
  460. package/dist/{service-ChDcsTBs.mjs.map → service-CDQQnT8W.mjs.map} +1 -1
  461. package/dist/{settings-DfxiWY_s.mjs → settings-BjBsmVAo.mjs} +10 -184
  462. package/dist/settings-BjBsmVAo.mjs.map +1 -0
  463. package/dist/{settings-Cv47v9u8.mjs → settings-sO0Fif4p.mjs} +2 -2
  464. package/dist/{settings-Cv47v9u8.mjs.map → settings-sO0Fif4p.mjs.map} +1 -1
  465. package/dist/{setup-complete-yvPE4OsP.mjs → setup-complete-CMMr-oZU.mjs} +1 -1
  466. package/dist/{setup-complete-yvPE4OsP.mjs.map → setup-complete-CMMr-oZU.mjs.map} +1 -1
  467. package/dist/{setup-nonce-C9aFzb94.mjs → setup-nonce-169xl4fV.mjs} +1 -1
  468. package/dist/{setup-nonce-C9aFzb94.mjs.map → setup-nonce-169xl4fV.mjs.map} +1 -1
  469. package/dist/single-flight-cache-C0UV1Npg.mjs +104 -0
  470. package/dist/single-flight-cache-C0UV1Npg.mjs.map +1 -0
  471. package/dist/{site-url-CnHlmAs9.mjs → site-url-vtsuOvSD.mjs} +1 -1
  472. package/dist/{site-url-CnHlmAs9.mjs.map → site-url-vtsuOvSD.mjs.map} +1 -1
  473. package/dist/{ssrf-BsVGIE0Z.mjs → ssrf-XO05Voq6.mjs} +1 -1
  474. package/dist/{ssrf-BsVGIE0Z.mjs.map → ssrf-XO05Voq6.mjs.map} +1 -1
  475. package/dist/status-2gZklYuj.mjs +30 -0
  476. package/dist/status-2gZklYuj.mjs.map +1 -0
  477. package/dist/storage/local.d.mts +1 -1
  478. package/dist/storage/local.mjs +2 -2
  479. package/dist/storage/s3.d.mts +1 -1
  480. package/dist/storage/s3.mjs +1 -1
  481. package/dist/{taxonomies-BdAmbOwx.mjs → taxonomies-BBxYA38v.mjs} +6 -6
  482. package/dist/{taxonomies-BdAmbOwx.mjs.map → taxonomies-BBxYA38v.mjs.map} +1 -1
  483. package/dist/{taxonomies-BILwiyGk.mjs → taxonomies-DuESHWKI.mjs} +2 -2
  484. package/dist/{taxonomies-BILwiyGk.mjs.map → taxonomies-DuESHWKI.mjs.map} +1 -1
  485. package/dist/{tokens-Bx2afeT-.mjs → tokens-DMkVjxrx.mjs} +1 -1
  486. package/dist/{tokens-Bx2afeT-.mjs.map → tokens-DMkVjxrx.mjs.map} +1 -1
  487. package/dist/{transport-CmpLD7W3.mjs → transport-1cIrOb1Y.mjs} +1 -1
  488. package/dist/{transport-CmpLD7W3.mjs.map → transport-1cIrOb1Y.mjs.map} +1 -1
  489. package/dist/{transport-B7PPP2CC.d.mts → transport-jdvsZEIt.d.mts} +1 -1
  490. package/dist/{transport-B7PPP2CC.d.mts.map → transport-jdvsZEIt.d.mts.map} +1 -1
  491. package/dist/{trusted-proxy-B4AfnoAp.mjs → trusted-proxy-CHp41Fjj.mjs} +1 -1
  492. package/dist/{trusted-proxy-B4AfnoAp.mjs.map → trusted-proxy-CHp41Fjj.mjs.map} +1 -1
  493. package/dist/{types-BFgrqwSk.d.mts → types-BFgYtuKd.d.mts} +1 -1
  494. package/dist/{types-BFgrqwSk.d.mts.map → types-BFgYtuKd.d.mts.map} +1 -1
  495. package/dist/{types-DZk_y-MU.mjs → types-BIduXPJk.mjs} +1 -1
  496. package/dist/{types-DZk_y-MU.mjs.map → types-BIduXPJk.mjs.map} +1 -1
  497. package/dist/{types-DTniiNto.d.mts → types-BTnnBYVX.d.mts} +2 -2
  498. package/dist/{types-DTniiNto.d.mts.map → types-BTnnBYVX.d.mts.map} +1 -1
  499. package/dist/{types-BUUVn1zr.d.mts → types-Bzfk2yC8.d.mts} +1 -1
  500. package/dist/{types-BUUVn1zr.d.mts.map → types-Bzfk2yC8.d.mts.map} +1 -1
  501. package/dist/{types-BH8-30hc.d.mts → types-CkEuk-Zr.d.mts} +1 -1
  502. package/dist/{types-BH8-30hc.d.mts.map → types-CkEuk-Zr.d.mts.map} +1 -1
  503. package/dist/{types-CPAPl93j.d.mts → types-DO7whVYU.d.mts} +2 -2
  504. package/dist/{types-CPAPl93j.d.mts.map → types-DO7whVYU.d.mts.map} +1 -1
  505. package/dist/{types-S15DXXNi.d.mts → types-DdkL6fyv.d.mts} +1 -1
  506. package/dist/{types-S15DXXNi.d.mts.map → types-DdkL6fyv.d.mts.map} +1 -1
  507. package/dist/{types-DpFmlNyB.mjs → types-DejCHqWT.mjs} +1 -1
  508. package/dist/{types-DpFmlNyB.mjs.map → types-DejCHqWT.mjs.map} +1 -1
  509. package/dist/{types-BPzXTV9x.d.mts → types-Del0VMij.d.mts} +1 -1
  510. package/dist/{types-BPzXTV9x.d.mts.map → types-Del0VMij.d.mts.map} +1 -1
  511. package/dist/{types-D4kUqbHh.d.mts → types-u_XxjbS8.d.mts} +1 -1
  512. package/dist/{types-D4kUqbHh.d.mts.map → types-u_XxjbS8.d.mts.map} +1 -1
  513. package/dist/{utils-C4Ih4DML.mjs → utils-C4M981Br.mjs} +1 -1
  514. package/dist/{utils-C4Ih4DML.mjs.map → utils-C4M981Br.mjs.map} +1 -1
  515. package/dist/{validate-Bz4vqcX1.mjs → validate-DGhQPXzI.mjs} +2 -2
  516. package/dist/{validate-Bz4vqcX1.mjs.map → validate-DGhQPXzI.mjs.map} +1 -1
  517. package/dist/{validate-CNwkPWzz.d.mts → validate-cJOiOvT2.d.mts} +5 -5
  518. package/dist/{validate-CNwkPWzz.d.mts.map → validate-cJOiOvT2.d.mts.map} +1 -1
  519. package/dist/{validation-DgGTJm3u.mjs → validation-DVHjPM1M.mjs} +5 -5
  520. package/dist/{validation-DgGTJm3u.mjs.map → validation-DVHjPM1M.mjs.map} +1 -1
  521. package/dist/version-BOjj_cfz.mjs +7 -0
  522. package/dist/{version-D-5txk2m.mjs.map → version-BOjj_cfz.mjs.map} +1 -1
  523. package/dist/{widgets-DZfmAbE4.mjs → widgets-Ci6hLwfO.mjs} +4 -4
  524. package/dist/{widgets-DZfmAbE4.mjs.map → widgets-Ci6hLwfO.mjs.map} +1 -1
  525. package/dist/{zod-generator-Djo_VHCt.mjs → zod-generator-CarzgPAu.mjs} +2 -2
  526. package/dist/{zod-generator-Djo_VHCt.mjs.map → zod-generator-CarzgPAu.mjs.map} +1 -1
  527. package/package.json +5 -5
  528. package/src/api/handlers/redirects.ts +24 -13
  529. package/src/api/schemas/redirects.ts +11 -4
  530. package/src/astro/integration/index.ts +44 -8
  531. package/src/astro/integration/routes.ts +46 -9
  532. package/src/astro/middleware/redirect.ts +12 -0
  533. package/src/bylines/field-defs-cache.ts +70 -20
  534. package/src/cli/commands/doctor.ts +1 -1
  535. package/src/config/secrets.ts +28 -14
  536. package/src/emdash-runtime.ts +5 -5
  537. package/src/redirects/status.ts +27 -0
  538. package/src/settings/index.ts +13 -13
  539. package/src/utils/{isolate-cache.ts → single-flight-cache.ts} +26 -21
  540. package/dist/byline-DUx48sJp.mjs.map +0 -1
  541. package/dist/redirects-DxVoR7PI.mjs.map +0 -1
  542. package/dist/secrets-ChPTmy9x.mjs.map +0 -1
  543. package/dist/settings-DfxiWY_s.mjs.map +0 -1
  544. package/dist/version-D-5txk2m.mjs +0 -7
  545. /package/dist/{api-tokens-Oq39ba-Z.mjs → api-tokens-C7ywRx7l.mjs} +0 -0
  546. /package/dist/{ssrf-BvgVcfNQ.mjs → ssrf-CRZGzjdL.mjs} +0 -0
  547. /package/dist/{types-CZI4E3qG.mjs → types-BoRm8-pp.mjs} +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"verify.mjs","names":[],"sources":["../../../../../../src/astro/routes/api/auth/passkey/verify.ts"],"sourcesContent":["/**\n * POST /_emdash/api/auth/passkey/verify\n *\n * Verify a passkey authentication and create a session\n */\n\nimport type { APIRoute } from \"astro\";\n\nexport const prerender = false;\n\nimport { createKyselyAdapter } from \"@emdash-cms/auth/adapters/kysely\";\nimport { authenticateWithPasskey, PasskeyAuthenticationError } from \"@emdash-cms/auth/passkey\";\n\nimport { apiError, apiSuccess, handleError } from \"#api/error.js\";\nimport { isParseError, parseBody } from \"#api/parse.js\";\nimport { getPublicOrigin } from \"#api/public-url.js\";\nimport { passkeyVerifyBody } from \"#api/schemas.js\";\nimport { getConfiguredAllowedOrigins, validateAllowedOrigins } from \"#auth/allowed-origins.js\";\nimport { createChallengeStore } from \"#auth/challenge-store.js\";\nimport { getPasskeyConfig } from \"#auth/passkey-config.js\";\nimport { OptionsRepository } from \"#db/repositories/options.js\";\n\nexport const POST: APIRoute = async ({ request, locals, session }) => {\n\tconst { emdash } = locals;\n\n\tif (!emdash?.db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\ttry {\n\t\tconst body = await parseBody(request, passkeyVerifyBody);\n\t\tif (isParseError(body)) return body;\n\n\t\t// Get passkey config\n\t\tconst url = new URL(request.url);\n\t\tconst options = new OptionsRepository(emdash.db);\n\t\tconst siteName = (await options.get<string>(\"emdash:site_title\")) ?? undefined;\n\t\tconst siteUrl = getPublicOrigin(url, emdash?.config);\n\t\tconst allowedOrigins = validateAllowedOrigins(\n\t\t\tsiteUrl,\n\t\t\tgetConfiguredAllowedOrigins(emdash?.config),\n\t\t);\n\t\tconst passkeyConfig = getPasskeyConfig(url, siteName, siteUrl, allowedOrigins);\n\n\t\t// Authenticate with passkey\n\t\tconst adapter = createKyselyAdapter(emdash.db);\n\t\tconst challengeStore = createChallengeStore(emdash.db);\n\n\t\tconst user = await authenticateWithPasskey(\n\t\t\tpasskeyConfig,\n\t\t\tadapter,\n\t\t\tbody.credential,\n\t\t\tchallengeStore,\n\t\t);\n\n\t\t// Create session\n\t\tif (session) {\n\t\t\tsession.set(\"user\", { id: user.id });\n\t\t}\n\n\t\treturn apiSuccess({\n\t\t\tsuccess: true,\n\t\t\tuser: {\n\t\t\t\tid: user.id,\n\t\t\t\temail: user.email,\n\t\t\t\tname: user.name,\n\t\t\t\trole: user.role,\n\t\t\t},\n\t\t});\n\t} catch (error) {\n\t\tif (error instanceof PasskeyAuthenticationError) {\n\t\t\treturn apiError(\"UNAUTHORIZED\", \"Authentication failed\", 401);\n\t\t}\n\n\t\treturn handleError(error, \"Authentication failed\", \"PASSKEY_VERIFY_ERROR\");\n\t}\n};\n"],"mappings":";;;;;;;;;;;;;;;;AAQA,MAAa,YAAY;AAczB,MAAa,OAAiB,OAAO,EAAE,SAAS,QAAQ,cAAc;CACrE,MAAM,EAAE,WAAW;AAEnB,KAAI,CAAC,QAAQ,GACZ,QAAO,SAAS,kBAAkB,6BAA6B,IAAI;AAGpE,KAAI;EACH,MAAM,OAAO,MAAM,UAAU,SAAS,kBAAkB;AACxD,MAAI,aAAa,KAAK,CAAE,QAAO;EAG/B,MAAM,MAAM,IAAI,IAAI,QAAQ,IAAI;EAEhC,MAAM,WAAY,MADF,IAAI,kBAAkB,OAAO,GAAG,CAChB,IAAY,oBAAoB,IAAK;EACrE,MAAM,UAAU,gBAAgB,KAAK,QAAQ,OAAO;EAKpD,MAAM,gBAAgB,iBAAiB,KAAK,UAAU,SAJ/B,uBACtB,SACA,4BAA4B,QAAQ,OAAO,CAC3C,CAC6E;EAG9E,MAAM,UAAU,oBAAoB,OAAO,GAAG;EAC9C,MAAM,iBAAiB,qBAAqB,OAAO,GAAG;EAEtD,MAAM,OAAO,MAAM,wBAClB,eACA,SACA,KAAK,YACL,eACA;AAGD,MAAI,QACH,SAAQ,IAAI,QAAQ,EAAE,IAAI,KAAK,IAAI,CAAC;AAGrC,SAAO,WAAW;GACjB,SAAS;GACT,MAAM;IACL,IAAI,KAAK;IACT,OAAO,KAAK;IACZ,MAAM,KAAK;IACX,MAAM,KAAK;IACX;GACD,CAAC;UACM,OAAO;AACf,MAAI,iBAAiB,2BACpB,QAAO,SAAS,gBAAgB,yBAAyB,IAAI;AAG9D,SAAO,YAAY,OAAO,yBAAyB,uBAAuB"}
1
+ {"version":3,"file":"verify.mjs","names":[],"sources":["../../../../../../src/astro/routes/api/auth/passkey/verify.ts"],"sourcesContent":["/**\n * POST /_emdash/api/auth/passkey/verify\n *\n * Verify a passkey authentication and create a session\n */\n\nimport type { APIRoute } from \"astro\";\n\nexport const prerender = false;\n\nimport { createKyselyAdapter } from \"@emdash-cms/auth/adapters/kysely\";\nimport { authenticateWithPasskey, PasskeyAuthenticationError } from \"@emdash-cms/auth/passkey\";\n\nimport { apiError, apiSuccess, handleError } from \"#api/error.js\";\nimport { isParseError, parseBody } from \"#api/parse.js\";\nimport { getPublicOrigin } from \"#api/public-url.js\";\nimport { passkeyVerifyBody } from \"#api/schemas.js\";\nimport { getConfiguredAllowedOrigins, validateAllowedOrigins } from \"#auth/allowed-origins.js\";\nimport { createChallengeStore } from \"#auth/challenge-store.js\";\nimport { getPasskeyConfig } from \"#auth/passkey-config.js\";\nimport { OptionsRepository } from \"#db/repositories/options.js\";\n\nexport const POST: APIRoute = async ({ request, locals, session }) => {\n\tconst { emdash } = locals;\n\n\tif (!emdash?.db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\ttry {\n\t\tconst body = await parseBody(request, passkeyVerifyBody);\n\t\tif (isParseError(body)) return body;\n\n\t\t// Get passkey config\n\t\tconst url = new URL(request.url);\n\t\tconst options = new OptionsRepository(emdash.db);\n\t\tconst siteName = (await options.get<string>(\"emdash:site_title\")) ?? undefined;\n\t\tconst siteUrl = getPublicOrigin(url, emdash?.config);\n\t\tconst allowedOrigins = validateAllowedOrigins(\n\t\t\tsiteUrl,\n\t\t\tgetConfiguredAllowedOrigins(emdash?.config),\n\t\t);\n\t\tconst passkeyConfig = getPasskeyConfig(url, siteName, siteUrl, allowedOrigins);\n\n\t\t// Authenticate with passkey\n\t\tconst adapter = createKyselyAdapter(emdash.db);\n\t\tconst challengeStore = createChallengeStore(emdash.db);\n\n\t\tconst user = await authenticateWithPasskey(\n\t\t\tpasskeyConfig,\n\t\t\tadapter,\n\t\t\tbody.credential,\n\t\t\tchallengeStore,\n\t\t);\n\n\t\t// Create session\n\t\tif (session) {\n\t\t\tsession.set(\"user\", { id: user.id });\n\t\t}\n\n\t\treturn apiSuccess({\n\t\t\tsuccess: true,\n\t\t\tuser: {\n\t\t\t\tid: user.id,\n\t\t\t\temail: user.email,\n\t\t\t\tname: user.name,\n\t\t\t\trole: user.role,\n\t\t\t},\n\t\t});\n\t} catch (error) {\n\t\tif (error instanceof PasskeyAuthenticationError) {\n\t\t\treturn apiError(\"UNAUTHORIZED\", \"Authentication failed\", 401);\n\t\t}\n\n\t\treturn handleError(error, \"Authentication failed\", \"PASSKEY_VERIFY_ERROR\");\n\t}\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AAQA,MAAa,YAAY;AAczB,MAAa,OAAiB,OAAO,EAAE,SAAS,QAAQ,cAAc;CACrE,MAAM,EAAE,WAAW;AAEnB,KAAI,CAAC,QAAQ,GACZ,QAAO,SAAS,kBAAkB,6BAA6B,IAAI;AAGpE,KAAI;EACH,MAAM,OAAO,MAAM,UAAU,SAAS,kBAAkB;AACxD,MAAI,aAAa,KAAK,CAAE,QAAO;EAG/B,MAAM,MAAM,IAAI,IAAI,QAAQ,IAAI;EAEhC,MAAM,WAAY,MADF,IAAI,kBAAkB,OAAO,GAAG,CAChB,IAAY,oBAAoB,IAAK;EACrE,MAAM,UAAU,gBAAgB,KAAK,QAAQ,OAAO;EAKpD,MAAM,gBAAgB,iBAAiB,KAAK,UAAU,SAJ/B,uBACtB,SACA,4BAA4B,QAAQ,OAAO,CAC3C,CAC6E;EAG9E,MAAM,UAAU,oBAAoB,OAAO,GAAG;EAC9C,MAAM,iBAAiB,qBAAqB,OAAO,GAAG;EAEtD,MAAM,OAAO,MAAM,wBAClB,eACA,SACA,KAAK,YACL,eACA;AAGD,MAAI,QACH,SAAQ,IAAI,QAAQ,EAAE,IAAI,KAAK,IAAI,CAAC;AAGrC,SAAO,WAAW;GACjB,SAAS;GACT,MAAM;IACL,IAAI,KAAK;IACT,OAAO,KAAK;IACZ,MAAM,KAAK;IACX,MAAM,KAAK;IACX;GACD,CAAC;UACM,OAAO;AACf,MAAI,iBAAiB,2BACpB,QAAO,SAAS,gBAAgB,yBAAyB,IAAI;AAG9D,SAAO,YAAY,OAAO,yBAAyB,uBAAuB"}
@@ -1,15 +1,16 @@
1
1
  import "../../../../../base64-CqR-7kqF.mjs";
2
2
  import "../../../../../types-BXSUSAjt.mjs";
3
3
  import { t as OptionsRepository } from "../../../../../options-BPCVnesz.mjs";
4
- import { n as apiSuccess, r as handleError, t as apiError } from "../../../../../error-RwM4dD35.mjs";
5
- import { n as parseBody, t as isParseError } from "../../../../../parse-CrGndy1A.mjs";
6
- import "../../../../../redirects-CCbCqCCd.mjs";
7
- import { O as signupCompleteBody } from "../../../../../byline-fields-8TMtkBnH.mjs";
4
+ import { n as apiSuccess, r as handleError, t as apiError } from "../../../../../error-CNn_w7jf.mjs";
5
+ import { n as parseBody, t as isParseError } from "../../../../../parse-DzSrk1t8.mjs";
6
+ import "../../../../../redirects-6Zg2SoYo.mjs";
7
+ import { O as signupCompleteBody } from "../../../../../byline-fields-B0NO1yUB.mjs";
8
+ import "../../../../../status-2gZklYuj.mjs";
8
9
  import "../../../../../api/schemas/index.mjs";
9
- import { n as getPublicOrigin } from "../../../../../public-url-BFVC2OTJ.mjs";
10
- import { n as validateAllowedOrigins, t as getConfiguredAllowedOrigins } from "../../../../../allowed-origins-B1u7Qnvg.mjs";
11
- import { n as createChallengeStore } from "../../../../../challenge-store-DXX3rfdI.mjs";
12
- import { t as getPasskeyConfig } from "../../../../../passkey-config-C3QgnQnU.mjs";
10
+ import { n as getPublicOrigin } from "../../../../../public-url-D_zARuvZ.mjs";
11
+ import { n as validateAllowedOrigins, t as getConfiguredAllowedOrigins } from "../../../../../allowed-origins-BqC8cul8.mjs";
12
+ import { n as createChallengeStore } from "../../../../../challenge-store-woE0bbCf.mjs";
13
+ import { t as getPasskeyConfig } from "../../../../../passkey-config-BpjbE_Uv.mjs";
13
14
  import { createKyselyAdapter } from "@emdash-cms/auth/adapters/kysely";
14
15
  import { SignupError, completeSignup } from "@emdash-cms/auth";
15
16
  import { registerPasskey, verifyRegistrationResponse } from "@emdash-cms/auth/passkey";
@@ -1 +1 @@
1
- {"version":3,"file":"complete.mjs","names":[],"sources":["../../../../../../src/astro/routes/api/auth/signup/complete.ts"],"sourcesContent":["/**\n * POST /_emdash/api/auth/signup/complete\n *\n * Complete self-signup by registering a passkey for the new user.\n * This creates the user account and establishes a session.\n */\n\nimport type { APIRoute } from \"astro\";\n\nexport const prerender = false;\n\nimport { completeSignup, SignupError } from \"@emdash-cms/auth\";\nimport { createKyselyAdapter } from \"@emdash-cms/auth/adapters/kysely\";\nimport { verifyRegistrationResponse, registerPasskey } from \"@emdash-cms/auth/passkey\";\n\nimport { apiError, apiSuccess, handleError } from \"#api/error.js\";\nimport { isParseError, parseBody } from \"#api/parse.js\";\nimport { getPublicOrigin } from \"#api/public-url.js\";\nimport { signupCompleteBody } from \"#api/schemas.js\";\nimport { getConfiguredAllowedOrigins, validateAllowedOrigins } from \"#auth/allowed-origins.js\";\nimport { createChallengeStore } from \"#auth/challenge-store.js\";\nimport { getPasskeyConfig } from \"#auth/passkey-config.js\";\nimport { OptionsRepository } from \"#db/repositories/options.js\";\n\nexport const POST: APIRoute = async ({ request, locals, session }) => {\n\tconst { emdash } = locals;\n\n\tif (!emdash?.db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\ttry {\n\t\tconst body = await parseBody(request, signupCompleteBody);\n\t\tif (isParseError(body)) return body;\n\n\t\tconst adapter = createKyselyAdapter(emdash.db);\n\n\t\t// Get passkey config\n\t\tconst url = new URL(request.url);\n\t\tconst options = new OptionsRepository(emdash.db);\n\t\tconst siteName = (await options.get<string>(\"emdash:site_title\")) ?? undefined;\n\t\tconst siteUrl = getPublicOrigin(url, emdash?.config);\n\t\tconst allowedOrigins = validateAllowedOrigins(\n\t\t\tsiteUrl,\n\t\t\tgetConfiguredAllowedOrigins(emdash?.config),\n\t\t);\n\t\tconst passkeyConfig = getPasskeyConfig(url, siteName, siteUrl, allowedOrigins);\n\n\t\t// Verify the passkey registration response\n\t\tconst challengeStore = createChallengeStore(emdash.db);\n\t\tconst verified = await verifyRegistrationResponse(\n\t\t\tpasskeyConfig,\n\t\t\tbody.credential,\n\t\t\tchallengeStore,\n\t\t);\n\n\t\t// Complete the signup - creates the user\n\t\tconst user = await completeSignup(adapter, body.token, {\n\t\t\tname: body.name,\n\t\t});\n\n\t\t// Register the passkey for the new user\n\t\tawait registerPasskey(adapter, user.id, verified, \"Initial passkey\");\n\n\t\t// Create session\n\t\tif (session) {\n\t\t\tsession.set(\"user\", { id: user.id });\n\t\t}\n\n\t\treturn apiSuccess({\n\t\t\tsuccess: true,\n\t\t\tuser: {\n\t\t\t\tid: user.id,\n\t\t\t\temail: user.email,\n\t\t\t\tname: user.name,\n\t\t\t\trole: user.role,\n\t\t\t},\n\t\t});\n\t} catch (error) {\n\t\tif (error instanceof SignupError) {\n\t\t\tconst statusMap: Record<string, number> = {\n\t\t\t\tinvalid_token: 404,\n\t\t\t\ttoken_expired: 410,\n\t\t\t\tuser_exists: 409,\n\t\t\t\tdomain_not_allowed: 403,\n\t\t\t};\n\t\t\treturn apiError(error.code.toUpperCase(), error.message, statusMap[error.code] ?? 400);\n\t\t}\n\n\t\treturn handleError(error, \"Failed to complete signup\", \"SIGNUP_COMPLETE_ERROR\");\n\t}\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AASA,MAAa,YAAY;AAezB,MAAa,OAAiB,OAAO,EAAE,SAAS,QAAQ,cAAc;CACrE,MAAM,EAAE,WAAW;AAEnB,KAAI,CAAC,QAAQ,GACZ,QAAO,SAAS,kBAAkB,6BAA6B,IAAI;AAGpE,KAAI;EACH,MAAM,OAAO,MAAM,UAAU,SAAS,mBAAmB;AACzD,MAAI,aAAa,KAAK,CAAE,QAAO;EAE/B,MAAM,UAAU,oBAAoB,OAAO,GAAG;EAG9C,MAAM,MAAM,IAAI,IAAI,QAAQ,IAAI;EAEhC,MAAM,WAAY,MADF,IAAI,kBAAkB,OAAO,GAAG,CAChB,IAAY,oBAAoB,IAAK;EACrE,MAAM,UAAU,gBAAgB,KAAK,QAAQ,OAAO;EAKpD,MAAM,gBAAgB,iBAAiB,KAAK,UAAU,SAJ/B,uBACtB,SACA,4BAA4B,QAAQ,OAAO,CAC3C,CAC6E;EAG9E,MAAM,iBAAiB,qBAAqB,OAAO,GAAG;EACtD,MAAM,WAAW,MAAM,2BACtB,eACA,KAAK,YACL,eACA;EAGD,MAAM,OAAO,MAAM,eAAe,SAAS,KAAK,OAAO,EACtD,MAAM,KAAK,MACX,CAAC;AAGF,QAAM,gBAAgB,SAAS,KAAK,IAAI,UAAU,kBAAkB;AAGpE,MAAI,QACH,SAAQ,IAAI,QAAQ,EAAE,IAAI,KAAK,IAAI,CAAC;AAGrC,SAAO,WAAW;GACjB,SAAS;GACT,MAAM;IACL,IAAI,KAAK;IACT,OAAO,KAAK;IACZ,MAAM,KAAK;IACX,MAAM,KAAK;IACX;GACD,CAAC;UACM,OAAO;AACf,MAAI,iBAAiB,YAOpB,QAAO,SAAS,MAAM,KAAK,aAAa,EAAE,MAAM,SANN;GACzC,eAAe;GACf,eAAe;GACf,aAAa;GACb,oBAAoB;GACpB,CACkE,MAAM,SAAS,IAAI;AAGvF,SAAO,YAAY,OAAO,6BAA6B,wBAAwB"}
1
+ {"version":3,"file":"complete.mjs","names":[],"sources":["../../../../../../src/astro/routes/api/auth/signup/complete.ts"],"sourcesContent":["/**\n * POST /_emdash/api/auth/signup/complete\n *\n * Complete self-signup by registering a passkey for the new user.\n * This creates the user account and establishes a session.\n */\n\nimport type { APIRoute } from \"astro\";\n\nexport const prerender = false;\n\nimport { completeSignup, SignupError } from \"@emdash-cms/auth\";\nimport { createKyselyAdapter } from \"@emdash-cms/auth/adapters/kysely\";\nimport { verifyRegistrationResponse, registerPasskey } from \"@emdash-cms/auth/passkey\";\n\nimport { apiError, apiSuccess, handleError } from \"#api/error.js\";\nimport { isParseError, parseBody } from \"#api/parse.js\";\nimport { getPublicOrigin } from \"#api/public-url.js\";\nimport { signupCompleteBody } from \"#api/schemas.js\";\nimport { getConfiguredAllowedOrigins, validateAllowedOrigins } from \"#auth/allowed-origins.js\";\nimport { createChallengeStore } from \"#auth/challenge-store.js\";\nimport { getPasskeyConfig } from \"#auth/passkey-config.js\";\nimport { OptionsRepository } from \"#db/repositories/options.js\";\n\nexport const POST: APIRoute = async ({ request, locals, session }) => {\n\tconst { emdash } = locals;\n\n\tif (!emdash?.db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\ttry {\n\t\tconst body = await parseBody(request, signupCompleteBody);\n\t\tif (isParseError(body)) return body;\n\n\t\tconst adapter = createKyselyAdapter(emdash.db);\n\n\t\t// Get passkey config\n\t\tconst url = new URL(request.url);\n\t\tconst options = new OptionsRepository(emdash.db);\n\t\tconst siteName = (await options.get<string>(\"emdash:site_title\")) ?? undefined;\n\t\tconst siteUrl = getPublicOrigin(url, emdash?.config);\n\t\tconst allowedOrigins = validateAllowedOrigins(\n\t\t\tsiteUrl,\n\t\t\tgetConfiguredAllowedOrigins(emdash?.config),\n\t\t);\n\t\tconst passkeyConfig = getPasskeyConfig(url, siteName, siteUrl, allowedOrigins);\n\n\t\t// Verify the passkey registration response\n\t\tconst challengeStore = createChallengeStore(emdash.db);\n\t\tconst verified = await verifyRegistrationResponse(\n\t\t\tpasskeyConfig,\n\t\t\tbody.credential,\n\t\t\tchallengeStore,\n\t\t);\n\n\t\t// Complete the signup - creates the user\n\t\tconst user = await completeSignup(adapter, body.token, {\n\t\t\tname: body.name,\n\t\t});\n\n\t\t// Register the passkey for the new user\n\t\tawait registerPasskey(adapter, user.id, verified, \"Initial passkey\");\n\n\t\t// Create session\n\t\tif (session) {\n\t\t\tsession.set(\"user\", { id: user.id });\n\t\t}\n\n\t\treturn apiSuccess({\n\t\t\tsuccess: true,\n\t\t\tuser: {\n\t\t\t\tid: user.id,\n\t\t\t\temail: user.email,\n\t\t\t\tname: user.name,\n\t\t\t\trole: user.role,\n\t\t\t},\n\t\t});\n\t} catch (error) {\n\t\tif (error instanceof SignupError) {\n\t\t\tconst statusMap: Record<string, number> = {\n\t\t\t\tinvalid_token: 404,\n\t\t\t\ttoken_expired: 410,\n\t\t\t\tuser_exists: 409,\n\t\t\t\tdomain_not_allowed: 403,\n\t\t\t};\n\t\t\treturn apiError(error.code.toUpperCase(), error.message, statusMap[error.code] ?? 400);\n\t\t}\n\n\t\treturn handleError(error, \"Failed to complete signup\", \"SIGNUP_COMPLETE_ERROR\");\n\t}\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AASA,MAAa,YAAY;AAezB,MAAa,OAAiB,OAAO,EAAE,SAAS,QAAQ,cAAc;CACrE,MAAM,EAAE,WAAW;AAEnB,KAAI,CAAC,QAAQ,GACZ,QAAO,SAAS,kBAAkB,6BAA6B,IAAI;AAGpE,KAAI;EACH,MAAM,OAAO,MAAM,UAAU,SAAS,mBAAmB;AACzD,MAAI,aAAa,KAAK,CAAE,QAAO;EAE/B,MAAM,UAAU,oBAAoB,OAAO,GAAG;EAG9C,MAAM,MAAM,IAAI,IAAI,QAAQ,IAAI;EAEhC,MAAM,WAAY,MADF,IAAI,kBAAkB,OAAO,GAAG,CAChB,IAAY,oBAAoB,IAAK;EACrE,MAAM,UAAU,gBAAgB,KAAK,QAAQ,OAAO;EAKpD,MAAM,gBAAgB,iBAAiB,KAAK,UAAU,SAJ/B,uBACtB,SACA,4BAA4B,QAAQ,OAAO,CAC3C,CAC6E;EAG9E,MAAM,iBAAiB,qBAAqB,OAAO,GAAG;EACtD,MAAM,WAAW,MAAM,2BACtB,eACA,KAAK,YACL,eACA;EAGD,MAAM,OAAO,MAAM,eAAe,SAAS,KAAK,OAAO,EACtD,MAAM,KAAK,MACX,CAAC;AAGF,QAAM,gBAAgB,SAAS,KAAK,IAAI,UAAU,kBAAkB;AAGpE,MAAI,QACH,SAAQ,IAAI,QAAQ,EAAE,IAAI,KAAK,IAAI,CAAC;AAGrC,SAAO,WAAW;GACjB,SAAS;GACT,MAAM;IACL,IAAI,KAAK;IACT,OAAO,KAAK;IACZ,MAAM,KAAK;IACX,MAAM,KAAK;IACX;GACD,CAAC;UACM,OAAO;AACf,MAAI,iBAAiB,YAOpB,QAAO,SAAS,MAAM,KAAK,aAAa,EAAE,MAAM,SANN;GACzC,eAAe;GACf,eAAe;GACf,aAAa;GACb,oBAAoB;GACpB,CACkE,MAAM,SAAS,IAAI;AAGvF,SAAO,YAAY,OAAO,6BAA6B,wBAAwB"}
@@ -1,14 +1,15 @@
1
1
  import "../../../../../base64-CqR-7kqF.mjs";
2
2
  import "../../../../../types-BXSUSAjt.mjs";
3
3
  import { t as OptionsRepository } from "../../../../../options-BPCVnesz.mjs";
4
- import { n as apiSuccess, t as apiError } from "../../../../../error-RwM4dD35.mjs";
5
- import { n as parseBody, t as isParseError } from "../../../../../parse-CrGndy1A.mjs";
6
- import "../../../../../redirects-CCbCqCCd.mjs";
7
- import { k as signupRequestBody } from "../../../../../byline-fields-8TMtkBnH.mjs";
4
+ import { n as apiSuccess, t as apiError } from "../../../../../error-CNn_w7jf.mjs";
5
+ import { n as parseBody, t as isParseError } from "../../../../../parse-DzSrk1t8.mjs";
6
+ import "../../../../../redirects-6Zg2SoYo.mjs";
7
+ import { k as signupRequestBody } from "../../../../../byline-fields-B0NO1yUB.mjs";
8
+ import "../../../../../status-2gZklYuj.mjs";
8
9
  import "../../../../../api/schemas/index.mjs";
9
- import { t as getTrustedProxyHeaders } from "../../../../../trusted-proxy-B4AfnoAp.mjs";
10
- import { t as getSiteBaseUrl } from "../../../../../site-url-CnHlmAs9.mjs";
11
- import { n as getClientIp, t as checkRateLimit } from "../../../../../rate-limit-C7hjdkS5.mjs";
10
+ import { t as getTrustedProxyHeaders } from "../../../../../trusted-proxy-CHp41Fjj.mjs";
11
+ import { t as getSiteBaseUrl } from "../../../../../site-url-vtsuOvSD.mjs";
12
+ import { n as getClientIp, t as checkRateLimit } from "../../../../../rate-limit-hRTBqmw1.mjs";
12
13
  import { createKyselyAdapter } from "@emdash-cms/auth/adapters/kysely";
13
14
  import { requestSignup } from "@emdash-cms/auth";
14
15
 
@@ -1 +1 @@
1
- {"version":3,"file":"request.mjs","names":[],"sources":["../../../../../../src/astro/routes/api/auth/signup/request.ts"],"sourcesContent":["/**\n * POST /_emdash/api/auth/signup/request\n *\n * Request self-signup. Sends verification email if domain is allowed.\n * Always returns 200 to prevent email enumeration.\n *\n * Rate limited: 3 requests per 5 minutes per IP. Mirrors magic-link/send.\n */\n\nimport type { APIRoute } from \"astro\";\n\nexport const prerender = false;\n\nimport { requestSignup } from \"@emdash-cms/auth\";\nimport { createKyselyAdapter } from \"@emdash-cms/auth/adapters/kysely\";\n\nimport { apiError, apiSuccess } from \"#api/error.js\";\nimport { isParseError, parseBody } from \"#api/parse.js\";\nimport { signupRequestBody } from \"#api/schemas.js\";\nimport { getSiteBaseUrl } from \"#api/site-url.js\";\nimport { checkRateLimit, getClientIp } from \"#auth/rate-limit.js\";\nimport { getTrustedProxyHeaders } from \"#auth/trusted-proxy.js\";\nimport { OptionsRepository } from \"#db/repositories/options.js\";\n\n// Generic response body used for both the real success path and the\n// rate-limited / domain-disallowed paths. Keeping them identical prevents\n// the caller from distinguishing between them.\nconst GENERIC_SUCCESS = {\n\tsuccess: true,\n\tmessage: \"If your email domain is allowed, you'll receive a verification email.\",\n};\n\nexport const POST: APIRoute = async ({ request, locals }) => {\n\tconst { emdash } = locals;\n\n\tif (!emdash?.db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\t// Check if email pipeline is available\n\tif (!emdash.email?.isAvailable()) {\n\t\treturn apiError(\n\t\t\t\"EMAIL_NOT_CONFIGURED\",\n\t\t\t\"Email not configured. Self-signup is unavailable.\",\n\t\t\t503,\n\t\t);\n\t}\n\n\ttry {\n\t\t// Parse the body first — this avoids burning a rate-limit slot on\n\t\t// malformed input and keeps the timing of the rate-limited and\n\t\t// real paths aligned.\n\t\tconst body = await parseBody(request, signupRequestBody);\n\t\tif (isParseError(body)) return body;\n\n\t\t// Rate limit: 3 requests per 300 seconds per IP. Matches magic-link/send.\n\t\tconst ip = getClientIp(request, getTrustedProxyHeaders(emdash.config));\n\t\tconst rateLimit = await checkRateLimit(emdash.db, ip, \"signup/request\", 3, 300);\n\t\tif (!rateLimit.allowed) {\n\t\t\t// Return success-shaped response to avoid revealing rate limiting\n\t\t\t// (and by extension, the fact that the caller is probing).\n\t\t\treturn apiSuccess(GENERIC_SUCCESS);\n\t\t}\n\n\t\tconst adapter = createKyselyAdapter(emdash.db);\n\n\t\t// Get site config for signup email\n\t\tconst options = new OptionsRepository(emdash.db);\n\t\tconst siteName = (await options.get<string>(\"emdash:site_title\")) || \"EmDash\";\n\n\t\t// Use stored site URL to prevent Host header spoofing in signup emails\n\t\tconst baseUrl = await getSiteBaseUrl(emdash.db, request);\n\n\t\t// Request signup - this handles all checks internally and fails silently\n\t\t// if domain not allowed or user exists (to prevent enumeration)\n\t\tawait requestSignup(\n\t\t\t{\n\t\t\t\tbaseUrl,\n\t\t\t\tsiteName,\n\t\t\t\temail: (message) => emdash.email!.send(message, \"system\"),\n\t\t\t},\n\t\t\tadapter,\n\t\t\tbody.email.toLowerCase().trim(),\n\t\t);\n\n\t\t// Always return success to prevent email enumeration\n\t\treturn apiSuccess(GENERIC_SUCCESS);\n\t} catch (error) {\n\t\tconsole.error(\"Signup request error:\", error);\n\n\t\t// Don't reveal internal errors - just return generic success\n\t\t// to prevent information leakage\n\t\treturn apiSuccess(GENERIC_SUCCESS);\n\t}\n};\n"],"mappings":";;;;;;;;;;;;;;;AAWA,MAAa,YAAY;AAgBzB,MAAM,kBAAkB;CACvB,SAAS;CACT,SAAS;CACT;AAED,MAAa,OAAiB,OAAO,EAAE,SAAS,aAAa;CAC5D,MAAM,EAAE,WAAW;AAEnB,KAAI,CAAC,QAAQ,GACZ,QAAO,SAAS,kBAAkB,6BAA6B,IAAI;AAIpE,KAAI,CAAC,OAAO,OAAO,aAAa,CAC/B,QAAO,SACN,wBACA,qDACA,IACA;AAGF,KAAI;EAIH,MAAM,OAAO,MAAM,UAAU,SAAS,kBAAkB;AACxD,MAAI,aAAa,KAAK,CAAE,QAAO;EAG/B,MAAM,KAAK,YAAY,SAAS,uBAAuB,OAAO,OAAO,CAAC;AAEtE,MAAI,EADc,MAAM,eAAe,OAAO,IAAI,IAAI,kBAAkB,GAAG,IAAI,EAChE,QAGd,QAAO,WAAW,gBAAgB;EAGnC,MAAM,UAAU,oBAAoB,OAAO,GAAG;EAI9C,MAAM,WAAY,MADF,IAAI,kBAAkB,OAAO,GAAG,CAChB,IAAY,oBAAoB,IAAK;AAOrE,QAAM,cACL;GACC,SANc,MAAM,eAAe,OAAO,IAAI,QAAQ;GAOtD;GACA,QAAQ,YAAY,OAAO,MAAO,KAAK,SAAS,SAAS;GACzD,EACD,SACA,KAAK,MAAM,aAAa,CAAC,MAAM,CAC/B;AAGD,SAAO,WAAW,gBAAgB;UAC1B,OAAO;AACf,UAAQ,MAAM,yBAAyB,MAAM;AAI7C,SAAO,WAAW,gBAAgB"}
1
+ {"version":3,"file":"request.mjs","names":[],"sources":["../../../../../../src/astro/routes/api/auth/signup/request.ts"],"sourcesContent":["/**\n * POST /_emdash/api/auth/signup/request\n *\n * Request self-signup. Sends verification email if domain is allowed.\n * Always returns 200 to prevent email enumeration.\n *\n * Rate limited: 3 requests per 5 minutes per IP. Mirrors magic-link/send.\n */\n\nimport type { APIRoute } from \"astro\";\n\nexport const prerender = false;\n\nimport { requestSignup } from \"@emdash-cms/auth\";\nimport { createKyselyAdapter } from \"@emdash-cms/auth/adapters/kysely\";\n\nimport { apiError, apiSuccess } from \"#api/error.js\";\nimport { isParseError, parseBody } from \"#api/parse.js\";\nimport { signupRequestBody } from \"#api/schemas.js\";\nimport { getSiteBaseUrl } from \"#api/site-url.js\";\nimport { checkRateLimit, getClientIp } from \"#auth/rate-limit.js\";\nimport { getTrustedProxyHeaders } from \"#auth/trusted-proxy.js\";\nimport { OptionsRepository } from \"#db/repositories/options.js\";\n\n// Generic response body used for both the real success path and the\n// rate-limited / domain-disallowed paths. Keeping them identical prevents\n// the caller from distinguishing between them.\nconst GENERIC_SUCCESS = {\n\tsuccess: true,\n\tmessage: \"If your email domain is allowed, you'll receive a verification email.\",\n};\n\nexport const POST: APIRoute = async ({ request, locals }) => {\n\tconst { emdash } = locals;\n\n\tif (!emdash?.db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\t// Check if email pipeline is available\n\tif (!emdash.email?.isAvailable()) {\n\t\treturn apiError(\n\t\t\t\"EMAIL_NOT_CONFIGURED\",\n\t\t\t\"Email not configured. Self-signup is unavailable.\",\n\t\t\t503,\n\t\t);\n\t}\n\n\ttry {\n\t\t// Parse the body first — this avoids burning a rate-limit slot on\n\t\t// malformed input and keeps the timing of the rate-limited and\n\t\t// real paths aligned.\n\t\tconst body = await parseBody(request, signupRequestBody);\n\t\tif (isParseError(body)) return body;\n\n\t\t// Rate limit: 3 requests per 300 seconds per IP. Matches magic-link/send.\n\t\tconst ip = getClientIp(request, getTrustedProxyHeaders(emdash.config));\n\t\tconst rateLimit = await checkRateLimit(emdash.db, ip, \"signup/request\", 3, 300);\n\t\tif (!rateLimit.allowed) {\n\t\t\t// Return success-shaped response to avoid revealing rate limiting\n\t\t\t// (and by extension, the fact that the caller is probing).\n\t\t\treturn apiSuccess(GENERIC_SUCCESS);\n\t\t}\n\n\t\tconst adapter = createKyselyAdapter(emdash.db);\n\n\t\t// Get site config for signup email\n\t\tconst options = new OptionsRepository(emdash.db);\n\t\tconst siteName = (await options.get<string>(\"emdash:site_title\")) || \"EmDash\";\n\n\t\t// Use stored site URL to prevent Host header spoofing in signup emails\n\t\tconst baseUrl = await getSiteBaseUrl(emdash.db, request);\n\n\t\t// Request signup - this handles all checks internally and fails silently\n\t\t// if domain not allowed or user exists (to prevent enumeration)\n\t\tawait requestSignup(\n\t\t\t{\n\t\t\t\tbaseUrl,\n\t\t\t\tsiteName,\n\t\t\t\temail: (message) => emdash.email!.send(message, \"system\"),\n\t\t\t},\n\t\t\tadapter,\n\t\t\tbody.email.toLowerCase().trim(),\n\t\t);\n\n\t\t// Always return success to prevent email enumeration\n\t\treturn apiSuccess(GENERIC_SUCCESS);\n\t} catch (error) {\n\t\tconsole.error(\"Signup request error:\", error);\n\n\t\t// Don't reveal internal errors - just return generic success\n\t\t// to prevent information leakage\n\t\treturn apiSuccess(GENERIC_SUCCESS);\n\t}\n};\n"],"mappings":";;;;;;;;;;;;;;;;AAWA,MAAa,YAAY;AAgBzB,MAAM,kBAAkB;CACvB,SAAS;CACT,SAAS;CACT;AAED,MAAa,OAAiB,OAAO,EAAE,SAAS,aAAa;CAC5D,MAAM,EAAE,WAAW;AAEnB,KAAI,CAAC,QAAQ,GACZ,QAAO,SAAS,kBAAkB,6BAA6B,IAAI;AAIpE,KAAI,CAAC,OAAO,OAAO,aAAa,CAC/B,QAAO,SACN,wBACA,qDACA,IACA;AAGF,KAAI;EAIH,MAAM,OAAO,MAAM,UAAU,SAAS,kBAAkB;AACxD,MAAI,aAAa,KAAK,CAAE,QAAO;EAG/B,MAAM,KAAK,YAAY,SAAS,uBAAuB,OAAO,OAAO,CAAC;AAEtE,MAAI,EADc,MAAM,eAAe,OAAO,IAAI,IAAI,kBAAkB,GAAG,IAAI,EAChE,QAGd,QAAO,WAAW,gBAAgB;EAGnC,MAAM,UAAU,oBAAoB,OAAO,GAAG;EAI9C,MAAM,WAAY,MADF,IAAI,kBAAkB,OAAO,GAAG,CAChB,IAAY,oBAAoB,IAAK;AAOrE,QAAM,cACL;GACC,SANc,MAAM,eAAe,OAAO,IAAI,QAAQ;GAOtD;GACA,QAAQ,YAAY,OAAO,MAAO,KAAK,SAAS,SAAS;GACzD,EACD,SACA,KAAK,MAAM,aAAa,CAAC,MAAM,CAC/B;AAGD,SAAO,WAAW,gBAAgB;UAC1B,OAAO;AACf,UAAQ,MAAM,yBAAyB,MAAM;AAI7C,SAAO,WAAW,gBAAgB"}
@@ -1,6 +1,6 @@
1
1
  import "../../../../../base64-CqR-7kqF.mjs";
2
2
  import "../../../../../types-BXSUSAjt.mjs";
3
- import { n as apiSuccess, r as handleError, t as apiError } from "../../../../../error-RwM4dD35.mjs";
3
+ import { n as apiSuccess, r as handleError, t as apiError } from "../../../../../error-CNn_w7jf.mjs";
4
4
  import { createKyselyAdapter } from "@emdash-cms/auth/adapters/kysely";
5
5
  import { SignupError, roleFromLevel, validateSignupToken } from "@emdash-cms/auth";
6
6
 
@@ -3,16 +3,18 @@ import "../../../../../../base64-CqR-7kqF.mjs";
3
3
  import "../../../../../../types-BXSUSAjt.mjs";
4
4
  import { t as CommentRepository } from "../../../../../../comment-sqQxNpN3.mjs";
5
5
  import "../../../../../../options-BPCVnesz.mjs";
6
- import { a as unwrapResult, i as requireDb, n as apiSuccess, r as handleError, t as apiError } from "../../../../../../error-RwM4dD35.mjs";
7
- import { n as parseBody, t as isParseError } from "../../../../../../parse-CrGndy1A.mjs";
8
- import { yt as createCommentBody } from "../../../../../../redirects-CCbCqCCd.mjs";
9
- import "../../../../../../byline-fields-8TMtkBnH.mjs";
6
+ import "../../../../../../init-lock-DlBHjf9-.mjs";
7
+ import { a as unwrapResult, i as requireDb, n as apiSuccess, r as handleError, t as apiError } from "../../../../../../error-CNn_w7jf.mjs";
8
+ import { n as parseBody, t as isParseError } from "../../../../../../parse-DzSrk1t8.mjs";
9
+ import { yt as createCommentBody } from "../../../../../../redirects-6Zg2SoYo.mjs";
10
+ import "../../../../../../byline-fields-B0NO1yUB.mjs";
11
+ import "../../../../../../status-2gZklYuj.mjs";
10
12
  import "../../../../../../api/schemas/index.mjs";
11
- import { t as extractRequestMeta } from "../../../../../../request-meta-7ByVLxB-.mjs";
12
- import { i as resolveSecretsCached } from "../../../../../../secrets-ChPTmy9x.mjs";
13
- import { c as hashIp, s as handleCommentList, t as checkRateLimit } from "../../../../../../comments-Vkivawyl.mjs";
14
- import { t as getSiteBaseUrl } from "../../../../../../site-url-CnHlmAs9.mjs";
15
- import { i as sendCommentNotification, t as createComment } from "../../../../../../service-ChDcsTBs.mjs";
13
+ import { t as extractRequestMeta } from "../../../../../../request-meta-DPechd0W.mjs";
14
+ import { i as resolveSecretsCached } from "../../../../../../secrets-C8xmE6mR.mjs";
15
+ import { c as hashIp, s as handleCommentList, t as checkRateLimit } from "../../../../../../comments-D2hNuxNa.mjs";
16
+ import { t as getSiteBaseUrl } from "../../../../../../site-url-vtsuOvSD.mjs";
17
+ import { i as sendCommentNotification, t as createComment } from "../../../../../../service-CDQQnT8W.mjs";
16
18
 
17
19
  //#region src/astro/routes/api/comments/[collection]/[contentId]/index.ts
18
20
  const prerender = false;
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../../../../src/astro/routes/api/comments/[collection]/[contentId]/index.ts"],"sourcesContent":["/**\n * Public comment endpoints\n *\n * GET /_emdash/api/comments/:collection/:contentId - List approved comments\n * POST /_emdash/api/comments/:collection/:contentId - Submit a comment\n */\n\nimport type { APIRoute } from \"astro\";\n\nimport { apiError, apiSuccess, handleError, requireDb, unwrapResult } from \"#api/error.js\";\nimport { handleCommentList, checkRateLimit, hashIp } from \"#api/handlers/comments.js\";\nimport { isParseError, parseBody } from \"#api/parse.js\";\nimport { createCommentBody } from \"#api/schemas.js\";\nimport { getSiteBaseUrl } from \"#api/site-url.js\";\nimport { sendCommentNotification } from \"#comments/notifications.js\";\nimport { createComment, type CommentHookRunner } from \"#comments/service.js\";\nimport { resolveSecretsCached } from \"#config/secrets.js\";\nimport { CommentRepository } from \"#db/repositories/comment.js\";\nimport { validateIdentifier } from \"#db/validate.js\";\nimport { extractRequestMeta } from \"#plugins/request-meta.js\";\nimport type { CollectionCommentSettings, ModerationDecision } from \"#plugins/types.js\";\n\nexport const prerender = false;\n\n/**\n * List approved comments for a content item (public, no auth required)\n */\nexport const GET: APIRoute = async ({ params, url, locals }) => {\n\tconst { emdash } = locals;\n\tconst { collection, contentId } = params;\n\n\tif (!collection || !contentId) {\n\t\treturn apiError(\"VALIDATION_ERROR\", \"Collection and content ID required\", 400);\n\t}\n\n\tconst dbErr = requireDb(emdash?.db);\n\tif (dbErr) return dbErr;\n\n\ttry {\n\t\tconst limit = Math.min(Number(url.searchParams.get(\"limit\") || 50), 100);\n\t\tconst cursor = url.searchParams.get(\"cursor\") ?? undefined;\n\t\tconst threaded = url.searchParams.get(\"threaded\") === \"true\";\n\n\t\t// Check collection exists and has comments enabled\n\t\tconst collectionRow = await emdash.db\n\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t.select([\"comments_enabled\"])\n\t\t\t.where(\"slug\", \"=\", collection)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!collectionRow) {\n\t\t\treturn apiError(\"NOT_FOUND\", `Collection '${collection}' not found`, 404);\n\t\t}\n\n\t\tif (!collectionRow.comments_enabled) {\n\t\t\treturn apiError(\"COMMENTS_DISABLED\", \"Comments are not enabled for this collection\", 403);\n\t\t}\n\n\t\tconst result = await handleCommentList(emdash.db, collection, contentId, {\n\t\t\tlimit,\n\t\t\tcursor,\n\t\t\tthreaded,\n\t\t});\n\n\t\treturn unwrapResult(result);\n\t} catch (error) {\n\t\treturn handleError(error, \"Failed to list comments\", \"COMMENT_LIST_ERROR\");\n\t}\n};\n\n/**\n * Submit a comment (public, gated by anti-spam checks)\n */\nexport const POST: APIRoute = async ({ params, request, locals }) => {\n\tconst { emdash, user } = locals;\n\tconst { collection, contentId } = params;\n\n\tif (!collection || !contentId) {\n\t\treturn apiError(\"VALIDATION_ERROR\", \"Collection and content ID required\", 400);\n\t}\n\n\tconst dbErr = requireDb(emdash?.db);\n\tif (dbErr) return dbErr;\n\n\ttry {\n\t\t// Parse and validate input\n\t\tconst body = await parseBody(request, createCommentBody);\n\t\tif (isParseError(body)) return body;\n\n\t\t// Check collection exists and has comments enabled\n\t\tconst collectionRow = await emdash.db\n\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t.select([\n\t\t\t\t\"comments_enabled\",\n\t\t\t\t\"comments_moderation\",\n\t\t\t\t\"comments_closed_after_days\",\n\t\t\t\t\"comments_auto_approve_users\",\n\t\t\t])\n\t\t\t.where(\"slug\", \"=\", collection)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!collectionRow) {\n\t\t\treturn apiError(\"NOT_FOUND\", `Collection '${collection}' not found`, 404);\n\t\t}\n\n\t\tif (!collectionRow.comments_enabled) {\n\t\t\treturn apiError(\"COMMENTS_DISABLED\", \"Comments are not enabled for this collection\", 403);\n\t\t}\n\n\t\t// Verify the content item exists, is published, and not soft-deleted\n\t\tvalidateIdentifier(collection, \"collection\");\n\t\tconst contentRow = await emdash.db\n\t\t\t.selectFrom(`ec_${collection}` as never)\n\t\t\t.select([\"id\" as never, \"slug\" as never, \"author_id\" as never, \"published_at\" as never])\n\t\t\t.where(\"id\" as never, \"=\", contentId as never)\n\t\t\t.where(\"status\" as never, \"=\", \"published\" as never)\n\t\t\t.where(\"deleted_at\" as never, \"is\", null as never)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!contentRow) {\n\t\t\treturn apiError(\"NOT_FOUND\", \"Content not found\", 404);\n\t\t}\n\n\t\t// Check if comments are closed (published_at + closed_after_days)\n\t\tif (collectionRow.comments_closed_after_days > 0) {\n\t\t\tconst publishedAt = (contentRow as { published_at: string | null }).published_at;\n\t\t\tif (publishedAt) {\n\t\t\t\tconst closedDate = new Date(publishedAt);\n\t\t\t\tclosedDate.setDate(closedDate.getDate() + collectionRow.comments_closed_after_days);\n\t\t\t\tif (new Date() > closedDate) {\n\t\t\t\t\treturn apiError(\"COMMENTS_CLOSED\", \"Comments are closed for this content\", 403);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Anti-spam: Honeypot — hidden field filled only by bots\n\t\tif (body.website_url) {\n\t\t\t// Silently accept — don't reveal the honeypot to bots\n\t\t\treturn apiSuccess({ status: \"pending\", message: \"Comment submitted for review\" });\n\t\t}\n\n\t\t// Anti-spam: Rate limiting\n\t\tconst meta = extractRequestMeta(request, emdash.config);\n\t\tconst { ipSalt } = await resolveSecretsCached(emdash.db);\n\t\tlet ipHash: string;\n\t\tif (meta.ip) {\n\t\t\tipHash = await hashIp(meta.ip, ipSalt);\n\t\t} else {\n\t\t\t// No trusted IP — fail closed by bucketing all unidentifiable\n\t\t\t// requests together. A larger limit reflects the shared bucket.\n\t\t\t//\n\t\t\t// Self-hosted operators behind a reverse proxy should set\n\t\t\t// `trustedProxyHeaders` in the EmDash config (or the\n\t\t\t// EMDASH_TRUSTED_PROXY_HEADERS env var) so this path isn't hit\n\t\t\t// for legitimate traffic. UA-hashing was previously used here\n\t\t\t// but was trivially rotatable — the shared bucket is stricter\n\t\t\t// and forces operators toward a real fix.\n\t\t\tipHash = \"unknown\";\n\t\t}\n\t\tconst unknownBucketLimit = ipHash === \"unknown\" ? 20 : undefined;\n\t\tconst rateLimited = await checkRateLimit(emdash.db, ipHash, unknownBucketLimit);\n\t\tif (rateLimited) {\n\t\t\treturn apiError(\"RATE_LIMITED\", \"Too many comments. Please try again later.\", 429);\n\t\t}\n\n\t\t// Build collection settings\n\t\tconst collectionSettings: CollectionCommentSettings = {\n\t\t\tcommentsEnabled: collectionRow.comments_enabled === 1,\n\t\t\tcommentsModeration:\n\t\t\t\tcollectionRow.comments_moderation as CollectionCommentSettings[\"commentsModeration\"],\n\t\t\tcommentsClosedAfterDays: collectionRow.comments_closed_after_days,\n\t\t\tcommentsAutoApproveUsers: collectionRow.comments_auto_approve_users === 1,\n\t\t};\n\n\t\t// Determine author fields — authenticated user overrides form input\n\t\tlet authorName = body.authorName;\n\t\tlet authorEmail = body.authorEmail;\n\t\tlet authorUserId: string | null = null;\n\n\t\tif (user) {\n\t\t\tauthorName = user.name || authorName;\n\t\t\tauthorEmail = user.email;\n\t\t\tauthorUserId = user.id;\n\t\t}\n\n\t\t// Validate parent exists and belongs to the same content.\n\t\t// Enforce 1-level nesting: if the parent is itself a reply, attach to its root.\n\t\tlet resolvedParentId = body.parentId ?? null;\n\t\tif (body.parentId) {\n\t\t\tconst repo = new CommentRepository(emdash.db);\n\t\t\tconst parent = await repo.findById(body.parentId);\n\t\t\tif (!parent) {\n\t\t\t\treturn apiError(\"VALIDATION_ERROR\", \"Parent comment not found\", 400);\n\t\t\t}\n\t\t\tif (parent.collection !== collection || parent.contentId !== contentId) {\n\t\t\t\treturn apiError(\"VALIDATION_ERROR\", \"Parent comment belongs to different content\", 400);\n\t\t\t}\n\t\t\t// Flatten: if parent is a reply, use its parent (the root) instead\n\t\t\tresolvedParentId = parent.parentId ?? parent.id;\n\t\t}\n\n\t\t// Wire the comment service to the real hook pipeline\n\t\tconst hookRunner: CommentHookRunner = {\n\t\t\tasync runBeforeCreate(event) {\n\t\t\t\treturn emdash.hooks.runCommentBeforeCreate(event);\n\t\t\t},\n\t\t\tasync runModerate(event) {\n\t\t\t\tconst result = await emdash.hooks.invokeExclusiveHook(\"comment:moderate\", event);\n\t\t\t\tif (!result) return { status: \"pending\" as const, reason: \"No moderator configured\" };\n\t\t\t\tif (result.error) {\n\t\t\t\t\tconsole.error(`[comments] Moderation error (${result.pluginId}):`, result.error.message);\n\t\t\t\t\treturn { status: \"pending\" as const, reason: \"Moderation error\" };\n\t\t\t\t}\n\t\t\t\treturn result.result as ModerationDecision;\n\t\t\t},\n\t\t\tfireAfterCreate(event) {\n\t\t\t\temdash.hooks\n\t\t\t\t\t.runCommentAfterCreate(event)\n\t\t\t\t\t.catch((err) =>\n\t\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\t\"[comments] afterCreate error:\",\n\t\t\t\t\t\t\terr instanceof Error ? err.message : err,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t},\n\t\t\tfireAfterModerate(event) {\n\t\t\t\temdash.hooks\n\t\t\t\t\t.runCommentAfterModerate(event)\n\t\t\t\t\t.catch((err) =>\n\t\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\t\"[comments] afterModerate error:\",\n\t\t\t\t\t\t\terr instanceof Error ? err.message : err,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t},\n\t\t};\n\n\t\t// Build content info for afterCreate hooks (e.g. email notifications)\n\t\tconst typedContent = contentRow as {\n\t\t\tid: string;\n\t\t\tslug: string;\n\t\t\tauthor_id: string | null;\n\t\t};\n\t\tlet contentAuthor: { id: string; name: string | null; email: string } | undefined;\n\t\tif (typedContent.author_id) {\n\t\t\tconst authorRow = await emdash.db\n\t\t\t\t.selectFrom(\"users\")\n\t\t\t\t.select([\"id\", \"name\", \"email\", \"email_verified\"])\n\t\t\t\t.where(\"id\", \"=\", typedContent.author_id)\n\t\t\t\t.executeTakeFirst();\n\t\t\tif (authorRow && authorRow.email_verified) {\n\t\t\t\tcontentAuthor = {\n\t\t\t\t\tid: authorRow.id,\n\t\t\t\t\tname: authorRow.name,\n\t\t\t\t\temail: authorRow.email,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tconst result = await createComment(\n\t\t\temdash.db,\n\t\t\t{\n\t\t\t\tcollection,\n\t\t\t\tcontentId,\n\t\t\t\tparentId: resolvedParentId,\n\t\t\t\tauthorName,\n\t\t\t\tauthorEmail,\n\t\t\t\tauthorUserId,\n\t\t\t\tbody: body.body,\n\t\t\t\tipHash,\n\t\t\t\tuserAgent: meta.userAgent,\n\t\t\t},\n\t\t\tcollectionSettings,\n\t\t\thookRunner,\n\t\t\t{\n\t\t\t\tid: typedContent.id,\n\t\t\t\tcollection,\n\t\t\t\tslug: typedContent.slug,\n\t\t\t\tauthor: contentAuthor,\n\t\t\t},\n\t\t);\n\n\t\tif (!result) {\n\t\t\treturn apiError(\"COMMENT_REJECTED\", \"Comment was rejected\", 403);\n\t\t}\n\n\t\t// Send notification to content author (awaited so it completes before\n\t\t// the response is sent — required for Cloudflare Workers where the\n\t\t// isolate terminates after the response).\n\t\tif (result.comment.status === \"approved\" && emdash.email && contentAuthor) {\n\t\t\ttry {\n\t\t\t\tconst adminBaseUrl = await getSiteBaseUrl(emdash.db, request);\n\t\t\t\tawait sendCommentNotification({\n\t\t\t\t\temail: emdash.email,\n\t\t\t\t\tcomment: result.comment,\n\t\t\t\t\tcontentAuthor,\n\t\t\t\t\tadminBaseUrl,\n\t\t\t\t});\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(\"[comments] notification error:\", err instanceof Error ? err.message : err);\n\t\t\t}\n\t\t}\n\n\t\treturn apiSuccess(\n\t\t\t{\n\t\t\t\tid: result.comment.id,\n\t\t\t\tstatus: result.comment.status,\n\t\t\t\tmessage:\n\t\t\t\t\tresult.comment.status === \"approved\"\n\t\t\t\t\t\t? \"Comment published\"\n\t\t\t\t\t\t: \"Comment submitted for review\",\n\t\t\t},\n\t\t\t201,\n\t\t);\n\t} catch (error) {\n\t\treturn handleError(error, \"Failed to submit comment\", \"COMMENT_CREATE_ERROR\");\n\t}\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../../../../../src/astro/routes/api/comments/[collection]/[contentId]/index.ts"],"sourcesContent":["/**\n * Public comment endpoints\n *\n * GET /_emdash/api/comments/:collection/:contentId - List approved comments\n * POST /_emdash/api/comments/:collection/:contentId - Submit a comment\n */\n\nimport type { APIRoute } from \"astro\";\n\nimport { apiError, apiSuccess, handleError, requireDb, unwrapResult } from \"#api/error.js\";\nimport { handleCommentList, checkRateLimit, hashIp } from \"#api/handlers/comments.js\";\nimport { isParseError, parseBody } from \"#api/parse.js\";\nimport { createCommentBody } from \"#api/schemas.js\";\nimport { getSiteBaseUrl } from \"#api/site-url.js\";\nimport { sendCommentNotification } from \"#comments/notifications.js\";\nimport { createComment, type CommentHookRunner } from \"#comments/service.js\";\nimport { resolveSecretsCached } from \"#config/secrets.js\";\nimport { CommentRepository } from \"#db/repositories/comment.js\";\nimport { validateIdentifier } from \"#db/validate.js\";\nimport { extractRequestMeta } from \"#plugins/request-meta.js\";\nimport type { CollectionCommentSettings, ModerationDecision } from \"#plugins/types.js\";\n\nexport const prerender = false;\n\n/**\n * List approved comments for a content item (public, no auth required)\n */\nexport const GET: APIRoute = async ({ params, url, locals }) => {\n\tconst { emdash } = locals;\n\tconst { collection, contentId } = params;\n\n\tif (!collection || !contentId) {\n\t\treturn apiError(\"VALIDATION_ERROR\", \"Collection and content ID required\", 400);\n\t}\n\n\tconst dbErr = requireDb(emdash?.db);\n\tif (dbErr) return dbErr;\n\n\ttry {\n\t\tconst limit = Math.min(Number(url.searchParams.get(\"limit\") || 50), 100);\n\t\tconst cursor = url.searchParams.get(\"cursor\") ?? undefined;\n\t\tconst threaded = url.searchParams.get(\"threaded\") === \"true\";\n\n\t\t// Check collection exists and has comments enabled\n\t\tconst collectionRow = await emdash.db\n\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t.select([\"comments_enabled\"])\n\t\t\t.where(\"slug\", \"=\", collection)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!collectionRow) {\n\t\t\treturn apiError(\"NOT_FOUND\", `Collection '${collection}' not found`, 404);\n\t\t}\n\n\t\tif (!collectionRow.comments_enabled) {\n\t\t\treturn apiError(\"COMMENTS_DISABLED\", \"Comments are not enabled for this collection\", 403);\n\t\t}\n\n\t\tconst result = await handleCommentList(emdash.db, collection, contentId, {\n\t\t\tlimit,\n\t\t\tcursor,\n\t\t\tthreaded,\n\t\t});\n\n\t\treturn unwrapResult(result);\n\t} catch (error) {\n\t\treturn handleError(error, \"Failed to list comments\", \"COMMENT_LIST_ERROR\");\n\t}\n};\n\n/**\n * Submit a comment (public, gated by anti-spam checks)\n */\nexport const POST: APIRoute = async ({ params, request, locals }) => {\n\tconst { emdash, user } = locals;\n\tconst { collection, contentId } = params;\n\n\tif (!collection || !contentId) {\n\t\treturn apiError(\"VALIDATION_ERROR\", \"Collection and content ID required\", 400);\n\t}\n\n\tconst dbErr = requireDb(emdash?.db);\n\tif (dbErr) return dbErr;\n\n\ttry {\n\t\t// Parse and validate input\n\t\tconst body = await parseBody(request, createCommentBody);\n\t\tif (isParseError(body)) return body;\n\n\t\t// Check collection exists and has comments enabled\n\t\tconst collectionRow = await emdash.db\n\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t.select([\n\t\t\t\t\"comments_enabled\",\n\t\t\t\t\"comments_moderation\",\n\t\t\t\t\"comments_closed_after_days\",\n\t\t\t\t\"comments_auto_approve_users\",\n\t\t\t])\n\t\t\t.where(\"slug\", \"=\", collection)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!collectionRow) {\n\t\t\treturn apiError(\"NOT_FOUND\", `Collection '${collection}' not found`, 404);\n\t\t}\n\n\t\tif (!collectionRow.comments_enabled) {\n\t\t\treturn apiError(\"COMMENTS_DISABLED\", \"Comments are not enabled for this collection\", 403);\n\t\t}\n\n\t\t// Verify the content item exists, is published, and not soft-deleted\n\t\tvalidateIdentifier(collection, \"collection\");\n\t\tconst contentRow = await emdash.db\n\t\t\t.selectFrom(`ec_${collection}` as never)\n\t\t\t.select([\"id\" as never, \"slug\" as never, \"author_id\" as never, \"published_at\" as never])\n\t\t\t.where(\"id\" as never, \"=\", contentId as never)\n\t\t\t.where(\"status\" as never, \"=\", \"published\" as never)\n\t\t\t.where(\"deleted_at\" as never, \"is\", null as never)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!contentRow) {\n\t\t\treturn apiError(\"NOT_FOUND\", \"Content not found\", 404);\n\t\t}\n\n\t\t// Check if comments are closed (published_at + closed_after_days)\n\t\tif (collectionRow.comments_closed_after_days > 0) {\n\t\t\tconst publishedAt = (contentRow as { published_at: string | null }).published_at;\n\t\t\tif (publishedAt) {\n\t\t\t\tconst closedDate = new Date(publishedAt);\n\t\t\t\tclosedDate.setDate(closedDate.getDate() + collectionRow.comments_closed_after_days);\n\t\t\t\tif (new Date() > closedDate) {\n\t\t\t\t\treturn apiError(\"COMMENTS_CLOSED\", \"Comments are closed for this content\", 403);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Anti-spam: Honeypot — hidden field filled only by bots\n\t\tif (body.website_url) {\n\t\t\t// Silently accept — don't reveal the honeypot to bots\n\t\t\treturn apiSuccess({ status: \"pending\", message: \"Comment submitted for review\" });\n\t\t}\n\n\t\t// Anti-spam: Rate limiting\n\t\tconst meta = extractRequestMeta(request, emdash.config);\n\t\tconst { ipSalt } = await resolveSecretsCached(emdash.db);\n\t\tlet ipHash: string;\n\t\tif (meta.ip) {\n\t\t\tipHash = await hashIp(meta.ip, ipSalt);\n\t\t} else {\n\t\t\t// No trusted IP — fail closed by bucketing all unidentifiable\n\t\t\t// requests together. A larger limit reflects the shared bucket.\n\t\t\t//\n\t\t\t// Self-hosted operators behind a reverse proxy should set\n\t\t\t// `trustedProxyHeaders` in the EmDash config (or the\n\t\t\t// EMDASH_TRUSTED_PROXY_HEADERS env var) so this path isn't hit\n\t\t\t// for legitimate traffic. UA-hashing was previously used here\n\t\t\t// but was trivially rotatable — the shared bucket is stricter\n\t\t\t// and forces operators toward a real fix.\n\t\t\tipHash = \"unknown\";\n\t\t}\n\t\tconst unknownBucketLimit = ipHash === \"unknown\" ? 20 : undefined;\n\t\tconst rateLimited = await checkRateLimit(emdash.db, ipHash, unknownBucketLimit);\n\t\tif (rateLimited) {\n\t\t\treturn apiError(\"RATE_LIMITED\", \"Too many comments. Please try again later.\", 429);\n\t\t}\n\n\t\t// Build collection settings\n\t\tconst collectionSettings: CollectionCommentSettings = {\n\t\t\tcommentsEnabled: collectionRow.comments_enabled === 1,\n\t\t\tcommentsModeration:\n\t\t\t\tcollectionRow.comments_moderation as CollectionCommentSettings[\"commentsModeration\"],\n\t\t\tcommentsClosedAfterDays: collectionRow.comments_closed_after_days,\n\t\t\tcommentsAutoApproveUsers: collectionRow.comments_auto_approve_users === 1,\n\t\t};\n\n\t\t// Determine author fields — authenticated user overrides form input\n\t\tlet authorName = body.authorName;\n\t\tlet authorEmail = body.authorEmail;\n\t\tlet authorUserId: string | null = null;\n\n\t\tif (user) {\n\t\t\tauthorName = user.name || authorName;\n\t\t\tauthorEmail = user.email;\n\t\t\tauthorUserId = user.id;\n\t\t}\n\n\t\t// Validate parent exists and belongs to the same content.\n\t\t// Enforce 1-level nesting: if the parent is itself a reply, attach to its root.\n\t\tlet resolvedParentId = body.parentId ?? null;\n\t\tif (body.parentId) {\n\t\t\tconst repo = new CommentRepository(emdash.db);\n\t\t\tconst parent = await repo.findById(body.parentId);\n\t\t\tif (!parent) {\n\t\t\t\treturn apiError(\"VALIDATION_ERROR\", \"Parent comment not found\", 400);\n\t\t\t}\n\t\t\tif (parent.collection !== collection || parent.contentId !== contentId) {\n\t\t\t\treturn apiError(\"VALIDATION_ERROR\", \"Parent comment belongs to different content\", 400);\n\t\t\t}\n\t\t\t// Flatten: if parent is a reply, use its parent (the root) instead\n\t\t\tresolvedParentId = parent.parentId ?? parent.id;\n\t\t}\n\n\t\t// Wire the comment service to the real hook pipeline\n\t\tconst hookRunner: CommentHookRunner = {\n\t\t\tasync runBeforeCreate(event) {\n\t\t\t\treturn emdash.hooks.runCommentBeforeCreate(event);\n\t\t\t},\n\t\t\tasync runModerate(event) {\n\t\t\t\tconst result = await emdash.hooks.invokeExclusiveHook(\"comment:moderate\", event);\n\t\t\t\tif (!result) return { status: \"pending\" as const, reason: \"No moderator configured\" };\n\t\t\t\tif (result.error) {\n\t\t\t\t\tconsole.error(`[comments] Moderation error (${result.pluginId}):`, result.error.message);\n\t\t\t\t\treturn { status: \"pending\" as const, reason: \"Moderation error\" };\n\t\t\t\t}\n\t\t\t\treturn result.result as ModerationDecision;\n\t\t\t},\n\t\t\tfireAfterCreate(event) {\n\t\t\t\temdash.hooks\n\t\t\t\t\t.runCommentAfterCreate(event)\n\t\t\t\t\t.catch((err) =>\n\t\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\t\"[comments] afterCreate error:\",\n\t\t\t\t\t\t\terr instanceof Error ? err.message : err,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t},\n\t\t\tfireAfterModerate(event) {\n\t\t\t\temdash.hooks\n\t\t\t\t\t.runCommentAfterModerate(event)\n\t\t\t\t\t.catch((err) =>\n\t\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\t\"[comments] afterModerate error:\",\n\t\t\t\t\t\t\terr instanceof Error ? err.message : err,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t},\n\t\t};\n\n\t\t// Build content info for afterCreate hooks (e.g. email notifications)\n\t\tconst typedContent = contentRow as {\n\t\t\tid: string;\n\t\t\tslug: string;\n\t\t\tauthor_id: string | null;\n\t\t};\n\t\tlet contentAuthor: { id: string; name: string | null; email: string } | undefined;\n\t\tif (typedContent.author_id) {\n\t\t\tconst authorRow = await emdash.db\n\t\t\t\t.selectFrom(\"users\")\n\t\t\t\t.select([\"id\", \"name\", \"email\", \"email_verified\"])\n\t\t\t\t.where(\"id\", \"=\", typedContent.author_id)\n\t\t\t\t.executeTakeFirst();\n\t\t\tif (authorRow && authorRow.email_verified) {\n\t\t\t\tcontentAuthor = {\n\t\t\t\t\tid: authorRow.id,\n\t\t\t\t\tname: authorRow.name,\n\t\t\t\t\temail: authorRow.email,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tconst result = await createComment(\n\t\t\temdash.db,\n\t\t\t{\n\t\t\t\tcollection,\n\t\t\t\tcontentId,\n\t\t\t\tparentId: resolvedParentId,\n\t\t\t\tauthorName,\n\t\t\t\tauthorEmail,\n\t\t\t\tauthorUserId,\n\t\t\t\tbody: body.body,\n\t\t\t\tipHash,\n\t\t\t\tuserAgent: meta.userAgent,\n\t\t\t},\n\t\t\tcollectionSettings,\n\t\t\thookRunner,\n\t\t\t{\n\t\t\t\tid: typedContent.id,\n\t\t\t\tcollection,\n\t\t\t\tslug: typedContent.slug,\n\t\t\t\tauthor: contentAuthor,\n\t\t\t},\n\t\t);\n\n\t\tif (!result) {\n\t\t\treturn apiError(\"COMMENT_REJECTED\", \"Comment was rejected\", 403);\n\t\t}\n\n\t\t// Send notification to content author (awaited so it completes before\n\t\t// the response is sent — required for Cloudflare Workers where the\n\t\t// isolate terminates after the response).\n\t\tif (result.comment.status === \"approved\" && emdash.email && contentAuthor) {\n\t\t\ttry {\n\t\t\t\tconst adminBaseUrl = await getSiteBaseUrl(emdash.db, request);\n\t\t\t\tawait sendCommentNotification({\n\t\t\t\t\temail: emdash.email,\n\t\t\t\t\tcomment: result.comment,\n\t\t\t\t\tcontentAuthor,\n\t\t\t\t\tadminBaseUrl,\n\t\t\t\t});\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(\"[comments] notification error:\", err instanceof Error ? err.message : err);\n\t\t\t}\n\t\t}\n\n\t\treturn apiSuccess(\n\t\t\t{\n\t\t\t\tid: result.comment.id,\n\t\t\t\tstatus: result.comment.status,\n\t\t\t\tmessage:\n\t\t\t\t\tresult.comment.status === \"approved\"\n\t\t\t\t\t\t? \"Comment published\"\n\t\t\t\t\t\t: \"Comment submitted for review\",\n\t\t\t},\n\t\t\t201,\n\t\t);\n\t} catch (error) {\n\t\treturn handleError(error, \"Failed to submit comment\", \"COMMENT_CREATE_ERROR\");\n\t}\n};\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import "../../../../../../base64-CqR-7kqF.mjs";
2
2
  import "../../../../../../types-BXSUSAjt.mjs";
3
- import { a as unwrapResult, t as apiError } from "../../../../../../error-RwM4dD35.mjs";
4
- import { n as requirePerm } from "../../../../../../authorize-DsMSVSaY.mjs";
3
+ import { a as unwrapResult, t as apiError } from "../../../../../../error-CNn_w7jf.mjs";
4
+ import { n as requirePerm } from "../../../../../../authorize-D5gfBVU5.mjs";
5
5
 
6
6
  //#region src/astro/routes/api/content/[collection]/[id]/compare.ts
7
7
  const prerender = false;
@@ -1,7 +1,7 @@
1
1
  import "../../../../../../base64-CqR-7kqF.mjs";
2
2
  import "../../../../../../types-BXSUSAjt.mjs";
3
- import { a as unwrapResult, o as mapErrorStatus, t as apiError } from "../../../../../../error-RwM4dD35.mjs";
4
- import { t as requireOwnerPerm } from "../../../../../../authorize-DsMSVSaY.mjs";
3
+ import { a as unwrapResult, o as mapErrorStatus, t as apiError } from "../../../../../../error-CNn_w7jf.mjs";
4
+ import { t as requireOwnerPerm } from "../../../../../../authorize-D5gfBVU5.mjs";
5
5
 
6
6
  //#region src/astro/routes/api/content/[collection]/[id]/discard-draft.ts
7
7
  const prerender = false;
@@ -1,7 +1,7 @@
1
1
  import "../../../../../../base64-CqR-7kqF.mjs";
2
2
  import "../../../../../../types-BXSUSAjt.mjs";
3
- import { a as unwrapResult, o as mapErrorStatus, t as apiError } from "../../../../../../error-RwM4dD35.mjs";
4
- import { n as requirePerm, t as requireOwnerPerm } from "../../../../../../authorize-DsMSVSaY.mjs";
3
+ import { a as unwrapResult, o as mapErrorStatus, t as apiError } from "../../../../../../error-CNn_w7jf.mjs";
4
+ import { n as requirePerm, t as requireOwnerPerm } from "../../../../../../authorize-D5gfBVU5.mjs";
5
5
 
6
6
  //#region src/astro/routes/api/content/[collection]/[id]/duplicate.ts
7
7
  const prerender = false;
@@ -1,7 +1,7 @@
1
1
  import "../../../../../../base64-CqR-7kqF.mjs";
2
2
  import "../../../../../../types-BXSUSAjt.mjs";
3
- import { a as unwrapResult, t as apiError } from "../../../../../../error-RwM4dD35.mjs";
4
- import { n as requirePerm } from "../../../../../../authorize-DsMSVSaY.mjs";
3
+ import { a as unwrapResult, t as apiError } from "../../../../../../error-CNn_w7jf.mjs";
4
+ import { n as requirePerm } from "../../../../../../authorize-D5gfBVU5.mjs";
5
5
 
6
6
  //#region src/astro/routes/api/content/[collection]/[id]/permanent.ts
7
7
  const prerender = false;
@@ -2,15 +2,17 @@ import { n as getI18nConfig } from "../../../../../../config-CVssduLe.mjs";
2
2
  import "../../../../../../base64-CqR-7kqF.mjs";
3
3
  import "../../../../../../types-BXSUSAjt.mjs";
4
4
  import "../../../../../../options-BPCVnesz.mjs";
5
- import { a as unwrapResult, n as apiSuccess, r as handleError, t as apiError } from "../../../../../../error-RwM4dD35.mjs";
6
- import { r as parseOptionalBody, t as isParseError } from "../../../../../../parse-CrGndy1A.mjs";
7
- import { an as contentPreviewUrlBody } from "../../../../../../redirects-CCbCqCCd.mjs";
8
- import "../../../../../../byline-fields-8TMtkBnH.mjs";
5
+ import "../../../../../../init-lock-DlBHjf9-.mjs";
6
+ import { a as unwrapResult, n as apiSuccess, r as handleError, t as apiError } from "../../../../../../error-CNn_w7jf.mjs";
7
+ import { r as parseOptionalBody, t as isParseError } from "../../../../../../parse-DzSrk1t8.mjs";
8
+ import { an as contentPreviewUrlBody } from "../../../../../../redirects-6Zg2SoYo.mjs";
9
+ import "../../../../../../byline-fields-B0NO1yUB.mjs";
10
+ import "../../../../../../status-2gZklYuj.mjs";
9
11
  import "../../../../../../api/schemas/index.mjs";
10
- import "../../../../../../tokens-Bx2afeT-.mjs";
11
- import { i as getPreviewUrl } from "../../../../../../preview-BfuRkVKW.mjs";
12
- import { i as resolveSecretsCached } from "../../../../../../secrets-ChPTmy9x.mjs";
13
- import { n as requirePerm } from "../../../../../../authorize-DsMSVSaY.mjs";
12
+ import "../../../../../../tokens-DMkVjxrx.mjs";
13
+ import { i as getPreviewUrl } from "../../../../../../preview-Dqv2hwXr.mjs";
14
+ import { i as resolveSecretsCached } from "../../../../../../secrets-C8xmE6mR.mjs";
15
+ import { n as requirePerm } from "../../../../../../authorize-D5gfBVU5.mjs";
14
16
 
15
17
  //#region src/astro/routes/api/content/[collection]/[id]/preview-url.ts
16
18
  const prerender = false;
@@ -1 +1 @@
1
- {"version":3,"file":"preview-url.mjs","names":[],"sources":["../../../../../../../src/astro/routes/api/content/[collection]/[id]/preview-url.ts"],"sourcesContent":["/**\n * Preview URL endpoint - generates a signed preview URL for content\n *\n * POST /_emdash/api/content/{collection}/{id}/preview-url\n *\n * Request body:\n * {\n * expiresIn?: string | number; // Default: \"1h\"\n * pathPattern?: string; // Default: \"/{collection}/{id}\" (or EMDASH_PREVIEW_PATH_PATTERN)\n * }\n *\n * Response:\n * {\n * url: string; // The preview URL with token\n * expiresAt: number; // Unix timestamp when token expires\n * }\n */\n\nimport type { APIRoute } from \"astro\";\n\nimport { requirePerm } from \"#api/authorize.js\";\nimport { apiError, apiSuccess, handleError, unwrapResult } from \"#api/error.js\";\nimport { parseOptionalBody, isParseError } from \"#api/parse.js\";\nimport { contentPreviewUrlBody } from \"#api/schemas.js\";\nimport { resolveSecretsCached } from \"#config/secrets.js\";\nimport { getPreviewUrl } from \"#preview/index.js\";\n\nimport { getI18nConfig } from \"../../../../../../i18n/config.js\";\n\nexport const prerender = false;\n\nconst DURATION_PATTERN = /^(\\d+)([smhdw])$/;\n\nexport const POST: APIRoute = async ({ params, request, locals }) => {\n\tconst { emdash, user } = locals;\n\tconst denied = requirePerm(user, \"content:read_drafts\");\n\tif (denied) return denied;\n\tconst collection = params.collection!;\n\tconst id = params.id!;\n\n\tif (!emdash?.db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\t// Resolve the preview secret. Env override wins; otherwise a stable\n\t// site-specific value is read from (or generated into) the options table.\n\t// The resolver always returns a usable secret, so this path can no\n\t// longer be silently disabled by a missing env var.\n\tconst { previewSecret } = await resolveSecretsCached(emdash.db);\n\n\t// Verify the content exists. The fetched item also yields the entry's\n\t// locale, used below to resolve the `{locale}` placeholder.\n\tlet entryLocale: string | null = null;\n\tif (emdash?.handleContentGet) {\n\t\tconst result = await emdash.handleContentGet(collection, id);\n\t\tif (!result.success) return unwrapResult(result);\n\t\tentryLocale = result.data?.item?.locale ?? null;\n\t}\n\n\t// Parse request body\n\tconst body = await parseOptionalBody(request, contentPreviewUrlBody, {});\n\tif (isParseError(body)) return body;\n\n\tconst expiresIn = body.expiresIn || \"1h\";\n\t// Allow a project-wide default `pathPattern` so the admin's \"View on site\"\n\t// link can match the site's actual route shape without each call having\n\t// to override the default `/{collection}/{id}`.\n\tconst defaultPathPattern = import.meta.env.EMDASH_PREVIEW_PATH_PATTERN || \"/{collection}/{id}\";\n\tconst pathPattern = body.pathPattern || defaultPathPattern;\n\n\t// Resolve the locale segment substituted for `{locale}`: empty when the\n\t// entry is in the default locale and `prefixDefaultLocale` is `false`,\n\t// the entry's own locale otherwise.\n\tconst i18n = getI18nConfig();\n\tlet localeSegment = \"\";\n\tif (entryLocale && i18n) {\n\t\tconst isDefault = entryLocale === i18n.defaultLocale;\n\t\tlocaleSegment = isDefault && !i18n.prefixDefaultLocale ? \"\" : entryLocale;\n\t} else if (entryLocale) {\n\t\tlocaleSegment = entryLocale;\n\t}\n\n\t// Calculate expiry timestamp\n\tconst expiresInSeconds = typeof expiresIn === \"number\" ? expiresIn : parseExpiresIn(expiresIn);\n\tconst expiresAt = Math.floor(Date.now() / 1000) + expiresInSeconds;\n\n\ttry {\n\t\tconst url = await getPreviewUrl({\n\t\t\tcollection,\n\t\t\tid,\n\t\t\tsecret: previewSecret,\n\t\t\texpiresIn,\n\t\t\tpathPattern,\n\t\t\tlocale: localeSegment,\n\t\t});\n\n\t\treturn apiSuccess({ url, expiresAt });\n\t} catch (error) {\n\t\treturn handleError(error, \"Failed to generate preview URL\", \"TOKEN_ERROR\");\n\t}\n};\n\n/**\n * Parse duration string to seconds\n */\nfunction parseExpiresIn(duration: string): number {\n\tconst match = duration.match(DURATION_PATTERN);\n\tif (!match) {\n\t\treturn 3600; // Default 1 hour\n\t}\n\n\tconst value = parseInt(match[1], 10);\n\tconst unit = match[2];\n\n\tswitch (unit) {\n\t\tcase \"s\":\n\t\t\treturn value;\n\t\tcase \"m\":\n\t\t\treturn value * 60;\n\t\tcase \"h\":\n\t\t\treturn value * 60 * 60;\n\t\tcase \"d\":\n\t\t\treturn value * 60 * 60 * 24;\n\t\tcase \"w\":\n\t\t\treturn value * 60 * 60 * 24 * 7;\n\t\tdefault:\n\t\t\treturn 3600;\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;AA6BA,MAAa,YAAY;AAEzB,MAAM,mBAAmB;AAEzB,MAAa,OAAiB,OAAO,EAAE,QAAQ,SAAS,aAAa;CACpE,MAAM,EAAE,QAAQ,SAAS;CACzB,MAAM,SAAS,YAAY,MAAM,sBAAsB;AACvD,KAAI,OAAQ,QAAO;CACnB,MAAM,aAAa,OAAO;CAC1B,MAAM,KAAK,OAAO;AAElB,KAAI,CAAC,QAAQ,GACZ,QAAO,SAAS,kBAAkB,6BAA6B,IAAI;CAOpE,MAAM,EAAE,kBAAkB,MAAM,qBAAqB,OAAO,GAAG;CAI/D,IAAI,cAA6B;AACjC,KAAI,QAAQ,kBAAkB;EAC7B,MAAM,SAAS,MAAM,OAAO,iBAAiB,YAAY,GAAG;AAC5D,MAAI,CAAC,OAAO,QAAS,QAAO,aAAa,OAAO;AAChD,gBAAc,OAAO,MAAM,MAAM,UAAU;;CAI5C,MAAM,OAAO,MAAM,kBAAkB,SAAS,uBAAuB,EAAE,CAAC;AACxE,KAAI,aAAa,KAAK,CAAE,QAAO;CAE/B,MAAM,YAAY,KAAK,aAAa;CAIpC,MAAM,qBAAqB,OAAO,KAAK,IAAI,+BAA+B;CAC1E,MAAM,cAAc,KAAK,eAAe;CAKxC,MAAM,OAAO,eAAe;CAC5B,IAAI,gBAAgB;AACpB,KAAI,eAAe,KAElB,iBADkB,gBAAgB,KAAK,iBACV,CAAC,KAAK,sBAAsB,KAAK;UACpD,YACV,iBAAgB;CAIjB,MAAM,mBAAmB,OAAO,cAAc,WAAW,YAAY,eAAe,UAAU;CAC9F,MAAM,YAAY,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK,GAAG;AAElD,KAAI;AAUH,SAAO,WAAW;GAAE,KATR,MAAM,cAAc;IAC/B;IACA;IACA,QAAQ;IACR;IACA;IACA,QAAQ;IACR,CAAC;GAEuB;GAAW,CAAC;UAC7B,OAAO;AACf,SAAO,YAAY,OAAO,kCAAkC,cAAc;;;;;;AAO5E,SAAS,eAAe,UAA0B;CACjD,MAAM,QAAQ,SAAS,MAAM,iBAAiB;AAC9C,KAAI,CAAC,MACJ,QAAO;CAGR,MAAM,QAAQ,SAAS,MAAM,IAAI,GAAG;AAGpC,SAFa,MAAM,IAEnB;EACC,KAAK,IACJ,QAAO;EACR,KAAK,IACJ,QAAO,QAAQ;EAChB,KAAK,IACJ,QAAO,QAAQ,KAAK;EACrB,KAAK,IACJ,QAAO,QAAQ,KAAK,KAAK;EAC1B,KAAK,IACJ,QAAO,QAAQ,KAAK,KAAK,KAAK;EAC/B,QACC,QAAO"}
1
+ {"version":3,"file":"preview-url.mjs","names":[],"sources":["../../../../../../../src/astro/routes/api/content/[collection]/[id]/preview-url.ts"],"sourcesContent":["/**\n * Preview URL endpoint - generates a signed preview URL for content\n *\n * POST /_emdash/api/content/{collection}/{id}/preview-url\n *\n * Request body:\n * {\n * expiresIn?: string | number; // Default: \"1h\"\n * pathPattern?: string; // Default: \"/{collection}/{id}\" (or EMDASH_PREVIEW_PATH_PATTERN)\n * }\n *\n * Response:\n * {\n * url: string; // The preview URL with token\n * expiresAt: number; // Unix timestamp when token expires\n * }\n */\n\nimport type { APIRoute } from \"astro\";\n\nimport { requirePerm } from \"#api/authorize.js\";\nimport { apiError, apiSuccess, handleError, unwrapResult } from \"#api/error.js\";\nimport { parseOptionalBody, isParseError } from \"#api/parse.js\";\nimport { contentPreviewUrlBody } from \"#api/schemas.js\";\nimport { resolveSecretsCached } from \"#config/secrets.js\";\nimport { getPreviewUrl } from \"#preview/index.js\";\n\nimport { getI18nConfig } from \"../../../../../../i18n/config.js\";\n\nexport const prerender = false;\n\nconst DURATION_PATTERN = /^(\\d+)([smhdw])$/;\n\nexport const POST: APIRoute = async ({ params, request, locals }) => {\n\tconst { emdash, user } = locals;\n\tconst denied = requirePerm(user, \"content:read_drafts\");\n\tif (denied) return denied;\n\tconst collection = params.collection!;\n\tconst id = params.id!;\n\n\tif (!emdash?.db) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\t// Resolve the preview secret. Env override wins; otherwise a stable\n\t// site-specific value is read from (or generated into) the options table.\n\t// The resolver always returns a usable secret, so this path can no\n\t// longer be silently disabled by a missing env var.\n\tconst { previewSecret } = await resolveSecretsCached(emdash.db);\n\n\t// Verify the content exists. The fetched item also yields the entry's\n\t// locale, used below to resolve the `{locale}` placeholder.\n\tlet entryLocale: string | null = null;\n\tif (emdash?.handleContentGet) {\n\t\tconst result = await emdash.handleContentGet(collection, id);\n\t\tif (!result.success) return unwrapResult(result);\n\t\tentryLocale = result.data?.item?.locale ?? null;\n\t}\n\n\t// Parse request body\n\tconst body = await parseOptionalBody(request, contentPreviewUrlBody, {});\n\tif (isParseError(body)) return body;\n\n\tconst expiresIn = body.expiresIn || \"1h\";\n\t// Allow a project-wide default `pathPattern` so the admin's \"View on site\"\n\t// link can match the site's actual route shape without each call having\n\t// to override the default `/{collection}/{id}`.\n\tconst defaultPathPattern = import.meta.env.EMDASH_PREVIEW_PATH_PATTERN || \"/{collection}/{id}\";\n\tconst pathPattern = body.pathPattern || defaultPathPattern;\n\n\t// Resolve the locale segment substituted for `{locale}`: empty when the\n\t// entry is in the default locale and `prefixDefaultLocale` is `false`,\n\t// the entry's own locale otherwise.\n\tconst i18n = getI18nConfig();\n\tlet localeSegment = \"\";\n\tif (entryLocale && i18n) {\n\t\tconst isDefault = entryLocale === i18n.defaultLocale;\n\t\tlocaleSegment = isDefault && !i18n.prefixDefaultLocale ? \"\" : entryLocale;\n\t} else if (entryLocale) {\n\t\tlocaleSegment = entryLocale;\n\t}\n\n\t// Calculate expiry timestamp\n\tconst expiresInSeconds = typeof expiresIn === \"number\" ? expiresIn : parseExpiresIn(expiresIn);\n\tconst expiresAt = Math.floor(Date.now() / 1000) + expiresInSeconds;\n\n\ttry {\n\t\tconst url = await getPreviewUrl({\n\t\t\tcollection,\n\t\t\tid,\n\t\t\tsecret: previewSecret,\n\t\t\texpiresIn,\n\t\t\tpathPattern,\n\t\t\tlocale: localeSegment,\n\t\t});\n\n\t\treturn apiSuccess({ url, expiresAt });\n\t} catch (error) {\n\t\treturn handleError(error, \"Failed to generate preview URL\", \"TOKEN_ERROR\");\n\t}\n};\n\n/**\n * Parse duration string to seconds\n */\nfunction parseExpiresIn(duration: string): number {\n\tconst match = duration.match(DURATION_PATTERN);\n\tif (!match) {\n\t\treturn 3600; // Default 1 hour\n\t}\n\n\tconst value = parseInt(match[1], 10);\n\tconst unit = match[2];\n\n\tswitch (unit) {\n\t\tcase \"s\":\n\t\t\treturn value;\n\t\tcase \"m\":\n\t\t\treturn value * 60;\n\t\tcase \"h\":\n\t\t\treturn value * 60 * 60;\n\t\tcase \"d\":\n\t\t\treturn value * 60 * 60 * 24;\n\t\tcase \"w\":\n\t\t\treturn value * 60 * 60 * 24 * 7;\n\t\tdefault:\n\t\t\treturn 3600;\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA6BA,MAAa,YAAY;AAEzB,MAAM,mBAAmB;AAEzB,MAAa,OAAiB,OAAO,EAAE,QAAQ,SAAS,aAAa;CACpE,MAAM,EAAE,QAAQ,SAAS;CACzB,MAAM,SAAS,YAAY,MAAM,sBAAsB;AACvD,KAAI,OAAQ,QAAO;CACnB,MAAM,aAAa,OAAO;CAC1B,MAAM,KAAK,OAAO;AAElB,KAAI,CAAC,QAAQ,GACZ,QAAO,SAAS,kBAAkB,6BAA6B,IAAI;CAOpE,MAAM,EAAE,kBAAkB,MAAM,qBAAqB,OAAO,GAAG;CAI/D,IAAI,cAA6B;AACjC,KAAI,QAAQ,kBAAkB;EAC7B,MAAM,SAAS,MAAM,OAAO,iBAAiB,YAAY,GAAG;AAC5D,MAAI,CAAC,OAAO,QAAS,QAAO,aAAa,OAAO;AAChD,gBAAc,OAAO,MAAM,MAAM,UAAU;;CAI5C,MAAM,OAAO,MAAM,kBAAkB,SAAS,uBAAuB,EAAE,CAAC;AACxE,KAAI,aAAa,KAAK,CAAE,QAAO;CAE/B,MAAM,YAAY,KAAK,aAAa;CAIpC,MAAM,qBAAqB,OAAO,KAAK,IAAI,+BAA+B;CAC1E,MAAM,cAAc,KAAK,eAAe;CAKxC,MAAM,OAAO,eAAe;CAC5B,IAAI,gBAAgB;AACpB,KAAI,eAAe,KAElB,iBADkB,gBAAgB,KAAK,iBACV,CAAC,KAAK,sBAAsB,KAAK;UACpD,YACV,iBAAgB;CAIjB,MAAM,mBAAmB,OAAO,cAAc,WAAW,YAAY,eAAe,UAAU;CAC9F,MAAM,YAAY,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK,GAAG;AAElD,KAAI;AAUH,SAAO,WAAW;GAAE,KATR,MAAM,cAAc;IAC/B;IACA;IACA,QAAQ;IACR;IACA;IACA,QAAQ;IACR,CAAC;GAEuB;GAAW,CAAC;UAC7B,OAAO;AACf,SAAO,YAAY,OAAO,kCAAkC,cAAc;;;;;;AAO5E,SAAS,eAAe,UAA0B;CACjD,MAAM,QAAQ,SAAS,MAAM,iBAAiB;AAC9C,KAAI,CAAC,MACJ,QAAO;CAGR,MAAM,QAAQ,SAAS,MAAM,IAAI,GAAG;AAGpC,SAFa,MAAM,IAEnB;EACC,KAAK,IACJ,QAAO;EACR,KAAK,IACJ,QAAO,QAAQ;EAChB,KAAK,IACJ,QAAO,QAAQ,KAAK;EACrB,KAAK,IACJ,QAAO,QAAQ,KAAK,KAAK;EAC1B,KAAK,IACJ,QAAO,QAAQ,KAAK,KAAK,KAAK;EAC/B,QACC,QAAO"}
@@ -1,11 +1,12 @@
1
1
  import "../../../../../../base64-CqR-7kqF.mjs";
2
2
  import "../../../../../../types-BXSUSAjt.mjs";
3
- import { a as unwrapResult, o as mapErrorStatus, t as apiError } from "../../../../../../error-RwM4dD35.mjs";
4
- import { r as parseOptionalBody, t as isParseError } from "../../../../../../parse-CrGndy1A.mjs";
5
- import { on as contentPublishBody } from "../../../../../../redirects-CCbCqCCd.mjs";
6
- import "../../../../../../byline-fields-8TMtkBnH.mjs";
3
+ import { a as unwrapResult, o as mapErrorStatus, t as apiError } from "../../../../../../error-CNn_w7jf.mjs";
4
+ import { r as parseOptionalBody, t as isParseError } from "../../../../../../parse-DzSrk1t8.mjs";
5
+ import { on as contentPublishBody } from "../../../../../../redirects-6Zg2SoYo.mjs";
6
+ import "../../../../../../byline-fields-B0NO1yUB.mjs";
7
+ import "../../../../../../status-2gZklYuj.mjs";
7
8
  import "../../../../../../api/schemas/index.mjs";
8
- import { t as requireOwnerPerm } from "../../../../../../authorize-DsMSVSaY.mjs";
9
+ import { t as requireOwnerPerm } from "../../../../../../authorize-D5gfBVU5.mjs";
9
10
  import { hasPermission } from "@emdash-cms/auth";
10
11
 
11
12
  //#region src/astro/routes/api/content/[collection]/[id]/publish.ts
@@ -1 +1 @@
1
- {"version":3,"file":"publish.mjs","names":[],"sources":["../../../../../../../src/astro/routes/api/content/[collection]/[id]/publish.ts"],"sourcesContent":["/**\n * Publish content - promotes draft to live\n *\n * POST /_emdash/api/content/{collection}/{id}/publish\n *\n * Optional JSON body: { publishedAt?: string }\n * publishedAt — ISO 8601 datetime to backdate the publish (e.g. when\n * migrating content). Writing publishedAt requires content:publish_any.\n * Without it, the existing published_at is preserved on re-publish and\n * falls back to the current time on first publish.\n */\n\nimport { hasPermission } from \"@emdash-cms/auth\";\nimport type { APIRoute } from \"astro\";\n\nimport { requireOwnerPerm } from \"#api/authorize.js\";\nimport { apiError, mapErrorStatus, unwrapResult } from \"#api/error.js\";\nimport { isParseError, parseOptionalBody } from \"#api/parse.js\";\nimport { contentPublishBody } from \"#api/schemas.js\";\n\nexport const prerender = false;\n\nexport const POST: APIRoute = async ({ params, request, locals, url, cache }) => {\n\tconst { emdash, user } = locals;\n\tconst collection = params.collection!;\n\tconst id = params.id!;\n\n\tif (!emdash?.handleContentPublish || !emdash?.handleContentGet) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\t// Body is optional — empty body means use the legacy behavior (preserve\n\t// or default published_at). Pass `publishedAt` to backdate.\n\tconst body = await parseOptionalBody(request, contentPublishBody, {});\n\tif (isParseError(body)) return body;\n\n\tconst locale = url.searchParams.get(\"locale\") || undefined;\n\n\t// Fetch item to check ownership\n\tconst existing = await emdash.handleContentGet(collection, id, locale);\n\tif (!existing.success) {\n\t\treturn apiError(\n\t\t\texisting.error?.code ?? \"UNKNOWN_ERROR\",\n\t\t\texisting.error?.message ?? \"Unknown error\",\n\t\t\tmapErrorStatus(existing.error?.code),\n\t\t);\n\t}\n\n\tconst existingData =\n\t\texisting.data && typeof existing.data === \"object\"\n\t\t\t? // eslint-disable-next-line typescript/no-unsafe-type-assertion -- handler returns unknown data; narrowed by typeof check above\n\t\t\t\t(existing.data as Record<string, unknown>)\n\t\t\t: undefined;\n\tconst existingItem =\n\t\texistingData?.item && typeof existingData.item === \"object\"\n\t\t\t? // eslint-disable-next-line typescript/no-unsafe-type-assertion -- narrowed by typeof check above\n\t\t\t\t(existingData.item as Record<string, unknown>)\n\t\t\t: existingData;\n\tconst authorId = typeof existingItem?.authorId === \"string\" ? existingItem.authorId : \"\";\n\tconst denied = requireOwnerPerm(user, authorId, \"content:publish_own\", \"content:publish_any\");\n\tif (denied) return denied;\n\n\t// Schema narrows `publishedAt` to `string | undefined`; null is rejected\n\t// at the schema layer (publish has no semantic meaning for \"clear\").\n\tconst publishedAt = body?.publishedAt;\n\n\t// Backdating overwrites historical record — gate behind publish_any\n\t// regardless of ownership.\n\tif (publishedAt !== undefined && !hasPermission(user, \"content:publish_any\")) {\n\t\treturn apiError(\n\t\t\t\"FORBIDDEN\",\n\t\t\t\"Setting publishedAt requires content:publish_any permission\",\n\t\t\t403,\n\t\t);\n\t}\n\n\tconst resolvedId = typeof existingItem?.id === \"string\" ? existingItem.id : id;\n\n\tconst result = await emdash.handleContentPublish(collection, resolvedId, {\n\t\tpublishedAt,\n\t});\n\n\tif (!result.success) return unwrapResult(result);\n\n\tif (cache?.enabled) await cache.invalidate({ tags: [collection, resolvedId] });\n\n\treturn unwrapResult(result);\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAoBA,MAAa,YAAY;AAEzB,MAAa,OAAiB,OAAO,EAAE,QAAQ,SAAS,QAAQ,KAAK,YAAY;CAChF,MAAM,EAAE,QAAQ,SAAS;CACzB,MAAM,aAAa,OAAO;CAC1B,MAAM,KAAK,OAAO;AAElB,KAAI,CAAC,QAAQ,wBAAwB,CAAC,QAAQ,iBAC7C,QAAO,SAAS,kBAAkB,6BAA6B,IAAI;CAKpE,MAAM,OAAO,MAAM,kBAAkB,SAAS,oBAAoB,EAAE,CAAC;AACrE,KAAI,aAAa,KAAK,CAAE,QAAO;CAE/B,MAAM,SAAS,IAAI,aAAa,IAAI,SAAS,IAAI;CAGjD,MAAM,WAAW,MAAM,OAAO,iBAAiB,YAAY,IAAI,OAAO;AACtE,KAAI,CAAC,SAAS,QACb,QAAO,SACN,SAAS,OAAO,QAAQ,iBACxB,SAAS,OAAO,WAAW,iBAC3B,eAAe,SAAS,OAAO,KAAK,CACpC;CAGF,MAAM,eACL,SAAS,QAAQ,OAAO,SAAS,SAAS,WAEvC,SAAS,OACT;CACJ,MAAM,eACL,cAAc,QAAQ,OAAO,aAAa,SAAS,WAEhD,aAAa,OACb;CAEJ,MAAM,SAAS,iBAAiB,MADf,OAAO,cAAc,aAAa,WAAW,aAAa,WAAW,IACtC,uBAAuB,sBAAsB;AAC7F,KAAI,OAAQ,QAAO;CAInB,MAAM,cAAc,MAAM;AAI1B,KAAI,gBAAgB,UAAa,CAAC,cAAc,MAAM,sBAAsB,CAC3E,QAAO,SACN,aACA,+DACA,IACA;CAGF,MAAM,aAAa,OAAO,cAAc,OAAO,WAAW,aAAa,KAAK;CAE5E,MAAM,SAAS,MAAM,OAAO,qBAAqB,YAAY,YAAY,EACxE,aACA,CAAC;AAEF,KAAI,CAAC,OAAO,QAAS,QAAO,aAAa,OAAO;AAEhD,KAAI,OAAO,QAAS,OAAM,MAAM,WAAW,EAAE,MAAM,CAAC,YAAY,WAAW,EAAE,CAAC;AAE9E,QAAO,aAAa,OAAO"}
1
+ {"version":3,"file":"publish.mjs","names":[],"sources":["../../../../../../../src/astro/routes/api/content/[collection]/[id]/publish.ts"],"sourcesContent":["/**\n * Publish content - promotes draft to live\n *\n * POST /_emdash/api/content/{collection}/{id}/publish\n *\n * Optional JSON body: { publishedAt?: string }\n * publishedAt — ISO 8601 datetime to backdate the publish (e.g. when\n * migrating content). Writing publishedAt requires content:publish_any.\n * Without it, the existing published_at is preserved on re-publish and\n * falls back to the current time on first publish.\n */\n\nimport { hasPermission } from \"@emdash-cms/auth\";\nimport type { APIRoute } from \"astro\";\n\nimport { requireOwnerPerm } from \"#api/authorize.js\";\nimport { apiError, mapErrorStatus, unwrapResult } from \"#api/error.js\";\nimport { isParseError, parseOptionalBody } from \"#api/parse.js\";\nimport { contentPublishBody } from \"#api/schemas.js\";\n\nexport const prerender = false;\n\nexport const POST: APIRoute = async ({ params, request, locals, url, cache }) => {\n\tconst { emdash, user } = locals;\n\tconst collection = params.collection!;\n\tconst id = params.id!;\n\n\tif (!emdash?.handleContentPublish || !emdash?.handleContentGet) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\t// Body is optional — empty body means use the legacy behavior (preserve\n\t// or default published_at). Pass `publishedAt` to backdate.\n\tconst body = await parseOptionalBody(request, contentPublishBody, {});\n\tif (isParseError(body)) return body;\n\n\tconst locale = url.searchParams.get(\"locale\") || undefined;\n\n\t// Fetch item to check ownership\n\tconst existing = await emdash.handleContentGet(collection, id, locale);\n\tif (!existing.success) {\n\t\treturn apiError(\n\t\t\texisting.error?.code ?? \"UNKNOWN_ERROR\",\n\t\t\texisting.error?.message ?? \"Unknown error\",\n\t\t\tmapErrorStatus(existing.error?.code),\n\t\t);\n\t}\n\n\tconst existingData =\n\t\texisting.data && typeof existing.data === \"object\"\n\t\t\t? // eslint-disable-next-line typescript/no-unsafe-type-assertion -- handler returns unknown data; narrowed by typeof check above\n\t\t\t\t(existing.data as Record<string, unknown>)\n\t\t\t: undefined;\n\tconst existingItem =\n\t\texistingData?.item && typeof existingData.item === \"object\"\n\t\t\t? // eslint-disable-next-line typescript/no-unsafe-type-assertion -- narrowed by typeof check above\n\t\t\t\t(existingData.item as Record<string, unknown>)\n\t\t\t: existingData;\n\tconst authorId = typeof existingItem?.authorId === \"string\" ? existingItem.authorId : \"\";\n\tconst denied = requireOwnerPerm(user, authorId, \"content:publish_own\", \"content:publish_any\");\n\tif (denied) return denied;\n\n\t// Schema narrows `publishedAt` to `string | undefined`; null is rejected\n\t// at the schema layer (publish has no semantic meaning for \"clear\").\n\tconst publishedAt = body?.publishedAt;\n\n\t// Backdating overwrites historical record — gate behind publish_any\n\t// regardless of ownership.\n\tif (publishedAt !== undefined && !hasPermission(user, \"content:publish_any\")) {\n\t\treturn apiError(\n\t\t\t\"FORBIDDEN\",\n\t\t\t\"Setting publishedAt requires content:publish_any permission\",\n\t\t\t403,\n\t\t);\n\t}\n\n\tconst resolvedId = typeof existingItem?.id === \"string\" ? existingItem.id : id;\n\n\tconst result = await emdash.handleContentPublish(collection, resolvedId, {\n\t\tpublishedAt,\n\t});\n\n\tif (!result.success) return unwrapResult(result);\n\n\tif (cache?.enabled) await cache.invalidate({ tags: [collection, resolvedId] });\n\n\treturn unwrapResult(result);\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAoBA,MAAa,YAAY;AAEzB,MAAa,OAAiB,OAAO,EAAE,QAAQ,SAAS,QAAQ,KAAK,YAAY;CAChF,MAAM,EAAE,QAAQ,SAAS;CACzB,MAAM,aAAa,OAAO;CAC1B,MAAM,KAAK,OAAO;AAElB,KAAI,CAAC,QAAQ,wBAAwB,CAAC,QAAQ,iBAC7C,QAAO,SAAS,kBAAkB,6BAA6B,IAAI;CAKpE,MAAM,OAAO,MAAM,kBAAkB,SAAS,oBAAoB,EAAE,CAAC;AACrE,KAAI,aAAa,KAAK,CAAE,QAAO;CAE/B,MAAM,SAAS,IAAI,aAAa,IAAI,SAAS,IAAI;CAGjD,MAAM,WAAW,MAAM,OAAO,iBAAiB,YAAY,IAAI,OAAO;AACtE,KAAI,CAAC,SAAS,QACb,QAAO,SACN,SAAS,OAAO,QAAQ,iBACxB,SAAS,OAAO,WAAW,iBAC3B,eAAe,SAAS,OAAO,KAAK,CACpC;CAGF,MAAM,eACL,SAAS,QAAQ,OAAO,SAAS,SAAS,WAEvC,SAAS,OACT;CACJ,MAAM,eACL,cAAc,QAAQ,OAAO,aAAa,SAAS,WAEhD,aAAa,OACb;CAEJ,MAAM,SAAS,iBAAiB,MADf,OAAO,cAAc,aAAa,WAAW,aAAa,WAAW,IACtC,uBAAuB,sBAAsB;AAC7F,KAAI,OAAQ,QAAO;CAInB,MAAM,cAAc,MAAM;AAI1B,KAAI,gBAAgB,UAAa,CAAC,cAAc,MAAM,sBAAsB,CAC3E,QAAO,SACN,aACA,+DACA,IACA;CAGF,MAAM,aAAa,OAAO,cAAc,OAAO,WAAW,aAAa,KAAK;CAE5E,MAAM,SAAS,MAAM,OAAO,qBAAqB,YAAY,YAAY,EACxE,aACA,CAAC;AAEF,KAAI,CAAC,OAAO,QAAS,QAAO,aAAa,OAAO;AAEhD,KAAI,OAAO,QAAS,OAAM,MAAM,WAAW,EAAE,MAAM,CAAC,YAAY,WAAW,EAAE,CAAC;AAE9E,QAAO,aAAa,OAAO"}
@@ -1,7 +1,7 @@
1
1
  import "../../../../../../base64-CqR-7kqF.mjs";
2
2
  import "../../../../../../types-BXSUSAjt.mjs";
3
- import { a as unwrapResult, o as mapErrorStatus, t as apiError } from "../../../../../../error-RwM4dD35.mjs";
4
- import { t as requireOwnerPerm } from "../../../../../../authorize-DsMSVSaY.mjs";
3
+ import { a as unwrapResult, o as mapErrorStatus, t as apiError } from "../../../../../../error-CNn_w7jf.mjs";
4
+ import { t as requireOwnerPerm } from "../../../../../../authorize-D5gfBVU5.mjs";
5
5
 
6
6
  //#region src/astro/routes/api/content/[collection]/[id]/restore.ts
7
7
  const prerender = false;
@@ -1,7 +1,7 @@
1
1
  import "../../../../../../base64-CqR-7kqF.mjs";
2
2
  import "../../../../../../types-BXSUSAjt.mjs";
3
- import { a as unwrapResult, t as apiError } from "../../../../../../error-RwM4dD35.mjs";
4
- import { n as requirePerm } from "../../../../../../authorize-DsMSVSaY.mjs";
3
+ import { a as unwrapResult, t as apiError } from "../../../../../../error-CNn_w7jf.mjs";
4
+ import { n as requirePerm } from "../../../../../../authorize-D5gfBVU5.mjs";
5
5
 
6
6
  //#region src/astro/routes/api/content/[collection]/[id]/revisions.ts
7
7
  const prerender = false;
@@ -1,11 +1,12 @@
1
1
  import "../../../../../../base64-CqR-7kqF.mjs";
2
2
  import "../../../../../../types-BXSUSAjt.mjs";
3
- import { a as unwrapResult, o as mapErrorStatus, t as apiError } from "../../../../../../error-RwM4dD35.mjs";
4
- import { n as parseBody, t as isParseError } from "../../../../../../parse-CrGndy1A.mjs";
5
- import { cn as contentScheduleBody } from "../../../../../../redirects-CCbCqCCd.mjs";
6
- import "../../../../../../byline-fields-8TMtkBnH.mjs";
3
+ import { a as unwrapResult, o as mapErrorStatus, t as apiError } from "../../../../../../error-CNn_w7jf.mjs";
4
+ import { n as parseBody, t as isParseError } from "../../../../../../parse-DzSrk1t8.mjs";
5
+ import { cn as contentScheduleBody } from "../../../../../../redirects-6Zg2SoYo.mjs";
6
+ import "../../../../../../byline-fields-B0NO1yUB.mjs";
7
+ import "../../../../../../status-2gZklYuj.mjs";
7
8
  import "../../../../../../api/schemas/index.mjs";
8
- import { t as requireOwnerPerm } from "../../../../../../authorize-DsMSVSaY.mjs";
9
+ import { t as requireOwnerPerm } from "../../../../../../authorize-D5gfBVU5.mjs";
9
10
 
10
11
  //#region src/astro/routes/api/content/[collection]/[id]/schedule.ts
11
12
  const prerender = false;
@@ -1 +1 @@
1
- {"version":3,"file":"schedule.mjs","names":[],"sources":["../../../../../../../src/astro/routes/api/content/[collection]/[id]/schedule.ts"],"sourcesContent":["/**\n * Schedule content for future publishing - injected by EmDash integration\n *\n * POST /_emdash/api/content/{collection}/{id}/schedule - Schedule for publishing\n * DELETE /_emdash/api/content/{collection}/{id}/schedule - Unschedule (clear scheduled time)\n */\n\nimport type { APIRoute } from \"astro\";\n\nimport { requireOwnerPerm } from \"#api/authorize.js\";\nimport { apiError, mapErrorStatus, unwrapResult } from \"#api/error.js\";\nimport { parseBody, isParseError } from \"#api/parse.js\";\nimport { contentScheduleBody } from \"#api/schemas.js\";\n\nexport const prerender = false;\n\n/**\n * Extract author ID from a content item response (shared by POST and DELETE).\n */\nfunction extractOwnership(data: unknown): { authorId: string; resolvedId: string | undefined } {\n\tconst obj =\n\t\tdata && typeof data === \"object\"\n\t\t\t? // eslint-disable-next-line typescript/no-unsafe-type-assertion -- handler returns unknown; narrowed by typeof\n\t\t\t\t(data as Record<string, unknown>)\n\t\t\t: undefined;\n\tconst item =\n\t\tobj?.item && typeof obj.item === \"object\"\n\t\t\t? // eslint-disable-next-line typescript/no-unsafe-type-assertion -- narrowed by typeof\n\t\t\t\t(obj.item as Record<string, unknown>)\n\t\t\t: obj;\n\treturn {\n\t\tauthorId: typeof item?.authorId === \"string\" ? item.authorId : \"\",\n\t\tresolvedId: typeof item?.id === \"string\" ? item.id : undefined,\n\t};\n}\n\nexport const POST: APIRoute = async ({ params, request, locals, url, cache }) => {\n\tconst { emdash, user } = locals;\n\tconst collection = params.collection!;\n\tconst id = params.id!;\n\tconst body = await parseBody(request, contentScheduleBody);\n\tif (isParseError(body)) return body;\n\n\tif (!emdash?.handleContentSchedule || !emdash?.handleContentGet) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\tconst locale = url.searchParams.get(\"locale\") || undefined;\n\n\t// Fetch item to check ownership\n\tconst existing = await emdash.handleContentGet(collection, id, locale);\n\tif (!existing.success) {\n\t\treturn apiError(\n\t\t\texisting.error?.code ?? \"UNKNOWN_ERROR\",\n\t\t\texisting.error?.message ?? \"Unknown error\",\n\t\t\tmapErrorStatus(existing.error?.code),\n\t\t);\n\t}\n\n\tconst { authorId, resolvedId } = extractOwnership(existing.data);\n\tconst denied = requireOwnerPerm(user, authorId, \"content:publish_own\", \"content:publish_any\");\n\tif (denied) return denied;\n\n\tconst result = await emdash.handleContentSchedule(collection, resolvedId ?? id, body.scheduledAt);\n\n\tif (!result.success) return unwrapResult(result);\n\n\tif (cache?.enabled) await cache.invalidate({ tags: [collection, resolvedId ?? id] });\n\n\treturn unwrapResult(result);\n};\n\nexport const DELETE: APIRoute = async ({ params, locals, url, cache }) => {\n\tconst { emdash, user } = locals;\n\tconst collection = params.collection!;\n\tconst id = params.id!;\n\n\tif (!emdash?.handleContentUnschedule || !emdash?.handleContentGet) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\tconst locale = url.searchParams.get(\"locale\") || undefined;\n\n\t// Fetch item to check ownership\n\tconst existing = await emdash.handleContentGet(collection, id, locale);\n\tif (!existing.success) {\n\t\treturn apiError(\n\t\t\texisting.error?.code ?? \"UNKNOWN_ERROR\",\n\t\t\texisting.error?.message ?? \"Unknown error\",\n\t\t\tmapErrorStatus(existing.error?.code),\n\t\t);\n\t}\n\n\tconst { authorId, resolvedId } = extractOwnership(existing.data);\n\tconst denied = requireOwnerPerm(user, authorId, \"content:publish_own\", \"content:publish_any\");\n\tif (denied) return denied;\n\n\tconst result = await emdash.handleContentUnschedule(collection, resolvedId ?? id);\n\n\tif (!result.success) return unwrapResult(result);\n\n\tif (cache?.enabled) await cache.invalidate({ tags: [collection, resolvedId ?? id] });\n\n\treturn unwrapResult(result);\n};\n"],"mappings":";;;;;;;;;;AAcA,MAAa,YAAY;;;;AAKzB,SAAS,iBAAiB,MAAqE;CAC9F,MAAM,MACL,QAAQ,OAAO,SAAS,WAErB,OACA;CACJ,MAAM,OACL,KAAK,QAAQ,OAAO,IAAI,SAAS,WAE9B,IAAI,OACJ;AACJ,QAAO;EACN,UAAU,OAAO,MAAM,aAAa,WAAW,KAAK,WAAW;EAC/D,YAAY,OAAO,MAAM,OAAO,WAAW,KAAK,KAAK;EACrD;;AAGF,MAAa,OAAiB,OAAO,EAAE,QAAQ,SAAS,QAAQ,KAAK,YAAY;CAChF,MAAM,EAAE,QAAQ,SAAS;CACzB,MAAM,aAAa,OAAO;CAC1B,MAAM,KAAK,OAAO;CAClB,MAAM,OAAO,MAAM,UAAU,SAAS,oBAAoB;AAC1D,KAAI,aAAa,KAAK,CAAE,QAAO;AAE/B,KAAI,CAAC,QAAQ,yBAAyB,CAAC,QAAQ,iBAC9C,QAAO,SAAS,kBAAkB,6BAA6B,IAAI;CAGpE,MAAM,SAAS,IAAI,aAAa,IAAI,SAAS,IAAI;CAGjD,MAAM,WAAW,MAAM,OAAO,iBAAiB,YAAY,IAAI,OAAO;AACtE,KAAI,CAAC,SAAS,QACb,QAAO,SACN,SAAS,OAAO,QAAQ,iBACxB,SAAS,OAAO,WAAW,iBAC3B,eAAe,SAAS,OAAO,KAAK,CACpC;CAGF,MAAM,EAAE,UAAU,eAAe,iBAAiB,SAAS,KAAK;CAChE,MAAM,SAAS,iBAAiB,MAAM,UAAU,uBAAuB,sBAAsB;AAC7F,KAAI,OAAQ,QAAO;CAEnB,MAAM,SAAS,MAAM,OAAO,sBAAsB,YAAY,cAAc,IAAI,KAAK,YAAY;AAEjG,KAAI,CAAC,OAAO,QAAS,QAAO,aAAa,OAAO;AAEhD,KAAI,OAAO,QAAS,OAAM,MAAM,WAAW,EAAE,MAAM,CAAC,YAAY,cAAc,GAAG,EAAE,CAAC;AAEpF,QAAO,aAAa,OAAO;;AAG5B,MAAa,SAAmB,OAAO,EAAE,QAAQ,QAAQ,KAAK,YAAY;CACzE,MAAM,EAAE,QAAQ,SAAS;CACzB,MAAM,aAAa,OAAO;CAC1B,MAAM,KAAK,OAAO;AAElB,KAAI,CAAC,QAAQ,2BAA2B,CAAC,QAAQ,iBAChD,QAAO,SAAS,kBAAkB,6BAA6B,IAAI;CAGpE,MAAM,SAAS,IAAI,aAAa,IAAI,SAAS,IAAI;CAGjD,MAAM,WAAW,MAAM,OAAO,iBAAiB,YAAY,IAAI,OAAO;AACtE,KAAI,CAAC,SAAS,QACb,QAAO,SACN,SAAS,OAAO,QAAQ,iBACxB,SAAS,OAAO,WAAW,iBAC3B,eAAe,SAAS,OAAO,KAAK,CACpC;CAGF,MAAM,EAAE,UAAU,eAAe,iBAAiB,SAAS,KAAK;CAChE,MAAM,SAAS,iBAAiB,MAAM,UAAU,uBAAuB,sBAAsB;AAC7F,KAAI,OAAQ,QAAO;CAEnB,MAAM,SAAS,MAAM,OAAO,wBAAwB,YAAY,cAAc,GAAG;AAEjF,KAAI,CAAC,OAAO,QAAS,QAAO,aAAa,OAAO;AAEhD,KAAI,OAAO,QAAS,OAAM,MAAM,WAAW,EAAE,MAAM,CAAC,YAAY,cAAc,GAAG,EAAE,CAAC;AAEpF,QAAO,aAAa,OAAO"}
1
+ {"version":3,"file":"schedule.mjs","names":[],"sources":["../../../../../../../src/astro/routes/api/content/[collection]/[id]/schedule.ts"],"sourcesContent":["/**\n * Schedule content for future publishing - injected by EmDash integration\n *\n * POST /_emdash/api/content/{collection}/{id}/schedule - Schedule for publishing\n * DELETE /_emdash/api/content/{collection}/{id}/schedule - Unschedule (clear scheduled time)\n */\n\nimport type { APIRoute } from \"astro\";\n\nimport { requireOwnerPerm } from \"#api/authorize.js\";\nimport { apiError, mapErrorStatus, unwrapResult } from \"#api/error.js\";\nimport { parseBody, isParseError } from \"#api/parse.js\";\nimport { contentScheduleBody } from \"#api/schemas.js\";\n\nexport const prerender = false;\n\n/**\n * Extract author ID from a content item response (shared by POST and DELETE).\n */\nfunction extractOwnership(data: unknown): { authorId: string; resolvedId: string | undefined } {\n\tconst obj =\n\t\tdata && typeof data === \"object\"\n\t\t\t? // eslint-disable-next-line typescript/no-unsafe-type-assertion -- handler returns unknown; narrowed by typeof\n\t\t\t\t(data as Record<string, unknown>)\n\t\t\t: undefined;\n\tconst item =\n\t\tobj?.item && typeof obj.item === \"object\"\n\t\t\t? // eslint-disable-next-line typescript/no-unsafe-type-assertion -- narrowed by typeof\n\t\t\t\t(obj.item as Record<string, unknown>)\n\t\t\t: obj;\n\treturn {\n\t\tauthorId: typeof item?.authorId === \"string\" ? item.authorId : \"\",\n\t\tresolvedId: typeof item?.id === \"string\" ? item.id : undefined,\n\t};\n}\n\nexport const POST: APIRoute = async ({ params, request, locals, url, cache }) => {\n\tconst { emdash, user } = locals;\n\tconst collection = params.collection!;\n\tconst id = params.id!;\n\tconst body = await parseBody(request, contentScheduleBody);\n\tif (isParseError(body)) return body;\n\n\tif (!emdash?.handleContentSchedule || !emdash?.handleContentGet) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\tconst locale = url.searchParams.get(\"locale\") || undefined;\n\n\t// Fetch item to check ownership\n\tconst existing = await emdash.handleContentGet(collection, id, locale);\n\tif (!existing.success) {\n\t\treturn apiError(\n\t\t\texisting.error?.code ?? \"UNKNOWN_ERROR\",\n\t\t\texisting.error?.message ?? \"Unknown error\",\n\t\t\tmapErrorStatus(existing.error?.code),\n\t\t);\n\t}\n\n\tconst { authorId, resolvedId } = extractOwnership(existing.data);\n\tconst denied = requireOwnerPerm(user, authorId, \"content:publish_own\", \"content:publish_any\");\n\tif (denied) return denied;\n\n\tconst result = await emdash.handleContentSchedule(collection, resolvedId ?? id, body.scheduledAt);\n\n\tif (!result.success) return unwrapResult(result);\n\n\tif (cache?.enabled) await cache.invalidate({ tags: [collection, resolvedId ?? id] });\n\n\treturn unwrapResult(result);\n};\n\nexport const DELETE: APIRoute = async ({ params, locals, url, cache }) => {\n\tconst { emdash, user } = locals;\n\tconst collection = params.collection!;\n\tconst id = params.id!;\n\n\tif (!emdash?.handleContentUnschedule || !emdash?.handleContentGet) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\tconst locale = url.searchParams.get(\"locale\") || undefined;\n\n\t// Fetch item to check ownership\n\tconst existing = await emdash.handleContentGet(collection, id, locale);\n\tif (!existing.success) {\n\t\treturn apiError(\n\t\t\texisting.error?.code ?? \"UNKNOWN_ERROR\",\n\t\t\texisting.error?.message ?? \"Unknown error\",\n\t\t\tmapErrorStatus(existing.error?.code),\n\t\t);\n\t}\n\n\tconst { authorId, resolvedId } = extractOwnership(existing.data);\n\tconst denied = requireOwnerPerm(user, authorId, \"content:publish_own\", \"content:publish_any\");\n\tif (denied) return denied;\n\n\tconst result = await emdash.handleContentUnschedule(collection, resolvedId ?? id);\n\n\tif (!result.success) return unwrapResult(result);\n\n\tif (cache?.enabled) await cache.invalidate({ tags: [collection, resolvedId ?? id] });\n\n\treturn unwrapResult(result);\n};\n"],"mappings":";;;;;;;;;;;AAcA,MAAa,YAAY;;;;AAKzB,SAAS,iBAAiB,MAAqE;CAC9F,MAAM,MACL,QAAQ,OAAO,SAAS,WAErB,OACA;CACJ,MAAM,OACL,KAAK,QAAQ,OAAO,IAAI,SAAS,WAE9B,IAAI,OACJ;AACJ,QAAO;EACN,UAAU,OAAO,MAAM,aAAa,WAAW,KAAK,WAAW;EAC/D,YAAY,OAAO,MAAM,OAAO,WAAW,KAAK,KAAK;EACrD;;AAGF,MAAa,OAAiB,OAAO,EAAE,QAAQ,SAAS,QAAQ,KAAK,YAAY;CAChF,MAAM,EAAE,QAAQ,SAAS;CACzB,MAAM,aAAa,OAAO;CAC1B,MAAM,KAAK,OAAO;CAClB,MAAM,OAAO,MAAM,UAAU,SAAS,oBAAoB;AAC1D,KAAI,aAAa,KAAK,CAAE,QAAO;AAE/B,KAAI,CAAC,QAAQ,yBAAyB,CAAC,QAAQ,iBAC9C,QAAO,SAAS,kBAAkB,6BAA6B,IAAI;CAGpE,MAAM,SAAS,IAAI,aAAa,IAAI,SAAS,IAAI;CAGjD,MAAM,WAAW,MAAM,OAAO,iBAAiB,YAAY,IAAI,OAAO;AACtE,KAAI,CAAC,SAAS,QACb,QAAO,SACN,SAAS,OAAO,QAAQ,iBACxB,SAAS,OAAO,WAAW,iBAC3B,eAAe,SAAS,OAAO,KAAK,CACpC;CAGF,MAAM,EAAE,UAAU,eAAe,iBAAiB,SAAS,KAAK;CAChE,MAAM,SAAS,iBAAiB,MAAM,UAAU,uBAAuB,sBAAsB;AAC7F,KAAI,OAAQ,QAAO;CAEnB,MAAM,SAAS,MAAM,OAAO,sBAAsB,YAAY,cAAc,IAAI,KAAK,YAAY;AAEjG,KAAI,CAAC,OAAO,QAAS,QAAO,aAAa,OAAO;AAEhD,KAAI,OAAO,QAAS,OAAM,MAAM,WAAW,EAAE,MAAM,CAAC,YAAY,cAAc,GAAG,EAAE,CAAC;AAEpF,QAAO,aAAa,OAAO;;AAG5B,MAAa,SAAmB,OAAO,EAAE,QAAQ,QAAQ,KAAK,YAAY;CACzE,MAAM,EAAE,QAAQ,SAAS;CACzB,MAAM,aAAa,OAAO;CAC1B,MAAM,KAAK,OAAO;AAElB,KAAI,CAAC,QAAQ,2BAA2B,CAAC,QAAQ,iBAChD,QAAO,SAAS,kBAAkB,6BAA6B,IAAI;CAGpE,MAAM,SAAS,IAAI,aAAa,IAAI,SAAS,IAAI;CAGjD,MAAM,WAAW,MAAM,OAAO,iBAAiB,YAAY,IAAI,OAAO;AACtE,KAAI,CAAC,SAAS,QACb,QAAO,SACN,SAAS,OAAO,QAAQ,iBACxB,SAAS,OAAO,WAAW,iBAC3B,eAAe,SAAS,OAAO,KAAK,CACpC;CAGF,MAAM,EAAE,UAAU,eAAe,iBAAiB,SAAS,KAAK;CAChE,MAAM,SAAS,iBAAiB,MAAM,UAAU,uBAAuB,sBAAsB;AAC7F,KAAI,OAAQ,QAAO;CAEnB,MAAM,SAAS,MAAM,OAAO,wBAAwB,YAAY,cAAc,GAAG;AAEjF,KAAI,CAAC,OAAO,QAAS,QAAO,aAAa,OAAO;AAEhD,KAAI,OAAO,QAAS,OAAM,MAAM,WAAW,EAAE,MAAM,CAAC,YAAY,cAAc,GAAG,EAAE,CAAC;AAEpF,QAAO,aAAa,OAAO"}
@@ -3,16 +3,17 @@ import { t as ContentRepository } from "../../../../../../../content-BIlVx-RX.mj
3
3
  import "../../../../../../../base64-CqR-7kqF.mjs";
4
4
  import "../../../../../../../types-BXSUSAjt.mjs";
5
5
  import { t as TaxonomyRepository } from "../../../../../../../taxonomy-CdllE4oq.mjs";
6
- import "../../../../../../../request-cache-D32LpnmI.mjs";
7
- import "../../../../../../../loader-ZN1ll-d-.mjs";
8
- import "../../../../../../../resolve-BqYMVG0D.mjs";
9
- import { l as invalidateTermCache } from "../../../../../../../taxonomies-BdAmbOwx.mjs";
10
- import { i as requireDb, n as apiSuccess, r as handleError, t as apiError } from "../../../../../../../error-RwM4dD35.mjs";
11
- import { n as parseBody, t as isParseError } from "../../../../../../../parse-CrGndy1A.mjs";
12
- import { dn as contentTermsBody } from "../../../../../../../redirects-CCbCqCCd.mjs";
13
- import "../../../../../../../byline-fields-8TMtkBnH.mjs";
6
+ import "../../../../../../../request-cache-UwmBAiUK.mjs";
7
+ import "../../../../../../../loader-BqWjcH3h.mjs";
8
+ import "../../../../../../../resolve-B3NUUtVY.mjs";
9
+ import { l as invalidateTermCache } from "../../../../../../../taxonomies-BBxYA38v.mjs";
10
+ import { i as requireDb, n as apiSuccess, r as handleError, t as apiError } from "../../../../../../../error-CNn_w7jf.mjs";
11
+ import { n as parseBody, t as isParseError } from "../../../../../../../parse-DzSrk1t8.mjs";
12
+ import { dn as contentTermsBody } from "../../../../../../../redirects-6Zg2SoYo.mjs";
13
+ import "../../../../../../../byline-fields-B0NO1yUB.mjs";
14
+ import "../../../../../../../status-2gZklYuj.mjs";
14
15
  import "../../../../../../../api/schemas/index.mjs";
15
- import { n as requirePerm, t as requireOwnerPerm } from "../../../../../../../authorize-DsMSVSaY.mjs";
16
+ import { n as requirePerm, t as requireOwnerPerm } from "../../../../../../../authorize-D5gfBVU5.mjs";
16
17
 
17
18
  //#region src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts
18
19
  const prerender = false;
@@ -1 +1 @@
1
- {"version":3,"file":"_taxonomy_.mjs","names":[],"sources":["../../../../../../../../src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts"],"sourcesContent":["/**\n * Content-taxonomy association endpoint\n *\n * GET /_emdash/api/content/:collection/:id/terms/:taxonomy - Get terms for an entry\n * POST /_emdash/api/content/:collection/:id/terms/:taxonomy - Set terms for an entry\n */\n\nimport type { APIRoute } from \"astro\";\n\nimport { requirePerm, requireOwnerPerm } from \"#api/authorize.js\";\nimport { apiError, apiSuccess, handleError, requireDb } from \"#api/error.js\";\nimport { parseBody, isParseError } from \"#api/parse.js\";\nimport { contentTermsBody } from \"#api/schemas.js\";\nimport { ContentRepository } from \"#db/repositories/content.js\";\nimport { TaxonomyRepository } from \"#db/repositories/taxonomy.js\";\nimport { invalidateTermCache } from \"#taxonomies/index.js\";\n\nexport const prerender = false;\n\n/**\n * Get terms assigned to an entry\n */\nexport const GET: APIRoute = async ({ params, locals }) => {\n\tconst { emdash, user } = locals;\n\tconst { collection, id, taxonomy } = params;\n\n\tconst denied = requirePerm(user, \"content:read\");\n\tif (denied) return denied;\n\n\tif (!collection || !id || !taxonomy) {\n\t\treturn apiError(\"VALIDATION_ERROR\", \"Collection, id, and taxonomy required\", 400);\n\t}\n\n\tconst dbErr = requireDb(emdash?.db);\n\tif (dbErr) return dbErr;\n\n\ttry {\n\t\t// Terms are stored against the per-locale entry row but their\n\t\t// translation_group spans every locale. Resolve the entry's own locale\n\t\t// server-side (deterministic, not client-spoofable) so only the matching\n\t\t// term variant is returned — see issue #1218.\n\t\tconst entry = await new ContentRepository(emdash.db).findByIdOrSlug(collection, id);\n\t\tif (!entry) return apiError(\"NOT_FOUND\", \"Content not found\", 404);\n\t\tconst locale = entry.locale ?? undefined;\n\n\t\tconst repo = new TaxonomyRepository(emdash.db);\n\t\tconst terms = await repo.getTermsForEntry(collection, entry.id, taxonomy, locale);\n\n\t\treturn apiSuccess({\n\t\t\tterms: terms.map((t) => ({\n\t\t\t\tid: t.id,\n\t\t\t\tname: t.name,\n\t\t\t\tslug: t.slug,\n\t\t\t\tlabel: t.label,\n\t\t\t\tparentId: t.parentId,\n\t\t\t})),\n\t\t});\n\t} catch (error) {\n\t\treturn handleError(error, \"Failed to get entry terms\", \"TERMS_GET_ERROR\");\n\t}\n};\n\n/**\n * Set terms for an entry (replaces existing)\n */\nexport const POST: APIRoute = async ({ params, request, locals }) => {\n\tconst { emdash, user } = locals;\n\tconst { collection, id, taxonomy } = params;\n\n\tif (!collection || !id || !taxonomy) {\n\t\treturn apiError(\"VALIDATION_ERROR\", \"Collection, id, and taxonomy required\", 400);\n\t}\n\n\tconst denied = requirePerm(user, \"content:edit_own\");\n\tif (denied) return denied;\n\n\tconst dbErr = requireDb(emdash?.db);\n\tif (dbErr) return dbErr;\n\n\tif (!emdash.handleContentGet) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\t// Verify the content exists before modifying its terms\n\tconst existing = await emdash.handleContentGet(collection, id);\n\tif (!existing.success) {\n\t\treturn apiError(\n\t\t\texisting.error?.code ?? \"NOT_FOUND\",\n\t\t\texisting.error?.message ?? \"Content not found\",\n\t\t\texisting.error?.code === \"NOT_FOUND\" ? 404 : 500,\n\t\t);\n\t}\n\n\t// Check ownership for edit permission\n\tconst existingData =\n\t\texisting.data && typeof existing.data === \"object\"\n\t\t\t? // eslint-disable-next-line typescript/no-unsafe-type-assertion -- handler returns unknown data; narrowed by typeof check above\n\t\t\t\t(existing.data as Record<string, unknown>)\n\t\t\t: undefined;\n\t// Handler returns { item, _rev } — extract the item for ownership check\n\tconst existingItem =\n\t\texistingData?.item && typeof existingData.item === \"object\"\n\t\t\t? // eslint-disable-next-line typescript/no-unsafe-type-assertion -- narrowed by typeof check above\n\t\t\t\t(existingData.item as Record<string, unknown>)\n\t\t\t: existingData;\n\tconst authorId = typeof existingItem?.authorId === \"string\" ? existingItem.authorId : \"\";\n\tconst editDenied = requireOwnerPerm(user, authorId, \"content:edit_own\", \"content:edit_any\");\n\tif (editDenied) return editDenied;\n\n\t// Resolve the canonical content ID from the handler result.\n\t// The URL `id` param may be a slug; we must use the real ID for term storage.\n\tconst canonicalId = typeof existingItem?.id === \"string\" ? existingItem.id : id;\n\t// The entry is per-locale; scope the term read to its locale so only the\n\t// matching translation variant is returned in the response — see #1218.\n\tconst entryLocale = typeof existingItem?.locale === \"string\" ? existingItem.locale : undefined;\n\n\ttry {\n\t\tconst body = await parseBody(request, contentTermsBody);\n\t\tif (isParseError(body)) return body;\n\t\tconst { termIds } = body;\n\n\t\tconst repo = new TaxonomyRepository(emdash.db);\n\n\t\t// Verify all term IDs exist and belong to the correct taxonomy\n\t\tfor (const termId of termIds) {\n\t\t\tconst term = await repo.findById(termId);\n\t\t\tif (!term) {\n\t\t\t\treturn apiError(\"NOT_FOUND\", `Term ID '${termId}' not found`, 404);\n\t\t\t}\n\t\t\tif (term.name !== taxonomy) {\n\t\t\t\treturn apiError(\n\t\t\t\t\t\"VALIDATION_ERROR\",\n\t\t\t\t\t`Term ID '${termId}' does not belong to taxonomy '${taxonomy}'`,\n\t\t\t\t\t400,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Set the terms (replaces existing) using the canonical ID\n\t\tawait repo.setTermsForEntry(collection, canonicalId, taxonomy, termIds);\n\n\t\t// Term assignments changed — invalidate the hasAnyTermAssignments cache\n\t\t// so hydration on subsequent reads issues a fresh query.\n\t\tinvalidateTermCache();\n\n\t\t// Get the updated terms using the canonical ID, scoped to the entry locale\n\t\tconst terms = await repo.getTermsForEntry(collection, canonicalId, taxonomy, entryLocale);\n\n\t\treturn apiSuccess({\n\t\t\tterms: terms.map((t) => ({\n\t\t\t\tid: t.id,\n\t\t\t\tname: t.name,\n\t\t\t\tslug: t.slug,\n\t\t\t\tlabel: t.label,\n\t\t\t\tparentId: t.parentId,\n\t\t\t})),\n\t\t});\n\t} catch (error) {\n\t\treturn handleError(error, \"Failed to set entry terms\", \"TERMS_SET_ERROR\");\n\t}\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"_taxonomy_.mjs","names":[],"sources":["../../../../../../../../src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts"],"sourcesContent":["/**\n * Content-taxonomy association endpoint\n *\n * GET /_emdash/api/content/:collection/:id/terms/:taxonomy - Get terms for an entry\n * POST /_emdash/api/content/:collection/:id/terms/:taxonomy - Set terms for an entry\n */\n\nimport type { APIRoute } from \"astro\";\n\nimport { requirePerm, requireOwnerPerm } from \"#api/authorize.js\";\nimport { apiError, apiSuccess, handleError, requireDb } from \"#api/error.js\";\nimport { parseBody, isParseError } from \"#api/parse.js\";\nimport { contentTermsBody } from \"#api/schemas.js\";\nimport { ContentRepository } from \"#db/repositories/content.js\";\nimport { TaxonomyRepository } from \"#db/repositories/taxonomy.js\";\nimport { invalidateTermCache } from \"#taxonomies/index.js\";\n\nexport const prerender = false;\n\n/**\n * Get terms assigned to an entry\n */\nexport const GET: APIRoute = async ({ params, locals }) => {\n\tconst { emdash, user } = locals;\n\tconst { collection, id, taxonomy } = params;\n\n\tconst denied = requirePerm(user, \"content:read\");\n\tif (denied) return denied;\n\n\tif (!collection || !id || !taxonomy) {\n\t\treturn apiError(\"VALIDATION_ERROR\", \"Collection, id, and taxonomy required\", 400);\n\t}\n\n\tconst dbErr = requireDb(emdash?.db);\n\tif (dbErr) return dbErr;\n\n\ttry {\n\t\t// Terms are stored against the per-locale entry row but their\n\t\t// translation_group spans every locale. Resolve the entry's own locale\n\t\t// server-side (deterministic, not client-spoofable) so only the matching\n\t\t// term variant is returned — see issue #1218.\n\t\tconst entry = await new ContentRepository(emdash.db).findByIdOrSlug(collection, id);\n\t\tif (!entry) return apiError(\"NOT_FOUND\", \"Content not found\", 404);\n\t\tconst locale = entry.locale ?? undefined;\n\n\t\tconst repo = new TaxonomyRepository(emdash.db);\n\t\tconst terms = await repo.getTermsForEntry(collection, entry.id, taxonomy, locale);\n\n\t\treturn apiSuccess({\n\t\t\tterms: terms.map((t) => ({\n\t\t\t\tid: t.id,\n\t\t\t\tname: t.name,\n\t\t\t\tslug: t.slug,\n\t\t\t\tlabel: t.label,\n\t\t\t\tparentId: t.parentId,\n\t\t\t})),\n\t\t});\n\t} catch (error) {\n\t\treturn handleError(error, \"Failed to get entry terms\", \"TERMS_GET_ERROR\");\n\t}\n};\n\n/**\n * Set terms for an entry (replaces existing)\n */\nexport const POST: APIRoute = async ({ params, request, locals }) => {\n\tconst { emdash, user } = locals;\n\tconst { collection, id, taxonomy } = params;\n\n\tif (!collection || !id || !taxonomy) {\n\t\treturn apiError(\"VALIDATION_ERROR\", \"Collection, id, and taxonomy required\", 400);\n\t}\n\n\tconst denied = requirePerm(user, \"content:edit_own\");\n\tif (denied) return denied;\n\n\tconst dbErr = requireDb(emdash?.db);\n\tif (dbErr) return dbErr;\n\n\tif (!emdash.handleContentGet) {\n\t\treturn apiError(\"NOT_CONFIGURED\", \"EmDash is not initialized\", 500);\n\t}\n\n\t// Verify the content exists before modifying its terms\n\tconst existing = await emdash.handleContentGet(collection, id);\n\tif (!existing.success) {\n\t\treturn apiError(\n\t\t\texisting.error?.code ?? \"NOT_FOUND\",\n\t\t\texisting.error?.message ?? \"Content not found\",\n\t\t\texisting.error?.code === \"NOT_FOUND\" ? 404 : 500,\n\t\t);\n\t}\n\n\t// Check ownership for edit permission\n\tconst existingData =\n\t\texisting.data && typeof existing.data === \"object\"\n\t\t\t? // eslint-disable-next-line typescript/no-unsafe-type-assertion -- handler returns unknown data; narrowed by typeof check above\n\t\t\t\t(existing.data as Record<string, unknown>)\n\t\t\t: undefined;\n\t// Handler returns { item, _rev } — extract the item for ownership check\n\tconst existingItem =\n\t\texistingData?.item && typeof existingData.item === \"object\"\n\t\t\t? // eslint-disable-next-line typescript/no-unsafe-type-assertion -- narrowed by typeof check above\n\t\t\t\t(existingData.item as Record<string, unknown>)\n\t\t\t: existingData;\n\tconst authorId = typeof existingItem?.authorId === \"string\" ? existingItem.authorId : \"\";\n\tconst editDenied = requireOwnerPerm(user, authorId, \"content:edit_own\", \"content:edit_any\");\n\tif (editDenied) return editDenied;\n\n\t// Resolve the canonical content ID from the handler result.\n\t// The URL `id` param may be a slug; we must use the real ID for term storage.\n\tconst canonicalId = typeof existingItem?.id === \"string\" ? existingItem.id : id;\n\t// The entry is per-locale; scope the term read to its locale so only the\n\t// matching translation variant is returned in the response — see #1218.\n\tconst entryLocale = typeof existingItem?.locale === \"string\" ? existingItem.locale : undefined;\n\n\ttry {\n\t\tconst body = await parseBody(request, contentTermsBody);\n\t\tif (isParseError(body)) return body;\n\t\tconst { termIds } = body;\n\n\t\tconst repo = new TaxonomyRepository(emdash.db);\n\n\t\t// Verify all term IDs exist and belong to the correct taxonomy\n\t\tfor (const termId of termIds) {\n\t\t\tconst term = await repo.findById(termId);\n\t\t\tif (!term) {\n\t\t\t\treturn apiError(\"NOT_FOUND\", `Term ID '${termId}' not found`, 404);\n\t\t\t}\n\t\t\tif (term.name !== taxonomy) {\n\t\t\t\treturn apiError(\n\t\t\t\t\t\"VALIDATION_ERROR\",\n\t\t\t\t\t`Term ID '${termId}' does not belong to taxonomy '${taxonomy}'`,\n\t\t\t\t\t400,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Set the terms (replaces existing) using the canonical ID\n\t\tawait repo.setTermsForEntry(collection, canonicalId, taxonomy, termIds);\n\n\t\t// Term assignments changed — invalidate the hasAnyTermAssignments cache\n\t\t// so hydration on subsequent reads issues a fresh query.\n\t\tinvalidateTermCache();\n\n\t\t// Get the updated terms using the canonical ID, scoped to the entry locale\n\t\tconst terms = await repo.getTermsForEntry(collection, canonicalId, taxonomy, entryLocale);\n\n\t\treturn apiSuccess({\n\t\t\tterms: terms.map((t) => ({\n\t\t\t\tid: t.id,\n\t\t\t\tname: t.name,\n\t\t\t\tslug: t.slug,\n\t\t\t\tlabel: t.label,\n\t\t\t\tparentId: t.parentId,\n\t\t\t})),\n\t\t});\n\t} catch (error) {\n\t\treturn handleError(error, \"Failed to set entry terms\", \"TERMS_SET_ERROR\");\n\t}\n};\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import "../../../../../../base64-CqR-7kqF.mjs";
2
2
  import "../../../../../../types-BXSUSAjt.mjs";
3
- import { a as unwrapResult, t as apiError } from "../../../../../../error-RwM4dD35.mjs";
4
- import { n as requirePerm } from "../../../../../../authorize-DsMSVSaY.mjs";
3
+ import { a as unwrapResult, t as apiError } from "../../../../../../error-CNn_w7jf.mjs";
4
+ import { n as requirePerm } from "../../../../../../authorize-D5gfBVU5.mjs";
5
5
  import { hasPermission } from "@emdash-cms/auth";
6
6
 
7
7
  //#region src/astro/routes/api/content/[collection]/[id]/translations.ts
@@ -1,7 +1,7 @@
1
1
  import "../../../../../../base64-CqR-7kqF.mjs";
2
2
  import "../../../../../../types-BXSUSAjt.mjs";
3
- import { a as unwrapResult, o as mapErrorStatus, t as apiError } from "../../../../../../error-RwM4dD35.mjs";
4
- import { t as requireOwnerPerm } from "../../../../../../authorize-DsMSVSaY.mjs";
3
+ import { a as unwrapResult, o as mapErrorStatus, t as apiError } from "../../../../../../error-CNn_w7jf.mjs";
4
+ import { t as requireOwnerPerm } from "../../../../../../authorize-D5gfBVU5.mjs";
5
5
 
6
6
  //#region src/astro/routes/api/content/[collection]/[id]/unpublish.ts
7
7
  const prerender = false;