@actuate-media/cms-core 0.57.0 → 0.58.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/dist/__tests__/api/stats-seo-cache.test.js +1 -1
- package/dist/__tests__/api/stats-seo-cache.test.js.map +1 -1
- package/dist/__tests__/auth/oauth.test.js +3 -1
- package/dist/__tests__/auth/oauth.test.js.map +1 -1
- package/dist/__tests__/auth/reset.test.js +1 -0
- package/dist/__tests__/auth/reset.test.js.map +1 -1
- package/dist/__tests__/auth/user-admin.test.js +14 -12
- package/dist/__tests__/auth/user-admin.test.js.map +1 -1
- package/dist/__tests__/db/model-types-freshness.test.d.ts +2 -0
- package/dist/__tests__/db/model-types-freshness.test.d.ts.map +1 -0
- package/dist/__tests__/db/model-types-freshness.test.js +46 -0
- package/dist/__tests__/db/model-types-freshness.test.js.map +1 -0
- package/dist/__tests__/scheduling/scheduling.test.js +8 -6
- package/dist/__tests__/scheduling/scheduling.test.js.map +1 -1
- package/dist/__tests__/server-site.test.js +3 -1
- package/dist/__tests__/server-site.test.js.map +1 -1
- package/dist/__tests__/setup/index.test.js +21 -10
- package/dist/__tests__/setup/index.test.js.map +1 -1
- package/dist/actions.d.ts +2 -1
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +15 -8
- package/dist/actions.js.map +1 -1
- package/dist/api/guarded-db.d.ts +14 -0
- package/dist/api/guarded-db.d.ts.map +1 -0
- package/dist/api/guarded-db.js +58 -0
- package/dist/api/guarded-db.js.map +1 -0
- package/dist/api/handler-factory.d.ts.map +1 -1
- package/dist/api/handler-factory.js +2 -1
- package/dist/api/handler-factory.js.map +1 -1
- package/dist/api/handlers.d.ts +5 -3
- package/dist/api/handlers.d.ts.map +1 -1
- package/dist/api/handlers.js +54 -13068
- package/dist/api/handlers.js.map +1 -1
- package/dist/api/route-helpers.d.ts +321 -0
- package/dist/api/route-helpers.d.ts.map +1 -0
- package/dist/api/route-helpers.js +1307 -0
- package/dist/api/route-helpers.js.map +1 -0
- package/dist/api/routes/ai-seo.d.ts +3 -0
- package/dist/api/routes/ai-seo.d.ts.map +1 -0
- package/dist/api/routes/ai-seo.js +377 -0
- package/dist/api/routes/ai-seo.js.map +1 -0
- package/dist/api/routes/ai-settings.d.ts +3 -0
- package/dist/api/routes/ai-settings.d.ts.map +1 -0
- package/dist/api/routes/ai-settings.js +764 -0
- package/dist/api/routes/ai-settings.js.map +1 -0
- package/dist/api/routes/auth.d.ts +3 -0
- package/dist/api/routes/auth.d.ts.map +1 -0
- package/dist/api/routes/auth.js +787 -0
- package/dist/api/routes/auth.js.map +1 -0
- package/dist/api/routes/collab.d.ts +3 -0
- package/dist/api/routes/collab.d.ts.map +1 -0
- package/dist/api/routes/collab.js +426 -0
- package/dist/api/routes/collab.js.map +1 -0
- package/dist/api/routes/dashboard.d.ts +7 -0
- package/dist/api/routes/dashboard.d.ts.map +1 -0
- package/dist/api/routes/dashboard.js +540 -0
- package/dist/api/routes/dashboard.js.map +1 -0
- package/dist/api/routes/documents.d.ts +3 -0
- package/dist/api/routes/documents.d.ts.map +1 -0
- package/dist/api/routes/documents.js +180 -0
- package/dist/api/routes/documents.js.map +1 -0
- package/dist/api/routes/folders.d.ts +3 -0
- package/dist/api/routes/folders.d.ts.map +1 -0
- package/dist/api/routes/folders.js +195 -0
- package/dist/api/routes/folders.js.map +1 -0
- package/dist/api/routes/forms.d.ts +3 -0
- package/dist/api/routes/forms.d.ts.map +1 -0
- package/dist/api/routes/forms.js +661 -0
- package/dist/api/routes/forms.js.map +1 -0
- package/dist/api/routes/globals.d.ts +3 -0
- package/dist/api/routes/globals.d.ts.map +1 -0
- package/dist/api/routes/globals.js +133 -0
- package/dist/api/routes/globals.js.map +1 -0
- package/dist/api/routes/media.d.ts +3 -0
- package/dist/api/routes/media.d.ts.map +1 -0
- package/dist/api/routes/media.js +521 -0
- package/dist/api/routes/media.js.map +1 -0
- package/dist/api/routes/meta.d.ts +3 -0
- package/dist/api/routes/meta.d.ts.map +1 -0
- package/dist/api/routes/meta.js +96 -0
- package/dist/api/routes/meta.js.map +1 -0
- package/dist/api/routes/page-templates.d.ts +3 -0
- package/dist/api/routes/page-templates.d.ts.map +1 -0
- package/dist/api/routes/page-templates.js +265 -0
- package/dist/api/routes/page-templates.js.map +1 -0
- package/dist/api/routes/presence.d.ts +3 -0
- package/dist/api/routes/presence.d.ts.map +1 -0
- package/dist/api/routes/presence.js +35 -0
- package/dist/api/routes/presence.js.map +1 -0
- package/dist/api/routes/preview.d.ts +3 -0
- package/dist/api/routes/preview.d.ts.map +1 -0
- package/dist/api/routes/preview.js +111 -0
- package/dist/api/routes/preview.js.map +1 -0
- package/dist/api/routes/redirects.d.ts +3 -0
- package/dist/api/routes/redirects.d.ts.map +1 -0
- package/dist/api/routes/redirects.js +790 -0
- package/dist/api/routes/redirects.js.map +1 -0
- package/dist/api/routes/saved-sections.d.ts +3 -0
- package/dist/api/routes/saved-sections.d.ts.map +1 -0
- package/dist/api/routes/saved-sections.js +1036 -0
- package/dist/api/routes/saved-sections.js.map +1 -0
- package/dist/api/routes/scheduling.d.ts +3 -0
- package/dist/api/routes/scheduling.d.ts.map +1 -0
- package/dist/api/routes/scheduling.js +373 -0
- package/dist/api/routes/scheduling.js.map +1 -0
- package/dist/api/routes/sections.d.ts +3 -0
- package/dist/api/routes/sections.d.ts.map +1 -0
- package/dist/api/routes/sections.js +500 -0
- package/dist/api/routes/sections.js.map +1 -0
- package/dist/api/routes/security-center.d.ts +3 -0
- package/dist/api/routes/security-center.d.ts.map +1 -0
- package/dist/api/routes/security-center.js +71 -0
- package/dist/api/routes/security-center.js.map +1 -0
- package/dist/api/routes/seo-public.d.ts +14 -0
- package/dist/api/routes/seo-public.d.ts.map +1 -0
- package/dist/api/routes/seo-public.js +930 -0
- package/dist/api/routes/seo-public.js.map +1 -0
- package/dist/api/routes/seo.d.ts +8 -0
- package/dist/api/routes/seo.d.ts.map +1 -0
- package/dist/api/routes/seo.js +848 -0
- package/dist/api/routes/seo.js.map +1 -0
- package/dist/api/routes/system.d.ts +3 -0
- package/dist/api/routes/system.d.ts.map +1 -0
- package/dist/api/routes/system.js +340 -0
- package/dist/api/routes/system.js.map +1 -0
- package/dist/api/routes/url-resolution.d.ts +3 -0
- package/dist/api/routes/url-resolution.d.ts.map +1 -0
- package/dist/api/routes/url-resolution.js +457 -0
- package/dist/api/routes/url-resolution.js.map +1 -0
- package/dist/api/routes/users.d.ts +3 -0
- package/dist/api/routes/users.d.ts.map +1 -0
- package/dist/api/routes/users.js +1162 -0
- package/dist/api/routes/users.js.map +1 -0
- package/dist/api/routes/webhooks.d.ts +3 -0
- package/dist/api/routes/webhooks.d.ts.map +1 -0
- package/dist/api/routes/webhooks.js +338 -0
- package/dist/api/routes/webhooks.js.map +1 -0
- package/dist/auth/invites.d.ts +9 -8
- package/dist/auth/invites.d.ts.map +1 -1
- package/dist/auth/invites.js.map +1 -1
- package/dist/auth/oauth.d.ts +2 -1
- package/dist/auth/oauth.d.ts.map +1 -1
- package/dist/auth/oauth.js.map +1 -1
- package/dist/auth/reset.d.ts +3 -2
- package/dist/auth/reset.d.ts.map +1 -1
- package/dist/auth/reset.js +5 -2
- package/dist/auth/reset.js.map +1 -1
- package/dist/auth/session.d.ts +3 -2
- package/dist/auth/session.d.ts.map +1 -1
- package/dist/auth/session.js.map +1 -1
- package/dist/auth/team.d.ts +4 -3
- package/dist/auth/team.d.ts.map +1 -1
- package/dist/auth/team.js.map +1 -1
- package/dist/auth/user-admin.d.ts +6 -5
- package/dist/auth/user-admin.d.ts.map +1 -1
- package/dist/auth/user-admin.js.map +1 -1
- package/dist/content-health/cron.d.ts +2 -1
- package/dist/content-health/cron.d.ts.map +1 -1
- package/dist/content-health/cron.js.map +1 -1
- package/dist/cron/index.d.ts +2 -1
- package/dist/cron/index.d.ts.map +1 -1
- package/dist/cron/index.js +2 -0
- package/dist/cron/index.js.map +1 -1
- package/dist/db/client-types.d.ts +75 -0
- package/dist/db/client-types.d.ts.map +1 -0
- package/dist/db/client-types.js +2 -0
- package/dist/db/client-types.js.map +1 -0
- package/dist/db/model-types.d.ts +564 -0
- package/dist/db/model-types.d.ts.map +1 -0
- package/dist/db/model-types.js +14 -0
- package/dist/db/model-types.js.map +1 -0
- package/dist/diagnostics/api-metrics.d.ts.map +1 -1
- package/dist/diagnostics/api-metrics.js.map +1 -1
- package/dist/forms/entries.d.ts.map +1 -1
- package/dist/forms/entries.js.map +1 -1
- package/dist/forms/files.d.ts.map +1 -1
- package/dist/forms/files.js.map +1 -1
- package/dist/forms/service.d.ts.map +1 -1
- package/dist/forms/service.js.map +1 -1
- package/dist/forms/submission.d.ts.map +1 -1
- package/dist/forms/submission.js.map +1 -1
- package/dist/forms/webhooks.d.ts.map +1 -1
- package/dist/forms/webhooks.js.map +1 -1
- package/dist/graphql/index.d.ts.map +1 -1
- package/dist/graphql/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/scheduling/index.d.ts +2 -1
- package/dist/scheduling/index.d.ts.map +1 -1
- package/dist/scheduling/index.js.map +1 -1
- package/dist/search/index.d.ts.map +1 -1
- package/dist/search/index.js.map +1 -1
- package/dist/security/audit.d.ts.map +1 -1
- package/dist/security/audit.js.map +1 -1
- package/dist/security/reauth.d.ts +2 -1
- package/dist/security/reauth.d.ts.map +1 -1
- package/dist/security/reauth.js.map +1 -1
- package/dist/seo/audit-runner.d.ts +3 -2
- package/dist/seo/audit-runner.d.ts.map +1 -1
- package/dist/seo/audit-runner.js.map +1 -1
- package/dist/server-site.d.ts +2 -1
- package/dist/server-site.d.ts.map +1 -1
- package/dist/server-site.js.map +1 -1
- package/dist/setup/index.d.ts +4 -3
- package/dist/setup/index.d.ts.map +1 -1
- package/dist/setup/index.js.map +1 -1
- package/dist/webhooks/index.d.ts +5 -5
- package/dist/webhooks/index.d.ts.map +1 -1
- package/dist/webhooks/index.js +8 -1
- package/dist/webhooks/index.js.map +1 -1
- package/dist/workflow/index.d.ts.map +1 -1
- package/dist/workflow/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
import { optimizeImage, formatBytes } from '../../media/optimize.js';
|
|
2
|
+
import { logEvent } from '../../security/audit.js';
|
|
3
|
+
import { validateMimeType, checkMagicBytes } from '../../security/upload.js';
|
|
4
|
+
import { sanitizeHtml } from '../../security/sanitize.js';
|
|
5
|
+
import { guardedDb } from '../guarded-db.js';
|
|
6
|
+
import { importBlobStorage, json, errorResponse, internalError, clampPageSize, normalizeMediaItem, buildMediaUsedOn, isAllowedStorageUrl, requireAuth, requireMediaScope, WRITE_ROLES, requireRole, uploadLimiter, checkRateLimitAsync, } from '../route-helpers.js';
|
|
7
|
+
export function registerMediaRoutes(router) {
|
|
8
|
+
const db = guardedDb;
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Media routes
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
router.get('/media', async (request) => {
|
|
13
|
+
try {
|
|
14
|
+
const auth = await requireAuth(request);
|
|
15
|
+
if (auth.error)
|
|
16
|
+
return auth.error;
|
|
17
|
+
const url = new URL(request.url);
|
|
18
|
+
const page = Number(url.searchParams.get('page')) || 1;
|
|
19
|
+
const pageSize = clampPageSize(url.searchParams.get('pageSize'));
|
|
20
|
+
const skip = (page - 1) * pageSize;
|
|
21
|
+
const folderParam = url.searchParams.get('folderId');
|
|
22
|
+
const where = {};
|
|
23
|
+
if (folderParam === 'none') {
|
|
24
|
+
where.folderId = null;
|
|
25
|
+
}
|
|
26
|
+
else if (folderParam) {
|
|
27
|
+
where.folderId = folderParam;
|
|
28
|
+
}
|
|
29
|
+
const [items, total] = await Promise.all([
|
|
30
|
+
db().media.findMany({
|
|
31
|
+
where,
|
|
32
|
+
skip,
|
|
33
|
+
take: pageSize,
|
|
34
|
+
orderBy: { createdAt: 'desc' },
|
|
35
|
+
include: {
|
|
36
|
+
mediaUsages: {
|
|
37
|
+
include: {
|
|
38
|
+
document: { select: { id: true, collection: true, title: true, deletedAt: true } },
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
}),
|
|
43
|
+
db().media.count({ where }),
|
|
44
|
+
]);
|
|
45
|
+
const normalized = items.map((item) => {
|
|
46
|
+
const { mediaUsages, ...rest } = normalizeMediaItem(item);
|
|
47
|
+
return { ...rest, usedOn: buildMediaUsedOn(mediaUsages) };
|
|
48
|
+
});
|
|
49
|
+
return json({
|
|
50
|
+
data: {
|
|
51
|
+
data: normalized,
|
|
52
|
+
items: normalized,
|
|
53
|
+
total,
|
|
54
|
+
page,
|
|
55
|
+
pageSize,
|
|
56
|
+
totalPages: Math.ceil(total / pageSize),
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
return internalError(err);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
// The /media/presign endpoint returns advisory upload metadata; the actual
|
|
65
|
+
// upload still goes through /media/upload (which performs validation).
|
|
66
|
+
// Returning a presigned URL pointing at our own non-presigned endpoint was
|
|
67
|
+
// misleading, so we deprecated this in favour of just using /media/upload
|
|
68
|
+
// directly. We keep the route for backward compatibility but it now just
|
|
69
|
+
// returns a hint to use the upload endpoint.
|
|
70
|
+
router.post('/media/presign', async (request) => {
|
|
71
|
+
try {
|
|
72
|
+
const auth = await requireAuth(request);
|
|
73
|
+
if (auth.error)
|
|
74
|
+
return auth.error;
|
|
75
|
+
const scopeErr = requireMediaScope(auth.session);
|
|
76
|
+
if (scopeErr)
|
|
77
|
+
return scopeErr;
|
|
78
|
+
const body = (await request.json());
|
|
79
|
+
if (!body.filename || !body.contentType) {
|
|
80
|
+
return errorResponse('filename and contentType are required', 400);
|
|
81
|
+
}
|
|
82
|
+
return json({
|
|
83
|
+
data: {
|
|
84
|
+
uploadUrl: '/api/cms/media/upload',
|
|
85
|
+
method: 'POST',
|
|
86
|
+
field: 'file',
|
|
87
|
+
deprecated: true,
|
|
88
|
+
message: 'Direct multipart upload to /media/upload is preferred; this endpoint will be removed in a future release.',
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
return internalError(err);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
const MAX_UPLOAD_BYTES = 50 * 1024 * 1024; // 50 MB
|
|
97
|
+
/**
|
|
98
|
+
* Allowlist of accepted upload mime types. Storing a file outside this set
|
|
99
|
+
* would let an attacker upload e.g. `.html`/`.js` and serve it from the same
|
|
100
|
+
* origin as the admin (cookie-leaking XSS), or `.svg` with embedded
|
|
101
|
+
* `<script>` (also XSS). Add to this list deliberately.
|
|
102
|
+
*/
|
|
103
|
+
const ALLOWED_UPLOAD_MIME_TYPES = [
|
|
104
|
+
'image/jpeg',
|
|
105
|
+
'image/png',
|
|
106
|
+
'image/gif',
|
|
107
|
+
'image/webp',
|
|
108
|
+
'image/avif',
|
|
109
|
+
'image/svg+xml',
|
|
110
|
+
'application/pdf',
|
|
111
|
+
'video/mp4',
|
|
112
|
+
'video/webm',
|
|
113
|
+
'audio/mpeg',
|
|
114
|
+
'audio/ogg',
|
|
115
|
+
'audio/wav',
|
|
116
|
+
];
|
|
117
|
+
router.post('/media/upload', async (request) => {
|
|
118
|
+
try {
|
|
119
|
+
const auth = await requireAuth(request);
|
|
120
|
+
if (auth.error)
|
|
121
|
+
return auth.error;
|
|
122
|
+
const scopeErr = requireMediaScope(auth.session);
|
|
123
|
+
if (scopeErr)
|
|
124
|
+
return scopeErr;
|
|
125
|
+
if (!(await checkRateLimitAsync(uploadLimiter, `upload:${auth.session.userId}`))) {
|
|
126
|
+
return errorResponse('Upload rate limit reached (20/hour). Try again later.', 429);
|
|
127
|
+
}
|
|
128
|
+
// Reject *before* buffering the body. We require a valid content-length
|
|
129
|
+
// header so that chunked / no-length requests (which would otherwise
|
|
130
|
+
// bypass this gate and allow `request.formData()` to buffer unbounded
|
|
131
|
+
// memory) are rejected up front. The multipart envelope is always
|
|
132
|
+
// larger than the underlying file, so checking against MAX_UPLOAD_BYTES
|
|
133
|
+
// here is a safe upper bound — a 50 MB file cannot fit inside a 50 MB
|
|
134
|
+
// request body.
|
|
135
|
+
const contentLengthHeader = request.headers.get('content-length');
|
|
136
|
+
if (!contentLengthHeader) {
|
|
137
|
+
return errorResponse('Content-Length header is required for uploads (chunked encoding is not supported)', 411);
|
|
138
|
+
}
|
|
139
|
+
const contentLength = parseInt(contentLengthHeader, 10);
|
|
140
|
+
if (!Number.isFinite(contentLength) || contentLength <= 0) {
|
|
141
|
+
return errorResponse('Invalid Content-Length header', 400);
|
|
142
|
+
}
|
|
143
|
+
if (contentLength > MAX_UPLOAD_BYTES) {
|
|
144
|
+
return errorResponse('File exceeds maximum size of 50MB', 413);
|
|
145
|
+
}
|
|
146
|
+
const formData = await request.formData();
|
|
147
|
+
const file = formData.get('file');
|
|
148
|
+
const skipOptimize = formData.get('skipOptimize') === 'true';
|
|
149
|
+
if (!file) {
|
|
150
|
+
return errorResponse('No file provided', 400);
|
|
151
|
+
}
|
|
152
|
+
const originalFilename = file.name;
|
|
153
|
+
const contentType = file.type;
|
|
154
|
+
const originalSize = file.size;
|
|
155
|
+
// Belt-and-braces: even with the header check above, re-validate the
|
|
156
|
+
// *actual* file size after parsing in case the multipart envelope
|
|
157
|
+
// disagreed with the header. This must come BEFORE any further
|
|
158
|
+
// processing (magic byte read, SVG sanitization, blob upload).
|
|
159
|
+
if (originalSize > MAX_UPLOAD_BYTES) {
|
|
160
|
+
return errorResponse('File exceeds maximum size of 50MB', 413);
|
|
161
|
+
}
|
|
162
|
+
// 1. Block file types that aren't on our allowlist outright.
|
|
163
|
+
if (!validateMimeType(contentType, ALLOWED_UPLOAD_MIME_TYPES)) {
|
|
164
|
+
return errorResponse(`Unsupported file type "${contentType || 'unknown'}". Allowed types: ${ALLOWED_UPLOAD_MIME_TYPES.join(', ')}`, 415);
|
|
165
|
+
}
|
|
166
|
+
const arrayBuffer = await file.arrayBuffer();
|
|
167
|
+
// 2. Verify the file's actual bytes match the claimed mime type. Without
|
|
168
|
+
// this, an attacker can upload a `.exe` with `Content-Type: image/png`
|
|
169
|
+
// and have it served from our origin.
|
|
170
|
+
const magicCheck = checkMagicBytes(arrayBuffer, contentType);
|
|
171
|
+
if (!magicCheck.valid) {
|
|
172
|
+
return errorResponse(`File contents do not match declared type "${contentType}".`, 415);
|
|
173
|
+
}
|
|
174
|
+
// 3. SVGs need an extra pass — even when the mime type is correct, the
|
|
175
|
+
// XML body can contain `<script>` or event-handler attributes that
|
|
176
|
+
// execute when an admin previews the file. Sanitize before storing.
|
|
177
|
+
let uploadBuffer;
|
|
178
|
+
let finalFilename = originalFilename;
|
|
179
|
+
let finalMimeType = contentType;
|
|
180
|
+
let finalSize = originalSize;
|
|
181
|
+
let width = null;
|
|
182
|
+
let height = null;
|
|
183
|
+
let blurHash = null;
|
|
184
|
+
let savings = 0;
|
|
185
|
+
if (contentType === 'image/svg+xml') {
|
|
186
|
+
// Strip <script>, on*, javascript: URLs, foreignObject, etc.
|
|
187
|
+
const xml = new TextDecoder('utf-8', { fatal: false }).decode(arrayBuffer);
|
|
188
|
+
const sanitized = sanitizeHtml(xml, {
|
|
189
|
+
allowedTags: [
|
|
190
|
+
'svg',
|
|
191
|
+
'g',
|
|
192
|
+
'path',
|
|
193
|
+
'circle',
|
|
194
|
+
'ellipse',
|
|
195
|
+
'line',
|
|
196
|
+
'polygon',
|
|
197
|
+
'polyline',
|
|
198
|
+
'rect',
|
|
199
|
+
'text',
|
|
200
|
+
'tspan',
|
|
201
|
+
'defs',
|
|
202
|
+
'use',
|
|
203
|
+
'symbol',
|
|
204
|
+
'title',
|
|
205
|
+
'desc',
|
|
206
|
+
'style',
|
|
207
|
+
'linearGradient',
|
|
208
|
+
'radialGradient',
|
|
209
|
+
'stop',
|
|
210
|
+
'mask',
|
|
211
|
+
'clipPath',
|
|
212
|
+
'pattern',
|
|
213
|
+
'filter',
|
|
214
|
+
'feGaussianBlur',
|
|
215
|
+
'feColorMatrix',
|
|
216
|
+
'feOffset',
|
|
217
|
+
'feBlend',
|
|
218
|
+
'feFlood',
|
|
219
|
+
'feComposite',
|
|
220
|
+
'feMerge',
|
|
221
|
+
'feMergeNode',
|
|
222
|
+
],
|
|
223
|
+
allowedAttributes: {
|
|
224
|
+
'*': [
|
|
225
|
+
'id',
|
|
226
|
+
'class',
|
|
227
|
+
'fill',
|
|
228
|
+
'stroke',
|
|
229
|
+
'stroke-width',
|
|
230
|
+
'stroke-linecap',
|
|
231
|
+
'stroke-linejoin',
|
|
232
|
+
'opacity',
|
|
233
|
+
'transform',
|
|
234
|
+
'd',
|
|
235
|
+
'cx',
|
|
236
|
+
'cy',
|
|
237
|
+
'r',
|
|
238
|
+
'rx',
|
|
239
|
+
'ry',
|
|
240
|
+
'x',
|
|
241
|
+
'y',
|
|
242
|
+
'x1',
|
|
243
|
+
'y1',
|
|
244
|
+
'x2',
|
|
245
|
+
'y2',
|
|
246
|
+
'width',
|
|
247
|
+
'height',
|
|
248
|
+
'viewBox',
|
|
249
|
+
'xmlns',
|
|
250
|
+
'xmlns:xlink',
|
|
251
|
+
'preserveAspectRatio',
|
|
252
|
+
'points',
|
|
253
|
+
'points',
|
|
254
|
+
'offset',
|
|
255
|
+
'stop-color',
|
|
256
|
+
'stop-opacity',
|
|
257
|
+
'gradientUnits',
|
|
258
|
+
'gradientTransform',
|
|
259
|
+
'href',
|
|
260
|
+
'xlink:href',
|
|
261
|
+
'fill-rule',
|
|
262
|
+
'clip-rule',
|
|
263
|
+
'mask',
|
|
264
|
+
'clip-path',
|
|
265
|
+
'filter',
|
|
266
|
+
'patternUnits',
|
|
267
|
+
'patternContentUnits',
|
|
268
|
+
'in',
|
|
269
|
+
'in2',
|
|
270
|
+
'result',
|
|
271
|
+
'mode',
|
|
272
|
+
'values',
|
|
273
|
+
'type',
|
|
274
|
+
'stdDeviation',
|
|
275
|
+
'dx',
|
|
276
|
+
'dy',
|
|
277
|
+
],
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
uploadBuffer = Buffer.from(sanitized, 'utf-8');
|
|
281
|
+
finalSize = uploadBuffer.byteLength;
|
|
282
|
+
}
|
|
283
|
+
else if (!skipOptimize && contentType.startsWith('image/')) {
|
|
284
|
+
const result = await optimizeImage(arrayBuffer, originalFilename, contentType);
|
|
285
|
+
uploadBuffer = result.buffer;
|
|
286
|
+
finalFilename = result.filename;
|
|
287
|
+
finalMimeType = result.mimeType;
|
|
288
|
+
finalSize = result.optimizedSize;
|
|
289
|
+
width = result.width || null;
|
|
290
|
+
height = result.height || null;
|
|
291
|
+
blurHash = result.blurHash ?? null;
|
|
292
|
+
savings = result.savings;
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
uploadBuffer = Buffer.from(arrayBuffer);
|
|
296
|
+
}
|
|
297
|
+
const sanitizedName = finalFilename.replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
298
|
+
const storageKey = `actuate/media/${Date.now()}-${sanitizedName}`;
|
|
299
|
+
let publicUrl = '';
|
|
300
|
+
// Prefer the configured platform storage adapter (e.g. platform-vercel,
|
|
301
|
+
// platform-aws, or a consumer-provided one). Falling through to
|
|
302
|
+
// @vercel/blob via dynamic import preserves the legacy behavior for
|
|
303
|
+
// installs that haven't wired up a platform package yet.
|
|
304
|
+
const { getStorageAdapter } = await import('../../storage/index.js');
|
|
305
|
+
const storage = getStorageAdapter();
|
|
306
|
+
if (storage) {
|
|
307
|
+
try {
|
|
308
|
+
publicUrl = await storage.upload(storageKey, uploadBuffer, finalMimeType);
|
|
309
|
+
}
|
|
310
|
+
catch (err) {
|
|
311
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
312
|
+
console.error('[Actuate CMS] Storage adapter upload failed:', err);
|
|
313
|
+
}
|
|
314
|
+
return errorResponse('Storage upload failed', 500);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
try {
|
|
319
|
+
const blob = await importBlobStorage();
|
|
320
|
+
const result = await blob.put(storageKey, uploadBuffer, {
|
|
321
|
+
access: 'public',
|
|
322
|
+
contentType: finalMimeType,
|
|
323
|
+
});
|
|
324
|
+
publicUrl = result.url;
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
publicUrl = `/api/cms/media/file/${storageKey}`;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
const media = await db().media.create({
|
|
331
|
+
data: {
|
|
332
|
+
filename: finalFilename,
|
|
333
|
+
storageKey: publicUrl || storageKey,
|
|
334
|
+
mimeType: finalMimeType,
|
|
335
|
+
fileSize: finalSize,
|
|
336
|
+
width,
|
|
337
|
+
height,
|
|
338
|
+
blurHash,
|
|
339
|
+
uploadedById: auth.session.userId,
|
|
340
|
+
},
|
|
341
|
+
});
|
|
342
|
+
await logEvent({
|
|
343
|
+
event: 'media_uploaded',
|
|
344
|
+
userId: auth.session.userId,
|
|
345
|
+
details: { mediaId: media.id, filename: finalFilename },
|
|
346
|
+
});
|
|
347
|
+
return json({
|
|
348
|
+
data: {
|
|
349
|
+
...media,
|
|
350
|
+
optimization: {
|
|
351
|
+
originalSize,
|
|
352
|
+
optimizedSize: finalSize,
|
|
353
|
+
savings,
|
|
354
|
+
originalFormat: contentType,
|
|
355
|
+
outputFormat: finalMimeType,
|
|
356
|
+
originalSizeFormatted: formatBytes(originalSize),
|
|
357
|
+
optimizedSizeFormatted: formatBytes(finalSize),
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
}, 201);
|
|
361
|
+
}
|
|
362
|
+
catch (err) {
|
|
363
|
+
return internalError(err);
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
router.post('/media/:id/optimize', async (request, params) => {
|
|
367
|
+
try {
|
|
368
|
+
const auth = await requireAuth(request);
|
|
369
|
+
if (auth.error)
|
|
370
|
+
return auth.error;
|
|
371
|
+
const media = await db().media.findUnique({ where: { id: params.id } });
|
|
372
|
+
if (!media)
|
|
373
|
+
return errorResponse('Media not found', 404);
|
|
374
|
+
if (!media.mimeType.startsWith('image/')) {
|
|
375
|
+
return errorResponse('Only images can be optimized', 400);
|
|
376
|
+
}
|
|
377
|
+
if (media.mimeType === 'image/webp') {
|
|
378
|
+
return json({
|
|
379
|
+
data: { ...media, optimization: { alreadyOptimized: true, savings: 0 } },
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
if (!isAllowedStorageUrl(media.storageKey)) {
|
|
383
|
+
return errorResponse('Invalid media storage URL', 400);
|
|
384
|
+
}
|
|
385
|
+
let fileBuffer;
|
|
386
|
+
try {
|
|
387
|
+
const response = await fetch(media.storageKey);
|
|
388
|
+
if (!response.ok)
|
|
389
|
+
throw new Error('Failed to fetch media file');
|
|
390
|
+
fileBuffer = Buffer.from(await response.arrayBuffer());
|
|
391
|
+
}
|
|
392
|
+
catch {
|
|
393
|
+
return errorResponse('Could not retrieve the original file for optimization', 500);
|
|
394
|
+
}
|
|
395
|
+
const result = await optimizeImage(fileBuffer, media.filename, media.mimeType);
|
|
396
|
+
if (result.savings <= 0) {
|
|
397
|
+
return json({
|
|
398
|
+
data: { ...media, optimization: { alreadyOptimized: true, savings: 0 } },
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
// Upload the optimized version
|
|
402
|
+
const sanitizedName = result.filename.replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
403
|
+
const newStorageKey = `actuate/media/${Date.now()}-${sanitizedName}`;
|
|
404
|
+
let newPublicUrl = '';
|
|
405
|
+
try {
|
|
406
|
+
const blob = await importBlobStorage();
|
|
407
|
+
const uploadResult = await blob.put(newStorageKey, result.buffer, {
|
|
408
|
+
access: 'public',
|
|
409
|
+
contentType: result.mimeType,
|
|
410
|
+
});
|
|
411
|
+
newPublicUrl = uploadResult.url;
|
|
412
|
+
// Delete the old blob
|
|
413
|
+
try {
|
|
414
|
+
await blob.del(media.storageKey);
|
|
415
|
+
}
|
|
416
|
+
catch {
|
|
417
|
+
/* best-effort */
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
catch {
|
|
421
|
+
newPublicUrl = `/api/cms/media/file/${newStorageKey}`;
|
|
422
|
+
}
|
|
423
|
+
const updated = await db().media.update({
|
|
424
|
+
where: { id: params.id },
|
|
425
|
+
data: {
|
|
426
|
+
filename: result.filename,
|
|
427
|
+
storageKey: newPublicUrl || newStorageKey,
|
|
428
|
+
mimeType: result.mimeType,
|
|
429
|
+
fileSize: result.optimizedSize,
|
|
430
|
+
width: result.width || undefined,
|
|
431
|
+
height: result.height || undefined,
|
|
432
|
+
blurHash: result.blurHash ?? undefined,
|
|
433
|
+
},
|
|
434
|
+
});
|
|
435
|
+
return json({
|
|
436
|
+
data: {
|
|
437
|
+
...updated,
|
|
438
|
+
optimization: {
|
|
439
|
+
originalSize: result.originalSize,
|
|
440
|
+
optimizedSize: result.optimizedSize,
|
|
441
|
+
savings: result.savings,
|
|
442
|
+
originalFormat: media.mimeType,
|
|
443
|
+
outputFormat: result.mimeType,
|
|
444
|
+
originalSizeFormatted: formatBytes(result.originalSize),
|
|
445
|
+
optimizedSizeFormatted: formatBytes(result.optimizedSize),
|
|
446
|
+
},
|
|
447
|
+
},
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
catch (err) {
|
|
451
|
+
return internalError(err);
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
router.put('/media/:id', async (request, params) => {
|
|
455
|
+
try {
|
|
456
|
+
const auth = await requireAuth(request);
|
|
457
|
+
if (auth.error)
|
|
458
|
+
return auth.error;
|
|
459
|
+
const scopeErr = requireMediaScope(auth.session);
|
|
460
|
+
if (scopeErr)
|
|
461
|
+
return scopeErr;
|
|
462
|
+
if (!auth.session.apiKey) {
|
|
463
|
+
const roleErr = requireRole(auth.session.role, WRITE_ROLES);
|
|
464
|
+
if (roleErr)
|
|
465
|
+
return roleErr;
|
|
466
|
+
}
|
|
467
|
+
const body = (await request.json());
|
|
468
|
+
const updated = await db().media.update({
|
|
469
|
+
where: { id: params.id },
|
|
470
|
+
data: {
|
|
471
|
+
...(body.alt !== undefined ? { altText: body.alt } : {}),
|
|
472
|
+
...(body.title !== undefined ? { title: body.title } : {}),
|
|
473
|
+
...(body.filename !== undefined ? { filename: body.filename } : {}),
|
|
474
|
+
...(body.focalX !== undefined ? { focalPointX: body.focalX } : {}),
|
|
475
|
+
...(body.focalY !== undefined ? { focalPointY: body.focalY } : {}),
|
|
476
|
+
},
|
|
477
|
+
});
|
|
478
|
+
return json({ data: updated });
|
|
479
|
+
}
|
|
480
|
+
catch (err) {
|
|
481
|
+
return internalError(err);
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
router.delete('/media/:id', async (request, params) => {
|
|
485
|
+
try {
|
|
486
|
+
const auth = await requireAuth(request);
|
|
487
|
+
if (auth.error)
|
|
488
|
+
return auth.error;
|
|
489
|
+
const scopeErr = requireMediaScope(auth.session);
|
|
490
|
+
if (scopeErr)
|
|
491
|
+
return scopeErr;
|
|
492
|
+
if (!auth.session.apiKey) {
|
|
493
|
+
const roleErr = requireRole(auth.session.role, WRITE_ROLES);
|
|
494
|
+
if (roleErr)
|
|
495
|
+
return roleErr;
|
|
496
|
+
}
|
|
497
|
+
const media = await db().media.findUnique({ where: { id: params.id } });
|
|
498
|
+
if (!media) {
|
|
499
|
+
return errorResponse('Media not found', 404);
|
|
500
|
+
}
|
|
501
|
+
try {
|
|
502
|
+
const blob = await importBlobStorage();
|
|
503
|
+
await blob.del(media.storageKey);
|
|
504
|
+
}
|
|
505
|
+
catch {
|
|
506
|
+
// Storage delete failed or not available
|
|
507
|
+
}
|
|
508
|
+
await db().media.delete({ where: { id: params.id } });
|
|
509
|
+
await logEvent({
|
|
510
|
+
event: 'media_deleted',
|
|
511
|
+
userId: auth.session.userId,
|
|
512
|
+
details: { mediaId: params.id, filename: media.filename },
|
|
513
|
+
});
|
|
514
|
+
return json({ data: { success: true } });
|
|
515
|
+
}
|
|
516
|
+
catch (err) {
|
|
517
|
+
return internalError(err);
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
//# sourceMappingURL=media.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media.js","sourceRoot":"","sources":["../../../src/api/routes/media.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meta.d.ts","sourceRoot":"","sources":["../../../src/api/routes/meta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAO7C,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA4F1D"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { summarizeCollectionFields } from '../collection-introspection.js';
|
|
2
|
+
import { generateToken as generateCsrfToken } from '../../security/csrf.js';
|
|
3
|
+
import { generateOpenAPISpec } from '../openapi.js';
|
|
4
|
+
import { getActuateConfig } from '../../config/runtime.js';
|
|
5
|
+
import { json, errorResponse } from '../route-helpers.js';
|
|
6
|
+
export function registerMetaRoutes(router) {
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// CSRF token endpoint
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
router.get('/auth/csrf', async () => {
|
|
11
|
+
const token = await generateCsrfToken();
|
|
12
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
13
|
+
const cookieFlags = [`actuate_csrf=${token}`, 'Path=/', 'SameSite=Lax', 'Max-Age=86400'];
|
|
14
|
+
if (isProduction)
|
|
15
|
+
cookieFlags.push('Secure');
|
|
16
|
+
const response = json({ data: { token } });
|
|
17
|
+
response.headers.set('Set-Cookie', cookieFlags.join('; '));
|
|
18
|
+
return response;
|
|
19
|
+
});
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// OpenAPI spec
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
router.get('/openapi.json', async () => {
|
|
24
|
+
const config = getActuateConfig();
|
|
25
|
+
if (!config)
|
|
26
|
+
return errorResponse('CMS not configured', 500);
|
|
27
|
+
const spec = generateOpenAPISpec(config);
|
|
28
|
+
return new Response(JSON.stringify(spec, null, 2), {
|
|
29
|
+
status: 200,
|
|
30
|
+
headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' },
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Collection discovery (Phase 7 PR F)
|
|
35
|
+
//
|
|
36
|
+
// Lightweight enumeration sourced directly from `getActuateConfig()` so
|
|
37
|
+
// agents and the MCP server's `list_collections` tool don't depend on
|
|
38
|
+
// OpenAPI spec generation succeeding. OpenAPI is still the source of
|
|
39
|
+
// truth for full per-field JSON Schemas; this endpoint trades depth
|
|
40
|
+
// for reliability and surfaces just enough metadata for the agent to
|
|
41
|
+
// plan a follow-up call (create_document, list_documents, etc.).
|
|
42
|
+
//
|
|
43
|
+
// Auth: unauthenticated, like /openapi.json — the response contains
|
|
44
|
+
// only schema-shape metadata that is already public via OpenAPI.
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
router.get('/collections', async () => {
|
|
47
|
+
const config = getActuateConfig();
|
|
48
|
+
if (!config)
|
|
49
|
+
return errorResponse('CMS not configured', 500);
|
|
50
|
+
const collections = Object.entries(config.collections ?? {}).map(([slug, def]) => {
|
|
51
|
+
// Reduce each field to `{ name, type, required }` plus, where present,
|
|
52
|
+
// the relationship target, select options, and (for `array`/`blocks`)
|
|
53
|
+
// the nested subfield schema — enough for the agent to plan a deep
|
|
54
|
+
// create/update AND resolve relationship fields (WS-B4 + WS-C C2)
|
|
55
|
+
// without sending the full validator. See collection-introspection.ts.
|
|
56
|
+
const fieldSummary = summarizeCollectionFields(def?.fields);
|
|
57
|
+
return {
|
|
58
|
+
slug,
|
|
59
|
+
labels: def?.labels ?? { singular: slug, plural: slug },
|
|
60
|
+
type: def?.type ?? 'page',
|
|
61
|
+
urlPrefix: def?.urlPrefix ?? slug,
|
|
62
|
+
hidden: def?.admin?.hidden === true,
|
|
63
|
+
// Surface the admin metadata the Posts area needs to render
|
|
64
|
+
// type tabs / cards (description, icon, accent color, group).
|
|
65
|
+
// None are sensitive — they all already live on the public
|
|
66
|
+
// collection definition the admin client receives via props.
|
|
67
|
+
description: def?.admin?.description ?? null,
|
|
68
|
+
icon: def?.admin?.icon ?? null,
|
|
69
|
+
color: def?.admin?.color ?? null,
|
|
70
|
+
group: def?.admin?.group ?? null,
|
|
71
|
+
fieldCount: fieldSummary.length,
|
|
72
|
+
fields: fieldSummary,
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
return json({ data: collections });
|
|
76
|
+
});
|
|
77
|
+
router.get('/docs', async () => {
|
|
78
|
+
const html = `<!DOCTYPE html>
|
|
79
|
+
<html>
|
|
80
|
+
<head>
|
|
81
|
+
<title>Actuate CMS API Reference</title>
|
|
82
|
+
<meta charset="utf-8" />
|
|
83
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
84
|
+
</head>
|
|
85
|
+
<body>
|
|
86
|
+
<script id="api-reference" data-url="/api/cms/openapi.json"></script>
|
|
87
|
+
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
|
|
88
|
+
</body>
|
|
89
|
+
</html>`;
|
|
90
|
+
return new Response(html, {
|
|
91
|
+
status: 200,
|
|
92
|
+
headers: { 'Content-Type': 'text/html; charset=utf-8' },
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=meta.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meta.js","sourceRoot":"","sources":["../../../src/api/routes/meta.ts"],"names":[],"mappings":"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"}
|