@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
package/dist/app.js ADDED
@@ -0,0 +1,252 @@
1
+ import Fastify from "fastify";
2
+ import fastifyCookie from "@fastify/cookie";
3
+ import fastifyJwt from "@fastify/jwt";
4
+ import fastifyMultipart from "@fastify/multipart";
5
+ import fastifySwagger from "@fastify/swagger";
6
+ import fastifySwaggerUi from "@fastify/swagger-ui";
7
+ import { createJsonSchemaTransform, serializerCompiler, validatorCompiler, } from "fastify-type-provider-zod";
8
+ import { ensureApiContentAuthor } from "./auth/api-content-author.js";
9
+ import { isHeadlessEnabled } from "./config/headless.js";
10
+ import { getMarketplaceBaseUrl } from "./config/marketplace.js";
11
+ import { isPublicDocsEnabled } from "./config/public-docs.js";
12
+ import { trustProxyFromEnv } from "./config/trust-proxy.js";
13
+ import { connectContentDb } from "./db/content-db.js";
14
+ import { prisma, resolvePrismaDialect } from "./db/prisma.js";
15
+ import { resolveClientDir } from "./db/resolve-client-dir.js";
16
+ import { filterPublicApiSpec } from "./docs/filter-public-api-spec.js";
17
+ import { startLicenseRevalidation } from "./marketplace/license-revalidation-job.js";
18
+ import { MediaService, mediaStorageConfigFromEnv } from "./media/media-service.js";
19
+ import { bootInstalledPlugins } from "./plugins/boot-plugin-loader.js";
20
+ import { createHookDispatcher } from "./plugins/hook-dispatcher.js";
21
+ import { PluginRegistryService } from "./plugins/plugin-registry-service.js";
22
+ import { SimpleStorageAPI } from "./plugins/simple-storage-api.js";
23
+ import { StructuredStorageAPI } from "./plugins/structured-storage-api.js";
24
+ import { StructuredSnapshotService } from "./plugins/structured-snapshot-service.js";
25
+ import { apiKeyRoutes } from "./routes/api-keys.js";
26
+ import { apiPlaygroundRoutes } from "./routes/api-playground.js";
27
+ import { authRoutes } from "./routes/auth.js";
28
+ import { contentRoutes } from "./routes/content.js";
29
+ import { documentTypeRoutes } from "./routes/document-types.js";
30
+ import { adminStaticRoutes } from "./routes/admin-static.js";
31
+ import { docsStaticRoutes } from "./routes/docs-static.js";
32
+ import { createFrameworkErrorHandler, registerHeadlessBridge } from "./routes/headless-bridge.js";
33
+ import { healthRoutes } from "./routes/health.js";
34
+ import { marketplaceProxyRoutes } from "./routes/marketplace-proxy.js";
35
+ import { mediaRoutes } from "./routes/media.js";
36
+ import { dataTypeRoutes } from "./routes/data-types.js";
37
+ import { pluginRoutes } from "./routes/plugins.js";
38
+ import { themeRoutes } from "./routes/themes.js";
39
+ import { pluginApiRoutes } from "./routes/plugin-api-routes.js";
40
+ import { pluginAssetsRoutes } from "./routes/plugin-assets.js";
41
+ import { pluginManagementRoutes } from "./routes/plugin-management.js";
42
+ import { pluginStorageRoutes } from "./routes/plugin-storage.js";
43
+ import { publicApiRoutes } from "./routes/public-api.js";
44
+ import { snapshotRoutes } from "./routes/snapshots.js";
45
+ import { setupRoutes } from "./routes/setup.js";
46
+ import { usersRoutes } from "./routes/users.js";
47
+ import { templateRoutes } from "./routes/templates.js";
48
+ export async function buildApp() {
49
+ // frameworkErrors (S156): the only hook into find-my-way's own
50
+ // pre-routing FST_ERR_BAD_URL/FST_ERR_ASYNC_CONSTRAINT responses — it's
51
+ // constructor-time-only, so it can't be registered later alongside
52
+ // registerHeadlessBridge below. See headless-bridge.ts's
53
+ // createFrameworkErrorHandler doc comment for why setNotFoundHandler /
54
+ // setErrorHandler can't reach this case.
55
+ //
56
+ // trustProxy (S160 final-fix, finding 1): constructor-time-only, same as
57
+ // frameworkErrors above — it configures Fastify's own request.ip/hostname
58
+ // resolution, not something that can be layered on via a hook afterward.
59
+ // trustProxyFromEnv() returns `undefined` when VELORA_TRUST_PROXY is
60
+ // unset, and the key is omitted from the options object entirely in that
61
+ // case (not passed as `trustProxy: undefined`) so today's behaviour — no
62
+ // trust, request.ip is always the socket's remote address — is preserved
63
+ // byte-for-byte for every deployment that hasn't opted in.
64
+ const trustProxy = trustProxyFromEnv();
65
+ const app = Fastify({
66
+ logger: true,
67
+ frameworkErrors: createFrameworkErrorHandler(),
68
+ ...(trustProxy !== undefined ? { trustProxy } : {}),
69
+ }).withTypeProvider();
70
+ app.setValidatorCompiler(validatorCompiler);
71
+ app.setSerializerCompiler(serializerCompiler);
72
+ const jwtSecret = process.env.JWT_SECRET;
73
+ if (!jwtSecret) {
74
+ throw new Error("JWT_SECRET must be set — see .env.example");
75
+ }
76
+ await app.register(fastifyJwt, { secret: jwtSecret });
77
+ await app.register(fastifyCookie);
78
+ await app.register(fastifyMultipart, {
79
+ // Media uploads only — 50 MiB covers images/documents; revisit when
80
+ // video needs streaming multipart instead of toBuffer().
81
+ limits: { fileSize: 50 * 1024 * 1024 },
82
+ });
83
+ await app.register(fastifySwagger, {
84
+ openapi: {
85
+ openapi: "3.1.0",
86
+ info: {
87
+ title: "Velora API",
88
+ version: "0.1.0",
89
+ },
90
+ },
91
+ // jsonSchemaTransform (the package's zero-config default) assumes
92
+ // EVERY route.schema it sees came from a zod object and unconditionally
93
+ // looks it up in zod's global registry — it throws, not skips, on a
94
+ // route carrying a plain JSON-schema object. mercurius (Session 113,
95
+ // Task 2) sets exactly that on its own /api/v1/graphql routes (request
96
+ // envelope validation it owns itself), so the one skip here keeps
97
+ // those out of zodSchemaToJson's path. GraphQL's shape (arbitrary
98
+ // query/variables JSON) isn't OpenAPI-expressible anyway — same reason
99
+ // this surface gets a GraphiQL page instead of a Swagger entry.
100
+ transform: createJsonSchemaTransform({ skipList: ["/api/v1/graphql"] }),
101
+ });
102
+ await app.register(fastifySwaggerUi, {
103
+ routePrefix: "/api/docs",
104
+ });
105
+ // Public API docs (Month 9, Session 109) — a SECOND Swagger UI serving
106
+ // ONLY the /api/v1 surface, via transformSpecification filtering the one
107
+ // generated spec. Registered only when headless is on (the /api/v1 API
108
+ // exists) AND the public-docs flag is enabled (off = hardening: the docs
109
+ // URL 404s, the API keeps answering). The internal /api/docs above is
110
+ // untouched.
111
+ //
112
+ // @fastify/swagger-ui's plugin is wrapped with fastify-plugin, so it
113
+ // always decorates whatever instance it's registered on (fastify-plugin
114
+ // deliberately breaks encapsulation). Registering it a second time
115
+ // directly on `app` collides with the /api/docs registration above on
116
+ // the `swaggerCSP` decorator ("already been added"). Registering it
117
+ // inside a plain (non-fp) async plugin function first forces avvio to
118
+ // create a genuinely new encapsulated child instance, so the second
119
+ // swagger-ui's decorator lands on that child instead of colliding with
120
+ // the parent's.
121
+ if (isHeadlessEnabled() && isPublicDocsEnabled()) {
122
+ await app.register(async (publicDocsApp) => {
123
+ await publicDocsApp.register(fastifySwaggerUi, {
124
+ routePrefix: "/api/v1/docs",
125
+ transformSpecification: (swaggerObject) => filterPublicApiSpec(swaggerObject),
126
+ // The transformed object is already the final public spec — no
127
+ // per-request cloning needed beyond filterPublicApiSpec's own copy.
128
+ transformSpecificationClone: false,
129
+ });
130
+ });
131
+ }
132
+ const contentDb = await connectContentDb();
133
+ app.addHook("onClose", async () => {
134
+ await contentDb.adapter.disconnect();
135
+ });
136
+ const mediaService = new MediaService(prisma, mediaStorageConfigFromEnv(),
137
+ // Routed through resolveClientDir() rather than reading
138
+ // VELORA_PRISMA_CLIENT_DIR directly, so there's one channel deciding
139
+ // "which generated client dir" instead of two (db/prisma.ts's own
140
+ // `prisma` export already goes through this same helper). Behaviour is
141
+ // unchanged for every existing case: velora-dev's sandbox value has no
142
+ // dialect-named suffix and passes through resolveClientDir's explicit
143
+ // branch untouched, so resolvePrismaDialect still falls through to the
144
+ // DATABASE_URL-scheme branch for it exactly as before.
145
+ resolvePrismaDialect(resolveClientDir(), process.env.DATABASE_URL));
146
+ await mediaService.ensureBucket();
147
+ // The registry's dialect gate follows the content database's resolved
148
+ // dialect — contentDb.dialect is the resolved postgresql|mysql|sqlite
149
+ // union (Session 122), so 'mock' is excluded by construction and no
150
+ // cast is needed.
151
+ const structuredSnapshotService = new StructuredSnapshotService(contentDb.adapter, prisma);
152
+ const pluginRegistry = new PluginRegistryService(prisma, contentDb.adapter, {
153
+ activeDialect: contentDb.dialect,
154
+ structuredDataLifecycle: {
155
+ snapshot: (pluginId) => structuredSnapshotService.snapshotPluginTables(pluginId, "uninstall"),
156
+ cleanup: (pluginId) => structuredSnapshotService.dropPluginTables(pluginId),
157
+ },
158
+ });
159
+ const simpleStorageAPI = new SimpleStorageAPI(contentDb.adapter, pluginRegistry);
160
+ const structuredStorageAPI = new StructuredStorageAPI(contentDb.adapter, pluginRegistry, prisma);
161
+ // S159: the hook engine — content lifecycle events reach active plugins'
162
+ // hook handlers. Wired post-construction (contentDb exists before the
163
+ // registry does); dispatch is fire-and-forget by construction.
164
+ contentDb.contentService.setOnContentEvent(createHookDispatcher({
165
+ registry: pluginRegistry,
166
+ structuredStorageAPI,
167
+ contentService: contentDb.contentService,
168
+ adapter: contentDb.adapter,
169
+ log: app.log,
170
+ }));
171
+ // Exposes the exact PluginRegistryService instance the routes below
172
+ // close over. A freshly-constructed PluginRegistryService in a test or
173
+ // script still has its own private, empty manifests Map, disjoint from
174
+ // this one (see plugin-registry-service.ts's attachManifest doc
175
+ // comment) — that part of the old caveat still holds. What's changed
176
+ // since Session 122: manifests now re-attach at every app build via
177
+ // bootInstalledPlugins below, reading from the managed package store,
178
+ // so a fresh buildApp() DOES see plugins installed in a previous
179
+ // process (the S89 no-boot-loader gap is closed). Installing or
180
+ // activating a plugin MID-PROCESS, though, must still go through THIS
181
+ // instance, not a separately `new`-ed one — that's what
182
+ // pluginApiRoutes's dispatcher below closes over.
183
+ app.decorate("pluginRegistry", pluginRegistry);
184
+ // Session 122: re-attach every installed plugin's manifest from the
185
+ // managed package store before the server answers requests — the
186
+ // post-restart bridge that closes the S89 no-boot-loader gap.
187
+ await bootInstalledPlugins(prisma, pluginRegistry, { info: (m) => app.log.info(m), warn: (m) => app.log.warn(m) });
188
+ await app.register(setupRoutes, { setupRequired: false, databaseConfigured: true });
189
+ await app.register(healthRoutes);
190
+ await app.register(docsStaticRoutes);
191
+ // The admin SPA at /velora (S184). Registered unconditionally, exactly
192
+ // like docsStaticRoutes above: the VELORA_ADMIN=off switch lives INSIDE
193
+ // the module so a disabled instance still answers /velora/* with a 404
194
+ // that names the flag (the design doc's verification calls for a
195
+ // 404-with-cause there, not a bare unmatched-route 404), while leaving
196
+ // GET / on its pre-S184 headless-bridge behaviour.
197
+ await app.register(adminStaticRoutes);
198
+ await app.register(pluginAssetsRoutes);
199
+ await app.register(authRoutes);
200
+ await app.register(usersRoutes);
201
+ await app.register(apiPlaygroundRoutes);
202
+ await app.register(apiKeyRoutes);
203
+ await app.register(pluginRoutes);
204
+ await app.register(themeRoutes);
205
+ await app.register(pluginManagementRoutes, { adapter: contentDb.adapter, structuredSnapshotService });
206
+ // In-CMS marketplace browse/detail proxy (Session 137). Session 180:
207
+ // getMarketplaceBaseUrl() now defaults to the hosted marketplace when
208
+ // VELORA_MARKETPLACE_URL is unset, so these routes register by default
209
+ // on a fresh install — only an explicit `off` (or a blank value) leaves
210
+ // them structurally absent, same precedent as isHeadlessEnabled below:
211
+ // a disabled instance gets no dead endpoints, not a 404 from an
212
+ // always-registered handler.
213
+ const marketplaceBaseUrl = getMarketplaceBaseUrl();
214
+ if (marketplaceBaseUrl) {
215
+ await app.register(marketplaceProxyRoutes, {
216
+ marketplaceBaseUrl,
217
+ activeDialect: contentDb.dialect,
218
+ adapter: contentDb.adapter,
219
+ });
220
+ // The first background timer in apps/server (Session 139). unref()'d
221
+ // internally so it can never hold the process open, and stopped here
222
+ // on close — every integration test that builds and closes an app via
223
+ // this same gate would otherwise leak a live interval.
224
+ const stopLicenseRevalidation = startLicenseRevalidation({ prisma, registry: pluginRegistry, marketplaceBaseUrl });
225
+ app.addHook("onClose", async () => {
226
+ await stopLicenseRevalidation();
227
+ });
228
+ }
229
+ await app.register(contentRoutes, contentDb);
230
+ await app.register(snapshotRoutes, contentDb);
231
+ await app.register(documentTypeRoutes, contentDb);
232
+ await app.register(dataTypeRoutes, contentDb);
233
+ await app.register(templateRoutes, contentDb);
234
+ await app.register(mediaRoutes, { mediaService });
235
+ await app.register(pluginStorageRoutes, { adapter: contentDb.adapter, simpleStorageAPI });
236
+ await app.register(pluginApiRoutes, {
237
+ registry: pluginRegistry,
238
+ structuredStorageAPI,
239
+ contentService: contentDb.contentService,
240
+ adapter: contentDb.adapter,
241
+ });
242
+ // Public Content API (Month 9) — only exists when the deploy-time
243
+ // headless flag is on. Off means structurally absent (404), not 403.
244
+ if (isHeadlessEnabled()) {
245
+ const apiContentAuthorId = await ensureApiContentAuthor(prisma);
246
+ await app.register(publicApiRoutes, { ...contentDb, mediaService, apiContentAuthorId });
247
+ }
248
+ // S156: the app-wide not-found handler — registered last, after every
249
+ // route mount, so it only ever sees genuinely unmatched URLs.
250
+ registerHeadlessBridge(app, { adapter: contentDb.adapter });
251
+ return app;
252
+ }
@@ -0,0 +1,2 @@
1
+ import type { PrismaClient } from "@prisma/client";
2
+ export declare function ensureApiContentAuthor(prisma: PrismaClient): Promise<string>;
@@ -0,0 +1,27 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { hashPassword } from "./tokens.js";
3
+ // The public Content API (Month 9) is key-gated — an API key carries no
4
+ // user id, but ContentService.createContent requires a createdBy UUID that
5
+ // lands in ContentVersion.createdBy (serialized back as a strict uuid by
6
+ // PublicContentWriteResponseSchema/ContentValidationErrorSchema's callers).
7
+ // Rather than inventing a fake uuid or loosening the uuid contract, every
8
+ // public-API write is attributed to one fixed, idempotent system user —
9
+ // resolved once at startup (buildApp) and reused for the life of the
10
+ // process. This is a non-login account: passwordHash is a random bcrypt
11
+ // hash nobody knows the plaintext for, so it can never authenticate via
12
+ // the normal login route.
13
+ const API_CONTENT_AUTHOR_EMAIL = "api-author@velora.internal";
14
+ export async function ensureApiContentAuthor(prisma) {
15
+ const nonLoginPasswordHash = await hashPassword(randomBytes(32).toString("hex"));
16
+ const user = await prisma.user.upsert({
17
+ where: { email: API_CONTENT_AUTHOR_EMAIL },
18
+ update: {},
19
+ create: {
20
+ email: API_CONTENT_AUTHOR_EMAIL,
21
+ passwordHash: nonLoginPasswordHash,
22
+ name: "Public API",
23
+ role: "editor",
24
+ },
25
+ });
26
+ return user.id;
27
+ }
@@ -0,0 +1,7 @@
1
+ import type { FastifyReply, FastifyRequest } from "fastify";
2
+ import type { ApiKeyScope } from "@velora-cms/api-schemas";
3
+ export declare function generateApiKey(): string;
4
+ export declare function hashApiKey(key: string): string;
5
+ export declare function isApiKeyHashValid(hash: string): Promise<boolean>;
6
+ export declare function requireApiKey(request: FastifyRequest, reply: FastifyReply): Promise<void>;
7
+ export declare function requireApiKeyScope(required: ApiKeyScope): (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
@@ -0,0 +1,85 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ import { prisma } from "../db/prisma.js";
3
+ // Public Content API keys (Month 9, Session 101). A deliberately separate
4
+ // code path from the admin JWT/cookie session (api-design.md: never
5
+ // conflate headless consumers with logged-in admin sessions).
6
+ const KEY_PREFIX = "vk_";
7
+ // High-entropy random secret, so sha256 (fast) is the right hash — same
8
+ // reasoning as hashRefreshToken in tokens.ts, NOT bcrypt like passwords.
9
+ export function generateApiKey() {
10
+ return KEY_PREFIX + randomBytes(32).toString("hex");
11
+ }
12
+ export function hashApiKey(key) {
13
+ return createHash("sha256").update(key).digest("hex");
14
+ }
15
+ // One uniform 401 for every failure mode — missing header, malformed,
16
+ // unknown, revoked. Distinguishing them would let a caller probe which
17
+ // keys exist. Never echo the presented key back.
18
+ const UNAUTHORIZED_MESSAGE = "A valid API key is required";
19
+ // Deliberately distinct from UNAUTHORIZED_MESSAGE and a 403, not a 401:
20
+ // the key authenticated fine, it simply is not allowed to write. Telling
21
+ // the caller that costs nothing (they already hold the key) and is the
22
+ // only way they can know to mint a write key instead.
23
+ const READ_ONLY_MESSAGE = "This API key is read-only";
24
+ // Shared with rate-limit.ts (Session 160, Task 1 fix round 3): the rate
25
+ // limiter's keyGenerator has to resolve a presented vk_ token to a real,
26
+ // non-revoked key BEFORE this function ever runs (it runs as an onRequest
27
+ // hook ahead of the route dispatch that eventually calls requireApiKey),
28
+ // so both need the exact same "is this hash a live key" query — a second,
29
+ // slightly-different query shape here would be a second place for that
30
+ // definition to drift. Returns the record (not just a boolean) so
31
+ // requireApiKey can still reach its id for the lastUsedAt write below.
32
+ async function findLiveApiKeyByHash(hash) {
33
+ const record = await prisma.apiKey.findUnique({ where: { keyHash: hash } });
34
+ return record && record.revokedAt === null ? record : null;
35
+ }
36
+ // rate-limit.ts's only use of this module: resolve a presented token to
37
+ // "is this a real, non-revoked key" without needing the full record or
38
+ // triggering requireApiKey's lastUsedAt write (that write belongs to an
39
+ // actual authenticated request, not a rate-limit bucket lookup that may
40
+ // run against routes this key was never even destined to authenticate).
41
+ export async function isApiKeyHashValid(hash) {
42
+ return (await findLiveApiKeyByHash(hash)) !== null;
43
+ }
44
+ // onRequest, not preHandler, for the same reason as require-auth.ts:
45
+ // body validation runs before preHandler, so unauthenticated requests
46
+ // would get validation 400s instead of a 401.
47
+ export async function requireApiKey(request, reply) {
48
+ const header = request.headers.authorization;
49
+ if (!header?.startsWith("Bearer ")) {
50
+ await reply.code(401).send({ message: UNAUTHORIZED_MESSAGE });
51
+ return;
52
+ }
53
+ const key = header.slice("Bearer ".length).trim();
54
+ if (!key.startsWith(KEY_PREFIX)) {
55
+ await reply.code(401).send({ message: UNAUTHORIZED_MESSAGE });
56
+ return;
57
+ }
58
+ const record = await findLiveApiKeyByHash(hashApiKey(key));
59
+ if (!record) {
60
+ await reply.code(401).send({ message: UNAUTHORIZED_MESSAGE });
61
+ return;
62
+ }
63
+ // Everything downstream (requireApiKeyScope, and any future per-key
64
+ // policy) reads the key off the request rather than re-querying.
65
+ request.apiKey = { id: record.id, scope: record.scope };
66
+ // Awaited, per-request write: fine at current scale, and it makes
67
+ // lastUsedAt trustworthy for Session 102's key-management UI. Revisit
68
+ // (throttle/fire-and-forget) with Session 108's rate limiting if it
69
+ // ever shows up in profiles.
70
+ await prisma.apiKey.update({
71
+ where: { id: record.id },
72
+ data: { lastUsedAt: new Date() },
73
+ });
74
+ }
75
+ // Runs AFTER requireApiKey in a route's onRequest array, so request.apiKey
76
+ // is already populated. "write" implies read, so only write-scoped routes
77
+ // ever call this with "write" — read routes need no scope hook at all.
78
+ export function requireApiKeyScope(required) {
79
+ return async (request, reply) => {
80
+ const scope = request.apiKey?.scope;
81
+ if (required === "write" && scope !== "write") {
82
+ await reply.code(403).send({ message: READ_ONLY_MESSAGE });
83
+ }
84
+ };
85
+ }
@@ -0,0 +1,16 @@
1
+ import "@fastify/jwt";
2
+ import type { UserRole } from "@velora-cms/api-schemas";
3
+ declare module "@fastify/jwt" {
4
+ interface FastifyJWT {
5
+ payload: {
6
+ sub: string;
7
+ email: string;
8
+ role: UserRole;
9
+ };
10
+ user: {
11
+ sub: string;
12
+ email: string;
13
+ role: UserRole;
14
+ };
15
+ }
16
+ }
@@ -0,0 +1 @@
1
+ import "@fastify/jwt";
@@ -0,0 +1,7 @@
1
+ export declare function mintPreviewToken(contentId: string, nowMs?: number): {
2
+ token: string;
3
+ expiresAt: string;
4
+ };
5
+ export declare function verifyPreviewToken(token: string, nowMs?: number): {
6
+ contentId: string;
7
+ } | null;
@@ -0,0 +1,76 @@
1
+ import { createHmac, timingSafeEqual } from "node:crypto";
2
+ import { previewTokenTtlSeconds } from "../config/preview.js";
3
+ // Stateless preview tokens (Session 116, Task 1). A deliberately separate
4
+ // code path from the admin JWT session (api-design.md: never conflate
5
+ // headless/short-lived tokens with logged-in admin sessions) — this is a
6
+ // signed-but-not-a-JWT format so it can never be replayed against a route
7
+ // that expects an admin JWT, or vice versa:
8
+ //
9
+ // base64url(JSON{contentId, exp}) + "." + base64url(hmacSignature)
10
+ //
11
+ // Exactly ONE dot. A real JWT is header.payload.signature (two dots) —
12
+ // splitting on "." and requiring exactly 2 segments rejects any JWT
13
+ // outright, by construction, with no explicit JWT-detection code needed.
14
+ //
15
+ // The signing key is derived per call from JWT_SECRET, never cached: tests
16
+ // toggle JWT_SECRET via process.env across calls in the same run, and
17
+ // caching would silently keep signing/verifying with a stale key.
18
+ const DERIVATION_LABEL = "velora-preview-token";
19
+ function derivedKey() {
20
+ const secret = process.env.JWT_SECRET;
21
+ if (!secret) {
22
+ throw new Error("JWT_SECRET must be set — see .env.example");
23
+ }
24
+ return createHmac("sha256", secret).update(DERIVATION_LABEL).digest();
25
+ }
26
+ function sign(payloadB64) {
27
+ return createHmac("sha256", derivedKey()).update(payloadB64).digest("base64url");
28
+ }
29
+ export function mintPreviewToken(contentId, nowMs = Date.now()) {
30
+ const exp = Math.floor(nowMs / 1000) + previewTokenTtlSeconds();
31
+ const payload = { contentId, exp };
32
+ const payloadB64 = Buffer.from(JSON.stringify(payload)).toString("base64url");
33
+ const signatureB64 = sign(payloadB64);
34
+ return {
35
+ token: `${payloadB64}.${signatureB64}`,
36
+ expiresAt: new Date(exp * 1000).toISOString(),
37
+ };
38
+ }
39
+ // Returns null for EVERY failure mode — malformed shape, bad signature,
40
+ // expired — with no distinction between them. Distinguishing would let a
41
+ // caller probe whether a guessed token is "close" (right shape, wrong sig)
42
+ // vs. garbage, which is exactly the kind of oracle a token verifier must
43
+ // not provide.
44
+ export function verifyPreviewToken(token, nowMs = Date.now()) {
45
+ const parts = token.split(".");
46
+ if (parts.length !== 2) {
47
+ return null;
48
+ }
49
+ const [payloadB64, signatureB64] = parts;
50
+ if (!payloadB64 || !signatureB64) {
51
+ return null;
52
+ }
53
+ const expectedSignatureB64 = sign(payloadB64);
54
+ const presented = Buffer.from(signatureB64, "base64url");
55
+ const expected = Buffer.from(expectedSignatureB64, "base64url");
56
+ // Length-check before timingSafeEqual: it throws on mismatched buffer
57
+ // lengths rather than returning false, and skipping this check would
58
+ // itself be a length side-channel.
59
+ if (presented.length !== expected.length || !timingSafeEqual(presented, expected)) {
60
+ return null;
61
+ }
62
+ let payload;
63
+ try {
64
+ payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
65
+ }
66
+ catch {
67
+ return null;
68
+ }
69
+ if (typeof payload?.contentId !== "string" || typeof payload?.exp !== "number") {
70
+ return null;
71
+ }
72
+ if (nowMs >= payload.exp * 1000) {
73
+ return null;
74
+ }
75
+ return { contentId: payload.contentId };
76
+ }
@@ -0,0 +1,16 @@
1
+ import type { FastifyRequest } from "fastify";
2
+ export declare function apiKeyValidityCacheSizeForTests(): number;
3
+ export declare function rateLimitKey(request: FastifyRequest): Promise<string>;
4
+ export declare function resolveGlobalTimeWindow(value: string | undefined): string;
5
+ export declare const rateLimitConfig: {
6
+ max: (_req: FastifyRequest, key: string) => number;
7
+ timeWindow: string;
8
+ keyGenerator: typeof rateLimitKey;
9
+ errorResponseBuilder: (_request: FastifyRequest, context: {
10
+ after: string;
11
+ statusCode: number;
12
+ }) => {
13
+ statusCode: number;
14
+ message: string;
15
+ };
16
+ };