@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,525 @@
1
+ import { createHash } from "node:crypto";
2
+ import { z } from "zod";
3
+ import { ApiErrorSchema, BundleUninstallConfirmationRequiredSchema, BundleUninstallSucceededSchema, LicenseRecheckResponseSchema, ManagedPluginListQuerySchema, ManagedPluginListResponseSchema, MarketplaceStatusResponseSchema, PluginIdParamsSchema, RestoreBodySchema, RestoreConfirmationRequiredSchema, RestoreSucceededSchema, SnapshotGroupListQuerySchema, SnapshotGroupListResponseSchema, UninstallBlockedByDocumentTypesSchema, UninstallConfirmationRequiredSchema, UninstallPluginBodySchema, UninstallSucceededSchema, } from "@velora-cms/api-schemas";
4
+ import { DocumentTypeInUseError } from "@velora-cms/content-service";
5
+ import { requireAdmin } from "../auth/require-auth.js";
6
+ import { getMarketplaceBaseUrl } from "../config/marketplace.js";
7
+ import { prisma } from "../db/prisma.js";
8
+ import { revalidateLicense } from "../marketplace/revalidate-license.js";
9
+ import { removePluginPackages } from "../plugins/plugin-package-store.js";
10
+ import { PluginDocumentTypesInUseError, PluginNotLoadedError, StructuredDataCleanupRequiresConfirmationError, } from "../plugins/plugin-registry-service.js";
11
+ import { listSnapshotGroups } from "../plugins/snapshot-groups.js";
12
+ import { BundleMemberBlockedError, BundleUninstallRequiresConfirmationError, uninstallBundle, } from "../plugins/uninstall-bundle.js";
13
+ // The ADMIN management surface (S135). Deliberately NOT under
14
+ // /api/plugins/... — the S89 dispatcher owns /api/plugins/:pluginId/*
15
+ // wildcards for plugin-contributed routes, and management routes there
16
+ // would shadow them.
17
+ //
18
+ // 204 responses declare `204: z.null()` and call reply.code(204).send(null).
19
+ // No existing route in this codebase returns 204 today (checked:
20
+ // document-types.ts/media.ts/api-keys.ts all return 200 with a body), so
21
+ // there's no precedent to match — this is fastify-type-provider-zod's own
22
+ // convention (createJsonSchemaTransform special-cases a `type: "null"`
23
+ // JSON schema for OpenAPI) plus the only shape that keeps the typed
24
+ // `reply.code(204)` callable at all: fastify-type-provider-zod's Reply
25
+ // type only accepts status codes present in the route's declared response
26
+ // map, so an UNDECLARED 204 fails to typecheck (`Argument of type '204'
27
+ // is not assignable...`). Sending a literal `null` is safe despite
28
+ // z.null() serializing it to the 4-byte string "null": Fastify's
29
+ // onSendEnd (reply.js) special-cases `statusCode === 204` unconditionally
30
+ // — it strips content-type/content-length and returns before the
31
+ // payload is ever written to the socket, regardless of what the
32
+ // serializer produced. Verified by reading node_modules/fastify's
33
+ // reply.js directly rather than assuming.
34
+ // S161 (S154 rider): closes the requestRestorePlan -> confirmRestore
35
+ // TOCTOU. The 409 hands the operator a plan and a hash of it; the confirm
36
+ // must carry that hash back, and we re-plan and re-hash before doing
37
+ // anything. If a snapshot landed in between, the plan the operator saw is
38
+ // no longer the plan we would execute, so we refuse and show the new one.
39
+ // The plan is sorted before hashing so an incidental ordering difference
40
+ // between two identical plans (planRestore already returns a
41
+ // tableName-sorted array, but nothing here should depend on that staying
42
+ // true) cannot read as a change. Hashed on tableName + snapshotGroupId
43
+ // only — those two fields are exactly what determines which snapshot rows
44
+ // restorePluginData reads for each table; groupTakenAt is display
45
+ // metadata derived from the group's own rows and carries no additional
46
+ // identity, so including it would make two plans that restore identically
47
+ // hash differently for no reason (e.g. if a group's takenAt were ever
48
+ // recomputed). Uses node:crypto's createHash directly — this is server
49
+ // code, not a Module-Federation-bundled plugin file, so the
50
+ // globalThis.crypto rule doesn't apply here.
51
+ function hashRestorePlan(plan) {
52
+ const canonical = plan
53
+ .map((entry) => `${entry.tableName}:${entry.snapshotGroupId}`)
54
+ .sort()
55
+ .join("|");
56
+ return createHash("sha256").update(canonical).digest("hex").slice(0, 32);
57
+ }
58
+ export const pluginManagementRoutes = async (app, opts) => {
59
+ const { adapter, structuredSnapshotService } = opts;
60
+ // S180: whether this instance has a marketplace to browse at all — the
61
+ // admin's Marketplace browse/detail screens gate on this once (at the
62
+ // section level) rather than inferring it from a load failure, which
63
+ // would conflate "no marketplace configured" with "marketplace
64
+ // configured but unreachable." Always registered (like every route in
65
+ // this file), even when the marketplace proxy routes themselves are
66
+ // structurally absent (app.ts) — this is the one endpoint an admin
67
+ // whose instance opted out via VELORA_MARKETPLACE_URL=off can still
68
+ // reach to learn that honestly.
69
+ app.get("/api/plugin-management/marketplace-status", {
70
+ onRequest: requireAdmin,
71
+ schema: {
72
+ response: {
73
+ 200: MarketplaceStatusResponseSchema,
74
+ 401: ApiErrorSchema,
75
+ 403: ApiErrorSchema,
76
+ },
77
+ },
78
+ }, async () => {
79
+ return { configured: getMarketplaceBaseUrl() !== undefined };
80
+ });
81
+ app.get("/api/plugin-management/plugins", {
82
+ onRequest: requireAdmin,
83
+ schema: {
84
+ querystring: ManagedPluginListQuerySchema,
85
+ response: {
86
+ 200: ManagedPluginListResponseSchema,
87
+ 401: ApiErrorSchema,
88
+ 403: ApiErrorSchema,
89
+ },
90
+ },
91
+ }, async (request) => {
92
+ const { cursor, limit } = request.query;
93
+ const rows = await prisma.pluginRegistry.findMany({
94
+ orderBy: { id: "asc" },
95
+ take: limit,
96
+ ...(cursor ? { cursor: { id: cursor }, skip: 1 } : {}),
97
+ });
98
+ // One extra query, not N+1: a single findMany keyed on the page's
99
+ // pluginIds, then map by pluginId below. No @relation exists between
100
+ // PluginRegistry and PluginLicense (same reasoning as
101
+ // PluginMigrationRecord/PluginDocumentType — tracked by pluginId,
102
+ // not a foreign key), so this can't be a Prisma `include`.
103
+ const licenses = await prisma.pluginLicense.findMany({
104
+ where: { pluginId: { in: rows.map((row) => row.pluginId) } },
105
+ // Never select `key` here — it's the plaintext license key
106
+ // (kept plaintext for S139's re-validation ping, see schema.prisma).
107
+ // This list endpoint needs the status plus the three fields the
108
+ // admin's license cell (S139 Task 5) reads to render an honest
109
+ // state without a second per-row request — still no `key`.
110
+ select: {
111
+ pluginId: true,
112
+ status: true,
113
+ lastReason: true,
114
+ lastValidatedAt: true,
115
+ lastAttemptError: true,
116
+ boundDomain: true,
117
+ },
118
+ });
119
+ const licenseByPluginId = new Map(licenses.map((license) => [license.pluginId, license]));
120
+ const items = rows.map((row) => {
121
+ const license = licenseByPluginId.get(row.pluginId);
122
+ return {
123
+ pluginId: row.pluginId,
124
+ name: row.name,
125
+ type: row.type,
126
+ version: row.version,
127
+ trustTier: row.trustTier,
128
+ // The manifest contract (plugin-sdk's databaseCompatibility) only
129
+ // ever produces "simple" | "structured" — there is no "none".
130
+ storageType: row.storageType,
131
+ enabled: row.enabled,
132
+ licenseStatus: license?.status ?? null,
133
+ licenseReason: license?.lastReason ?? null,
134
+ licenseLastValidatedAt: license?.lastValidatedAt?.toISOString() ?? null,
135
+ licenseLastAttemptError: license?.lastAttemptError ?? null,
136
+ boundDomain: license?.boundDomain ?? null,
137
+ };
138
+ });
139
+ const nextCursor = items.length === limit ? (rows[rows.length - 1]?.id ?? null) : null;
140
+ return { items, nextCursor };
141
+ });
142
+ app.post("/api/plugin-management/plugins/:pluginId/activate", {
143
+ onRequest: requireAdmin,
144
+ schema: {
145
+ params: PluginIdParamsSchema,
146
+ response: {
147
+ 204: z.null(),
148
+ 401: ApiErrorSchema,
149
+ 403: ApiErrorSchema,
150
+ 404: ApiErrorSchema,
151
+ 409: ApiErrorSchema,
152
+ },
153
+ },
154
+ }, async (request, reply) => {
155
+ const { pluginId } = request.params;
156
+ const row = await prisma.pluginRegistry.findUnique({ where: { pluginId } });
157
+ if (!row)
158
+ return reply.code(404).send({ message: `plugin "${pluginId}" is not installed` });
159
+ // A hard-revoked plugin must not be toggleable back on. domain_mismatch
160
+ // and an unreachable license server both fall through to activation
161
+ // (S139: warn, don't block; fail open). That fail-open promise has to
162
+ // hold even when the license CHECK ITSELF blows up (a Prisma pool
163
+ // exhaustion, a lock timeout, anything revalidateLicense() doesn't
164
+ // already catch internally) — an ordinary database hiccup here must
165
+ // never read as "revoked" and block an admin from enabling a plugin.
166
+ // Same log-and-continue shape as license-revalidation-job.ts's sweep.
167
+ const marketplaceBaseUrl = getMarketplaceBaseUrl();
168
+ if (marketplaceBaseUrl) {
169
+ try {
170
+ const outcome = await revalidateLicense({ prisma, registry: app.pluginRegistry, marketplaceBaseUrl }, pluginId);
171
+ if (outcome.checked && outcome.status === "revoked") {
172
+ return reply.code(403).send({ message: `license check failed: ${outcome.reason}` });
173
+ }
174
+ }
175
+ catch (error) {
176
+ console.error(`[license] revalidation threw for ${pluginId} during activation — failing open`, error);
177
+ }
178
+ }
179
+ try {
180
+ await app.pluginRegistry.activate(pluginId);
181
+ }
182
+ catch (error) {
183
+ if (error instanceof PluginNotLoadedError) {
184
+ return reply.code(409).send({ message: error.message });
185
+ }
186
+ throw error;
187
+ }
188
+ // S153 recovery symmetry: re-licensing a revoked bundle must bring
189
+ // the SET back, not just the functionless bundle row. Best-effort
190
+ // per member — one member's failing onActivate never blocks the
191
+ // others or the 204 (the bundle itself did activate).
192
+ if (row.type === "bundle") {
193
+ const members = await prisma.pluginBundleMember.findMany({
194
+ where: { bundlePluginId: pluginId },
195
+ orderBy: { position: "asc" },
196
+ });
197
+ for (const member of members) {
198
+ try {
199
+ await app.pluginRegistry.activate(member.memberPluginId);
200
+ }
201
+ catch (error) {
202
+ request.log.warn({ err: error }, `bundle member ${member.memberPluginId} could not be re-activated`);
203
+ }
204
+ }
205
+ }
206
+ return reply.code(204).send(null);
207
+ });
208
+ app.post("/api/plugin-management/plugins/:pluginId/license/recheck", {
209
+ onRequest: requireAdmin,
210
+ schema: {
211
+ params: PluginIdParamsSchema,
212
+ response: {
213
+ 200: LicenseRecheckResponseSchema,
214
+ 401: ApiErrorSchema,
215
+ 403: ApiErrorSchema,
216
+ 404: ApiErrorSchema,
217
+ },
218
+ },
219
+ }, async (request, reply) => {
220
+ const { pluginId } = request.params;
221
+ const row = await prisma.pluginRegistry.findUnique({ where: { pluginId } });
222
+ if (!row)
223
+ return reply.code(404).send({ message: `plugin "${pluginId}" is not installed` });
224
+ const marketplaceBaseUrl = getMarketplaceBaseUrl();
225
+ if (!marketplaceBaseUrl) {
226
+ // revalidateLicense() requires a marketplaceBaseUrl to call, so this
227
+ // can't distinguish "no license" from "license, but unchecked" the
228
+ // way the branch below does — it can still tell whether there's
229
+ // anything on record worth reporting on at all.
230
+ const license = await prisma.pluginLicense.findUnique({ where: { pluginId } });
231
+ if (!license)
232
+ return reply.code(404).send({ message: `plugin "${pluginId}" has no license on record` });
233
+ return {
234
+ pluginId,
235
+ checked: false,
236
+ status: null,
237
+ reason: "the marketplace is not configured (VELORA_MARKETPLACE_URL is unset or \"off\") — skipping the license check",
238
+ // L1: this branch never actually checked anything, so — same as
239
+ // the fail-open catch branch below and the outcome.checked===false
240
+ // branch further down — the row's own lastValidatedAt must ride
241
+ // along unchanged. A hardcoded null here previously made a
242
+ // perfectly healthy license flip to "unreachable" in the admin
243
+ // UI (isLicenseStale treats a null lastValidatedAt as stale) the
244
+ // moment someone clicked Re-check with no marketplace configured.
245
+ lastValidatedAt: license.lastValidatedAt?.toISOString() ?? null,
246
+ };
247
+ }
248
+ // Same fail-open contract as the activate handler above: a Prisma
249
+ // failure inside revalidateLicense() (not the upstream-unreachable
250
+ // case it already catches itself) must report as an unchecked result,
251
+ // not a 500 — this is a diagnostic endpoint, and "the check itself
252
+ // errored" is exactly what checked:false already means to callers.
253
+ let outcome;
254
+ try {
255
+ outcome = await revalidateLicense({ prisma, registry: app.pluginRegistry, marketplaceBaseUrl }, pluginId);
256
+ }
257
+ catch (error) {
258
+ console.error(`[license] revalidation threw for ${pluginId} during a manual recheck`, error);
259
+ const license = await prisma.pluginLicense.findUnique({ where: { pluginId } });
260
+ if (!license)
261
+ return reply.code(404).send({ message: `plugin "${pluginId}" has no license on record` });
262
+ return {
263
+ pluginId,
264
+ checked: false,
265
+ status: null,
266
+ reason: `the license check itself failed: ${error instanceof Error ? error.message : String(error)}`,
267
+ lastValidatedAt: license.lastValidatedAt?.toISOString() ?? null,
268
+ };
269
+ }
270
+ if (!outcome.checked && "missingLicense" in outcome && outcome.missingLicense) {
271
+ return reply.code(404).send({ message: `plugin "${pluginId}" has no license on record` });
272
+ }
273
+ const license = await prisma.pluginLicense.findUnique({ where: { pluginId } });
274
+ return {
275
+ pluginId,
276
+ checked: outcome.checked,
277
+ status: outcome.checked ? outcome.status : null,
278
+ reason: outcome.checked ? (outcome.status === "valid" ? null : outcome.reason) : outcome.reason,
279
+ lastValidatedAt: license?.lastValidatedAt?.toISOString() ?? null,
280
+ };
281
+ });
282
+ app.post("/api/plugin-management/plugins/:pluginId/deactivate", {
283
+ onRequest: requireAdmin,
284
+ schema: {
285
+ params: PluginIdParamsSchema,
286
+ response: {
287
+ 204: z.null(),
288
+ 401: ApiErrorSchema,
289
+ 403: ApiErrorSchema,
290
+ 404: ApiErrorSchema,
291
+ },
292
+ },
293
+ }, async (request, reply) => {
294
+ const { pluginId } = request.params;
295
+ const row = await prisma.pluginRegistry.findUnique({ where: { pluginId } });
296
+ if (!row)
297
+ return reply.code(404).send({ message: `plugin "${pluginId}" is not installed` });
298
+ await app.pluginRegistry.deactivate(pluginId);
299
+ // S153: manual bundle deactivation cascades like revocation does —
300
+ // reverse install order, best-effort per member.
301
+ if (row.type === "bundle") {
302
+ const members = await prisma.pluginBundleMember.findMany({
303
+ where: { bundlePluginId: pluginId },
304
+ orderBy: { position: "desc" },
305
+ });
306
+ for (const member of members) {
307
+ try {
308
+ await app.pluginRegistry.deactivate(member.memberPluginId);
309
+ }
310
+ catch (error) {
311
+ request.log.warn({ err: error }, `bundle member ${member.memberPluginId} could not be deactivated`);
312
+ }
313
+ }
314
+ }
315
+ return reply.code(204).send(null);
316
+ });
317
+ app.delete("/api/plugin-management/plugins/:pluginId", {
318
+ onRequest: requireAdmin,
319
+ schema: {
320
+ params: PluginIdParamsSchema,
321
+ body: UninstallPluginBodySchema,
322
+ response: {
323
+ 200: UninstallSucceededSchema.or(BundleUninstallSucceededSchema),
324
+ 401: ApiErrorSchema,
325
+ 403: ApiErrorSchema,
326
+ 404: ApiErrorSchema,
327
+ 409: UninstallConfirmationRequiredSchema.or(BundleUninstallConfirmationRequiredSchema)
328
+ .or(UninstallBlockedByDocumentTypesSchema)
329
+ .or(ApiErrorSchema),
330
+ },
331
+ },
332
+ }, async (request, reply) => {
333
+ const { pluginId } = request.params;
334
+ const row = await prisma.pluginRegistry.findUnique({ where: { pluginId } });
335
+ if (!row)
336
+ return reply.code(404).send({ message: `plugin "${pluginId}" is not installed` });
337
+ if (row.type === "bundle") {
338
+ // S152: bundle uninstall cascades to the members THIS bundle
339
+ // installed (skip-don't-claim members were never recorded and are
340
+ // untouched). uninstallBundle preflights the whole set before
341
+ // removing anything and does the per-plugin route-level cleanup
342
+ // itself, so this branch returns without the single-plugin cleanup
343
+ // lines below.
344
+ let memberSnapshots;
345
+ try {
346
+ ({ memberSnapshots } = await uninstallBundle({ prisma, registry: app.pluginRegistry, adapter }, pluginId, { confirmStructuredDataCleanup: request.body?.confirmStructuredDataCleanup === true }));
347
+ }
348
+ catch (error) {
349
+ if (error instanceof BundleUninstallRequiresConfirmationError) {
350
+ return reply.code(409).send({
351
+ requiresConfirmation: true,
352
+ memberSnapshots: error.memberSnapshots,
353
+ message: error.message,
354
+ });
355
+ }
356
+ // F1 QA fix wave: uninstallBundle()'s own preflight 1 (above,
357
+ // same file) still throws the bare per-id DocumentTypeInUseError
358
+ // directly from DocumentTypeService — that's what normally
359
+ // surfaces here. PluginDocumentTypesInUseError is the cascade's
360
+ // OWN per-member registry.uninstall() call re-checking the same
361
+ // guard and (TOCTOU: content added after preflight 1 passed)
362
+ // finding it blocked after all — falling back to the plain
363
+ // message, same as the single-plugin path did before this fix
364
+ // wave, rather than an uncaught 500.
365
+ if (error instanceof BundleMemberBlockedError ||
366
+ error instanceof DocumentTypeInUseError ||
367
+ error instanceof PluginDocumentTypesInUseError) {
368
+ return reply.code(409).send({ message: error.message });
369
+ }
370
+ throw error;
371
+ }
372
+ return reply.code(200).send({ memberSnapshots });
373
+ }
374
+ let snapshotGroupId;
375
+ try {
376
+ ({ snapshotGroupId } = await app.pluginRegistry.uninstall(pluginId, {
377
+ confirmStructuredDataCleanup: request.body?.confirmStructuredDataCleanup === true,
378
+ }));
379
+ }
380
+ catch (error) {
381
+ if (error instanceof StructuredDataCleanupRequiresConfirmationError) {
382
+ // The snapshot is ALREADY taken (S87's ordering) — retrying
383
+ // with confirmation is always safe.
384
+ return reply.code(409).send({
385
+ requiresConfirmation: true,
386
+ snapshotGroupId: error.snapshotGroupId,
387
+ message: error.message,
388
+ });
389
+ }
390
+ // F1 QA fix wave: uninstall()'s document-type guard now aggregates
391
+ // every blocked tracked document type into PluginDocumentTypesInUseError
392
+ // (named, with a content count where the guard already has one) —
393
+ // checked before the plain DocumentTypeInUseError below, which
394
+ // stays as a fallback only content-service's own single-id error
395
+ // (not reachable from this call site anymore, but kept for safety
396
+ // should that ever change) would hit. Re-throw only unknown shapes.
397
+ if (error instanceof PluginDocumentTypesInUseError) {
398
+ return reply.code(409).send({
399
+ message: error.message,
400
+ blockedBy: error.blockers.map((blocker) => ({
401
+ documentType: blocker.documentType,
402
+ contentCount: blocker.contentCount,
403
+ })),
404
+ });
405
+ }
406
+ if (error instanceof DocumentTypeInUseError) {
407
+ return reply.code(409).send({ message: error.message });
408
+ }
409
+ throw error;
410
+ }
411
+ // Success: the route (not the service) orchestrates cleanup — the
412
+ // S122 flag wired at last, plus in-process detach so the plugin's
413
+ // contributed routes stop answering without a restart. M3 (S138
414
+ // hardening review): PluginRegistryService.uninstall() never touched
415
+ // PluginLicense — without this, a plaintext license key survived
416
+ // uninstalling the very plugin it licensed, quietly turning "one
417
+ // license per installed plugin" into "per plugin ever installed."
418
+ // deleteMany (not delete): most plugins were never licensed at all,
419
+ // and deleteMany is a correct no-op for that case rather than a
420
+ // P2025 "record not found" this route would then have to catch.
421
+ removePluginPackages(pluginId);
422
+ app.pluginRegistry.detachManifest(pluginId);
423
+ await prisma.pluginLicense.deleteMany({ where: { pluginId } });
424
+ return reply.code(200).send({ snapshotGroupId });
425
+ });
426
+ // S154 Task 4: a plugin's structured-data snapshot GROUPS (derived, see
427
+ // snapshot-groups.ts). Deliberately keyed off SNAPSHOT rows, not the
428
+ // registry row: uninstall deletes the PluginRegistry row but never the
429
+ // PluginStructuredSnapshot rows, so an uninstalled plugin's groups must
430
+ // still list — that's exactly when a snapshot history matters most. The
431
+ // 404 below therefore fires ONLY when there is neither a registry row
432
+ // NOR any snapshot rows at all — a plugin this site has never heard of.
433
+ app.get("/api/plugin-management/plugins/:pluginId/snapshot-groups", {
434
+ onRequest: requireAdmin,
435
+ schema: {
436
+ params: PluginIdParamsSchema,
437
+ querystring: SnapshotGroupListQuerySchema,
438
+ response: {
439
+ 200: SnapshotGroupListResponseSchema,
440
+ 401: ApiErrorSchema,
441
+ 403: ApiErrorSchema,
442
+ 404: ApiErrorSchema,
443
+ },
444
+ },
445
+ }, async (request, reply) => {
446
+ const { pluginId } = request.params;
447
+ const { cursor, limit } = request.query;
448
+ const registryRow = await prisma.pluginRegistry.findUnique({ where: { pluginId } });
449
+ if (!registryRow) {
450
+ const anySnapshot = await prisma.pluginStructuredSnapshot.findFirst({
451
+ where: { pluginId },
452
+ select: { id: true },
453
+ });
454
+ if (!anySnapshot) {
455
+ return reply.code(404).send({ message: `plugin "${pluginId}" is not installed and has no snapshot history` });
456
+ }
457
+ }
458
+ const { items, nextCursor } = await listSnapshotGroups(prisma, pluginId, { cursor, limit });
459
+ return { pluginId, items, nextCursor };
460
+ });
461
+ // S154 Task 5: the UNION restore — the mockup's "restore" action.
462
+ // Unlike restoreSnapshot(groupId) (S87, untouched, still keyed to one
463
+ // specific group), this drives StructuredSnapshotService.restorePluginData,
464
+ // which replaces each of the plugin's tables with the newest available
465
+ // snapshot copy of that table across ALL of its groups. 404 fires only
466
+ // when the plugin has no snapshot rows at all — unlike the GET route
467
+ // above, a live PluginRegistry row is neither required nor consulted:
468
+ // restore only ever needs the snapshot history, and the collision case
469
+ // this exists for (restoring onto a reinstalled plugin) has a registry
470
+ // row throughout.
471
+ app.post("/api/plugin-management/plugins/:pluginId/snapshot-groups/restore", {
472
+ onRequest: requireAdmin,
473
+ schema: {
474
+ params: PluginIdParamsSchema,
475
+ body: RestoreBodySchema,
476
+ response: {
477
+ 200: RestoreSucceededSchema,
478
+ 401: ApiErrorSchema,
479
+ 403: ApiErrorSchema,
480
+ 404: ApiErrorSchema,
481
+ 409: RestoreConfirmationRequiredSchema,
482
+ },
483
+ },
484
+ }, async (request, reply) => {
485
+ const { pluginId } = request.params;
486
+ const anySnapshot = await prisma.pluginStructuredSnapshot.findFirst({
487
+ where: { pluginId },
488
+ select: { id: true },
489
+ });
490
+ if (!anySnapshot) {
491
+ return reply.code(404).send({ message: `plugin "${pluginId}" has no snapshot history to restore from` });
492
+ }
493
+ // Unconfirmed: return the plan (plus a hash of it), touch nothing.
494
+ // request.body is `null` for a genuinely absent payload (Fastify's
495
+ // validator substitution, same as UninstallPluginBodySchema above),
496
+ // so this optional-chain read tolerates both null and undefined.
497
+ if (request.body?.confirmRestore !== true) {
498
+ const plan = await structuredSnapshotService.planRestore(pluginId);
499
+ return reply.code(409).send({
500
+ requiresConfirmation: true,
501
+ plan,
502
+ planHash: hashRestorePlan(plan),
503
+ message: `restoring "${pluginId}" will replace ${plan.length} table(s) with the newest available snapshot of each — confirm to proceed`,
504
+ });
505
+ }
506
+ // Confirmed: re-plan and re-hash before touching anything. request.body
507
+ // is guaranteed non-null here (confirmRestore === true just matched),
508
+ // but request.body.planHash is still only optional at the schema
509
+ // level (see RestoreBodySchema) — a confirm sent without one simply
510
+ // can never match the freshly computed hash below, so it takes the
511
+ // same refusal path as a genuinely stale one.
512
+ const freshPlan = await structuredSnapshotService.planRestore(pluginId);
513
+ const freshHash = hashRestorePlan(freshPlan);
514
+ if (request.body.planHash !== freshHash) {
515
+ return reply.code(409).send({
516
+ requiresConfirmation: true,
517
+ plan: freshPlan,
518
+ planHash: freshHash,
519
+ message: `the restore plan for "${pluginId}" has changed since it was last reviewed — check the refreshed plan below and confirm again`,
520
+ });
521
+ }
522
+ const { preRestoreSnapshotGroupId, restoredTables } = await structuredSnapshotService.restorePluginData(pluginId);
523
+ return { preRestoreSnapshotGroupId, restoredTables };
524
+ });
525
+ };
@@ -0,0 +1,8 @@
1
+ import type { FastifyPluginAsyncZod } from "fastify-type-provider-zod";
2
+ import type { DatabaseAdapter } from "@velora-cms/db-adapter";
3
+ import type { SimpleStorageAPI } from "../plugins/simple-storage-api.js";
4
+ export interface PluginStorageRouteOptions {
5
+ adapter: DatabaseAdapter;
6
+ simpleStorageAPI: SimpleStorageAPI;
7
+ }
8
+ export declare const pluginStorageRoutes: FastifyPluginAsyncZod<PluginStorageRouteOptions>;
@@ -0,0 +1,104 @@
1
+ import { ApiErrorSchema, PluginFieldValueResponseSchema, PluginStorageParamsSchema, SetPluginFieldValueRequestSchema, } from "@velora-cms/api-schemas";
2
+ import { requireAuth } from "../auth/require-auth.js";
3
+ import { PluginNotActiveError, PluginNotInstalledError } from "../plugins/plugin-registry-service.js";
4
+ // SimpleStorageAPI's HTTP surface (Session 68) — the callable-from-the-
5
+ // browser layer Session 70's usePluginValue() wires the SDK to.
6
+ // /api/plugins/:pluginId/... namespacing matches api-design.md's
7
+ // plugin-route convention (avoids two plugins colliding on a path).
8
+ //
9
+ // S163 closes the S110 flag: BOTH verbs now require auth. The previous
10
+ // comment here claimed the public GET matched "every other route in this
11
+ // codebase (data-types.ts, content.ts)" — that was false, and is the reason
12
+ // this comment was rewritten once already. State only what is true and
13
+ // verifiable, with a file:line for any claim about another file, so it
14
+ // goes stale visibly instead of silently:
15
+ // - content.ts requires auth on EVERY route, including reads
16
+ // (apps/server/src/routes/content.ts:67,90,141,162,193,215).
17
+ // - data-types.ts does NOT: its two GETs
18
+ // (apps/server/src/routes/data-types.ts:50-51 list, :68-69 by id) carry
19
+ // no `onRequest` and there is no route-family-wide auth hook — only its
20
+ // POST/PATCH mutations require auth. The previous comment's citation of
21
+ // data-types.ts was itself wrong.
22
+ // - This was NOT the CMS's only unauthenticated read of internal content
23
+ // data — GET /api/media and GET /api/media/:id
24
+ // (apps/server/src/routes/media.ts:65-66, 84-85) are unauthenticated
25
+ // and return media items each carrying a fresh presigned downloadUrl,
26
+ // and GET /api/document-types (apps/server/src/routes/document-types.ts:146-147),
27
+ // GET /api/templates (apps/server/src/routes/templates.ts:40-41),
28
+ // GET /api/themes (apps/server/src/routes/themes.ts:15-16), and
29
+ // GET /api/plugins (apps/server/src/routes/plugins.ts:10-11) are also
30
+ // open. None of that is fixed here — it's a separate surface with its
31
+ // own consumers, recorded as a pre-deploy backlog item rather than
32
+ // closed unexamined.
33
+ // getContentNode does not filter by publish state, so anyone holding a
34
+ // content UUID could read plugin field values on DRAFT content — that part
35
+ // of the original rationale for closing this flag was accurate. Nothing
36
+ // real broke by closing it: the only caller is the SDK's
37
+ // SimpleStorageValueProvider, which fetches through usePluginApi() —
38
+ // admin-ui's authFetch — and already sends a session.
39
+ export const pluginStorageRoutes = async (app, opts) => {
40
+ const { adapter, simpleStorageAPI } = opts;
41
+ app.get("/api/plugins/:pluginId/storage/:contentId/:fieldId", {
42
+ onRequest: requireAuth,
43
+ schema: {
44
+ params: PluginStorageParamsSchema,
45
+ response: {
46
+ 200: PluginFieldValueResponseSchema,
47
+ 401: ApiErrorSchema,
48
+ 403: ApiErrorSchema,
49
+ 404: ApiErrorSchema,
50
+ },
51
+ },
52
+ }, async (request, reply) => {
53
+ const { pluginId, contentId, fieldId } = request.params;
54
+ const node = await adapter.getContentNode(contentId);
55
+ if (!node) {
56
+ return reply.code(404).send({ message: `No content node found with id ${contentId}` });
57
+ }
58
+ try {
59
+ const value = await simpleStorageAPI.get(contentId, fieldId, pluginId);
60
+ return { value };
61
+ }
62
+ catch (err) {
63
+ if (err instanceof PluginNotInstalledError) {
64
+ return reply.code(404).send({ message: err.message });
65
+ }
66
+ if (err instanceof PluginNotActiveError) {
67
+ return reply.code(403).send({ message: err.message });
68
+ }
69
+ throw err;
70
+ }
71
+ });
72
+ app.put("/api/plugins/:pluginId/storage/:contentId/:fieldId", {
73
+ onRequest: requireAuth,
74
+ schema: {
75
+ params: PluginStorageParamsSchema,
76
+ body: SetPluginFieldValueRequestSchema,
77
+ response: {
78
+ 200: PluginFieldValueResponseSchema,
79
+ 401: ApiErrorSchema,
80
+ 403: ApiErrorSchema,
81
+ 404: ApiErrorSchema,
82
+ },
83
+ },
84
+ }, async (request, reply) => {
85
+ const { pluginId, contentId, fieldId } = request.params;
86
+ const node = await adapter.getContentNode(contentId);
87
+ if (!node) {
88
+ return reply.code(404).send({ message: `No content node found with id ${contentId}` });
89
+ }
90
+ try {
91
+ await simpleStorageAPI.set(contentId, fieldId, pluginId, request.body.value);
92
+ return { value: request.body.value };
93
+ }
94
+ catch (err) {
95
+ if (err instanceof PluginNotInstalledError) {
96
+ return reply.code(404).send({ message: err.message });
97
+ }
98
+ if (err instanceof PluginNotActiveError) {
99
+ return reply.code(403).send({ message: err.message });
100
+ }
101
+ throw err;
102
+ }
103
+ });
104
+ };
@@ -0,0 +1,2 @@
1
+ import type { FastifyPluginAsyncZod } from "fastify-type-provider-zod";
2
+ export declare const pluginRoutes: FastifyPluginAsyncZod;