domma-cms 0.7.5 → 0.7.6

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/admin/js/app.js CHANGED
@@ -1,6 +1,6 @@
1
- import{getSidebarConfig as J}from"./config/sidebar-config.js";import{views as K}from"./views/index.js";import{api as o,getUser as r,isAuthenticated as l,logout as H}from"./api.js";$(()=>{(async()=>{try{const t=l()?await o.settings.get():null;Domma.theme.init({theme:t?.adminTheme||"charcoal-dark",persist:!0})}catch{Domma.theme.init({theme:"charcoal-dark",persist:!0})}})();const k=["jb-company","jb-agent","jb-candidate"],P=["/job-board","/my-profile"];function d(t){return t&&k.includes(t.role)}function m(t){return P.some(e=>t===e||t.startsWith(e+"/"))}R.use(async(t,e,i)=>{if(t.path==="/login"||t.path==="/reset-password")return i();if(!l()){R.navigate("/login");return}if(d(r())&&!m(t.path)){R.navigate("/job-board");return}i()});let c=null;async function x(){if(!l())return{};try{const[t,e,i,a,s,n,W,B,T,V,I,U,_]=await Promise.all([o.pages.list().catch(()=>[]),o.media.list().catch(()=>[]),o.users.list().catch(()=>[]),o.plugins.list().catch(()=>[]),o.collections.list().catch(()=>[]),o.forms.list().catch(()=>[]),o.themes.list().catch(()=>[]),o.views.list().catch(()=>[]),o.actions.list().catch(()=>[]),o.blocks.list().catch(()=>[]),o.navigation.get().catch(()=>({})),o.layouts.get().catch(()=>({})),o.get("/collections/roles/entries?limit=100").catch(()=>({entries:[]}))]),C=I.items||[],O=C.filter(h=>!h.hidden).length,D=C.length,F=a.filter(h=>h.enabled).length,y=a.length;return{pages:t.length,media:e.length,users:i.length,plugins:y>0?`${F}/${y}`:null,collections:s.length,forms:n.length,themes:W.length,views:B.length,actions:T.length,blocks:V.length,navigation:D>0?`${O}/${D}`:null,layouts:Object.keys(U).length,roles:(_.entries||[]).length}}catch{return{}}}async function u(){try{return(await o.get("/auth/permissions")).permissions||[]}catch{return[]}}async function j(){try{return await o.get("/settings/db-status")}catch{return{configured:!1}}}async function g(t){c&&$("#admin-sidebar").empty();const[e,i]=await Promise.all([x(),j()]),a=w.map(s=>{if(!s.countKey)return s;const n=e[s.countKey];return{...s,badge:n!=null&&n>0?String(n):null}});if(c=Domma.elements.sidebar("#admin-sidebar",{header:{title:"CMS Admin",icon:"layout"},items:J(t,e,a),collapsible:!0,collapseAt:992,push:!0,contentSelector:".dashboard-main",top:"60px"}),A(),L(),i?.configured){const s=$("#admin-sidebar").find(".sidebar-header-title").first();s.length&&!s.find(".db-badge").length&&s.append('<span class="db-badge" title="MongoDB connected" style="display:inline-flex;align-items:center;background:#16a34a;color:#fff;font-size:10px;font-weight:700;padding:2px 6px;border-radius:9999px;letter-spacing:.5px;margin-left:6px;vertical-align:middle;line-height:1;">DB</span>')}}function A(){$("#admin-sidebar .sidebar-link").each(function(){const t=$(this).find(".sidebar-text").text().trim();t&&$(this).attr("data-tooltip",t)}),E.tooltip("#admin-sidebar [data-tooltip]",{placement:"right"})}function L(){$("#admin-sidebar .sidebar-heading").each(function(){const t=$(this);t.addClass("sidebar-heading--collapsible"),t.append('<span class="sidebar-heading-toggle"><span data-icon="chevron-down"></span></span>'),t.on("click",function(){const e=!t.hasClass("is-collapsed");t.toggleClass("is-collapsed",e);let i=t.next();for(;i.length&&!i.hasClass("sidebar-heading")&&!i.hasClass("sidebar-divider");)i.toggle(!e),i=i.next()})}),Domma.icons.scan("#admin-sidebar")}M.subscribe("router:afterChange",({to:t})=>{c&&t.path!=="/login"&&t.path!=="/reset-password"&&c.setActive("#"+t.path)});const N=[{path:"/",view:"dashboard",title:"Dashboard - Domma CMS"},{path:"/pages",view:"pages",title:"Pages - Domma CMS"},{path:"/pages/new",view:"pageEditor",title:"New Page - Domma CMS"},{path:"/pages/edit/*",view:"pageEditor",title:"Edit Page - Domma CMS"},{path:"/media",view:"media",title:"Media - Domma CMS"},{path:"/navigation",view:"navigation",title:"Navigation - Domma CMS"},{path:"/layouts",view:"layouts",title:"Layouts - Domma CMS"},{path:"/settings",view:"settings",title:"Settings - Domma CMS"},{path:"/users",view:"users",title:"Users - Domma CMS"},{path:"/users/new",view:"userEditor",title:"New User - Domma CMS"},{path:"/users/edit/:id",view:"userEditor",title:"Edit User - Domma CMS"},{path:"/plugins",view:"plugins",title:"Plugins - Domma CMS"},{path:"/documentation",view:"documentation",title:"Usage - Domma CMS"},{path:"/tutorials",view:"tutorials",title:"Tutorials - Domma CMS"},{path:"/api-reference",view:"apiReference",title:"API Reference - Domma CMS"},{path:"/collections",view:"collections",title:"Collections - Domma CMS"},{path:"/collections/new",view:"collectionEditor",title:"New Collection - Domma CMS"},{path:"/collections/edit/:slug",view:"collectionEditor",title:"Edit Collection - Domma CMS"},{path:"/collections/:slug/entries",view:"collectionEntries",title:"Entries - Domma CMS"},{path:"/forms",view:"forms",title:"Forms - Domma CMS"},{path:"/forms/new",view:"formEditor",title:"New Form - Domma CMS"},{path:"/forms/edit/:slug",view:"formEditor",title:"Edit Form - Domma CMS"},{path:"/forms/:slug/submissions",view:"formSubmissions",title:"Submissions - Domma CMS"},{path:"/views",view:"viewsList",title:"Views - Domma CMS"},{path:"/views/new",view:"viewEditor",title:"New View - Domma CMS"},{path:"/views/edit/:slug",view:"viewEditor",title:"Edit View - Domma CMS"},{path:"/views/:slug/preview",view:"viewPreview",title:"View Preview - Domma CMS"},{path:"/actions",view:"actionsList",title:"Actions - Domma CMS"},{path:"/actions/new",view:"actionEditor",title:"New Action - Domma CMS"},{path:"/actions/edit/:slug",view:"actionEditor",title:"Edit Action - Domma CMS"},{path:"/pro/docs",view:"proDocs",title:"Pro Documentation - Domma CMS"},{path:"/blocks",view:"blocks",title:"Blocks - Domma CMS"},{path:"/blocks/new",view:"blockEditor",title:"New Block - Domma CMS"},{path:"/blocks/edit/:name",view:"blockEditor",title:"Edit Block - Domma CMS"},{path:"/my-profile",view:"myProfile",title:"My Profile - Domma CMS"},{path:"/roles",view:"roles",title:"Roles & Permissions - Domma CMS"},{path:"/roles/edit/:id",view:"roleEditor",title:"Edit Role - Domma CMS"},{path:"/login",view:"login",title:"Sign in - Domma CMS",onEnter:()=>{$("#admin-sidebar").hide(),$("#admin-topbar").hide()}},{path:"/reset-password",view:"login",title:"Reset Password - Domma CMS",onEnter:()=>{$("#admin-sidebar").hide(),$("#admin-topbar").hide()}}];M.subscribe("router:afterChange",async({to:t,from:e})=>{if(!(t.path==="/login"||t.path==="/reset-password")){if(d(r())&&!m(t.path)){R.navigate("/job-board");return}if($("#admin-sidebar").show(),$("#admin-topbar").show(),e?.path==="/login"||e?.path==="/reset-password"){$("#topbar-user-name").remove(),await f();const i=await u();g(i)}v()}}),M.subscribe("router:afterChange",()=>{setTimeout(()=>{$(".btn-primary, .btn-danger").length&&Domma.effects.reveal(".btn-primary, .btn-danger",{animation:"fade",stagger:40,duration:300})},50)}),$("#view-container").on("click",".card-collapsible .card-header",function(t){$(t.target).closest("button, a").length||$(this).closest(".card").toggleClass("card-collapsed")}),document.addEventListener("keydown",t=>{if(!(t.ctrlKey||t.metaKey)||t.key!=="s"||window.location.hash==="#/login"||window.location.hash.startsWith("#/reset-password"))return;const e=document.querySelector("#view-container .view-header button.btn-primary");e&&(t.preventDefault(),e.click())});const b={...K},p=[...N];let w=[];async function f(){if(l())try{const t=await o.plugins.adminConfig();w=t.sidebar||[],t.routes?.length&&p.push(...t.routes);for(const[e,i]of Object.entries(t.views||{}))try{const a=await import(`/plugins/${i.entry}`);b[e]=a[i.exportName]}catch{}}catch{}}function v(){const t=r();if(!t||$("#topbar-user-name").length)return;const i={admin:"Admin",manager:"Manager",editor:"Editor",subscriber:"Subscriber","jb-company":"Company","jb-agent":"Agent","jb-candidate":"Candidate"}[t.role]||t.role;$("#topbar-user").html(`
2
- <span id="topbar-user-name" class="topbar-user-name">${S(t.name)}</span>
3
- <span class="topbar-role-badge topbar-role-badge--${S(t.role)}">${i}</span>
1
+ import{getSidebarConfig as J}from"./config/sidebar-config.js";import{views as H}from"./views/index.js";import{api as o,getUser as l,isAuthenticated as r,logout as q}from"./api.js";$(()=>{(async()=>{try{const t=r()?await o.settings.get():null;Domma.theme.init({theme:t?.adminTheme||"charcoal-dark",persist:!0})}catch{Domma.theme.init({theme:"charcoal-dark",persist:!0})}})();const P=["jb-company","jb-agent","jb-candidate"],x=["/job-board","/my-profile"];function d(t){return t&&P.includes(t.role)}function m(t){return x.some(i=>t===i||t.startsWith(i+"/"))}R.use(async(t,i,a)=>{if(t.path==="/login"||t.path==="/reset-password")return a();if(!r()){R.navigate("/login");return}if(d(l())&&!m(t.path)){R.navigate("/job-board");return}a()});let c=null;async function j(){if(!r())return{};try{const[t,i,a,s,e,n,_,B,T,V,I,O,U]=await Promise.all([o.pages.list().catch(()=>[]),o.media.list().catch(()=>[]),o.users.list().catch(()=>[]),o.plugins.list().catch(()=>[]),o.collections.list().catch(()=>[]),o.forms.list().catch(()=>[]),o.themes.list().catch(()=>[]),o.views.list().catch(()=>[]),o.actions.list().catch(()=>[]),o.blocks.list().catch(()=>[]),o.navigation.get().catch(()=>({})),o.layouts.get().catch(()=>({})),o.get("/collections/roles/entries?limit=100").catch(()=>({entries:[]}))]),D=I.items||[],K=D.filter(h=>!h.hidden).length,y=D.length,F=s.filter(h=>h.enabled).length,k=s.length;return{pages:t.length,media:i.length,users:a.length,plugins:k>0?`${F}/${k}`:null,collections:e.length,forms:n.length,themes:_.length,views:B.length,actions:T.length,blocks:V.length,navigation:y>0?`${K}/${y}`:null,layouts:Object.keys(O).length,roles:(U.entries||[]).length}}catch{return{}}}async function g(){try{return(await o.get("/auth/permissions")).permissions||[]}catch{return[]}}async function A(){try{return await o.get("/settings/db-status")}catch{return{configured:!1}}}async function u(t){c&&$("#admin-sidebar").empty();const[i,a]=await Promise.all([j(),A()]),s=w.map(e=>{if(!e.countKey)return e;const n=i[e.countKey];return{...e,badge:n!=null&&n>0?String(n):null}});if(c=Domma.elements.sidebar("#admin-sidebar",{header:{title:"CMS Admin",icon:"layout"},items:J(t,i,s),collapsible:!0,collapseAt:992,push:!0,contentSelector:".dashboard-main",top:"60px"}),L(),N(),a?.configured){const e=$("#admin-sidebar").find(".sidebar-header-title").first();e.length&&!e.find(".db-badge").length&&e.append('<span class="db-badge" title="MongoDB connected" style="display:inline-flex;align-items:center;background:#16a34a;color:#fff;font-size:10px;font-weight:700;padding:2px 6px;border-radius:9999px;letter-spacing:.5px;margin-left:6px;vertical-align:middle;line-height:1;">DB</span>')}}function L(){$("#admin-sidebar .sidebar-link").each(function(){const t=$(this).find(".sidebar-text").text().trim();t&&$(this).attr("data-tooltip",t)}),E.tooltip("#admin-sidebar [data-tooltip]",{placement:"right"})}function N(){const t="sidebar_collapsed_sections",i=new Set(S.get(t)||[]);$("#admin-sidebar .sidebar-heading").each(function(){const a=$(this);a.addClass("sidebar-heading--collapsible"),a.append('<span class="sidebar-heading-toggle"><span data-icon="chevron-down"></span></span>');const s=a.text().trim();if(i.has(s)){a.addClass("is-collapsed");let e=a.next();for(;e.length&&!e.hasClass("sidebar-heading")&&!e.hasClass("sidebar-divider");)e.hide(),e=e.next()}a.on("click",function(){const e=!a.hasClass("is-collapsed");a.toggleClass("is-collapsed",e);let n=a.next();for(;n.length&&!n.hasClass("sidebar-heading")&&!n.hasClass("sidebar-divider");)n.toggle(!e),n=n.next();e?i.add(s):i.delete(s),S.set(t,[...i])})}),Domma.icons.scan("#admin-sidebar")}M.subscribe("router:afterChange",({to:t})=>{c&&t.path!=="/login"&&t.path!=="/reset-password"&&c.setActive("#"+t.path)});const W=[{path:"/",view:"dashboard",title:"Dashboard - Domma CMS"},{path:"/pages",view:"pages",title:"Pages - Domma CMS"},{path:"/pages/new",view:"pageEditor",title:"New Page - Domma CMS"},{path:"/pages/edit/*",view:"pageEditor",title:"Edit Page - Domma CMS"},{path:"/media",view:"media",title:"Media - Domma CMS"},{path:"/navigation",view:"navigation",title:"Navigation - Domma CMS"},{path:"/layouts",view:"layouts",title:"Layouts - Domma CMS"},{path:"/settings",view:"settings",title:"Settings - Domma CMS"},{path:"/users",view:"users",title:"Users - Domma CMS"},{path:"/users/new",view:"userEditor",title:"New User - Domma CMS"},{path:"/users/edit/:id",view:"userEditor",title:"Edit User - Domma CMS"},{path:"/plugins",view:"plugins",title:"Plugins - Domma CMS"},{path:"/documentation",view:"documentation",title:"Usage - Domma CMS"},{path:"/tutorials",view:"tutorials",title:"Tutorials - Domma CMS"},{path:"/api-reference",view:"apiReference",title:"API Reference - Domma CMS"},{path:"/collections",view:"collections",title:"Collections - Domma CMS"},{path:"/collections/new",view:"collectionEditor",title:"New Collection - Domma CMS"},{path:"/collections/edit/:slug",view:"collectionEditor",title:"Edit Collection - Domma CMS"},{path:"/collections/:slug/entries",view:"collectionEntries",title:"Entries - Domma CMS"},{path:"/forms",view:"forms",title:"Forms - Domma CMS"},{path:"/forms/new",view:"formEditor",title:"New Form - Domma CMS"},{path:"/forms/edit/:slug",view:"formEditor",title:"Edit Form - Domma CMS"},{path:"/forms/:slug/submissions",view:"formSubmissions",title:"Submissions - Domma CMS"},{path:"/views",view:"viewsList",title:"Views - Domma CMS"},{path:"/views/new",view:"viewEditor",title:"New View - Domma CMS"},{path:"/views/edit/:slug",view:"viewEditor",title:"Edit View - Domma CMS"},{path:"/views/:slug/preview",view:"viewPreview",title:"View Preview - Domma CMS"},{path:"/actions",view:"actionsList",title:"Actions - Domma CMS"},{path:"/actions/new",view:"actionEditor",title:"New Action - Domma CMS"},{path:"/actions/edit/:slug",view:"actionEditor",title:"Edit Action - Domma CMS"},{path:"/pro/docs",view:"proDocs",title:"Pro Documentation - Domma CMS"},{path:"/blocks",view:"blocks",title:"Blocks - Domma CMS"},{path:"/blocks/new",view:"blockEditor",title:"New Block - Domma CMS"},{path:"/blocks/edit/:name",view:"blockEditor",title:"Edit Block - Domma CMS"},{path:"/my-profile",view:"myProfile",title:"My Profile - Domma CMS"},{path:"/roles",view:"roles",title:"Roles & Permissions - Domma CMS"},{path:"/roles/edit/:id",view:"roleEditor",title:"Edit Role - Domma CMS"},{path:"/login",view:"login",title:"Sign in - Domma CMS",onEnter:()=>{$("#admin-sidebar").hide(),$("#admin-topbar").hide()}},{path:"/reset-password",view:"login",title:"Reset Password - Domma CMS",onEnter:()=>{$("#admin-sidebar").hide(),$("#admin-topbar").hide()}}];M.subscribe("router:afterChange",async({to:t,from:i})=>{if(!(t.path==="/login"||t.path==="/reset-password")){if(d(l())&&!m(t.path)){R.navigate("/job-board");return}if($("#admin-sidebar").show(),$("#admin-topbar").show(),i?.path==="/login"||i?.path==="/reset-password"){$("#topbar-user-name").remove(),await f();const a=await g();u(a)}v()}}),M.subscribe("router:afterChange",()=>{setTimeout(()=>{$(".btn-primary, .btn-danger").length&&Domma.effects.reveal(".btn-primary, .btn-danger",{animation:"fade",stagger:40,duration:300})},50)}),$("#view-container").on("click",".card-collapsible .card-header",function(t){$(t.target).closest("button, a").length||$(this).closest(".card").toggleClass("card-collapsed")}),document.addEventListener("keydown",t=>{if(!(t.ctrlKey||t.metaKey)||t.key!=="s"||window.location.hash==="#/login"||window.location.hash.startsWith("#/reset-password"))return;const i=document.querySelector("#view-container .view-header button.btn-primary");i&&(t.preventDefault(),i.click())});const b={...H},p=[...W];let w=[];async function f(){if(r())try{const t=await o.plugins.adminConfig();w=t.sidebar||[],t.routes?.length&&p.push(...t.routes);for(const[i,a]of Object.entries(t.views||{}))try{const s=await import(`/plugins/${a.entry}`);b[i]=s[a.exportName]}catch{}}catch{}}function v(){const t=l();if(!t||$("#topbar-user-name").length)return;const a={admin:"Admin",manager:"Manager",editor:"Editor",subscriber:"Subscriber","jb-company":"Company","jb-agent":"Agent","jb-candidate":"Candidate"}[t.role]||t.role;$("#topbar-user").html(`
2
+ <span id="topbar-user-name" class="topbar-user-name">${C(t.name)}</span>
3
+ <span class="topbar-role-badge topbar-role-badge--${C(t.role)}">${a}</span>
4
4
  `),$("#topbar-actions").html(`
5
5
  <a href="#/my-profile" class="topbar-action-link" data-tooltip="My Profile" data-tooltip-placement="bottom">
6
6
  <span data-icon="user"></span>
@@ -14,4 +14,4 @@ import{getSidebarConfig as J}from"./config/sidebar-config.js";import{views as K}
14
14
  <span data-icon="log-out"></span>
15
15
  <span>Sign out</span>
16
16
  </a>
17
- `),$("#topbar-logout-btn").on("click",a=>{a.preventDefault(),H()}),Domma.icons.scan("#admin-topbar"),E.tooltip("#topbar-actions [data-tooltip]",{placement:"bottom"})}function S(t){return String(t).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}(async()=>{if(!l()&&!window.location.hash.startsWith("#/reset-password"))window.location.hash="#/login";else{const a=(window.location.hash||"#/").slice(1)||"/";if(d(r())&&!m(a)&&(window.location.hash="#/job-board"),await f(),r()){const n=await u();await g(n),v()}}R.init({container:"#view-container",routes:p,views:b,default:"/",transitions:{enter:"fadeIn",leave:"fadeOut",duration:150}});const t=R._extractParams.bind(R);R._extractParams=function(a,s){if(a.endsWith("/*")){const n=a.slice(0,-2);return s.startsWith(n+"/")?{}:null}return t(a,s)};const e=(window.location.hash||"#/").slice(1)||"/";p.filter(a=>a.path.endsWith("/*")).some(a=>e.startsWith(a.path.slice(0,-2)+"/"))&&R._handleRouteChange()})()});
17
+ `),$("#topbar-logout-btn").on("click",s=>{s.preventDefault(),q()}),Domma.icons.scan("#admin-topbar"),E.tooltip("#topbar-actions [data-tooltip]",{placement:"bottom"})}function C(t){return String(t).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}(async()=>{if(!r()&&!window.location.hash.startsWith("#/reset-password"))window.location.hash="#/login";else{const s=(window.location.hash||"#/").slice(1)||"/";if(d(l())&&!m(s)&&(window.location.hash="#/job-board"),await f(),l()){const n=await g();await u(n),v()}}R.init({container:"#view-container",routes:p,views:b,default:"/",transitions:{enter:"fadeIn",leave:"fadeOut",duration:150}});const t=R._extractParams.bind(R);R._extractParams=function(s,e){if(s.endsWith("/*")){const n=s.slice(0,-2);return e.startsWith(n+"/")?{}:null}return t(s,e)};const i=(window.location.hash||"#/").slice(1)||"/";p.filter(s=>s.path.endsWith("/*")).some(s=>i.startsWith(s.path.slice(0,-2)+"/"))&&R._handleRouteChange()})()});
@@ -17,15 +17,6 @@
17
17
  "debounceMs": 300
18
18
  }
19
19
  },
20
- "domma-effects": {
21
- "enabled": true,
22
- "settings": {
23
- "respectMotion": false,
24
- "defaultDuration": 600,
25
- "defaultAnimation": "fade",
26
- "defaultThreshold": 0.1
27
- }
28
- },
29
20
  "job-board": {
30
21
  "enabled": false,
31
22
  "settings": {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "domma-cms",
3
- "version": "0.7.5",
3
+ "version": "0.7.6",
4
4
  "description": "File-based CMS powered by Domma and Fastify. Run npx domma-cms my-site to create a new project.",
5
5
  "type": "module",
6
6
  "main": "server/server.js",
@@ -16,7 +16,7 @@
16
16
  "/resources/dependencies": 2,
17
17
  "/resources/components": 6,
18
18
  "/gdpr": 3,
19
- "/scratch": 82,
19
+ "/scratch": 84,
20
20
  "/getting-started": 3,
21
21
  "/resources/pro": 1,
22
22
  "/todo": 23,
package/server/server.js CHANGED
@@ -45,7 +45,11 @@ if (!JWT_SECRET || JWT_SECRET === 'CHANGE_ME' || JWT_SECRET.length < 32) {
45
45
  }
46
46
 
47
47
  const app = Fastify({
48
- logger: { level: process.env.NODE_ENV === 'development' ? 'info' : 'warn' }
48
+ logger: {level: process.env.NODE_ENV === 'development' ? 'info' : 'warn'},
49
+ // When running behind a reverse proxy (e.g. domma-cms-manager), trust the
50
+ // X-Forwarded-For header so @fastify/rate-limit keys on the real client IP
51
+ // rather than the proxy's loopback address.
52
+ trustProxy: !!process.env.TRUST_PROXY,
49
53
  });
50
54
 
51
55
  // Register process error handlers immediately so startup errors are captured
@@ -85,7 +89,11 @@ await app.register(multipart, { limits: { fileSize: serverConfig.uploads.maxFile
85
89
  await app.register(rateLimit, {
86
90
  global: true, // apply default limit to all routes; stricter per-route limits override this
87
91
  max: 100,
88
- timeWindow: '1 minute'
92
+ timeWindow: '1 minute',
93
+ // When running behind domma-cms-manager, all traffic arrives from 127.0.0.1.
94
+ // Exempt loopback so the proxy and health checks are never rate-limited;
95
+ // external rate limiting is the manager's responsibility.
96
+ allowList: process.env.TRUST_PROXY ? ['127.0.0.1', '::1', '::ffff:127.0.0.1'] : [],
89
97
  });
90
98
 
91
99
  // ---------------------------------------------------------------------------
@@ -20,6 +20,12 @@ const PLUGINS_DIR = path.resolve('plugins');
20
20
 
21
21
  const REQUIRED_MANIFEST_FIELDS = ['name', 'displayName', 'version', 'description', 'author', 'date', 'icon'];
22
22
 
23
+ /**
24
+ * Core plugins — always loaded regardless of plugins.json enabled state.
25
+ * These are considered first-class CMS features, not optional add-ons.
26
+ */
27
+ const CORE_PLUGINS = new Set(['domma-effects']);
28
+
23
29
  /**
24
30
  * Scan the plugins/ directory and return all valid manifests.
25
31
  * Validates mandatory fields and required files (plugin.js, config.js).
@@ -147,7 +153,7 @@ export async function registerPlugins(fastify) {
147
153
  const loaded = [];
148
154
  for (const manifest of manifests) {
149
155
  const state = states[manifest.name] || {};
150
- if (!state.enabled) continue;
156
+ if (!state.enabled && !CORE_PLUGINS.has(manifest.name)) continue;
151
157
 
152
158
  const entryPath = path.join(PLUGINS_DIR, manifest.name, 'plugin.js');
153
159
  try {
@@ -201,7 +207,7 @@ export async function getInjectionSnippets() {
201
207
 
202
208
  for (const manifest of manifests) {
203
209
  const state = states[manifest.name] || {};
204
- if (!state.enabled || !manifest.inject) continue;
210
+ if ((!state.enabled && !CORE_PLUGINS.has(manifest.name)) || !manifest.inject) continue;
205
211
 
206
212
  const pluginRoot = path.resolve(PLUGINS_DIR, manifest.name);
207
213
 
@@ -271,7 +277,7 @@ export async function getAdminPluginConfig() {
271
277
 
272
278
  for (const manifest of manifests) {
273
279
  const state = states[manifest.name] || {};
274
- if (!state.enabled || !manifest.admin) continue;
280
+ if ((!state.enabled && !CORE_PLUGINS.has(manifest.name)) || !manifest.admin) continue;
275
281
 
276
282
  if (manifest.admin.sidebar) sidebar.push(...manifest.admin.sidebar);
277
283
  if (manifest.admin.routes) routes.push(...manifest.admin.routes);