@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,100 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { ApiErrorSchema, CreateDataTypeRequestSchema, DataTypeListQuerySchema, DataTypeListResponseSchema, DataTypeResponseSchema, UpdateDataTypeRequestSchema, } from "@velora-cms/api-schemas";
|
|
4
|
+
import { requireAuth } from "../auth/require-auth.js";
|
|
5
|
+
import { validateDataTypeSettings } from "../data-types/validate-data-type-settings.js";
|
|
6
|
+
const DataTypeIdParamsSchema = z.object({ id: z.string().uuid() });
|
|
7
|
+
// Data Types: named, admin-created configurations of registry editors.
|
|
8
|
+
// Auth tiers mirror document-types exactly (mutations need a JWT, reads
|
|
9
|
+
// are public) — they're the same kind of schema object.
|
|
10
|
+
export const dataTypeRoutes = async (app, opts) => {
|
|
11
|
+
const { adapter } = opts;
|
|
12
|
+
app.post("/api/data-types", {
|
|
13
|
+
onRequest: requireAuth,
|
|
14
|
+
schema: {
|
|
15
|
+
body: CreateDataTypeRequestSchema,
|
|
16
|
+
response: {
|
|
17
|
+
201: DataTypeResponseSchema,
|
|
18
|
+
400: ApiErrorSchema,
|
|
19
|
+
401: ApiErrorSchema,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
}, async (request, reply) => {
|
|
23
|
+
const dataType = { id: randomUUID(), ...request.body };
|
|
24
|
+
const settingsErrors = validateDataTypeSettings(dataType.editorId, dataType.settings);
|
|
25
|
+
if (settingsErrors.length > 0) {
|
|
26
|
+
return reply.code(400).send({
|
|
27
|
+
message: `Invalid settings for editor "${dataType.editorId}": ${settingsErrors.join("; ")}`,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
await adapter.createDataTypeConfig(dataType);
|
|
31
|
+
return reply.code(201).send({ dataType });
|
|
32
|
+
});
|
|
33
|
+
app.get("/api/data-types", {
|
|
34
|
+
schema: {
|
|
35
|
+
querystring: DataTypeListQuerySchema,
|
|
36
|
+
response: {
|
|
37
|
+
200: DataTypeListResponseSchema,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
}, async (request) => {
|
|
41
|
+
const { cursor, limit } = request.query;
|
|
42
|
+
const items = await adapter.listDataTypeConfigs({ cursor, limit });
|
|
43
|
+
const nextCursor = items.length === limit ? (items[items.length - 1]?.id ?? null) : null;
|
|
44
|
+
return { items, nextCursor };
|
|
45
|
+
});
|
|
46
|
+
app.get("/api/data-types/:id", {
|
|
47
|
+
schema: {
|
|
48
|
+
params: DataTypeIdParamsSchema,
|
|
49
|
+
response: {
|
|
50
|
+
200: DataTypeResponseSchema,
|
|
51
|
+
404: ApiErrorSchema,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
}, async (request, reply) => {
|
|
55
|
+
const dataType = await adapter.getDataTypeConfig(request.params.id);
|
|
56
|
+
if (!dataType) {
|
|
57
|
+
return reply.code(404).send({ message: `No data type found with id ${request.params.id}` });
|
|
58
|
+
}
|
|
59
|
+
return { dataType };
|
|
60
|
+
});
|
|
61
|
+
// Partial update of the two mutable columns; editorId is immutable
|
|
62
|
+
// (absent from the request schema on purpose — see
|
|
63
|
+
// data-type-api.schema.ts).
|
|
64
|
+
app.patch("/api/data-types/:id", {
|
|
65
|
+
onRequest: requireAuth,
|
|
66
|
+
schema: {
|
|
67
|
+
params: DataTypeIdParamsSchema,
|
|
68
|
+
body: UpdateDataTypeRequestSchema,
|
|
69
|
+
response: {
|
|
70
|
+
200: DataTypeResponseSchema,
|
|
71
|
+
400: ApiErrorSchema,
|
|
72
|
+
401: ApiErrorSchema,
|
|
73
|
+
404: ApiErrorSchema,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
}, async (request, reply) => {
|
|
77
|
+
const existing = await adapter.getDataTypeConfig(request.params.id);
|
|
78
|
+
if (!existing) {
|
|
79
|
+
return reply.code(404).send({ message: `No data type found with id ${request.params.id}` });
|
|
80
|
+
}
|
|
81
|
+
// Validate on every update, even name-only ones — a self-healing
|
|
82
|
+
// invariant once this ships (existing.settings is always valid
|
|
83
|
+
// going forward). If a built-in's settingsSchema ever tightens
|
|
84
|
+
// later, an old config with now-invalid stored settings would
|
|
85
|
+
// block until fixed.
|
|
86
|
+
const settings = request.body.settings ?? existing.settings;
|
|
87
|
+
const settingsErrors = validateDataTypeSettings(existing.editorId, settings);
|
|
88
|
+
if (settingsErrors.length > 0) {
|
|
89
|
+
return reply.code(400).send({
|
|
90
|
+
message: `Invalid settings for editor "${existing.editorId}": ${settingsErrors.join("; ")}`,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
await adapter.updateDataTypeConfig(existing.id, {
|
|
94
|
+
name: request.body.name ?? existing.name,
|
|
95
|
+
settings,
|
|
96
|
+
});
|
|
97
|
+
const dataType = await adapter.getDataTypeConfig(existing.id);
|
|
98
|
+
return { dataType: dataType };
|
|
99
|
+
});
|
|
100
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FastifyInstance } from "fastify";
|
|
2
|
+
interface DocsDirResolution {
|
|
3
|
+
dir: string | null;
|
|
4
|
+
invalidOverride: string | undefined;
|
|
5
|
+
}
|
|
6
|
+
export declare function resolveDocsDir(): DocsDirResolution;
|
|
7
|
+
export declare function docsStaticRoutes(app: FastifyInstance): Promise<void>;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import fastifyStatic from "@fastify/static";
|
|
5
|
+
import { rateLimitConfig } from "../auth/rate-limit.js";
|
|
6
|
+
import { DOCS_RATE_LIMIT } from "../auth/route-rate-limits.js";
|
|
7
|
+
// A usable docs directory: it must actually be a directory (not a file —
|
|
8
|
+
// @fastify/static@10.1.2 throws "root must be a directory" at registration
|
|
9
|
+
// otherwise, crashing buildApp()) and it must contain a "v1" edition — an
|
|
10
|
+
// empty directory would otherwise resolve successfully and then serve bare
|
|
11
|
+
// per-route 404s instead of the clear 404-with-cause.
|
|
12
|
+
function isUsableDocsDir(candidate) {
|
|
13
|
+
try {
|
|
14
|
+
return statSync(candidate).isDirectory() && existsSync(join(candidate, "v1"));
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function resolveDocsDir() {
|
|
21
|
+
const override = process.env.VELORA_DOCS_DIR;
|
|
22
|
+
if (override) {
|
|
23
|
+
// path.resolve() before anything else: @fastify/static@10.1.2 also
|
|
24
|
+
// throws ("root must be an absolute path") at registration when root
|
|
25
|
+
// is relative. Resolving against cwd up front means a relative
|
|
26
|
+
// override behaves the same as an absolute one instead of crashing
|
|
27
|
+
// boot.
|
|
28
|
+
const resolved = resolve(override);
|
|
29
|
+
if (isUsableDocsDir(resolved))
|
|
30
|
+
return { dir: resolved, invalidOverride: undefined };
|
|
31
|
+
return { dir: null, invalidOverride: override };
|
|
32
|
+
}
|
|
33
|
+
const here = dirname(fileURLToPath(import.meta.url)); // dist/routes or src/routes
|
|
34
|
+
const candidates = [
|
|
35
|
+
join(here, "..", "docs-bundle"),
|
|
36
|
+
join(here, "..", "..", "..", "..", "apps", "docs", "dist-bundle"),
|
|
37
|
+
];
|
|
38
|
+
const dir = candidates.find((c) => isUsableDocsDir(c)) ?? null;
|
|
39
|
+
return { dir, invalidOverride: undefined };
|
|
40
|
+
}
|
|
41
|
+
function escapeHtml(text) {
|
|
42
|
+
return text
|
|
43
|
+
.replace(/&/g, "&")
|
|
44
|
+
.replace(/</g, "<")
|
|
45
|
+
.replace(/>/g, ">")
|
|
46
|
+
.replace(/"/g, """);
|
|
47
|
+
}
|
|
48
|
+
// The admin's always-visible Docs tab opens /docs/* directly in a browser
|
|
49
|
+
// tab, so a degraded response there should be readable HTML, not raw JSON.
|
|
50
|
+
// Anything else (headless API consumers, tests) keeps the existing JSON
|
|
51
|
+
// shape.
|
|
52
|
+
function wantsHtml(req) {
|
|
53
|
+
return (req.headers.accept ?? "").includes("text/html");
|
|
54
|
+
}
|
|
55
|
+
function sendDegraded(req, reply, message) {
|
|
56
|
+
if (wantsHtml(req)) {
|
|
57
|
+
return reply
|
|
58
|
+
.code(404)
|
|
59
|
+
.type("text/html")
|
|
60
|
+
.send(`<!doctype html><html><head><meta charset="utf-8"><title>Documentation unavailable</title></head>` +
|
|
61
|
+
`<body><h1>Documentation unavailable</h1><p>${escapeHtml(message)}</p></body></html>`);
|
|
62
|
+
}
|
|
63
|
+
return reply.code(404).send({ message });
|
|
64
|
+
}
|
|
65
|
+
export async function docsStaticRoutes(app) {
|
|
66
|
+
const { dir: docsDir, invalidOverride } = resolveDocsDir();
|
|
67
|
+
// S162: static-ish reads, but not unbounded — this family previously had
|
|
68
|
+
// no ceiling at all. Registered per-scope, matching the precedent in
|
|
69
|
+
// auth.ts/plugin-api-routes.ts/public-api.ts: @fastify/rate-limit
|
|
70
|
+
// decorates the instance, so it is registered in child scopes OR at the
|
|
71
|
+
// root, never both.
|
|
72
|
+
await app.register(import("@fastify/rate-limit"), rateLimitConfig);
|
|
73
|
+
// This module's routes serve static HTML/asset infra, not Zod-validated
|
|
74
|
+
// JSON — api-design.md's "every route needs a request/response schema"
|
|
75
|
+
// rule governs the Zod→OpenAPI chain, which these deliberately sit
|
|
76
|
+
// outside of. Hidden from Swagger up front (the same treatment
|
|
77
|
+
// public-api.ts gives mercurius's schema-less GraphiQL routes) rather
|
|
78
|
+
// than left to leak into the spec as bare, undocumented path entries.
|
|
79
|
+
app.addHook("onRoute", (routeOptions) => {
|
|
80
|
+
if (routeOptions.url.startsWith("/docs")) {
|
|
81
|
+
routeOptions.schema = { ...routeOptions.schema, hide: true };
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
// Exact-path redirects win over the wildcard the static handler adds.
|
|
85
|
+
// Fastify 5's Reply#redirect signature is (url, code), not (code, url) —
|
|
86
|
+
// verified against this repo's installed fastify@5.8.5 source
|
|
87
|
+
// (lib/reply.js), which differs from older Fastify majors.
|
|
88
|
+
const redirect = (reply) => reply.redirect("/docs/v1/", 302);
|
|
89
|
+
const docsRateLimit = { rateLimit: { max: DOCS_RATE_LIMIT.max(), timeWindow: DOCS_RATE_LIMIT.timeWindow() } };
|
|
90
|
+
app.get("/docs", { config: docsRateLimit }, async (_req, reply) => redirect(reply));
|
|
91
|
+
if (!docsDir) {
|
|
92
|
+
const message = invalidOverride
|
|
93
|
+
? `VELORA_DOCS_DIR is set to "${invalidOverride}" but it is not usable (it must resolve to a ` +
|
|
94
|
+
'directory containing a "v1" edition). Fix the override or unset VELORA_DOCS_DIR to fall back ' +
|
|
95
|
+
"to the built-in bundle location."
|
|
96
|
+
: "The documentation bundle is not installed. Expected it inside the server build " +
|
|
97
|
+
"(dist/docs-bundle) or at VELORA_DOCS_DIR. Build with `turbo run build` to produce it.";
|
|
98
|
+
app.get("/docs/", { config: docsRateLimit }, async (req, reply) => sendDegraded(req, reply, message));
|
|
99
|
+
app.get("/docs/*", { config: docsRateLimit }, async (req, reply) => sendDegraded(req, reply, message));
|
|
100
|
+
app.log.warn("[docs-static] no docs bundle found — /docs/* will answer 404-with-cause");
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
// Registering the static mount and its not-found handler inside their own
|
|
104
|
+
// encapsulated child context (rather than directly on `app`) confines
|
|
105
|
+
// setNotFoundHandler to routes under this context's prefix. Fastify scopes
|
|
106
|
+
// setNotFoundHandler by the context's prefix — calling it directly on
|
|
107
|
+
// `app` (prefix "") would replace the app-wide 404 (and its JSON shape)
|
|
108
|
+
// for every unmatched route, including /api/*. The prefix here is
|
|
109
|
+
// "/docs/" (trailing slash, not "/docs") deliberately: Fastify collapses
|
|
110
|
+
// a route path of "/" registered inside a prefixed context down to the
|
|
111
|
+
// bare prefix string itself, so a "/docs" prefix would make the child's
|
|
112
|
+
// `get("/", ...)` land on "/docs" — colliding with the exact `app.get
|
|
113
|
+
// ("/docs", ...)` redirect above. Using "/docs/" as the prefix makes that
|
|
114
|
+
// same collapse land exactly on "/docs/", which is what we want.
|
|
115
|
+
// Isolation is covered by docs-static.integration.test.ts's "does not
|
|
116
|
+
// leak the docs-scoped 404 handler onto unrelated routes" test (an
|
|
117
|
+
// unknown /api/... path keeps Fastify's default 404 shape) and its
|
|
118
|
+
// /api/health check in the no-bundle-installed case.
|
|
119
|
+
await app.register(async (docsApp) => {
|
|
120
|
+
docsApp.get("/", { config: docsRateLimit }, async (_req, reply) => redirect(reply));
|
|
121
|
+
await docsApp.register(fastifyStatic, {
|
|
122
|
+
root: docsDir,
|
|
123
|
+
decorateReply: false,
|
|
124
|
+
redirect: true, // /docs/v1 -> /docs/v1/ so index resolution works
|
|
125
|
+
});
|
|
126
|
+
// Unknown paths under /docs/ get the bundle's own 404 page when present.
|
|
127
|
+
docsApp.setNotFoundHandler((_req, reply) => {
|
|
128
|
+
const notFoundPage = join(docsDir, "404.html");
|
|
129
|
+
if (existsSync(notFoundPage)) {
|
|
130
|
+
// decorateReply: false means reply.sendFile isn't available here —
|
|
131
|
+
// read the bundle's 404 page directly and send it.
|
|
132
|
+
return reply.code(404).type("text/html").send(readFileSync(notFoundPage));
|
|
133
|
+
}
|
|
134
|
+
return reply.code(404).send({ message: "not found" });
|
|
135
|
+
});
|
|
136
|
+
}, { prefix: "/docs/" });
|
|
137
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ApiErrorSchema, CreateDocumentTypeRequestSchema, CreateDocumentTypeResponseSchema, DeleteDocumentTypeResponseSchema, DocumentTypeListQuerySchema, DocumentTypeListResponseSchema, SetDefaultTemplateRequestSchema, UpdateDocumentTypeRequestSchema, } from "@velora-cms/api-schemas";
|
|
3
|
+
import { DocumentTypeInUseError, DocumentTypeService, DocumentTypeUnknownDataTypesError } from "@velora-cms/content-service";
|
|
4
|
+
import { requireAuth } from "../auth/require-auth.js";
|
|
5
|
+
const DocumentTypeIdParamsSchema = z.object({ id: z.string().uuid() });
|
|
6
|
+
export const documentTypeRoutes = async (app, opts) => {
|
|
7
|
+
const { adapter } = opts;
|
|
8
|
+
const documentTypeService = new DocumentTypeService(adapter);
|
|
9
|
+
app.post("/api/document-types", {
|
|
10
|
+
onRequest: requireAuth,
|
|
11
|
+
schema: {
|
|
12
|
+
body: CreateDocumentTypeRequestSchema,
|
|
13
|
+
response: {
|
|
14
|
+
201: CreateDocumentTypeResponseSchema,
|
|
15
|
+
400: ApiErrorSchema,
|
|
16
|
+
401: ApiErrorSchema,
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
}, async (request, reply) => {
|
|
20
|
+
try {
|
|
21
|
+
const documentType = await documentTypeService.createDocumentType(request.body);
|
|
22
|
+
return reply.code(201).send({ documentType });
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
if (error instanceof DocumentTypeUnknownDataTypesError) {
|
|
26
|
+
return reply.code(400).send({ message: error.message });
|
|
27
|
+
}
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
// Full edit of the mutable definition (Session 51). kind can never
|
|
32
|
+
// change (the strict schema has no kind key) and the default-template
|
|
33
|
+
// pointer is untouched — that stays with its dedicated PATCH below.
|
|
34
|
+
app.put("/api/document-types/:id", {
|
|
35
|
+
onRequest: requireAuth,
|
|
36
|
+
schema: {
|
|
37
|
+
params: DocumentTypeIdParamsSchema,
|
|
38
|
+
body: UpdateDocumentTypeRequestSchema,
|
|
39
|
+
response: {
|
|
40
|
+
200: CreateDocumentTypeResponseSchema,
|
|
41
|
+
400: ApiErrorSchema,
|
|
42
|
+
401: ApiErrorSchema,
|
|
43
|
+
404: ApiErrorSchema,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
}, async (request, reply) => {
|
|
47
|
+
const existing = await adapter.getDocumentType(request.params.id);
|
|
48
|
+
if (!existing) {
|
|
49
|
+
return reply.code(404).send({ message: `No document type found with id ${request.params.id}` });
|
|
50
|
+
}
|
|
51
|
+
// treeRules semantics follow the STORED kind — the update schema
|
|
52
|
+
// can't know it.
|
|
53
|
+
if (existing.kind === "page" && !request.body.treeRules) {
|
|
54
|
+
return reply.code(400).send({ message: "treeRules is required when updating a page document type" });
|
|
55
|
+
}
|
|
56
|
+
if (existing.kind === "element" && request.body.treeRules) {
|
|
57
|
+
return reply.code(400).send({ message: "treeRules is not allowed on an element document type" });
|
|
58
|
+
}
|
|
59
|
+
const unknown = await documentTypeService.findUnknownDataTypeIds(request.body.fields);
|
|
60
|
+
if (unknown.length > 0) {
|
|
61
|
+
return reply.code(400).send({
|
|
62
|
+
message: `Unknown data type id(s): ${unknown.join(", ")}`,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
await adapter.updateDocumentType(existing.id, {
|
|
66
|
+
name: request.body.name,
|
|
67
|
+
icon: request.body.icon,
|
|
68
|
+
fields: request.body.fields,
|
|
69
|
+
tabs: request.body.tabs,
|
|
70
|
+
treeRules: request.body.treeRules,
|
|
71
|
+
});
|
|
72
|
+
const documentType = await adapter.getDocumentType(existing.id);
|
|
73
|
+
return { documentType: documentType };
|
|
74
|
+
});
|
|
75
|
+
// No cascade: content and templates both block the delete rather than
|
|
76
|
+
// being silently swept away (the project's no-silent-data-loss rule).
|
|
77
|
+
app.delete("/api/document-types/:id", {
|
|
78
|
+
onRequest: requireAuth,
|
|
79
|
+
schema: {
|
|
80
|
+
params: DocumentTypeIdParamsSchema,
|
|
81
|
+
response: {
|
|
82
|
+
200: DeleteDocumentTypeResponseSchema,
|
|
83
|
+
401: ApiErrorSchema,
|
|
84
|
+
404: ApiErrorSchema,
|
|
85
|
+
409: ApiErrorSchema,
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
}, async (request, reply) => {
|
|
89
|
+
const existing = await adapter.getDocumentType(request.params.id);
|
|
90
|
+
if (!existing) {
|
|
91
|
+
return reply.code(404).send({ message: `No document type found with id ${request.params.id}` });
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
await documentTypeService.assertDocumentTypeDeletable(existing.id);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
if (error instanceof DocumentTypeInUseError) {
|
|
98
|
+
const message = error.reason === "has-content"
|
|
99
|
+
? `${error.contentCount} content item(s) use this document type — delete or move them first`
|
|
100
|
+
: "This document type still has templates — remove them first";
|
|
101
|
+
return reply.code(409).send({ message });
|
|
102
|
+
}
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
// S154: deleteDocumentType now purges any trashed content of this
|
|
106
|
+
// type first (the one real hard delete in the trash lifecycle — see
|
|
107
|
+
// DocumentTypeService.deleteDocumentType) before removing the type
|
|
108
|
+
// row itself. The guard above already passed once that content was
|
|
109
|
+
// trashed, since countContentByDocumentType excludes trashed rows.
|
|
110
|
+
await documentTypeService.deleteDocumentType(existing.id);
|
|
111
|
+
return { success: true };
|
|
112
|
+
});
|
|
113
|
+
app.get("/api/document-types", {
|
|
114
|
+
schema: {
|
|
115
|
+
querystring: DocumentTypeListQuerySchema,
|
|
116
|
+
response: {
|
|
117
|
+
200: DocumentTypeListResponseSchema,
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
}, async (request) => {
|
|
121
|
+
const { cursor, limit } = request.query;
|
|
122
|
+
const items = await adapter.listDocumentTypes({ cursor, limit });
|
|
123
|
+
const nextCursor = items.length === limit ? (items[items.length - 1]?.id ?? null) : null;
|
|
124
|
+
return { items, nextCursor };
|
|
125
|
+
});
|
|
126
|
+
// Narrow, single-purpose pointer-update endpoint — a template can't
|
|
127
|
+
// exist before its document type does, so defaultTemplateId is never set
|
|
128
|
+
// at create time. Deliberately not a general document-type update route.
|
|
129
|
+
app.patch("/api/document-types/:id/default-template", {
|
|
130
|
+
onRequest: requireAuth,
|
|
131
|
+
schema: {
|
|
132
|
+
params: DocumentTypeIdParamsSchema,
|
|
133
|
+
body: SetDefaultTemplateRequestSchema,
|
|
134
|
+
response: {
|
|
135
|
+
200: CreateDocumentTypeResponseSchema,
|
|
136
|
+
401: ApiErrorSchema,
|
|
137
|
+
404: ApiErrorSchema,
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
}, async (request, reply) => {
|
|
141
|
+
const existing = await adapter.getDocumentType(request.params.id);
|
|
142
|
+
if (!existing) {
|
|
143
|
+
return reply.code(404).send({ message: `No document type found with id ${request.params.id}` });
|
|
144
|
+
}
|
|
145
|
+
await adapter.setDefaultTemplate(request.params.id, request.body.templateId);
|
|
146
|
+
const documentType = await adapter.getDocumentType(request.params.id);
|
|
147
|
+
return { documentType: documentType };
|
|
148
|
+
});
|
|
149
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { DatabaseAdapter } from "@velora-cms/db-adapter";
|
|
2
|
+
import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
|
|
3
|
+
export declare function toContentPath(rawUrl: string): string | null;
|
|
4
|
+
export declare function createFrameworkErrorHandler(): (error: Error & {
|
|
5
|
+
code?: string;
|
|
6
|
+
statusCode?: number;
|
|
7
|
+
}, request: FastifyRequest, reply: FastifyReply) => void;
|
|
8
|
+
export declare function registerHeadlessBridge(app: FastifyInstance, deps: {
|
|
9
|
+
adapter: DatabaseAdapter;
|
|
10
|
+
}): void;
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { STATUS_CODES } from "node:http";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { isHeadlessEnabled } from "../config/headless.js";
|
|
5
|
+
// S156: the honest-headless bridge. Velora is headless-first (the template
|
|
6
|
+
// rendering engine is the v1.x epic) — until it lands, a content node's
|
|
7
|
+
// URL on this host renders an explainer instead of a bare 404, and every
|
|
8
|
+
// non-content path gets one neutral page that never leaks whether an
|
|
9
|
+
// unpublished or trashed node exists there. Only GET + Accept: text/html
|
|
10
|
+
// sees any of this; API consumers keep Fastify's stock JSON 404
|
|
11
|
+
// byte-identical. Deliberately claims the app-wide not-found handler —
|
|
12
|
+
// the docs-static.ts warning about doing so is exactly the intent here
|
|
13
|
+
// (that handler is scoped to its own "/docs/" prefixed child context, so
|
|
14
|
+
// it never collides with this app-wide one). registerHeadlessBridge below
|
|
15
|
+
// covers ordinary unmatched routes; createFrameworkErrorHandler (wired into
|
|
16
|
+
// Fastify's constructor-time `frameworkErrors` option in app.ts, not into
|
|
17
|
+
// this function) covers the separate low-level "URL couldn't even be
|
|
18
|
+
// routed" case find-my-way raises for hostile encodings — see its own doc
|
|
19
|
+
// comment for why that needs a different hook entirely.
|
|
20
|
+
// Rate limiting deferred to S162 (tracked for a future session).
|
|
21
|
+
// tokens.css is generated at build time (not committed) — resolved through
|
|
22
|
+
// the package's exported "./tokens.css" subpath (NOT dist/tokens.css, which
|
|
23
|
+
// is not an exported subpath — recorded gotcha). A missing build must not
|
|
24
|
+
// fail boot: the page degrades to its fallback values.
|
|
25
|
+
function loadTokensCss() {
|
|
26
|
+
try {
|
|
27
|
+
const require = createRequire(import.meta.url);
|
|
28
|
+
return readFileSync(require.resolve("@velora-cms/design-tokens/tokens.css"), "utf8");
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return "";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function escapeHtml(text) {
|
|
35
|
+
return text
|
|
36
|
+
.replace(/&/g, "&")
|
|
37
|
+
.replace(/</g, "<")
|
|
38
|
+
.replace(/>/g, ">")
|
|
39
|
+
.replace(/"/g, """);
|
|
40
|
+
}
|
|
41
|
+
function wantsHtml(req) {
|
|
42
|
+
return (req.headers.accept ?? "").includes("text/html");
|
|
43
|
+
}
|
|
44
|
+
// True when `text` contains a C0 control character (code points 0-31,
|
|
45
|
+
// i.e. NUL through unit separator). Written as a charCode scan rather
|
|
46
|
+
// than a regex literal so no raw control-byte source text has to survive
|
|
47
|
+
// tool round-tripping intact.
|
|
48
|
+
function hasControlChars(text) {
|
|
49
|
+
for (let i = 0; i < text.length; i++) {
|
|
50
|
+
if (text.charCodeAt(i) <= 31)
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
// Normalizes the request URL into a candidate content path, or null when
|
|
56
|
+
// it can't be one: undecodable escapes and control characters render the
|
|
57
|
+
// neutral page rather than a 500.
|
|
58
|
+
export function toContentPath(rawUrl) {
|
|
59
|
+
let pathname;
|
|
60
|
+
try {
|
|
61
|
+
pathname = decodeURIComponent(new URL(rawUrl, "http://placeholder").pathname);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
if (hasControlChars(pathname))
|
|
67
|
+
return null;
|
|
68
|
+
if (pathname.length > 1 && pathname.endsWith("/"))
|
|
69
|
+
pathname = pathname.slice(0, -1);
|
|
70
|
+
return pathname;
|
|
71
|
+
}
|
|
72
|
+
// Real token names (packages/design-tokens/dist/tokens.css, generated by
|
|
73
|
+
// `pnpm --filter @velora-cms/design-tokens build`): there is no --color-background
|
|
74
|
+
// / --color-foreground / --color-muted / --color-primary pair — those were
|
|
75
|
+
// placeholder guesses. The actual scale is --color-background-base,
|
|
76
|
+
// --color-text-primary, --color-background-hover (used here as the
|
|
77
|
+
// <code> block's fill), and --color-primary-600 (the 600 step reads as
|
|
78
|
+
// "link" contrast against the light background token). Fallback values
|
|
79
|
+
// are kept so a missing build still renders something reasonable.
|
|
80
|
+
function pageHtml(tokensCss, content) {
|
|
81
|
+
return (`<!doctype html><html dir="auto"><head><meta charset="utf-8">` +
|
|
82
|
+
`<meta name="viewport" content="width=device-width, initial-scale=1">` +
|
|
83
|
+
`<meta name="robots" content="noindex">` +
|
|
84
|
+
`<title>${escapeHtml(content.title)} — Velora</title>` +
|
|
85
|
+
`<style>${tokensCss}` +
|
|
86
|
+
`body{margin:0;font-family:system-ui,sans-serif;background:var(--color-background-base,#f8fafc);` +
|
|
87
|
+
`color:var(--color-text-primary,#0f172a);display:grid;min-block-size:100vh;place-items:center}` +
|
|
88
|
+
`main{max-inline-size:40rem;padding-block:2rem;padding-inline:1.5rem}` +
|
|
89
|
+
`h1{font-size:1.5rem;margin-block-end:.5rem}` +
|
|
90
|
+
`p{line-height:1.6;margin-block:.5rem}` +
|
|
91
|
+
`code{display:block;overflow-x:auto;padding-block:.75rem;padding-inline:1rem;` +
|
|
92
|
+
`border-radius:.5rem;background:var(--color-background-hover,#e2e8f0);font-size:.875rem}` +
|
|
93
|
+
`a{color:var(--color-primary-600,#2563eb)}` +
|
|
94
|
+
`.type{font-size:.875rem;opacity:.7}` +
|
|
95
|
+
`</style></head><body><main>${content.bodyHtml}</main></body></html>`);
|
|
96
|
+
}
|
|
97
|
+
function apiSectionHtml(origin, path) {
|
|
98
|
+
if (!isHeadlessEnabled()) {
|
|
99
|
+
return (`<p>The public content API is not enabled on this instance ` +
|
|
100
|
+
`(<code style="display:inline;padding:0">VELORA_HEADLESS</code> is off), so there is no ` +
|
|
101
|
+
`public URL for this content. An administrator can enable it at deploy time.</p>`);
|
|
102
|
+
}
|
|
103
|
+
const byPathUrl = `${origin}/api/v1/content/by-path?path=${encodeURIComponent(path)}`;
|
|
104
|
+
return (`<p>Fetch it from the headless API (requests need an API key):</p>` +
|
|
105
|
+
`<code>${escapeHtml(byPathUrl)}</code>` +
|
|
106
|
+
`<p>GraphQL is available at <code style="display:inline;padding:0">/api/v1/graphql</code>.</p>`);
|
|
107
|
+
}
|
|
108
|
+
// The one neutral page shared by missing, unpublished, and trashed paths —
|
|
109
|
+
// existence is never leaked. The copy doubles as the instance's front door
|
|
110
|
+
// (this is also what "/" renders).
|
|
111
|
+
function neutralPageContent() {
|
|
112
|
+
const bodyHtml = `<h1>This is a headless Velora CMS</h1>` +
|
|
113
|
+
`<p>Nothing is published at this URL. Content on this instance is not rendered ` +
|
|
114
|
+
`into pages here — it is served through the API to a frontend of your choice ` +
|
|
115
|
+
`(see the starter frontends in the documentation).</p>` +
|
|
116
|
+
`<p><a href="/docs/v1/">Documentation</a></p>`;
|
|
117
|
+
return { title: "Headless Velora", bodyHtml };
|
|
118
|
+
}
|
|
119
|
+
// True for requests this bridge answers at all: a browser-tab GET outside
|
|
120
|
+
// /api (which keeps its own JSON 404s — /docs and /plugin-assets have
|
|
121
|
+
// their own scoped handlers and never reach either handler below).
|
|
122
|
+
function isBridgedRequest(request) {
|
|
123
|
+
return request.method === "GET" && wantsHtml(request) && !(request.raw.url ?? "").startsWith("/api/");
|
|
124
|
+
}
|
|
125
|
+
// find-my-way rejects some malformed URLs (a lone "%ff" with no valid UTF-8
|
|
126
|
+
// decoding, "%00", etc.) at the routing layer itself — before Fastify ever
|
|
127
|
+
// builds a matched or not-found route. Verified against the installed
|
|
128
|
+
// fastify@5.8.5 source (fastify.js's onBadUrl): with no `frameworkErrors`
|
|
129
|
+
// option, that path answers via a raw `res.writeHead(400, ...); res.end(...)`
|
|
130
|
+
// call that bypasses setNotFoundHandler, setErrorHandler, hooks — everything
|
|
131
|
+
// — so setNotFoundHandler alone (the brief's original sketch) can never see
|
|
132
|
+
// or reshape this case. `frameworkErrors`, a Fastify constructor-time-only
|
|
133
|
+
// option, is the one hook the framework offers into that path; it also
|
|
134
|
+
// covers FST_ERR_ASYNC_CONSTRAINT (unreachable here — this app registers no
|
|
135
|
+
// route constraints), so anything other than a bridged bad-url request
|
|
136
|
+
// reproduces Fastify's own no-frameworkErrors JSON body/status exactly,
|
|
137
|
+
// changing nothing this bridge doesn't own.
|
|
138
|
+
export function createFrameworkErrorHandler() {
|
|
139
|
+
const tokensCss = loadTokensCss();
|
|
140
|
+
return (error, request, reply) => {
|
|
141
|
+
if (error.code === "FST_ERR_BAD_URL" && isBridgedRequest(request)) {
|
|
142
|
+
reply.header("X-Robots-Tag", "noindex");
|
|
143
|
+
reply.header("Cache-Control", "no-store");
|
|
144
|
+
reply.code(404).type("text/html").send(pageHtml(tokensCss, neutralPageContent()));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const statusCode = error.statusCode ?? 500;
|
|
148
|
+
reply.code(statusCode).send({
|
|
149
|
+
error: STATUS_CODES[statusCode] ?? "Internal Server Error",
|
|
150
|
+
code: error.code,
|
|
151
|
+
message: error.message,
|
|
152
|
+
statusCode,
|
|
153
|
+
});
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
export function registerHeadlessBridge(app, deps) {
|
|
157
|
+
const tokensCss = loadTokensCss();
|
|
158
|
+
if (!tokensCss) {
|
|
159
|
+
app.log.warn("[headless-bridge] tokens.css not found — explainer pages use fallback styling");
|
|
160
|
+
}
|
|
161
|
+
app.setNotFoundHandler(async (request, reply) => {
|
|
162
|
+
if (!isBridgedRequest(request)) {
|
|
163
|
+
const message = `Route ${request.method}:${request.url} not found`;
|
|
164
|
+
// Fastify's default 404 handler logs this line; keep parity so
|
|
165
|
+
// operators grepping for it don't lose unmatched-route visibility.
|
|
166
|
+
request.log.info(message);
|
|
167
|
+
return reply.code(404).send({
|
|
168
|
+
message,
|
|
169
|
+
error: "Not Found",
|
|
170
|
+
statusCode: 404,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
reply.header("X-Robots-Tag", "noindex");
|
|
174
|
+
reply.header("Cache-Control", "no-store");
|
|
175
|
+
const path = toContentPath(request.url);
|
|
176
|
+
try {
|
|
177
|
+
const node = path
|
|
178
|
+
? await deps.adapter.getContentNodeByPath(path, { publishedOnly: true })
|
|
179
|
+
: null;
|
|
180
|
+
if (!node || !path) {
|
|
181
|
+
return reply.code(404).type("text/html").send(pageHtml(tokensCss, neutralPageContent()));
|
|
182
|
+
}
|
|
183
|
+
const label = path.split("/").filter(Boolean).pop() ?? path;
|
|
184
|
+
const documentType = await deps.adapter.getDocumentType(node.documentTypeId);
|
|
185
|
+
const origin = `${request.protocol}://${request.headers.host ?? "localhost"}`;
|
|
186
|
+
const bodyHtml = `<h1>${escapeHtml(label)}</h1>` +
|
|
187
|
+
(documentType ? `<p class="type">${escapeHtml(documentType.name)}</p>` : "") +
|
|
188
|
+
`<p>This content exists — but this Velora instance is headless, so content URLs ` +
|
|
189
|
+
`don't render pages on this host.</p>` +
|
|
190
|
+
apiSectionHtml(origin, path) +
|
|
191
|
+
`<p><a href="/docs/v1/">Documentation</a></p>`;
|
|
192
|
+
return reply.code(200).type("text/html").send(pageHtml(tokensCss, { title: label, bodyHtml }));
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
// A rejected adapter call (e.g. a dropped DB connection) must never
|
|
196
|
+
// reach Fastify's default error handler here: this is the public
|
|
197
|
+
// origin, and that handler serializes err.message — a raw adapter/DB
|
|
198
|
+
// error string — into a 500 body for an anonymous visitor. The
|
|
199
|
+
// neutral 404 is the correct degraded answer: it's already what
|
|
200
|
+
// "genuinely nothing here" and "can't tell" both render as, so a
|
|
201
|
+
// lookup failure just joins that same bucket rather than leaking
|
|
202
|
+
// internals or the fact that a lookup was attempted at all.
|
|
203
|
+
app.log.error({ err: error }, "[headless-bridge] adapter lookup failed; degrading to the neutral 404");
|
|
204
|
+
return reply.code(404).type("text/html").send(pageHtml(tokensCss, neutralPageContent()));
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { HealthCheckSchema } from "@velora-cms/api-schemas";
|
|
2
|
+
export const healthRoutes = async (app) => {
|
|
3
|
+
app.get("/api/health", {
|
|
4
|
+
schema: {
|
|
5
|
+
response: {
|
|
6
|
+
200: HealthCheckSchema,
|
|
7
|
+
},
|
|
8
|
+
},
|
|
9
|
+
}, async () => {
|
|
10
|
+
return {
|
|
11
|
+
status: "ok",
|
|
12
|
+
timestamp: new Date().toISOString(),
|
|
13
|
+
};
|
|
14
|
+
});
|
|
15
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FastifyPluginAsyncZod } from "fastify-type-provider-zod";
|
|
2
|
+
import type { DatabaseAdapter } from "@velora-cms/db-adapter";
|
|
3
|
+
export interface MarketplaceProxyOptions {
|
|
4
|
+
marketplaceBaseUrl: string;
|
|
5
|
+
activeDialect: string;
|
|
6
|
+
adapter: DatabaseAdapter;
|
|
7
|
+
}
|
|
8
|
+
export declare const marketplaceProxyRoutes: FastifyPluginAsyncZod<MarketplaceProxyOptions>;
|