@momentumcms/server-analog 0.4.1 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/index.cjs +96 -63
- package/index.js +96 -63
- package/package.json +3 -2
- package/src/lib/server-analog.d.ts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## 0.5.0 (2026-02-23)
|
|
2
|
+
|
|
3
|
+
This was a version bump only for server-analog to align it with other projects, there were no code changes.
|
|
4
|
+
|
|
5
|
+
## 0.4.1 (2026-02-22)
|
|
6
|
+
|
|
7
|
+
This was a version bump only for server-analog to align it with other projects, there were no code changes.
|
|
8
|
+
|
|
1
9
|
## 0.4.0 (2026-02-22)
|
|
2
10
|
|
|
3
11
|
### 🚀 Features
|
package/index.cjs
CHANGED
|
@@ -37,6 +37,35 @@ var init_storage_types = __esm({
|
|
|
37
37
|
}
|
|
38
38
|
});
|
|
39
39
|
|
|
40
|
+
// libs/storage/src/lib/storage-utils.ts
|
|
41
|
+
function getExtensionFromMimeType(mimeType) {
|
|
42
|
+
return MIME_TO_EXT[mimeType] ?? "";
|
|
43
|
+
}
|
|
44
|
+
var MIME_TO_EXT;
|
|
45
|
+
var init_storage_utils = __esm({
|
|
46
|
+
"libs/storage/src/lib/storage-utils.ts"() {
|
|
47
|
+
"use strict";
|
|
48
|
+
MIME_TO_EXT = {
|
|
49
|
+
"image/jpeg": ".jpg",
|
|
50
|
+
"image/png": ".png",
|
|
51
|
+
"image/gif": ".gif",
|
|
52
|
+
"image/webp": ".webp",
|
|
53
|
+
"image/svg+xml": ".svg",
|
|
54
|
+
"application/pdf": ".pdf",
|
|
55
|
+
"application/json": ".json",
|
|
56
|
+
"text/plain": ".txt",
|
|
57
|
+
"text/html": ".html",
|
|
58
|
+
"text/css": ".css",
|
|
59
|
+
"application/javascript": ".js",
|
|
60
|
+
"video/mp4": ".mp4",
|
|
61
|
+
"video/webm": ".webm",
|
|
62
|
+
"audio/mpeg": ".mp3",
|
|
63
|
+
"audio/wav": ".wav",
|
|
64
|
+
"application/zip": ".zip"
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
40
69
|
// libs/storage/src/lib/storage-local.ts
|
|
41
70
|
function localStorageAdapter(options) {
|
|
42
71
|
const { directory, baseUrl } = options;
|
|
@@ -103,27 +132,6 @@ function localStorageAdapter(options) {
|
|
|
103
132
|
}
|
|
104
133
|
};
|
|
105
134
|
}
|
|
106
|
-
function getExtensionFromMimeType(mimeType) {
|
|
107
|
-
const mimeToExt = {
|
|
108
|
-
"image/jpeg": ".jpg",
|
|
109
|
-
"image/png": ".png",
|
|
110
|
-
"image/gif": ".gif",
|
|
111
|
-
"image/webp": ".webp",
|
|
112
|
-
"image/svg+xml": ".svg",
|
|
113
|
-
"application/pdf": ".pdf",
|
|
114
|
-
"application/json": ".json",
|
|
115
|
-
"text/plain": ".txt",
|
|
116
|
-
"text/html": ".html",
|
|
117
|
-
"text/css": ".css",
|
|
118
|
-
"application/javascript": ".js",
|
|
119
|
-
"video/mp4": ".mp4",
|
|
120
|
-
"video/webm": ".webm",
|
|
121
|
-
"audio/mpeg": ".mp3",
|
|
122
|
-
"audio/wav": ".wav",
|
|
123
|
-
"application/zip": ".zip"
|
|
124
|
-
};
|
|
125
|
-
return mimeToExt[mimeType] ?? "";
|
|
126
|
-
}
|
|
127
135
|
var import_node_fs, import_node_path, import_node_crypto2;
|
|
128
136
|
var init_storage_local = __esm({
|
|
129
137
|
"libs/storage/src/lib/storage-local.ts"() {
|
|
@@ -131,6 +139,7 @@ var init_storage_local = __esm({
|
|
|
131
139
|
import_node_fs = require("node:fs");
|
|
132
140
|
import_node_path = require("node:path");
|
|
133
141
|
import_node_crypto2 = require("node:crypto");
|
|
142
|
+
init_storage_utils();
|
|
134
143
|
}
|
|
135
144
|
});
|
|
136
145
|
|
|
@@ -192,7 +201,7 @@ function s3StorageAdapter(options) {
|
|
|
192
201
|
return {
|
|
193
202
|
async upload(file, uploadOptions) {
|
|
194
203
|
const s3 = await getClient();
|
|
195
|
-
const ext = (0, import_node_path2.extname)(file.originalName) ||
|
|
204
|
+
const ext = (0, import_node_path2.extname)(file.originalName) || getExtensionFromMimeType(file.mimeType);
|
|
196
205
|
const filename = uploadOptions?.filename ? `${uploadOptions.filename}${ext}` : `${(0, import_node_crypto3.randomUUID)()}${ext}`;
|
|
197
206
|
const key = uploadOptions?.directory ? `${uploadOptions.directory}/${filename}` : filename;
|
|
198
207
|
await s3.send(
|
|
@@ -281,33 +290,13 @@ function s3StorageAdapter(options) {
|
|
|
281
290
|
}
|
|
282
291
|
};
|
|
283
292
|
}
|
|
284
|
-
function getExtensionFromMimeType2(mimeType) {
|
|
285
|
-
const mimeToExt = {
|
|
286
|
-
"image/jpeg": ".jpg",
|
|
287
|
-
"image/png": ".png",
|
|
288
|
-
"image/gif": ".gif",
|
|
289
|
-
"image/webp": ".webp",
|
|
290
|
-
"image/svg+xml": ".svg",
|
|
291
|
-
"application/pdf": ".pdf",
|
|
292
|
-
"application/json": ".json",
|
|
293
|
-
"text/plain": ".txt",
|
|
294
|
-
"text/html": ".html",
|
|
295
|
-
"text/css": ".css",
|
|
296
|
-
"application/javascript": ".js",
|
|
297
|
-
"video/mp4": ".mp4",
|
|
298
|
-
"video/webm": ".webm",
|
|
299
|
-
"audio/mpeg": ".mp3",
|
|
300
|
-
"audio/wav": ".wav",
|
|
301
|
-
"application/zip": ".zip"
|
|
302
|
-
};
|
|
303
|
-
return mimeToExt[mimeType] ?? "";
|
|
304
|
-
}
|
|
305
293
|
var import_node_crypto3, import_node_path2, S3Client, PutObjectCommand, DeleteObjectCommand, HeadObjectCommand, GetObjectCommand, getSignedUrl;
|
|
306
294
|
var init_storage_s3 = __esm({
|
|
307
295
|
"libs/storage/src/lib/storage-s3.ts"() {
|
|
308
296
|
"use strict";
|
|
309
297
|
import_node_crypto3 = require("node:crypto");
|
|
310
298
|
import_node_path2 = require("node:path");
|
|
299
|
+
init_storage_utils();
|
|
311
300
|
}
|
|
312
301
|
});
|
|
313
302
|
|
|
@@ -328,7 +317,7 @@ function detectMimeType(buffer) {
|
|
|
328
317
|
if (match) {
|
|
329
318
|
if (sig.bytes[0] === 82 && sig.bytes[1] === 73) {
|
|
330
319
|
if (buffer.length >= 12) {
|
|
331
|
-
const formatId = buffer.
|
|
320
|
+
const formatId = String.fromCharCode(...buffer.subarray(8, 12));
|
|
332
321
|
if (formatId === "WEBP") {
|
|
333
322
|
return "image/webp";
|
|
334
323
|
}
|
|
@@ -341,7 +330,7 @@ function detectMimeType(buffer) {
|
|
|
341
330
|
}
|
|
342
331
|
}
|
|
343
332
|
if (sig.mimeType === "video/mp4" && buffer.length >= 8) {
|
|
344
|
-
const boxType = buffer.
|
|
333
|
+
const boxType = String.fromCharCode(...buffer.subarray(4, 8));
|
|
345
334
|
if (boxType === "ftyp") {
|
|
346
335
|
return "video/mp4";
|
|
347
336
|
}
|
|
@@ -350,7 +339,7 @@ function detectMimeType(buffer) {
|
|
|
350
339
|
}
|
|
351
340
|
}
|
|
352
341
|
if (isTextContent(buffer)) {
|
|
353
|
-
const text2 = buffer.
|
|
342
|
+
const text2 = new TextDecoder().decode(buffer.subarray(0, Math.min(buffer.length, 1e3)));
|
|
354
343
|
if (text2.trim().startsWith("{") || text2.trim().startsWith("[")) {
|
|
355
344
|
return "application/json";
|
|
356
345
|
}
|
|
@@ -524,6 +513,7 @@ var init_mime_validator = __esm({
|
|
|
524
513
|
var src_exports = {};
|
|
525
514
|
__export(src_exports, {
|
|
526
515
|
detectMimeType: () => detectMimeType,
|
|
516
|
+
getExtensionFromMimeType: () => getExtensionFromMimeType,
|
|
527
517
|
isMimeTypeAllowed: () => isMimeTypeAllowed,
|
|
528
518
|
localStorageAdapter: () => localStorageAdapter,
|
|
529
519
|
mimeTypeMatches: () => mimeTypeMatches,
|
|
@@ -536,6 +526,7 @@ var init_src = __esm({
|
|
|
536
526
|
init_storage_types();
|
|
537
527
|
init_storage_local();
|
|
538
528
|
init_storage_s3();
|
|
529
|
+
init_storage_utils();
|
|
539
530
|
init_mime_validator();
|
|
540
531
|
}
|
|
541
532
|
});
|
|
@@ -4344,11 +4335,11 @@ SwaggerUIBundle({
|
|
|
4344
4335
|
|
|
4345
4336
|
// libs/server-core/src/lib/preview-renderer.ts
|
|
4346
4337
|
function renderPreviewHTML(options) {
|
|
4347
|
-
const { doc, collection } = options;
|
|
4338
|
+
const { doc, collection, customFieldRenderers } = options;
|
|
4348
4339
|
const titleField = collection.admin?.useAsTitle ?? "id";
|
|
4349
4340
|
const title = escapeHtml(String(doc[titleField] ?? doc["id"] ?? "Untitled"));
|
|
4350
4341
|
const fields = collection.fields ?? [];
|
|
4351
|
-
const fieldHtml = fields.filter((f) => !isHiddenField(f) && !isLayoutField(f) && f.name !== titleField).map((f) => renderField(f, doc)).filter(Boolean).join("\n");
|
|
4342
|
+
const fieldHtml = fields.filter((f) => !isHiddenField(f) && !isLayoutField(f) && f.name !== titleField).map((f) => renderField(f, doc, customFieldRenderers)).filter(Boolean).join("\n");
|
|
4352
4343
|
const richTextFields = fields.filter((f) => f.type === "richText").map((f) => f.name);
|
|
4353
4344
|
return `<!DOCTYPE html>
|
|
4354
4345
|
<html lang="en">
|
|
@@ -4382,6 +4373,7 @@ ${fieldHtml}
|
|
|
4382
4373
|
(function(){
|
|
4383
4374
|
var richTextFields=${JSON.stringify(richTextFields)};
|
|
4384
4375
|
window.addEventListener('message',function(e){
|
|
4376
|
+
if(e.origin!==window.location.origin)return;
|
|
4385
4377
|
if(!e.data||e.data.type!=='momentum-preview-update')return;
|
|
4386
4378
|
var d=e.data.data;if(!d)return;
|
|
4387
4379
|
document.querySelectorAll('[data-field]').forEach(function(el){
|
|
@@ -4402,16 +4394,20 @@ if(titleEl){var tf=titleEl.getAttribute('data-field');if(d[tf]!==undefined)title
|
|
|
4402
4394
|
</body>
|
|
4403
4395
|
</html>`;
|
|
4404
4396
|
}
|
|
4405
|
-
function renderField(field, doc) {
|
|
4397
|
+
function renderField(field, doc, customRenderers) {
|
|
4406
4398
|
const value = doc[field.name];
|
|
4407
4399
|
if (value === void 0 || value === null) {
|
|
4408
4400
|
return renderFieldWrapper(field, "");
|
|
4409
4401
|
}
|
|
4402
|
+
const editorKey = field.admin?.editor;
|
|
4403
|
+
if (editorKey && customRenderers?.[editorKey]) {
|
|
4404
|
+
return renderFieldWrapper(field, customRenderers[editorKey](value, field));
|
|
4405
|
+
}
|
|
4410
4406
|
switch (field.type) {
|
|
4411
4407
|
case "richText":
|
|
4412
4408
|
return renderFieldWrapper(
|
|
4413
4409
|
field,
|
|
4414
|
-
`<div class="field-value rich-text" data-field="${escapeHtml(field.name)}">${String(value)}</div>`
|
|
4410
|
+
`<div class="field-value rich-text" data-field="${escapeHtml(field.name)}">${escapeHtml(String(value))}</div>`
|
|
4415
4411
|
);
|
|
4416
4412
|
case "checkbox":
|
|
4417
4413
|
return renderFieldWrapper(
|
|
@@ -4742,6 +4738,21 @@ function coerceCsvValue(value, fieldType) {
|
|
|
4742
4738
|
}
|
|
4743
4739
|
|
|
4744
4740
|
// libs/server-analog/src/lib/server-analog.ts
|
|
4741
|
+
function getEmailBuilderFieldName(collection) {
|
|
4742
|
+
const field = collection.fields.find(
|
|
4743
|
+
(f) => f.type === "json" && f.admin?.editor === "email-builder"
|
|
4744
|
+
);
|
|
4745
|
+
return field?.name;
|
|
4746
|
+
}
|
|
4747
|
+
async function renderEmailPreviewHTML(doc, blocksFieldName) {
|
|
4748
|
+
const emailPkg = "@momentumcms/email";
|
|
4749
|
+
const { renderEmailFromBlocks } = await import(emailPkg);
|
|
4750
|
+
const blocks2 = doc[blocksFieldName];
|
|
4751
|
+
if (!Array.isArray(blocks2) || blocks2.length === 0) {
|
|
4752
|
+
return '<html><body style="display:flex;align-items:center;justify-content:center;min-height:100vh;color:#666;font-family:sans-serif"><p>No email blocks yet.</p></body></html>';
|
|
4753
|
+
}
|
|
4754
|
+
return renderEmailFromBlocks({ blocks: blocks2 });
|
|
4755
|
+
}
|
|
4745
4756
|
function nestBracketParams(flat) {
|
|
4746
4757
|
const result = {};
|
|
4747
4758
|
for (const [key, value] of Object.entries(flat)) {
|
|
@@ -5426,26 +5437,47 @@ function createComprehensiveMomentumHandler(config) {
|
|
|
5426
5437
|
};
|
|
5427
5438
|
}
|
|
5428
5439
|
}
|
|
5429
|
-
if (seg2 === "preview" && seg1 && method === "GET") {
|
|
5440
|
+
if (seg2 === "preview" && seg1 && (method === "GET" || method === "POST")) {
|
|
5441
|
+
if (!user) {
|
|
5442
|
+
utils.setResponseStatus(event, 401);
|
|
5443
|
+
return { error: "Authentication required to access preview" };
|
|
5444
|
+
}
|
|
5430
5445
|
try {
|
|
5431
5446
|
const collectionSlug2 = seg0;
|
|
5432
5447
|
const docId = seg1;
|
|
5433
|
-
const contextApi = getContextualAPI(user);
|
|
5434
|
-
const doc = await contextApi.collection(collectionSlug2).findById(docId);
|
|
5435
|
-
if (!doc) {
|
|
5436
|
-
utils.setResponseStatus(event, 404);
|
|
5437
|
-
return { error: "Document not found" };
|
|
5438
|
-
}
|
|
5439
5448
|
const collectionConfig = config.collections.find((c) => c.slug === collectionSlug2);
|
|
5440
5449
|
if (!collectionConfig) {
|
|
5441
5450
|
utils.setResponseStatus(event, 404);
|
|
5442
5451
|
return { error: "Collection not found" };
|
|
5443
5452
|
}
|
|
5444
|
-
const
|
|
5445
|
-
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5453
|
+
const accessFn = collectionConfig.access?.read;
|
|
5454
|
+
if (accessFn) {
|
|
5455
|
+
const allowed = await Promise.resolve(accessFn({ req: { user } }));
|
|
5456
|
+
if (!allowed) {
|
|
5457
|
+
utils.setResponseStatus(event, 403);
|
|
5458
|
+
return { error: "Access denied" };
|
|
5459
|
+
}
|
|
5460
|
+
}
|
|
5461
|
+
let docRecord;
|
|
5462
|
+
if (method === "POST") {
|
|
5463
|
+
const body2 = await safeReadBody(event, utils, method);
|
|
5464
|
+
if (body2["data"] && typeof body2["data"] === "object") {
|
|
5465
|
+
docRecord = body2["data"];
|
|
5466
|
+
} else {
|
|
5467
|
+
utils.setResponseStatus(event, 400);
|
|
5468
|
+
return { error: "POST preview requires { data: ... } body" };
|
|
5469
|
+
}
|
|
5470
|
+
} else {
|
|
5471
|
+
const contextApi = getContextualAPI(user);
|
|
5472
|
+
const doc = await contextApi.collection(collectionSlug2).findById(docId);
|
|
5473
|
+
if (!doc) {
|
|
5474
|
+
utils.setResponseStatus(event, 404);
|
|
5475
|
+
return { error: "Document not found" };
|
|
5476
|
+
}
|
|
5477
|
+
docRecord = doc;
|
|
5478
|
+
}
|
|
5479
|
+
const emailField = getEmailBuilderFieldName(collectionConfig);
|
|
5480
|
+
const html = emailField ? await renderEmailPreviewHTML(docRecord, emailField) : renderPreviewHTML({ doc: docRecord, collection: collectionConfig });
|
|
5449
5481
|
utils.setResponseHeader(event, "Content-Type", "text/html; charset=utf-8");
|
|
5450
5482
|
return utils.send(event, html);
|
|
5451
5483
|
} catch (error) {
|
|
@@ -5714,12 +5746,13 @@ function createComprehensiveMomentumHandler(config) {
|
|
|
5714
5746
|
fields[field.name] = field.data.toString("utf-8");
|
|
5715
5747
|
}
|
|
5716
5748
|
}
|
|
5749
|
+
const collectionUpload = postUploadCol.upload ?? {};
|
|
5717
5750
|
const uploadRequest = {
|
|
5718
5751
|
file,
|
|
5719
5752
|
user,
|
|
5720
5753
|
fields,
|
|
5721
5754
|
collectionSlug: seg0,
|
|
5722
|
-
collectionUpload
|
|
5755
|
+
collectionUpload
|
|
5723
5756
|
};
|
|
5724
5757
|
const response2 = await handleCollectionUpload(uploadConfig, uploadRequest);
|
|
5725
5758
|
utils.setResponseStatus(event, response2.status);
|
package/index.js
CHANGED
|
@@ -15,6 +15,35 @@ var init_storage_types = __esm({
|
|
|
15
15
|
}
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
+
// libs/storage/src/lib/storage-utils.ts
|
|
19
|
+
function getExtensionFromMimeType(mimeType) {
|
|
20
|
+
return MIME_TO_EXT[mimeType] ?? "";
|
|
21
|
+
}
|
|
22
|
+
var MIME_TO_EXT;
|
|
23
|
+
var init_storage_utils = __esm({
|
|
24
|
+
"libs/storage/src/lib/storage-utils.ts"() {
|
|
25
|
+
"use strict";
|
|
26
|
+
MIME_TO_EXT = {
|
|
27
|
+
"image/jpeg": ".jpg",
|
|
28
|
+
"image/png": ".png",
|
|
29
|
+
"image/gif": ".gif",
|
|
30
|
+
"image/webp": ".webp",
|
|
31
|
+
"image/svg+xml": ".svg",
|
|
32
|
+
"application/pdf": ".pdf",
|
|
33
|
+
"application/json": ".json",
|
|
34
|
+
"text/plain": ".txt",
|
|
35
|
+
"text/html": ".html",
|
|
36
|
+
"text/css": ".css",
|
|
37
|
+
"application/javascript": ".js",
|
|
38
|
+
"video/mp4": ".mp4",
|
|
39
|
+
"video/webm": ".webm",
|
|
40
|
+
"audio/mpeg": ".mp3",
|
|
41
|
+
"audio/wav": ".wav",
|
|
42
|
+
"application/zip": ".zip"
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
18
47
|
// libs/storage/src/lib/storage-local.ts
|
|
19
48
|
import { existsSync, mkdirSync, writeFileSync, unlinkSync, readFileSync, lstatSync } from "node:fs";
|
|
20
49
|
import { join, extname, resolve, normalize } from "node:path";
|
|
@@ -84,30 +113,10 @@ function localStorageAdapter(options) {
|
|
|
84
113
|
}
|
|
85
114
|
};
|
|
86
115
|
}
|
|
87
|
-
function getExtensionFromMimeType(mimeType) {
|
|
88
|
-
const mimeToExt = {
|
|
89
|
-
"image/jpeg": ".jpg",
|
|
90
|
-
"image/png": ".png",
|
|
91
|
-
"image/gif": ".gif",
|
|
92
|
-
"image/webp": ".webp",
|
|
93
|
-
"image/svg+xml": ".svg",
|
|
94
|
-
"application/pdf": ".pdf",
|
|
95
|
-
"application/json": ".json",
|
|
96
|
-
"text/plain": ".txt",
|
|
97
|
-
"text/html": ".html",
|
|
98
|
-
"text/css": ".css",
|
|
99
|
-
"application/javascript": ".js",
|
|
100
|
-
"video/mp4": ".mp4",
|
|
101
|
-
"video/webm": ".webm",
|
|
102
|
-
"audio/mpeg": ".mp3",
|
|
103
|
-
"audio/wav": ".wav",
|
|
104
|
-
"application/zip": ".zip"
|
|
105
|
-
};
|
|
106
|
-
return mimeToExt[mimeType] ?? "";
|
|
107
|
-
}
|
|
108
116
|
var init_storage_local = __esm({
|
|
109
117
|
"libs/storage/src/lib/storage-local.ts"() {
|
|
110
118
|
"use strict";
|
|
119
|
+
init_storage_utils();
|
|
111
120
|
}
|
|
112
121
|
});
|
|
113
122
|
|
|
@@ -171,7 +180,7 @@ function s3StorageAdapter(options) {
|
|
|
171
180
|
return {
|
|
172
181
|
async upload(file, uploadOptions) {
|
|
173
182
|
const s3 = await getClient();
|
|
174
|
-
const ext = extname2(file.originalName) ||
|
|
183
|
+
const ext = extname2(file.originalName) || getExtensionFromMimeType(file.mimeType);
|
|
175
184
|
const filename = uploadOptions?.filename ? `${uploadOptions.filename}${ext}` : `${randomUUID2()}${ext}`;
|
|
176
185
|
const key = uploadOptions?.directory ? `${uploadOptions.directory}/${filename}` : filename;
|
|
177
186
|
await s3.send(
|
|
@@ -260,31 +269,11 @@ function s3StorageAdapter(options) {
|
|
|
260
269
|
}
|
|
261
270
|
};
|
|
262
271
|
}
|
|
263
|
-
function getExtensionFromMimeType2(mimeType) {
|
|
264
|
-
const mimeToExt = {
|
|
265
|
-
"image/jpeg": ".jpg",
|
|
266
|
-
"image/png": ".png",
|
|
267
|
-
"image/gif": ".gif",
|
|
268
|
-
"image/webp": ".webp",
|
|
269
|
-
"image/svg+xml": ".svg",
|
|
270
|
-
"application/pdf": ".pdf",
|
|
271
|
-
"application/json": ".json",
|
|
272
|
-
"text/plain": ".txt",
|
|
273
|
-
"text/html": ".html",
|
|
274
|
-
"text/css": ".css",
|
|
275
|
-
"application/javascript": ".js",
|
|
276
|
-
"video/mp4": ".mp4",
|
|
277
|
-
"video/webm": ".webm",
|
|
278
|
-
"audio/mpeg": ".mp3",
|
|
279
|
-
"audio/wav": ".wav",
|
|
280
|
-
"application/zip": ".zip"
|
|
281
|
-
};
|
|
282
|
-
return mimeToExt[mimeType] ?? "";
|
|
283
|
-
}
|
|
284
272
|
var S3Client, PutObjectCommand, DeleteObjectCommand, HeadObjectCommand, GetObjectCommand, getSignedUrl;
|
|
285
273
|
var init_storage_s3 = __esm({
|
|
286
274
|
"libs/storage/src/lib/storage-s3.ts"() {
|
|
287
275
|
"use strict";
|
|
276
|
+
init_storage_utils();
|
|
288
277
|
}
|
|
289
278
|
});
|
|
290
279
|
|
|
@@ -305,7 +294,7 @@ function detectMimeType(buffer) {
|
|
|
305
294
|
if (match) {
|
|
306
295
|
if (sig.bytes[0] === 82 && sig.bytes[1] === 73) {
|
|
307
296
|
if (buffer.length >= 12) {
|
|
308
|
-
const formatId = buffer.
|
|
297
|
+
const formatId = String.fromCharCode(...buffer.subarray(8, 12));
|
|
309
298
|
if (formatId === "WEBP") {
|
|
310
299
|
return "image/webp";
|
|
311
300
|
}
|
|
@@ -318,7 +307,7 @@ function detectMimeType(buffer) {
|
|
|
318
307
|
}
|
|
319
308
|
}
|
|
320
309
|
if (sig.mimeType === "video/mp4" && buffer.length >= 8) {
|
|
321
|
-
const boxType = buffer.
|
|
310
|
+
const boxType = String.fromCharCode(...buffer.subarray(4, 8));
|
|
322
311
|
if (boxType === "ftyp") {
|
|
323
312
|
return "video/mp4";
|
|
324
313
|
}
|
|
@@ -327,7 +316,7 @@ function detectMimeType(buffer) {
|
|
|
327
316
|
}
|
|
328
317
|
}
|
|
329
318
|
if (isTextContent(buffer)) {
|
|
330
|
-
const text2 = buffer.
|
|
319
|
+
const text2 = new TextDecoder().decode(buffer.subarray(0, Math.min(buffer.length, 1e3)));
|
|
331
320
|
if (text2.trim().startsWith("{") || text2.trim().startsWith("[")) {
|
|
332
321
|
return "application/json";
|
|
333
322
|
}
|
|
@@ -501,6 +490,7 @@ var init_mime_validator = __esm({
|
|
|
501
490
|
var src_exports = {};
|
|
502
491
|
__export(src_exports, {
|
|
503
492
|
detectMimeType: () => detectMimeType,
|
|
493
|
+
getExtensionFromMimeType: () => getExtensionFromMimeType,
|
|
504
494
|
isMimeTypeAllowed: () => isMimeTypeAllowed,
|
|
505
495
|
localStorageAdapter: () => localStorageAdapter,
|
|
506
496
|
mimeTypeMatches: () => mimeTypeMatches,
|
|
@@ -513,6 +503,7 @@ var init_src = __esm({
|
|
|
513
503
|
init_storage_types();
|
|
514
504
|
init_storage_local();
|
|
515
505
|
init_storage_s3();
|
|
506
|
+
init_storage_utils();
|
|
516
507
|
init_mime_validator();
|
|
517
508
|
}
|
|
518
509
|
});
|
|
@@ -4325,11 +4316,11 @@ SwaggerUIBundle({
|
|
|
4325
4316
|
|
|
4326
4317
|
// libs/server-core/src/lib/preview-renderer.ts
|
|
4327
4318
|
function renderPreviewHTML(options) {
|
|
4328
|
-
const { doc, collection } = options;
|
|
4319
|
+
const { doc, collection, customFieldRenderers } = options;
|
|
4329
4320
|
const titleField = collection.admin?.useAsTitle ?? "id";
|
|
4330
4321
|
const title = escapeHtml(String(doc[titleField] ?? doc["id"] ?? "Untitled"));
|
|
4331
4322
|
const fields = collection.fields ?? [];
|
|
4332
|
-
const fieldHtml = fields.filter((f) => !isHiddenField(f) && !isLayoutField(f) && f.name !== titleField).map((f) => renderField(f, doc)).filter(Boolean).join("\n");
|
|
4323
|
+
const fieldHtml = fields.filter((f) => !isHiddenField(f) && !isLayoutField(f) && f.name !== titleField).map((f) => renderField(f, doc, customFieldRenderers)).filter(Boolean).join("\n");
|
|
4333
4324
|
const richTextFields = fields.filter((f) => f.type === "richText").map((f) => f.name);
|
|
4334
4325
|
return `<!DOCTYPE html>
|
|
4335
4326
|
<html lang="en">
|
|
@@ -4363,6 +4354,7 @@ ${fieldHtml}
|
|
|
4363
4354
|
(function(){
|
|
4364
4355
|
var richTextFields=${JSON.stringify(richTextFields)};
|
|
4365
4356
|
window.addEventListener('message',function(e){
|
|
4357
|
+
if(e.origin!==window.location.origin)return;
|
|
4366
4358
|
if(!e.data||e.data.type!=='momentum-preview-update')return;
|
|
4367
4359
|
var d=e.data.data;if(!d)return;
|
|
4368
4360
|
document.querySelectorAll('[data-field]').forEach(function(el){
|
|
@@ -4383,16 +4375,20 @@ if(titleEl){var tf=titleEl.getAttribute('data-field');if(d[tf]!==undefined)title
|
|
|
4383
4375
|
</body>
|
|
4384
4376
|
</html>`;
|
|
4385
4377
|
}
|
|
4386
|
-
function renderField(field, doc) {
|
|
4378
|
+
function renderField(field, doc, customRenderers) {
|
|
4387
4379
|
const value = doc[field.name];
|
|
4388
4380
|
if (value === void 0 || value === null) {
|
|
4389
4381
|
return renderFieldWrapper(field, "");
|
|
4390
4382
|
}
|
|
4383
|
+
const editorKey = field.admin?.editor;
|
|
4384
|
+
if (editorKey && customRenderers?.[editorKey]) {
|
|
4385
|
+
return renderFieldWrapper(field, customRenderers[editorKey](value, field));
|
|
4386
|
+
}
|
|
4391
4387
|
switch (field.type) {
|
|
4392
4388
|
case "richText":
|
|
4393
4389
|
return renderFieldWrapper(
|
|
4394
4390
|
field,
|
|
4395
|
-
`<div class="field-value rich-text" data-field="${escapeHtml(field.name)}">${String(value)}</div>`
|
|
4391
|
+
`<div class="field-value rich-text" data-field="${escapeHtml(field.name)}">${escapeHtml(String(value))}</div>`
|
|
4396
4392
|
);
|
|
4397
4393
|
case "checkbox":
|
|
4398
4394
|
return renderFieldWrapper(
|
|
@@ -4723,6 +4719,21 @@ function coerceCsvValue(value, fieldType) {
|
|
|
4723
4719
|
}
|
|
4724
4720
|
|
|
4725
4721
|
// libs/server-analog/src/lib/server-analog.ts
|
|
4722
|
+
function getEmailBuilderFieldName(collection) {
|
|
4723
|
+
const field = collection.fields.find(
|
|
4724
|
+
(f) => f.type === "json" && f.admin?.editor === "email-builder"
|
|
4725
|
+
);
|
|
4726
|
+
return field?.name;
|
|
4727
|
+
}
|
|
4728
|
+
async function renderEmailPreviewHTML(doc, blocksFieldName) {
|
|
4729
|
+
const emailPkg = "@momentumcms/email";
|
|
4730
|
+
const { renderEmailFromBlocks } = await import(emailPkg);
|
|
4731
|
+
const blocks2 = doc[blocksFieldName];
|
|
4732
|
+
if (!Array.isArray(blocks2) || blocks2.length === 0) {
|
|
4733
|
+
return '<html><body style="display:flex;align-items:center;justify-content:center;min-height:100vh;color:#666;font-family:sans-serif"><p>No email blocks yet.</p></body></html>';
|
|
4734
|
+
}
|
|
4735
|
+
return renderEmailFromBlocks({ blocks: blocks2 });
|
|
4736
|
+
}
|
|
4726
4737
|
function nestBracketParams(flat) {
|
|
4727
4738
|
const result = {};
|
|
4728
4739
|
for (const [key, value] of Object.entries(flat)) {
|
|
@@ -5407,26 +5418,47 @@ function createComprehensiveMomentumHandler(config) {
|
|
|
5407
5418
|
};
|
|
5408
5419
|
}
|
|
5409
5420
|
}
|
|
5410
|
-
if (seg2 === "preview" && seg1 && method === "GET") {
|
|
5421
|
+
if (seg2 === "preview" && seg1 && (method === "GET" || method === "POST")) {
|
|
5422
|
+
if (!user) {
|
|
5423
|
+
utils.setResponseStatus(event, 401);
|
|
5424
|
+
return { error: "Authentication required to access preview" };
|
|
5425
|
+
}
|
|
5411
5426
|
try {
|
|
5412
5427
|
const collectionSlug2 = seg0;
|
|
5413
5428
|
const docId = seg1;
|
|
5414
|
-
const contextApi = getContextualAPI(user);
|
|
5415
|
-
const doc = await contextApi.collection(collectionSlug2).findById(docId);
|
|
5416
|
-
if (!doc) {
|
|
5417
|
-
utils.setResponseStatus(event, 404);
|
|
5418
|
-
return { error: "Document not found" };
|
|
5419
|
-
}
|
|
5420
5429
|
const collectionConfig = config.collections.find((c) => c.slug === collectionSlug2);
|
|
5421
5430
|
if (!collectionConfig) {
|
|
5422
5431
|
utils.setResponseStatus(event, 404);
|
|
5423
5432
|
return { error: "Collection not found" };
|
|
5424
5433
|
}
|
|
5425
|
-
const
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5434
|
+
const accessFn = collectionConfig.access?.read;
|
|
5435
|
+
if (accessFn) {
|
|
5436
|
+
const allowed = await Promise.resolve(accessFn({ req: { user } }));
|
|
5437
|
+
if (!allowed) {
|
|
5438
|
+
utils.setResponseStatus(event, 403);
|
|
5439
|
+
return { error: "Access denied" };
|
|
5440
|
+
}
|
|
5441
|
+
}
|
|
5442
|
+
let docRecord;
|
|
5443
|
+
if (method === "POST") {
|
|
5444
|
+
const body2 = await safeReadBody(event, utils, method);
|
|
5445
|
+
if (body2["data"] && typeof body2["data"] === "object") {
|
|
5446
|
+
docRecord = body2["data"];
|
|
5447
|
+
} else {
|
|
5448
|
+
utils.setResponseStatus(event, 400);
|
|
5449
|
+
return { error: "POST preview requires { data: ... } body" };
|
|
5450
|
+
}
|
|
5451
|
+
} else {
|
|
5452
|
+
const contextApi = getContextualAPI(user);
|
|
5453
|
+
const doc = await contextApi.collection(collectionSlug2).findById(docId);
|
|
5454
|
+
if (!doc) {
|
|
5455
|
+
utils.setResponseStatus(event, 404);
|
|
5456
|
+
return { error: "Document not found" };
|
|
5457
|
+
}
|
|
5458
|
+
docRecord = doc;
|
|
5459
|
+
}
|
|
5460
|
+
const emailField = getEmailBuilderFieldName(collectionConfig);
|
|
5461
|
+
const html = emailField ? await renderEmailPreviewHTML(docRecord, emailField) : renderPreviewHTML({ doc: docRecord, collection: collectionConfig });
|
|
5430
5462
|
utils.setResponseHeader(event, "Content-Type", "text/html; charset=utf-8");
|
|
5431
5463
|
return utils.send(event, html);
|
|
5432
5464
|
} catch (error) {
|
|
@@ -5695,12 +5727,13 @@ function createComprehensiveMomentumHandler(config) {
|
|
|
5695
5727
|
fields[field.name] = field.data.toString("utf-8");
|
|
5696
5728
|
}
|
|
5697
5729
|
}
|
|
5730
|
+
const collectionUpload = postUploadCol.upload ?? {};
|
|
5698
5731
|
const uploadRequest = {
|
|
5699
5732
|
file,
|
|
5700
5733
|
user,
|
|
5701
5734
|
fields,
|
|
5702
5735
|
collectionSlug: seg0,
|
|
5703
|
-
collectionUpload
|
|
5736
|
+
collectionUpload
|
|
5704
5737
|
};
|
|
5705
5738
|
const response2 = await handleCollectionUpload(uploadConfig, uploadRequest);
|
|
5706
5739
|
utils.setResponseStatus(event, response2.status);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@momentumcms/server-analog",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Nitro/h3 adapter for Momentum CMS with Analog.js support",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Momentum CMS Contributors",
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"types": "./src/index.d.ts",
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"@momentumcms/core": ">=0.0.1",
|
|
31
|
-
"@momentumcms/server-core": ">=0.0.1"
|
|
31
|
+
"@momentumcms/server-core": ">=0.0.1",
|
|
32
|
+
"@momentumcms/storage": ">=0.0.1"
|
|
32
33
|
},
|
|
33
34
|
"dependencies": {
|
|
34
35
|
"@aws-sdk/client-s3": "^3.983.0",
|
|
@@ -43,7 +43,7 @@ export type ReadMultipartFormDataFn = (event: H3Event) => Promise<Array<{
|
|
|
43
43
|
/**
|
|
44
44
|
* Type for send function from h3.
|
|
45
45
|
*/
|
|
46
|
-
export type SendFn = (event: H3Event, data: Buffer | string, type?: string) => unknown;
|
|
46
|
+
export type SendFn = (event: H3Event, data: Buffer | Uint8Array | string, type?: string) => unknown;
|
|
47
47
|
/**
|
|
48
48
|
* Extended h3 utilities for comprehensive API handling.
|
|
49
49
|
*/
|
|
@@ -59,7 +59,7 @@ export interface MomentumH3Utils {
|
|
|
59
59
|
type?: string;
|
|
60
60
|
data: Buffer;
|
|
61
61
|
}> | undefined>;
|
|
62
|
-
send(event: H3Event, data: Buffer | string, type?: string): unknown;
|
|
62
|
+
send(event: H3Event, data: Buffer | Uint8Array | string, type?: string): unknown;
|
|
63
63
|
}
|
|
64
64
|
/**
|
|
65
65
|
* Creates an h3 event handler for Momentum CMS API.
|