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