emdash 0.0.0-a → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (661) hide show
  1. package/README.md +87 -1
  2. package/dist/adapters-BLMa4JGD.d.mts +106 -0
  3. package/dist/adapters-BLMa4JGD.d.mts.map +1 -0
  4. package/dist/apply-Bjfq_b4-.mjs +1293 -0
  5. package/dist/apply-Bjfq_b4-.mjs.map +1 -0
  6. package/dist/astro/index.d.mts +51 -0
  7. package/dist/astro/index.d.mts.map +1 -0
  8. package/dist/astro/index.mjs +1333 -0
  9. package/dist/astro/index.mjs.map +1 -0
  10. package/dist/astro/middleware/auth.d.mts +31 -0
  11. package/dist/astro/middleware/auth.d.mts.map +1 -0
  12. package/dist/astro/middleware/auth.mjs +654 -0
  13. package/dist/astro/middleware/auth.mjs.map +1 -0
  14. package/dist/astro/middleware/redirect.d.mts +22 -0
  15. package/dist/astro/middleware/redirect.d.mts.map +1 -0
  16. package/dist/astro/middleware/redirect.mjs +63 -0
  17. package/dist/astro/middleware/redirect.mjs.map +1 -0
  18. package/dist/astro/middleware/request-context.d.mts +18 -0
  19. package/dist/astro/middleware/request-context.d.mts.map +1 -0
  20. package/dist/astro/middleware/request-context.mjs +1310 -0
  21. package/dist/astro/middleware/request-context.mjs.map +1 -0
  22. package/dist/astro/middleware/setup.d.mts +20 -0
  23. package/dist/astro/middleware/setup.d.mts.map +1 -0
  24. package/dist/astro/middleware/setup.mjs +47 -0
  25. package/dist/astro/middleware/setup.mjs.map +1 -0
  26. package/dist/astro/middleware.d.mts +13 -0
  27. package/dist/astro/middleware.d.mts.map +1 -0
  28. package/dist/astro/middleware.mjs +1613 -0
  29. package/dist/astro/middleware.mjs.map +1 -0
  30. package/dist/astro/types.d.mts +250 -0
  31. package/dist/astro/types.d.mts.map +1 -0
  32. package/dist/astro/types.mjs +1 -0
  33. package/dist/base64-MBPo9ozB.mjs +59 -0
  34. package/dist/base64-MBPo9ozB.mjs.map +1 -0
  35. package/dist/byline-CL847F26.mjs +213 -0
  36. package/dist/byline-CL847F26.mjs.map +1 -0
  37. package/dist/bylines-C2a-2TGt.mjs +136 -0
  38. package/dist/bylines-C2a-2TGt.mjs.map +1 -0
  39. package/dist/chunk-ClPoSABd.mjs +21 -0
  40. package/dist/cli/index.d.mts +1 -0
  41. package/dist/cli/index.mjs +3909 -0
  42. package/dist/cli/index.mjs.map +1 -0
  43. package/dist/client/cf-access.d.mts +60 -0
  44. package/dist/client/cf-access.d.mts.map +1 -0
  45. package/dist/client/cf-access.mjs +179 -0
  46. package/dist/client/cf-access.mjs.map +1 -0
  47. package/dist/client/index.d.mts +398 -0
  48. package/dist/client/index.d.mts.map +1 -0
  49. package/dist/client/index.mjs +346 -0
  50. package/dist/client/index.mjs.map +1 -0
  51. package/dist/config-CKE8p9xM.mjs +55 -0
  52. package/dist/config-CKE8p9xM.mjs.map +1 -0
  53. package/dist/connection-B4zVnQIa.mjs +40 -0
  54. package/dist/connection-B4zVnQIa.mjs.map +1 -0
  55. package/dist/content-D6C2WsZC.mjs +824 -0
  56. package/dist/content-D6C2WsZC.mjs.map +1 -0
  57. package/dist/db/index.d.mts +4 -0
  58. package/dist/db/index.mjs +62 -0
  59. package/dist/db/index.mjs.map +1 -0
  60. package/dist/db/libsql.d.mts +11 -0
  61. package/dist/db/libsql.d.mts.map +1 -0
  62. package/dist/db/libsql.mjs +17 -0
  63. package/dist/db/libsql.mjs.map +1 -0
  64. package/dist/db/postgres.d.mts +11 -0
  65. package/dist/db/postgres.d.mts.map +1 -0
  66. package/dist/db/postgres.mjs +30 -0
  67. package/dist/db/postgres.mjs.map +1 -0
  68. package/dist/db/sqlite.d.mts +11 -0
  69. package/dist/db/sqlite.d.mts.map +1 -0
  70. package/dist/db/sqlite.mjs +16 -0
  71. package/dist/db/sqlite.mjs.map +1 -0
  72. package/dist/default-Cyi4aAxu.mjs +81 -0
  73. package/dist/default-Cyi4aAxu.mjs.map +1 -0
  74. package/dist/dialect-helpers-B9uSp2GJ.mjs +90 -0
  75. package/dist/dialect-helpers-B9uSp2GJ.mjs.map +1 -0
  76. package/dist/error-Cxz0tQeO.mjs +27 -0
  77. package/dist/error-Cxz0tQeO.mjs.map +1 -0
  78. package/dist/index-C1xF3OGh.d.mts +4527 -0
  79. package/dist/index-C1xF3OGh.d.mts.map +1 -0
  80. package/dist/index.d.mts +16 -0
  81. package/dist/index.mjs +30 -0
  82. package/dist/load-yOOlckBj.mjs +28 -0
  83. package/dist/load-yOOlckBj.mjs.map +1 -0
  84. package/dist/loader-fz8Q_3EO.mjs +447 -0
  85. package/dist/loader-fz8Q_3EO.mjs.map +1 -0
  86. package/dist/manifest-schema-Dcl0R6nM.mjs +184 -0
  87. package/dist/manifest-schema-Dcl0R6nM.mjs.map +1 -0
  88. package/dist/media/index.d.mts +26 -0
  89. package/dist/media/index.d.mts.map +1 -0
  90. package/dist/media/index.mjs +55 -0
  91. package/dist/media/index.mjs.map +1 -0
  92. package/dist/media/local-runtime.d.mts +39 -0
  93. package/dist/media/local-runtime.d.mts.map +1 -0
  94. package/dist/media/local-runtime.mjs +133 -0
  95. package/dist/media/local-runtime.mjs.map +1 -0
  96. package/dist/media-DqHVh136.mjs +200 -0
  97. package/dist/media-DqHVh136.mjs.map +1 -0
  98. package/dist/mode-C2EzN1uE.mjs +23 -0
  99. package/dist/mode-C2EzN1uE.mjs.map +1 -0
  100. package/dist/page/index.d.mts +140 -0
  101. package/dist/page/index.d.mts.map +1 -0
  102. package/dist/page/index.mjs +416 -0
  103. package/dist/page/index.mjs.map +1 -0
  104. package/dist/placeholder-CmGAmqeO.d.mts +276 -0
  105. package/dist/placeholder-CmGAmqeO.d.mts.map +1 -0
  106. package/dist/placeholder-SmpOx-_v.mjs +243 -0
  107. package/dist/placeholder-SmpOx-_v.mjs.map +1 -0
  108. package/dist/plugin-utils.d.mts +58 -0
  109. package/dist/plugin-utils.d.mts.map +1 -0
  110. package/dist/plugin-utils.mjs +78 -0
  111. package/dist/plugin-utils.mjs.map +1 -0
  112. package/dist/plugins/adapt-sandbox-entry.d.mts +22 -0
  113. package/dist/plugins/adapt-sandbox-entry.d.mts.map +1 -0
  114. package/dist/plugins/adapt-sandbox-entry.mjs +113 -0
  115. package/dist/plugins/adapt-sandbox-entry.mjs.map +1 -0
  116. package/dist/query-CS_iSj34.mjs +460 -0
  117. package/dist/query-CS_iSj34.mjs.map +1 -0
  118. package/dist/redirect-DIfIni3r.mjs +329 -0
  119. package/dist/redirect-DIfIni3r.mjs.map +1 -0
  120. package/dist/registry-D_w5HW4G.mjs +863 -0
  121. package/dist/registry-D_w5HW4G.mjs.map +1 -0
  122. package/dist/request-context.d.mts +49 -0
  123. package/dist/request-context.d.mts.map +1 -0
  124. package/dist/request-context.mjs +43 -0
  125. package/dist/request-context.mjs.map +1 -0
  126. package/dist/runner-B-u2F2b6.mjs +1412 -0
  127. package/dist/runner-B-u2F2b6.mjs.map +1 -0
  128. package/dist/runner-EAtf0ZIe.d.mts +27 -0
  129. package/dist/runner-EAtf0ZIe.d.mts.map +1 -0
  130. package/dist/runtime.d.mts +26 -0
  131. package/dist/runtime.d.mts.map +1 -0
  132. package/dist/runtime.mjs +42 -0
  133. package/dist/runtime.mjs.map +1 -0
  134. package/dist/search-DG603UrT.mjs +9211 -0
  135. package/dist/search-DG603UrT.mjs.map +1 -0
  136. package/dist/seed/index.d.mts +3 -0
  137. package/dist/seed/index.mjs +15 -0
  138. package/dist/seo/index.d.mts +70 -0
  139. package/dist/seo/index.d.mts.map +1 -0
  140. package/dist/seo/index.mjs +70 -0
  141. package/dist/seo/index.mjs.map +1 -0
  142. package/dist/storage/local.d.mts +39 -0
  143. package/dist/storage/local.d.mts.map +1 -0
  144. package/dist/storage/local.mjs +166 -0
  145. package/dist/storage/local.mjs.map +1 -0
  146. package/dist/storage/s3.d.mts +32 -0
  147. package/dist/storage/s3.d.mts.map +1 -0
  148. package/dist/storage/s3.mjs +175 -0
  149. package/dist/storage/s3.mjs.map +1 -0
  150. package/dist/tokens-DpgrkrXK.mjs +171 -0
  151. package/dist/tokens-DpgrkrXK.mjs.map +1 -0
  152. package/dist/transport-BFGblqwG.d.mts +42 -0
  153. package/dist/transport-BFGblqwG.d.mts.map +1 -0
  154. package/dist/transport-yxiQsi8I.mjs +418 -0
  155. package/dist/transport-yxiQsi8I.mjs.map +1 -0
  156. package/dist/types-BRuPJGdV.d.mts +102 -0
  157. package/dist/types-BRuPJGdV.d.mts.map +1 -0
  158. package/dist/types-C4-fAxN3.d.mts +182 -0
  159. package/dist/types-C4-fAxN3.d.mts.map +1 -0
  160. package/dist/types-CMMN0pNg.mjs +31 -0
  161. package/dist/types-CMMN0pNg.mjs.map +1 -0
  162. package/dist/types-CUBbjgmP.mjs +16 -0
  163. package/dist/types-CUBbjgmP.mjs.map +1 -0
  164. package/dist/types-DRjfYOEv.d.mts +426 -0
  165. package/dist/types-DRjfYOEv.d.mts.map +1 -0
  166. package/dist/types-DY5zk5HN.mjs +73 -0
  167. package/dist/types-DY5zk5HN.mjs.map +1 -0
  168. package/dist/types-DaNLHo_T.d.mts +184 -0
  169. package/dist/types-DaNLHo_T.d.mts.map +1 -0
  170. package/dist/types-DvhsUmSJ.d.mts +1111 -0
  171. package/dist/types-DvhsUmSJ.d.mts.map +1 -0
  172. package/dist/validate-CpBtVMsD.d.mts +378 -0
  173. package/dist/validate-CpBtVMsD.d.mts.map +1 -0
  174. package/dist/validate-CqRJb_xU.mjs +97 -0
  175. package/dist/validate-CqRJb_xU.mjs.map +1 -0
  176. package/dist/validate-O7PWmlnq.mjs +328 -0
  177. package/dist/validate-O7PWmlnq.mjs.map +1 -0
  178. package/locals.d.ts +46 -0
  179. package/package.json +233 -19
  180. package/src/api/authorize.ts +63 -0
  181. package/src/api/csrf.ts +48 -0
  182. package/src/api/error.ts +99 -0
  183. package/src/api/errors.ts +445 -0
  184. package/src/api/escape.ts +9 -0
  185. package/src/api/handlers/api-tokens.ts +240 -0
  186. package/src/api/handlers/comments.ts +314 -0
  187. package/src/api/handlers/content.ts +1315 -0
  188. package/src/api/handlers/dashboard.ts +205 -0
  189. package/src/api/handlers/device-flow.ts +687 -0
  190. package/src/api/handlers/index.ts +163 -0
  191. package/src/api/handlers/manifest.ts +158 -0
  192. package/src/api/handlers/marketplace.ts +930 -0
  193. package/src/api/handlers/media.ts +207 -0
  194. package/src/api/handlers/menus.ts +493 -0
  195. package/src/api/handlers/oauth-authorization.ts +429 -0
  196. package/src/api/handlers/oauth-clients.ts +353 -0
  197. package/src/api/handlers/oauth-user-lookup.ts +39 -0
  198. package/src/api/handlers/plugins.ts +254 -0
  199. package/src/api/handlers/redirects.ts +360 -0
  200. package/src/api/handlers/revision.ts +145 -0
  201. package/src/api/handlers/schema.ts +534 -0
  202. package/src/api/handlers/sections.ts +289 -0
  203. package/src/api/handlers/seo.ts +115 -0
  204. package/src/api/handlers/settings.ts +49 -0
  205. package/src/api/handlers/snapshot.ts +350 -0
  206. package/src/api/handlers/taxonomies.ts +523 -0
  207. package/src/api/index.ts +6 -0
  208. package/src/api/openapi/document.ts +2368 -0
  209. package/src/api/openapi/index.ts +1 -0
  210. package/src/api/parse.ts +139 -0
  211. package/src/api/redirect.ts +14 -0
  212. package/src/api/rev.ts +67 -0
  213. package/src/api/schemas/auth.ts +112 -0
  214. package/src/api/schemas/bylines.ts +85 -0
  215. package/src/api/schemas/comments.ts +117 -0
  216. package/src/api/schemas/common.ts +89 -0
  217. package/src/api/schemas/content.ts +191 -0
  218. package/src/api/schemas/import.ts +52 -0
  219. package/src/api/schemas/index.ts +17 -0
  220. package/src/api/schemas/media.ts +116 -0
  221. package/src/api/schemas/menus.ts +111 -0
  222. package/src/api/schemas/redirects.ts +155 -0
  223. package/src/api/schemas/schema.ts +203 -0
  224. package/src/api/schemas/search.ts +63 -0
  225. package/src/api/schemas/sections.ts +67 -0
  226. package/src/api/schemas/settings.ts +63 -0
  227. package/src/api/schemas/setup.ts +37 -0
  228. package/src/api/schemas/taxonomies.ts +113 -0
  229. package/src/api/schemas/users.ts +96 -0
  230. package/src/api/schemas/widgets.ts +80 -0
  231. package/src/api/site-url.ts +25 -0
  232. package/src/api/types.ts +82 -0
  233. package/src/astro/index.ts +27 -0
  234. package/src/astro/integration/index.ts +303 -0
  235. package/src/astro/integration/routes.ts +834 -0
  236. package/src/astro/integration/runtime.ts +338 -0
  237. package/src/astro/integration/virtual-modules.ts +469 -0
  238. package/src/astro/integration/vite-config.ts +328 -0
  239. package/src/astro/middleware/auth.ts +743 -0
  240. package/src/astro/middleware/redirect.ts +89 -0
  241. package/src/astro/middleware/request-context.ts +129 -0
  242. package/src/astro/middleware/setup.ts +89 -0
  243. package/src/astro/middleware.ts +398 -0
  244. package/src/astro/routes/PluginRegistry.tsx +15 -0
  245. package/src/astro/routes/admin.astro +81 -0
  246. package/src/astro/routes/api/admin/allowed-domains/[domain].ts +112 -0
  247. package/src/astro/routes/api/admin/allowed-domains/index.ts +108 -0
  248. package/src/astro/routes/api/admin/api-tokens/[id].ts +40 -0
  249. package/src/astro/routes/api/admin/api-tokens/index.ts +68 -0
  250. package/src/astro/routes/api/admin/bylines/[id]/index.ts +87 -0
  251. package/src/astro/routes/api/admin/bylines/index.ts +72 -0
  252. package/src/astro/routes/api/admin/comments/[id]/status.ts +120 -0
  253. package/src/astro/routes/api/admin/comments/[id].ts +64 -0
  254. package/src/astro/routes/api/admin/comments/bulk.ts +42 -0
  255. package/src/astro/routes/api/admin/comments/counts.ts +30 -0
  256. package/src/astro/routes/api/admin/comments/index.ts +46 -0
  257. package/src/astro/routes/api/admin/hooks/exclusive/[hookName].ts +91 -0
  258. package/src/astro/routes/api/admin/hooks/exclusive/index.ts +51 -0
  259. package/src/astro/routes/api/admin/oauth-clients/[id].ts +110 -0
  260. package/src/astro/routes/api/admin/oauth-clients/index.ts +71 -0
  261. package/src/astro/routes/api/admin/plugins/[id]/disable.ts +39 -0
  262. package/src/astro/routes/api/admin/plugins/[id]/enable.ts +39 -0
  263. package/src/astro/routes/api/admin/plugins/[id]/index.ts +38 -0
  264. package/src/astro/routes/api/admin/plugins/[id]/uninstall.ts +48 -0
  265. package/src/astro/routes/api/admin/plugins/[id]/update.ts +59 -0
  266. package/src/astro/routes/api/admin/plugins/index.ts +32 -0
  267. package/src/astro/routes/api/admin/plugins/marketplace/[id]/icon.ts +61 -0
  268. package/src/astro/routes/api/admin/plugins/marketplace/[id]/index.ts +33 -0
  269. package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +62 -0
  270. package/src/astro/routes/api/admin/plugins/marketplace/index.ts +38 -0
  271. package/src/astro/routes/api/admin/plugins/updates.ts +28 -0
  272. package/src/astro/routes/api/admin/themes/marketplace/[id]/index.ts +33 -0
  273. package/src/astro/routes/api/admin/themes/marketplace/[id]/thumbnail.ts +61 -0
  274. package/src/astro/routes/api/admin/themes/marketplace/index.ts +45 -0
  275. package/src/astro/routes/api/admin/users/[id]/disable.ts +69 -0
  276. package/src/astro/routes/api/admin/users/[id]/enable.ts +48 -0
  277. package/src/astro/routes/api/admin/users/[id]/index.ts +146 -0
  278. package/src/astro/routes/api/admin/users/[id]/send-recovery.ts +72 -0
  279. package/src/astro/routes/api/admin/users/index.ts +66 -0
  280. package/src/astro/routes/api/auth/dev-bypass.ts +139 -0
  281. package/src/astro/routes/api/auth/invite/accept.ts +52 -0
  282. package/src/astro/routes/api/auth/invite/complete.ts +84 -0
  283. package/src/astro/routes/api/auth/invite/index.ts +99 -0
  284. package/src/astro/routes/api/auth/logout.ts +40 -0
  285. package/src/astro/routes/api/auth/magic-link/send.ts +89 -0
  286. package/src/astro/routes/api/auth/magic-link/verify.ts +71 -0
  287. package/src/astro/routes/api/auth/me.ts +60 -0
  288. package/src/astro/routes/api/auth/oauth/[provider]/callback.ts +219 -0
  289. package/src/astro/routes/api/auth/oauth/[provider].ts +119 -0
  290. package/src/astro/routes/api/auth/passkey/[id].ts +124 -0
  291. package/src/astro/routes/api/auth/passkey/index.ts +54 -0
  292. package/src/astro/routes/api/auth/passkey/options.ts +82 -0
  293. package/src/astro/routes/api/auth/passkey/register/options.ts +86 -0
  294. package/src/astro/routes/api/auth/passkey/register/verify.ts +117 -0
  295. package/src/astro/routes/api/auth/passkey/verify.ts +66 -0
  296. package/src/astro/routes/api/auth/signup/complete.ts +85 -0
  297. package/src/astro/routes/api/auth/signup/request.ts +77 -0
  298. package/src/astro/routes/api/auth/signup/verify.ts +53 -0
  299. package/src/astro/routes/api/comments/[collection]/[contentId]/index.ts +312 -0
  300. package/src/astro/routes/api/content/[collection]/[id]/compare.ts +28 -0
  301. package/src/astro/routes/api/content/[collection]/[id]/discard-draft.ts +54 -0
  302. package/src/astro/routes/api/content/[collection]/[id]/duplicate.ts +61 -0
  303. package/src/astro/routes/api/content/[collection]/[id]/permanent.ts +33 -0
  304. package/src/astro/routes/api/content/[collection]/[id]/preview-url.ts +107 -0
  305. package/src/astro/routes/api/content/[collection]/[id]/publish.ts +56 -0
  306. package/src/astro/routes/api/content/[collection]/[id]/restore.ts +54 -0
  307. package/src/astro/routes/api/content/[collection]/[id]/revisions.ts +31 -0
  308. package/src/astro/routes/api/content/[collection]/[id]/schedule.ts +105 -0
  309. package/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts +140 -0
  310. package/src/astro/routes/api/content/[collection]/[id]/translations.ts +30 -0
  311. package/src/astro/routes/api/content/[collection]/[id]/unpublish.ts +56 -0
  312. package/src/astro/routes/api/content/[collection]/[id].ts +137 -0
  313. package/src/astro/routes/api/content/[collection]/index.ts +59 -0
  314. package/src/astro/routes/api/content/[collection]/trash.ts +33 -0
  315. package/src/astro/routes/api/dashboard.ts +32 -0
  316. package/src/astro/routes/api/dev/emails.ts +36 -0
  317. package/src/astro/routes/api/import/probe.ts +47 -0
  318. package/src/astro/routes/api/import/wordpress/analyze.ts +510 -0
  319. package/src/astro/routes/api/import/wordpress/execute.ts +283 -0
  320. package/src/astro/routes/api/import/wordpress/media.ts +338 -0
  321. package/src/astro/routes/api/import/wordpress/prepare.ts +181 -0
  322. package/src/astro/routes/api/import/wordpress/rewrite-urls.ts +393 -0
  323. package/src/astro/routes/api/import/wordpress-plugin/analyze.ts +111 -0
  324. package/src/astro/routes/api/import/wordpress-plugin/callback.ts +58 -0
  325. package/src/astro/routes/api/import/wordpress-plugin/execute.ts +347 -0
  326. package/src/astro/routes/api/manifest.ts +62 -0
  327. package/src/astro/routes/api/mcp.ts +124 -0
  328. package/src/astro/routes/api/media/[id]/confirm.ts +93 -0
  329. package/src/astro/routes/api/media/[id].ts +145 -0
  330. package/src/astro/routes/api/media/file/[key].ts +79 -0
  331. package/src/astro/routes/api/media/providers/[providerId]/[itemId].ts +86 -0
  332. package/src/astro/routes/api/media/providers/[providerId]/index.ts +111 -0
  333. package/src/astro/routes/api/media/providers/index.ts +30 -0
  334. package/src/astro/routes/api/media/upload-url.ts +137 -0
  335. package/src/astro/routes/api/media.ts +190 -0
  336. package/src/astro/routes/api/menus/[name]/items.ts +87 -0
  337. package/src/astro/routes/api/menus/[name]/reorder.ts +33 -0
  338. package/src/astro/routes/api/menus/[name].ts +65 -0
  339. package/src/astro/routes/api/menus/index.ts +47 -0
  340. package/src/astro/routes/api/oauth/authorize.ts +412 -0
  341. package/src/astro/routes/api/oauth/device/authorize.ts +45 -0
  342. package/src/astro/routes/api/oauth/device/code.ts +51 -0
  343. package/src/astro/routes/api/oauth/device/token.ts +69 -0
  344. package/src/astro/routes/api/oauth/token/refresh.ts +38 -0
  345. package/src/astro/routes/api/oauth/token/revoke.ts +38 -0
  346. package/src/astro/routes/api/oauth/token.ts +184 -0
  347. package/src/astro/routes/api/openapi.json.ts +32 -0
  348. package/src/astro/routes/api/plugins/[pluginId]/[...path].ts +92 -0
  349. package/src/astro/routes/api/redirects/404s/index.ts +72 -0
  350. package/src/astro/routes/api/redirects/404s/summary.ts +33 -0
  351. package/src/astro/routes/api/redirects/[id].ts +84 -0
  352. package/src/astro/routes/api/redirects/index.ts +52 -0
  353. package/src/astro/routes/api/revisions/[revisionId]/index.ts +29 -0
  354. package/src/astro/routes/api/revisions/[revisionId]/restore.ts +62 -0
  355. package/src/astro/routes/api/schema/collections/[slug]/fields/[fieldSlug].ts +76 -0
  356. package/src/astro/routes/api/schema/collections/[slug]/fields/index.ts +52 -0
  357. package/src/astro/routes/api/schema/collections/[slug]/fields/reorder.ts +32 -0
  358. package/src/astro/routes/api/schema/collections/[slug]/index.ts +80 -0
  359. package/src/astro/routes/api/schema/collections/index.ts +47 -0
  360. package/src/astro/routes/api/schema/index.ts +109 -0
  361. package/src/astro/routes/api/schema/orphans/[slug].ts +36 -0
  362. package/src/astro/routes/api/schema/orphans/index.ts +26 -0
  363. package/src/astro/routes/api/search/enable.ts +64 -0
  364. package/src/astro/routes/api/search/index.ts +55 -0
  365. package/src/astro/routes/api/search/rebuild.ts +72 -0
  366. package/src/astro/routes/api/search/stats.ts +35 -0
  367. package/src/astro/routes/api/search/suggest.ts +53 -0
  368. package/src/astro/routes/api/sections/[slug].ts +84 -0
  369. package/src/astro/routes/api/sections/index.ts +52 -0
  370. package/src/astro/routes/api/settings/email.ts +150 -0
  371. package/src/astro/routes/api/settings.ts +67 -0
  372. package/src/astro/routes/api/setup/admin-verify.ts +100 -0
  373. package/src/astro/routes/api/setup/admin.ts +94 -0
  374. package/src/astro/routes/api/setup/dev-bypass.ts +199 -0
  375. package/src/astro/routes/api/setup/dev-reset.ts +40 -0
  376. package/src/astro/routes/api/setup/index.ts +126 -0
  377. package/src/astro/routes/api/setup/status.ts +122 -0
  378. package/src/astro/routes/api/snapshot.ts +75 -0
  379. package/src/astro/routes/api/taxonomies/[name]/terms/[slug].ts +95 -0
  380. package/src/astro/routes/api/taxonomies/[name]/terms/index.ts +69 -0
  381. package/src/astro/routes/api/taxonomies/index.ts +59 -0
  382. package/src/astro/routes/api/themes/preview.ts +77 -0
  383. package/src/astro/routes/api/typegen.ts +114 -0
  384. package/src/astro/routes/api/well-known/auth.ts +68 -0
  385. package/src/astro/routes/api/well-known/oauth-authorization-server.ts +44 -0
  386. package/src/astro/routes/api/well-known/oauth-protected-resource.ts +37 -0
  387. package/src/astro/routes/api/widget-areas/[name]/reorder.ts +72 -0
  388. package/src/astro/routes/api/widget-areas/[name]/widgets/[id].ts +127 -0
  389. package/src/astro/routes/api/widget-areas/[name]/widgets.ts +80 -0
  390. package/src/astro/routes/api/widget-areas/[name].ts +87 -0
  391. package/src/astro/routes/api/widget-areas/index.ts +99 -0
  392. package/src/astro/routes/api/widget-components.ts +22 -0
  393. package/src/astro/routes/robots.txt.ts +77 -0
  394. package/src/astro/routes/sitemap.xml.ts +97 -0
  395. package/src/astro/storage/adapters.ts +74 -0
  396. package/src/astro/storage/index.ts +19 -0
  397. package/src/astro/storage/types.ts +60 -0
  398. package/src/astro/types.ts +346 -0
  399. package/src/auth/api-tokens.ts +25 -0
  400. package/src/auth/challenge-store.ts +80 -0
  401. package/src/auth/mode.ts +96 -0
  402. package/src/auth/oauth-state-store.ts +96 -0
  403. package/src/auth/passkey-config.ts +27 -0
  404. package/src/auth/rate-limit.ts +158 -0
  405. package/src/auth/scopes.ts +33 -0
  406. package/src/auth/types.ts +104 -0
  407. package/src/aws-sdk.d.ts +100 -0
  408. package/src/bylines/index.ts +237 -0
  409. package/src/cleanup.ts +153 -0
  410. package/src/cli/client-factory.ts +100 -0
  411. package/src/cli/commands/auth.ts +46 -0
  412. package/src/cli/commands/bundle-utils.ts +247 -0
  413. package/src/cli/commands/bundle.ts +609 -0
  414. package/src/cli/commands/content.ts +442 -0
  415. package/src/cli/commands/dev.ts +191 -0
  416. package/src/cli/commands/doctor.ts +211 -0
  417. package/src/cli/commands/export-seed.ts +630 -0
  418. package/src/cli/commands/import/wordpress.ts +1056 -0
  419. package/src/cli/commands/init.ts +192 -0
  420. package/src/cli/commands/login.ts +547 -0
  421. package/src/cli/commands/media.ts +165 -0
  422. package/src/cli/commands/menu.ts +67 -0
  423. package/src/cli/commands/plugin-init.ts +291 -0
  424. package/src/cli/commands/plugin-validate.ts +31 -0
  425. package/src/cli/commands/plugin.ts +33 -0
  426. package/src/cli/commands/publish.ts +699 -0
  427. package/src/cli/commands/schema.ts +233 -0
  428. package/src/cli/commands/search-cmd.ts +54 -0
  429. package/src/cli/commands/seed.ts +288 -0
  430. package/src/cli/commands/taxonomy.ts +128 -0
  431. package/src/cli/commands/types.ts +68 -0
  432. package/src/cli/credentials.ts +236 -0
  433. package/src/cli/index.ts +70 -0
  434. package/src/cli/output.ts +75 -0
  435. package/src/cli/wxr/parser.ts +969 -0
  436. package/src/client/cf-access.ts +193 -0
  437. package/src/client/index.ts +854 -0
  438. package/src/client/portable-text.ts +413 -0
  439. package/src/client/transport.ts +200 -0
  440. package/src/comments/moderator.ts +46 -0
  441. package/src/comments/notifications.ts +144 -0
  442. package/src/comments/query.ts +105 -0
  443. package/src/comments/service.ts +213 -0
  444. package/src/components/Break.astro +45 -0
  445. package/src/components/Button.astro +71 -0
  446. package/src/components/Buttons.astro +49 -0
  447. package/src/components/Code.astro +59 -0
  448. package/src/components/Columns.astro +59 -0
  449. package/src/components/CommentForm.astro +315 -0
  450. package/src/components/Comments.astro +232 -0
  451. package/src/components/Cover.astro +128 -0
  452. package/src/components/EmDashBodyEnd.astro +32 -0
  453. package/src/components/EmDashBodyStart.astro +32 -0
  454. package/src/components/EmDashHead.astro +53 -0
  455. package/src/components/EmDashImage.astro +178 -0
  456. package/src/components/EmDashMedia.astro +167 -0
  457. package/src/components/Embed.astro +128 -0
  458. package/src/components/File.astro +122 -0
  459. package/src/components/Gallery.astro +93 -0
  460. package/src/components/HtmlBlock.astro +33 -0
  461. package/src/components/Image.astro +178 -0
  462. package/src/components/InlineEditor.astro +27 -0
  463. package/src/components/InlinePortableTextEditor.tsx +1905 -0
  464. package/src/components/LiveSearch.astro +614 -0
  465. package/src/components/PortableText.astro +51 -0
  466. package/src/components/Pullquote.astro +51 -0
  467. package/src/components/Table.astro +108 -0
  468. package/src/components/WidgetArea.astro +22 -0
  469. package/src/components/WidgetRenderer.astro +72 -0
  470. package/src/components/index.ts +116 -0
  471. package/src/components/marks/Link.astro +31 -0
  472. package/src/components/marks/StrikeThrough.astro +7 -0
  473. package/src/components/marks/Subscript.astro +7 -0
  474. package/src/components/marks/Superscript.astro +7 -0
  475. package/src/components/marks/Underline.astro +7 -0
  476. package/src/components/widgets/Archives.astro +65 -0
  477. package/src/components/widgets/Categories.astro +35 -0
  478. package/src/components/widgets/RecentPosts.astro +51 -0
  479. package/src/components/widgets/Search.astro +18 -0
  480. package/src/components/widgets/Tags.astro +38 -0
  481. package/src/content/converters/index.ts +9 -0
  482. package/src/content/converters/portable-text-to-prosemirror.ts +385 -0
  483. package/src/content/converters/prosemirror-to-portable-text.ts +413 -0
  484. package/src/content/converters/types.ts +120 -0
  485. package/src/content/index.ts +5 -0
  486. package/src/database/connection.ts +67 -0
  487. package/src/database/dialect-helpers.ts +138 -0
  488. package/src/database/index.ts +5 -0
  489. package/src/database/migrations/001_initial.ts +136 -0
  490. package/src/database/migrations/002_media_status.ts +26 -0
  491. package/src/database/migrations/003_schema_registry.ts +79 -0
  492. package/src/database/migrations/004_plugins.ts +62 -0
  493. package/src/database/migrations/005_menus.ts +67 -0
  494. package/src/database/migrations/006_taxonomy_defs.ts +51 -0
  495. package/src/database/migrations/007_widgets.ts +42 -0
  496. package/src/database/migrations/008_auth.ts +194 -0
  497. package/src/database/migrations/009_user_disabled.ts +27 -0
  498. package/src/database/migrations/011_sections.ts +65 -0
  499. package/src/database/migrations/012_search.ts +25 -0
  500. package/src/database/migrations/013_scheduled_publishing.ts +51 -0
  501. package/src/database/migrations/014_draft_revisions.ts +72 -0
  502. package/src/database/migrations/015_indexes.ts +82 -0
  503. package/src/database/migrations/016_api_tokens.ts +89 -0
  504. package/src/database/migrations/017_authorization_codes.ts +45 -0
  505. package/src/database/migrations/018_seo.ts +56 -0
  506. package/src/database/migrations/019_i18n.ts +618 -0
  507. package/src/database/migrations/020_collection_url_pattern.ts +23 -0
  508. package/src/database/migrations/021_remove_section_categories.ts +43 -0
  509. package/src/database/migrations/022_marketplace_plugin_state.ts +46 -0
  510. package/src/database/migrations/023_plugin_metadata.ts +33 -0
  511. package/src/database/migrations/024_media_placeholders.ts +32 -0
  512. package/src/database/migrations/025_oauth_clients.ts +28 -0
  513. package/src/database/migrations/026_cron_tasks.ts +49 -0
  514. package/src/database/migrations/027_comments.ts +87 -0
  515. package/src/database/migrations/028_drop_author_url.ts +9 -0
  516. package/src/database/migrations/029_redirects.ts +67 -0
  517. package/src/database/migrations/030_widen_scheduled_index.ts +48 -0
  518. package/src/database/migrations/031_bylines.ts +90 -0
  519. package/src/database/migrations/032_rate_limits.ts +42 -0
  520. package/src/database/migrations/runner.ts +170 -0
  521. package/src/database/repositories/audit.ts +294 -0
  522. package/src/database/repositories/byline.ts +387 -0
  523. package/src/database/repositories/comment.ts +458 -0
  524. package/src/database/repositories/content.ts +1144 -0
  525. package/src/database/repositories/index.ts +30 -0
  526. package/src/database/repositories/media.ts +347 -0
  527. package/src/database/repositories/options.ts +150 -0
  528. package/src/database/repositories/plugin-storage.ts +373 -0
  529. package/src/database/repositories/redirect.ts +480 -0
  530. package/src/database/repositories/revision.ts +200 -0
  531. package/src/database/repositories/seo.ts +176 -0
  532. package/src/database/repositories/taxonomy.ts +294 -0
  533. package/src/database/repositories/types.ts +132 -0
  534. package/src/database/repositories/user.ts +258 -0
  535. package/src/database/transaction.ts +54 -0
  536. package/src/database/types.ts +501 -0
  537. package/src/database/validate.ts +138 -0
  538. package/src/db/adapters.ts +125 -0
  539. package/src/db/index.ts +37 -0
  540. package/src/db/libsql.ts +23 -0
  541. package/src/db/postgres.ts +30 -0
  542. package/src/db/sqlite.ts +27 -0
  543. package/src/emdash-runtime.ts +2096 -0
  544. package/src/fields/boolean.ts +34 -0
  545. package/src/fields/datetime.ts +44 -0
  546. package/src/fields/file.ts +41 -0
  547. package/src/fields/image.ts +34 -0
  548. package/src/fields/index.ts +42 -0
  549. package/src/fields/integer.ts +50 -0
  550. package/src/fields/json.ts +37 -0
  551. package/src/fields/multiselect.ts +48 -0
  552. package/src/fields/number.ts +52 -0
  553. package/src/fields/portable-text.ts +33 -0
  554. package/src/fields/reference.ts +29 -0
  555. package/src/fields/richtext.ts +31 -0
  556. package/src/fields/select.ts +46 -0
  557. package/src/fields/slug.ts +38 -0
  558. package/src/fields/text.ts +55 -0
  559. package/src/fields/textarea.ts +52 -0
  560. package/src/fields/types.ts +64 -0
  561. package/src/i18n/config.ts +68 -0
  562. package/src/import/index.ts +90 -0
  563. package/src/import/menus.ts +436 -0
  564. package/src/import/registry.ts +111 -0
  565. package/src/import/sections.ts +103 -0
  566. package/src/import/settings.ts +281 -0
  567. package/src/import/sources/wordpress-plugin.ts +641 -0
  568. package/src/import/sources/wordpress-rest.ts +191 -0
  569. package/src/import/sources/wxr.ts +330 -0
  570. package/src/import/ssrf.ts +260 -0
  571. package/src/import/types.ts +418 -0
  572. package/src/import/utils.ts +412 -0
  573. package/src/index.ts +481 -0
  574. package/src/loader.ts +770 -0
  575. package/src/mcp/server.ts +1463 -0
  576. package/src/media/index.ts +32 -0
  577. package/src/media/local-runtime.ts +213 -0
  578. package/src/media/local.ts +46 -0
  579. package/src/media/normalize.ts +190 -0
  580. package/src/media/placeholder.ts +150 -0
  581. package/src/media/provider-loader.ts +78 -0
  582. package/src/media/types.ts +279 -0
  583. package/src/menus/index.ts +324 -0
  584. package/src/menus/types.ts +112 -0
  585. package/src/page/context.ts +93 -0
  586. package/src/page/fragments.ts +89 -0
  587. package/src/page/index.ts +58 -0
  588. package/src/page/jsonld.ts +94 -0
  589. package/src/page/metadata.ts +185 -0
  590. package/src/page/seo-contributions.ts +136 -0
  591. package/src/plugin-utils.ts +80 -0
  592. package/src/plugins/adapt-sandbox-entry.ts +207 -0
  593. package/src/plugins/context.ts +833 -0
  594. package/src/plugins/cron.ts +361 -0
  595. package/src/plugins/define-plugin.ts +259 -0
  596. package/src/plugins/email-console.ts +73 -0
  597. package/src/plugins/email.ts +209 -0
  598. package/src/plugins/hooks.ts +1273 -0
  599. package/src/plugins/index.ts +193 -0
  600. package/src/plugins/manager.ts +595 -0
  601. package/src/plugins/manifest-schema.ts +230 -0
  602. package/src/plugins/marketplace.ts +460 -0
  603. package/src/plugins/request-meta.ts +139 -0
  604. package/src/plugins/routes.ts +302 -0
  605. package/src/plugins/sandbox/index.ts +18 -0
  606. package/src/plugins/sandbox/noop.ts +76 -0
  607. package/src/plugins/sandbox/types.ts +173 -0
  608. package/src/plugins/scheduler/node.ts +122 -0
  609. package/src/plugins/scheduler/piggyback.ts +71 -0
  610. package/src/plugins/scheduler/types.ts +27 -0
  611. package/src/plugins/state.ts +208 -0
  612. package/src/plugins/storage-indexes.ts +326 -0
  613. package/src/plugins/storage-query.ts +240 -0
  614. package/src/plugins/types.ts +1284 -0
  615. package/src/preview/helpers.ts +27 -0
  616. package/src/preview/index.ts +40 -0
  617. package/src/preview/tokens.ts +279 -0
  618. package/src/preview/urls.ts +118 -0
  619. package/src/query.ts +674 -0
  620. package/src/redirects/patterns.ts +224 -0
  621. package/src/request-context.ts +67 -0
  622. package/src/runtime.ts +21 -0
  623. package/src/schema/index.ts +29 -0
  624. package/src/schema/query.ts +44 -0
  625. package/src/schema/registry.ts +965 -0
  626. package/src/schema/types.ts +276 -0
  627. package/src/schema/zod-generator.ts +413 -0
  628. package/src/search/fts-manager.ts +452 -0
  629. package/src/search/index.ts +26 -0
  630. package/src/search/query.ts +396 -0
  631. package/src/search/text-extraction.ts +162 -0
  632. package/src/search/types.ts +114 -0
  633. package/src/sections/index.ts +226 -0
  634. package/src/sections/types.ts +86 -0
  635. package/src/seed/apply.ts +1141 -0
  636. package/src/seed/default.ts +86 -0
  637. package/src/seed/index.ts +28 -0
  638. package/src/seed/load.ts +35 -0
  639. package/src/seed/types.ts +341 -0
  640. package/src/seed/validate.ts +642 -0
  641. package/src/seo/index.ts +179 -0
  642. package/src/settings/index.ts +203 -0
  643. package/src/settings/types.ts +58 -0
  644. package/src/storage/index.ts +28 -0
  645. package/src/storage/local.ts +253 -0
  646. package/src/storage/s3.ts +271 -0
  647. package/src/storage/types.ts +204 -0
  648. package/src/taxonomies/index.ts +309 -0
  649. package/src/taxonomies/types.ts +61 -0
  650. package/src/ui.ts +75 -0
  651. package/src/utils/base64.ts +73 -0
  652. package/src/utils/hash.ts +36 -0
  653. package/src/utils/sanitize.ts +20 -0
  654. package/src/utils/slugify.ts +29 -0
  655. package/src/utils/url.ts +48 -0
  656. package/src/virtual-modules.d.ts +111 -0
  657. package/src/visual-editing/editable.ts +108 -0
  658. package/src/visual-editing/toolbar.ts +1229 -0
  659. package/src/widgets/components.ts +105 -0
  660. package/src/widgets/index.ts +131 -0
  661. package/src/widgets/types.ts +81 -0
@@ -0,0 +1,312 @@
1
+ /**
2
+ * Public comment endpoints
3
+ *
4
+ * GET /_emdash/api/comments/:collection/:contentId - List approved comments
5
+ * POST /_emdash/api/comments/:collection/:contentId - Submit a comment
6
+ */
7
+
8
+ import type { APIRoute } from "astro";
9
+
10
+ import { apiError, apiSuccess, handleError, requireDb, unwrapResult } from "#api/error.js";
11
+ import { handleCommentList, checkRateLimit, hashIp } from "#api/handlers/comments.js";
12
+ import { isParseError, parseBody } from "#api/parse.js";
13
+ import { createCommentBody } from "#api/schemas.js";
14
+ import { getSiteBaseUrl } from "#api/site-url.js";
15
+ import { sendCommentNotification } from "#comments/notifications.js";
16
+ import { createComment, type CommentHookRunner } from "#comments/service.js";
17
+ import { CommentRepository } from "#db/repositories/comment.js";
18
+ import { extractRequestMeta } from "#plugins/request-meta.js";
19
+ import type { CollectionCommentSettings, ModerationDecision } from "#plugins/types.js";
20
+
21
+ export const prerender = false;
22
+
23
+ /**
24
+ * List approved comments for a content item (public, no auth required)
25
+ */
26
+ export const GET: APIRoute = async ({ params, url, locals }) => {
27
+ const { emdash } = locals;
28
+ const { collection, contentId } = params;
29
+
30
+ if (!collection || !contentId) {
31
+ return apiError("VALIDATION_ERROR", "Collection and content ID required", 400);
32
+ }
33
+
34
+ const dbErr = requireDb(emdash?.db);
35
+ if (dbErr) return dbErr;
36
+
37
+ try {
38
+ const limit = Math.min(Number(url.searchParams.get("limit") || 50), 100);
39
+ const cursor = url.searchParams.get("cursor") ?? undefined;
40
+ const threaded = url.searchParams.get("threaded") === "true";
41
+
42
+ // Check collection exists and has comments enabled
43
+ const collectionRow = await emdash.db
44
+ .selectFrom("_emdash_collections")
45
+ .select(["comments_enabled"])
46
+ .where("slug", "=", collection)
47
+ .executeTakeFirst();
48
+
49
+ if (!collectionRow) {
50
+ return apiError("NOT_FOUND", `Collection '${collection}' not found`, 404);
51
+ }
52
+
53
+ if (!collectionRow.comments_enabled) {
54
+ return apiError("COMMENTS_DISABLED", "Comments are not enabled for this collection", 403);
55
+ }
56
+
57
+ const result = await handleCommentList(emdash.db, collection, contentId, {
58
+ limit,
59
+ cursor,
60
+ threaded,
61
+ });
62
+
63
+ return unwrapResult(result);
64
+ } catch (error) {
65
+ return handleError(error, "Failed to list comments", "COMMENT_LIST_ERROR");
66
+ }
67
+ };
68
+
69
+ /**
70
+ * Submit a comment (public, gated by anti-spam checks)
71
+ */
72
+ export const POST: APIRoute = async ({ params, request, locals }) => {
73
+ const { emdash, user } = locals;
74
+ const { collection, contentId } = params;
75
+
76
+ if (!collection || !contentId) {
77
+ return apiError("VALIDATION_ERROR", "Collection and content ID required", 400);
78
+ }
79
+
80
+ const dbErr = requireDb(emdash?.db);
81
+ if (dbErr) return dbErr;
82
+
83
+ try {
84
+ // Parse and validate input
85
+ const body = await parseBody(request, createCommentBody);
86
+ if (isParseError(body)) return body;
87
+
88
+ // Check collection exists and has comments enabled
89
+ const collectionRow = await emdash.db
90
+ .selectFrom("_emdash_collections")
91
+ .select([
92
+ "comments_enabled",
93
+ "comments_moderation",
94
+ "comments_closed_after_days",
95
+ "comments_auto_approve_users",
96
+ ])
97
+ .where("slug", "=", collection)
98
+ .executeTakeFirst();
99
+
100
+ if (!collectionRow) {
101
+ return apiError("NOT_FOUND", `Collection '${collection}' not found`, 404);
102
+ }
103
+
104
+ if (!collectionRow.comments_enabled) {
105
+ return apiError("COMMENTS_DISABLED", "Comments are not enabled for this collection", 403);
106
+ }
107
+
108
+ // Verify the content item exists, is published, and not soft-deleted
109
+ const contentRow = await emdash.db
110
+ .selectFrom(`ec_${collection}` as never)
111
+ .select(["id" as never, "slug" as never, "author_id" as never, "published_at" as never])
112
+ .where("id" as never, "=", contentId as never)
113
+ .where("status" as never, "=", "published" as never)
114
+ .where("deleted_at" as never, "is", null as never)
115
+ .executeTakeFirst();
116
+
117
+ if (!contentRow) {
118
+ return apiError("NOT_FOUND", "Content not found", 404);
119
+ }
120
+
121
+ // Check if comments are closed (published_at + closed_after_days)
122
+ if (collectionRow.comments_closed_after_days > 0) {
123
+ const publishedAt = (contentRow as { published_at: string | null }).published_at;
124
+ if (publishedAt) {
125
+ const closedDate = new Date(publishedAt);
126
+ closedDate.setDate(closedDate.getDate() + collectionRow.comments_closed_after_days);
127
+ if (new Date() > closedDate) {
128
+ return apiError("COMMENTS_CLOSED", "Comments are closed for this content", 403);
129
+ }
130
+ }
131
+ }
132
+
133
+ // Anti-spam: Honeypot — hidden field filled only by bots
134
+ if (body.website_url) {
135
+ // Silently accept — don't reveal the honeypot to bots
136
+ return apiSuccess({ status: "pending", message: "Comment submitted for review" });
137
+ }
138
+
139
+ // Anti-spam: Rate limiting
140
+ const meta = extractRequestMeta(request);
141
+ const ipSalt =
142
+ import.meta.env.EMDASH_AUTH_SECRET || import.meta.env.AUTH_SECRET || "emdash-ip-salt";
143
+ let ipHash: string;
144
+ if (meta.ip) {
145
+ ipHash = await hashIp(meta.ip, ipSalt);
146
+ } else if (meta.userAgent) {
147
+ // Fallback: hash user-agent as a rough identifier when IP is unavailable
148
+ ipHash = await hashIp(`ua:${meta.userAgent}`, ipSalt);
149
+ } else {
150
+ // Fail closed: all unidentifiable requests share one rate-limit bucket.
151
+ // Use a larger limit since this bucket is shared across all anonymous users.
152
+ ipHash = "unknown";
153
+ }
154
+ const unknownBucketLimit = ipHash === "unknown" ? 20 : undefined;
155
+ const rateLimited = await checkRateLimit(emdash.db, ipHash, unknownBucketLimit);
156
+ if (rateLimited) {
157
+ return apiError("RATE_LIMITED", "Too many comments. Please try again later.", 429);
158
+ }
159
+
160
+ // Build collection settings
161
+ const collectionSettings: CollectionCommentSettings = {
162
+ commentsEnabled: collectionRow.comments_enabled === 1,
163
+ commentsModeration:
164
+ collectionRow.comments_moderation as CollectionCommentSettings["commentsModeration"],
165
+ commentsClosedAfterDays: collectionRow.comments_closed_after_days,
166
+ commentsAutoApproveUsers: collectionRow.comments_auto_approve_users === 1,
167
+ };
168
+
169
+ // Determine author fields — authenticated user overrides form input
170
+ let authorName = body.authorName;
171
+ let authorEmail = body.authorEmail;
172
+ let authorUserId: string | null = null;
173
+
174
+ if (user) {
175
+ authorName = user.name || authorName;
176
+ authorEmail = user.email;
177
+ authorUserId = user.id;
178
+ }
179
+
180
+ // Validate parent exists and belongs to the same content.
181
+ // Enforce 1-level nesting: if the parent is itself a reply, attach to its root.
182
+ let resolvedParentId = body.parentId ?? null;
183
+ if (body.parentId) {
184
+ const repo = new CommentRepository(emdash.db);
185
+ const parent = await repo.findById(body.parentId);
186
+ if (!parent) {
187
+ return apiError("VALIDATION_ERROR", "Parent comment not found", 400);
188
+ }
189
+ if (parent.collection !== collection || parent.contentId !== contentId) {
190
+ return apiError("VALIDATION_ERROR", "Parent comment belongs to different content", 400);
191
+ }
192
+ // Flatten: if parent is a reply, use its parent (the root) instead
193
+ resolvedParentId = parent.parentId ?? parent.id;
194
+ }
195
+
196
+ // Wire the comment service to the real hook pipeline
197
+ const hookRunner: CommentHookRunner = {
198
+ async runBeforeCreate(event) {
199
+ return emdash.hooks.runCommentBeforeCreate(event);
200
+ },
201
+ async runModerate(event) {
202
+ const result = await emdash.hooks.invokeExclusiveHook("comment:moderate", event);
203
+ if (!result) return { status: "pending" as const, reason: "No moderator configured" };
204
+ if (result.error) {
205
+ console.error(`[comments] Moderation error (${result.pluginId}):`, result.error.message);
206
+ return { status: "pending" as const, reason: "Moderation error" };
207
+ }
208
+ return result.result as ModerationDecision;
209
+ },
210
+ fireAfterCreate(event) {
211
+ emdash.hooks
212
+ .runCommentAfterCreate(event)
213
+ .catch((err) =>
214
+ console.error(
215
+ "[comments] afterCreate error:",
216
+ err instanceof Error ? err.message : err,
217
+ ),
218
+ );
219
+ },
220
+ fireAfterModerate(event) {
221
+ emdash.hooks
222
+ .runCommentAfterModerate(event)
223
+ .catch((err) =>
224
+ console.error(
225
+ "[comments] afterModerate error:",
226
+ err instanceof Error ? err.message : err,
227
+ ),
228
+ );
229
+ },
230
+ };
231
+
232
+ // Build content info for afterCreate hooks (e.g. email notifications)
233
+ const typedContent = contentRow as {
234
+ id: string;
235
+ slug: string;
236
+ author_id: string | null;
237
+ };
238
+ let contentAuthor: { id: string; name: string | null; email: string } | undefined;
239
+ if (typedContent.author_id) {
240
+ const authorRow = await emdash.db
241
+ .selectFrom("users")
242
+ .select(["id", "name", "email", "email_verified"])
243
+ .where("id", "=", typedContent.author_id)
244
+ .executeTakeFirst();
245
+ if (authorRow && authorRow.email_verified) {
246
+ contentAuthor = {
247
+ id: authorRow.id,
248
+ name: authorRow.name,
249
+ email: authorRow.email,
250
+ };
251
+ }
252
+ }
253
+
254
+ const result = await createComment(
255
+ emdash.db,
256
+ {
257
+ collection,
258
+ contentId,
259
+ parentId: resolvedParentId,
260
+ authorName,
261
+ authorEmail,
262
+ authorUserId,
263
+ body: body.body,
264
+ ipHash,
265
+ userAgent: meta.userAgent,
266
+ },
267
+ collectionSettings,
268
+ hookRunner,
269
+ {
270
+ id: typedContent.id,
271
+ collection,
272
+ slug: typedContent.slug,
273
+ author: contentAuthor,
274
+ },
275
+ );
276
+
277
+ if (!result) {
278
+ return apiError("COMMENT_REJECTED", "Comment was rejected", 403);
279
+ }
280
+
281
+ // Send notification to content author (awaited so it completes before
282
+ // the response is sent — required for Cloudflare Workers where the
283
+ // isolate terminates after the response).
284
+ if (result.comment.status === "approved" && emdash.email && contentAuthor) {
285
+ try {
286
+ const adminBaseUrl = await getSiteBaseUrl(emdash.db, request);
287
+ await sendCommentNotification({
288
+ email: emdash.email,
289
+ comment: result.comment,
290
+ contentAuthor,
291
+ adminBaseUrl,
292
+ });
293
+ } catch (err) {
294
+ console.error("[comments] notification error:", err instanceof Error ? err.message : err);
295
+ }
296
+ }
297
+
298
+ return apiSuccess(
299
+ {
300
+ id: result.comment.id,
301
+ status: result.comment.status,
302
+ message:
303
+ result.comment.status === "approved"
304
+ ? "Comment published"
305
+ : "Comment submitted for review",
306
+ },
307
+ 201,
308
+ );
309
+ } catch (error) {
310
+ return handleError(error, "Failed to submit comment", "COMMENT_CREATE_ERROR");
311
+ }
312
+ };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Compare live and draft revisions
3
+ *
4
+ * GET /_emdash/api/content/{collection}/{id}/compare
5
+ */
6
+
7
+ import type { APIRoute } from "astro";
8
+
9
+ import { requirePerm } from "#api/authorize.js";
10
+ import { apiError, unwrapResult } from "#api/error.js";
11
+
12
+ export const prerender = false;
13
+
14
+ export const GET: APIRoute = async ({ params, locals }) => {
15
+ const { emdash, user } = locals;
16
+ const denied = requirePerm(user, "content:read");
17
+ if (denied) return denied;
18
+ const collection = params.collection!;
19
+ const id = params.id!;
20
+
21
+ if (!emdash?.handleContentCompare) {
22
+ return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
23
+ }
24
+
25
+ const result = await emdash.handleContentCompare(collection, id);
26
+
27
+ return unwrapResult(result);
28
+ };
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Discard draft changes - reverts to live version
3
+ *
4
+ * POST /_emdash/api/content/{collection}/{id}/discard-draft
5
+ */
6
+
7
+ import type { APIRoute } from "astro";
8
+
9
+ import { requireOwnerPerm } from "#api/authorize.js";
10
+ import { apiError, mapErrorStatus, unwrapResult } from "#api/error.js";
11
+
12
+ export const prerender = false;
13
+
14
+ export const POST: APIRoute = async ({ params, locals, cache }) => {
15
+ const { emdash, user } = locals;
16
+ const collection = params.collection!;
17
+ const id = params.id!;
18
+
19
+ if (!emdash?.handleContentDiscardDraft || !emdash?.handleContentGet) {
20
+ return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
21
+ }
22
+
23
+ // Fetch item to check ownership
24
+ const existing = await emdash.handleContentGet(collection, id);
25
+ if (!existing.success) {
26
+ return apiError(
27
+ existing.error?.code ?? "UNKNOWN_ERROR",
28
+ existing.error?.message ?? "Unknown error",
29
+ mapErrorStatus(existing.error?.code),
30
+ );
31
+ }
32
+ const existingData =
33
+ existing.data && typeof existing.data === "object"
34
+ ? // eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- handler returns unknown data; narrowed by typeof check above
35
+ (existing.data as Record<string, unknown>)
36
+ : undefined;
37
+ // Handler returns { item, _rev } — extract the item for ownership check
38
+ const existingItem =
39
+ existingData?.item && typeof existingData.item === "object"
40
+ ? // eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- narrowed by typeof check above
41
+ (existingData.item as Record<string, unknown>)
42
+ : existingData;
43
+ const authorId = typeof existingItem?.authorId === "string" ? existingItem.authorId : "";
44
+ const denied = requireOwnerPerm(user, authorId, "content:edit_own", "content:edit_any");
45
+ if (denied) return denied;
46
+
47
+ const result = await emdash.handleContentDiscardDraft(collection, id);
48
+
49
+ if (!result.success) return unwrapResult(result);
50
+
51
+ if (cache.enabled) await cache.invalidate({ tags: [collection, id] });
52
+
53
+ return unwrapResult(result);
54
+ };
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Duplicate content endpoint - injected by EmDash integration
3
+ *
4
+ * POST /_emdash/api/content/{collection}/{id}/duplicate - Create a copy
5
+ */
6
+
7
+ import type { APIRoute } from "astro";
8
+
9
+ import { requirePerm, requireOwnerPerm } from "#api/authorize.js";
10
+ import { apiError, mapErrorStatus, unwrapResult } from "#api/error.js";
11
+
12
+ export const prerender = false;
13
+
14
+ export const POST: APIRoute = async ({ params, locals, cache }) => {
15
+ const { emdash, user } = locals;
16
+ const collection = params.collection!;
17
+ const id = params.id!;
18
+
19
+ const denied = requirePerm(user, "content:create");
20
+ if (denied) return denied;
21
+
22
+ if (!emdash?.handleContentDuplicate || !emdash?.handleContentGet) {
23
+ return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
24
+ }
25
+
26
+ // Fetch item to check ownership — duplicating requires read access to the source
27
+ const existing = await emdash.handleContentGet(collection, id);
28
+ if (!existing.success) {
29
+ return apiError(
30
+ existing.error?.code ?? "UNKNOWN_ERROR",
31
+ existing.error?.message ?? "Unknown error",
32
+ mapErrorStatus(existing.error?.code),
33
+ );
34
+ }
35
+
36
+ const existingData =
37
+ existing.data && typeof existing.data === "object"
38
+ ? // eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- handler returns unknown data; narrowed by typeof check above
39
+ (existing.data as Record<string, unknown>)
40
+ : undefined;
41
+ // Handler returns { item, _rev } — extract the item for ownership check
42
+ const existingItem =
43
+ existingData?.item && typeof existingData.item === "object"
44
+ ? // eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- narrowed by typeof check above
45
+ (existingData.item as Record<string, unknown>)
46
+ : existingData;
47
+ const authorId = typeof existingItem?.authorId === "string" ? existingItem.authorId : "";
48
+ // Duplicating requires read access to the source — check ownership-based edit permissions
49
+ // since content:read is flat (no own/any split). This ensures authors can only duplicate their own.
50
+ const readDenied = requireOwnerPerm(user, authorId, "content:edit_own", "content:edit_any");
51
+ if (readDenied) return readDenied;
52
+
53
+ const resolvedId = typeof existingItem?.id === "string" ? existingItem.id : id;
54
+ const result = await emdash.handleContentDuplicate(collection, resolvedId, user?.id);
55
+
56
+ if (!result.success) return unwrapResult(result);
57
+
58
+ if (cache.enabled) await cache.invalidate({ tags: [collection] });
59
+
60
+ return unwrapResult(result, 201);
61
+ };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Permanent delete content endpoint - injected by EmDash integration
3
+ *
4
+ * DELETE /_emdash/api/content/{collection}/{id}/permanent - Permanently delete (no undo)
5
+ */
6
+
7
+ import type { APIRoute } from "astro";
8
+
9
+ import { requirePerm } from "#api/authorize.js";
10
+ import { apiError, unwrapResult } from "#api/error.js";
11
+
12
+ export const prerender = false;
13
+
14
+ export const DELETE: APIRoute = async ({ params, locals, cache }) => {
15
+ const { emdash, user } = locals;
16
+ const collection = params.collection!;
17
+ const id = params.id!;
18
+
19
+ const denied = requirePerm(user, "import:execute");
20
+ if (denied) return denied;
21
+
22
+ if (!emdash?.handleContentPermanentDelete) {
23
+ return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
24
+ }
25
+
26
+ const result = await emdash.handleContentPermanentDelete(collection, id);
27
+
28
+ if (!result.success) return unwrapResult(result);
29
+
30
+ if (cache.enabled) await cache.invalidate({ tags: [collection, id] });
31
+
32
+ return unwrapResult(result);
33
+ };
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Preview URL endpoint - generates a signed preview URL for content
3
+ *
4
+ * POST /_emdash/api/content/{collection}/{id}/preview-url
5
+ *
6
+ * Request body:
7
+ * {
8
+ * expiresIn?: string | number; // Default: "1h"
9
+ * pathPattern?: string; // Default: "/{collection}/{id}"
10
+ * }
11
+ *
12
+ * Response:
13
+ * {
14
+ * url: string; // The preview URL with token
15
+ * expiresAt: number; // Unix timestamp when token expires
16
+ * }
17
+ */
18
+
19
+ import type { APIRoute } from "astro";
20
+
21
+ import { requirePerm } from "#api/authorize.js";
22
+ import { apiError, apiSuccess, handleError, unwrapResult } from "#api/error.js";
23
+ import { parseOptionalBody, isParseError } from "#api/parse.js";
24
+ import { contentPreviewUrlBody } from "#api/schemas.js";
25
+ import { getPreviewUrl } from "#preview/index.js";
26
+
27
+ export const prerender = false;
28
+
29
+ const DURATION_PATTERN = /^(\d+)([smhdw])$/;
30
+
31
+ export const POST: APIRoute = async ({ params, request, locals }) => {
32
+ const { emdash, user } = locals;
33
+ const denied = requirePerm(user, "content:read");
34
+ if (denied) return denied;
35
+ const collection = params.collection!;
36
+ const id = params.id!;
37
+
38
+ // Get the preview secret from environment
39
+ const previewSecret = import.meta.env.EMDASH_PREVIEW_SECRET || import.meta.env.PREVIEW_SECRET;
40
+
41
+ if (!previewSecret) {
42
+ return apiError(
43
+ "NOT_CONFIGURED",
44
+ "Preview not configured. Set EMDASH_PREVIEW_SECRET environment variable.",
45
+ 500,
46
+ );
47
+ }
48
+
49
+ // Verify the content exists (optional, but good for UX)
50
+ if (emdash?.handleContentGet) {
51
+ const result = await emdash.handleContentGet(collection, id);
52
+ if (!result.success) return unwrapResult(result);
53
+ }
54
+
55
+ // Parse request body
56
+ const body = await parseOptionalBody(request, contentPreviewUrlBody, {});
57
+ if (isParseError(body)) return body;
58
+
59
+ const expiresIn = body.expiresIn || "1h";
60
+ const pathPattern = body.pathPattern;
61
+
62
+ // Calculate expiry timestamp
63
+ const expiresInSeconds = typeof expiresIn === "number" ? expiresIn : parseExpiresIn(expiresIn);
64
+ const expiresAt = Math.floor(Date.now() / 1000) + expiresInSeconds;
65
+
66
+ try {
67
+ const url = await getPreviewUrl({
68
+ collection,
69
+ id,
70
+ secret: previewSecret,
71
+ expiresIn,
72
+ pathPattern,
73
+ });
74
+
75
+ return apiSuccess({ url, expiresAt });
76
+ } catch (error) {
77
+ return handleError(error, "Failed to generate preview URL", "TOKEN_ERROR");
78
+ }
79
+ };
80
+
81
+ /**
82
+ * Parse duration string to seconds
83
+ */
84
+ function parseExpiresIn(duration: string): number {
85
+ const match = duration.match(DURATION_PATTERN);
86
+ if (!match) {
87
+ return 3600; // Default 1 hour
88
+ }
89
+
90
+ const value = parseInt(match[1], 10);
91
+ const unit = match[2];
92
+
93
+ switch (unit) {
94
+ case "s":
95
+ return value;
96
+ case "m":
97
+ return value * 60;
98
+ case "h":
99
+ return value * 60 * 60;
100
+ case "d":
101
+ return value * 60 * 60 * 24;
102
+ case "w":
103
+ return value * 60 * 60 * 24 * 7;
104
+ default:
105
+ return 3600;
106
+ }
107
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Publish content - promotes draft to live
3
+ *
4
+ * POST /_emdash/api/content/{collection}/{id}/publish
5
+ */
6
+
7
+ import type { APIRoute } from "astro";
8
+
9
+ import { requireOwnerPerm } from "#api/authorize.js";
10
+ import { apiError, mapErrorStatus, unwrapResult } from "#api/error.js";
11
+
12
+ export const prerender = false;
13
+
14
+ export const POST: APIRoute = async ({ params, locals, cache }) => {
15
+ const { emdash, user } = locals;
16
+ const collection = params.collection!;
17
+ const id = params.id!;
18
+
19
+ if (!emdash?.handleContentPublish || !emdash?.handleContentGet) {
20
+ return apiError("NOT_CONFIGURED", "EmDash is not initialized", 500);
21
+ }
22
+
23
+ // Fetch item to check ownership
24
+ const existing = await emdash.handleContentGet(collection, id);
25
+ if (!existing.success) {
26
+ return apiError(
27
+ existing.error?.code ?? "UNKNOWN_ERROR",
28
+ existing.error?.message ?? "Unknown error",
29
+ mapErrorStatus(existing.error?.code),
30
+ );
31
+ }
32
+
33
+ const existingData =
34
+ existing.data && typeof existing.data === "object"
35
+ ? // eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- handler returns unknown data; narrowed by typeof check above
36
+ (existing.data as Record<string, unknown>)
37
+ : undefined;
38
+ const existingItem =
39
+ existingData?.item && typeof existingData.item === "object"
40
+ ? // eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- narrowed by typeof check above
41
+ (existingData.item as Record<string, unknown>)
42
+ : existingData;
43
+ const authorId = typeof existingItem?.authorId === "string" ? existingItem.authorId : "";
44
+ const denied = requireOwnerPerm(user, authorId, "content:publish_own", "content:publish_any");
45
+ if (denied) return denied;
46
+
47
+ const resolvedId = typeof existingItem?.id === "string" ? existingItem.id : id;
48
+
49
+ const result = await emdash.handleContentPublish(collection, resolvedId);
50
+
51
+ if (!result.success) return unwrapResult(result);
52
+
53
+ if (cache.enabled) await cache.invalidate({ tags: [collection, resolvedId] });
54
+
55
+ return unwrapResult(result);
56
+ };