@velora-cms/server 0.11.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 (367) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +65 -0
  3. package/dist/admin-bundle/assets/__virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare__react__loadShare__.js_commonjs-proxy-Ng0fOpdU.js +1 -0
  4. package/dist/admin-bundle/assets/__virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare__react_mf_2_dom__loadShare__.js_commonjs-proxy-oc1i_iBU.js +9 -0
  5. package/dist/admin-bundle/assets/_virtual_mf-localSharedImportMap___mfe_internal__velora_admin__mf_owner__1-DOKdWYyj.js +1 -0
  6. package/dist/admin-bundle/assets/_virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare___mf_0_velora_mf_2_cms_mf_1_plugin_mf_2_sdk__loadShare__.js-C1-ZAxwO.js +2 -0
  7. package/dist/admin-bundle/assets/_virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare__react__loadShare__.js-BuUBskgj.js +9 -0
  8. package/dist/admin-bundle/assets/_virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.js-DMaku-HC.js +9 -0
  9. package/dist/admin-bundle/assets/_virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare__react_mf_2_dom__loadShare__.js-DYfqn12F.js +1 -0
  10. package/dist/admin-bundle/assets/_virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare__react_mf_2_dom_mf_1_client__loadShare__.js-BPoj3DC9.js +25 -0
  11. package/dist/admin-bundle/assets/content-trash.schema-DfXHkC-w.js +72 -0
  12. package/dist/admin-bundle/assets/hostInit--C4xK6nj.js +1 -0
  13. package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
  14. package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
  15. package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
  16. package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
  17. package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
  18. package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
  19. package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
  20. package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
  21. package/dist/admin-bundle/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  22. package/dist/admin-bundle/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  23. package/dist/admin-bundle/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  24. package/dist/admin-bundle/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  25. package/dist/admin-bundle/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
  26. package/dist/admin-bundle/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
  27. package/dist/admin-bundle/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
  28. package/dist/admin-bundle/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
  29. package/dist/admin-bundle/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
  30. package/dist/admin-bundle/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
  31. package/dist/admin-bundle/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
  32. package/dist/admin-bundle/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
  33. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-400-normal-CyU-ddYS.woff2 +0 -0
  34. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-400-normal-EA1o-u_Q.woff +0 -0
  35. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-500-normal-C4MQITzh.woff2 +0 -0
  36. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-500-normal-DPXcppbm.woff +0 -0
  37. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-600-normal-0pRdybE_.woff2 +0 -0
  38. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-600-normal-MJZ0liF5.woff +0 -0
  39. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-400-normal-Bo5KPYvw.woff2 +0 -0
  40. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-400-normal-CFZh1unN.woff +0 -0
  41. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-500-normal-BletK1Hp.woff +0 -0
  42. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-500-normal-Cd6jVIg7.woff2 +0 -0
  43. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-600-normal-Dk5eISD_.woff +0 -0
  44. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-600-normal-KrqB56Mw.woff2 +0 -0
  45. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-400-normal-BeVLg08x.woff +0 -0
  46. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-400-normal-BpLfq-63.woff2 +0 -0
  47. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-500-normal-Ck_W4G78.woff2 +0 -0
  48. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-500-normal-eJxPnL1P.woff +0 -0
  49. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-600-normal-DT29Q8HW.woff +0 -0
  50. package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-600-normal-DjDAeNQD.woff2 +0 -0
  51. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-400-normal-BTotfTJu.woff +0 -0
  52. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-400-normal-DZqxrq2p.woff2 +0 -0
  53. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-500-normal-ByOcLdNv.woff +0 -0
  54. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-500-normal-CocWQlwt.woff2 +0 -0
  55. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-600-normal-71GNu3SW.woff2 +0 -0
  56. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-600-normal-BGq0mW3O.woff +0 -0
  57. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-400-normal-Dsrv2Tcn.woff +0 -0
  58. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-400-normal-g30qAdWV.woff2 +0 -0
  59. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-500-normal-Cs5J6C77.woff2 +0 -0
  60. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-500-normal-DB5PtV2g.woff +0 -0
  61. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-600-normal-Bz0x94Yp.woff +0 -0
  62. package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-600-normal-DUMzJB7m.woff2 +0 -0
  63. package/dist/admin-bundle/assets/ibm-plex-sans-greek-400-normal-D9ESIMu3.woff +0 -0
  64. package/dist/admin-bundle/assets/ibm-plex-sans-greek-400-normal-_efipK4i.woff2 +0 -0
  65. package/dist/admin-bundle/assets/ibm-plex-sans-greek-500-normal-CuWXN6rf.woff +0 -0
  66. package/dist/admin-bundle/assets/ibm-plex-sans-greek-500-normal-JMMifIXV.woff2 +0 -0
  67. package/dist/admin-bundle/assets/ibm-plex-sans-greek-600-normal-D-CqTdkO.woff +0 -0
  68. package/dist/admin-bundle/assets/ibm-plex-sans-greek-600-normal-DzTrcv_p.woff2 +0 -0
  69. package/dist/admin-bundle/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
  70. package/dist/admin-bundle/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
  71. package/dist/admin-bundle/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
  72. package/dist/admin-bundle/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
  73. package/dist/admin-bundle/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
  74. package/dist/admin-bundle/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
  75. package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-400-normal-C5H60-Va.woff2 +0 -0
  76. package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-400-normal-RBey6euL.woff +0 -0
  77. package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-500-normal-D0aIdm-b.woff +0 -0
  78. package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-500-normal-DakdToA3.woff2 +0 -0
  79. package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-600-normal-DIrixKbi.woff +0 -0
  80. package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-600-normal-DOrvGEcy.woff2 +0 -0
  81. package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-400-normal-DG4YqDda.woff2 +0 -0
  82. package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-400-normal-fK1oJ5dG.woff +0 -0
  83. package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-500-normal-BEb3_waV.woff +0 -0
  84. package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-500-normal-e4dixQRQ.woff2 +0 -0
  85. package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-600-normal-DgdngZtN.woff +0 -0
  86. package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-600-normal-DpPYBSTl.woff2 +0 -0
  87. package/dist/admin-bundle/assets/index-BIAF0ShP.js +542 -0
  88. package/dist/admin-bundle/assets/index-DhMY-0sC.js +148 -0
  89. package/dist/admin-bundle/assets/index-DnZV_gsg.css +1 -0
  90. package/dist/admin-bundle/assets/index-Do71v6NZ.js +7 -0
  91. package/dist/admin-bundle/assets/literata-cyrillic-ext-opsz-italic-5yUt4G_9.woff2 +0 -0
  92. package/dist/admin-bundle/assets/literata-cyrillic-ext-opsz-normal-C5tfwALZ.woff2 +0 -0
  93. package/dist/admin-bundle/assets/literata-cyrillic-opsz-italic-Bj0ndbyp.woff2 +0 -0
  94. package/dist/admin-bundle/assets/literata-cyrillic-opsz-normal-DgmypKXX.woff2 +0 -0
  95. package/dist/admin-bundle/assets/literata-greek-ext-opsz-italic-DFpO5j-Z.woff2 +0 -0
  96. package/dist/admin-bundle/assets/literata-greek-ext-opsz-normal-DGm64vGR.woff2 +0 -0
  97. package/dist/admin-bundle/assets/literata-greek-opsz-italic-CHERNRFq.woff2 +0 -0
  98. package/dist/admin-bundle/assets/literata-greek-opsz-normal-DFglUmAd.woff2 +0 -0
  99. package/dist/admin-bundle/assets/literata-latin-ext-opsz-italic-4y8iAqqs.woff2 +0 -0
  100. package/dist/admin-bundle/assets/literata-latin-ext-opsz-normal-STiTPs0F.woff2 +0 -0
  101. package/dist/admin-bundle/assets/literata-latin-opsz-italic-CJ93D38p.woff2 +0 -0
  102. package/dist/admin-bundle/assets/literata-latin-opsz-normal-BHZsWIlc.woff2 +0 -0
  103. package/dist/admin-bundle/assets/literata-vietnamese-opsz-italic-BdcrZMcO.woff2 +0 -0
  104. package/dist/admin-bundle/assets/literata-vietnamese-opsz-normal-Gqw5JQlq.woff2 +0 -0
  105. package/dist/admin-bundle/assets/mf-entry-bootstrap-0-aef015cd.js +41 -0
  106. package/dist/admin-bundle/assets/remoteEntry-CNyN68bc.js +14 -0
  107. package/dist/admin-bundle/assets/virtualExposes-BDCSo5OS.js +1 -0
  108. package/dist/admin-bundle/assets/vite-preload-helper-Cgledu0r.js +1 -0
  109. package/dist/admin-bundle/index.html +16 -0
  110. package/dist/app.d.ts +8 -0
  111. package/dist/app.js +252 -0
  112. package/dist/auth/api-content-author.d.ts +2 -0
  113. package/dist/auth/api-content-author.js +27 -0
  114. package/dist/auth/api-keys.d.ts +7 -0
  115. package/dist/auth/api-keys.js +85 -0
  116. package/dist/auth/jwt-payload.d.ts +16 -0
  117. package/dist/auth/jwt-payload.js +1 -0
  118. package/dist/auth/preview-tokens.d.ts +7 -0
  119. package/dist/auth/preview-tokens.js +76 -0
  120. package/dist/auth/rate-limit.d.ts +16 -0
  121. package/dist/auth/rate-limit.js +283 -0
  122. package/dist/auth/require-auth.d.ts +4 -0
  123. package/dist/auth/require-auth.js +28 -0
  124. package/dist/auth/route-rate-limits.d.ts +31 -0
  125. package/dist/auth/route-rate-limits.js +155 -0
  126. package/dist/auth/tokens.d.ts +15 -0
  127. package/dist/auth/tokens.js +38 -0
  128. package/dist/bin.d.ts +2 -0
  129. package/dist/bin.js +6 -0
  130. package/dist/boot/core-migration-gate.d.ts +12 -0
  131. package/dist/boot/core-migration-gate.js +52 -0
  132. package/dist/boot/resolve-boot-mode.d.ts +6 -0
  133. package/dist/boot/resolve-boot-mode.js +206 -0
  134. package/dist/bundled-templates/com.velora.blog/manifest.json +39 -0
  135. package/dist/bundled-templates/com.velora.blog/package.tar.gz +0 -0
  136. package/dist/bundled-templates/com.velora.blog/sample-content.json +14 -0
  137. package/dist/config/admin-ui.d.ts +1 -0
  138. package/dist/config/admin-ui.js +10 -0
  139. package/dist/config/cors.d.ts +7 -0
  140. package/dist/config/cors.js +53 -0
  141. package/dist/config/env-file-path.d.ts +2 -0
  142. package/dist/config/env-file-path.js +22 -0
  143. package/dist/config/graphql-limits.d.ts +2 -0
  144. package/dist/config/graphql-limits.js +26 -0
  145. package/dist/config/headless.d.ts +1 -0
  146. package/dist/config/headless.js +8 -0
  147. package/dist/config/license-revalidation.d.ts +2 -0
  148. package/dist/config/license-revalidation.js +16 -0
  149. package/dist/config/load-env.d.ts +1 -0
  150. package/dist/config/load-env.js +53 -0
  151. package/dist/config/marketplace.d.ts +11 -0
  152. package/dist/config/marketplace.js +19 -0
  153. package/dist/config/preview.d.ts +3 -0
  154. package/dist/config/preview.js +39 -0
  155. package/dist/config/public-docs.d.ts +1 -0
  156. package/dist/config/public-docs.js +9 -0
  157. package/dist/config/site.d.ts +7 -0
  158. package/dist/config/site.js +12 -0
  159. package/dist/config/trust-proxy.d.ts +2 -0
  160. package/dist/config/trust-proxy.js +42 -0
  161. package/dist/data-types/validate-data-type-settings.d.ts +1 -0
  162. package/dist/data-types/validate-data-type-settings.js +33 -0
  163. package/dist/db/content-db.d.ts +16 -0
  164. package/dist/db/content-db.js +109 -0
  165. package/dist/db/core-migrations/index.d.ts +14 -0
  166. package/dist/db/core-migrations/index.js +184 -0
  167. package/dist/db/core-migrations/steps.d.ts +4 -0
  168. package/dist/db/core-migrations/steps.js +66 -0
  169. package/dist/db/load-prisma-client.d.ts +2 -0
  170. package/dist/db/load-prisma-client.js +44 -0
  171. package/dist/db/prisma.d.ts +3 -0
  172. package/dist/db/prisma.js +67 -0
  173. package/dist/db/resolve-client-dir.d.ts +2 -0
  174. package/dist/db/resolve-client-dir.js +83 -0
  175. package/dist/docs/filter-public-api-spec.d.ts +2 -0
  176. package/dist/docs/filter-public-api-spec.js +52 -0
  177. package/dist/docs-bundle/404.html +41 -0
  178. package/dist/docs-bundle/_astro/MobileTableOfContents.astro_astro_type_script_index_0_lang.BcSo_yiZ.js +1 -0
  179. package/dist/docs-bundle/_astro/Search.astro_astro_type_script_index_0_lang.CavldbAs.js +1 -0
  180. package/dist/docs-bundle/_astro/TableOfContents.astro_astro_type_script_index_0_lang.Csloo1VZ.js +1 -0
  181. package/dist/docs-bundle/_astro/common.DTXA7sXM.css +3 -0
  182. package/dist/docs-bundle/_astro/ec.0vx5m.js +3 -0
  183. package/dist/docs-bundle/_astro/ec.w36nc.css +1 -0
  184. package/dist/docs-bundle/_astro/page.Dwipeu-R.js +1 -0
  185. package/dist/docs-bundle/_astro/print.ehPL0gv-.css +1 -0
  186. package/dist/docs-bundle/_astro/starlight-toc.CTCQXEge.js +1 -0
  187. package/dist/docs-bundle/_astro/ui-core.D-7Scnhs.js +2 -0
  188. package/dist/docs-bundle/favicon.svg +1 -0
  189. package/dist/docs-bundle/index.html +1 -0
  190. package/dist/docs-bundle/pagefind/fragment/en_18a36d7.pf_fragment +0 -0
  191. package/dist/docs-bundle/pagefind/fragment/en_1d9eb24.pf_fragment +0 -0
  192. package/dist/docs-bundle/pagefind/fragment/en_1ef320c.pf_fragment +0 -0
  193. package/dist/docs-bundle/pagefind/fragment/en_3099406.pf_fragment +0 -0
  194. package/dist/docs-bundle/pagefind/fragment/en_4a5b6fe.pf_fragment +0 -0
  195. package/dist/docs-bundle/pagefind/fragment/en_9aa652e.pf_fragment +0 -0
  196. package/dist/docs-bundle/pagefind/fragment/en_baccdf6.pf_fragment +0 -0
  197. package/dist/docs-bundle/pagefind/fragment/en_d81f3fe.pf_fragment +0 -0
  198. package/dist/docs-bundle/pagefind/fragment/en_d855550.pf_fragment +0 -0
  199. package/dist/docs-bundle/pagefind/fragment/en_e6d88d8.pf_fragment +0 -0
  200. package/dist/docs-bundle/pagefind/fragment/en_e9d3189.pf_fragment +0 -0
  201. package/dist/docs-bundle/pagefind/index/en_5feeed7.pf_index +0 -0
  202. package/dist/docs-bundle/pagefind/index/en_c6981f9.pf_index +0 -0
  203. package/dist/docs-bundle/pagefind/index/en_ce4f6bf.pf_index +0 -0
  204. package/dist/docs-bundle/pagefind/pagefind-component-ui.css +1509 -0
  205. package/dist/docs-bundle/pagefind/pagefind-component-ui.js +56 -0
  206. package/dist/docs-bundle/pagefind/pagefind-entry.json +1 -0
  207. package/dist/docs-bundle/pagefind/pagefind-highlight.js +1070 -0
  208. package/dist/docs-bundle/pagefind/pagefind-modular-ui.css +214 -0
  209. package/dist/docs-bundle/pagefind/pagefind-modular-ui.js +8 -0
  210. package/dist/docs-bundle/pagefind/pagefind-ui.css +1 -0
  211. package/dist/docs-bundle/pagefind/pagefind-ui.js +2 -0
  212. package/dist/docs-bundle/pagefind/pagefind-worker.js +6 -0
  213. package/dist/docs-bundle/pagefind/pagefind.en_1e94abd852.pf_meta +0 -0
  214. package/dist/docs-bundle/pagefind/pagefind.js +6 -0
  215. package/dist/docs-bundle/pagefind/wasm.en.pagefind +0 -0
  216. package/dist/docs-bundle/pagefind/wasm.unknown.pagefind +0 -0
  217. package/dist/docs-bundle/v1/index.html +86 -0
  218. package/dist/docs-bundle/v1/using-velora/build-a-headless-frontend/index.html +413 -0
  219. package/dist/docs-bundle/v1/using-velora/content-model/index.html +858 -0
  220. package/dist/docs-bundle/v1/using-velora/editing/index.html +748 -0
  221. package/dist/docs-bundle/v1/using-velora/getting-started/index.html +588 -0
  222. package/dist/docs-bundle/v1/using-velora/headless-api/index.html +1620 -0
  223. package/dist/docs-bundle/v1/using-velora/index.html +215 -0
  224. package/dist/docs-bundle/v1/using-velora/installation/index.html +886 -0
  225. package/dist/docs-bundle/v1/using-velora/installing-plugins/index.html +836 -0
  226. package/dist/docs-bundle/v1/using-velora/media/index.html +468 -0
  227. package/dist/docs-bundle/v1/using-velora/users-and-roles/index.html +561 -0
  228. package/dist/graphql/field-count-rule.d.ts +5 -0
  229. package/dist/graphql/field-count-rule.js +59 -0
  230. package/dist/graphql/schema.d.ts +12 -0
  231. package/dist/graphql/schema.js +330 -0
  232. package/dist/index.d.ts +1 -0
  233. package/dist/index.js +180 -0
  234. package/dist/lib.d.ts +2 -0
  235. package/dist/lib.js +39 -0
  236. package/dist/marketplace/install-bundle-members.d.ts +33 -0
  237. package/dist/marketplace/install-bundle-members.js +137 -0
  238. package/dist/marketplace/install-from-marketplace.d.ts +60 -0
  239. package/dist/marketplace/install-from-marketplace.js +581 -0
  240. package/dist/marketplace/license-revalidation-job.d.ts +38 -0
  241. package/dist/marketplace/license-revalidation-job.js +139 -0
  242. package/dist/marketplace/marketplace-client.d.ts +43 -0
  243. package/dist/marketplace/marketplace-client.js +228 -0
  244. package/dist/marketplace/revalidate-license.d.ts +39 -0
  245. package/dist/marketplace/revalidate-license.js +103 -0
  246. package/dist/media/media-service.d.ts +36 -0
  247. package/dist/media/media-service.js +162 -0
  248. package/dist/plugins/boot-plugin-loader.d.ts +32 -0
  249. package/dist/plugins/boot-plugin-loader.js +57 -0
  250. package/dist/plugins/hook-dispatcher.d.ts +13 -0
  251. package/dist/plugins/hook-dispatcher.js +68 -0
  252. package/dist/plugins/install-built-package.d.ts +36 -0
  253. package/dist/plugins/install-built-package.js +199 -0
  254. package/dist/plugins/install-package-tarball.d.ts +25 -0
  255. package/dist/plugins/install-package-tarball.js +73 -0
  256. package/dist/plugins/plugin-context.d.ts +5 -0
  257. package/dist/plugins/plugin-context.js +26 -0
  258. package/dist/plugins/plugin-package-store.d.ts +31 -0
  259. package/dist/plugins/plugin-package-store.js +64 -0
  260. package/dist/plugins/plugin-registry-service.d.ts +227 -0
  261. package/dist/plugins/plugin-registry-service.js +636 -0
  262. package/dist/plugins/seed-template-content.d.ts +23 -0
  263. package/dist/plugins/seed-template-content.js +198 -0
  264. package/dist/plugins/server-serializers.d.ts +3 -0
  265. package/dist/plugins/server-serializers.js +16 -0
  266. package/dist/plugins/simple-storage-api.d.ts +9 -0
  267. package/dist/plugins/simple-storage-api.js +26 -0
  268. package/dist/plugins/snapshot-groups.d.ts +16 -0
  269. package/dist/plugins/snapshot-groups.js +81 -0
  270. package/dist/plugins/structured-query-builder.d.ts +21 -0
  271. package/dist/plugins/structured-query-builder.js +60 -0
  272. package/dist/plugins/structured-snapshot-service.d.ts +16 -0
  273. package/dist/plugins/structured-snapshot-service.js +286 -0
  274. package/dist/plugins/structured-storage-api.d.ts +22 -0
  275. package/dist/plugins/structured-storage-api.js +91 -0
  276. package/dist/plugins/structured-storage-transaction.d.ts +22 -0
  277. package/dist/plugins/structured-storage-transaction.js +63 -0
  278. package/dist/plugins/structured-table-naming.d.ts +1 -0
  279. package/dist/plugins/structured-table-naming.js +18 -0
  280. package/dist/plugins/trusted-keys.d.ts +11 -0
  281. package/dist/plugins/trusted-keys.js +67 -0
  282. package/dist/plugins/trusted-signing-keys.d.ts +27 -0
  283. package/dist/plugins/trusted-signing-keys.js +40 -0
  284. package/dist/plugins/uninstall-bundle.d.ts +29 -0
  285. package/dist/plugins/uninstall-bundle.js +150 -0
  286. package/dist/plugins/verify-package.d.ts +36 -0
  287. package/dist/plugins/verify-package.js +121 -0
  288. package/dist/routes/admin-static.d.ts +8 -0
  289. package/dist/routes/admin-static.js +196 -0
  290. package/dist/routes/api-keys.d.ts +2 -0
  291. package/dist/routes/api-keys.js +95 -0
  292. package/dist/routes/api-playground.d.ts +2 -0
  293. package/dist/routes/api-playground.js +21 -0
  294. package/dist/routes/auth.d.ts +2 -0
  295. package/dist/routes/auth.js +102 -0
  296. package/dist/routes/content.d.ts +3 -0
  297. package/dist/routes/content.js +471 -0
  298. package/dist/routes/data-types.d.ts +3 -0
  299. package/dist/routes/data-types.js +100 -0
  300. package/dist/routes/docs-static.d.ts +8 -0
  301. package/dist/routes/docs-static.js +137 -0
  302. package/dist/routes/document-types.d.ts +3 -0
  303. package/dist/routes/document-types.js +149 -0
  304. package/dist/routes/headless-bridge.d.ts +10 -0
  305. package/dist/routes/headless-bridge.js +207 -0
  306. package/dist/routes/health.d.ts +2 -0
  307. package/dist/routes/health.js +15 -0
  308. package/dist/routes/marketplace-proxy.d.ts +8 -0
  309. package/dist/routes/marketplace-proxy.js +135 -0
  310. package/dist/routes/media.d.ts +6 -0
  311. package/dist/routes/media.js +93 -0
  312. package/dist/routes/plugin-api-routes.d.ts +12 -0
  313. package/dist/routes/plugin-api-routes.js +224 -0
  314. package/dist/routes/plugin-assets.d.ts +8 -0
  315. package/dist/routes/plugin-assets.js +51 -0
  316. package/dist/routes/plugin-management.d.ts +7 -0
  317. package/dist/routes/plugin-management.js +525 -0
  318. package/dist/routes/plugin-storage.d.ts +8 -0
  319. package/dist/routes/plugin-storage.js +104 -0
  320. package/dist/routes/plugins.d.ts +2 -0
  321. package/dist/routes/plugins.js +36 -0
  322. package/dist/routes/public-api.d.ts +9 -0
  323. package/dist/routes/public-api.js +545 -0
  324. package/dist/routes/published-item.d.ts +57 -0
  325. package/dist/routes/published-item.js +236 -0
  326. package/dist/routes/setup.d.ts +9 -0
  327. package/dist/routes/setup.js +477 -0
  328. package/dist/routes/snapshots.d.ts +3 -0
  329. package/dist/routes/snapshots.js +65 -0
  330. package/dist/routes/templates.d.ts +3 -0
  331. package/dist/routes/templates.js +37 -0
  332. package/dist/routes/themes.d.ts +2 -0
  333. package/dist/routes/themes.js +30 -0
  334. package/dist/routes/users.d.ts +2 -0
  335. package/dist/routes/users.js +113 -0
  336. package/dist/sandbox/register-local-plugin.d.ts +36 -0
  337. package/dist/sandbox/register-local-plugin.js +108 -0
  338. package/dist/sandbox/seed-sandbox.d.ts +20 -0
  339. package/dist/sandbox/seed-sandbox.js +122 -0
  340. package/dist/scripts/copy-admin-bundle.d.ts +1 -0
  341. package/dist/scripts/copy-admin-bundle.js +24 -0
  342. package/dist/scripts/copy-docs-bundle.d.ts +1 -0
  343. package/dist/scripts/copy-docs-bundle.js +22 -0
  344. package/dist/scripts/create-api-key.d.ts +5 -0
  345. package/dist/scripts/create-api-key.js +56 -0
  346. package/dist/scripts/generate-prisma-clients.d.ts +6 -0
  347. package/dist/scripts/generate-prisma-clients.js +125 -0
  348. package/dist/scripts/pack-bundled-templates.d.ts +1 -0
  349. package/dist/scripts/pack-bundled-templates.js +87 -0
  350. package/dist/setup/build-connection-string.d.ts +19 -0
  351. package/dist/setup/build-connection-string.js +14 -0
  352. package/dist/setup/bundled-templates.d.ts +19 -0
  353. package/dist/setup/bundled-templates.js +121 -0
  354. package/dist/setup/ensure-prisma-client.d.ts +3 -0
  355. package/dist/setup/ensure-prisma-client.js +23 -0
  356. package/dist/setup/install-starter.d.ts +14 -0
  357. package/dist/setup/install-starter.js +76 -0
  358. package/dist/setup/normalize-database-url.d.ts +2 -0
  359. package/dist/setup/normalize-database-url.js +35 -0
  360. package/dist/setup/persist-env-config.d.ts +2 -0
  361. package/dist/setup/persist-env-config.js +63 -0
  362. package/dist/setup/provision-database.d.ts +2 -0
  363. package/dist/setup/provision-database.js +89 -0
  364. package/dist/setup-app.d.ts +4 -0
  365. package/dist/setup-app.js +43 -0
  366. package/package.json +96 -0
  367. package/prisma/schema.prisma +303 -0
@@ -0,0 +1,21 @@
1
+ import { z } from "zod";
2
+ import { ApiErrorSchema } from "@velora-cms/api-schemas";
3
+ import { requireAdmin } from "../auth/require-auth.js";
4
+ import { filterPublicApiSpec } from "../docs/filter-public-api-spec.js";
5
+ // Admin-only source of the public /api/v1 spec for the API playground
6
+ // (Session 109). Deliberately separate from the public docs surface so the
7
+ // admin tool works regardless of the VELORA_PUBLIC_DOCS off-switch. The
8
+ // response is an arbitrary OpenAPI object, so it's documented with a loose
9
+ // passthrough schema rather than a hand-modelled OpenAPI Zod type.
10
+ export const apiPlaygroundRoutes = async (app) => {
11
+ app.get("/api/api-playground/openapi", {
12
+ onRequest: requireAdmin,
13
+ schema: {
14
+ response: { 200: z.record(z.string(), z.unknown()), 401: ApiErrorSchema, 403: ApiErrorSchema },
15
+ },
16
+ },
17
+ // app.swagger() returns @fastify/swagger's Document type, which has no
18
+ // index signature — same plain-object shape as filterPublicApiSpec's
19
+ // OpenAPISpec, just a differently-shaped type declaration.
20
+ async () => filterPublicApiSpec(app.swagger()));
21
+ };
@@ -0,0 +1,2 @@
1
+ import type { FastifyPluginAsyncZod } from "fastify-type-provider-zod";
2
+ export declare const authRoutes: FastifyPluginAsyncZod;
@@ -0,0 +1,102 @@
1
+ import { AuthErrorSchema, LoginRequestSchema, LoginResponseSchema, LogoutResponseSchema, RefreshResponseSchema, } from "@velora-cms/api-schemas";
2
+ import { prisma } from "../db/prisma.js";
3
+ import { rateLimitConfig } from "../auth/rate-limit.js";
4
+ import { LOGIN_RATE_LIMIT, ipOnlyKeyGenerator } from "../auth/route-rate-limits.js";
5
+ import { ACCESS_TOKEN_TTL, generateRefreshToken, hashRefreshToken, refreshCookieOptions, refreshTokenExpiryDate, REFRESH_COOKIE_NAME, verifyPassword, } from "../auth/tokens.js";
6
+ export const authRoutes = async (app) => {
7
+ // S162: this scope had NO ceiling at all — POST /api/auth/login is
8
+ // unauthenticated and is the system's most obvious brute-force target.
9
+ // Registered per-scope, matching the precedent in plugin-api-routes.ts
10
+ // and public-api.ts: @fastify/rate-limit decorates the instance, so it
11
+ // is registered in child scopes OR at the root, never both.
12
+ await app.register(import("@fastify/rate-limit"), rateLimitConfig);
13
+ app.post("/api/auth/login", {
14
+ schema: {
15
+ body: LoginRequestSchema,
16
+ response: {
17
+ 200: LoginResponseSchema,
18
+ 401: AuthErrorSchema,
19
+ },
20
+ },
21
+ config: {
22
+ rateLimit: {
23
+ max: LOGIN_RATE_LIMIT.max(),
24
+ timeWindow: LOGIN_RATE_LIMIT.timeWindow(),
25
+ // IP-only: see ipOnlyKeyGenerator's comment. Overriding the
26
+ // shared generator here is the whole point — the default would
27
+ // put a token-bearing request in the unlimited jwt: lane.
28
+ keyGenerator: ipOnlyKeyGenerator,
29
+ },
30
+ },
31
+ }, async (request, reply) => {
32
+ const { email, password } = request.body;
33
+ const user = await prisma.user.findUnique({ where: { email } });
34
+ if (!user || !(await verifyPassword(password, user.passwordHash))) {
35
+ return reply.code(401).send({ message: "Invalid email or password" });
36
+ }
37
+ const { accessToken } = await issueSession(app, reply, user);
38
+ return {
39
+ accessToken,
40
+ user: { id: user.id, email: user.email, name: user.name, role: user.role },
41
+ };
42
+ });
43
+ app.post("/api/auth/refresh", {
44
+ schema: {
45
+ response: {
46
+ 200: RefreshResponseSchema,
47
+ 401: AuthErrorSchema,
48
+ },
49
+ },
50
+ }, async (request, reply) => {
51
+ const currentRefreshToken = request.cookies[REFRESH_COOKIE_NAME];
52
+ if (!currentRefreshToken) {
53
+ return reply.code(401).send({ message: "No refresh session" });
54
+ }
55
+ const tokenHash = hashRefreshToken(currentRefreshToken);
56
+ const session = await prisma.session.findUnique({ where: { refreshTokenHash: tokenHash } });
57
+ if (!session || session.revokedAt || session.expiresAt < new Date()) {
58
+ reply.clearCookie(REFRESH_COOKIE_NAME, { path: "/api/auth" });
59
+ return reply.code(401).send({ message: "Invalid or expired refresh token" });
60
+ }
61
+ const user = await prisma.user.findUniqueOrThrow({ where: { id: session.userId } });
62
+ // Rotate: revoke the session this token belonged to, then issue a
63
+ // brand-new one — a leaked refresh token can't be replayed after use.
64
+ await prisma.session.update({ where: { id: session.id }, data: { revokedAt: new Date() } });
65
+ const { accessToken } = await issueSession(app, reply, user);
66
+ return {
67
+ accessToken,
68
+ user: { id: user.id, email: user.email, name: user.name, role: user.role },
69
+ };
70
+ });
71
+ app.post("/api/auth/logout", {
72
+ schema: {
73
+ response: {
74
+ 200: LogoutResponseSchema,
75
+ },
76
+ },
77
+ }, async (request, reply) => {
78
+ const currentRefreshToken = request.cookies[REFRESH_COOKIE_NAME];
79
+ if (currentRefreshToken) {
80
+ const tokenHash = hashRefreshToken(currentRefreshToken);
81
+ await prisma.session.updateMany({
82
+ where: { refreshTokenHash: tokenHash, revokedAt: null },
83
+ data: { revokedAt: new Date() },
84
+ });
85
+ }
86
+ reply.clearCookie(REFRESH_COOKIE_NAME, { path: "/api/auth" });
87
+ return { success: true };
88
+ });
89
+ };
90
+ async function issueSession(app, reply, user) {
91
+ const accessToken = app.jwt.sign({ sub: user.id, email: user.email, role: user.role }, { expiresIn: ACCESS_TOKEN_TTL });
92
+ const refreshToken = generateRefreshToken();
93
+ await prisma.session.create({
94
+ data: {
95
+ userId: user.id,
96
+ refreshTokenHash: hashRefreshToken(refreshToken),
97
+ expiresAt: refreshTokenExpiryDate(),
98
+ },
99
+ });
100
+ reply.setCookie(REFRESH_COOKIE_NAME, refreshToken, refreshCookieOptions());
101
+ return { accessToken };
102
+ }
@@ -0,0 +1,3 @@
1
+ import type { FastifyPluginAsyncZod } from "fastify-type-provider-zod";
2
+ import type { ContentDb } from "../db/content-db.js";
3
+ export declare const contentRoutes: FastifyPluginAsyncZod<ContentDb>;
@@ -0,0 +1,471 @@
1
+ import { z } from "zod";
2
+ import { ApiErrorSchema, ContentByPathQuerySchema, ContentByPathResponseSchema, ContentChildrenQuerySchema, ContentChildrenResponseSchema, ContentItemQuerySchema, ContentItemResponseSchema, ContentRestoreBlockedSchema, ContentRestoreResultSchema, ContentTrashBodySchema, ContentTrashConfirmationSchema, ContentTrashListQuerySchema, ContentTrashListResponseSchema, ContentTrashResultSchema, ContentValidationErrorSchema, CreateContentRequestSchema, LatestVersionResponseSchema, VersionListQuerySchema, VersionListResponseSchema, PreviewTokenResponseSchema, PublishContentRequestSchema, PublishContentResponseSchema, ReorderNodesRequestSchema, ReorderNodesResponseSchema, SaveContentRequestSchema, SaveContentResponseSchema, } from "@velora-cms/api-schemas";
3
+ import { ContentValidationError, DocumentTypeNotFoundForContentError, ParentContentNotFoundError, RestoreBlockedError, TreeRulesViolationError, } from "@velora-cms/content-service";
4
+ import { mintPreviewToken } from "../auth/preview-tokens.js";
5
+ import { requireAdmin, requireAuth } from "../auth/require-auth.js";
6
+ import { buildPreviewUrl } from "../config/preview.js";
7
+ const ContentParamsSchema = z.object({ id: z.string().uuid() });
8
+ // ContentService signals a missing content id / version number with a
9
+ // throw (same style throughout publishContent) — routes translate exactly
10
+ // those into 404s and let anything else stay a 500. There is no separate
11
+ // rollback route: POST /:id/publish with an explicit `version` repoints
12
+ // current at that older version, which is the rollback path.
13
+ function isNotFoundError(error) {
14
+ return (error instanceof Error &&
15
+ /no (content|version \d+) found/i.test(error.message));
16
+ }
17
+ export const contentRoutes = async (app, opts) => {
18
+ const { adapter, contentService } = opts;
19
+ // S156: the admin origin's honest-headless view resolves a pasted URL to a
20
+ // node. requireAuth (not requireAdmin) — it's a read, like this file's
21
+ // other GETs; drafts are visible because this is the back office.
22
+ // No locale/fallback params by decision: a URL names one node, and the
23
+ // adapter's default-locale-first tiebreak IS the resolution semantic here
24
+ // (a recorded deviation from api-design.md's locale-params convention).
25
+ app.get("/api/content/by-path", {
26
+ onRequest: requireAuth,
27
+ schema: {
28
+ querystring: ContentByPathQuerySchema,
29
+ response: {
30
+ 200: ContentByPathResponseSchema,
31
+ 401: ApiErrorSchema,
32
+ 404: ApiErrorSchema,
33
+ 500: ApiErrorSchema,
34
+ },
35
+ },
36
+ }, async (request, reply) => {
37
+ const node = await adapter.getContentNodeByPath(request.query.path);
38
+ if (!node) {
39
+ return reply.code(404).send({ message: `No content found at path ${request.query.path}` });
40
+ }
41
+ return { node };
42
+ });
43
+ app.get("/api/content/:id", {
44
+ onRequest: requireAuth,
45
+ schema: {
46
+ params: ContentParamsSchema,
47
+ querystring: ContentItemQuerySchema,
48
+ response: {
49
+ 200: ContentItemResponseSchema,
50
+ 401: ApiErrorSchema,
51
+ 404: ApiErrorSchema,
52
+ 500: ApiErrorSchema,
53
+ },
54
+ },
55
+ }, async (request, reply) => {
56
+ let node = await adapter.getContentNode(request.params.id);
57
+ if (!node) {
58
+ return reply.code(404).send({ message: `No content found for id ${request.params.id}` });
59
+ }
60
+ // ?locale=fr&fallback=en resolves through the node's translation
61
+ // group. The fallback decision itself lives in ContentService, not
62
+ // here (database.md) — without an explicit fallback param, only an
63
+ // exact translation matches.
64
+ if (request.query.locale) {
65
+ try {
66
+ node = await contentService.getByLocale(node.translationGroupId, request.query.locale, {
67
+ fallback: request.query.fallback ?? false,
68
+ });
69
+ }
70
+ catch (error) {
71
+ if (isNotFoundError(error)) {
72
+ return reply.code(404).send({ message: error.message });
73
+ }
74
+ throw error;
75
+ }
76
+ }
77
+ const version = await adapter.getContentVersion(node.id, node.currentVersion);
78
+ if (!version) {
79
+ // current_version pointing at a missing row is a broken invariant,
80
+ // not a caller mistake.
81
+ return reply.code(500).send({
82
+ message: `current_version (${node.currentVersion}) for ${node.id} has no matching content version`,
83
+ });
84
+ }
85
+ return { node, version };
86
+ });
87
+ app.get("/api/content/roots", {
88
+ onRequest: requireAuth,
89
+ schema: {
90
+ querystring: ContentChildrenQuerySchema,
91
+ response: {
92
+ 200: ContentChildrenResponseSchema,
93
+ 401: ApiErrorSchema,
94
+ },
95
+ },
96
+ }, async (request) => {
97
+ const { cursor, limit } = request.query;
98
+ const items = await adapter.getRootNodes({ cursor, limit });
99
+ const nextCursor = items.length === limit ? (items[items.length - 1]?.id ?? null) : null;
100
+ return { items, nextCursor };
101
+ });
102
+ app.get("/api/content/:id/children", {
103
+ onRequest: requireAuth,
104
+ schema: {
105
+ params: ContentParamsSchema,
106
+ querystring: ContentChildrenQuerySchema,
107
+ response: {
108
+ 200: ContentChildrenResponseSchema,
109
+ 401: ApiErrorSchema,
110
+ 404: ApiErrorSchema,
111
+ },
112
+ },
113
+ }, async (request, reply) => {
114
+ const parent = await adapter.getContentNode(request.params.id);
115
+ if (!parent) {
116
+ return reply.code(404).send({ message: `No content found for id ${request.params.id}` });
117
+ }
118
+ const { cursor, limit } = request.query;
119
+ const items = await adapter.getChildren(parent.id, { cursor, limit });
120
+ // A short page means the walk is done; a full page hands back the last
121
+ // id as the next cursor (an exactly-full final page just costs one
122
+ // extra empty-page request).
123
+ const nextCursor = items.length === limit ? (items[items.length - 1]?.id ?? null) : null;
124
+ return { items, nextCursor };
125
+ });
126
+ app.post("/api/content/roots/reorder", {
127
+ onRequest: requireAuth,
128
+ schema: {
129
+ body: ReorderNodesRequestSchema,
130
+ response: {
131
+ 200: ReorderNodesResponseSchema,
132
+ 400: ApiErrorSchema,
133
+ },
134
+ },
135
+ }, async (request, reply) => {
136
+ try {
137
+ await adapter.reorderSiblings(null, request.body.orderedIds);
138
+ }
139
+ catch (error) {
140
+ return reply.code(400).send({ message: error instanceof Error ? error.message : "Reorder failed" });
141
+ }
142
+ return { success: true };
143
+ });
144
+ app.post("/api/content/:id/children/reorder", {
145
+ onRequest: requireAuth,
146
+ schema: {
147
+ params: ContentParamsSchema,
148
+ body: ReorderNodesRequestSchema,
149
+ response: {
150
+ 200: ReorderNodesResponseSchema,
151
+ 400: ApiErrorSchema,
152
+ 404: ApiErrorSchema,
153
+ },
154
+ },
155
+ }, async (request, reply) => {
156
+ const parent = await adapter.getContentNode(request.params.id);
157
+ if (!parent) {
158
+ return reply.code(404).send({ message: `No content found for id ${request.params.id}` });
159
+ }
160
+ try {
161
+ await adapter.reorderSiblings(parent.id, request.body.orderedIds);
162
+ }
163
+ catch (error) {
164
+ return reply.code(400).send({ message: error instanceof Error ? error.message : "Reorder failed" });
165
+ }
166
+ return { success: true };
167
+ });
168
+ // The newest saved version, regardless of the current_version pointer —
169
+ // GET /api/content/:id deliberately serves the pointer version, so this
170
+ // is the only way to read an unpublished draft (what the content editor
171
+ // edits). Session-gated like the other legacy content reads as of
172
+ // Session 110.
173
+ app.get("/api/content/:id/versions/latest", {
174
+ onRequest: requireAuth,
175
+ schema: {
176
+ params: ContentParamsSchema,
177
+ response: {
178
+ 200: LatestVersionResponseSchema,
179
+ 401: ApiErrorSchema,
180
+ 404: ApiErrorSchema,
181
+ },
182
+ },
183
+ }, async (request, reply) => {
184
+ const version = await adapter.getLatestContentVersion(request.params.id);
185
+ if (!version) {
186
+ return reply.code(404).send({ message: `No content found for id ${request.params.id}` });
187
+ }
188
+ return { version };
189
+ });
190
+ // Version history, newest first, metadata only (no data payload).
191
+ // Version numbers are contiguous 1..latest and rows are never deleted
192
+ // (immutability), so the page enumerates numbers and resolves each via
193
+ // the unique (content_id, version) index — no list method on the
194
+ // adapter needed. If history views ever page through thousands of
195
+ // versions, a real listContentVersions adapter method replaces this
196
+ // loop; at a history page size of ≤100 point lookups it's not worth
197
+ // the interface change yet.
198
+ app.get("/api/content/:id/versions", {
199
+ onRequest: requireAuth,
200
+ schema: {
201
+ params: ContentParamsSchema,
202
+ querystring: VersionListQuerySchema,
203
+ response: {
204
+ 200: VersionListResponseSchema,
205
+ 401: ApiErrorSchema,
206
+ 404: ApiErrorSchema,
207
+ },
208
+ },
209
+ }, async (request, reply) => {
210
+ const latest = await adapter.getLatestContentVersion(request.params.id);
211
+ if (!latest) {
212
+ return reply.code(404).send({ message: `No content found for id ${request.params.id}` });
213
+ }
214
+ const { cursor, limit } = request.query;
215
+ const start = cursor ? Math.min(cursor - 1, latest.version) : latest.version;
216
+ const numbers = [];
217
+ for (let n = start; n >= 1 && numbers.length < limit; n--)
218
+ numbers.push(n);
219
+ const items = [];
220
+ for (const n of numbers) {
221
+ const full = await adapter.getContentVersion(request.params.id, n);
222
+ if (full) {
223
+ const { data: _data, ...meta } = full;
224
+ items.push(meta);
225
+ }
226
+ }
227
+ const last = items[items.length - 1];
228
+ const nextCursor = last && last.version > 1 ? last.version : null;
229
+ return { items, nextCursor };
230
+ });
231
+ // One specific version, full data — the history view's "see what it
232
+ // looked like before" read.
233
+ app.get("/api/content/:id/versions/:version", {
234
+ onRequest: requireAuth,
235
+ schema: {
236
+ params: ContentParamsSchema.extend({
237
+ version: z.coerce.number().int().positive(),
238
+ }),
239
+ response: {
240
+ 200: LatestVersionResponseSchema,
241
+ 401: ApiErrorSchema,
242
+ 404: ApiErrorSchema,
243
+ },
244
+ },
245
+ }, async (request, reply) => {
246
+ const version = await adapter.getContentVersion(request.params.id, request.params.version);
247
+ if (!version) {
248
+ return reply.code(404).send({
249
+ message: `No version ${request.params.version} found for content ${request.params.id}`,
250
+ });
251
+ }
252
+ return { version };
253
+ });
254
+ app.post("/api/content", {
255
+ onRequest: requireAuth,
256
+ schema: {
257
+ body: CreateContentRequestSchema,
258
+ response: {
259
+ 201: ContentItemResponseSchema,
260
+ // Union so both bodies serialize on the one 400 slot: the
261
+ // structured field-validation body, and the plain-message body
262
+ // Fastify's own Zod body validation produces (a bare
263
+ // ContentValidationErrorSchema turned those into a 500).
264
+ 400: z.union([ContentValidationErrorSchema, ApiErrorSchema]),
265
+ 401: ApiErrorSchema,
266
+ 404: ApiErrorSchema,
267
+ },
268
+ },
269
+ }, async (request, reply) => {
270
+ try {
271
+ const result = await contentService.createContent({
272
+ ...request.body,
273
+ createdBy: request.user.sub,
274
+ });
275
+ return reply.code(201).send(result);
276
+ }
277
+ catch (error) {
278
+ if (error instanceof ContentValidationError) {
279
+ return reply.code(400).send({ message: error.message, fieldErrors: error.failures });
280
+ }
281
+ if (error instanceof ParentContentNotFoundError) {
282
+ return reply.code(404).send({ message: error.message });
283
+ }
284
+ if (error instanceof DocumentTypeNotFoundForContentError || error instanceof TreeRulesViolationError) {
285
+ return reply.code(400).send({ message: error.message });
286
+ }
287
+ throw error;
288
+ }
289
+ });
290
+ app.put("/api/content/:id", {
291
+ onRequest: requireAuth,
292
+ schema: {
293
+ params: ContentParamsSchema,
294
+ body: SaveContentRequestSchema,
295
+ response: {
296
+ 200: SaveContentResponseSchema,
297
+ 400: z.union([ContentValidationErrorSchema, ApiErrorSchema]),
298
+ 401: ApiErrorSchema,
299
+ 404: ApiErrorSchema,
300
+ },
301
+ },
302
+ }, async (request, reply) => {
303
+ try {
304
+ const version = await contentService.saveContent(request.params.id, request.body.data, request.user.sub);
305
+ return { version };
306
+ }
307
+ catch (error) {
308
+ if (error instanceof ContentValidationError) {
309
+ return reply.code(400).send({ message: error.message, fieldErrors: error.failures });
310
+ }
311
+ if (isNotFoundError(error)) {
312
+ return reply.code(404).send({ message: error.message });
313
+ }
314
+ throw error;
315
+ }
316
+ });
317
+ app.post("/api/content/:id/publish", {
318
+ onRequest: requireAuth,
319
+ schema: {
320
+ params: ContentParamsSchema,
321
+ // Optional body: absent (or {}) publishes the latest saved version.
322
+ // Fastify hands a bodyless POST to validation as null, not
323
+ // undefined — hence nullish, not optional.
324
+ body: PublishContentRequestSchema.nullish(),
325
+ response: {
326
+ 200: PublishContentResponseSchema,
327
+ 401: ApiErrorSchema,
328
+ 404: ApiErrorSchema,
329
+ },
330
+ },
331
+ }, async (request, reply) => {
332
+ try {
333
+ const node = await contentService.publishContent(request.params.id, request.body?.version);
334
+ return { node };
335
+ }
336
+ catch (error) {
337
+ if (isNotFoundError(error)) {
338
+ return reply.code(404).send({ message: error.message });
339
+ }
340
+ throw error;
341
+ }
342
+ });
343
+ // S161: the trash browse surface. Admin-gated like DELETE below (and
344
+ // unlike this file's requireAuth routes) — trash operations are
345
+ // destructive-adjacent even though reversible. This resolves correctly
346
+ // against GET /api/content/:id (registered earlier in this file, not
347
+ // later) regardless of registration order: find-my-way ranks a static
348
+ // path segment ("trash") above a parametric one ("/:id") when matching,
349
+ // and ContentParamsSchema requires :id to be a uuid, which the literal
350
+ // string "trash" never is. Registration order is not the mechanism here.
351
+ app.get("/api/content/trash", {
352
+ onRequest: requireAdmin,
353
+ schema: {
354
+ querystring: ContentTrashListQuerySchema,
355
+ response: {
356
+ 200: ContentTrashListResponseSchema,
357
+ 401: ApiErrorSchema,
358
+ 403: ApiErrorSchema,
359
+ },
360
+ },
361
+ }, async (request, reply) => {
362
+ const { cursor, limit } = request.query;
363
+ const items = await adapter.listTrashedSubtrees({ cursor, limit });
364
+ // A full page means there may be more; a short page is the end. Same
365
+ // convention as this file's other cursor-paginated reads.
366
+ const last = items.at(-1);
367
+ const nextCursor = items.length === limit && last ? `${last.deletedAt}|${last.id}` : null;
368
+ return reply.code(200).send({ items, nextCursor });
369
+ });
370
+ // S154: content's first delete surface. Two-step like the structured-data
371
+ // uninstall gate — unconfirmed 409 names the blast radius (the whole
372
+ // subtree, not just this node), confirmed trashes it. Soft delete only:
373
+ // reversible via the restore route below. Admin-gated (not just
374
+ // requireAuth, unlike this file's other routes) since it's destructive
375
+ // even though reversible.
376
+ app.delete("/api/content/:id", {
377
+ onRequest: requireAdmin,
378
+ schema: {
379
+ params: ContentParamsSchema,
380
+ body: ContentTrashBodySchema,
381
+ response: {
382
+ 200: ContentTrashResultSchema,
383
+ 401: ApiErrorSchema,
384
+ 403: ApiErrorSchema,
385
+ 404: ApiErrorSchema,
386
+ 409: ContentTrashConfirmationSchema,
387
+ },
388
+ },
389
+ }, async (request, reply) => {
390
+ const node = await adapter.getContentNode(request.params.id);
391
+ if (!node) {
392
+ return reply.code(404).send({ message: `No content found for id ${request.params.id}` });
393
+ }
394
+ if (request.body?.confirm !== true) {
395
+ const subtreeCount = await adapter.countContentSubtree(request.params.id);
396
+ // Clamped defensively: countContentSubtree only counts LIVE rows,
397
+ // so if the target itself somehow isn't live when this executes
398
+ // (e.g. trashed out from under this request between the
399
+ // getContentNode check above and here), subtreeCount could be 0 —
400
+ // and 0 - 1 would render as "-1 descendant(s)" in the 409 body.
401
+ const descendantCount = Math.max(0, subtreeCount - 1);
402
+ return reply.code(409).send({
403
+ requiresConfirmation: true,
404
+ // ContentNode has no display-name field of its own — path is
405
+ // the closest thing to one.
406
+ nodeName: node.path,
407
+ descendantCount,
408
+ message: `deleting "${node.path}" moves it and ${descendantCount} descendant(s) to the trash — retry with confirm: true`,
409
+ });
410
+ }
411
+ const result = await contentService.trashContent(request.params.id);
412
+ return reply.code(200).send(result);
413
+ });
414
+ app.post("/api/content/:id/restore", {
415
+ onRequest: requireAdmin,
416
+ schema: {
417
+ params: ContentParamsSchema,
418
+ response: {
419
+ 200: ContentRestoreResultSchema,
420
+ 401: ApiErrorSchema,
421
+ 403: ApiErrorSchema,
422
+ 404: ApiErrorSchema,
423
+ 409: ContentRestoreBlockedSchema,
424
+ },
425
+ },
426
+ }, async (request, reply) => {
427
+ try {
428
+ const result = await contentService.restoreContent(request.params.id);
429
+ return reply.code(200).send(result);
430
+ }
431
+ catch (error) {
432
+ if (error instanceof RestoreBlockedError) {
433
+ return reply.code(409).send({
434
+ message: error.message,
435
+ blockingAncestorPath: error.blockingAncestorPath,
436
+ });
437
+ }
438
+ if (isNotFoundError(error)) {
439
+ return reply.code(404).send({ message: error.message });
440
+ }
441
+ throw error;
442
+ }
443
+ });
444
+ // Session 116: mints a stateless, short-TTL, single-content-id preview
445
+ // token (apps/server/src/auth/preview-tokens.ts) that unlocks a draft
446
+ // read on the public content API for exactly this id. Any existing
447
+ // content id may be minted for, published or not — a session holder is
448
+ // a trusted editor previewing their own work, not an anonymous reader.
449
+ app.post("/api/content/:id/preview-token", {
450
+ onRequest: requireAuth,
451
+ schema: {
452
+ params: ContentParamsSchema,
453
+ response: {
454
+ 200: PreviewTokenResponseSchema,
455
+ 401: ApiErrorSchema,
456
+ 404: ApiErrorSchema,
457
+ },
458
+ },
459
+ }, async (request, reply) => {
460
+ const node = await adapter.getContentNode(request.params.id);
461
+ if (!node) {
462
+ return reply.code(404).send({ message: `No content found for id ${request.params.id}` });
463
+ }
464
+ const { token, expiresAt } = mintPreviewToken(node.id);
465
+ return {
466
+ token,
467
+ expiresAt,
468
+ previewUrl: buildPreviewUrl(token, node.id),
469
+ };
470
+ });
471
+ };
@@ -0,0 +1,3 @@
1
+ import type { FastifyPluginAsyncZod } from "fastify-type-provider-zod";
2
+ import type { ContentDb } from "../db/content-db.js";
3
+ export declare const dataTypeRoutes: FastifyPluginAsyncZod<ContentDb>;