includio-cms 0.7.1 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +117 -0
- package/ROADMAP.md +44 -2
- package/dist/admin/api/generate-styles.d.ts +2 -0
- package/dist/admin/api/generate-styles.js +32 -0
- package/dist/admin/api/handler.js +33 -0
- package/dist/admin/api/media-gc.js +10 -4
- package/dist/admin/api/rest/handler.js +17 -0
- package/dist/admin/api/rest/routes/collections.js +25 -13
- package/dist/admin/api/rest/routes/entries.d.ts +1 -1
- package/dist/admin/api/rest/routes/entries.js +10 -10
- package/dist/admin/api/rest/routes/media.d.ts +2 -0
- package/dist/admin/api/rest/routes/media.js +9 -0
- package/dist/admin/api/rest/routes/schema.d.ts +5 -0
- package/dist/admin/api/rest/routes/schema.js +152 -0
- package/dist/admin/api/rest/routes/singletons.d.ts +1 -1
- package/dist/admin/api/rest/routes/singletons.js +8 -7
- package/dist/admin/api/rest/routes/upload.d.ts +2 -0
- package/dist/admin/api/rest/routes/upload.js +28 -0
- package/dist/admin/api/upload.js +13 -0
- package/dist/admin/client/collection/collection-entries.svelte +19 -6
- package/dist/admin/client/entry/entry.svelte +21 -23
- package/dist/admin/client/entry/header/a11y-validator.js +2 -2
- package/dist/admin/client/entry/header/publish-panel.svelte +33 -85
- package/dist/admin/client/entry/header/status-badge.svelte +2 -2
- package/dist/admin/client/entry/header/version-history-sheet.svelte +9 -9
- package/dist/admin/client/entry/header/visibility.svelte +16 -10
- package/dist/admin/client/entry/utils.d.ts +3 -0
- package/dist/admin/client/entry/utils.js +22 -4
- package/dist/admin/client/form/form-submission/form-submission-page.svelte +4 -1
- package/dist/admin/client/form/form-submission/submission-field.svelte +10 -0
- package/dist/admin/client/index.d.ts +1 -0
- package/dist/admin/client/index.js +1 -0
- package/dist/admin/client/maintenance/maintenance-page.svelte +146 -2
- package/dist/admin/components/fields/blocks-field.svelte +9 -10
- package/dist/admin/components/fields/field-renderer.svelte +4 -8
- package/dist/admin/components/fields/object-field.svelte +7 -12
- package/dist/admin/components/fields/select-field.svelte +8 -2
- package/dist/admin/components/fields/seo-field.svelte +40 -93
- package/dist/admin/components/fields/simple-array-field.svelte +5 -5
- package/dist/admin/components/fields/text-field-wrapper.svelte +52 -197
- package/dist/admin/components/fields/text-field-wrapper.svelte.d.ts +2 -2
- package/dist/admin/components/fields/url-field-wrapper.svelte +15 -25
- package/dist/admin/components/fields/url-field.svelte +61 -72
- package/dist/admin/components/media/file-upload.svelte +5 -1
- package/dist/admin/components/media/file-upload.svelte.d.ts +1 -0
- package/dist/admin/components/media/media-library.svelte +109 -37
- package/dist/admin/components/media/media-selector.svelte +79 -11
- package/dist/admin/components/media/tag-sidebar.svelte +10 -6
- package/dist/admin/components/media/tag-sidebar.svelte.d.ts +7 -2
- package/dist/admin/components/tiptap/InlineBlockNodeView.svelte +21 -93
- package/dist/admin/components/tiptap/inline-block-node.js +6 -5
- package/dist/admin/components/tiptap/link-dialog.svelte +10 -11
- package/dist/admin/components/tiptap/slash-command.js +1 -1
- package/dist/admin/remote/entry.remote.d.ts +2 -5
- package/dist/admin/remote/entry.remote.js +22 -27
- package/dist/admin/remote/media.remote.d.ts +15 -0
- package/dist/admin/remote/media.remote.js +18 -2
- package/dist/admin/remote/preview.remote.js +3 -1
- package/dist/admin/utils/entryLabel.js +9 -6
- package/dist/admin/utils/translationStatus.js +1 -2
- package/dist/cli/create-user.d.ts +2 -0
- package/dist/cli/create-user.js +81 -0
- package/dist/cli/index.js +5 -0
- package/dist/cli/scaffold/admin.js +34 -2
- package/dist/cms/runtime/api.d.ts +16 -12
- package/dist/cms/runtime/api.js +7 -6
- package/dist/cms/runtime/remote.js +2 -2
- package/dist/cms/runtime/schemas.d.ts +1 -1
- package/dist/cms/runtime/schemas.js +1 -1
- package/dist/cms/runtime/types.d.ts +118 -112
- package/dist/cms/runtime/types.js +0 -12
- package/dist/core/cms.d.ts +3 -1
- package/dist/core/cms.js +30 -0
- package/dist/core/fields/fieldSchemaToTs.js +9 -15
- package/dist/core/fields/formFieldSchemaToTs.js +7 -0
- package/dist/core/server/entries/operations/create.js +10 -4
- package/dist/core/server/entries/operations/get.d.ts +1 -0
- package/dist/core/server/entries/operations/get.js +186 -191
- package/dist/core/server/entries/operations/update.d.ts +6 -7
- package/dist/core/server/entries/operations/update.js +20 -38
- package/dist/core/server/fields/populateEntry.js +16 -52
- package/dist/core/server/fields/resolveImageFields.js +69 -120
- package/dist/core/server/fields/resolveRelationFields.js +30 -51
- package/dist/core/server/fields/resolveRichtextLinks.js +46 -100
- package/dist/core/server/fields/resolveTypographyOrphans.bench.d.ts +1 -0
- package/dist/core/server/fields/resolveTypographyOrphans.bench.js +87 -0
- package/dist/core/server/fields/resolveTypographyOrphans.d.ts +3 -0
- package/dist/core/server/fields/resolveTypographyOrphans.js +128 -0
- package/dist/core/server/fields/resolveUrlFields.js +47 -56
- package/dist/core/server/fields/utils/fixOrphans.d.ts +5 -0
- package/dist/core/server/fields/utils/fixOrphans.js +12 -0
- package/dist/core/server/fields/utils/imageStyles.d.ts +4 -2
- package/dist/core/server/fields/utils/imageStyles.js +41 -25
- package/dist/core/server/fields/utils/resolveMedia.js +1 -6
- package/dist/core/server/forms/submissions/operations/delete.js +26 -2
- package/dist/core/server/forms/submissions/utils/parseMultipart.d.ts +2 -0
- package/dist/core/server/forms/submissions/utils/parseMultipart.js +75 -0
- package/dist/core/server/generator/fields.d.ts +6 -0
- package/dist/core/server/generator/fields.js +43 -5
- package/dist/core/server/generator/formFieldSchemaToString.js +10 -0
- package/dist/core/server/generator/formFields.js +1 -0
- package/dist/core/server/generator/generator.js +98 -30
- package/dist/core/server/media/operations/getFiles.d.ts +5 -0
- package/dist/core/server/media/operations/getFiles.js +6 -0
- package/dist/core/server/media/operations/uploadPrivateFile.d.ts +4 -0
- package/dist/core/server/media/operations/uploadPrivateFile.js +8 -0
- package/dist/core/server/media/styles/operations/batchGenerateStyles.d.ts +16 -0
- package/dist/core/server/media/styles/operations/batchGenerateStyles.js +144 -0
- package/dist/db-postgres/index.js +303 -37
- package/dist/db-postgres/schema/entry.d.ts +0 -94
- package/dist/db-postgres/schema/entry.js +0 -6
- package/dist/db-postgres/schema/entryVersion.d.ts +17 -0
- package/dist/db-postgres/schema/entryVersion.js +1 -0
- package/dist/entity/index.d.ts +9 -4
- package/dist/entity/index.js +24 -24
- package/dist/files-local/index.js +43 -0
- package/dist/sveltekit/components/preview.svelte +2 -326
- package/dist/sveltekit/components/preview.svelte.d.ts +5 -16
- package/dist/sveltekit/server/index.d.ts +2 -1
- package/dist/sveltekit/server/index.js +2 -1
- package/dist/sveltekit/server/preview.js +4 -7
- package/dist/types/adapters/db.d.ts +15 -1
- package/dist/types/adapters/files.d.ts +6 -0
- package/dist/types/cms.d.ts +5 -0
- package/dist/types/entries.d.ts +54 -18
- package/dist/types/fields.d.ts +14 -24
- package/dist/types/formFields.d.ts +7 -2
- package/dist/types/index.d.ts +2 -2
- package/dist/types/structured-content.d.ts +5 -0
- package/dist/updates/0.10.0/index.d.ts +2 -0
- package/dist/updates/0.10.0/index.js +15 -0
- package/dist/updates/0.11.0/index.d.ts +2 -0
- package/dist/updates/0.11.0/index.js +12 -0
- package/dist/updates/0.12.0/index.d.ts +2 -0
- package/dist/updates/0.12.0/index.js +12 -0
- package/dist/updates/0.13.0/index.d.ts +2 -0
- package/dist/updates/0.13.0/index.js +10 -0
- package/dist/updates/0.7.2/index.d.ts +2 -0
- package/dist/updates/0.7.2/index.js +10 -0
- package/dist/updates/0.7.3/index.d.ts +2 -0
- package/dist/updates/0.7.3/index.js +10 -0
- package/dist/updates/0.8.0/index.d.ts +2 -0
- package/dist/updates/0.8.0/index.js +18 -0
- package/dist/updates/0.8.0/migrate.d.ts +2 -0
- package/dist/updates/0.8.0/migrate.js +101 -0
- package/dist/updates/0.9.0/index.d.ts +2 -0
- package/dist/updates/0.9.0/index.js +38 -0
- package/dist/updates/index.js +9 -1
- package/package.json +7 -6
- package/dist/admin/components/fields/image-field.svelte +0 -198
- package/dist/admin/components/fields/image-field.svelte.d.ts +0 -8
- package/dist/admin/components/fields/richtext-field.svelte +0 -13
- package/dist/admin/components/fields/richtext-field.svelte.d.ts +0 -8
- package/dist/admin/components/tiptap.svelte +0 -11
- package/dist/admin/components/tiptap.svelte.d.ts +0 -6
- package/dist/core/server/entries/utils/getEntryTranslation.d.ts +0 -1
- package/dist/core/server/entries/utils/getEntryTranslation.js +0 -18
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,123 @@
|
|
|
3
3
|
All notable changes to includio-cms are documented here.
|
|
4
4
|
Generated from `src/lib/updates/` — do not edit manually.
|
|
5
5
|
|
|
6
|
+
## 0.13.0 — 2026-03-19
|
|
7
|
+
|
|
8
|
+
Private file uploads for form submissions
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Private file uploads — form submissions can upload files to a non-public directory with access control
|
|
12
|
+
|
|
13
|
+
## 0.12.0 — 2026-03-18
|
|
14
|
+
|
|
15
|
+
File field type, dataOrderBy, and _url population
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- File field type with multipart upload, file validation, and rate limiting
|
|
19
|
+
- `dataOrderBy` — sort entries by JSON data fields in queries
|
|
20
|
+
- Auto-populate `_url` field on entries from slug resolver and `pathTemplate`
|
|
21
|
+
|
|
22
|
+
## 0.11.0 — 2026-03-18
|
|
23
|
+
|
|
24
|
+
Codegen overhaul and REST API consolidation
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
- Codegen: flat entry types, inline block types, `countEntries` query helper, simplified API surface
|
|
28
|
+
- REST API: catch-all route handler, file upload endpoint, media endpoint, schema template endpoints
|
|
29
|
+
- Admin scaffold updated for new REST route structure
|
|
30
|
+
|
|
31
|
+
## 0.10.0 — 2026-03-18
|
|
32
|
+
|
|
33
|
+
Remove image, video, richtext field types — consolidate to media and content
|
|
34
|
+
|
|
35
|
+
### Breaking
|
|
36
|
+
- Removed `image` field type — use `media` with `accept: "image/*"` instead
|
|
37
|
+
- Removed `richtext` field type — use `content` (structured ProseMirror JSON) instead
|
|
38
|
+
- Removed `ImageField` and `RichtextField` TypeScript interfaces
|
|
39
|
+
- `image-field.svelte` and `richtext-field.svelte` components removed
|
|
40
|
+
- `tiptap.svelte` (HTML richtext wrapper) removed
|
|
41
|
+
|
|
42
|
+
### Notes
|
|
43
|
+
|
|
44
|
+
No automatic data migration. Replace `type: "image"` with `type: "media", accept: "image/*"` in your collection/singleton configs. Replace `type: "richtext"` with `type: "content"`. Existing stored data (media UUIDs, ProseMirror docs) remains compatible.
|
|
45
|
+
|
|
46
|
+
## 0.9.0 — 2026-03-17
|
|
47
|
+
|
|
48
|
+
Per-language entry versions — separate entry_version per language
|
|
49
|
+
|
|
50
|
+
### Added
|
|
51
|
+
- Per-language entry versions: each language gets its own entry_version with flat data
|
|
52
|
+
- Per-language publication: publish/unpublish each language independently
|
|
53
|
+
- Simplified data format: no more nested `{ pl: "X", en: "Y" }` — values are flat strings
|
|
54
|
+
- Language switching in admin loads different version data
|
|
55
|
+
|
|
56
|
+
### Breaking
|
|
57
|
+
- `entry_version` table: new `lang` column (NOT NULL)
|
|
58
|
+
- `entry` table: removed `published_at`, `published_version_id`, `published_by`, `available_locales` columns
|
|
59
|
+
- `RawEntry`: `publishedVersion`/`draftVersion`/`scheduledVersion` replaced with per-lang `publishedVersions`/`draftVersions`/`scheduledVersions` records
|
|
60
|
+
- `DbEntryVersion`/`DbEntryVersionInsert`: new required `lang` field
|
|
61
|
+
- `translateObject` removed — data is single-language per version
|
|
62
|
+
- Resolve functions (relation, media, url, richtext) expect flat content/richtext values, not `Record<lang, value>`
|
|
63
|
+
- `upsertDraftVersion` and `pruneOldDraftVersions` now require `lang` parameter
|
|
64
|
+
- `unpublishEntry` renamed to `unpublishEntryLang` with required `lang` parameter
|
|
65
|
+
|
|
66
|
+
### Migration
|
|
67
|
+
|
|
68
|
+
```sql
|
|
69
|
+
-- Step 1: Add lang column as nullable
|
|
70
|
+
ALTER TABLE entry_version ADD COLUMN lang TEXT;
|
|
71
|
+
|
|
72
|
+
-- Step 2: Run migration script to split multi-lang versions (scripts/migrate-lang-versions.ts)
|
|
73
|
+
-- After migration, set NOT NULL:
|
|
74
|
+
ALTER TABLE entry_version ALTER COLUMN lang SET NOT NULL;
|
|
75
|
+
|
|
76
|
+
-- Step 3: Create index for performance
|
|
77
|
+
CREATE INDEX idx_entry_version_publish ON entry_version(entry_id, lang, published_at);
|
|
78
|
+
|
|
79
|
+
-- Step 4: Remove entry-level publish columns
|
|
80
|
+
ALTER TABLE entry DROP COLUMN IF EXISTS published_at;
|
|
81
|
+
ALTER TABLE entry DROP COLUMN IF EXISTS published_version_id;
|
|
82
|
+
ALTER TABLE entry DROP COLUMN IF EXISTS published_by;
|
|
83
|
+
ALTER TABLE entry DROP COLUMN IF EXISTS available_locales;
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Notes
|
|
87
|
+
|
|
88
|
+
Run the migration script BEFORE setting lang to NOT NULL. See scripts/migrate-lang-versions.ts for data migration.
|
|
89
|
+
|
|
90
|
+
## 0.8.0 — 2026-03-17
|
|
91
|
+
|
|
92
|
+
Flat entry format — remove data wrapper from objects/blocks
|
|
93
|
+
|
|
94
|
+
### Added
|
|
95
|
+
- Flat entry format: objects use `_slug` + spread fields instead of `slug` + `data` wrapper
|
|
96
|
+
- Blocks use `_slug` instead of `slug` with flat fields
|
|
97
|
+
- Entry type unified: `FlatEntry` renamed to `Entry`, old `Entry` removed
|
|
98
|
+
|
|
99
|
+
### Breaking
|
|
100
|
+
- Object field data format: `{ slug, data: {...} }` → `{ _slug, ...fields }`
|
|
101
|
+
- Blocks field data format: `{ _id, slug, data: {...} }` → `{ _id, _slug, ...fields }`
|
|
102
|
+
- `FlatEntry` type removed — use `Entry` instead
|
|
103
|
+
- `flattenEntry`/`flattenFields` removed — data is flat from resolve functions
|
|
104
|
+
|
|
105
|
+
### Notes
|
|
106
|
+
|
|
107
|
+
Run `includio update` to migrate existing entry data in the database.
|
|
108
|
+
|
|
109
|
+
## 0.7.3 — 2026-03-17
|
|
110
|
+
|
|
111
|
+
Typography orphan fix for Polish conjunctions
|
|
112
|
+
|
|
113
|
+
### Added
|
|
114
|
+
- Read-time orphan fix — replace space after single-letter Polish conjunctions (i, w, z, o, u, a) with non-breaking space `\u00A0`
|
|
115
|
+
|
|
116
|
+
## 0.7.2 — 2026-03-16
|
|
117
|
+
|
|
118
|
+
CLI create-user command
|
|
119
|
+
|
|
120
|
+
### Added
|
|
121
|
+
- CLI `create-user` command — interactive user creation with email, password (auto-generate option), name, and role via better-auth
|
|
122
|
+
|
|
6
123
|
## 0.7.1 — 2026-03-13
|
|
7
124
|
|
|
8
125
|
Custom fields plugin system, pathTemplate, admin public API
|
package/ROADMAP.md
CHANGED
|
@@ -163,14 +163,56 @@
|
|
|
163
163
|
- [x] `[feature]` `[P2]` resolveMediaWithStyles utility & core svelte export
|
|
164
164
|
- [x] `[fix]` `[P1]` Schema serialization type cast (stricter TS compatibility)
|
|
165
165
|
|
|
166
|
-
## 0.
|
|
166
|
+
## 0.7.2 — CLI create-user
|
|
167
|
+
|
|
168
|
+
- [x] `[feature]` `[P1]` CLI `create-user` command — interactive user creation with role assignment <!-- files: src/lib/cli/create-user.ts, src/lib/cli/index.ts -->
|
|
169
|
+
|
|
170
|
+
## 0.7.3 — Typography
|
|
171
|
+
|
|
172
|
+
- [x] `[feature]` `[P1]` Read-time orphan fix — replace space after single-letter Polish conjunctions with `\u00A0` <!-- files: src/lib/core/server/fields/resolveTypographyOrphans.ts, src/lib/core/server/fields/utils/fixOrphans.ts -->
|
|
173
|
+
|
|
174
|
+
## 0.8.0 — Flat entry format
|
|
175
|
+
|
|
176
|
+
- [x] `[breaking]` `[P0]` Flat entry format: objects use `_slug` + spread fields instead of `slug` + `data` wrapper
|
|
177
|
+
- [x] `[breaking]` `[P0]` Blocks use `_slug` instead of `slug` with flat fields
|
|
178
|
+
- [x] `[breaking]` `[P0]` `FlatEntry` type removed — use `Entry` instead
|
|
179
|
+
|
|
180
|
+
## 0.9.0 — Per-language entry versions
|
|
181
|
+
|
|
182
|
+
- [x] `[breaking]` `[P0]` Per-language entry versions: each language gets its own `entry_version` with flat data
|
|
183
|
+
- [x] `[feature]` `[P0]` Per-language publication: publish/unpublish each language independently
|
|
184
|
+
- [x] `[breaking]` `[P0]` Simplified data format: no more nested `{ pl: "X", en: "Y" }` — values are flat strings
|
|
185
|
+
|
|
186
|
+
## 0.10.0 — Field type consolidation
|
|
187
|
+
|
|
188
|
+
- [x] `[breaking]` `[P1]` Remove `image` field type — use `media` with `accept: "image/*"`
|
|
189
|
+
- [x] `[breaking]` `[P1]` Remove `richtext` field type — use `content` (structured JSON)
|
|
190
|
+
- [x] `[chore]` `[P1]` Remove `ImageField`, `RichtextField` interfaces + components
|
|
191
|
+
|
|
192
|
+
## 0.11.0 — Codegen & REST consolidation
|
|
193
|
+
|
|
194
|
+
- [x] `[feature]` `[P1]` Codegen: flat types, inline block types, `countEntries`, simplified API
|
|
195
|
+
- [x] `[feature]` `[P1]` REST API: catch-all route, upload endpoint, media endpoint, schema templates
|
|
196
|
+
- [x] `[chore]` `[P1]` Admin scaffold updated for new REST route structure
|
|
197
|
+
|
|
198
|
+
## 0.12.0 — File field, dataOrderBy, _url
|
|
199
|
+
|
|
200
|
+
- [x] `[feature]` `[P1]` File field type with multipart upload, validation, rate limiting
|
|
201
|
+
- [x] `[feature]` `[P1]` `dataOrderBy` — sort entries by JSON data fields
|
|
202
|
+
- [x] `[feature]` `[P1]` Auto-populate `_url` field on entries from slug resolver
|
|
203
|
+
|
|
204
|
+
## 0.13.0 — Private file uploads
|
|
205
|
+
|
|
206
|
+
- [x] `[feature]` `[P1]` Private file uploads — form submissions can upload files to non-public directory
|
|
207
|
+
|
|
208
|
+
## 0.14.0 — SEO module
|
|
167
209
|
|
|
168
210
|
- [ ] `[feature]` `[P1]` SERP preview + character limits for title/description <!-- files: src/lib/admin/components/fields/seo-field.svelte -->
|
|
169
211
|
- [ ] `[feature]` `[P1]` Global SEO settings
|
|
170
212
|
- [ ] `[feature]` `[P1]` Dedicated frontend SEO components <!-- files: src/lib/sveltekit/components/seo.svelte -->
|
|
171
213
|
- [ ] `[feature]` `[P2]` Sitemap generation
|
|
172
214
|
|
|
173
|
-
## 0.
|
|
215
|
+
## 0.15.0 — WCAG/ATAG compliance
|
|
174
216
|
|
|
175
217
|
- [ ] `[chore]` `[P0]` Full WCAG/ATAG audit
|
|
176
218
|
- [ ] `[feature]` `[P0]` Accessibility rework based on audit findings
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { requireRole } from '../remote/middleware/auth.js';
|
|
2
|
+
import { batchGenerateAllStyles } from '../../core/server/media/styles/operations/batchGenerateStyles.js';
|
|
3
|
+
export const POST = async () => {
|
|
4
|
+
requireRole('admin');
|
|
5
|
+
const abort = new AbortController();
|
|
6
|
+
const encoder = new TextEncoder();
|
|
7
|
+
const stream = new ReadableStream({
|
|
8
|
+
async start(controller) {
|
|
9
|
+
try {
|
|
10
|
+
for await (const event of batchGenerateAllStyles(abort.signal)) {
|
|
11
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`));
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
catch (e) {
|
|
15
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'error', error: e instanceof Error ? e.message : String(e) })}\n\n`));
|
|
16
|
+
}
|
|
17
|
+
finally {
|
|
18
|
+
controller.close();
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
cancel() {
|
|
22
|
+
abort.abort();
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
return new Response(stream, {
|
|
26
|
+
headers: {
|
|
27
|
+
'Content-Type': 'text/event-stream',
|
|
28
|
+
'Cache-Control': 'no-cache',
|
|
29
|
+
Connection: 'keep-alive'
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
};
|
|
@@ -5,6 +5,10 @@ import * as replaceHandlers from './replace.js';
|
|
|
5
5
|
import * as inviteHandlers from './invite.js';
|
|
6
6
|
import * as acceptInviteHandlers from './accept-invite.js';
|
|
7
7
|
import * as mediaGcHandlers from './media-gc.js';
|
|
8
|
+
import * as generateStylesHandlers from './generate-styles.js';
|
|
9
|
+
import { requireAuth } from '../remote/middleware/auth.js';
|
|
10
|
+
import { getCMS } from '../../core/cms.js';
|
|
11
|
+
import { lookup } from 'mrmime';
|
|
8
12
|
export function createAdminApiHandler(options) {
|
|
9
13
|
const routes = {
|
|
10
14
|
upload: uploadHandlers,
|
|
@@ -13,14 +17,43 @@ export function createAdminApiHandler(options) {
|
|
|
13
17
|
invite: inviteHandlers,
|
|
14
18
|
'accept-invite': acceptInviteHandlers,
|
|
15
19
|
'media-gc': mediaGcHandlers,
|
|
20
|
+
'generate-styles': generateStylesHandlers,
|
|
16
21
|
...options?.extraRoutes
|
|
17
22
|
};
|
|
23
|
+
const privateMediaGet = async (event) => {
|
|
24
|
+
requireAuth();
|
|
25
|
+
const path = event.params.path;
|
|
26
|
+
const filename = path.replace('media/private/', '');
|
|
27
|
+
if (!filename || filename.includes('/') || filename.includes('..')) {
|
|
28
|
+
return json({ error: 'Invalid filename' }, { status: 400 });
|
|
29
|
+
}
|
|
30
|
+
const adapter = getCMS().filesAdapter;
|
|
31
|
+
if (!adapter.downloadPrivateFile) {
|
|
32
|
+
return json({ error: 'Not supported' }, { status: 501 });
|
|
33
|
+
}
|
|
34
|
+
const file = await adapter.downloadPrivateFile(filename);
|
|
35
|
+
if (!file) {
|
|
36
|
+
return json({ error: 'Not found' }, { status: 404 });
|
|
37
|
+
}
|
|
38
|
+
const contentType = lookup(filename) || 'application/octet-stream';
|
|
39
|
+
return new Response(await file.arrayBuffer(), {
|
|
40
|
+
headers: {
|
|
41
|
+
'Content-Type': contentType,
|
|
42
|
+
'Content-Disposition': `inline; filename="${filename}"`,
|
|
43
|
+
'Cache-Control': 'private, no-store'
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
};
|
|
18
47
|
function handle(method) {
|
|
19
48
|
return (event) => {
|
|
20
49
|
const path = event.params.path;
|
|
21
50
|
if (!path) {
|
|
22
51
|
return json({ error: 'Not found' }, { status: 404 });
|
|
23
52
|
}
|
|
53
|
+
// Handle media/private/[filename] route
|
|
54
|
+
if (path.startsWith('media/private/') && method === 'GET') {
|
|
55
|
+
return privateMediaGet(event);
|
|
56
|
+
}
|
|
24
57
|
const handler = routes[path]?.[method];
|
|
25
58
|
if (!handler) {
|
|
26
59
|
return json({ error: 'Not found' }, { status: 404 });
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import { requireRole } from '../remote/middleware/auth.js';
|
|
2
|
-
import {
|
|
2
|
+
import { purgeAllImageStyles } from '../../core/server/media/operations/purgeImageStyles.js';
|
|
3
3
|
import { getReconciliationReport, deleteOrphanedDiskFiles } from '../../core/server/media/operations/reconcileMedia.js';
|
|
4
|
+
import { getStylesStatus } from '../../core/server/media/styles/operations/batchGenerateStyles.js';
|
|
4
5
|
import { json } from '@sveltejs/kit';
|
|
5
6
|
export const GET = async ({ url }) => {
|
|
6
7
|
requireRole('admin');
|
|
7
|
-
const
|
|
8
|
-
|
|
8
|
+
const [stylesStatus, report] = await Promise.all([
|
|
9
|
+
getStylesStatus(),
|
|
10
|
+
getReconciliationReport()
|
|
11
|
+
]);
|
|
9
12
|
return json({
|
|
10
|
-
imageStylesCount:
|
|
13
|
+
imageStylesCount: stylesStatus.existingStyles,
|
|
14
|
+
processableImagesCount: stylesStatus.processableImages,
|
|
15
|
+
expectedStylesCount: stylesStatus.expectedStyles,
|
|
16
|
+
missingStylesCount: stylesStatus.missingStyles,
|
|
11
17
|
orphanedDiskFiles: report.orphanedDisk,
|
|
12
18
|
missingDiskRecords: report.missingDisk
|
|
13
19
|
});
|
|
@@ -5,6 +5,8 @@ import * as languagesRoutes from './routes/languages.js';
|
|
|
5
5
|
import * as collectionsRoutes from './routes/collections.js';
|
|
6
6
|
import * as singletonsRoutes from './routes/singletons.js';
|
|
7
7
|
import * as entriesRoutes from './routes/entries.js';
|
|
8
|
+
import * as uploadRoutes from './routes/upload.js';
|
|
9
|
+
import * as mediaRoutes from './routes/media.js';
|
|
8
10
|
function matchRoute(method, path) {
|
|
9
11
|
// Schema routes
|
|
10
12
|
if (method === 'GET' && path === 'schema') {
|
|
@@ -17,6 +19,15 @@ function matchRoute(method, path) {
|
|
|
17
19
|
if (method === 'GET' && path === 'languages') {
|
|
18
20
|
return { handler: 'languages', params: [] };
|
|
19
21
|
}
|
|
22
|
+
// Upload: POST /upload
|
|
23
|
+
if (method === 'POST' && path === 'upload') {
|
|
24
|
+
return { handler: 'upload', params: [] };
|
|
25
|
+
}
|
|
26
|
+
// Media: GET /media/:id
|
|
27
|
+
const mediaMatch = path.match(/^media\/([^/]+)$/);
|
|
28
|
+
if (method === 'GET' && mediaMatch) {
|
|
29
|
+
return { handler: 'media', params: [mediaMatch[1]] };
|
|
30
|
+
}
|
|
20
31
|
// Entries lifecycle: POST /entries/:id/:action
|
|
21
32
|
const entriesMatch = path.match(/^entries\/([^/]+)\/(publish|unpublish|archive|unarchive)$/);
|
|
22
33
|
if (method === 'POST' && entriesMatch) {
|
|
@@ -53,6 +64,12 @@ export function createRestApiHandler() {
|
|
|
53
64
|
}
|
|
54
65
|
try {
|
|
55
66
|
switch (route.handler) {
|
|
67
|
+
case 'upload':
|
|
68
|
+
return await uploadRoutes.POST(event);
|
|
69
|
+
case 'media': {
|
|
70
|
+
const [mediaId] = route.params;
|
|
71
|
+
return await mediaRoutes.GET(event, mediaId);
|
|
72
|
+
}
|
|
56
73
|
case 'schema': {
|
|
57
74
|
// Pass restPath minus 'schema/' prefix
|
|
58
75
|
const schemaPath = restPath === 'schema' ? '' : restPath.slice(7);
|
|
@@ -2,13 +2,14 @@ import { json } from '@sveltejs/kit';
|
|
|
2
2
|
import { getCMS } from '../../../../core/cms.js';
|
|
3
3
|
import { getRawEntries, getRawEntry, countRawEntries } from '../../../../core/server/entries/operations/get.js';
|
|
4
4
|
import { createEntry } from '../../../../core/server/entries/operations/create.js';
|
|
5
|
-
import { upsertDraftVersion } from '../../../../core/server/entries/operations/update.js';
|
|
5
|
+
import { upsertDraftVersion, updateEntry, updateEntryVersion } from '../../../../core/server/entries/operations/update.js';
|
|
6
6
|
import { deleteEntry } from '../../../../core/server/entries/operations/delete.js';
|
|
7
7
|
export async function GET(event, slug, id) {
|
|
8
8
|
const cms = getCMS();
|
|
9
9
|
if (!cms.collections[slug]) {
|
|
10
10
|
return json({ error: `Collection "${slug}" not found` }, { status: 404 });
|
|
11
11
|
}
|
|
12
|
+
const lang = event.url.searchParams.get('lang') || cms.languages[0] || 'en';
|
|
12
13
|
// GET /collections/:slug/:id — single entry
|
|
13
14
|
if (id) {
|
|
14
15
|
const entry = await getRawEntry({ id, slug, includeArchived: true });
|
|
@@ -21,14 +22,12 @@ export async function GET(event, slug, id) {
|
|
|
21
22
|
type: entry.type,
|
|
22
23
|
createdAt: entry.createdAt,
|
|
23
24
|
updatedAt: entry.updatedAt,
|
|
24
|
-
publishedAt: entry.publishedAt,
|
|
25
25
|
archivedAt: entry.archivedAt,
|
|
26
|
-
availableLocales: entry.availableLocales,
|
|
27
26
|
sortOrder: entry.sortOrder,
|
|
28
|
-
draftData: entry.
|
|
29
|
-
publishedData: entry.
|
|
30
|
-
draftVersionId: entry.
|
|
31
|
-
publishedVersionId: entry.
|
|
27
|
+
draftData: entry.draftVersions[lang]?.data ?? null,
|
|
28
|
+
publishedData: entry.publishedVersions[lang]?.data ?? null,
|
|
29
|
+
draftVersionId: entry.draftVersions[lang]?.id ?? null,
|
|
30
|
+
publishedVersionId: entry.publishedVersions[lang]?.id ?? null
|
|
32
31
|
});
|
|
33
32
|
}
|
|
34
33
|
// GET /collections/:slug — list
|
|
@@ -48,12 +47,10 @@ export async function GET(event, slug, id) {
|
|
|
48
47
|
type: entry.type,
|
|
49
48
|
createdAt: entry.createdAt,
|
|
50
49
|
updatedAt: entry.updatedAt,
|
|
51
|
-
publishedAt: entry.publishedAt,
|
|
52
50
|
archivedAt: entry.archivedAt,
|
|
53
|
-
availableLocales: entry.availableLocales,
|
|
54
51
|
sortOrder: entry.sortOrder,
|
|
55
|
-
draftData: entry.
|
|
56
|
-
publishedData: entry.
|
|
52
|
+
draftData: entry.draftVersions[lang]?.data ?? null,
|
|
53
|
+
publishedData: entry.publishedVersions[lang]?.data ?? null
|
|
57
54
|
}));
|
|
58
55
|
return json({ items, total, limit, offset });
|
|
59
56
|
}
|
|
@@ -62,11 +59,25 @@ export async function POST(event, slug) {
|
|
|
62
59
|
if (!cms.collections[slug]) {
|
|
63
60
|
return json({ error: `Collection "${slug}" not found` }, { status: 404 });
|
|
64
61
|
}
|
|
62
|
+
const lang = event.url.searchParams.get('lang') || cms.languages[0] || 'en';
|
|
65
63
|
const body = await event.request.json().catch(() => null);
|
|
66
64
|
const entry = await createEntry({ slug, type: 'collection' });
|
|
67
65
|
// If data provided, save as draft
|
|
68
66
|
if (body?.data && typeof body.data === 'object') {
|
|
69
|
-
await upsertDraftVersion(entry.id, body.data, { skipValidation: true });
|
|
67
|
+
await upsertDraftVersion(entry.id, body.data, lang, { skipValidation: true });
|
|
68
|
+
}
|
|
69
|
+
// Auto-publish if requested
|
|
70
|
+
if (body?.publish) {
|
|
71
|
+
const raw = await getRawEntry({ id: entry.id, slug });
|
|
72
|
+
const draftVersion = raw?.draftVersions[lang];
|
|
73
|
+
if (draftVersion) {
|
|
74
|
+
const user = event.locals.user;
|
|
75
|
+
await updateEntryVersion(draftVersion.id, {
|
|
76
|
+
publishedAt: new Date(),
|
|
77
|
+
publishedBy: user?.id ?? 'api'
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return json({ id: entry.id, slug: entry.slug, published: true }, { status: 201 });
|
|
70
81
|
}
|
|
71
82
|
return json({ id: entry.id, slug: entry.slug }, { status: 201 });
|
|
72
83
|
}
|
|
@@ -79,11 +90,12 @@ export async function PUT(event, slug, id) {
|
|
|
79
90
|
if (!entry) {
|
|
80
91
|
return json({ error: 'Entry not found' }, { status: 404 });
|
|
81
92
|
}
|
|
93
|
+
const lang = event.url.searchParams.get('lang') || cms.languages[0] || 'en';
|
|
82
94
|
const body = await event.request.json().catch(() => null);
|
|
83
95
|
if (!body?.data || typeof body.data !== 'object') {
|
|
84
96
|
return json({ error: 'Request body must contain "data" object' }, { status: 400 });
|
|
85
97
|
}
|
|
86
|
-
const version = await upsertDraftVersion(entry.id, body.data, { skipValidation: true });
|
|
98
|
+
const version = await upsertDraftVersion(entry.id, body.data, lang, { skipValidation: true });
|
|
87
99
|
return json({
|
|
88
100
|
id: entry.id,
|
|
89
101
|
versionId: version.id,
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { type RequestEvent } from '@sveltejs/kit';
|
|
2
|
-
export declare function POST(
|
|
2
|
+
export declare function POST(event: RequestEvent, id: string, action: string): Promise<Response>;
|
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
import { json } from '@sveltejs/kit';
|
|
2
|
-
import {
|
|
2
|
+
import { getCMS } from '../../../../core/cms.js';
|
|
3
3
|
import { getRawEntryOrThrow } from '../../../../core/server/entries/operations/get.js';
|
|
4
|
-
import { updateEntry,
|
|
5
|
-
export async function POST(
|
|
6
|
-
const
|
|
4
|
+
import { updateEntry, updateEntryVersion, unpublishEntryLang } from '../../../../core/server/entries/operations/update.js';
|
|
5
|
+
export async function POST(event, id, action) {
|
|
6
|
+
const user = event.locals.user;
|
|
7
|
+
const lang = event.url.searchParams.get('lang') || getCMS().languages[0] || 'en';
|
|
7
8
|
switch (action) {
|
|
8
9
|
case 'publish': {
|
|
9
10
|
const entry = await getRawEntryOrThrow({ id, includeArchived: false });
|
|
10
|
-
const draftVersion = entry.
|
|
11
|
+
const draftVersion = entry.draftVersions[lang];
|
|
11
12
|
if (!draftVersion) {
|
|
12
13
|
return json({ error: 'No draft version to publish' }, { status: 400 });
|
|
13
14
|
}
|
|
14
|
-
await
|
|
15
|
-
publishedVersionId: draftVersion.id,
|
|
15
|
+
await updateEntryVersion(draftVersion.id, {
|
|
16
16
|
publishedAt: new Date(),
|
|
17
|
-
publishedBy: user.id
|
|
18
|
-
archivedAt: null
|
|
17
|
+
publishedBy: user.id
|
|
19
18
|
});
|
|
19
|
+
await updateEntry(id, { archivedAt: null });
|
|
20
20
|
return json({ success: true, publishedVersionId: draftVersion.id });
|
|
21
21
|
}
|
|
22
22
|
case 'unpublish': {
|
|
23
|
-
await
|
|
23
|
+
await unpublishEntryLang(id, lang);
|
|
24
24
|
return json({ success: true });
|
|
25
25
|
}
|
|
26
26
|
case 'archive': {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { json } from '@sveltejs/kit';
|
|
2
|
+
import { getFile } from '../../../../core/server/media/operations/getFiles.js';
|
|
3
|
+
export async function GET(_event, id) {
|
|
4
|
+
const file = await getFile(id);
|
|
5
|
+
if (!file) {
|
|
6
|
+
return json({ error: 'Media file not found' }, { status: 404 });
|
|
7
|
+
}
|
|
8
|
+
return json(file);
|
|
9
|
+
}
|
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
import { type RequestEvent } from '@sveltejs/kit';
|
|
2
|
+
import type { Field } from '../../../../types/fields.js';
|
|
3
|
+
export declare function generateTemplate(fields: Field[], languages: string[]): {
|
|
4
|
+
template: Record<string, unknown>;
|
|
5
|
+
meta: Record<string, string>;
|
|
6
|
+
};
|
|
2
7
|
export declare function GET(event: RequestEvent): Promise<Response>;
|
|
@@ -10,9 +10,161 @@ function serializeField(field) {
|
|
|
10
10
|
function serializeFields(fields) {
|
|
11
11
|
return fields.map(serializeField);
|
|
12
12
|
}
|
|
13
|
+
function localize(value, languages) {
|
|
14
|
+
const obj = {};
|
|
15
|
+
for (const lang of languages) {
|
|
16
|
+
obj[lang] = value;
|
|
17
|
+
}
|
|
18
|
+
return obj;
|
|
19
|
+
}
|
|
20
|
+
function generateFieldTemplate(field, languages) {
|
|
21
|
+
const wrap = (v, hint) => {
|
|
22
|
+
if (field.localized) {
|
|
23
|
+
return { value: localize(v, languages), hint: `${hint} (localized)` };
|
|
24
|
+
}
|
|
25
|
+
return { value: v, hint };
|
|
26
|
+
};
|
|
27
|
+
switch (field.type) {
|
|
28
|
+
case 'text':
|
|
29
|
+
case 'slug':
|
|
30
|
+
return wrap('', 'string');
|
|
31
|
+
case 'content':
|
|
32
|
+
return wrap({ type: 'doc', content: [] }, 'ProseMirror JSON document');
|
|
33
|
+
case 'number':
|
|
34
|
+
return wrap(0, 'number');
|
|
35
|
+
case 'boolean':
|
|
36
|
+
return { value: false, hint: 'boolean' };
|
|
37
|
+
case 'date':
|
|
38
|
+
return wrap('2024-01-01', 'ISO date string');
|
|
39
|
+
case 'datetime':
|
|
40
|
+
return wrap('2024-01-01T00:00:00Z', 'ISO datetime string');
|
|
41
|
+
case 'file':
|
|
42
|
+
if (field.multiple) {
|
|
43
|
+
return { value: ['<media-uuid-from-upload>'], hint: 'array of media UUIDs from POST /upload' };
|
|
44
|
+
}
|
|
45
|
+
return { value: '<media-uuid-from-upload>', hint: 'media UUID from POST /upload' };
|
|
46
|
+
case 'media':
|
|
47
|
+
if (field.multiple) {
|
|
48
|
+
return { value: ['<media-uuid-from-upload>'], hint: 'array of media UUIDs (image or video)' };
|
|
49
|
+
}
|
|
50
|
+
return { value: '<media-uuid-from-upload>', hint: 'media UUID (image or video) from POST /upload' };
|
|
51
|
+
case 'select': {
|
|
52
|
+
const sf = field;
|
|
53
|
+
const opts = sf.options.map((o) => o.value);
|
|
54
|
+
if (sf.multiple) {
|
|
55
|
+
return { value: opts.length ? [opts[0]] : [], hint: `array of: ${opts.join(' | ')}` };
|
|
56
|
+
}
|
|
57
|
+
return { value: opts[0] ?? '', hint: `one of: ${opts.join(' | ')}` };
|
|
58
|
+
}
|
|
59
|
+
case 'radio': {
|
|
60
|
+
const rf = field;
|
|
61
|
+
const opts = rf.options.map((o) => o.value);
|
|
62
|
+
return { value: opts[0] ?? '', hint: `one of: ${opts.join(' | ')}` };
|
|
63
|
+
}
|
|
64
|
+
case 'checkboxes': {
|
|
65
|
+
const cf = field;
|
|
66
|
+
const opts = cf.options.map((o) => o.value);
|
|
67
|
+
return { value: opts.length ? [opts[0]] : [], hint: `array of: ${opts.join(' | ')}` };
|
|
68
|
+
}
|
|
69
|
+
case 'relation': {
|
|
70
|
+
const rf = field;
|
|
71
|
+
if (rf.multiple) {
|
|
72
|
+
return { value: ['<entry-uuid>'], hint: `array of ${rf.collection} entry UUIDs` };
|
|
73
|
+
}
|
|
74
|
+
return { value: '<entry-uuid>', hint: `${rf.collection} entry UUID` };
|
|
75
|
+
}
|
|
76
|
+
case 'object': {
|
|
77
|
+
const of = field;
|
|
78
|
+
const { template, meta } = generateTemplate(of.fields, languages);
|
|
79
|
+
return { value: template, hint: `object — ${JSON.stringify(meta)}` };
|
|
80
|
+
}
|
|
81
|
+
case 'blocks': {
|
|
82
|
+
const bf = field;
|
|
83
|
+
const blocks = bf.of.map((block) => {
|
|
84
|
+
const { template } = generateTemplate(block.fields, languages);
|
|
85
|
+
return { _slug: block.slug, ...template };
|
|
86
|
+
});
|
|
87
|
+
return { value: blocks, hint: 'array of block objects with _slug' };
|
|
88
|
+
}
|
|
89
|
+
case 'array': {
|
|
90
|
+
const af = field;
|
|
91
|
+
const examples = { text: '', number: 0, url: { url: { [languages[0]]: '' } } };
|
|
92
|
+
return { value: [examples[af.of] ?? ''], hint: `array of ${af.of}` };
|
|
93
|
+
}
|
|
94
|
+
case 'seo':
|
|
95
|
+
return {
|
|
96
|
+
value: {
|
|
97
|
+
slug: localize('', languages),
|
|
98
|
+
title: localize('', languages),
|
|
99
|
+
description: localize('', languages),
|
|
100
|
+
ogImage: '',
|
|
101
|
+
keywords: localize('', languages),
|
|
102
|
+
canonicalUrl: localize('', languages),
|
|
103
|
+
customCode: localize('', languages)
|
|
104
|
+
},
|
|
105
|
+
hint: 'SEO data — slug/title required, ogImage = media UUID or empty'
|
|
106
|
+
};
|
|
107
|
+
case 'url':
|
|
108
|
+
return {
|
|
109
|
+
value: {
|
|
110
|
+
url: localize('', languages),
|
|
111
|
+
text: localize('', languages),
|
|
112
|
+
newTab: false
|
|
113
|
+
},
|
|
114
|
+
hint: 'URL field with optional text and newTab'
|
|
115
|
+
};
|
|
116
|
+
case 'custom':
|
|
117
|
+
return { value: null, hint: 'custom field — check plugin docs' };
|
|
118
|
+
default:
|
|
119
|
+
return { value: null, hint: 'unknown field type' };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
export function generateTemplate(fields, languages) {
|
|
123
|
+
const template = {};
|
|
124
|
+
const meta = {};
|
|
125
|
+
for (const field of fields) {
|
|
126
|
+
const { value, hint } = generateFieldTemplate(field, languages);
|
|
127
|
+
template[field.slug] = value;
|
|
128
|
+
meta[field.slug] = hint;
|
|
129
|
+
}
|
|
130
|
+
return { template, meta };
|
|
131
|
+
}
|
|
13
132
|
export async function GET(event) {
|
|
14
133
|
const cms = getCMS();
|
|
15
134
|
const path = event.params.restPath || '';
|
|
135
|
+
// GET /schema/collections/:slug/template
|
|
136
|
+
const templateMatch = path.match(/^collections\/([^/]+)\/template$/);
|
|
137
|
+
if (templateMatch) {
|
|
138
|
+
const slug = templateMatch[1];
|
|
139
|
+
const collection = cms.collections[slug];
|
|
140
|
+
if (!collection) {
|
|
141
|
+
return json({ error: `Collection "${slug}" not found` }, { status: 404 });
|
|
142
|
+
}
|
|
143
|
+
const fields = getFieldsFromConfig(collection);
|
|
144
|
+
const { template, meta } = generateTemplate(fields, cms.languages);
|
|
145
|
+
return json({
|
|
146
|
+
data: template,
|
|
147
|
+
publish: true,
|
|
148
|
+
_meta: meta,
|
|
149
|
+
_notes: 'POST this to /collections/' + slug + ' — set publish:true to auto-publish'
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
// GET /schema/singletons/:slug/template
|
|
153
|
+
const singletonTemplateMatch = path.match(/^singletons\/([^/]+)\/template$/);
|
|
154
|
+
if (singletonTemplateMatch) {
|
|
155
|
+
const slug = singletonTemplateMatch[1];
|
|
156
|
+
const single = cms.singles[slug];
|
|
157
|
+
if (!single) {
|
|
158
|
+
return json({ error: `Singleton "${slug}" not found` }, { status: 404 });
|
|
159
|
+
}
|
|
160
|
+
const fields = getFieldsFromConfig(single);
|
|
161
|
+
const { template, meta } = generateTemplate(fields, cms.languages);
|
|
162
|
+
return json({
|
|
163
|
+
data: template,
|
|
164
|
+
_meta: meta,
|
|
165
|
+
_notes: 'PUT this to /singletons/' + slug
|
|
166
|
+
});
|
|
167
|
+
}
|
|
16
168
|
// GET /schema/collections/:slug
|
|
17
169
|
const collectionMatch = path.match(/^collections\/([^/]+)$/);
|
|
18
170
|
if (collectionMatch) {
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { type RequestEvent } from '@sveltejs/kit';
|
|
2
|
-
export declare function GET(
|
|
2
|
+
export declare function GET(event: RequestEvent, slug: string): Promise<Response>;
|
|
3
3
|
export declare function PUT(event: RequestEvent, slug: string): Promise<Response>;
|