domma-cms 0.3.0 → 0.5.2
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 +3 -3
- package/admin/css/admin.css +1 -1
- package/admin/dist/domma/domma-tools.css +2313 -0
- package/admin/dist/domma/domma-tools.min.js +10 -0
- package/admin/index.html +4 -0
- package/admin/js/api.js +1 -1
- package/admin/js/app.js +8 -4
- package/admin/js/config/sidebar-config.js +1 -1
- package/admin/js/lib/markdown-toolbar.js +18 -10
- package/admin/js/templates/action-editor.html +171 -0
- package/admin/js/templates/actions-list.html +19 -0
- package/admin/js/templates/api-reference.html +1411 -0
- package/admin/js/templates/block-editor.html +158 -0
- package/admin/js/templates/blocks.html +8 -0
- package/admin/js/templates/collection-editor.html +47 -0
- package/admin/js/templates/collection-entries.html +3 -0
- package/admin/js/templates/collections.html +51 -4
- package/admin/js/templates/documentation.html +258 -0
- package/{plugins/form-builder/admin → admin/js}/templates/form-editor.html +238 -199
- package/{plugins/form-builder/admin → admin/js}/templates/form-submissions.html +30 -30
- package/{plugins/form-builder/admin/templates/forms-list.html → admin/js/templates/forms.html} +17 -17
- package/admin/js/templates/login.html +29 -4
- package/admin/js/templates/my-profile.html +17 -0
- package/admin/js/templates/page-editor.html +39 -0
- package/admin/js/templates/pages.html +6 -1
- package/admin/js/templates/pro-docs.html +259 -0
- package/admin/js/templates/role-editor.html +59 -0
- package/admin/js/templates/roles.html +10 -0
- package/admin/js/templates/settings.html +167 -23
- package/admin/js/templates/tutorials.html +81 -0
- package/admin/js/templates/user-editor.html +7 -0
- package/admin/js/templates/users.html +3 -26
- package/admin/js/templates/view-editor.html +201 -0
- package/admin/js/templates/view-preview.html +51 -0
- package/admin/js/templates/views-list.html +19 -0
- package/admin/js/views/action-editor.js +1 -0
- package/admin/js/views/actions-list.js +1 -0
- package/admin/js/views/api-reference.js +1 -0
- package/admin/js/views/block-editor.js +8 -0
- package/admin/js/views/blocks.js +4 -0
- package/admin/js/views/collection-editor.js +3 -3
- package/admin/js/views/collection-entries.js +1 -1
- package/admin/js/views/collections.js +1 -1
- package/admin/js/views/dashboard.js +1 -1
- package/admin/js/views/form-editor.js +8 -0
- package/admin/js/views/form-submissions.js +1 -0
- package/admin/js/views/forms.js +1 -0
- package/admin/js/views/index.js +1 -1
- package/admin/js/views/login.js +2 -2
- package/admin/js/views/media.js +1 -1
- package/admin/js/views/my-profile.js +1 -0
- package/admin/js/views/page-editor.js +34 -15
- package/admin/js/views/pages.js +5 -5
- package/admin/js/views/plugins.js +10 -10
- package/admin/js/views/pro-docs.js +1 -0
- package/admin/js/views/role-editor.js +1 -0
- package/admin/js/views/roles.js +4 -0
- package/admin/js/views/settings.js +3 -1
- package/admin/js/views/user-editor.js +1 -1
- package/admin/js/views/users.js +4 -7
- package/admin/js/views/view-editor.js +1 -0
- package/admin/js/views/view-preview.js +1 -0
- package/admin/js/views/views-list.js +1 -0
- package/bin/cli.js +1 -1
- package/config/auth.json +1 -0
- package/config/connections.json.bak +9 -0
- package/config/connections.json.example +9 -0
- package/config/navigation.json +5 -15
- package/config/plugins.json +19 -29
- package/config/server.json +6 -6
- package/config/site.json +16 -6
- package/package.json +25 -10
- package/plugins/example-analytics/stats.json +17 -12
- package/plugins/form-builder/data/forms/contacts.json +62 -62
- package/plugins/form-builder/data/forms/enquiries.json +103 -0
- package/plugins/form-builder/data/forms/feedback.json +17 -16
- package/plugins/form-builder/data/forms/notes.json +79 -0
- package/plugins/form-builder/data/forms/to-do.json +100 -0
- package/plugins/form-builder/data/submissions/contacts.json +1 -26
- package/plugins/form-builder/data/submissions/notes.json +1 -0
- package/plugins/form-builder/data/submissions/to-do.json +1 -0
- package/plugins/theme-roller/admin/templates/theme-roller.html +71 -0
- package/plugins/theme-roller/admin/views/theme-roller-view.js +403 -0
- package/plugins/theme-roller/config.js +1 -0
- package/plugins/theme-roller/plugin.js +233 -0
- package/plugins/theme-roller/plugin.json +31 -0
- package/plugins/theme-roller/public/active-theme.css +0 -0
- package/plugins/theme-roller/public/inject-head-late.html +1 -0
- package/public/css/forms.css +1 -0
- package/public/css/site.css +1 -1
- package/public/js/forms.js +1 -0
- package/public/js/site.js +1 -1
- package/scripts/build.js +194 -129
- package/scripts/pro.js +254 -0
- package/scripts/reset.js +33 -8
- package/scripts/seed.js +677 -128
- package/scripts/setup.js +1 -0
- package/server/middleware/auth.js +136 -120
- package/server/routes/api/actions.js +200 -0
- package/server/routes/api/auth.js +292 -146
- package/server/routes/api/blocks.js +84 -0
- package/server/routes/api/collections.js +79 -27
- package/{plugins/form-builder/plugin.js → server/routes/api/forms.js} +491 -505
- package/server/routes/api/layouts.js +49 -39
- package/server/routes/api/media.js +118 -92
- package/server/routes/api/navigation.js +40 -36
- package/server/routes/api/pages.js +132 -118
- package/server/routes/api/plugins.js +6 -3
- package/server/routes/api/settings.js +104 -88
- package/server/routes/api/users.js +27 -19
- package/server/routes/api/views.js +148 -0
- package/server/routes/public.js +124 -108
- package/server/server.js +269 -181
- package/server/services/actions.js +387 -0
- package/server/services/adapterRegistry.js +98 -0
- package/server/services/adapters/FileAdapter.js +192 -0
- package/server/services/adapters/MongoAdapter.js +220 -0
- package/server/services/blocks.js +162 -0
- package/server/services/collections.js +74 -86
- package/server/services/connectionManager.js +102 -0
- package/server/services/content.js +312 -307
- package/server/services/email.js +126 -0
- package/server/services/forms.js +173 -0
- package/server/services/markdown.js +1378 -747
- package/server/services/permissionRegistry.js +173 -0
- package/server/services/presetCollections.js +251 -0
- package/server/services/renderer.js +98 -2
- package/server/services/roles.js +227 -0
- package/server/services/rowAccess.js +104 -0
- package/server/services/userProfiles.js +199 -0
- package/server/services/users.js +281 -212
- package/server/services/views.js +280 -0
- package/server/templates/page.html +124 -113
- package/plugins/form-builder/admin/templates/form-settings.html +0 -29
- package/plugins/form-builder/admin/views/form-editor.js +0 -1444
- package/plugins/form-builder/admin/views/form-settings.js +0 -38
- package/plugins/form-builder/admin/views/form-submissions.js +0 -295
- package/plugins/form-builder/admin/views/forms-list.js +0 -164
- package/plugins/form-builder/config.js +0 -9
- package/plugins/form-builder/data/forms/consent.json +0 -104
- package/plugins/form-builder/data/forms/contact-details.json +0 -99
- package/plugins/form-builder/data/submissions/consent.json +0 -13
- package/plugins/form-builder/plugin.json +0 -52
- package/plugins/form-builder/public/inject-body.html +0 -352
- package/plugins/form-builder/public/inject-head.html +0 -58
- package/plugins/form-builder/public/package.json +0 -1
- package/scripts/copy-domma.js +0 -48
- package/server/services/userTypes.js +0 -167
- /package/plugins/form-builder/data/submissions/{contact-details.json → enquiries.json} +0 -0
- /package/{plugins/form-builder/public → public/js}/form-logic-engine.js +0 -0
|
@@ -0,0 +1,1411 @@
|
|
|
1
|
+
<div class="view-header">
|
|
2
|
+
<h1><span data-icon="code"></span> API Reference</h1>
|
|
3
|
+
</div>
|
|
4
|
+
|
|
5
|
+
<style>
|
|
6
|
+
.method-badge {
|
|
7
|
+
display: inline-block;
|
|
8
|
+
font-size: 11px;
|
|
9
|
+
font-weight: 700;
|
|
10
|
+
letter-spacing: 0.5px;
|
|
11
|
+
padding: 2px 7px;
|
|
12
|
+
border-radius: 4px;
|
|
13
|
+
font-family: var(--dm-font-mono, monospace);
|
|
14
|
+
margin-right: 6px;
|
|
15
|
+
vertical-align: middle;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.method-get {
|
|
19
|
+
background: #d1fae5;
|
|
20
|
+
color: #065f46;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.method-post {
|
|
24
|
+
background: #dbeafe;
|
|
25
|
+
color: #1e40af;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.method-put {
|
|
29
|
+
background: #fef3c7;
|
|
30
|
+
color: #92400e;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.method-patch {
|
|
34
|
+
background: #ccfbf1;
|
|
35
|
+
color: #134e4a;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.method-delete {
|
|
39
|
+
background: #fee2e2;
|
|
40
|
+
color: #991b1b;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.endpoint-path {
|
|
44
|
+
font-family: var(--dm-font-mono, monospace);
|
|
45
|
+
font-size: 14px;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.auth-note {
|
|
49
|
+
font-size: 12px;
|
|
50
|
+
color: var(--dm-color-text-muted, #6b7280);
|
|
51
|
+
margin: 4px 0 12px;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.auth-note code {
|
|
55
|
+
font-size: 11px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.docs-body h3 {
|
|
59
|
+
margin-top: 24px;
|
|
60
|
+
margin-bottom: 8px;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.docs-body h3:first-child {
|
|
64
|
+
margin-top: 0;
|
|
65
|
+
}
|
|
66
|
+
</style>
|
|
67
|
+
|
|
68
|
+
<div class="row">
|
|
69
|
+
<div class="col-12">
|
|
70
|
+
|
|
71
|
+
<div class="card mb-4">
|
|
72
|
+
<div class="card-body docs-body">
|
|
73
|
+
<p>
|
|
74
|
+
All admin endpoints are prefixed with <code>/api</code> and require a Bearer token in the
|
|
75
|
+
<code>Authorization</code> header unless noted otherwise. Content type for request bodies is
|
|
76
|
+
<code>application/json</code>. Tokens are obtained via <code>POST /api/auth/login</code>.
|
|
77
|
+
</p>
|
|
78
|
+
<p>
|
|
79
|
+
Base URL: <code>http://your-domain/api</code>
|
|
80
|
+
</p>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<!-- ─── Authentication ─────────────────────────────────────────── -->
|
|
85
|
+
<div class="card card-collapsible mb-4">
|
|
86
|
+
<div class="card-header" role="button" tabindex="0">
|
|
87
|
+
<div class="card-header-content"><h2><span data-icon="lock"></span> Authentication</h2></div>
|
|
88
|
+
<span class="card-collapse-icon" data-icon="chevron-down"></span>
|
|
89
|
+
</div>
|
|
90
|
+
<div class="card-body docs-body">
|
|
91
|
+
|
|
92
|
+
<h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/auth/setup-status</span>
|
|
93
|
+
</h3>
|
|
94
|
+
<p class="auth-note">No authentication required.</p>
|
|
95
|
+
<p>Check whether the CMS has been set up. Returns <code>{ needsSetup: true }</code> when no users exist.</p>
|
|
96
|
+
<pre class="code-block"><code>// Response
|
|
97
|
+
{ "needsSetup": false }</code></pre>
|
|
98
|
+
|
|
99
|
+
<h3><span class="method-badge method-post">POST</span><span class="endpoint-path">/api/auth/setup</span></h3>
|
|
100
|
+
<p class="auth-note">No authentication required. Only succeeds when zero users exist.</p>
|
|
101
|
+
<p>Create the initial admin account. Blocked once any user exists (returns 403).</p>
|
|
102
|
+
<table class="table table-sm">
|
|
103
|
+
<thead>
|
|
104
|
+
<tr>
|
|
105
|
+
<th>Field</th>
|
|
106
|
+
<th>Type</th>
|
|
107
|
+
<th>Description</th>
|
|
108
|
+
</tr>
|
|
109
|
+
</thead>
|
|
110
|
+
<tbody>
|
|
111
|
+
<tr>
|
|
112
|
+
<td><code>name</code></td>
|
|
113
|
+
<td>string</td>
|
|
114
|
+
<td>Display name</td>
|
|
115
|
+
</tr>
|
|
116
|
+
<tr>
|
|
117
|
+
<td><code>email</code></td>
|
|
118
|
+
<td>string</td>
|
|
119
|
+
<td>Email address</td>
|
|
120
|
+
</tr>
|
|
121
|
+
<tr>
|
|
122
|
+
<td><code>password</code></td>
|
|
123
|
+
<td>string</td>
|
|
124
|
+
<td>Minimum 8 characters</td>
|
|
125
|
+
</tr>
|
|
126
|
+
</tbody>
|
|
127
|
+
</table>
|
|
128
|
+
<pre class="code-block"><code>// Response 201
|
|
129
|
+
{ "token": "eyJ...", "refreshToken": "eyJ...", "user": { "id": "...", "name": "...", "email": "...", "role": "admin" } }</code></pre>
|
|
130
|
+
|
|
131
|
+
<h3><span class="method-badge method-post">POST</span><span class="endpoint-path">/api/auth/login</span></h3>
|
|
132
|
+
<p class="auth-note">No authentication required.</p>
|
|
133
|
+
<p>Authenticate with email and password. Returns access and refresh tokens.</p>
|
|
134
|
+
<table class="table table-sm">
|
|
135
|
+
<thead>
|
|
136
|
+
<tr>
|
|
137
|
+
<th>Field</th>
|
|
138
|
+
<th>Type</th>
|
|
139
|
+
<th>Description</th>
|
|
140
|
+
</tr>
|
|
141
|
+
</thead>
|
|
142
|
+
<tbody>
|
|
143
|
+
<tr>
|
|
144
|
+
<td><code>email</code></td>
|
|
145
|
+
<td>string</td>
|
|
146
|
+
<td>User email</td>
|
|
147
|
+
</tr>
|
|
148
|
+
<tr>
|
|
149
|
+
<td><code>password</code></td>
|
|
150
|
+
<td>string</td>
|
|
151
|
+
<td>User password</td>
|
|
152
|
+
</tr>
|
|
153
|
+
</tbody>
|
|
154
|
+
</table>
|
|
155
|
+
<pre class="code-block"><code>// Response 200
|
|
156
|
+
{ "token": "eyJ...", "refreshToken": "eyJ...", "user": { "id": "uuid", "name": "Alice", "email": "alice@example.com", "role": "admin" } }
|
|
157
|
+
|
|
158
|
+
// Error 401
|
|
159
|
+
{ "error": "Invalid credentials" }</code></pre>
|
|
160
|
+
|
|
161
|
+
<h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/auth/me</span></h3>
|
|
162
|
+
<p class="auth-note">Requires Bearer token.</p>
|
|
163
|
+
<p>Return the authenticated user's profile.</p>
|
|
164
|
+
<pre class="code-block"><code>// Response 200
|
|
165
|
+
{ "id": "uuid", "name": "Alice", "email": "alice@example.com", "role": "admin", "isActive": true }</code></pre>
|
|
166
|
+
|
|
167
|
+
<h3><span class="method-badge method-post">POST</span><span class="endpoint-path">/api/auth/logout</span></h3>
|
|
168
|
+
<p class="auth-note">No authentication required. Safe to call without a token.</p>
|
|
169
|
+
<p>Blacklists the provided refresh token. The in-memory blacklist is cleared on server restart.</p>
|
|
170
|
+
<table class="table table-sm">
|
|
171
|
+
<thead>
|
|
172
|
+
<tr>
|
|
173
|
+
<th>Field</th>
|
|
174
|
+
<th>Type</th>
|
|
175
|
+
<th>Description</th>
|
|
176
|
+
</tr>
|
|
177
|
+
</thead>
|
|
178
|
+
<tbody>
|
|
179
|
+
<tr>
|
|
180
|
+
<td><code>refreshToken</code></td>
|
|
181
|
+
<td>string</td>
|
|
182
|
+
<td>The refresh token to revoke (optional)</td>
|
|
183
|
+
</tr>
|
|
184
|
+
</tbody>
|
|
185
|
+
</table>
|
|
186
|
+
<pre class="code-block"><code>// Response 200
|
|
187
|
+
{ "ok": true }</code></pre>
|
|
188
|
+
|
|
189
|
+
<h3><span class="method-badge method-post">POST</span><span class="endpoint-path">/api/auth/refresh</span></h3>
|
|
190
|
+
<p class="auth-note">No authentication required. Provide a valid refresh token.</p>
|
|
191
|
+
<p>Exchange a refresh token for a new access token.</p>
|
|
192
|
+
<table class="table table-sm">
|
|
193
|
+
<thead>
|
|
194
|
+
<tr>
|
|
195
|
+
<th>Field</th>
|
|
196
|
+
<th>Type</th>
|
|
197
|
+
<th>Description</th>
|
|
198
|
+
</tr>
|
|
199
|
+
</thead>
|
|
200
|
+
<tbody>
|
|
201
|
+
<tr>
|
|
202
|
+
<td><code>refreshToken</code></td>
|
|
203
|
+
<td>string</td>
|
|
204
|
+
<td>A valid, non-revoked refresh token</td>
|
|
205
|
+
</tr>
|
|
206
|
+
</tbody>
|
|
207
|
+
</table>
|
|
208
|
+
<pre class="code-block"><code>// Response 200
|
|
209
|
+
{ "token": "eyJ..." }
|
|
210
|
+
|
|
211
|
+
// Error 401
|
|
212
|
+
{ "error": "Invalid or expired refresh token" }</code></pre>
|
|
213
|
+
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
|
|
217
|
+
<!-- ─── Pages ──────────────────────────────────────────────────── -->
|
|
218
|
+
<div class="card card-collapsible mb-4">
|
|
219
|
+
<div class="card-header" role="button" tabindex="0">
|
|
220
|
+
<div class="card-header-content"><h2><span data-icon="file-text"></span> Pages</h2></div>
|
|
221
|
+
<span class="card-collapse-icon" data-icon="chevron-down"></span>
|
|
222
|
+
</div>
|
|
223
|
+
<div class="card-body docs-body">
|
|
224
|
+
|
|
225
|
+
<h3><span class="method-badge method-post">POST</span><span class="endpoint-path">/api/pages/preview</span></h3>
|
|
226
|
+
<p class="auth-note">Requires Bearer token + <code>pages</code> permission.</p>
|
|
227
|
+
<p>Render Markdown to HTML (shortcodes processed, no frontmatter). Useful for live editor previews.</p>
|
|
228
|
+
<table class="table table-sm">
|
|
229
|
+
<thead>
|
|
230
|
+
<tr>
|
|
231
|
+
<th>Field</th>
|
|
232
|
+
<th>Type</th>
|
|
233
|
+
<th>Description</th>
|
|
234
|
+
</tr>
|
|
235
|
+
</thead>
|
|
236
|
+
<tbody>
|
|
237
|
+
<tr>
|
|
238
|
+
<td><code>markdown</code></td>
|
|
239
|
+
<td>string</td>
|
|
240
|
+
<td>Markdown string to render</td>
|
|
241
|
+
</tr>
|
|
242
|
+
</tbody>
|
|
243
|
+
</table>
|
|
244
|
+
<pre class="code-block"><code>// Response 200
|
|
245
|
+
{ "html": "<p>Hello <strong>world</strong></p>" }</code></pre>
|
|
246
|
+
|
|
247
|
+
<h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/pages/tags</span></h3>
|
|
248
|
+
<p class="auth-note">Requires Bearer token + <code>pages</code> permission.</p>
|
|
249
|
+
<p>Aggregate all unique tags across every page, sorted alphabetically.</p>
|
|
250
|
+
<pre class="code-block"><code>// Response 200
|
|
251
|
+
{ "tags": ["guide", "news", "tutorial"] }</code></pre>
|
|
252
|
+
|
|
253
|
+
<h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/pages</span></h3>
|
|
254
|
+
<p class="auth-note">Requires Bearer token + <code>pages</code> permission.</p>
|
|
255
|
+
<p>List all pages with their metadata. Body content is excluded.</p>
|
|
256
|
+
<pre class="code-block"><code>// Response 200
|
|
257
|
+
[
|
|
258
|
+
{
|
|
259
|
+
"urlPath": "/about",
|
|
260
|
+
"title": "About Us",
|
|
261
|
+
"slug": "about",
|
|
262
|
+
"status": "published",
|
|
263
|
+
"layout": "default",
|
|
264
|
+
"showInNav": true,
|
|
265
|
+
"sortOrder": 1,
|
|
266
|
+
"category": null,
|
|
267
|
+
"visibility": "public",
|
|
268
|
+
"tags": [],
|
|
269
|
+
"updatedAt": "2024-01-15T10:30:00.000Z",
|
|
270
|
+
"createdAt": "2024-01-01T09:00:00.000Z"
|
|
271
|
+
}
|
|
272
|
+
]</code></pre>
|
|
273
|
+
|
|
274
|
+
<h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/pages/*</span></h3>
|
|
275
|
+
<p class="auth-note">Requires Bearer token + <code>pages</code> permission. The <code>*</code> is the URL path,
|
|
276
|
+
e.g. <code>/api/pages/about</code> or <code>/api/pages/blog/post-1</code>.</p>
|
|
277
|
+
<p>Retrieve a single page including its frontmatter and full body content.</p>
|
|
278
|
+
<pre class="code-block"><code>// Response 200
|
|
279
|
+
{
|
|
280
|
+
"urlPath": "/about",
|
|
281
|
+
"title": "About Us",
|
|
282
|
+
"body": "## Our Story\n\nWe started...",
|
|
283
|
+
"status": "published",
|
|
284
|
+
...
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Error 404
|
|
288
|
+
{ "error": "Page not found" }</code></pre>
|
|
289
|
+
|
|
290
|
+
<h3><span class="method-badge method-post">POST</span><span class="endpoint-path">/api/pages</span></h3>
|
|
291
|
+
<p class="auth-note">Requires Bearer token + <code>pages</code> permission.</p>
|
|
292
|
+
<p>Create a new page. Fails with 409 if a page already exists at the given path.</p>
|
|
293
|
+
<table class="table table-sm">
|
|
294
|
+
<thead>
|
|
295
|
+
<tr>
|
|
296
|
+
<th>Field</th>
|
|
297
|
+
<th>Type</th>
|
|
298
|
+
<th>Description</th>
|
|
299
|
+
</tr>
|
|
300
|
+
</thead>
|
|
301
|
+
<tbody>
|
|
302
|
+
<tr>
|
|
303
|
+
<td><code>urlPath</code></td>
|
|
304
|
+
<td>string</td>
|
|
305
|
+
<td>Required. Public URL path e.g. <code>/about</code></td>
|
|
306
|
+
</tr>
|
|
307
|
+
<tr>
|
|
308
|
+
<td><code>frontmatter</code></td>
|
|
309
|
+
<td>object</td>
|
|
310
|
+
<td>YAML frontmatter fields (title, status, etc.)</td>
|
|
311
|
+
</tr>
|
|
312
|
+
<tr>
|
|
313
|
+
<td><code>body</code></td>
|
|
314
|
+
<td>string</td>
|
|
315
|
+
<td>Markdown content</td>
|
|
316
|
+
</tr>
|
|
317
|
+
</tbody>
|
|
318
|
+
</table>
|
|
319
|
+
<pre class="code-block"><code>// Response 201 — returns the created page object</code></pre>
|
|
320
|
+
|
|
321
|
+
<h3><span class="method-badge method-put">PUT</span><span class="endpoint-path">/api/pages/*</span></h3>
|
|
322
|
+
<p class="auth-note">Requires Bearer token + <code>pages</code> permission.</p>
|
|
323
|
+
<p>Update an existing page. Optionally rename it to a new URL path (navigation links are rewritten
|
|
324
|
+
automatically).</p>
|
|
325
|
+
<table class="table table-sm">
|
|
326
|
+
<thead>
|
|
327
|
+
<tr>
|
|
328
|
+
<th>Field</th>
|
|
329
|
+
<th>Type</th>
|
|
330
|
+
<th>Description</th>
|
|
331
|
+
</tr>
|
|
332
|
+
</thead>
|
|
333
|
+
<tbody>
|
|
334
|
+
<tr>
|
|
335
|
+
<td><code>frontmatter</code></td>
|
|
336
|
+
<td>object</td>
|
|
337
|
+
<td>Updated frontmatter fields</td>
|
|
338
|
+
</tr>
|
|
339
|
+
<tr>
|
|
340
|
+
<td><code>body</code></td>
|
|
341
|
+
<td>string</td>
|
|
342
|
+
<td>Updated Markdown content</td>
|
|
343
|
+
</tr>
|
|
344
|
+
<tr>
|
|
345
|
+
<td><code>newUrlPath</code></td>
|
|
346
|
+
<td>string</td>
|
|
347
|
+
<td>Optional. Rename the page to a different URL path</td>
|
|
348
|
+
</tr>
|
|
349
|
+
</tbody>
|
|
350
|
+
</table>
|
|
351
|
+
<pre class="code-block"><code>// Response 200 — returns the updated page object
|
|
352
|
+
|
|
353
|
+
// Error 404
|
|
354
|
+
{ "error": "Page not found" }
|
|
355
|
+
|
|
356
|
+
// Error 409
|
|
357
|
+
{ "error": "A page already exists at that path" }</code></pre>
|
|
358
|
+
|
|
359
|
+
<h3><span class="method-badge method-delete">DELETE</span><span class="endpoint-path">/api/pages/*</span></h3>
|
|
360
|
+
<p class="auth-note">Requires Bearer token + <code>pages</code> permission.</p>
|
|
361
|
+
<p>Delete a page and its source file.</p>
|
|
362
|
+
<pre class="code-block"><code>// Response 200
|
|
363
|
+
{ "success": true }
|
|
364
|
+
|
|
365
|
+
// Error 404
|
|
366
|
+
{ "error": "Page not found" }</code></pre>
|
|
367
|
+
|
|
368
|
+
</div>
|
|
369
|
+
</div>
|
|
370
|
+
|
|
371
|
+
<!-- ─── Settings ───────────────────────────────────────────────── -->
|
|
372
|
+
<div class="card card-collapsible mb-4">
|
|
373
|
+
<div class="card-header" role="button" tabindex="0">
|
|
374
|
+
<div class="card-header-content"><h2><span data-icon="settings"></span> Settings</h2></div>
|
|
375
|
+
<span class="card-collapse-icon" data-icon="chevron-down"></span>
|
|
376
|
+
</div>
|
|
377
|
+
<div class="card-body docs-body">
|
|
378
|
+
|
|
379
|
+
<h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/settings</span></h3>
|
|
380
|
+
<p class="auth-note">Requires Bearer token + <code>settings</code> permission.</p>
|
|
381
|
+
<p>Return the full site settings object from <code>config/site.json</code>.</p>
|
|
382
|
+
<pre class="code-block"><code>// Response 200
|
|
383
|
+
{
|
|
384
|
+
"siteName": "My Site",
|
|
385
|
+
"adminTheme": "charcoal-dark",
|
|
386
|
+
"smtp": { "host": "smtp.example.com", "port": 587, ... },
|
|
387
|
+
...
|
|
388
|
+
}</code></pre>
|
|
389
|
+
|
|
390
|
+
<h3><span class="method-badge method-put">PUT</span><span class="endpoint-path">/api/settings</span></h3>
|
|
391
|
+
<p class="auth-note">Requires Bearer token + <code>settings</code> permission.</p>
|
|
392
|
+
<p>Replace the site settings object. Send the full merged object — partial updates overwrite the entire
|
|
393
|
+
config.</p>
|
|
394
|
+
<pre class="code-block"><code>// Request body — full site settings object
|
|
395
|
+
{ "siteName": "My Site", "adminTheme": "ocean-dark", ... }
|
|
396
|
+
|
|
397
|
+
// Response 200
|
|
398
|
+
{ "success": true }</code></pre>
|
|
399
|
+
|
|
400
|
+
<h3><span class="method-badge method-post">POST</span><span
|
|
401
|
+
class="endpoint-path">/api/settings/test-email</span></h3>
|
|
402
|
+
<p class="auth-note">Requires Bearer token + <code>settings</code> permission.</p>
|
|
403
|
+
<p>Send a test email using the stored SMTP configuration. Fails if SMTP host is not configured.</p>
|
|
404
|
+
<table class="table table-sm">
|
|
405
|
+
<thead>
|
|
406
|
+
<tr>
|
|
407
|
+
<th>Field</th>
|
|
408
|
+
<th>Type</th>
|
|
409
|
+
<th>Description</th>
|
|
410
|
+
</tr>
|
|
411
|
+
</thead>
|
|
412
|
+
<tbody>
|
|
413
|
+
<tr>
|
|
414
|
+
<td><code>to</code></td>
|
|
415
|
+
<td>string</td>
|
|
416
|
+
<td>Optional. Recipient address. Defaults to the configured From Address.</td>
|
|
417
|
+
</tr>
|
|
418
|
+
</tbody>
|
|
419
|
+
</table>
|
|
420
|
+
<pre class="code-block"><code>// Response 200
|
|
421
|
+
{ "success": true, "message": "Test email sent to alice@example.com" }
|
|
422
|
+
|
|
423
|
+
// Error 400
|
|
424
|
+
{ "error": "SMTP is not configured. Save your SMTP settings first." }</code></pre>
|
|
425
|
+
|
|
426
|
+
<h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/settings/custom-css</span>
|
|
427
|
+
</h3>
|
|
428
|
+
<p class="auth-note">Requires Bearer token + <code>settings</code> permission.</p>
|
|
429
|
+
<p>Return the current custom CSS from <code>content/custom.css</code>. Returns an empty string if the file does
|
|
430
|
+
not exist.</p>
|
|
431
|
+
<pre class="code-block"><code>// Response 200
|
|
432
|
+
{ "css": "body { font-family: sans-serif; }" }</code></pre>
|
|
433
|
+
|
|
434
|
+
<h3><span class="method-badge method-put">PUT</span><span class="endpoint-path">/api/settings/custom-css</span>
|
|
435
|
+
</h3>
|
|
436
|
+
<p class="auth-note">Requires Bearer token + <code>settings</code> permission.</p>
|
|
437
|
+
<p>Write CSS to <code>content/custom.css</code>. Maximum size is 100 KB.</p>
|
|
438
|
+
<table class="table table-sm">
|
|
439
|
+
<thead>
|
|
440
|
+
<tr>
|
|
441
|
+
<th>Field</th>
|
|
442
|
+
<th>Type</th>
|
|
443
|
+
<th>Description</th>
|
|
444
|
+
</tr>
|
|
445
|
+
</thead>
|
|
446
|
+
<tbody>
|
|
447
|
+
<tr>
|
|
448
|
+
<td><code>css</code></td>
|
|
449
|
+
<td>string</td>
|
|
450
|
+
<td>CSS string (max 100 KB)</td>
|
|
451
|
+
</tr>
|
|
452
|
+
</tbody>
|
|
453
|
+
</table>
|
|
454
|
+
<pre class="code-block"><code>// Response 200
|
|
455
|
+
{ "success": true }</code></pre>
|
|
456
|
+
|
|
457
|
+
</div>
|
|
458
|
+
</div>
|
|
459
|
+
|
|
460
|
+
<!-- ─── Layouts ────────────────────────────────────────────────── -->
|
|
461
|
+
<div class="card card-collapsible mb-4">
|
|
462
|
+
<div class="card-header" role="button" tabindex="0">
|
|
463
|
+
<div class="card-header-content"><h2><span data-icon="layout"></span> Layouts</h2></div>
|
|
464
|
+
<span class="card-collapse-icon" data-icon="chevron-down"></span>
|
|
465
|
+
</div>
|
|
466
|
+
<div class="card-body docs-body">
|
|
467
|
+
|
|
468
|
+
<h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/layouts</span></h3>
|
|
469
|
+
<p class="auth-note">Requires Bearer token + <code>layouts</code> permission.</p>
|
|
470
|
+
<p>Return all layout presets from <code>config/presets.json</code>.</p>
|
|
471
|
+
<pre class="code-block"><code>// Response 200
|
|
472
|
+
{
|
|
473
|
+
"default": { "label": "Default", "sections": [...] },
|
|
474
|
+
"full-width": { "label": "Full Width", "sections": [...] }
|
|
475
|
+
}</code></pre>
|
|
476
|
+
|
|
477
|
+
<h3><span class="method-badge method-put">PUT</span><span class="endpoint-path">/api/layouts</span></h3>
|
|
478
|
+
<p class="auth-note">Requires Bearer token + <code>layouts</code> permission.</p>
|
|
479
|
+
<p>Replace the entire layout presets object.</p>
|
|
480
|
+
<pre class="code-block"><code>// Response 200
|
|
481
|
+
{ "success": true }</code></pre>
|
|
482
|
+
|
|
483
|
+
<h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/layouts/options</span></h3>
|
|
484
|
+
<p class="auth-note">Requires Bearer token + <code>layouts</code> permission.</p>
|
|
485
|
+
<p>Return layout display options (e.g. spacer size) stored in <code>config/site.json</code> under <code>layoutOptions</code>.
|
|
486
|
+
</p>
|
|
487
|
+
<pre class="code-block"><code>// Response 200
|
|
488
|
+
{ "spacerSize": 8 }</code></pre>
|
|
489
|
+
|
|
490
|
+
<h3><span class="method-badge method-put">PUT</span><span class="endpoint-path">/api/layouts/options</span></h3>
|
|
491
|
+
<p class="auth-note">Requires Bearer token + <code>layouts</code> permission.</p>
|
|
492
|
+
<p>Merge layout option updates into the existing options. Existing keys not included in the request are
|
|
493
|
+
preserved.</p>
|
|
494
|
+
<table class="table table-sm">
|
|
495
|
+
<thead>
|
|
496
|
+
<tr>
|
|
497
|
+
<th>Field</th>
|
|
498
|
+
<th>Type</th>
|
|
499
|
+
<th>Description</th>
|
|
500
|
+
</tr>
|
|
501
|
+
</thead>
|
|
502
|
+
<tbody>
|
|
503
|
+
<tr>
|
|
504
|
+
<td><code>spacerSize</code></td>
|
|
505
|
+
<td>number</td>
|
|
506
|
+
<td>Default spacer block size in pixels</td>
|
|
507
|
+
</tr>
|
|
508
|
+
</tbody>
|
|
509
|
+
</table>
|
|
510
|
+
<pre class="code-block"><code>// Response 200
|
|
511
|
+
{ "success": true }</code></pre>
|
|
512
|
+
|
|
513
|
+
</div>
|
|
514
|
+
</div>
|
|
515
|
+
|
|
516
|
+
<!-- ─── Navigation ─────────────────────────────────────────────── -->
|
|
517
|
+
<div class="card card-collapsible mb-4">
|
|
518
|
+
<div class="card-header" role="button" tabindex="0">
|
|
519
|
+
<div class="card-header-content"><h2><span data-icon="menu"></span> Navigation</h2></div>
|
|
520
|
+
<span class="card-collapse-icon" data-icon="chevron-down"></span>
|
|
521
|
+
</div>
|
|
522
|
+
<div class="card-body docs-body">
|
|
523
|
+
|
|
524
|
+
<h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/navigation</span></h3>
|
|
525
|
+
<p class="auth-note">Requires Bearer token + <code>navigation</code> permission.</p>
|
|
526
|
+
<p>Return the navigation configuration from <code>config/navigation.json</code>.</p>
|
|
527
|
+
<pre class="code-block"><code>// Response 200
|
|
528
|
+
{
|
|
529
|
+
"items": [
|
|
530
|
+
{ "label": "Home", "url": "/" },
|
|
531
|
+
{ "label": "About", "url": "/about" },
|
|
532
|
+
{
|
|
533
|
+
"label": "Resources",
|
|
534
|
+
"items": [
|
|
535
|
+
{ "label": "Blog", "url": "/blog" }
|
|
536
|
+
]
|
|
537
|
+
}
|
|
538
|
+
]
|
|
539
|
+
}</code></pre>
|
|
540
|
+
|
|
541
|
+
<h3><span class="method-badge method-put">PUT</span><span class="endpoint-path">/api/navigation</span></h3>
|
|
542
|
+
<p class="auth-note">Requires Bearer token + <code>navigation</code> permission.</p>
|
|
543
|
+
<p>Replace the navigation config. Sub-items must use the <code>items</code> key (the server normalises <code>children</code>
|
|
544
|
+
to <code>items</code> automatically).</p>
|
|
545
|
+
<pre class="code-block"><code>// Request body
|
|
546
|
+
{
|
|
547
|
+
"items": [
|
|
548
|
+
{ "label": "Home", "url": "/" },
|
|
549
|
+
{ "label": "About", "url": "/about" }
|
|
550
|
+
]
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Response 200
|
|
554
|
+
{ "success": true }</code></pre>
|
|
555
|
+
|
|
556
|
+
</div>
|
|
557
|
+
</div>
|
|
558
|
+
|
|
559
|
+
<!-- ─── Media ──────────────────────────────────────────────────── -->
|
|
560
|
+
<div class="card card-collapsible mb-4">
|
|
561
|
+
<div class="card-header" role="button" tabindex="0">
|
|
562
|
+
<div class="card-header-content"><h2><span data-icon="image"></span> Media</h2></div>
|
|
563
|
+
<span class="card-collapse-icon" data-icon="chevron-down"></span>
|
|
564
|
+
</div>
|
|
565
|
+
<div class="card-body docs-body">
|
|
566
|
+
|
|
567
|
+
<h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/media</span></h3>
|
|
568
|
+
<p class="auth-note">Requires Bearer token + <code>media</code> permission.</p>
|
|
569
|
+
<p>List all media files in the uploads directory.</p>
|
|
570
|
+
<pre class="code-block"><code>// Response 200
|
|
571
|
+
[
|
|
572
|
+
{ "name": "hero.jpg", "url": "/media/hero.jpg", "size": 204800, "mime": "image/jpeg" }
|
|
573
|
+
]</code></pre>
|
|
574
|
+
|
|
575
|
+
<h3><span class="method-badge method-post">POST</span><span class="endpoint-path">/api/media</span></h3>
|
|
576
|
+
<p class="auth-note">Requires Bearer token + <code>media</code> permission. Content-Type: <code>multipart/form-data</code>.
|
|
577
|
+
</p>
|
|
578
|
+
<p>Upload one or more files. Filenames are sanitised (only alphanumeric, dot, underscore, hyphen allowed).
|
|
579
|
+
Returns a single object for one file, or an array for multiple.</p>
|
|
580
|
+
<pre class="code-block"><code>// Response 201 (single file)
|
|
581
|
+
{ "name": "photo.jpg", "url": "/media/photo.jpg", "size": 98304, "mime": "image/jpeg" }
|
|
582
|
+
|
|
583
|
+
// Response 201 (multiple files)
|
|
584
|
+
[
|
|
585
|
+
{ "name": "photo1.jpg", "url": "/media/photo1.jpg", ... },
|
|
586
|
+
{ "name": "photo2.jpg", "url": "/media/photo2.jpg", ... }
|
|
587
|
+
]</code></pre>
|
|
588
|
+
|
|
589
|
+
<h3><span class="method-badge method-patch">PATCH</span><span class="endpoint-path">/api/media/:name</span></h3>
|
|
590
|
+
<p class="auth-note">Requires Bearer token + <code>media</code> permission.</p>
|
|
591
|
+
<p>Rename a media file. Returns the updated file info. Fails with 409 if the new name already exists.</p>
|
|
592
|
+
<table class="table table-sm">
|
|
593
|
+
<thead>
|
|
594
|
+
<tr>
|
|
595
|
+
<th>Field</th>
|
|
596
|
+
<th>Type</th>
|
|
597
|
+
<th>Description</th>
|
|
598
|
+
</tr>
|
|
599
|
+
</thead>
|
|
600
|
+
<tbody>
|
|
601
|
+
<tr>
|
|
602
|
+
<td><code>newName</code></td>
|
|
603
|
+
<td>string</td>
|
|
604
|
+
<td>New filename (will be sanitised)</td>
|
|
605
|
+
</tr>
|
|
606
|
+
</tbody>
|
|
607
|
+
</table>
|
|
608
|
+
<pre class="code-block"><code>// Response 200
|
|
609
|
+
{ "name": "new-photo.jpg", "url": "/media/new-photo.jpg", ... }</code></pre>
|
|
610
|
+
|
|
611
|
+
<h3><span class="method-badge method-delete">DELETE</span><span class="endpoint-path">/api/media/:name</span>
|
|
612
|
+
</h3>
|
|
613
|
+
<p class="auth-note">Requires Bearer token + <code>media</code> permission.</p>
|
|
614
|
+
<p>Delete a media file from the uploads directory.</p>
|
|
615
|
+
<pre class="code-block"><code>// Response 200
|
|
616
|
+
{ "success": true }</code></pre>
|
|
617
|
+
|
|
618
|
+
<h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/media/:name/info</span>
|
|
619
|
+
</h3>
|
|
620
|
+
<p class="auth-note">Requires Bearer token + <code>media</code> permission.</p>
|
|
621
|
+
<p>Return image metadata (dimensions, format, file size) for editable image formats (JPEG, PNG, WebP, GIF,
|
|
622
|
+
TIFF).</p>
|
|
623
|
+
<pre class="code-block"><code>// Response 200
|
|
624
|
+
{ "width": 1920, "height": 1080, "format": "jpeg", "size": 204800 }
|
|
625
|
+
|
|
626
|
+
// Error 400
|
|
627
|
+
{ "error": "Not an editable image format" }</code></pre>
|
|
628
|
+
|
|
629
|
+
<h3><span class="method-badge method-post">POST</span><span
|
|
630
|
+
class="endpoint-path">/api/media/:name/transform</span></h3>
|
|
631
|
+
<p class="auth-note">Requires Bearer token + <code>media</code> permission.</p>
|
|
632
|
+
<p>Apply image transformations (resize, crop, rotate, watermark, etc.) and optionally save to a new
|
|
633
|
+
filename.</p>
|
|
634
|
+
<table class="table table-sm">
|
|
635
|
+
<thead>
|
|
636
|
+
<tr>
|
|
637
|
+
<th>Field</th>
|
|
638
|
+
<th>Type</th>
|
|
639
|
+
<th>Description</th>
|
|
640
|
+
</tr>
|
|
641
|
+
</thead>
|
|
642
|
+
<tbody>
|
|
643
|
+
<tr>
|
|
644
|
+
<td><code>operations</code></td>
|
|
645
|
+
<td>object</td>
|
|
646
|
+
<td>Transformation operations to apply</td>
|
|
647
|
+
</tr>
|
|
648
|
+
<tr>
|
|
649
|
+
<td><code>saveAs</code></td>
|
|
650
|
+
<td>string</td>
|
|
651
|
+
<td>Optional. Output filename. Defaults to overwriting the source.</td>
|
|
652
|
+
</tr>
|
|
653
|
+
</tbody>
|
|
654
|
+
</table>
|
|
655
|
+
<pre class="code-block"><code>// Request body example
|
|
656
|
+
{
|
|
657
|
+
"operations": { "resize": { "width": 800, "height": 600 }, "format": "webp" },
|
|
658
|
+
"saveAs": "hero-800.webp"
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// Response 200
|
|
662
|
+
{ "name": "hero-800.webp", "url": "/media/hero-800.webp", ... }</code></pre>
|
|
663
|
+
|
|
664
|
+
</div>
|
|
665
|
+
</div>
|
|
666
|
+
|
|
667
|
+
<!-- ─── Users ──────────────────────────────────────────────────── -->
|
|
668
|
+
<div class="card card-collapsible mb-4">
|
|
669
|
+
<div class="card-header" role="button" tabindex="0">
|
|
670
|
+
<div class="card-header-content"><h2><span data-icon="users"></span> Users</h2></div>
|
|
671
|
+
<span class="card-collapse-icon" data-icon="chevron-down"></span>
|
|
672
|
+
</div>
|
|
673
|
+
<div class="card-body docs-body">
|
|
674
|
+
|
|
675
|
+
<p>Role hierarchy governs which users can manage other users. A manager cannot create, edit, or delete an admin.
|
|
676
|
+
Self-deletion is always blocked.</p>
|
|
677
|
+
|
|
678
|
+
<h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/users</span></h3>
|
|
679
|
+
<p class="auth-note">Requires Bearer token + <code>users</code> permission (admin or manager).</p>
|
|
680
|
+
<p>Return all users. Passwords are stripped from the response.</p>
|
|
681
|
+
<pre class="code-block"><code>// Response 200
|
|
682
|
+
[
|
|
683
|
+
{ "id": "uuid", "name": "Alice", "email": "alice@example.com", "role": "admin", "isActive": true }
|
|
684
|
+
]</code></pre>
|
|
685
|
+
|
|
686
|
+
<h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/users/:id</span></h3>
|
|
687
|
+
<p class="auth-note">Requires Bearer token. Accessible to the user themselves, or a user with <code>users</code>
|
|
688
|
+
permission.</p>
|
|
689
|
+
<p>Return a single user by ID.</p>
|
|
690
|
+
<pre class="code-block"><code>// Response 200
|
|
691
|
+
{ "id": "uuid", "name": "Alice", "email": "alice@example.com", "role": "admin", "isActive": true }
|
|
692
|
+
|
|
693
|
+
// Error 404
|
|
694
|
+
{ "error": "User not found" }</code></pre>
|
|
695
|
+
|
|
696
|
+
<h3><span class="method-badge method-post">POST</span><span class="endpoint-path">/api/users</span></h3>
|
|
697
|
+
<p class="auth-note">Requires Bearer token + <code>users</code> permission.</p>
|
|
698
|
+
<p>Create a new user. The actor cannot assign a role higher than their own level.</p>
|
|
699
|
+
<table class="table table-sm">
|
|
700
|
+
<thead>
|
|
701
|
+
<tr>
|
|
702
|
+
<th>Field</th>
|
|
703
|
+
<th>Type</th>
|
|
704
|
+
<th>Description</th>
|
|
705
|
+
</tr>
|
|
706
|
+
</thead>
|
|
707
|
+
<tbody>
|
|
708
|
+
<tr>
|
|
709
|
+
<td><code>name</code></td>
|
|
710
|
+
<td>string</td>
|
|
711
|
+
<td>Display name</td>
|
|
712
|
+
</tr>
|
|
713
|
+
<tr>
|
|
714
|
+
<td><code>email</code></td>
|
|
715
|
+
<td>string</td>
|
|
716
|
+
<td>Unique email address</td>
|
|
717
|
+
</tr>
|
|
718
|
+
<tr>
|
|
719
|
+
<td><code>password</code></td>
|
|
720
|
+
<td>string</td>
|
|
721
|
+
<td>Minimum 8 characters</td>
|
|
722
|
+
</tr>
|
|
723
|
+
<tr>
|
|
724
|
+
<td><code>role</code></td>
|
|
725
|
+
<td>string</td>
|
|
726
|
+
<td>Optional. Defaults to <code>editor</code>.</td>
|
|
727
|
+
</tr>
|
|
728
|
+
</tbody>
|
|
729
|
+
</table>
|
|
730
|
+
<pre class="code-block"><code>// Response 201
|
|
731
|
+
{ "id": "uuid", "name": "Bob", "email": "bob@example.com", "role": "editor", "isActive": true }
|
|
732
|
+
|
|
733
|
+
// Error 409
|
|
734
|
+
{ "error": "Email already in use" }</code></pre>
|
|
735
|
+
|
|
736
|
+
<h3><span class="method-badge method-put">PUT</span><span class="endpoint-path">/api/users/:id</span></h3>
|
|
737
|
+
<p class="auth-note">Requires Bearer token + <code>users</code> permission.</p>
|
|
738
|
+
<p>Update a user's details. Managers cannot edit admins. Role escalation beyond the actor's own level is
|
|
739
|
+
blocked.</p>
|
|
740
|
+
<table class="table table-sm">
|
|
741
|
+
<thead>
|
|
742
|
+
<tr>
|
|
743
|
+
<th>Field</th>
|
|
744
|
+
<th>Type</th>
|
|
745
|
+
<th>Description</th>
|
|
746
|
+
</tr>
|
|
747
|
+
</thead>
|
|
748
|
+
<tbody>
|
|
749
|
+
<tr>
|
|
750
|
+
<td><code>name</code></td>
|
|
751
|
+
<td>string</td>
|
|
752
|
+
<td>New display name</td>
|
|
753
|
+
</tr>
|
|
754
|
+
<tr>
|
|
755
|
+
<td><code>email</code></td>
|
|
756
|
+
<td>string</td>
|
|
757
|
+
<td>New email address</td>
|
|
758
|
+
</tr>
|
|
759
|
+
<tr>
|
|
760
|
+
<td><code>password</code></td>
|
|
761
|
+
<td>string</td>
|
|
762
|
+
<td>New password (min 8 chars)</td>
|
|
763
|
+
</tr>
|
|
764
|
+
<tr>
|
|
765
|
+
<td><code>role</code></td>
|
|
766
|
+
<td>string</td>
|
|
767
|
+
<td>New role</td>
|
|
768
|
+
</tr>
|
|
769
|
+
<tr>
|
|
770
|
+
<td><code>isActive</code></td>
|
|
771
|
+
<td>boolean</td>
|
|
772
|
+
<td>Enable or disable the account</td>
|
|
773
|
+
</tr>
|
|
774
|
+
</tbody>
|
|
775
|
+
</table>
|
|
776
|
+
<pre class="code-block"><code>// Response 200 — returns the updated user object</code></pre>
|
|
777
|
+
|
|
778
|
+
<h3><span class="method-badge method-delete">DELETE</span><span class="endpoint-path">/api/users/:id</span></h3>
|
|
779
|
+
<p class="auth-note">Requires Bearer token + <code>users</code> permission.</p>
|
|
780
|
+
<p>Delete a user. Cannot delete your own account or a user with a higher role level.</p>
|
|
781
|
+
<pre class="code-block"><code>// Response 200
|
|
782
|
+
{ "success": true }
|
|
783
|
+
|
|
784
|
+
// Error 403
|
|
785
|
+
{ "error": "You cannot delete your own account" }</code></pre>
|
|
786
|
+
|
|
787
|
+
</div>
|
|
788
|
+
</div>
|
|
789
|
+
|
|
790
|
+
<!-- ─── Plugins ────────────────────────────────────────────────── -->
|
|
791
|
+
<div class="card card-collapsible mb-4">
|
|
792
|
+
<div class="card-header" role="button" tabindex="0">
|
|
793
|
+
<div class="card-header-content"><h2><span data-icon="package"></span> Plugins</h2></div>
|
|
794
|
+
<span class="card-collapse-icon" data-icon="chevron-down"></span>
|
|
795
|
+
</div>
|
|
796
|
+
<div class="card-body docs-body">
|
|
797
|
+
|
|
798
|
+
<h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/plugins</span></h3>
|
|
799
|
+
<p class="auth-note">Requires Bearer token + admin role.</p>
|
|
800
|
+
<p>List all discovered plugins with their current enabled state and settings.</p>
|
|
801
|
+
<pre class="code-block"><code>// Response 200
|
|
802
|
+
[
|
|
803
|
+
{
|
|
804
|
+
"name": "form-builder",
|
|
805
|
+
"displayName": "Form Builder",
|
|
806
|
+
"version": "1.0.0",
|
|
807
|
+
"description": "Build and manage contact forms.",
|
|
808
|
+
"author": "Domma Team",
|
|
809
|
+
"date": "2024-01-01",
|
|
810
|
+
"icon": "clipboard",
|
|
811
|
+
"enabled": true,
|
|
812
|
+
"settings": {}
|
|
813
|
+
}
|
|
814
|
+
]</code></pre>
|
|
815
|
+
|
|
816
|
+
<h3><span class="method-badge method-put">PUT</span><span class="endpoint-path">/api/plugins/:name</span></h3>
|
|
817
|
+
<p class="auth-note">Requires Bearer token + admin role.</p>
|
|
818
|
+
<p>Enable or disable a plugin, or update its settings. Plugin must exist in the <code>plugins/</code> directory.
|
|
819
|
+
</p>
|
|
820
|
+
<table class="table table-sm">
|
|
821
|
+
<thead>
|
|
822
|
+
<tr>
|
|
823
|
+
<th>Field</th>
|
|
824
|
+
<th>Type</th>
|
|
825
|
+
<th>Description</th>
|
|
826
|
+
</tr>
|
|
827
|
+
</thead>
|
|
828
|
+
<tbody>
|
|
829
|
+
<tr>
|
|
830
|
+
<td><code>enabled</code></td>
|
|
831
|
+
<td>boolean</td>
|
|
832
|
+
<td>Whether the plugin is active</td>
|
|
833
|
+
</tr>
|
|
834
|
+
<tr>
|
|
835
|
+
<td><code>settings</code></td>
|
|
836
|
+
<td>object</td>
|
|
837
|
+
<td>Plugin-specific settings object</td>
|
|
838
|
+
</tr>
|
|
839
|
+
</tbody>
|
|
840
|
+
</table>
|
|
841
|
+
<pre class="code-block"><code>// Response 200
|
|
842
|
+
{ "success": true }
|
|
843
|
+
|
|
844
|
+
// Error 404
|
|
845
|
+
{ "error": "Plugin not found" }</code></pre>
|
|
846
|
+
|
|
847
|
+
<h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/plugins/admin-config</span>
|
|
848
|
+
</h3>
|
|
849
|
+
<p class="auth-note">Requires Bearer token (any role).</p>
|
|
850
|
+
<p>Return the merged admin configuration for all enabled plugins — sidebar items, SPA routes, and view entry
|
|
851
|
+
points. Used by the admin SPA on startup to inject plugin UI.</p>
|
|
852
|
+
<pre class="code-block"><code>// Response 200
|
|
853
|
+
{
|
|
854
|
+
"sidebar": [
|
|
855
|
+
{ "id": "form-builder", "text": "Form Settings", "icon": "clipboard", "url": "#/form-settings" }
|
|
856
|
+
],
|
|
857
|
+
"routes": [
|
|
858
|
+
{ "path": "/form-settings", "view": "formSettings", "title": "Form Settings - Domma CMS" }
|
|
859
|
+
],
|
|
860
|
+
"views": {
|
|
861
|
+
"formSettings": { "entry": "form-builder/admin/views/settings.js", "exportName": "formSettingsView" }
|
|
862
|
+
}
|
|
863
|
+
}</code></pre>
|
|
864
|
+
|
|
865
|
+
</div>
|
|
866
|
+
</div>
|
|
867
|
+
|
|
868
|
+
<!-- ─── Collections ────────────────────────────────────────────── -->
|
|
869
|
+
<div class="card card-collapsible mb-4">
|
|
870
|
+
<div class="card-header" role="button" tabindex="0">
|
|
871
|
+
<div class="card-header-content"><h2><span data-icon="database"></span> Collections</h2></div>
|
|
872
|
+
<span class="card-collapse-icon" data-icon="chevron-down"></span>
|
|
873
|
+
</div>
|
|
874
|
+
<div class="card-body docs-body">
|
|
875
|
+
|
|
876
|
+
<p>Collections have two access planes: <strong>admin endpoints</strong> (authenticated, role-gated) and <strong>public
|
|
877
|
+
endpoints</strong> (access level configured per collection).</p>
|
|
878
|
+
|
|
879
|
+
<h3 style="margin-top:16px;font-size:15px;text-transform:uppercase;letter-spacing:.5px;opacity:.6">Schema
|
|
880
|
+
Management</h3>
|
|
881
|
+
|
|
882
|
+
<h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/collections</span></h3>
|
|
883
|
+
<p class="auth-note">Requires Bearer token + <code>collections</code> permission.</p>
|
|
884
|
+
<p>List all collection schemas (metadata only, no entries).</p>
|
|
885
|
+
<pre class="code-block"><code>// Response 200
|
|
886
|
+
[
|
|
887
|
+
{ "slug": "blog", "title": "Blog Posts", "description": "...", "fields": [...] }
|
|
888
|
+
]</code></pre>
|
|
889
|
+
|
|
890
|
+
<h3><span class="method-badge method-get">GET</span><span
|
|
891
|
+
class="endpoint-path">/api/collections/pro-status</span></h3>
|
|
892
|
+
<p class="auth-note">Requires Bearer token + <code>collections</code> permission.</p>
|
|
893
|
+
<p>Check whether the Pro (MongoDB) storage adapter is available. Returns named connections if configured.</p>
|
|
894
|
+
<pre class="code-block"><code>// Response 200 (free)
|
|
895
|
+
{ "pro": false, "connections": [] }
|
|
896
|
+
|
|
897
|
+
// Response 200 (pro)
|
|
898
|
+
{ "pro": true, "connections": ["default", "analytics"] }</code></pre>
|
|
899
|
+
|
|
900
|
+
<h3><span class="method-badge method-get">GET</span><span
|
|
901
|
+
class="endpoint-path">/api/collections/connections</span></h3>
|
|
902
|
+
<p class="auth-note">Requires Bearer token + admin role.</p>
|
|
903
|
+
<p>Return configured MongoDB connections from <code>config/connections.json</code>.</p>
|
|
904
|
+
<pre class="code-block"><code>// Response 200
|
|
905
|
+
{
|
|
906
|
+
"default": { "type": "mongodb", "uri": "mongodb://localhost:27017", "database": "my_cms" }
|
|
907
|
+
}</code></pre>
|
|
908
|
+
|
|
909
|
+
<h3><span class="method-badge method-put">PUT</span><span
|
|
910
|
+
class="endpoint-path">/api/collections/connections</span></h3>
|
|
911
|
+
<p class="auth-note">Requires Bearer token + admin role.</p>
|
|
912
|
+
<p>Save MongoDB connection definitions. Each connection requires <code>type</code>, <code>uri</code>, and <code>database</code>.
|
|
913
|
+
</p>
|
|
914
|
+
<table class="table table-sm">
|
|
915
|
+
<thead>
|
|
916
|
+
<tr>
|
|
917
|
+
<th>Field</th>
|
|
918
|
+
<th>Type</th>
|
|
919
|
+
<th>Description</th>
|
|
920
|
+
</tr>
|
|
921
|
+
</thead>
|
|
922
|
+
<tbody>
|
|
923
|
+
<tr>
|
|
924
|
+
<td><code>{name}</code></td>
|
|
925
|
+
<td>object</td>
|
|
926
|
+
<td>Named connection with <code>type</code>, <code>uri</code>, <code>database</code></td>
|
|
927
|
+
</tr>
|
|
928
|
+
</tbody>
|
|
929
|
+
</table>
|
|
930
|
+
<pre class="code-block"><code>// Response 200
|
|
931
|
+
{ "success": true }
|
|
932
|
+
|
|
933
|
+
// Error 400
|
|
934
|
+
{ "error": "Connection \"default\" requires type, uri, and database" }</code></pre>
|
|
935
|
+
|
|
936
|
+
<h3><span class="method-badge method-post">POST</span><span class="endpoint-path">/api/collections</span></h3>
|
|
937
|
+
<p class="auth-note">Requires Bearer token + <code>collections</code> permission.</p>
|
|
938
|
+
<p>Create a new collection. A <code>slug</code> is auto-generated from the title if not provided.</p>
|
|
939
|
+
<table class="table table-sm">
|
|
940
|
+
<thead>
|
|
941
|
+
<tr>
|
|
942
|
+
<th>Field</th>
|
|
943
|
+
<th>Type</th>
|
|
944
|
+
<th>Description</th>
|
|
945
|
+
</tr>
|
|
946
|
+
</thead>
|
|
947
|
+
<tbody>
|
|
948
|
+
<tr>
|
|
949
|
+
<td><code>title</code></td>
|
|
950
|
+
<td>string</td>
|
|
951
|
+
<td>Required. Human-readable collection name</td>
|
|
952
|
+
</tr>
|
|
953
|
+
<tr>
|
|
954
|
+
<td><code>slug</code></td>
|
|
955
|
+
<td>string</td>
|
|
956
|
+
<td>Optional. URL-safe identifier. Auto-generated if omitted.</td>
|
|
957
|
+
</tr>
|
|
958
|
+
<tr>
|
|
959
|
+
<td><code>description</code></td>
|
|
960
|
+
<td>string</td>
|
|
961
|
+
<td>Optional description</td>
|
|
962
|
+
</tr>
|
|
963
|
+
<tr>
|
|
964
|
+
<td><code>fields</code></td>
|
|
965
|
+
<td>array</td>
|
|
966
|
+
<td>Field definitions</td>
|
|
967
|
+
</tr>
|
|
968
|
+
<tr>
|
|
969
|
+
<td><code>api</code></td>
|
|
970
|
+
<td>object</td>
|
|
971
|
+
<td>Public API access config per operation</td>
|
|
972
|
+
</tr>
|
|
973
|
+
<tr>
|
|
974
|
+
<td><code>storage</code></td>
|
|
975
|
+
<td>object</td>
|
|
976
|
+
<td>Optional. Pro: <code>{ "adapter": "mongodb", "connection": "default" }</code></td>
|
|
977
|
+
</tr>
|
|
978
|
+
</tbody>
|
|
979
|
+
</table>
|
|
980
|
+
<pre class="code-block"><code>// Response 201 — returns the created schema object
|
|
981
|
+
|
|
982
|
+
// Error 409
|
|
983
|
+
{ "error": "A collection with that slug already exists" }</code></pre>
|
|
984
|
+
|
|
985
|
+
<h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/collections/:slug</span>
|
|
986
|
+
</h3>
|
|
987
|
+
<p class="auth-note">Requires Bearer token + <code>collections</code> permission.</p>
|
|
988
|
+
<p>Return the schema for a single collection by slug.</p>
|
|
989
|
+
<pre class="code-block"><code>// Response 200
|
|
990
|
+
{
|
|
991
|
+
"slug": "blog",
|
|
992
|
+
"title": "Blog Posts",
|
|
993
|
+
"fields": [
|
|
994
|
+
{ "name": "title", "type": "text", "required": true },
|
|
995
|
+
{ "name": "body", "type": "richtext" }
|
|
996
|
+
],
|
|
997
|
+
"api": { "read": { "enabled": true, "access": "public" }, ... }
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
// Error 404
|
|
1001
|
+
{ "error": "Collection not found" }</code></pre>
|
|
1002
|
+
|
|
1003
|
+
<h3><span class="method-badge method-put">PUT</span><span class="endpoint-path">/api/collections/:slug</span>
|
|
1004
|
+
</h3>
|
|
1005
|
+
<p class="auth-note">Requires Bearer token + <code>collections</code> permission.</p>
|
|
1006
|
+
<p>Update a collection schema.</p>
|
|
1007
|
+
<pre class="code-block"><code>// Response 200 — returns the updated schema
|
|
1008
|
+
|
|
1009
|
+
// Error 404
|
|
1010
|
+
{ "error": "Collection not found" }</code></pre>
|
|
1011
|
+
|
|
1012
|
+
<h3><span class="method-badge method-delete">DELETE</span><span
|
|
1013
|
+
class="endpoint-path">/api/collections/:slug</span></h3>
|
|
1014
|
+
<p class="auth-note">Requires Bearer token + <code>collections</code> permission.</p>
|
|
1015
|
+
<p>Delete a collection and all its entries. Preset collections (e.g. <code>roles</code>) cannot be deleted.
|
|
1016
|
+
</p>
|
|
1017
|
+
<pre class="code-block"><code>// Response 200
|
|
1018
|
+
{ "success": true }
|
|
1019
|
+
|
|
1020
|
+
// Error 403
|
|
1021
|
+
{ "error": "Cannot delete a preset collection" }</code></pre>
|
|
1022
|
+
|
|
1023
|
+
<h3 style="margin-top:24px;font-size:15px;text-transform:uppercase;letter-spacing:.5px;opacity:.6">Admin Entry
|
|
1024
|
+
CRUD</h3>
|
|
1025
|
+
|
|
1026
|
+
<h3><span class="method-badge method-get">GET</span><span
|
|
1027
|
+
class="endpoint-path">/api/collections/:slug/entries</span></h3>
|
|
1028
|
+
<p class="auth-note">Requires Bearer token + <code>collections</code> permission.</p>
|
|
1029
|
+
<p>List entries with pagination, sorting, and full-text search.</p>
|
|
1030
|
+
<table class="table table-sm">
|
|
1031
|
+
<thead>
|
|
1032
|
+
<tr>
|
|
1033
|
+
<th>Query param</th>
|
|
1034
|
+
<th>Default</th>
|
|
1035
|
+
<th>Description</th>
|
|
1036
|
+
</tr>
|
|
1037
|
+
</thead>
|
|
1038
|
+
<tbody>
|
|
1039
|
+
<tr>
|
|
1040
|
+
<td><code>page</code></td>
|
|
1041
|
+
<td>1</td>
|
|
1042
|
+
<td>Page number</td>
|
|
1043
|
+
</tr>
|
|
1044
|
+
<tr>
|
|
1045
|
+
<td><code>limit</code></td>
|
|
1046
|
+
<td>50</td>
|
|
1047
|
+
<td>Entries per page</td>
|
|
1048
|
+
</tr>
|
|
1049
|
+
<tr>
|
|
1050
|
+
<td><code>sort</code></td>
|
|
1051
|
+
<td>createdAt</td>
|
|
1052
|
+
<td>Field to sort by</td>
|
|
1053
|
+
</tr>
|
|
1054
|
+
<tr>
|
|
1055
|
+
<td><code>order</code></td>
|
|
1056
|
+
<td>desc</td>
|
|
1057
|
+
<td><code>asc</code> or <code>desc</code></td>
|
|
1058
|
+
</tr>
|
|
1059
|
+
<tr>
|
|
1060
|
+
<td><code>search</code></td>
|
|
1061
|
+
<td>—</td>
|
|
1062
|
+
<td>Full-text search query</td>
|
|
1063
|
+
</tr>
|
|
1064
|
+
</tbody>
|
|
1065
|
+
</table>
|
|
1066
|
+
<pre class="code-block"><code>// Response 200
|
|
1067
|
+
{
|
|
1068
|
+
"entries": [ { "id": "uuid", "data": { ... }, "createdAt": "...", "updatedAt": "..." } ],
|
|
1069
|
+
"total": 42,
|
|
1070
|
+
"page": 1,
|
|
1071
|
+
"limit": 50
|
|
1072
|
+
}</code></pre>
|
|
1073
|
+
|
|
1074
|
+
<h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/collections/:slug/entries/:id</span>
|
|
1075
|
+
</h3>
|
|
1076
|
+
<p class="auth-note">Requires Bearer token + <code>collections</code> permission.</p>
|
|
1077
|
+
<p>Return a single entry by ID.</p>
|
|
1078
|
+
<pre class="code-block"><code>// Response 200
|
|
1079
|
+
{ "id": "uuid", "data": { "title": "Hello World", "body": "..." }, "createdAt": "...", "updatedAt": "..." }
|
|
1080
|
+
|
|
1081
|
+
// Error 404
|
|
1082
|
+
{ "error": "Entry not found" }</code></pre>
|
|
1083
|
+
|
|
1084
|
+
<h3><span class="method-badge method-post">POST</span><span
|
|
1085
|
+
class="endpoint-path">/api/collections/:slug/entries</span></h3>
|
|
1086
|
+
<p class="auth-note">Requires Bearer token + <code>collections</code> permission.</p>
|
|
1087
|
+
<p>Create a new entry. Data is validated against the collection schema.</p>
|
|
1088
|
+
<table class="table table-sm">
|
|
1089
|
+
<thead>
|
|
1090
|
+
<tr>
|
|
1091
|
+
<th>Field</th>
|
|
1092
|
+
<th>Type</th>
|
|
1093
|
+
<th>Description</th>
|
|
1094
|
+
</tr>
|
|
1095
|
+
</thead>
|
|
1096
|
+
<tbody>
|
|
1097
|
+
<tr>
|
|
1098
|
+
<td><code>data</code></td>
|
|
1099
|
+
<td>object</td>
|
|
1100
|
+
<td>Entry field values keyed by field name</td>
|
|
1101
|
+
</tr>
|
|
1102
|
+
</tbody>
|
|
1103
|
+
</table>
|
|
1104
|
+
<pre class="code-block"><code>// Response 201 — returns the created entry</code></pre>
|
|
1105
|
+
|
|
1106
|
+
<h3><span class="method-badge method-put">PUT</span><span class="endpoint-path">/api/collections/:slug/entries/:id</span>
|
|
1107
|
+
</h3>
|
|
1108
|
+
<p class="auth-note">Requires Bearer token + <code>collections</code> permission.</p>
|
|
1109
|
+
<p>Update an entry. Data is validated against the schema.</p>
|
|
1110
|
+
<pre class="code-block"><code>// Response 200 — returns the updated entry
|
|
1111
|
+
|
|
1112
|
+
// Error 404
|
|
1113
|
+
{ "error": "Entry not found" }</code></pre>
|
|
1114
|
+
|
|
1115
|
+
<h3><span class="method-badge method-delete">DELETE</span><span class="endpoint-path">/api/collections/:slug/entries/:id</span>
|
|
1116
|
+
</h3>
|
|
1117
|
+
<p class="auth-note">Requires Bearer token + <code>collections</code> permission.</p>
|
|
1118
|
+
<p>Delete a single entry. Deleting the root admin role from <code>roles</code> is blocked.</p>
|
|
1119
|
+
<pre class="code-block"><code>// Response 200
|
|
1120
|
+
{ "success": true }</code></pre>
|
|
1121
|
+
|
|
1122
|
+
<h3><span class="method-badge method-delete">DELETE</span><span class="endpoint-path">/api/collections/:slug/entries</span>
|
|
1123
|
+
</h3>
|
|
1124
|
+
<p class="auth-note">Requires Bearer token + <code>collections</code> permission.</p>
|
|
1125
|
+
<p>Clear all entries from a collection. Irreversible.</p>
|
|
1126
|
+
<pre class="code-block"><code>// Response 200
|
|
1127
|
+
{ "success": true }</code></pre>
|
|
1128
|
+
|
|
1129
|
+
<h3 style="margin-top:24px;font-size:15px;text-transform:uppercase;letter-spacing:.5px;opacity:.6">Export &
|
|
1130
|
+
Import</h3>
|
|
1131
|
+
|
|
1132
|
+
<h3><span class="method-badge method-get">GET</span><span
|
|
1133
|
+
class="endpoint-path">/api/collections/:slug/export</span></h3>
|
|
1134
|
+
<p class="auth-note">Requires Bearer token + <code>collections</code> permission.</p>
|
|
1135
|
+
<p>Download all entries as a file attachment.</p>
|
|
1136
|
+
<table class="table table-sm">
|
|
1137
|
+
<thead>
|
|
1138
|
+
<tr>
|
|
1139
|
+
<th>Query param</th>
|
|
1140
|
+
<th>Values</th>
|
|
1141
|
+
<th>Description</th>
|
|
1142
|
+
</tr>
|
|
1143
|
+
</thead>
|
|
1144
|
+
<tbody>
|
|
1145
|
+
<tr>
|
|
1146
|
+
<td><code>format</code></td>
|
|
1147
|
+
<td><code>json</code> (default), <code>csv</code></td>
|
|
1148
|
+
<td>Export format</td>
|
|
1149
|
+
</tr>
|
|
1150
|
+
</tbody>
|
|
1151
|
+
</table>
|
|
1152
|
+
<pre class="code-block"><code>// Response 200 — file download
|
|
1153
|
+
// Content-Disposition: attachment; filename="blog-entries.json"</code></pre>
|
|
1154
|
+
|
|
1155
|
+
<h3><span class="method-badge method-post">POST</span><span
|
|
1156
|
+
class="endpoint-path">/api/collections/:slug/import</span></h3>
|
|
1157
|
+
<p class="auth-note">Requires Bearer token + <code>collections</code> permission.</p>
|
|
1158
|
+
<p>Bulk-import entries from a JSON array. Existing entries are not removed.</p>
|
|
1159
|
+
<table class="table table-sm">
|
|
1160
|
+
<thead>
|
|
1161
|
+
<tr>
|
|
1162
|
+
<th>Field</th>
|
|
1163
|
+
<th>Type</th>
|
|
1164
|
+
<th>Description</th>
|
|
1165
|
+
</tr>
|
|
1166
|
+
</thead>
|
|
1167
|
+
<tbody>
|
|
1168
|
+
<tr>
|
|
1169
|
+
<td><code>entries</code></td>
|
|
1170
|
+
<td>array</td>
|
|
1171
|
+
<td>Array of entry objects with a <code>data</code> field each</td>
|
|
1172
|
+
</tr>
|
|
1173
|
+
</tbody>
|
|
1174
|
+
</table>
|
|
1175
|
+
<pre class="code-block"><code>// Request body
|
|
1176
|
+
{ "entries": [ { "data": { "title": "Post 1" } }, { "data": { "title": "Post 2" } } ] }
|
|
1177
|
+
|
|
1178
|
+
// Response 201
|
|
1179
|
+
{ "imported": 2, "skipped": 0 }</code></pre>
|
|
1180
|
+
|
|
1181
|
+
<h3 style="margin-top:24px;font-size:15px;text-transform:uppercase;letter-spacing:.5px;opacity:.6">Public
|
|
1182
|
+
Access</h3>
|
|
1183
|
+
|
|
1184
|
+
<p>Public endpoints respect the per-collection <code>api</code> config. Each operation (<code>read</code>,
|
|
1185
|
+
<code>create</code>, <code>update</code>, <code>delete</code>) can be <strong>disabled</strong>, <strong>public</strong>
|
|
1186
|
+
(no auth), or restricted to a minimum role level.</p>
|
|
1187
|
+
|
|
1188
|
+
<h3><span class="method-badge method-get">GET</span><span
|
|
1189
|
+
class="endpoint-path">/api/collections/:slug/public</span></h3>
|
|
1190
|
+
<p class="auth-note">Access level: per collection <code>api.read</code> config.</p>
|
|
1191
|
+
<p>List entries publicly. Supports the same pagination and search query params as the admin endpoint.</p>
|
|
1192
|
+
|
|
1193
|
+
<h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/collections/:slug/public/:id</span>
|
|
1194
|
+
</h3>
|
|
1195
|
+
<p class="auth-note">Access level: per collection <code>api.read</code> config.</p>
|
|
1196
|
+
<p>Return a single entry publicly by ID.</p>
|
|
1197
|
+
|
|
1198
|
+
<h3><span class="method-badge method-post">POST</span><span
|
|
1199
|
+
class="endpoint-path">/api/collections/:slug/public</span></h3>
|
|
1200
|
+
<p class="auth-note">Access level: per collection <code>api.create</code> config.</p>
|
|
1201
|
+
<p>Create an entry publicly (e.g. form submissions). Entry is tagged with <code>source: "api"</code>.</p>
|
|
1202
|
+
|
|
1203
|
+
<h3><span class="method-badge method-put">PUT</span><span class="endpoint-path">/api/collections/:slug/public/:id</span>
|
|
1204
|
+
</h3>
|
|
1205
|
+
<p class="auth-note">Access level: per collection <code>api.update</code> config.</p>
|
|
1206
|
+
<p>Update an entry publicly.</p>
|
|
1207
|
+
|
|
1208
|
+
<h3><span class="method-badge method-delete">DELETE</span><span class="endpoint-path">/api/collections/:slug/public/:id</span>
|
|
1209
|
+
</h3>
|
|
1210
|
+
<p class="auth-note">Access level: per collection <code>api.delete</code> config.</p>
|
|
1211
|
+
<p>Delete an entry publicly.</p>
|
|
1212
|
+
|
|
1213
|
+
</div>
|
|
1214
|
+
</div>
|
|
1215
|
+
|
|
1216
|
+
<!-- Views API -->
|
|
1217
|
+
<div class="card card-collapsible mb-4">
|
|
1218
|
+
<div class="card-header" role="button" tabindex="0">
|
|
1219
|
+
<div class="card-header-content">
|
|
1220
|
+
<h2><span data-icon="eye"></span> Views API
|
|
1221
|
+
<span class="badge badge-warning" style="font-size:.7rem;margin-left:.4rem;">Pro</span>
|
|
1222
|
+
</h2>
|
|
1223
|
+
</div>
|
|
1224
|
+
<span class="card-collapse-icon" data-icon="chevron-down"></span>
|
|
1225
|
+
</div>
|
|
1226
|
+
<div class="card-body docs-body">
|
|
1227
|
+
<p>Views require a MongoDB connection. All admin endpoints require authentication and the
|
|
1228
|
+
<code>views</code> permission. View configs are stored in the <code>cms__views</code> MongoDB collection
|
|
1229
|
+
on the <code>default</code> connection.</p>
|
|
1230
|
+
|
|
1231
|
+
<h3 style="margin-top:24px;font-size:15px;text-transform:uppercase;letter-spacing:.5px;opacity:.6">
|
|
1232
|
+
Admin Endpoints</h3>
|
|
1233
|
+
|
|
1234
|
+
<h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/views</span></h3>
|
|
1235
|
+
<p class="auth-note">Requires: <code>views</code> permission</p>
|
|
1236
|
+
<p>List all view configs, sorted by creation date descending.</p>
|
|
1237
|
+
|
|
1238
|
+
<h3><span class="method-badge method-post">POST</span><span class="endpoint-path">/api/views</span></h3>
|
|
1239
|
+
<p class="auth-note">Requires: <code>views</code> permission</p>
|
|
1240
|
+
<p>Create a new view config. Returns <code>201</code> on success.</p>
|
|
1241
|
+
<pre class="code-block"><code>{
|
|
1242
|
+
"title": "Active Premium Users",
|
|
1243
|
+
"slug": "active-premium-users", // optional — auto-derived from title
|
|
1244
|
+
"description": "...",
|
|
1245
|
+
"connection": "default",
|
|
1246
|
+
"pipeline": {
|
|
1247
|
+
"source": "users", // CMS collection slug
|
|
1248
|
+
"stages": [
|
|
1249
|
+
{ "type": "$match", "config": { "data.status": "active" } },
|
|
1250
|
+
{ "type": "$sort", "config": { "meta.createdAt": -1 } },
|
|
1251
|
+
{ "type": "$project", "config": { "data.name": 1, "data.email": 1 } }
|
|
1252
|
+
]
|
|
1253
|
+
},
|
|
1254
|
+
"display": {
|
|
1255
|
+
"mode": "table", // "table" | "list"
|
|
1256
|
+
"columns": [
|
|
1257
|
+
{ "key": "data.name", "label": "Name" },
|
|
1258
|
+
{ "key": "data.email", "label": "Email" }
|
|
1259
|
+
],
|
|
1260
|
+
"pageSize": 25
|
|
1261
|
+
},
|
|
1262
|
+
"access": {
|
|
1263
|
+
"roles": ["admin", "manager"],
|
|
1264
|
+
"public": false
|
|
1265
|
+
}
|
|
1266
|
+
}</code></pre>
|
|
1267
|
+
|
|
1268
|
+
<h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/views/:slug</span></h3>
|
|
1269
|
+
<p class="auth-note">Requires: <code>views</code> permission</p>
|
|
1270
|
+
<p>Return a single view config by slug.</p>
|
|
1271
|
+
|
|
1272
|
+
<h3><span class="method-badge method-put">PUT</span><span class="endpoint-path">/api/views/:slug</span></h3>
|
|
1273
|
+
<p class="auth-note">Requires: <code>views</code> permission</p>
|
|
1274
|
+
<p>Update a view config. Accepts the same body shape as POST; all fields are optional.</p>
|
|
1275
|
+
|
|
1276
|
+
<h3><span class="method-badge method-delete">DELETE</span><span class="endpoint-path">/api/views/:slug</span></h3>
|
|
1277
|
+
<p class="auth-note">Requires: <code>views</code> permission</p>
|
|
1278
|
+
<p>Delete a view config.</p>
|
|
1279
|
+
|
|
1280
|
+
<h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/views/:slug/execute</span></h3>
|
|
1281
|
+
<p class="auth-note">Requires: <code>views</code> permission</p>
|
|
1282
|
+
<p>Execute the view's aggregation pipeline and return paginated results.</p>
|
|
1283
|
+
<p><strong>Query params:</strong> <code>page</code> (default 1), <code>limit</code> (default 25).</p>
|
|
1284
|
+
<pre class="code-block"><code>// Response
|
|
1285
|
+
{
|
|
1286
|
+
"results": [ { "data": { "name": "Alice" }, ... }, ... ],
|
|
1287
|
+
"total": 142,
|
|
1288
|
+
"page": 1,
|
|
1289
|
+
"limit": 25
|
|
1290
|
+
}</code></pre>
|
|
1291
|
+
|
|
1292
|
+
<h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/views/collection/:slug</span></h3>
|
|
1293
|
+
<p class="auth-note">Requires: <code>views</code> permission</p>
|
|
1294
|
+
<p>List all view configs whose <code>pipeline.source</code> matches the given collection slug.</p>
|
|
1295
|
+
|
|
1296
|
+
<h3 style="margin-top:24px;font-size:15px;text-transform:uppercase;letter-spacing:.5px;opacity:.6">
|
|
1297
|
+
Public Endpoint</h3>
|
|
1298
|
+
|
|
1299
|
+
<h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/views/:slug/public</span></h3>
|
|
1300
|
+
<p class="auth-note">Access level: per view <code>access</code> config</p>
|
|
1301
|
+
<p>Execute the view publicly. If <code>access.public</code> is <code>false</code>, a valid JWT and
|
|
1302
|
+
a role listed in <code>access.roles</code> is required. Same query params and response shape as
|
|
1303
|
+
<code>/execute</code>.</p>
|
|
1304
|
+
|
|
1305
|
+
</div>
|
|
1306
|
+
</div>
|
|
1307
|
+
|
|
1308
|
+
<!-- Actions API -->
|
|
1309
|
+
<div class="card card-collapsible mb-4">
|
|
1310
|
+
<div class="card-header" role="button" tabindex="0">
|
|
1311
|
+
<div class="card-header-content">
|
|
1312
|
+
<h2><span data-icon="zap"></span> Actions API
|
|
1313
|
+
<span class="badge badge-warning" style="font-size:.7rem;margin-left:.4rem;">Pro</span>
|
|
1314
|
+
</h2>
|
|
1315
|
+
</div>
|
|
1316
|
+
<span class="card-collapse-icon" data-icon="chevron-down"></span>
|
|
1317
|
+
</div>
|
|
1318
|
+
<div class="card-body docs-body">
|
|
1319
|
+
<p>Actions require a MongoDB connection. All admin endpoints require authentication and the
|
|
1320
|
+
<code>actions</code> permission. Action configs are stored in <code>cms__actions</code>.</p>
|
|
1321
|
+
|
|
1322
|
+
<h3 style="margin-top:24px;font-size:15px;text-transform:uppercase;letter-spacing:.5px;opacity:.6">
|
|
1323
|
+
Admin Endpoints</h3>
|
|
1324
|
+
|
|
1325
|
+
<h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/actions</span></h3>
|
|
1326
|
+
<p class="auth-note">Requires: <code>actions</code> permission</p>
|
|
1327
|
+
<p>List all action configs.</p>
|
|
1328
|
+
|
|
1329
|
+
<h3><span class="method-badge method-post">POST</span><span class="endpoint-path">/api/actions</span></h3>
|
|
1330
|
+
<p class="auth-note">Requires: <code>actions</code> permission</p>
|
|
1331
|
+
<p>Create a new action config. Returns <code>201</code> on success.</p>
|
|
1332
|
+
<pre class="code-block"><code>{
|
|
1333
|
+
"title": "Approve Application",
|
|
1334
|
+
"slug": "approve-application", // optional
|
|
1335
|
+
"description": "...",
|
|
1336
|
+
"collection": "applications",
|
|
1337
|
+
"trigger": {
|
|
1338
|
+
"type": "manual",
|
|
1339
|
+
"label": "Approve",
|
|
1340
|
+
"icon": "check-circle",
|
|
1341
|
+
"confirmMessage": "Approve this application?" // null to skip confirmation
|
|
1342
|
+
},
|
|
1343
|
+
"steps": [
|
|
1344
|
+
{ "type": "updateField", "config": { "field": "status", "value": "approved" } },
|
|
1345
|
+
{ "type": "updateField", "config": { "field": "approvedAt", "value": "{{now}}" } },
|
|
1346
|
+
{ "type": "webhook", "config": { "url": "https://hooks.example.com/approved", "method": "POST",
|
|
1347
|
+
"body": { "email": "{{entry.data.email}}" } } },
|
|
1348
|
+
{ "type": "email", "config": { "to": "{{entry.data.email}}",
|
|
1349
|
+
"subject": "Application approved",
|
|
1350
|
+
"template": "Hi {{entry.data.name}}, your application is approved." } }
|
|
1351
|
+
],
|
|
1352
|
+
"access": { "roles": ["admin", "manager"] }
|
|
1353
|
+
}</code></pre>
|
|
1354
|
+
|
|
1355
|
+
<h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/actions/:slug</span></h3>
|
|
1356
|
+
<p class="auth-note">Requires: <code>actions</code> permission</p>
|
|
1357
|
+
<p>Return a single action config by slug.</p>
|
|
1358
|
+
|
|
1359
|
+
<h3><span class="method-badge method-put">PUT</span><span class="endpoint-path">/api/actions/:slug</span></h3>
|
|
1360
|
+
<p class="auth-note">Requires: <code>actions</code> permission</p>
|
|
1361
|
+
<p>Update an action config.</p>
|
|
1362
|
+
|
|
1363
|
+
<h3><span class="method-badge method-delete">DELETE</span><span class="endpoint-path">/api/actions/:slug</span></h3>
|
|
1364
|
+
<p class="auth-note">Requires: <code>actions</code> permission</p>
|
|
1365
|
+
<p>Delete an action config.</p>
|
|
1366
|
+
|
|
1367
|
+
<h3><span class="method-badge method-post">POST</span><span class="endpoint-path">/api/actions/:slug/execute</span></h3>
|
|
1368
|
+
<p class="auth-note">Requires: <code>actions</code> permission</p>
|
|
1369
|
+
<p>Execute an action against a specific entry.</p>
|
|
1370
|
+
<pre class="code-block"><code>// Request body
|
|
1371
|
+
{ "entryId": "uuid-of-the-entry" }
|
|
1372
|
+
|
|
1373
|
+
// Response
|
|
1374
|
+
{
|
|
1375
|
+
"success": true,
|
|
1376
|
+
"stepsCompleted": 4,
|
|
1377
|
+
"results": [
|
|
1378
|
+
{ "type": "updateField", "success": true, "result": { "field": "status", "value": "approved" } },
|
|
1379
|
+
{ "type": "email", "success": true, "result": { "to": "user@example.com" } }
|
|
1380
|
+
]
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
// Partial failure response
|
|
1384
|
+
{
|
|
1385
|
+
"success": false,
|
|
1386
|
+
"stepsCompleted": 2,
|
|
1387
|
+
"results": [
|
|
1388
|
+
{ "type": "updateField", "success": true, "result": { "field": "status", "value": "approved" } },
|
|
1389
|
+
{ "type": "webhook", "success": false, "error": "Webhook returned HTTP 500" }
|
|
1390
|
+
]
|
|
1391
|
+
}</code></pre>
|
|
1392
|
+
|
|
1393
|
+
<h3><span class="method-badge method-get">GET</span><span class="endpoint-path">/api/actions/collection/:slug</span></h3>
|
|
1394
|
+
<p class="auth-note">Requires: <code>actions</code> permission</p>
|
|
1395
|
+
<p>List all action configs targeting a given collection slug. Used by the entry list view to
|
|
1396
|
+
populate per-row trigger buttons.</p>
|
|
1397
|
+
|
|
1398
|
+
<h3 style="margin-top:24px;font-size:15px;text-transform:uppercase;letter-spacing:.5px;opacity:.6">
|
|
1399
|
+
Public Endpoint</h3>
|
|
1400
|
+
|
|
1401
|
+
<h3><span class="method-badge method-post">POST</span><span class="endpoint-path">/api/actions/:slug/public</span></h3>
|
|
1402
|
+
<p class="auth-note">Requires: JWT + role in <code>access.roles</code></p>
|
|
1403
|
+
<p>Execute an action publicly. Always requires a valid JWT — the role is checked against
|
|
1404
|
+
<code>access.roles</code>. Request body and response shape are identical to the admin
|
|
1405
|
+
<code>/execute</code> endpoint.</p>
|
|
1406
|
+
|
|
1407
|
+
</div>
|
|
1408
|
+
</div>
|
|
1409
|
+
|
|
1410
|
+
</div>
|
|
1411
|
+
</div>
|