@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.
- package/LICENSE +202 -0
- package/README.md +65 -0
- package/dist/admin-bundle/assets/__virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare__react__loadShare__.js_commonjs-proxy-Ng0fOpdU.js +1 -0
- 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
- package/dist/admin-bundle/assets/_virtual_mf-localSharedImportMap___mfe_internal__velora_admin__mf_owner__1-DOKdWYyj.js +1 -0
- 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
- package/dist/admin-bundle/assets/_virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare__react__loadShare__.js-BuUBskgj.js +9 -0
- 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
- 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
- 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
- package/dist/admin-bundle/assets/content-trash.schema-DfXHkC-w.js +72 -0
- package/dist/admin-bundle/assets/hostInit--C4xK6nj.js +1 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-400-normal-CyU-ddYS.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-400-normal-EA1o-u_Q.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-500-normal-C4MQITzh.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-500-normal-DPXcppbm.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-600-normal-0pRdybE_.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-arabic-600-normal-MJZ0liF5.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-400-normal-Bo5KPYvw.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-400-normal-CFZh1unN.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-500-normal-BletK1Hp.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-500-normal-Cd6jVIg7.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-600-normal-Dk5eISD_.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-600-normal-KrqB56Mw.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-400-normal-BeVLg08x.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-400-normal-BpLfq-63.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-500-normal-Ck_W4G78.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-500-normal-eJxPnL1P.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-600-normal-DT29Q8HW.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-arabic-latin-ext-600-normal-DjDAeNQD.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-400-normal-BTotfTJu.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-400-normal-DZqxrq2p.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-500-normal-ByOcLdNv.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-500-normal-CocWQlwt.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-600-normal-71GNu3SW.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-600-normal-BGq0mW3O.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-400-normal-Dsrv2Tcn.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-400-normal-g30qAdWV.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-500-normal-Cs5J6C77.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-500-normal-DB5PtV2g.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-600-normal-Bz0x94Yp.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-cyrillic-ext-600-normal-DUMzJB7m.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-greek-400-normal-D9ESIMu3.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-greek-400-normal-_efipK4i.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-greek-500-normal-CuWXN6rf.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-greek-500-normal-JMMifIXV.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-greek-600-normal-D-CqTdkO.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-greek-600-normal-DzTrcv_p.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-400-normal-C5H60-Va.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-400-normal-RBey6euL.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-500-normal-D0aIdm-b.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-500-normal-DakdToA3.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-600-normal-DIrixKbi.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-latin-ext-600-normal-DOrvGEcy.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-400-normal-DG4YqDda.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-400-normal-fK1oJ5dG.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-500-normal-BEb3_waV.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-500-normal-e4dixQRQ.woff2 +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-600-normal-DgdngZtN.woff +0 -0
- package/dist/admin-bundle/assets/ibm-plex-sans-vietnamese-600-normal-DpPYBSTl.woff2 +0 -0
- package/dist/admin-bundle/assets/index-BIAF0ShP.js +542 -0
- package/dist/admin-bundle/assets/index-DhMY-0sC.js +148 -0
- package/dist/admin-bundle/assets/index-DnZV_gsg.css +1 -0
- package/dist/admin-bundle/assets/index-Do71v6NZ.js +7 -0
- package/dist/admin-bundle/assets/literata-cyrillic-ext-opsz-italic-5yUt4G_9.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-cyrillic-ext-opsz-normal-C5tfwALZ.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-cyrillic-opsz-italic-Bj0ndbyp.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-cyrillic-opsz-normal-DgmypKXX.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-greek-ext-opsz-italic-DFpO5j-Z.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-greek-ext-opsz-normal-DGm64vGR.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-greek-opsz-italic-CHERNRFq.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-greek-opsz-normal-DFglUmAd.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-latin-ext-opsz-italic-4y8iAqqs.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-latin-ext-opsz-normal-STiTPs0F.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-latin-opsz-italic-CJ93D38p.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-latin-opsz-normal-BHZsWIlc.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-vietnamese-opsz-italic-BdcrZMcO.woff2 +0 -0
- package/dist/admin-bundle/assets/literata-vietnamese-opsz-normal-Gqw5JQlq.woff2 +0 -0
- package/dist/admin-bundle/assets/mf-entry-bootstrap-0-aef015cd.js +41 -0
- package/dist/admin-bundle/assets/remoteEntry-CNyN68bc.js +14 -0
- package/dist/admin-bundle/assets/virtualExposes-BDCSo5OS.js +1 -0
- package/dist/admin-bundle/assets/vite-preload-helper-Cgledu0r.js +1 -0
- package/dist/admin-bundle/index.html +16 -0
- package/dist/app.d.ts +8 -0
- package/dist/app.js +252 -0
- package/dist/auth/api-content-author.d.ts +2 -0
- package/dist/auth/api-content-author.js +27 -0
- package/dist/auth/api-keys.d.ts +7 -0
- package/dist/auth/api-keys.js +85 -0
- package/dist/auth/jwt-payload.d.ts +16 -0
- package/dist/auth/jwt-payload.js +1 -0
- package/dist/auth/preview-tokens.d.ts +7 -0
- package/dist/auth/preview-tokens.js +76 -0
- package/dist/auth/rate-limit.d.ts +16 -0
- package/dist/auth/rate-limit.js +283 -0
- package/dist/auth/require-auth.d.ts +4 -0
- package/dist/auth/require-auth.js +28 -0
- package/dist/auth/route-rate-limits.d.ts +31 -0
- package/dist/auth/route-rate-limits.js +155 -0
- package/dist/auth/tokens.d.ts +15 -0
- package/dist/auth/tokens.js +38 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +6 -0
- package/dist/boot/core-migration-gate.d.ts +12 -0
- package/dist/boot/core-migration-gate.js +52 -0
- package/dist/boot/resolve-boot-mode.d.ts +6 -0
- package/dist/boot/resolve-boot-mode.js +206 -0
- package/dist/bundled-templates/com.velora.blog/manifest.json +39 -0
- package/dist/bundled-templates/com.velora.blog/package.tar.gz +0 -0
- package/dist/bundled-templates/com.velora.blog/sample-content.json +14 -0
- package/dist/config/admin-ui.d.ts +1 -0
- package/dist/config/admin-ui.js +10 -0
- package/dist/config/cors.d.ts +7 -0
- package/dist/config/cors.js +53 -0
- package/dist/config/env-file-path.d.ts +2 -0
- package/dist/config/env-file-path.js +22 -0
- package/dist/config/graphql-limits.d.ts +2 -0
- package/dist/config/graphql-limits.js +26 -0
- package/dist/config/headless.d.ts +1 -0
- package/dist/config/headless.js +8 -0
- package/dist/config/license-revalidation.d.ts +2 -0
- package/dist/config/license-revalidation.js +16 -0
- package/dist/config/load-env.d.ts +1 -0
- package/dist/config/load-env.js +53 -0
- package/dist/config/marketplace.d.ts +11 -0
- package/dist/config/marketplace.js +19 -0
- package/dist/config/preview.d.ts +3 -0
- package/dist/config/preview.js +39 -0
- package/dist/config/public-docs.d.ts +1 -0
- package/dist/config/public-docs.js +9 -0
- package/dist/config/site.d.ts +7 -0
- package/dist/config/site.js +12 -0
- package/dist/config/trust-proxy.d.ts +2 -0
- package/dist/config/trust-proxy.js +42 -0
- package/dist/data-types/validate-data-type-settings.d.ts +1 -0
- package/dist/data-types/validate-data-type-settings.js +33 -0
- package/dist/db/content-db.d.ts +16 -0
- package/dist/db/content-db.js +109 -0
- package/dist/db/core-migrations/index.d.ts +14 -0
- package/dist/db/core-migrations/index.js +184 -0
- package/dist/db/core-migrations/steps.d.ts +4 -0
- package/dist/db/core-migrations/steps.js +66 -0
- package/dist/db/load-prisma-client.d.ts +2 -0
- package/dist/db/load-prisma-client.js +44 -0
- package/dist/db/prisma.d.ts +3 -0
- package/dist/db/prisma.js +67 -0
- package/dist/db/resolve-client-dir.d.ts +2 -0
- package/dist/db/resolve-client-dir.js +83 -0
- package/dist/docs/filter-public-api-spec.d.ts +2 -0
- package/dist/docs/filter-public-api-spec.js +52 -0
- package/dist/docs-bundle/404.html +41 -0
- package/dist/docs-bundle/_astro/MobileTableOfContents.astro_astro_type_script_index_0_lang.BcSo_yiZ.js +1 -0
- package/dist/docs-bundle/_astro/Search.astro_astro_type_script_index_0_lang.CavldbAs.js +1 -0
- package/dist/docs-bundle/_astro/TableOfContents.astro_astro_type_script_index_0_lang.Csloo1VZ.js +1 -0
- package/dist/docs-bundle/_astro/common.DTXA7sXM.css +3 -0
- package/dist/docs-bundle/_astro/ec.0vx5m.js +3 -0
- package/dist/docs-bundle/_astro/ec.w36nc.css +1 -0
- package/dist/docs-bundle/_astro/page.Dwipeu-R.js +1 -0
- package/dist/docs-bundle/_astro/print.ehPL0gv-.css +1 -0
- package/dist/docs-bundle/_astro/starlight-toc.CTCQXEge.js +1 -0
- package/dist/docs-bundle/_astro/ui-core.D-7Scnhs.js +2 -0
- package/dist/docs-bundle/favicon.svg +1 -0
- package/dist/docs-bundle/index.html +1 -0
- package/dist/docs-bundle/pagefind/fragment/en_18a36d7.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_1d9eb24.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_1ef320c.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_3099406.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_4a5b6fe.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_9aa652e.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_baccdf6.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_d81f3fe.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_d855550.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_e6d88d8.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_e9d3189.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/index/en_5feeed7.pf_index +0 -0
- package/dist/docs-bundle/pagefind/index/en_c6981f9.pf_index +0 -0
- package/dist/docs-bundle/pagefind/index/en_ce4f6bf.pf_index +0 -0
- package/dist/docs-bundle/pagefind/pagefind-component-ui.css +1509 -0
- package/dist/docs-bundle/pagefind/pagefind-component-ui.js +56 -0
- package/dist/docs-bundle/pagefind/pagefind-entry.json +1 -0
- package/dist/docs-bundle/pagefind/pagefind-highlight.js +1070 -0
- package/dist/docs-bundle/pagefind/pagefind-modular-ui.css +214 -0
- package/dist/docs-bundle/pagefind/pagefind-modular-ui.js +8 -0
- package/dist/docs-bundle/pagefind/pagefind-ui.css +1 -0
- package/dist/docs-bundle/pagefind/pagefind-ui.js +2 -0
- package/dist/docs-bundle/pagefind/pagefind-worker.js +6 -0
- package/dist/docs-bundle/pagefind/pagefind.en_1e94abd852.pf_meta +0 -0
- package/dist/docs-bundle/pagefind/pagefind.js +6 -0
- package/dist/docs-bundle/pagefind/wasm.en.pagefind +0 -0
- package/dist/docs-bundle/pagefind/wasm.unknown.pagefind +0 -0
- package/dist/docs-bundle/v1/index.html +86 -0
- package/dist/docs-bundle/v1/using-velora/build-a-headless-frontend/index.html +413 -0
- package/dist/docs-bundle/v1/using-velora/content-model/index.html +858 -0
- package/dist/docs-bundle/v1/using-velora/editing/index.html +748 -0
- package/dist/docs-bundle/v1/using-velora/getting-started/index.html +588 -0
- package/dist/docs-bundle/v1/using-velora/headless-api/index.html +1620 -0
- package/dist/docs-bundle/v1/using-velora/index.html +215 -0
- package/dist/docs-bundle/v1/using-velora/installation/index.html +886 -0
- package/dist/docs-bundle/v1/using-velora/installing-plugins/index.html +836 -0
- package/dist/docs-bundle/v1/using-velora/media/index.html +468 -0
- package/dist/docs-bundle/v1/using-velora/users-and-roles/index.html +561 -0
- package/dist/graphql/field-count-rule.d.ts +5 -0
- package/dist/graphql/field-count-rule.js +59 -0
- package/dist/graphql/schema.d.ts +12 -0
- package/dist/graphql/schema.js +330 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +180 -0
- package/dist/lib.d.ts +2 -0
- package/dist/lib.js +39 -0
- package/dist/marketplace/install-bundle-members.d.ts +33 -0
- package/dist/marketplace/install-bundle-members.js +137 -0
- package/dist/marketplace/install-from-marketplace.d.ts +60 -0
- package/dist/marketplace/install-from-marketplace.js +581 -0
- package/dist/marketplace/license-revalidation-job.d.ts +38 -0
- package/dist/marketplace/license-revalidation-job.js +139 -0
- package/dist/marketplace/marketplace-client.d.ts +43 -0
- package/dist/marketplace/marketplace-client.js +228 -0
- package/dist/marketplace/revalidate-license.d.ts +39 -0
- package/dist/marketplace/revalidate-license.js +103 -0
- package/dist/media/media-service.d.ts +36 -0
- package/dist/media/media-service.js +162 -0
- package/dist/plugins/boot-plugin-loader.d.ts +32 -0
- package/dist/plugins/boot-plugin-loader.js +57 -0
- package/dist/plugins/hook-dispatcher.d.ts +13 -0
- package/dist/plugins/hook-dispatcher.js +68 -0
- package/dist/plugins/install-built-package.d.ts +36 -0
- package/dist/plugins/install-built-package.js +199 -0
- package/dist/plugins/install-package-tarball.d.ts +25 -0
- package/dist/plugins/install-package-tarball.js +73 -0
- package/dist/plugins/plugin-context.d.ts +5 -0
- package/dist/plugins/plugin-context.js +26 -0
- package/dist/plugins/plugin-package-store.d.ts +31 -0
- package/dist/plugins/plugin-package-store.js +64 -0
- package/dist/plugins/plugin-registry-service.d.ts +227 -0
- package/dist/plugins/plugin-registry-service.js +636 -0
- package/dist/plugins/seed-template-content.d.ts +23 -0
- package/dist/plugins/seed-template-content.js +198 -0
- package/dist/plugins/server-serializers.d.ts +3 -0
- package/dist/plugins/server-serializers.js +16 -0
- package/dist/plugins/simple-storage-api.d.ts +9 -0
- package/dist/plugins/simple-storage-api.js +26 -0
- package/dist/plugins/snapshot-groups.d.ts +16 -0
- package/dist/plugins/snapshot-groups.js +81 -0
- package/dist/plugins/structured-query-builder.d.ts +21 -0
- package/dist/plugins/structured-query-builder.js +60 -0
- package/dist/plugins/structured-snapshot-service.d.ts +16 -0
- package/dist/plugins/structured-snapshot-service.js +286 -0
- package/dist/plugins/structured-storage-api.d.ts +22 -0
- package/dist/plugins/structured-storage-api.js +91 -0
- package/dist/plugins/structured-storage-transaction.d.ts +22 -0
- package/dist/plugins/structured-storage-transaction.js +63 -0
- package/dist/plugins/structured-table-naming.d.ts +1 -0
- package/dist/plugins/structured-table-naming.js +18 -0
- package/dist/plugins/trusted-keys.d.ts +11 -0
- package/dist/plugins/trusted-keys.js +67 -0
- package/dist/plugins/trusted-signing-keys.d.ts +27 -0
- package/dist/plugins/trusted-signing-keys.js +40 -0
- package/dist/plugins/uninstall-bundle.d.ts +29 -0
- package/dist/plugins/uninstall-bundle.js +150 -0
- package/dist/plugins/verify-package.d.ts +36 -0
- package/dist/plugins/verify-package.js +121 -0
- package/dist/routes/admin-static.d.ts +8 -0
- package/dist/routes/admin-static.js +196 -0
- package/dist/routes/api-keys.d.ts +2 -0
- package/dist/routes/api-keys.js +95 -0
- package/dist/routes/api-playground.d.ts +2 -0
- package/dist/routes/api-playground.js +21 -0
- package/dist/routes/auth.d.ts +2 -0
- package/dist/routes/auth.js +102 -0
- package/dist/routes/content.d.ts +3 -0
- package/dist/routes/content.js +471 -0
- package/dist/routes/data-types.d.ts +3 -0
- package/dist/routes/data-types.js +100 -0
- package/dist/routes/docs-static.d.ts +8 -0
- package/dist/routes/docs-static.js +137 -0
- package/dist/routes/document-types.d.ts +3 -0
- package/dist/routes/document-types.js +149 -0
- package/dist/routes/headless-bridge.d.ts +10 -0
- package/dist/routes/headless-bridge.js +207 -0
- package/dist/routes/health.d.ts +2 -0
- package/dist/routes/health.js +15 -0
- package/dist/routes/marketplace-proxy.d.ts +8 -0
- package/dist/routes/marketplace-proxy.js +135 -0
- package/dist/routes/media.d.ts +6 -0
- package/dist/routes/media.js +93 -0
- package/dist/routes/plugin-api-routes.d.ts +12 -0
- package/dist/routes/plugin-api-routes.js +224 -0
- package/dist/routes/plugin-assets.d.ts +8 -0
- package/dist/routes/plugin-assets.js +51 -0
- package/dist/routes/plugin-management.d.ts +7 -0
- package/dist/routes/plugin-management.js +525 -0
- package/dist/routes/plugin-storage.d.ts +8 -0
- package/dist/routes/plugin-storage.js +104 -0
- package/dist/routes/plugins.d.ts +2 -0
- package/dist/routes/plugins.js +36 -0
- package/dist/routes/public-api.d.ts +9 -0
- package/dist/routes/public-api.js +545 -0
- package/dist/routes/published-item.d.ts +57 -0
- package/dist/routes/published-item.js +236 -0
- package/dist/routes/setup.d.ts +9 -0
- package/dist/routes/setup.js +477 -0
- package/dist/routes/snapshots.d.ts +3 -0
- package/dist/routes/snapshots.js +65 -0
- package/dist/routes/templates.d.ts +3 -0
- package/dist/routes/templates.js +37 -0
- package/dist/routes/themes.d.ts +2 -0
- package/dist/routes/themes.js +30 -0
- package/dist/routes/users.d.ts +2 -0
- package/dist/routes/users.js +113 -0
- package/dist/sandbox/register-local-plugin.d.ts +36 -0
- package/dist/sandbox/register-local-plugin.js +108 -0
- package/dist/sandbox/seed-sandbox.d.ts +20 -0
- package/dist/sandbox/seed-sandbox.js +122 -0
- package/dist/scripts/copy-admin-bundle.d.ts +1 -0
- package/dist/scripts/copy-admin-bundle.js +24 -0
- package/dist/scripts/copy-docs-bundle.d.ts +1 -0
- package/dist/scripts/copy-docs-bundle.js +22 -0
- package/dist/scripts/create-api-key.d.ts +5 -0
- package/dist/scripts/create-api-key.js +56 -0
- package/dist/scripts/generate-prisma-clients.d.ts +6 -0
- package/dist/scripts/generate-prisma-clients.js +125 -0
- package/dist/scripts/pack-bundled-templates.d.ts +1 -0
- package/dist/scripts/pack-bundled-templates.js +87 -0
- package/dist/setup/build-connection-string.d.ts +19 -0
- package/dist/setup/build-connection-string.js +14 -0
- package/dist/setup/bundled-templates.d.ts +19 -0
- package/dist/setup/bundled-templates.js +121 -0
- package/dist/setup/ensure-prisma-client.d.ts +3 -0
- package/dist/setup/ensure-prisma-client.js +23 -0
- package/dist/setup/install-starter.d.ts +14 -0
- package/dist/setup/install-starter.js +76 -0
- package/dist/setup/normalize-database-url.d.ts +2 -0
- package/dist/setup/normalize-database-url.js +35 -0
- package/dist/setup/persist-env-config.d.ts +2 -0
- package/dist/setup/persist-env-config.js +63 -0
- package/dist/setup/provision-database.d.ts +2 -0
- package/dist/setup/provision-database.js +89 -0
- package/dist/setup-app.d.ts +4 -0
- package/dist/setup-app.js +43 -0
- package/package.json +96 -0
- 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
|
+
};
|