domma-cms 1.6.60 → 1.7.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/CHANGELOG.md +4349 -0
- package/README.md +1 -1
- package/admin/dist/domma/domma-tools.css +3 -3
- package/admin/dist/domma/domma-tools.min.js +3 -3
- package/admin/js/lib/effect-defs.js +1 -1
- package/admin/js/lib/effects-builder.js +2 -2
- package/admin/js/lib/mail-compose.js +1 -0
- package/admin/js/lib/page-editor-reference.js +5 -5
- package/admin/js/lib/shortcode-tags.js +1 -1
- package/admin/js/templates/call-new.html +1 -1
- package/admin/js/templates/docs/usage-shortcodes.html +39 -0
- package/admin/js/views/call-new.js +1 -1
- package/admin/js/views/contacts.js +15 -15
- package/admin/js/views/form-submissions.js +2 -2
- package/docs/DommaFeatures.md +3005 -0
- package/docs/MultiTenant.md +176 -0
- package/docs/api-reference.md +1667 -0
- package/docs/cache.md +96 -0
- package/docs/configuration.md +1811 -0
- package/docs/editions-and-licences.md +190 -0
- package/docs/getting-started.md +201 -0
- package/docs/guides/form-triggers.md +260 -0
- package/docs/guides/mongodb-storage-adapter.md +282 -0
- package/docs/guides/multi-page-forms.md +121 -0
- package/docs/images/scribe-composer.png +0 -0
- package/docs/markdown-shortcodes.md +1738 -0
- package/docs/pentest-report-2026-04-03.md +181 -0
- package/docs/plans/2026-02-28-admin-topbar-login-forms.md +349 -0
- package/docs/plans/2026-02-28-domma-ui-polish.md +286 -0
- package/docs/plans/2026-02-28-reset-seed-fresh-commands.md +520 -0
- package/docs/plans/2026-03-02-cookie-consent-design.md +86 -0
- package/docs/plans/2026-03-02-cookie-consent-plugin.md +453 -0
- package/docs/plans/2026-03-02-tooltip-sweep-and-back-to-top-plugin-design.md +126 -0
- package/docs/plans/2026-03-02-tooltip-sweep-back-to-top.md +427 -0
- package/docs/plans/2026-03-05-cookie-consent-btt-core.md +115 -0
- package/docs/plans/2026-03-05-custom-css-migrate-to-core-design.md +35 -0
- package/docs/plans/2026-03-05-custom-css-migrate-to-core.md +352 -0
- package/docs/plans/2026-03-05-scribe-script-shortcode.md +373 -0
- package/docs/plans/2026-03-05-user-types-design.md +139 -0
- package/docs/plans/2026-03-05-user-types-implementation.md +1035 -0
- package/docs/plans/2026-03-09-page-editor-view-cta-inserts-design.md +78 -0
- package/docs/plans/2026-03-09-page-editor-view-cta-inserts.md +388 -0
- package/docs/plans/2026-03-09-pro-documentation-design.md +80 -0
- package/docs/plans/2026-03-09-pro-documentation.md +1017 -0
- package/docs/plans/2026-06-10-core-project-design.md +240 -0
- package/docs/plugin-development.md +827 -0
- package/docs/recruitment-recipe.md +501 -0
- package/docs/scaffolding.md +312 -0
- package/docs/screenshots/calculator-floating.png +0 -0
- package/docs/screenshots/calculator-standard.png +0 -0
- package/docs/screenshots/email-settings.png +0 -0
- package/docs/screenshots/messages-count.png +0 -0
- package/docs/structure/user-types.md +107 -0
- package/docs/superpowers/plans/2026-05-25-admin-sidebar-via-menus.md +1112 -0
- package/docs/superpowers/plans/2026-06-08-navbar-mobile-drawer-and-menu-enrichment.md +1161 -0
- package/docs/superpowers/plans/2026-06-25-domma-docs-project.md +695 -0
- package/docs/superpowers/plans/2026-06-26-surveys-HANDOFF.md +68 -0
- package/docs/superpowers/plans/2026-06-26-surveys-pro-plugin.md +1902 -0
- package/docs/superpowers/plans/2026-08-19-collection-context-sort-export.md +1577 -0
- package/docs/superpowers/plans/2026-10-09-content-api.md +1192 -0
- package/docs/superpowers/specs/2026-03-12-form-followup-actions-design.md +152 -0
- package/docs/superpowers/specs/2026-03-12-page-editor-form-insert-and-per-page-theme-design.md +115 -0
- package/docs/superpowers/specs/2026-03-17-nav-hidden-items-design.md +85 -0
- package/docs/superpowers/specs/2026-03-19-accordion-insert-design.md +175 -0
- package/docs/superpowers/specs/2026-03-25-static-block-shortcode-design.md +153 -0
- package/docs/superpowers/specs/2026-03-25-text-shortcode-design.md +168 -0
- package/docs/superpowers/specs/2026-04-05-card-builder-design.md +315 -0
- package/docs/superpowers/specs/2026-04-06-insert-builders-design.md +278 -0
- package/docs/superpowers/specs/2026-04-06-layouts-design.md +226 -0
- package/docs/superpowers/specs/2026-04-07-blog-plugin-design.md +300 -0
- package/docs/superpowers/specs/2026-04-07-calendar-plugin-design.md +538 -0
- package/docs/superpowers/specs/2026-04-17-components-area-design.md +368 -0
- package/docs/superpowers/specs/2026-05-02-effects-slideover-overhaul-design.md +193 -0
- package/docs/superpowers/specs/2026-05-06-dashboard-rebuild-design.md +257 -0
- package/docs/superpowers/specs/2026-05-24-menus-design.md +466 -0
- package/docs/superpowers/specs/2026-05-25-admin-sidebar-via-menus-design.md +358 -0
- package/docs/superpowers/specs/2026-05-25-projects-design.md +380 -0
- package/docs/superpowers/specs/2026-06-08-navbar-mobile-drawer-and-menu-enrichment-design.md +292 -0
- package/docs/superpowers/specs/2026-06-08-sidebar-tools-plugins-folders-design.md +59 -0
- package/docs/superpowers/specs/2026-06-25-domma-docs-project-design.md +192 -0
- package/docs/superpowers/specs/2026-06-26-domma-charts-upstream-proposal.md +44 -0
- package/docs/superpowers/specs/2026-06-26-manager-scheduler-upstream-proposal.md +46 -0
- package/docs/superpowers/specs/2026-06-26-surveys-pro-plugin-design.md +206 -0
- package/docs/superpowers/specs/2026-07-02-shortcode-context-menu-design.md +96 -0
- package/docs/superpowers/specs/2026-08-19-collection-context-sort-export-design.md +330 -0
- package/docs/superpowers/specs/2026-09-22-dcms-marketplace-design.md +1150 -0
- package/docs/superpowers/specs/2026-10-09-content-api-design.md +325 -0
- package/docs/superpowers/specs/plans/2026-03-12-form-followup-actions.md +624 -0
- package/docs/superpowers/specs/plans/2026-03-12-page-editor-form-insert-and-per-page-theme.md +125 -0
- package/docs/superpowers/specs/plans/2026-03-17-nav-hidden-items.md +578 -0
- package/docs/superpowers/specs/plans/2026-03-19-accordion-insert.md +635 -0
- package/docs/superpowers/specs/plans/2026-03-25-text-shortcode.md +545 -0
- package/docs/superpowers/specs/plans/2026-04-05-card-builder.md +1970 -0
- package/docs/superpowers/specs/plans/2026-04-06-insert-builders.md +1049 -0
- package/docs/superpowers/specs/plans/2026-04-06-layouts.md +1015 -0
- package/docs/superpowers/specs/plans/2026-04-07-calendar-plugin.md +1526 -0
- package/docs/superpowers/specs/plans/2026-04-08-plugin-monetisation-split.md +242 -0
- package/docs/superpowers/specs/plans/2026-04-17-components-area.md +2038 -0
- package/docs/superpowers/specs/plans/2026-05-02-effects-slideover-overhaul.md +2335 -0
- package/docs/superpowers/specs/plans/2026-05-06-dashboard-rebuild.md +1999 -0
- package/docs/superpowers/specs/plans/2026-05-17-cache-system.md +1436 -0
- package/docs/superpowers/specs/plans/2026-05-24-menus.md +3096 -0
- package/docs/superpowers/specs/plans/2026-05-25-projects.md +2869 -0
- package/docs/superpowers/specs/plans/2026-09-22-marketplace-step2-consolidation.md +505 -0
- package/docs/superpowers/specs/plans/2026-09-22-marketplace-step3-catalogue-and-key.md +154 -0
- package/docs/superpowers/specs/plans/2026-09-22-tools-promotion.md +260 -0
- package/docs/theming.md +320 -0
- package/package.json +2 -2
- package/plugins/_lib/admin/mail/compose-window.js +3 -1
- package/plugins/_lib/admin/mail/identity.js +18 -3
- package/plugins/_lib/admin/mail/reader-view.js +32 -3
- package/plugins/blog/CLAUDE.md +4 -0
- package/plugins/blog/admin/lib/email.js +191 -0
- package/plugins/blog/admin/views/comments.js +3 -1
- package/plugins/blog/plugin.json +2 -2
- package/plugins/blog/plugin.public.js +1 -1
- package/plugins/blog/tests/email.test.js +69 -0
- package/plugins/booking/CLAUDE.md +4 -0
- package/plugins/booking/admin/lib/email.js +191 -0
- package/plugins/booking/admin/views/panels.js +7 -3
- package/plugins/booking/plugin.json +1 -1
- package/plugins/booking/tests/email.test.js +69 -0
- package/plugins/free-tier.lock.json +27 -19
- package/plugins/shopping-cart/CLAUDE.md +4 -0
- package/plugins/shopping-cart/admin/lib/email.js +191 -0
- package/plugins/shopping-cart/admin/views/orders.js +11 -3
- package/plugins/shopping-cart/plugin.json +2 -2
- package/plugins/shopping-cart/tests/email.test.js +69 -0
- package/plugins/support/CLAUDE.md +4 -0
- package/plugins/support/admin/lib/email.js +191 -0
- package/plugins/support/admin/views/desk.js +4 -1
- package/plugins/support/admin/views/inbox.js +6 -2
- package/plugins/support/plugin.json +2 -2
- package/plugins/support/tests/email.test.js +69 -0
- package/public/js/celebrations/core/canvas.js +1 -1
- package/public/js/celebrations/index.js +1 -1
- package/public/js/celebrations/licenses/ABeeZee-Regular-OFL.txt +93 -0
- package/public/js/celebrations/licenses/Allura-Regular-OFL.txt +93 -0
- package/public/js/celebrations/message-attrs.mjs +1 -0
- package/public/js/celebrations/themes/anniversary.js +1 -0
- package/public/js/celebrations/themes/autumn.js +1 -0
- package/public/js/celebrations/themes/birthday.js +1 -0
- package/public/js/celebrations/themes/butterfly-flight.js +1 -0
- package/public/js/celebrations/themes/calendar.js +1 -0
- package/public/js/celebrations/themes/chinese-new-year-dates.js +1 -0
- package/public/js/celebrations/themes/chinese-new-year.js +1 -0
- package/public/js/celebrations/themes/christmas.js +1 -1
- package/public/js/celebrations/themes/easter.js +1 -0
- package/public/js/celebrations/themes/garden-drawing.js +1 -0
- package/public/js/celebrations/themes/garden.js +1 -0
- package/public/js/celebrations/themes/graduation.js +1 -0
- package/public/js/celebrations/themes/greeting.js +1 -0
- package/public/js/celebrations/themes/make-it-rain.js +1 -0
- package/public/js/celebrations/themes/money-rain.js +1 -0
- package/public/js/celebrations/themes/new-year.js +1 -0
- package/public/js/celebrations/themes/occasion-drawing.js +1 -0
- package/public/js/celebrations/themes/occasions.js +1 -0
- package/public/js/celebrations/themes/rain-control.js +1 -0
- package/public/js/celebrations/themes/sparkler-font-data.js +1 -0
- package/public/js/celebrations/themes/sparkler-font.js +1 -0
- package/public/js/celebrations/themes/sparkler-writing.js +1 -0
- package/public/js/celebrations/themes/summer-garden.js +1 -0
- package/public/js/celebrations/themes/winter.js +1 -0
- package/public/js/celebrations/trait-data.mjs +1 -1
- package/public/js/effects.js +1 -1
- package/scripts/build.js +4 -0
- package/server/services/markdown.js +5 -2
- package/server/templates/page.html +1 -1
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Theoretical: Multi-Tenancy in Domma CMS
|
|
2
|
+
|
|
3
|
+
## Context
|
|
4
|
+
|
|
5
|
+
Domma CMS is a file-based CMS (Fastify 5 ESM backend, Domma SPA admin, SSR public site). Every service currently assumes
|
|
6
|
+
a single tenant - config, content, users, collections, and plugins all resolve from fixed module-level directory
|
|
7
|
+
constants. The user asked whether multi-tenancy is **possible**, not to implement it.
|
|
8
|
+
|
|
9
|
+
## Answer: Yes, But It's a Spectrum
|
|
10
|
+
|
|
11
|
+
There are three viable approaches, each with a different trade-off between isolation, complexity, and operational
|
|
12
|
+
overhead.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Approach 1: Process-Per-Tenant (Simplest)
|
|
17
|
+
|
|
18
|
+
Run a separate Domma CMS instance per tenant, each with its own `content/`, `config/`, and port.
|
|
19
|
+
|
|
20
|
+
| Aspect | Detail |
|
|
21
|
+
|----------------------|----------------------------------------------------|
|
|
22
|
+
| **Isolation** | Complete - separate processes, separate file trees |
|
|
23
|
+
| **Code changes** | Zero |
|
|
24
|
+
| **Operational cost** | High - N processes, N ports, reverse proxy routing |
|
|
25
|
+
| **Tenant limit** | ~50-100 before operational pain |
|
|
26
|
+
| **Best for** | Agency hosting a handful of client sites |
|
|
27
|
+
|
|
28
|
+
A reverse proxy (nginx/Caddy) maps `tenant1.example.com` → `:4096`, `tenant2.example.com` → `:4097`, etc. Each instance
|
|
29
|
+
is a vanilla Domma CMS install.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Approach 2: Single Process, Directory-Scoped Tenancy (Medium)
|
|
34
|
+
|
|
35
|
+
One Fastify instance, tenant resolved from hostname/subdomain, all services parameterised by tenant ID.
|
|
36
|
+
|
|
37
|
+
### What Changes
|
|
38
|
+
|
|
39
|
+
**A. Tenant detection hook** (new - `server/middleware/tenant.js`)
|
|
40
|
+
|
|
41
|
+
- Fastify `onRequest` hook derives `request.tenant` from subdomain or `X-Tenant` header
|
|
42
|
+
- All downstream services receive tenant context
|
|
43
|
+
|
|
44
|
+
**B. Config** (`server/config.js`)
|
|
45
|
+
|
|
46
|
+
- `getConfig(name)` → `getConfig(name, tenantId)` resolving to `config/{tenantId}/{name}.json`
|
|
47
|
+
- Startup singleton `config` (server/auth/content) stays global (shared infrastructure config)
|
|
48
|
+
- Admin-editable config (site/navigation/plugins/presets) becomes per-tenant
|
|
49
|
+
|
|
50
|
+
**C. Content & Collections** (`server/services/content.js`, `collections.js`)
|
|
51
|
+
|
|
52
|
+
- Module-level constants (`PAGES_DIR`, `MEDIA_DIR`, `COLLECTIONS_DIR`) become functions: `getPagesDir(tenantId)` →
|
|
53
|
+
`content/{tenantId}/pages/`
|
|
54
|
+
- Every exported function gains a tenant parameter
|
|
55
|
+
- URL-to-file mapping scoped: `/about` → `content/{tenantId}/pages/about.md`
|
|
56
|
+
|
|
57
|
+
**D. Users & Auth** (`server/services/users.js`, `server/middleware/auth.js`)
|
|
58
|
+
|
|
59
|
+
- Users dir: `content/{tenantId}/users/`
|
|
60
|
+
- JWT tokens include `tenantId` claim - validated on every request
|
|
61
|
+
- Role caches keyed by `tenantId:roleName`
|
|
62
|
+
|
|
63
|
+
**E. Storage Adapters** (`server/services/adapterRegistry.js`)
|
|
64
|
+
|
|
65
|
+
- Cache key changes from `slug` to `{tenantId}:{slug}`
|
|
66
|
+
- MongoDB: per-tenant database or `{tenantId}_` collection prefix
|
|
67
|
+
- FileAdapter: tenant-scoped base directory
|
|
68
|
+
|
|
69
|
+
**F. Renderer** (`server/services/renderer.js`)
|
|
70
|
+
|
|
71
|
+
- Already reads config fresh per render - making `getConfig()` tenant-aware cascades naturally
|
|
72
|
+
- Template cache keyed by `tenantId`
|
|
73
|
+
- Custom CSS path: `content/{tenantId}/custom.css`
|
|
74
|
+
|
|
75
|
+
**G. Plugins** (`server/services/plugins.js`, `server/services/hooks.js`) - **HARDEST PART**
|
|
76
|
+
|
|
77
|
+
- Plugin code stays shared (global `plugins/` directory)
|
|
78
|
+
- Enabled/disabled state + settings become per-tenant (via scoped `config/{tenantId}/plugins.json`)
|
|
79
|
+
- Hook/shortcode/transform registries need namespacing - currently global `Map`s and a single `EventEmitter`
|
|
80
|
+
- Plugin-registered Fastify routes need tenant middleware guards
|
|
81
|
+
|
|
82
|
+
### Effort Estimate by Area
|
|
83
|
+
|
|
84
|
+
| Area | Effort | Why |
|
|
85
|
+
|-----------------------|-------------|------------------------------------------------------|
|
|
86
|
+
| Tenant detection hook | Low | New file, ~30 lines |
|
|
87
|
+
| Config scoping | Medium | `getConfig`/`saveConfig` + directory structure |
|
|
88
|
+
| Content service | Medium | ~15 functions to parameterise |
|
|
89
|
+
| Collections | Medium | Same pattern as content |
|
|
90
|
+
| Users & auth | Medium-High | JWT claims, role cache refactor |
|
|
91
|
+
| Adapters | Low-Medium | Cache key change, already supports named connections |
|
|
92
|
+
| Renderer | Low | Already re-reads config per render |
|
|
93
|
+
| Plugins/hooks | **High** | Global registries, no teardown, route scoping |
|
|
94
|
+
| Admin SPA | Medium | API calls need tenant context (header or URL prefix) |
|
|
95
|
+
| **Total** | **Large** | ~2-3 weeks full-time for an experienced developer |
|
|
96
|
+
|
|
97
|
+
### Directory Structure
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
config/
|
|
101
|
+
server.json # shared infrastructure
|
|
102
|
+
auth.json # shared infrastructure
|
|
103
|
+
content.json # shared infrastructure
|
|
104
|
+
tenants.json # NEW - tenant registry (id, hostname, plan, etc.)
|
|
105
|
+
{tenantId}/
|
|
106
|
+
site.json
|
|
107
|
+
navigation.json
|
|
108
|
+
plugins.json
|
|
109
|
+
presets.json
|
|
110
|
+
|
|
111
|
+
content/
|
|
112
|
+
{tenantId}/
|
|
113
|
+
pages/
|
|
114
|
+
media/
|
|
115
|
+
users/
|
|
116
|
+
collections/
|
|
117
|
+
custom.css
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Approach 3: MongoDB-Native Multi-Tenancy (Pro Only)
|
|
123
|
+
|
|
124
|
+
For Pro users already on MongoDB - tenancy lives entirely in the database layer.
|
|
125
|
+
|
|
126
|
+
- Each tenant gets a separate MongoDB database (strongest isolation) or a `tenantId` field on every document (weaker but
|
|
127
|
+
simpler)
|
|
128
|
+
- `connectionManager.js` already supports named connections - extend to per-tenant connections
|
|
129
|
+
- File-based content (pages, media) still needs directory scoping OR moves entirely to MongoDB (GridFS for media,
|
|
130
|
+
documents for pages)
|
|
131
|
+
- Most natural extension of the existing adapter pattern
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## The Five Hardest Problems
|
|
136
|
+
|
|
137
|
+
1. **Plugin hook registries are global singletons.** The `EventEmitter`, `_shortcodes` Map, `_sanitizeExtensions`, and
|
|
138
|
+
`_transforms` Map in `hooks.js` have no tenant dimension. Options: (a) namespace keys as `{tenantId}:{hookName}`, (b)
|
|
139
|
+
per-tenant EventEmitter instances, (c) accept that plugins are global and only settings are per-tenant.
|
|
140
|
+
|
|
141
|
+
2. **Module-level constants everywhere.** `PAGES_DIR`, `MEDIA_DIR`, `USERS_DIR`, `COLLECTIONS_DIR` are all `const` at
|
|
142
|
+
module scope. Every function using them would need refactoring to accept or derive tenant context. This is the bulk
|
|
143
|
+
of the mechanical work.
|
|
144
|
+
|
|
145
|
+
3. **No request context threading.** There is no `AsyncLocalStorage` or similar mechanism to pass tenant context through
|
|
146
|
+
the call stack without explicitly threading it through every function parameter. Adding `AsyncLocalStorage` would be
|
|
147
|
+
the cleanest approach but touches every service boundary.
|
|
148
|
+
|
|
149
|
+
4. **Admin SPA is a single build.** The admin panel (`admin/`) is served as static files and makes API calls without
|
|
150
|
+
tenant context. It would need to either: (a) send a tenant header with every API call, (b) be served from a
|
|
151
|
+
tenant-scoped URL, or (c) derive tenant from the page hostname.
|
|
152
|
+
|
|
153
|
+
5. **Static asset isolation.** `public/css/site.css`, `public/js/site.js`, and theme assets are shared. Per-tenant
|
|
154
|
+
CSS/JS customisation would need a serving layer that overlays tenant-specific files on the shared base.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Pragmatic Recommendation
|
|
159
|
+
|
|
160
|
+
If multi-tenancy is a real future goal:
|
|
161
|
+
|
|
162
|
+
1. **Start with Approach 1** (process-per-tenant) for immediate needs - zero code changes
|
|
163
|
+
2. **Introduce `AsyncLocalStorage`** for tenant context propagation as a foundation
|
|
164
|
+
3. **Scope config first** (`getConfig` + `saveConfig`) - lowest risk, highest leverage since the renderer already
|
|
165
|
+
re-reads config per render
|
|
166
|
+
4. **Scope content second** - parameterise path helpers in content.js
|
|
167
|
+
5. **Leave plugins global** with per-tenant settings - full plugin isolation is disproportionately expensive
|
|
168
|
+
6. **MongoDB-native tenancy** for Pro users as the long-term path - the adapter pattern already supports this direction
|
|
169
|
+
|
|
170
|
+
## Verification (If Implemented)
|
|
171
|
+
|
|
172
|
+
1. Two tenants configured with different `site.json` (title, theme) serve distinct homepages from the same process
|
|
173
|
+
2. Tenant A cannot access Tenant B's content, users, or media (path traversal guards)
|
|
174
|
+
3. JWT from Tenant A is rejected on Tenant B's API routes
|
|
175
|
+
4. Admin panel for each tenant shows only that tenant's pages/collections
|
|
176
|
+
5. Shared plugins work for both tenants with independent settings
|