@velora-cms/server 0.11.0 → 0.12.1
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/README.md +6 -0
- package/dist/admin/sync-admin-bundle.d.ts +9 -0
- package/dist/admin/sync-admin-bundle.js +120 -0
- package/dist/admin-bundle/.velora-version +1 -0
- package/dist/admin-bundle/assets/{_virtual_mf-localSharedImportMap___mfe_internal__velora_admin__mf_owner__1-DOKdWYyj.js → _virtual_mf-localSharedImportMap___mfe_internal__velora_admin__mf_owner__1-DlIgIuFP.js} +1 -1
- 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 → _virtual_mf___mfe_internal__velora_admin__mf_owner__1__loadShare___mf_0_velora_mf_2_cms_mf_1_plugin_mf_2_sdk__loadShare__.js-Bm9EahCG.js} +2 -2
- package/dist/admin-bundle/assets/{content-trash.schema-DfXHkC-w.js → content-trash.schema-tvJ4Lbos.js} +5 -5
- package/dist/admin-bundle/assets/{hostInit--C4xK6nj.js → hostInit-B-6-34XY.js} +1 -1
- package/dist/admin-bundle/assets/{index-DhMY-0sC.js → index-CHCGuSbb.js} +1 -1
- package/dist/admin-bundle/assets/{index-BIAF0ShP.js → index-DYO7-IK8.js} +33 -33
- package/dist/admin-bundle/assets/{mf-entry-bootstrap-0-aef015cd.js → mf-entry-bootstrap-0-7b0ce2fe.js} +2 -2
- package/dist/admin-bundle/assets/{remoteEntry-CNyN68bc.js → remoteEntry-J2yJXsGB.js} +2 -2
- package/dist/admin-bundle/index.html +4 -4
- package/dist/app.d.ts +16 -1
- package/dist/app.js +63 -4
- package/dist/auth/api-keys.d.ts +1 -0
- package/dist/auth/api-keys.js +5 -1
- package/dist/auth/route-rate-limits.d.ts +4 -0
- package/dist/auth/route-rate-limits.js +13 -0
- package/dist/bin-sync-admin.d.ts +2 -0
- package/dist/bin-sync-admin.js +39 -0
- package/dist/bundled-templates/com.velora.blog/package.tar.gz +0 -0
- package/dist/config/public-url.d.ts +3 -0
- package/dist/config/public-url.js +45 -0
- package/dist/db/core-migrations/steps.js +95 -16
- package/dist/docs-bundle/pagefind/fragment/en_42f5356.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_6c5ee82.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_80941b8.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_a64440b.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_eb168aa.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/fragment/en_f053753.pf_fragment +0 -0
- package/dist/docs-bundle/pagefind/index/en_33ea61e.pf_index +0 -0
- package/dist/docs-bundle/pagefind/index/en_bb7c383.pf_index +0 -0
- package/dist/docs-bundle/pagefind/index/en_ff6eddf.pf_index +0 -0
- package/dist/docs-bundle/pagefind/pagefind-entry.json +1 -1
- package/dist/docs-bundle/pagefind/pagefind.en_6bbff64ea9.pf_meta +0 -0
- package/dist/docs-bundle/v1/index.html +4 -4
- package/dist/docs-bundle/v1/using-velora/getting-started/index.html +5 -3
- package/dist/docs-bundle/v1/using-velora/headless-api/index.html +10 -4
- package/dist/docs-bundle/v1/using-velora/index.html +6 -4
- package/dist/docs-bundle/v1/using-velora/installation/index.html +73 -15
- package/dist/docs-bundle/v1/using-velora/media/index.html +118 -34
- package/dist/graphql/schema.d.ts +1 -0
- package/dist/graphql/schema.js +5 -2
- package/dist/lib.d.ts +1 -0
- package/dist/lib.js +45 -11
- package/dist/media/media-service.d.ts +14 -15
- package/dist/media/media-service.js +121 -59
- package/dist/media/media-tokens.d.ts +6 -0
- package/dist/media/media-tokens.js +88 -0
- package/dist/media/storage/local.d.ts +16 -0
- package/dist/media/storage/local.js +99 -0
- package/dist/media/storage/s3.d.ts +25 -0
- package/dist/media/storage/s3.js +134 -0
- package/dist/media/storage/types.d.ts +21 -0
- package/dist/media/storage/types.js +44 -0
- package/dist/routes/admin-static.d.ts +2 -0
- package/dist/routes/admin-static.js +42 -4
- package/dist/routes/media-public.d.ts +6 -0
- package/dist/routes/media-public.js +265 -0
- package/dist/routes/media.d.ts +2 -1
- package/dist/routes/media.js +95 -5
- package/dist/routes/plugin-storage.js +5 -1
- package/dist/routes/public-api.js +5 -3
- package/dist/routes/published-item.d.ts +2 -1
- package/dist/routes/published-item.js +28 -5
- package/dist/scripts/copy-admin-bundle.js +9 -1
- package/package.json +12 -11
- package/prisma/schema.prisma +16 -3
- 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_9aa652e.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/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.en_1e94abd852.pf_meta +0 -0
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
})();
|
|
72
72
|
</script></sl-sidebar-state-persist><div class="md:sl-hidden"><div class="mobile-preferences sl-flex astro-yfet6llj"><div class="social-icons astro-yfet6llj"></div><starlight-theme-select><label style="--sl-select-width: 6.25em" class="astro-7uz3f5wr"><span class="sr-only astro-7uz3f5wr">Select theme</span><svg aria-hidden="true" class="icon label-icon astro-7uz3f5wr astro-24n5ceuf" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1em;"><path d="M21 14h-1V7a3 3 0 0 0-3-3H7a3 3 0 0 0-3 3v7H3a1 1 0 0 0-1 1v2a3 3 0 0 0 3 3h14a3 3 0 0 0 3-3v-2a1 1 0 0 0-1-1ZM6 7a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v7H6V7Zm14 10a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-1h16v1Z"/></svg><select autocomplete="off" class="astro-7uz3f5wr"><option value="dark" class="astro-7uz3f5wr">Dark</option><option value="light" class="astro-7uz3f5wr">Light</option><option value="auto" selected class="astro-7uz3f5wr">Auto</option></select><svg aria-hidden="true" class="icon caret astro-7uz3f5wr astro-24n5ceuf" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1em;"><path d="M17 9.17a1 1 0 0 0-1.41 0L12 12.71 8.46 9.17a1 1 0 1 0-1.41 1.42l4.24 4.24a1.002 1.002 0 0 0 1.42 0L17 10.59a1.002 1.002 0 0 0 0-1.42Z"/></svg></label></starlight-theme-select><script>
|
|
73
73
|
StarlightThemeProvider.updatePickers();
|
|
74
|
-
</script></div></div></div></div></nav><div class="main-frame astro-5auw3bkx"><script type="module">var e=document.getElementById(`starlight__sidebar`),t=e?.querySelector(`sl-sidebar-state-persist`),n=`sl-sidebar-state`,r=()=>{let r=[],i=t?.dataset.hash||``;try{let e=sessionStorage.getItem(n),t=JSON.parse(e||`{}`);Array.isArray(t.open)&&t.hash===i&&(r=t.open)}catch{}return{hash:i,open:r,scroll:e?.scrollTop||0}},i=e=>{try{sessionStorage.setItem(n,JSON.stringify(e))}catch{}},a=()=>i(r()),o=(e,t)=>{let n=r();n.open[t]=e,i(n)};t?.addEventListener(`click`,e=>{if(!(e.target instanceof Element))return;let t=e.target.closest(`summary`)?.closest(`details`);if(!t)return;let n=t.querySelector(`sl-sidebar-restore`),r=parseInt(n?.dataset.index||``);isNaN(r)||o(!t.open,r)}),addEventListener(`visibilitychange`,()=>{document.visibilityState===`hidden`&&a()});</script><div class="lg:sl-flex astro-perbdhd2"><aside class="right-sidebar-container print:hidden astro-perbdhd2"><div class="right-sidebar astro-perbdhd2"><div class="lg:sl-hidden astro-epm5tkrc"><mobile-starlight-toc data-min-h="2" data-max-h="3" class="astro-bnvebtr4"><nav aria-labelledby="starlight__on-this-page--mobile" class="astro-bnvebtr4"><details id="starlight__mobile-toc" class="astro-bnvebtr4"><summary id="starlight__on-this-page--mobile" class="sl-flex astro-bnvebtr4"><span class="toggle sl-flex astro-bnvebtr4">On this page<svg aria-hidden="true" class="caret astro-bnvebtr4 astro-24n5ceuf" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1rem;"><path d="m14.83 11.29-4.24-4.24a1 1 0 1 0-1.42 1.41L12.71 12l-3.54 3.54a1 1 0 0 0 0 1.41 1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.24-4.24a1.002 1.002 0 0 0 0-1.42Z"/></svg></span><span class="display-current astro-bnvebtr4"></span></summary><div class="dropdown astro-bnvebtr4"><ul class="isMobile astro-lptfoybu" style="--depth: 0;"><li style="--depth: 0;" class="astro-lptfoybu"><a href="#_top" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Overview</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#about-this-documentation" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">About this documentation</span></a></li></ul></div></details></nav></mobile-starlight-toc><script type="module" src="/docs/_astro/MobileTableOfContents.astro_astro_type_script_index_0_lang.BcSo_yiZ.js"></script></div><div class="right-sidebar-panel sl-hidden lg:sl-block astro-epm5tkrc"><div class="sl-container astro-epm5tkrc"><starlight-toc data-min-h="2" data-max-h="3"><nav aria-labelledby="starlight__on-this-page"><h2 id="starlight__on-this-page">On this page</h2><ul class="astro-lptfoybu" style="--depth: 0;"><li style="--depth: 0;" class="astro-lptfoybu"><a href="#_top" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Overview</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#about-this-documentation" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">About this documentation</span></a></li></ul></nav></starlight-toc><script type="module" src="/docs/_astro/TableOfContents.astro_astro_type_script_index_0_lang.Csloo1VZ.js"></script></div></div></div></aside><div class="main-pane astro-perbdhd2"><main data-pagefind-body class="astro-ewhz5xxr" lang="en" dir="ltr"><div class="content-panel astro-ahkzlqop"><div class="sl-container astro-ahkzlqop"><h1 id="_top" class="astro-oef2efwn">Velora</h1></div></div><div class="content-panel astro-ahkzlqop"><div class="sl-container astro-ahkzlqop"><div class="sl-markdown-content"><p>Velora is
|
|
74
|
+
</script></div></div></div></div></nav><div class="main-frame astro-5auw3bkx"><script type="module">var e=document.getElementById(`starlight__sidebar`),t=e?.querySelector(`sl-sidebar-state-persist`),n=`sl-sidebar-state`,r=()=>{let r=[],i=t?.dataset.hash||``;try{let e=sessionStorage.getItem(n),t=JSON.parse(e||`{}`);Array.isArray(t.open)&&t.hash===i&&(r=t.open)}catch{}return{hash:i,open:r,scroll:e?.scrollTop||0}},i=e=>{try{sessionStorage.setItem(n,JSON.stringify(e))}catch{}},a=()=>i(r()),o=(e,t)=>{let n=r();n.open[t]=e,i(n)};t?.addEventListener(`click`,e=>{if(!(e.target instanceof Element))return;let t=e.target.closest(`summary`)?.closest(`details`);if(!t)return;let n=t.querySelector(`sl-sidebar-restore`),r=parseInt(n?.dataset.index||``);isNaN(r)||o(!t.open,r)}),addEventListener(`visibilitychange`,()=>{document.visibilityState===`hidden`&&a()});</script><div class="lg:sl-flex astro-perbdhd2"><aside class="right-sidebar-container print:hidden astro-perbdhd2"><div class="right-sidebar astro-perbdhd2"><div class="lg:sl-hidden astro-epm5tkrc"><mobile-starlight-toc data-min-h="2" data-max-h="3" class="astro-bnvebtr4"><nav aria-labelledby="starlight__on-this-page--mobile" class="astro-bnvebtr4"><details id="starlight__mobile-toc" class="astro-bnvebtr4"><summary id="starlight__on-this-page--mobile" class="sl-flex astro-bnvebtr4"><span class="toggle sl-flex astro-bnvebtr4">On this page<svg aria-hidden="true" class="caret astro-bnvebtr4 astro-24n5ceuf" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1rem;"><path d="m14.83 11.29-4.24-4.24a1 1 0 1 0-1.42 1.41L12.71 12l-3.54 3.54a1 1 0 0 0 0 1.41 1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.24-4.24a1.002 1.002 0 0 0 0-1.42Z"/></svg></span><span class="display-current astro-bnvebtr4"></span></summary><div class="dropdown astro-bnvebtr4"><ul class="isMobile astro-lptfoybu" style="--depth: 0;"><li style="--depth: 0;" class="astro-lptfoybu"><a href="#_top" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Overview</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#about-this-documentation" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">About this documentation</span></a></li></ul></div></details></nav></mobile-starlight-toc><script type="module" src="/docs/_astro/MobileTableOfContents.astro_astro_type_script_index_0_lang.BcSo_yiZ.js"></script></div><div class="right-sidebar-panel sl-hidden lg:sl-block astro-epm5tkrc"><div class="sl-container astro-epm5tkrc"><starlight-toc data-min-h="2" data-max-h="3"><nav aria-labelledby="starlight__on-this-page"><h2 id="starlight__on-this-page">On this page</h2><ul class="astro-lptfoybu" style="--depth: 0;"><li style="--depth: 0;" class="astro-lptfoybu"><a href="#_top" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Overview</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#about-this-documentation" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">About this documentation</span></a></li></ul></nav></starlight-toc><script type="module" src="/docs/_astro/TableOfContents.astro_astro_type_script_index_0_lang.Csloo1VZ.js"></script></div></div></div></aside><div class="main-pane astro-perbdhd2"><main data-pagefind-body class="astro-ewhz5xxr" lang="en" dir="ltr"><div class="content-panel astro-ahkzlqop"><div class="sl-container astro-ahkzlqop"><h1 id="_top" class="astro-oef2efwn">Velora</h1></div></div><div class="content-panel astro-ahkzlqop"><div class="sl-container astro-ahkzlqop"><div class="sl-markdown-content"><p>Velora is a CMS framework built on Node.js and React. Content is
|
|
75
75
|
immutable — every save writes a new version and rollback only changes which
|
|
76
76
|
version is current — updates never break a running site, the content tree
|
|
77
77
|
scales from a handful of pages to very large ones, and the same instance can
|
|
@@ -81,6 +81,6 @@ be used headless through its API or traditionally through its own rendering.</p>
|
|
|
81
81
|
<p>These docs describe <strong>v1</strong>, the first public release line. Everything lives
|
|
82
82
|
under <code dir="auto">/v1/</code> so that future versions can be published alongside it without
|
|
83
83
|
breaking links to this one.</p>
|
|
84
|
-
<p>
|
|
85
|
-
<
|
|
86
|
-
|
|
84
|
+
<p>Get a Velora instance running with <a href="/docs/v1/using-velora/installation/">Installation</a>:
|
|
85
|
+
<code dir="auto">npm create velora@latest my-site</code> scaffolds a runnable project in one
|
|
86
|
+
command, and the setup wizard takes it from there.</p></div><footer class="sl-flex astro-gjkl2a5w"><div class="meta sl-flex astro-gjkl2a5w"></div><div class="pagination-links print:hidden astro-3tj6ibx2" dir="ltr"></div></footer><div class="velora-bundle-stamp astro-llmj63sw">Velora 0.9.0 · bundled documentation</div></div></div></main></div></div></div></div></body></html>
|
|
@@ -83,9 +83,11 @@ fresh, unconfigured instance instead, the same shape a real deployment
|
|
|
83
83
|
boots into on first run (see <code dir="auto">DEPLOYMENT.md</code> at the repo root). The
|
|
84
84
|
<a href="https://docs.velora-cms.com/v1/contributing/local-setup/">Local setup</a> page’s prerequisites still
|
|
85
85
|
apply — a checkout of the Velora repo, Node.js 22.12+, and <code dir="auto">pnpm</code> — so
|
|
86
|
-
this walkthrough is a contributor path run from a source-checkout sandbox
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
this walkthrough is a contributor path run from a source-checkout sandbox.
|
|
87
|
+
The fastest way to run Velora yourself is the one-command install —
|
|
88
|
+
<code dir="auto">npm create velora@latest my-site</code>, which writes a <code dir="auto">server.js</code> project you
|
|
89
|
+
run directly — covered in <a href="/docs/v1/using-velora/installation/">Installation</a>;
|
|
90
|
+
the hosted demo needs no install at all, and the <a href="/docs/v1/using-velora/build-a-headless-frontend/">headless tutorial</a>
|
|
89
91
|
walks the public API this same Blog content is served through.</p>
|
|
90
92
|
<div class="sl-heading-wrapper level-h2"><h2 id="environment-variables-this-tutorial-sets">Environment variables this tutorial sets</h2><a class="sl-anchor-link" href="#environment-variables-this-tutorial-sets"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “Environment variables this tutorial sets”</span></a></div>
|
|
91
93
|
<p>You’ll set four environment variables along the way, each explained in
|
|
@@ -1198,7 +1198,7 @@ those.</p>
|
|
|
1198
1198
|
|
|
1199
1199
|
<table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">:id</code></td><td>string (uuid), path parameter</td><td>The media file’s identity.</td><td><code dir="auto">9f1d3b7c-52ae-4d08-9b31-6c74a0f5e218</code></td></tr></tbody></table>
|
|
1200
1200
|
<p>Nothing is uploaded on the demo, so this shape is schema-derived:</p>
|
|
1201
|
-
<div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="json"><code><div class="ec-line"><div class="code"><span style="--0:#D6DEEB;--1:#403F53">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"id"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">9f1d3b7c-52ae-4d08-9b31-6c74a0f5e218</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"filename"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">cover.png</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"mimeType"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">image/png</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"size"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#F78C6C;--1:#AA0982">48213</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"downloadUrl"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">https://
|
|
1201
|
+
<div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="json"><code><div class="ec-line"><div class="code"><span style="--0:#D6DEEB;--1:#403F53">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"id"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">9f1d3b7c-52ae-4d08-9b31-6c74a0f5e218</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"filename"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">cover.png</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"mimeType"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">image/png</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"size"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#F78C6C;--1:#AA0982">48213</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"downloadUrl"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">https://demo.velora-cms.com/media/9f1d3b7c-52ae-4d08-9b31-6c74a0f5e218/cover.png</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"visibility"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">public</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"uploadedAt"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">2026-08-31T09:12:04.006Z</span><span style="--0:#D9F5DD;--1:#111111">"</span></div></div><div class="ec-line"><div class="code"><span style="--0:#D6DEEB;--1:#403F53">}</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="{ "id": "9f1d3b7c-52ae-4d08-9b31-6c74a0f5e218", "filename": "cover.png", "mimeType": "image/png", "size": 48213, "downloadUrl": "https://demo.velora-cms.com/media/9f1d3b7c-52ae-4d08-9b31-6c74a0f5e218/cover.png", "visibility": "public", "uploadedAt": "2026-08-31T09:12:04.006Z"}"><div></div></button></div></figure></div>
|
|
1202
1202
|
<p>Every property:</p>
|
|
1203
1203
|
|
|
1204
1204
|
|
|
@@ -1247,9 +1247,15 @@ those.</p>
|
|
|
1247
1247
|
|
|
1248
1248
|
|
|
1249
1249
|
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1250
|
+
|
|
1251
|
+
|
|
1252
|
+
|
|
1253
|
+
|
|
1254
|
+
|
|
1255
|
+
|
|
1256
|
+
<table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">id</code></td><td>string (uuid)</td><td>The file’s identity — this is the value an Image field stores in <code dir="auto">data</code>.</td><td><code dir="auto">9f1d3b7c-52ae-4d08-9b31-6c74a0f5e218</code></td></tr><tr><td><code dir="auto">filename</code></td><td>string, non-empty</td><td>The original filename, kept verbatim.</td><td><code dir="auto">cover.png</code></td></tr><tr><td><code dir="auto">mimeType</code></td><td>string, non-empty</td><td>Content type recorded at upload.</td><td><code dir="auto">image/png</code></td></tr><tr><td><code dir="auto">size</code></td><td>integer, 0 or more</td><td>Byte length, measured server-side.</td><td><code dir="auto">48213</code></td></tr><tr><td><code dir="auto">downloadUrl</code></td><td>string (absolute URL)</td><td>The stable <code dir="auto">/media/:id/:filename</code> path, made absolute against <code dir="auto">VELORA_PUBLIC_URL</code> when set, or the request’s own host otherwise. <strong>Permanent</strong> for a public file — safe to persist or embed, never re-signed, never expires. For a private file it carries a freshly minted, short-lived <code dir="auto">?token=</code> instead, and the bare path 404s for anyone without that token, a session, or a read-scoped key — same body as an unknown id, so this route never confirms a private id exists.</td><td><code dir="auto">https://cms.example.com/media/9f1d3b7c…/cover.png</code></td></tr><tr><td><code dir="auto">visibility</code></td><td><code dir="auto">"public"</code> | <code dir="auto">"private"</code></td><td>Whether <code dir="auto">downloadUrl</code> needed a token.</td><td><code dir="auto">public</code></td></tr><tr><td><code dir="auto">uploadedAt</code></td><td>ISO datetime string</td><td>When the record was written.</td><td><code dir="auto">2026-08-31T09:12:04.006Z</code></td></tr></tbody></table>
|
|
1257
|
+
<p>The uploader’s user id and the path-relative admin <code dir="auto">url</code> are on the admin
|
|
1258
|
+
shape only, deliberately — see <a href="/docs/v1/using-velora/media/">Media</a>. There is no
|
|
1253
1259
|
public upload route: writes to the media library go through the admin API
|
|
1254
1260
|
with a signed-in session.</p>
|
|
1255
1261
|
<div class="sl-heading-wrapper level-h2"><h2 id="templates">Templates</h2><a class="sl-anchor-link" href="#templates"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “Templates”</span></a></div>
|
|
@@ -209,7 +209,9 @@ a worked frontend that renders content from a live Velora instance.</li>
|
|
|
209
209
|
so it travels with your instance and always matches the version you are
|
|
210
210
|
running. Documentation for <em>writing</em> plugins is hosted only, at
|
|
211
211
|
<a href="https://docs.velora-cms.com/v1/building-plugins/">docs.velora-cms.com/v1/building-plugins</a>.</p></div></aside>
|
|
212
|
-
<aside aria-label="
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
212
|
+
<aside aria-label="Tip" class="starlight-aside starlight-aside--tip"><p class="starlight-aside__title" aria-hidden="true"><svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" class="starlight-aside__icon"><path fill-rule="evenodd" d="M1.44 8.855v-.001l3.527-3.516c.34-.344.802-.541 1.285-.548h6.649l.947-.947c3.07-3.07 6.207-3.072 7.62-2.868a1.821 1.821 0 0 1 1.557 1.557c.204 1.413.203 4.55-2.868 7.62l-.946.946v6.649a1.845 1.845 0 0 1-.549 1.286l-3.516 3.528a1.844 1.844 0 0 1-3.11-.944l-.858-4.275-4.52-4.52-2.31-.463-1.964-.394A1.847 1.847 0 0 1 .98 10.693a1.843 1.843 0 0 1 .46-1.838Zm5.379 2.017-3.873-.776L6.32 6.733h4.638l-4.14 4.14Zm8.403-5.655c2.459-2.46 4.856-2.463 5.89-2.33.134 1.035.13 3.432-2.329 5.891l-6.71 6.71-3.561-3.56 6.71-6.711Zm-1.318 15.837-.776-3.873 4.14-4.14v4.639l-3.364 3.374Z" clip-rule="evenodd"></path><path d="M9.318 18.345a.972.972 0 0 0-1.86-.561c-.482 1.435-1.687 2.204-2.934 2.619a8.22 8.22 0 0 1-1.23.302c.062-.365.157-.79.303-1.229.415-1.247 1.184-2.452 2.62-2.935a.971.971 0 1 0-.62-1.842c-.12.04-.236.084-.35.13-2.02.828-3.012 2.588-3.493 4.033a10.383 10.383 0 0 0-.51 2.845l-.001.016v.063c0 .536.434.972.97.972H2.24a7.21 7.21 0 0 0 .878-.065c.527-.063 1.248-.19 2.02-.447 1.445-.48 3.205-1.472 4.033-3.494a5.828 5.828 0 0 0 .147-.407Z"></path></svg>Tip</p><div class="starlight-aside__content"><p>Run your own instance with one command: <code dir="auto">npm create velora@latest my-site</code> —
|
|
213
|
+
it writes a plain Node project (a <code dir="auto">server.js</code> you own, plus the admin UI
|
|
214
|
+
copied into <code dir="auto">public/</code>); see <a href="/docs/v1/using-velora/installation/">Installation</a>
|
|
215
|
+
for the full layout. The hosted demo above needs no install at all, and
|
|
216
|
+
contributors can still work from source via
|
|
217
|
+
<a href="/docs/v1/using-velora/getting-started/">Getting started</a>.</p></div></aside></div><footer class="sl-flex astro-gjkl2a5w"><div class="meta sl-flex astro-gjkl2a5w"></div><div class="pagination-links print:hidden astro-3tj6ibx2" dir="ltr"><a href="/docs/v1/using-velora/getting-started/" rel="next" class="astro-3tj6ibx2"><svg aria-hidden="true" class="astro-3tj6ibx2 astro-24n5ceuf" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1.5rem;"><path d="M17.92 11.62a1.001 1.001 0 0 0-.21-.33l-5-5a1.003 1.003 0 1 0-1.42 1.42l3.3 3.29H7a1 1 0 0 0 0 2h7.59l-3.3 3.29a1.002 1.002 0 0 0 .325 1.639 1 1 0 0 0 1.095-.219l5-5a1 1 0 0 0 .21-.33 1 1 0 0 0 0-.76Z"/></svg><span class="astro-3tj6ibx2">Next<br class="astro-3tj6ibx2"><span class="link-title astro-3tj6ibx2">Getting started</span></span></a></div></footer><div class="velora-bundle-stamp astro-llmj63sw">Velora 0.9.0 · bundled documentation</div></div></div></main></div></div></div></div></body></html>
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
})();
|
|
71
71
|
</script></sl-sidebar-state-persist><div class="md:sl-hidden"><div class="mobile-preferences sl-flex astro-yfet6llj"><div class="social-icons astro-yfet6llj"></div><starlight-theme-select><label style="--sl-select-width: 6.25em" class="astro-7uz3f5wr"><span class="sr-only astro-7uz3f5wr">Select theme</span><svg aria-hidden="true" class="icon label-icon astro-7uz3f5wr astro-24n5ceuf" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1em;"><path d="M21 14h-1V7a3 3 0 0 0-3-3H7a3 3 0 0 0-3 3v7H3a1 1 0 0 0-1 1v2a3 3 0 0 0 3 3h14a3 3 0 0 0 3-3v-2a1 1 0 0 0-1-1ZM6 7a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v7H6V7Zm14 10a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-1h16v1Z"/></svg><select autocomplete="off" class="astro-7uz3f5wr"><option value="dark" class="astro-7uz3f5wr">Dark</option><option value="light" class="astro-7uz3f5wr">Light</option><option value="auto" selected class="astro-7uz3f5wr">Auto</option></select><svg aria-hidden="true" class="icon caret astro-7uz3f5wr astro-24n5ceuf" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1em;"><path d="M17 9.17a1 1 0 0 0-1.41 0L12 12.71 8.46 9.17a1 1 0 1 0-1.41 1.42l4.24 4.24a1.002 1.002 0 0 0 1.42 0L17 10.59a1.002 1.002 0 0 0 0-1.42Z"/></svg></label></starlight-theme-select><script>
|
|
72
72
|
StarlightThemeProvider.updatePickers();
|
|
73
|
-
</script></div></div></div></div></nav><div class="main-frame astro-5auw3bkx"><script type="module">var e=document.getElementById(`starlight__sidebar`),t=e?.querySelector(`sl-sidebar-state-persist`),n=`sl-sidebar-state`,r=()=>{let r=[],i=t?.dataset.hash||``;try{let e=sessionStorage.getItem(n),t=JSON.parse(e||`{}`);Array.isArray(t.open)&&t.hash===i&&(r=t.open)}catch{}return{hash:i,open:r,scroll:e?.scrollTop||0}},i=e=>{try{sessionStorage.setItem(n,JSON.stringify(e))}catch{}},a=()=>i(r()),o=(e,t)=>{let n=r();n.open[t]=e,i(n)};t?.addEventListener(`click`,e=>{if(!(e.target instanceof Element))return;let t=e.target.closest(`summary`)?.closest(`details`);if(!t)return;let n=t.querySelector(`sl-sidebar-restore`),r=parseInt(n?.dataset.index||``);isNaN(r)||o(!t.open,r)}),addEventListener(`visibilitychange`,()=>{document.visibilityState===`hidden`&&a()});</script><div class="lg:sl-flex astro-perbdhd2"><aside class="right-sidebar-container print:hidden astro-perbdhd2"><div class="right-sidebar astro-perbdhd2"><div class="lg:sl-hidden astro-epm5tkrc"><mobile-starlight-toc data-min-h="2" data-max-h="3" class="astro-bnvebtr4"><nav aria-labelledby="starlight__on-this-page--mobile" class="astro-bnvebtr4"><details id="starlight__mobile-toc" class="astro-bnvebtr4"><summary id="starlight__on-this-page--mobile" class="sl-flex astro-bnvebtr4"><span class="toggle sl-flex astro-bnvebtr4">On this page<svg aria-hidden="true" class="caret astro-bnvebtr4 astro-24n5ceuf" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1rem;"><path d="m14.83 11.29-4.24-4.24a1 1 0 1 0-1.42 1.41L12.71 12l-3.54 3.54a1 1 0 0 0 0 1.41 1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.24-4.24a1.002 1.002 0 0 0 0-1.42Z"/></svg></span><span class="display-current astro-bnvebtr4"></span></summary><div class="dropdown astro-bnvebtr4"><ul class="isMobile astro-lptfoybu" style="--depth: 0;"><li style="--depth: 0;" class="astro-lptfoybu"><a href="#_top" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Overview</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#what-the-wizard-does" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">What the wizard does</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#step-1--the-first-admin-account" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Step 1 — the first admin account</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#step-2--the-database" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Step 2 — the database</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#step-3--the-starter-template" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Step 3 — the starter template</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#the-wizards-api" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">The wizard’s API</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#choosing-a-database" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Choosing a database</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#configuring-the-server" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Configuring the server</span></a><ul class="isMobile astro-lptfoybu" style="--depth: 1;"><li style="--depth: 1;" class="astro-lptfoybu"><a href="#core" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">Core</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#features" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">Features</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#media-storage" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">Media storage</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#limits" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">Limits</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#licensing" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">Licensing</span></a></li></ul></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#after-setup" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">After setup</span></a></li></ul></div></details></nav></mobile-starlight-toc><script type="module" src="/docs/_astro/MobileTableOfContents.astro_astro_type_script_index_0_lang.BcSo_yiZ.js"></script></div><div class="right-sidebar-panel sl-hidden lg:sl-block astro-epm5tkrc"><div class="sl-container astro-epm5tkrc"><starlight-toc data-min-h="2" data-max-h="3"><nav aria-labelledby="starlight__on-this-page"><h2 id="starlight__on-this-page">On this page</h2><ul class="astro-lptfoybu" style="--depth: 0;"><li style="--depth: 0;" class="astro-lptfoybu"><a href="#_top" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Overview</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#what-the-wizard-does" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">What the wizard does</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#step-1--the-first-admin-account" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Step 1 — the first admin account</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#step-2--the-database" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Step 2 — the database</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#step-3--the-starter-template" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Step 3 — the starter template</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#the-wizards-api" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">The wizard’s API</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#choosing-a-database" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Choosing a database</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#configuring-the-server" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Configuring the server</span></a><ul class="astro-lptfoybu" style="--depth: 1;"><li style="--depth: 1;" class="astro-lptfoybu"><a href="#core" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">Core</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#features" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">Features</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#media-storage" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">Media storage</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#limits" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">Limits</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#licensing" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">Licensing</span></a></li></ul></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#after-setup" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">After setup</span></a></li></ul></nav></starlight-toc><script type="module" src="/docs/_astro/TableOfContents.astro_astro_type_script_index_0_lang.Csloo1VZ.js"></script></div></div></div></aside><div class="main-pane astro-perbdhd2"><main data-pagefind-body class="astro-ewhz5xxr" lang="en" dir="ltr"><div class="content-panel astro-ahkzlqop"><div class="sl-container astro-ahkzlqop"><h1 id="_top" class="astro-oef2efwn">Installation</h1></div></div><div class="content-panel astro-ahkzlqop"><div class="sl-container astro-ahkzlqop"><div class="sl-markdown-content"><p>Velora ships as a self-hosted application: <code dir="auto">apps/server</code> (the API) and
|
|
73
|
+
</script></div></div></div></div></nav><div class="main-frame astro-5auw3bkx"><script type="module">var e=document.getElementById(`starlight__sidebar`),t=e?.querySelector(`sl-sidebar-state-persist`),n=`sl-sidebar-state`,r=()=>{let r=[],i=t?.dataset.hash||``;try{let e=sessionStorage.getItem(n),t=JSON.parse(e||`{}`);Array.isArray(t.open)&&t.hash===i&&(r=t.open)}catch{}return{hash:i,open:r,scroll:e?.scrollTop||0}},i=e=>{try{sessionStorage.setItem(n,JSON.stringify(e))}catch{}},a=()=>i(r()),o=(e,t)=>{let n=r();n.open[t]=e,i(n)};t?.addEventListener(`click`,e=>{if(!(e.target instanceof Element))return;let t=e.target.closest(`summary`)?.closest(`details`);if(!t)return;let n=t.querySelector(`sl-sidebar-restore`),r=parseInt(n?.dataset.index||``);isNaN(r)||o(!t.open,r)}),addEventListener(`visibilitychange`,()=>{document.visibilityState===`hidden`&&a()});</script><div class="lg:sl-flex astro-perbdhd2"><aside class="right-sidebar-container print:hidden astro-perbdhd2"><div class="right-sidebar astro-perbdhd2"><div class="lg:sl-hidden astro-epm5tkrc"><mobile-starlight-toc data-min-h="2" data-max-h="3" class="astro-bnvebtr4"><nav aria-labelledby="starlight__on-this-page--mobile" class="astro-bnvebtr4"><details id="starlight__mobile-toc" class="astro-bnvebtr4"><summary id="starlight__on-this-page--mobile" class="sl-flex astro-bnvebtr4"><span class="toggle sl-flex astro-bnvebtr4">On this page<svg aria-hidden="true" class="caret astro-bnvebtr4 astro-24n5ceuf" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="--sl-icon-size: 1rem;"><path d="m14.83 11.29-4.24-4.24a1 1 0 1 0-1.42 1.41L12.71 12l-3.54 3.54a1 1 0 0 0 0 1.41 1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.24-4.24a1.002 1.002 0 0 0 0-1.42Z"/></svg></span><span class="display-current astro-bnvebtr4"></span></summary><div class="dropdown astro-bnvebtr4"><ul class="isMobile astro-lptfoybu" style="--depth: 0;"><li style="--depth: 0;" class="astro-lptfoybu"><a href="#_top" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Overview</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#what-the-wizard-does" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">What the wizard does</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#whats-in-the-scaffolded-folder" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">What’s in the scaffolded folder</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#step-1--the-first-admin-account" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Step 1 — the first admin account</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#step-2--the-database" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Step 2 — the database</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#step-3--the-starter-template" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Step 3 — the starter template</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#the-wizards-api" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">The wizard’s API</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#choosing-a-database" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Choosing a database</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#configuring-the-server" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Configuring the server</span></a><ul class="isMobile astro-lptfoybu" style="--depth: 1;"><li style="--depth: 1;" class="astro-lptfoybu"><a href="#core" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">Core</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#shared-hosting" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">Shared hosting</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#features" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">Features</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#media-storage" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">Media storage</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#limits" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">Limits</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#licensing" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">Licensing</span></a></li></ul></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#after-setup" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">After setup</span></a></li></ul></div></details></nav></mobile-starlight-toc><script type="module" src="/docs/_astro/MobileTableOfContents.astro_astro_type_script_index_0_lang.BcSo_yiZ.js"></script></div><div class="right-sidebar-panel sl-hidden lg:sl-block astro-epm5tkrc"><div class="sl-container astro-epm5tkrc"><starlight-toc data-min-h="2" data-max-h="3"><nav aria-labelledby="starlight__on-this-page"><h2 id="starlight__on-this-page">On this page</h2><ul class="astro-lptfoybu" style="--depth: 0;"><li style="--depth: 0;" class="astro-lptfoybu"><a href="#_top" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Overview</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#what-the-wizard-does" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">What the wizard does</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#whats-in-the-scaffolded-folder" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">What’s in the scaffolded folder</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#step-1--the-first-admin-account" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Step 1 — the first admin account</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#step-2--the-database" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Step 2 — the database</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#step-3--the-starter-template" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Step 3 — the starter template</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#the-wizards-api" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">The wizard’s API</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#choosing-a-database" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Choosing a database</span></a></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#configuring-the-server" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">Configuring the server</span></a><ul class="astro-lptfoybu" style="--depth: 1;"><li style="--depth: 1;" class="astro-lptfoybu"><a href="#core" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">Core</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#shared-hosting" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">Shared hosting</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#features" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">Features</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#media-storage" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">Media storage</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#limits" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">Limits</span></a></li><li style="--depth: 1;" class="astro-lptfoybu"><a href="#licensing" style="--depth: 1;" class="astro-lptfoybu"><span style="--depth: 1;" class="astro-lptfoybu">Licensing</span></a></li></ul></li><li style="--depth: 0;" class="astro-lptfoybu"><a href="#after-setup" style="--depth: 0;" class="astro-lptfoybu"><span style="--depth: 0;" class="astro-lptfoybu">After setup</span></a></li></ul></nav></starlight-toc><script type="module" src="/docs/_astro/TableOfContents.astro_astro_type_script_index_0_lang.Csloo1VZ.js"></script></div></div></div></aside><div class="main-pane astro-perbdhd2"><main data-pagefind-body class="astro-ewhz5xxr" lang="en" dir="ltr"><div class="content-panel astro-ahkzlqop"><div class="sl-container astro-ahkzlqop"><h1 id="_top" class="astro-oef2efwn">Installation</h1></div></div><div class="content-panel astro-ahkzlqop"><div class="sl-container astro-ahkzlqop"><div class="sl-markdown-content"><p>Velora ships as a self-hosted application: <code dir="auto">apps/server</code> (the API) and
|
|
74
74
|
<code dir="auto">apps/admin-ui</code> (the back office) run against a database you own. There
|
|
75
75
|
is no hosted signup step — the first time the server boots against an
|
|
76
76
|
unconfigured database, it serves the setup wizard instead of the normal
|
|
@@ -115,12 +115,26 @@ this off with <code dir="auto">VELORA_ADMIN=off</code> (see <code dir="auto">VEL
|
|
|
115
115
|
affecting the API; and if the admin bundle is missing entirely, <code dir="auto">/velora/*</code>
|
|
116
116
|
degrades to a clear 404 naming the cause rather than breaking anything
|
|
117
117
|
else.</p>
|
|
118
|
-
<aside aria-label="
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
<
|
|
122
|
-
|
|
123
|
-
|
|
118
|
+
<aside aria-label="Tip" class="starlight-aside starlight-aside--tip"><p class="starlight-aside__title" aria-hidden="true"><svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" class="starlight-aside__icon"><path fill-rule="evenodd" d="M1.44 8.855v-.001l3.527-3.516c.34-.344.802-.541 1.285-.548h6.649l.947-.947c3.07-3.07 6.207-3.072 7.62-2.868a1.821 1.821 0 0 1 1.557 1.557c.204 1.413.203 4.55-2.868 7.62l-.946.946v6.649a1.845 1.845 0 0 1-.549 1.286l-3.516 3.528a1.844 1.844 0 0 1-3.11-.944l-.858-4.275-4.52-4.52-2.31-.463-1.964-.394A1.847 1.847 0 0 1 .98 10.693a1.843 1.843 0 0 1 .46-1.838Zm5.379 2.017-3.873-.776L6.32 6.733h4.638l-4.14 4.14Zm8.403-5.655c2.459-2.46 4.856-2.463 5.89-2.33.134 1.035.13 3.432-2.329 5.891l-6.71 6.71-3.561-3.56 6.71-6.711Zm-1.318 15.837-.776-3.873 4.14-4.14v4.639l-3.364 3.374Z" clip-rule="evenodd"></path><path d="M9.318 18.345a.972.972 0 0 0-1.86-.561c-.482 1.435-1.687 2.204-2.934 2.619a8.22 8.22 0 0 1-1.23.302c.062-.365.157-.79.303-1.229.415-1.247 1.184-2.452 2.62-2.935a.971.971 0 1 0-.62-1.842c-.12.04-.236.084-.35.13-2.02.828-3.012 2.588-3.493 4.033a10.383 10.383 0 0 0-.51 2.845l-.001.016v.063c0 .536.434.972.97.972H2.24a7.21 7.21 0 0 0 .878-.065c.527-.063 1.248-.19 2.02-.447 1.445-.48 3.205-1.472 4.033-3.494a5.828 5.828 0 0 0 .147-.407Z"></path></svg>Tip</p><div class="starlight-aside__content"><p>The fastest install is one command:</p><div class="expressive-code"><link rel="stylesheet" href="/docs/_astro/ec.w36nc.css"><script type="module" src="/docs/_astro/ec.0vx5m.js"></script><figure class="frame is-terminal not-content"><figcaption class="header"><span class="title"></span><span class="sr-only">Terminal window</span></figcaption><pre data-language="sh"><code><div class="ec-line"><div class="code"><span style="--0:#82AAFF;--1:#3B61B0">npm</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#ECC48D;--1:#3B61B0">create</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#ECC48D;--1:#3B61B0">velora@latest</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#ECC48D;--1:#3B61B0">my-site</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="npm create velora@latest my-site"><div></div></button></div></figure></div><p>It scaffolds a project folder, installs the server from npm, and either
|
|
119
|
+
hands you this wizard in the browser (Quick) or completes the whole
|
|
120
|
+
setup from the terminal (Custom). <code dir="auto">cd my-site && npm run dev</code>, open
|
|
121
|
+
<code dir="auto">http://localhost:3000/velora</code>, and continue below. Contributors who
|
|
122
|
+
want to run from source instead: see
|
|
123
|
+
<a href="https://docs.velora-cms.com/v1/contributing/local-setup/">Local setup</a>.</p></div></aside>
|
|
124
|
+
<div class="sl-heading-wrapper level-h2"><h2 id="whats-in-the-scaffolded-folder">What’s in the scaffolded folder</h2><a class="sl-anchor-link" href="#whats-in-the-scaffolded-folder"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “What’s in the scaffolded folder”</span></a></div>
|
|
125
|
+
<p><code dir="auto">npm create velora@latest</code> writes a normal Node project you can read and
|
|
126
|
+
extend — the Velora engine itself lives in <code dir="auto">node_modules</code> and updates with
|
|
127
|
+
<code dir="auto">npm update</code>; everything else is a visible file that’s yours:</p>
|
|
128
|
+
<div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="plaintext"><code><div class="ec-line"><div class="code"><span style="--0:#d6deeb;--1:#403f53">my-site/</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#d6deeb;--1:#403f53"> </span></span><span style="--0:#d6deeb;--1:#403f53">server.js your entry point — imports the Velora engine from</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#d6deeb;--1:#403f53"> </span></span><span style="--0:#d6deeb;--1:#403f53">node_modules and starts it; add your own routes,</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#d6deeb;--1:#403f53"> </span></span><span style="--0:#d6deeb;--1:#403f53">middleware, or redirects here</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#d6deeb;--1:#403f53"> </span></span><span style="--0:#d6deeb;--1:#403f53">public/</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#d6deeb;--1:#403f53"> </span></span><span style="--0:#d6deeb;--1:#403f53">velora/</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#d6deeb;--1:#403f53"> </span></span><span style="--0:#d6deeb;--1:#403f53">admin/ the admin UI, copied from the installed server</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#d6deeb;--1:#403f53"> </span></span><span style="--0:#d6deeb;--1:#403f53">package (re-copy with `npm run sync-admin`)</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#d6deeb;--1:#403f53"> </span></span><span style="--0:#d6deeb;--1:#403f53">media/ uploaded files (in .gitignore, like velora.db)</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#d6deeb;--1:#403f53"> </span></span><span style="--0:#d6deeb;--1:#403f53">templates/ reserved for a future release — templates created</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#d6deeb;--1:#403f53"> </span></span><span style="--0:#d6deeb;--1:#403f53">in the admin will land here as editable files</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#d6deeb;--1:#403f53"> </span></span><span style="--0:#d6deeb;--1:#403f53">velora.db content + settings — SQLite only, appears after</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#d6deeb;--1:#403f53"> </span></span><span style="--0:#d6deeb;--1:#403f53">setup. With PostgreSQL/MySQL the data lives in the</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#d6deeb;--1:#403f53"> </span></span><span style="--0:#d6deeb;--1:#403f53">database server instead, and only DATABASE_URL in</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#d6deeb;--1:#403f53"> </span></span><span style="--0:#d6deeb;--1:#403f53">.env points at it.</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#d6deeb;--1:#403f53"> </span></span><span style="--0:#d6deeb;--1:#403f53">.env JWT_SECRET, VELORA_ADMIN_DIR, and (PostgreSQL/MySQL)</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#d6deeb;--1:#403f53"> </span></span><span style="--0:#d6deeb;--1:#403f53">DATABASE_URL</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#d6deeb;--1:#403f53"> </span></span><span style="--0:#d6deeb;--1:#403f53">.gitignore</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#d6deeb;--1:#403f53"> </span></span><span style="--0:#d6deeb;--1:#403f53">package.json</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#d6deeb;--1:#403f53"> </span></span><span style="--0:#d6deeb;--1:#403f53">README.md</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#d6deeb;--1:#403f53"> </span></span><span style="--0:#d6deeb;--1:#403f53">docker-compose.yml Custom mode with PostgreSQL or MySQL only</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="my-site/ server.js your entry point — imports the Velora engine from node_modules and starts it; add your own routes, middleware, or redirects here public/ velora/ admin/ the admin UI, copied from the installed server package (re-copy with `npm run sync-admin`) media/ uploaded files (in .gitignore, like velora.db) templates/ reserved for a future release — templates created in the admin will land here as editable files velora.db content + settings — SQLite only, appears after setup. With PostgreSQL/MySQL the data lives in the database server instead, and only DATABASE_URL in .env points at it. .env JWT_SECRET, VELORA_ADMIN_DIR, and (PostgreSQL/MySQL) DATABASE_URL .gitignore package.json README.md docker-compose.yml Custom mode with PostgreSQL or MySQL only"><div></div></button></div></figure></div>
|
|
129
|
+
<p><code dir="auto">server.js</code> is yours to extend — nothing between <code dir="auto">createApp()</code> and
|
|
130
|
+
<code dir="auto">listen()</code> is ever overwritten by an update. Add a route, for example:</p>
|
|
131
|
+
<div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="js"><code><div class="ec-line"><div class="code"><span style="--0:#7FDBCA;--1:#096E72">app</span><span style="--0:#C792EA;--1:#8844AE">.</span><span style="--0:#82AAFF;--1:#3B61B0">get</span><span style="--0:#D6DEEB;--1:#403F53">(</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">/hello</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">, </span><span style="--0:#C792EA;--1:#8844AE">async</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#D9F5DD;--1:#111111">()</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#C792EA;--1:#8844AE">=></span><span style="--0:#D6DEEB;--1:#403F53"> ({ hello: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#ECC48D;--1:#984E4D">world</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53"> }));</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="app.get("/hello", async () => ({ hello: "world" }));"><div></div></button></div></figure></div>
|
|
132
|
+
<p><code dir="auto">public/velora/admin/</code> is a copy of the admin UI, not a live link into
|
|
133
|
+
<code dir="auto">node_modules</code>, so keep it in sync after an update:</p>
|
|
134
|
+
<div class="expressive-code"><figure class="frame is-terminal not-content"><figcaption class="header"><span class="title"></span><span class="sr-only">Terminal window</span></figcaption><pre data-language="sh"><code><div class="ec-line"><div class="code"><span style="--0:#82AAFF;--1:#3B61B0">npm</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#ECC48D;--1:#3B61B0">update</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#ECC48D;--1:#3B61B0">@velora-cms/server</span><span style="--0:#D6DEEB;--1:#403F53"> && </span><span style="--0:#82AAFF;--1:#3B61B0">npm</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#ECC48D;--1:#3B61B0">run</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#ECC48D;--1:#3B61B0">sync-admin</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="npm update @velora-cms/server && npm run sync-admin"><div></div></button></div></figure></div>
|
|
135
|
+
<p>Skip that and the server still boots and serves the (stale) copy — it
|
|
136
|
+
just logs one warning at startup naming the version mismatch, rather than
|
|
137
|
+
overwriting files you might have touched.</p>
|
|
124
138
|
<div class="sl-heading-wrapper level-h2"><h2 id="step-1--the-first-admin-account">Step 1 — the first admin account</h2><a class="sl-anchor-link" href="#step-1--the-first-admin-account"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “Step 1 — the first admin account”</span></a></div>
|
|
125
139
|
<p>The wizard opens on “Set up Velora”. All four fields are validated in the
|
|
126
140
|
browser before it will advance, and the first three are re-validated on
|
|
@@ -260,7 +274,7 @@ admin can install the template later from the marketplace instead.</p>
|
|
|
260
274
|
<p>The wizard is an ordinary client of two public routes; you can drive them
|
|
261
275
|
directly, which is what an automated provisioning script would do. Ask a
|
|
262
276
|
fresh instance what it still needs:</p>
|
|
263
|
-
<div class="expressive-code"><
|
|
277
|
+
<div class="expressive-code"><figure class="frame is-terminal not-content"><figcaption class="header"><span class="title"></span><span class="sr-only">Terminal window</span></figcaption><pre data-language="sh"><code><div class="ec-line"><div class="code"><span style="--0:#82AAFF;--1:#3B61B0">curl</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#82AAFF;--1:#3B61B0">-s</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#ECC48D;--1:#3B61B0">http://localhost:3000/api/setup/status</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="curl -s http://localhost:3000/api/setup/status"><div></div></button></div></figure></div>
|
|
264
278
|
<p>On an unconfigured instance it reports that setup is required, that no
|
|
265
279
|
database is configured, and which templates it can offer:</p>
|
|
266
280
|
<div class="expressive-code"><figure class="frame not-content"><figcaption class="header"></figcaption><pre data-language="json"><code><div class="ec-line"><div class="code"><span style="--0:#D6DEEB;--1:#403F53">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"setupRequired"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#FF6A83;--1:#A24848">true</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"bundledTemplates"</span><span style="--0:#D6DEEB;--1:#403F53">: [</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#D6DEEB;--1:#403F53"> </span></span><span style="--0:#D6DEEB;--1:#403F53">{</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"pluginId"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">com.velora.blog</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"version"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">0.1.0</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"name"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">Blog</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"description"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#C789D6;--1:#7C5686">The official Velora blog template — posts, authors and tags, ready to publish.</span><span style="--0:#D9F5DD;--1:#111111">"</span><span style="--0:#D6DEEB;--1:#403F53">,</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"sampleContent"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#FF6A83;--1:#A24848">true</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#D6DEEB;--1:#403F53"> </span></span><span style="--0:#D6DEEB;--1:#403F53">}</span></div></div><div class="ec-line"><div class="code"><span class="indent"><span style="--0:#D6DEEB;--1:#403F53"> </span></span><span style="--0:#D6DEEB;--1:#403F53">],</span></div></div><div class="ec-line"><div class="code"><span class="indent"> </span><span style="--0:#7FDBCA;--1:#096E72">"databaseConfigured"</span><span style="--0:#D6DEEB;--1:#403F53">: </span><span style="--0:#FF6A83;--1:#A24848">false</span></div></div><div class="ec-line"><div class="code"><span style="--0:#D6DEEB;--1:#403F53">}</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="{ "setupRequired": true, "bundledTemplates": [ { "pluginId": "com.velora.blog", "version": "0.1.0", "name": "Blog", "description": "The official Velora blog template — posts, authors and tags, ready to publish.", "sampleContent": true } ], "databaseConfigured": false}"><div></div></button></div></figure></div>
|
|
@@ -590,6 +604,12 @@ sandbox and test setup and aren’t listed here.</p>
|
|
|
590
604
|
|
|
591
605
|
|
|
592
606
|
<table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">DATABASE_URL</code></td><td>string (URL)</td><td>Connection string for the configured database. No default: unset means the server boots into the full setup wizard, and the wizard writes this variable itself when it finishes. A relative SQLite path is made absolute once, at boot.</td><td><code dir="auto">postgresql://velora:velora@localhost:5432/velora</code></td></tr><tr><td><code dir="auto">JWT_SECRET</code></td><td>string</td><td>Signs and verifies the session tokens issued by <code dir="auto">/api/auth/*</code>. No default and no fallback — the server refuses to start with the error <code dir="auto">JWT_SECRET must be set — see .env.example</code>. Generate one with <code dir="auto">openssl rand -hex 32</code>.</td><td><code dir="auto">9f2c…</code> (64 hex chars)</td></tr><tr><td><code dir="auto">PORT</code></td><td>integer</td><td>TCP port the server listens on. Default <code dir="auto">3000</code>.</td><td><code dir="auto">8080</code></td></tr><tr><td><code dir="auto">HOST</code></td><td>string</td><td>Interface to bind. Default <code dir="auto">127.0.0.1</code> — loopback only. Set <code dir="auto">0.0.0.0</code> to accept connections from outside the host (behind a reverse proxy).</td><td><code dir="auto">0.0.0.0</code></td></tr><tr><td><code dir="auto">VELORA_ENV_FILE</code></td><td>string (path)</td><td>Which env file the server reads at boot and the setup wizard writes to. Default: <code dir="auto">.env</code> resolved against the working directory. A relative override is also resolved against the working directory.</td><td><code dir="auto">/srv/velora/config/velora.env</code></td></tr></tbody></table>
|
|
607
|
+
<div class="sl-heading-wrapper level-h3"><h3 id="shared-hosting">Shared hosting</h3><a class="sl-anchor-link" href="#shared-hosting"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “Shared hosting”</span></a></div>
|
|
608
|
+
<p>Because <code dir="auto">server.js</code> is a plain Node entry point, a scaffolded project runs
|
|
609
|
+
on hosts that expect one — cPanel, Passenger, and similar shared hosts —
|
|
610
|
+
with no separate build step:</p>
|
|
611
|
+
<div class="expressive-code"><figure class="frame is-terminal not-content"><figcaption class="header"><span class="title"></span><span class="sr-only">Terminal window</span></figcaption><pre data-language="sh"><code><div class="ec-line"><div class="code"><span style="--0:#82AAFF;--1:#3B61B0">node</span><span style="--0:#D6DEEB;--1:#403F53"> </span><span style="--0:#ECC48D;--1:#3B61B0">server.js</span></div></div></code></pre><div class="copy"><div aria-live="polite"></div><button title="Copy to clipboard" data-copied="Copied!" data-code="node server.js"><div></div></button></div></figure></div>
|
|
612
|
+
<p><code dir="auto">PORT</code> and <code dir="auto">HOST</code> above control where it listens.</p>
|
|
593
613
|
<div class="sl-heading-wrapper level-h3"><h3 id="features">Features</h3><a class="sl-anchor-link" href="#features"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “Features”</span></a></div>
|
|
594
614
|
|
|
595
615
|
|
|
@@ -686,12 +706,26 @@ sandbox and test setup and aren’t listed here.</p>
|
|
|
686
706
|
|
|
687
707
|
|
|
688
708
|
|
|
689
|
-
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
<table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">VELORA_HEADLESS</code></td><td><code dir="auto">"true"</code> | <code dir="auto">"1"</code></td><td>Registers the key-gated public <code dir="auto">/api/v1/*</code> namespace at boot. Default unset = off, and anything other than those two values is off. Deploy-time only; there is deliberately no runtime toggle, so changing it needs a restart.</td><td><code dir="auto">true</code></td></tr><tr><td><code dir="auto">VELORA_MARKETPLACE_URL</code></td><td>string (origin) | <code dir="auto">off</code></td><td>Which marketplace the admin browses. Default <code dir="auto">https://marketplace.velora-cms.com</code>. <code dir="auto">off</code> (any case) or a blank value removes the in-CMS marketplace routes entirely, for air-gapped installs. Any other value is treated as a custom marketplace origin; trailing slashes are stripped.</td><td><code dir="auto">https://marketplace.velora-cms.com</code></td></tr><tr><td><code dir="auto">VELORA_PUBLIC_DOCS</code></td><td><code dir="auto">"false"</code> | <code dir="auto">"0"</code></td><td>Serves the public OpenAPI UI at <code dir="auto">/api/v1/docs</code>. <strong>On by default</strong> — only an explicit <code dir="auto">false</code>/<code dir="auto">0</code> disables it, which 404s that URL without touching the <code dir="auto">/api/v1</code> API itself.</td><td><code dir="auto">false</code></td></tr><tr><td><code dir="auto">VELORA_ADMIN</code></td><td><code dir="auto">"off"</code></td><td>Serves the admin SPA at <code dir="auto">/velora</code>. <strong>On by default</strong> — only the literal <code dir="auto">off</code> (trimmed, case-insensitive) disables it; every other value, including unset, leaves it on. Turning it off never touches the API, only <code dir="auto">/velora/*</code> and the <code dir="auto">/</code> redirect below.</td><td><code dir="auto">off</code></td></tr><tr><td><code dir="auto">VELORA_ADMIN_DIR</code></td><td>string (path)</td><td>Override for the bundled admin UI served at <code dir="auto">/velora/</code>. A scaffolded project’s <code dir="auto">.env</code> sets this to <code dir="auto">public/velora/admin</code> by default, pointing at the copy <code dir="auto">npm create velora</code> writes there (see “What’s in the scaffolded folder” above); without it — or outside a scaffold — the server tries <code dir="auto"><server dist>/admin-bundle</code>, then <code dir="auto"><repo>/apps/admin-ui/dist-bundle</code>. The directory must exist and contain an <code dir="auto">index.html</code>, or <code dir="auto">/velora/*</code> answers a 404 naming the override as the cause — a missing bundle never fails boot.</td><td><code dir="auto">/srv/velora/admin-bundle</code></td></tr><tr><td><code dir="auto">VELORA_DOCS_DIR</code></td><td>string (path)</td><td>Override for the bundled documentation served at <code dir="auto">/docs/</code>. No default; without it the server tries <code dir="auto"><server dist>/docs-bundle</code>, then <code dir="auto"><repo>/apps/docs/dist-bundle</code>. The directory must exist and contain a <code dir="auto">v1</code> subdirectory, or <code dir="auto">/docs/*</code> answers a 404 that names the cause — a missing bundle never fails boot.</td><td><code dir="auto">/srv/velora/docs-bundle</code></td></tr><tr><td><code dir="auto">VELORA_PLUGINS_DIR</code></td><td>string (path)</td><td>Where installed plugin packages are stored on disk. Default <code dir="auto">.velora/plugins</code> under the working directory.</td><td><code dir="auto">/var/lib/velora/plugins</code></td></tr><tr><td><code dir="auto">VELORA_TRUSTED_SIGNING_KEYS</code></td><td>JSON string</td><td>Extra plugin signing keys this instance trusts, merged over the set shipped with the server so a key can be added or a shipped one overridden without waiting for a release. A marketplace install blocks unless the package is signed by one of these keys; a sideload never blocks either way. Invalid JSON is warned about and ignored rather than stopping the server. Tabled in full, with the key format, under <a href="/docs/v1/using-velora/installing-plugins/">Installing plugins</a>.</td><td><code dir="auto">{"keys":[{"keyId":"a1b2…","publicKey":"MCowBQ…"}]}</code></td></tr><tr><td><code dir="auto">VELORA_BUNDLED_TEMPLATES_DIR</code></td><td>string (path)</td><td>Override for the starter templates the wizard offers. No default; without it the server tries <code dir="auto"><server dist>/bundled-templates</code>, then <code dir="auto">apps/server/bundled-templates</code>. A missing or malformed tree degrades to an empty list rather than failing.</td><td><code dir="auto">/srv/velora/bundled-templates</code></td></tr><tr><td><code dir="auto">VELORA_SITE_DOMAIN</code></td><td>string (host)</td><td>This instance’s own domain, as the operator declares it — never taken from a request’s <code dir="auto">Host</code> header. A paid plugin’s license binds to this value once, so it must be stable. A scheme and trailing slash are tolerated and stripped; the value is lowercased. No default.</td><td><code dir="auto">example.com</code></td></tr><tr><td><code dir="auto">VELORA_PUBLIC_URL</code></td><td>string (origin)</td><td>Base URL used to make the <strong>public</strong> <code dir="auto">/api/v1/media</code> and GraphQL <code dir="auto">downloadUrl</code> fields absolute, e.g. <code dir="auto">https://cms.example.com/media/<id>/<filename></code> — a headless frontend fetches that URL from its own origin, so a path-relative value would resolve against the wrong host. Set this behind a reverse proxy/CDN where the request’s own <code dir="auto">Host</code>/protocol isn’t the public-facing one. Unset (or blank) means the base is derived per-request from the incoming <code dir="auto">Host</code> header and protocol instead (honors <code dir="auto">VELORA_TRUST_PROXY</code>) — fine for direct access. Doesn’t affect the backoffice <code dir="auto">/api/media</code> surface, which stays path-relative, same-origin with the admin SPA either way. Trailing slashes are stripped.</td><td><code dir="auto">https://cms.example.com</code></td></tr><tr><td><code dir="auto">VELORA_PREVIEW_BASE_URL</code></td><td>string (origin)</td><td>Base URL preview links are built against, as <code dir="auto"><base>/api/preview?token=…&id=…</code>. No default: unset (or blank) means no preview URL can be built. Trailing slashes are stripped.</td><td><code dir="auto">https://www.example.com</code></td></tr><tr><td><code dir="auto">VELORA_PREVIEW_TOKEN_TTL</code></td><td>integer (seconds)</td><td>How long a minted preview token stays valid. Default <code dir="auto">1800</code> (30 minutes).</td><td><code dir="auto">600</code></td></tr><tr><td><code dir="auto">VELORA_CORS_ORIGINS</code></td><td>comma-separated origins</td><td>Which browser origins may call public plugin routes cross-origin (<code dir="auto">GET</code>, <code dir="auto">POST</code>, <code dir="auto">OPTIONS</code> only). Default empty = no CORS headers at all, same-origin only. Entries that are not absolute origins — a trailing slash, a bare host, a path — are dropped with a warning.</td><td><code dir="auto">https://example.com,https://www.example.com</code></td></tr><tr><td><code dir="auto">VELORA_TRUST_PROXY</code></td><td><code dir="auto">true</code> | <code dir="auto">false</code> | integer | string</td><td>Whether, and how far, to trust <code dir="auto">X-Forwarded-*</code> when resolving the caller’s IP. Default unset = trust nothing, so every visitor behind a proxy collapses onto the proxy’s address — which flattens the per-IP rate lane. Set it only when genuinely behind a proxy you control; setting it otherwise lets a caller forge any IP.</td><td><code dir="auto">1</code></td></tr></tbody></table>
|
|
690
716
|
<div class="sl-heading-wrapper level-h3"><h3 id="media-storage">Media storage</h3><a class="sl-anchor-link" href="#media-storage"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “Media storage”</span></a></div>
|
|
691
|
-
<p>Media uploads go
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
717
|
+
<p>Media uploads go through one of two storage drivers — see
|
|
718
|
+
<a href="/docs/v1/using-velora/media/#where-the-bytes-live">Media</a> for the full
|
|
719
|
+
picture. Individual uploads are capped at 50 MiB.</p>
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
|
|
695
729
|
|
|
696
730
|
|
|
697
731
|
|
|
@@ -733,7 +767,19 @@ capped at 50 MiB.</p>
|
|
|
733
767
|
|
|
734
768
|
|
|
735
769
|
|
|
736
|
-
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
|
|
773
|
+
<table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">MEDIA_STORAGE</code></td><td><code dir="auto">local</code> | <code dir="auto">s3</code></td><td>Explicit driver choice; wins over the auto-detection below. Unset (or blank) falls through to that detection instead.</td><td><code dir="auto">local</code></td></tr><tr><td><code dir="auto">MEDIA_DIR</code></td><td>string (path)</td><td>Where the local driver writes files, resolved against the working directory like <code dir="auto">.env</code>. Default <code dir="auto">./media</code>, next to <code dir="auto">velora.db</code>. Only read when the local driver is active.</td><td><code dir="auto">./media</code></td></tr><tr><td><code dir="auto">S3_ENDPOINT</code></td><td>string (URL)</td><td>Storage endpoint. Default <code dir="auto">http://localhost:9000</code>, matching the repository’s local MinIO service.</td><td><code dir="auto">https://s3.eu-central-1.amazonaws.com</code></td></tr><tr><td><code dir="auto">S3_REGION</code></td><td>string</td><td>Bucket region. Default <code dir="auto">us-east-1</code>.</td><td><code dir="auto">eu-central-1</code></td></tr><tr><td><code dir="auto">S3_ACCESS_KEY_ID</code></td><td>string</td><td>Access key id. Default <code dir="auto">velora</code>.</td><td><code dir="auto">AKIA…</code></td></tr><tr><td><code dir="auto">S3_SECRET_ACCESS_KEY</code></td><td>string</td><td>Secret access key. Default <code dir="auto">velora-secret</code>.</td><td><code dir="auto">wJalr…</code></td></tr><tr><td><code dir="auto">S3_BUCKET</code></td><td>string</td><td>Bucket name. Default <code dir="auto">velora-media</code>.</td><td><code dir="auto">velora-media</code></td></tr></tbody></table>
|
|
774
|
+
<p>Driver selection, in order: <code dir="auto">MEDIA_STORAGE</code> wins outright when set to
|
|
775
|
+
<code dir="auto">local</code> or <code dir="auto">s3</code> (any other value fails to boot). Otherwise, any <code dir="auto">S3_*</code>
|
|
776
|
+
variable above being set to a non-empty value selects <code dir="auto">s3</code> automatically —
|
|
777
|
+
this is why the five <code dir="auto">S3_*</code> defaults only matter once something has
|
|
778
|
+
already opted into the <code dir="auto">s3</code> driver. With nothing set at all — the case for
|
|
779
|
+
a fresh <code dir="auto">npm create velora</code> scaffold — the instance gets the <code dir="auto">local</code>
|
|
780
|
+
driver with no further configuration, and uploads land under <code dir="auto">MEDIA_DIR</code>.
|
|
781
|
+
An empty string (<code dir="auto">MEDIA_STORAGE=</code> or <code dir="auto">S3_ENDPOINT=</code> left over in <code dir="auto">.env</code>)
|
|
782
|
+
counts as unset, the same as omitting the line.</p>
|
|
737
783
|
<div class="sl-heading-wrapper level-h3"><h3 id="limits">Limits</h3><a class="sl-anchor-link" href="#limits"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “Limits”</span></a></div>
|
|
738
784
|
<p>Rate limiting runs in three lanes, keyed by what the request actually
|
|
739
785
|
presents: a <code dir="auto">vk_</code> token that resolves to a live API key (<code dir="auto">key:</code>), a
|
|
@@ -843,7 +889,19 @@ Windows accept any duration string the limiter can parse (“1 minute”,
|
|
|
843
889
|
|
|
844
890
|
|
|
845
891
|
|
|
846
|
-
|
|
892
|
+
|
|
893
|
+
|
|
894
|
+
|
|
895
|
+
|
|
896
|
+
|
|
897
|
+
|
|
898
|
+
|
|
899
|
+
|
|
900
|
+
|
|
901
|
+
|
|
902
|
+
|
|
903
|
+
|
|
904
|
+
<table><thead><tr><th>Name</th><th>Type</th><th>Meaning</th><th>Example</th></tr></thead><tbody><tr><td><code dir="auto">VELORA_RATE_LIMIT_MAX</code></td><td>integer > 0</td><td>Requests per window for a caller presenting a valid API key (<code dir="auto">key:</code> lane). Default <code dir="auto">100</code>.</td><td><code dir="auto">500</code></td></tr><tr><td><code dir="auto">VELORA_RATE_LIMIT_IP_MAX</code></td><td>integer > 0</td><td>Requests per window for anonymous callers (<code dir="auto">ip:</code> lane), including public plugin routes. Also the ceiling for the setup scope. Default <code dir="auto">60</code>.</td><td><code dir="auto">120</code></td></tr><tr><td><code dir="auto">VELORA_RATE_LIMIT_WINDOW</code></td><td>duration string</td><td>The window for the shared three-lane limiter — the default for auth, docs, plugin routes, and the public API. Default <code dir="auto">1 minute</code>.</td><td><code dir="auto">1 minute</code></td></tr><tr><td><code dir="auto">VELORA_RATE_LIMIT_LOGIN</code></td><td>integer > 0</td><td><code dir="auto">POST /api/auth/login</code> attempts per window, keyed on IP alone. The tightest ceiling in the system. Default <code dir="auto">10</code>.</td><td><code dir="auto">10</code></td></tr><tr><td><code dir="auto">VELORA_RATE_LIMIT_LOGIN_WINDOW</code></td><td>duration string</td><td>Window for the login ceiling. Default <code dir="auto">15 minutes</code>.</td><td><code dir="auto">15 minutes</code></td></tr><tr><td><code dir="auto">VELORA_RATE_LIMIT_SETUP</code></td><td>integer > 0</td><td><code dir="auto">POST /api/setup/complete</code> attempts per window. Default <code dir="auto">5</code>.</td><td><code dir="auto">5</code></td></tr><tr><td><code dir="auto">VELORA_RATE_LIMIT_SETUP_WINDOW</code></td><td>duration string</td><td>Window for the setup ceiling. Default <code dir="auto">1 hour</code>.</td><td><code dir="auto">1 hour</code></td></tr><tr><td><code dir="auto">VELORA_RATE_LIMIT_DOCS</code></td><td>integer > 0</td><td>Requests per window for the bundled documentation served at <code dir="auto">/docs/</code>. Default <code dir="auto">120</code>.</td><td><code dir="auto">120</code></td></tr><tr><td><code dir="auto">VELORA_RATE_LIMIT_DOCS_WINDOW</code></td><td>duration string</td><td>Window for the bundled-documentation rate limit above. Default <code dir="auto">1 minute</code>.</td><td><code dir="auto">1 minute</code></td></tr><tr><td><code dir="auto">VELORA_RATE_LIMIT_MEDIA</code></td><td>integer > 0</td><td>Requests per window for the stable <code dir="auto">GET /media/:id/:filename</code> route (see <a href="/docs/v1/using-velora/media/">Media</a>), keyed on IP — it serves anonymous public-asset requests and headless consumers directly, so it can’t bucket on the three-lane limiter. Generous by design: one page load can fire many requests against it. Default <code dir="auto">300</code>.</td><td><code dir="auto">300</code></td></tr><tr><td><code dir="auto">VELORA_RATE_LIMIT_MEDIA_WINDOW</code></td><td>duration string</td><td>Window for the media rate limit above. Default <code dir="auto">1 minute</code>.</td><td><code dir="auto">1 minute</code></td></tr><tr><td><code dir="auto">VELORA_RATE_LIMIT_ADMIN</code></td><td>integer > 0</td><td>Requests per window for the admin SPA served at <code dir="auto">/velora/</code>, keyed on IP. Covers the page shell, the SPA fallback, and every hashed asset file the bundle serves — they share this one plugin-level ceiling because the asset routes carry no per-route rate config of their own. Default <code dir="auto">120</code>.</td><td><code dir="auto">120</code></td></tr><tr><td><code dir="auto">VELORA_RATE_LIMIT_ADMIN_WINDOW</code></td><td>duration string</td><td>Window for the admin rate limit above. Default <code dir="auto">1 minute</code>.</td><td><code dir="auto">1 minute</code></td></tr><tr><td><code dir="auto">VELORA_RATE_LIMIT_KEY_CACHE_TTL_MS</code></td><td>integer > 0 (milliseconds)</td><td>How long the limiter caches whether a presented API key is live, so repeat calls with the same key avoid a database lookup. Default <code dir="auto">30000</code>.</td><td><code dir="auto">30000</code></td></tr><tr><td><code dir="auto">VELORA_RATE_LIMIT_KEY_CACHE_MAX_ENTRIES</code></td><td>integer > 0</td><td>Hard ceiling on that cache, so a flood of never-repeating fake tokens cannot grow it without bound. Default <code dir="auto">5000</code>.</td><td><code dir="auto">5000</code></td></tr><tr><td><code dir="auto">VELORA_GRAPHQL_MAX_DEPTH</code></td><td>integer > 0</td><td>Maximum nesting depth accepted on the public GraphQL endpoint. Default <code dir="auto">8</code>.</td><td><code dir="auto">8</code></td></tr><tr><td><code dir="auto">VELORA_GRAPHQL_MAX_COMPLEXITY</code></td><td>integer > 0</td><td>Maximum field-count complexity accepted on the same endpoint. Default <code dir="auto">500</code>.</td><td><code dir="auto">500</code></td></tr></tbody></table>
|
|
847
905
|
<div class="sl-heading-wrapper level-h3"><h3 id="licensing">Licensing</h3><a class="sl-anchor-link" href="#licensing"><span aria-hidden="true" class="sl-anchor-icon"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 1 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 0 0-1.42-1.42m8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 1 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 0 0 0 1.42 1 1 0 0 0 1.42 0l3.88-3.89a4.49 4.49 0 0 0 0-6.33M8.83 15.17a1 1 0 0 0 .71.29 1 1 0 0 0 .71-.29l4.92-4.92a1 1 0 1 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42"></path></svg></span><span class="sr-only" data-pagefind-ignore="">Section titled “Licensing”</span></a></div>
|
|
848
906
|
<p>Paid plugins re-validate against the marketplace’s license server on a
|
|
849
907
|
schedule; both knobs are read fresh, so a change takes effect without a
|