@momentumcms/server-core 0.1.10 → 0.3.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 +28 -0
- package/index.cjs +100 -6
- package/index.js +97 -5
- package/package.json +1 -1
- package/src/index.d.ts +1 -1
- package/src/lib/upload-handler.d.ts +45 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,31 @@
|
|
|
1
|
+
## 0.3.0 (2026-02-20)
|
|
2
|
+
|
|
3
|
+
### 🚀 Features
|
|
4
|
+
|
|
5
|
+
- add article slugs, detail pages, live preview, and fix PATCH field hooks ([454b61c](https://github.com/DonaldMurillo/momentum-cms/commit/454b61c))
|
|
6
|
+
- add named tabs support with nested data grouping and UI improvements ([#30](https://github.com/DonaldMurillo/momentum-cms/pull/30))
|
|
7
|
+
|
|
8
|
+
### 🩹 Fixes
|
|
9
|
+
|
|
10
|
+
- address code review issues across admin, server-core, and e2e ([4664463](https://github.com/DonaldMurillo/momentum-cms/commit/4664463))
|
|
11
|
+
- add auth guard and MIME validation to PATCH upload route; fix pagination with client-side filtering ([#32](https://github.com/DonaldMurillo/momentum-cms/pull/32))
|
|
12
|
+
|
|
13
|
+
### ❤️ Thank You
|
|
14
|
+
|
|
15
|
+
- Claude Opus 4.6
|
|
16
|
+
- Donald Murillo @DonaldMurillo
|
|
17
|
+
|
|
18
|
+
## 0.2.0 (2026-02-17)
|
|
19
|
+
|
|
20
|
+
### 🚀 Features
|
|
21
|
+
|
|
22
|
+
- add named tabs support with nested data grouping and tab UI improvements ([63ab63e](https://github.com/DonaldMurillo/momentum-cms/commit/63ab63e))
|
|
23
|
+
|
|
24
|
+
### ❤️ Thank You
|
|
25
|
+
|
|
26
|
+
- Claude Opus 4.6
|
|
27
|
+
- Donald Murillo @DonaldMurillo
|
|
28
|
+
|
|
1
29
|
## 0.1.10 (2026-02-17)
|
|
2
30
|
|
|
3
31
|
### 🩹 Fixes
|
package/index.cjs
CHANGED
|
@@ -54,6 +54,7 @@ __export(src_exports, {
|
|
|
54
54
|
getMomentumAPI: () => getMomentumAPI,
|
|
55
55
|
getSwaggerUIHTML: () => getSwaggerUIHTML,
|
|
56
56
|
getUploadConfig: () => getUploadConfig,
|
|
57
|
+
handleCollectionUpload: () => handleCollectionUpload,
|
|
57
58
|
handleFileDelete: () => handleFileDelete,
|
|
58
59
|
handleFileGet: () => handleFileGet,
|
|
59
60
|
handleUpload: () => handleUpload,
|
|
@@ -75,7 +76,8 @@ __export(src_exports, {
|
|
|
75
76
|
sanitizeErrorMessage: () => sanitizeErrorMessage,
|
|
76
77
|
sanitizeFilename: () => sanitizeFilename,
|
|
77
78
|
shouldRunSeeding: () => shouldRunSeeding,
|
|
78
|
-
startPublishScheduler: () => startPublishScheduler
|
|
79
|
+
startPublishScheduler: () => startPublishScheduler,
|
|
80
|
+
validateMimeType: () => validateMimeType2
|
|
79
81
|
});
|
|
80
82
|
module.exports = __toCommonJS(src_exports);
|
|
81
83
|
|
|
@@ -344,12 +346,26 @@ var ReferentialIntegrityError = class extends Error {
|
|
|
344
346
|
this.constraint = constraint;
|
|
345
347
|
}
|
|
346
348
|
};
|
|
349
|
+
function isNamedTab(tab) {
|
|
350
|
+
return typeof tab.name === "string" && tab.name.length > 0;
|
|
351
|
+
}
|
|
347
352
|
function flattenDataFields(fields) {
|
|
348
353
|
const result = [];
|
|
349
354
|
for (const field of fields) {
|
|
350
355
|
if (field.type === "tabs") {
|
|
351
356
|
for (const tab of field.tabs) {
|
|
352
|
-
|
|
357
|
+
if (isNamedTab(tab)) {
|
|
358
|
+
const syntheticGroup = {
|
|
359
|
+
name: tab.name,
|
|
360
|
+
type: "group",
|
|
361
|
+
label: tab.label,
|
|
362
|
+
description: tab.description,
|
|
363
|
+
fields: tab.fields
|
|
364
|
+
};
|
|
365
|
+
result.push(syntheticGroup);
|
|
366
|
+
} else {
|
|
367
|
+
result.push(...flattenDataFields(tab.fields));
|
|
368
|
+
}
|
|
353
369
|
}
|
|
354
370
|
} else if (field.type === "collapsible" || field.type === "row") {
|
|
355
371
|
result.push(...flattenDataFields(field.fields));
|
|
@@ -501,6 +517,9 @@ var MediaCollection = defineCollection({
|
|
|
501
517
|
singular: "Media",
|
|
502
518
|
plural: "Media"
|
|
503
519
|
},
|
|
520
|
+
upload: {
|
|
521
|
+
mimeTypes: ["image/*", "application/pdf", "video/*", "audio/*"]
|
|
522
|
+
},
|
|
504
523
|
admin: {
|
|
505
524
|
useAsTitle: "filename",
|
|
506
525
|
defaultColumns: ["filename", "mimeType", "filesize", "createdAt"]
|
|
@@ -521,7 +540,6 @@ var MediaCollection = defineCollection({
|
|
|
521
540
|
description: "File size in bytes"
|
|
522
541
|
}),
|
|
523
542
|
text("path", {
|
|
524
|
-
required: true,
|
|
525
543
|
label: "Storage Path",
|
|
526
544
|
description: "Path/key where the file is stored",
|
|
527
545
|
admin: {
|
|
@@ -802,7 +820,20 @@ async function runFieldHooks(hookType, fields, data, req, operation) {
|
|
|
802
820
|
for (const field of fields) {
|
|
803
821
|
if (field.type === "tabs") {
|
|
804
822
|
for (const tab of field.tabs) {
|
|
805
|
-
|
|
823
|
+
if (isNamedTab(tab)) {
|
|
824
|
+
const nested = processedData[tab.name];
|
|
825
|
+
if (nested && typeof nested === "object" && !Array.isArray(nested)) {
|
|
826
|
+
processedData[tab.name] = await runFieldHooks(
|
|
827
|
+
hookType,
|
|
828
|
+
tab.fields,
|
|
829
|
+
nested,
|
|
830
|
+
req,
|
|
831
|
+
operation
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
} else {
|
|
835
|
+
processedData = await runFieldHooks(hookType, tab.fields, processedData, req, operation);
|
|
836
|
+
}
|
|
806
837
|
}
|
|
807
838
|
continue;
|
|
808
839
|
}
|
|
@@ -812,6 +843,7 @@ async function runFieldHooks(hookType, fields, data, req, operation) {
|
|
|
812
843
|
}
|
|
813
844
|
const hooks = field.hooks?.[hookType];
|
|
814
845
|
if (hooks && hooks.length > 0) {
|
|
846
|
+
const fieldExistsInData = field.name in processedData;
|
|
815
847
|
let value = processedData[field.name];
|
|
816
848
|
for (const hook of hooks) {
|
|
817
849
|
const result = await Promise.resolve(
|
|
@@ -826,7 +858,9 @@ async function runFieldHooks(hookType, fields, data, req, operation) {
|
|
|
826
858
|
value = result;
|
|
827
859
|
}
|
|
828
860
|
}
|
|
829
|
-
|
|
861
|
+
if (fieldExistsInData || value !== void 0) {
|
|
862
|
+
processedData[field.name] = value;
|
|
863
|
+
}
|
|
830
864
|
}
|
|
831
865
|
if (field.type === "group" && processedData[field.name] && typeof processedData[field.name] === "object" && !Array.isArray(processedData[field.name])) {
|
|
832
866
|
processedData[field.name] = await runFieldHooks(
|
|
@@ -4110,6 +4144,64 @@ async function handleUpload(config, request) {
|
|
|
4110
4144
|
};
|
|
4111
4145
|
}
|
|
4112
4146
|
}
|
|
4147
|
+
async function handleCollectionUpload(globalConfig, request) {
|
|
4148
|
+
const { adapter } = globalConfig;
|
|
4149
|
+
const { file, user, fields, collectionSlug, collectionUpload } = request;
|
|
4150
|
+
const maxFileSize = collectionUpload.maxFileSize ?? globalConfig.maxFileSize ?? 10 * 1024 * 1024;
|
|
4151
|
+
const allowedMimeTypes = collectionUpload.mimeTypes ?? globalConfig.allowedMimeTypes ?? [];
|
|
4152
|
+
try {
|
|
4153
|
+
if (!user) {
|
|
4154
|
+
return {
|
|
4155
|
+
status: 401,
|
|
4156
|
+
error: "Authentication required to upload files"
|
|
4157
|
+
};
|
|
4158
|
+
}
|
|
4159
|
+
const sizeError = validateFileSize(file, maxFileSize);
|
|
4160
|
+
if (sizeError) {
|
|
4161
|
+
return { status: 400, error: sizeError };
|
|
4162
|
+
}
|
|
4163
|
+
const mimeError = validateMimeType2(file.mimeType, allowedMimeTypes);
|
|
4164
|
+
if (mimeError) {
|
|
4165
|
+
return { status: 400, error: mimeError };
|
|
4166
|
+
}
|
|
4167
|
+
if (file.buffer && file.buffer.length > 0) {
|
|
4168
|
+
const magicByteResult = validateMimeType(
|
|
4169
|
+
file.buffer,
|
|
4170
|
+
file.mimeType,
|
|
4171
|
+
allowedMimeTypes
|
|
4172
|
+
);
|
|
4173
|
+
if (!magicByteResult.valid) {
|
|
4174
|
+
return {
|
|
4175
|
+
status: 400,
|
|
4176
|
+
error: magicByteResult.error ?? "File content does not match claimed type"
|
|
4177
|
+
};
|
|
4178
|
+
}
|
|
4179
|
+
}
|
|
4180
|
+
const storedFile = await adapter.upload(file);
|
|
4181
|
+
const docData = {
|
|
4182
|
+
...fields,
|
|
4183
|
+
filename: file.originalName,
|
|
4184
|
+
mimeType: file.mimeType,
|
|
4185
|
+
filesize: file.size,
|
|
4186
|
+
path: storedFile.path,
|
|
4187
|
+
url: storedFile.url
|
|
4188
|
+
};
|
|
4189
|
+
const api = getMomentumAPI().setContext({ user });
|
|
4190
|
+
const doc = await api.collection(collectionSlug).create(docData);
|
|
4191
|
+
return {
|
|
4192
|
+
status: 201,
|
|
4193
|
+
doc
|
|
4194
|
+
};
|
|
4195
|
+
} catch (error) {
|
|
4196
|
+
if (error instanceof Error) {
|
|
4197
|
+
if (error.message.includes("Access denied")) {
|
|
4198
|
+
return { status: 403, error: error.message };
|
|
4199
|
+
}
|
|
4200
|
+
return { status: 500, error: `Upload failed: ${error.message}` };
|
|
4201
|
+
}
|
|
4202
|
+
return { status: 500, error: "Upload failed: Unknown error" };
|
|
4203
|
+
}
|
|
4204
|
+
}
|
|
4113
4205
|
async function handleFileDelete(adapter, path) {
|
|
4114
4206
|
return adapter.delete(path);
|
|
4115
4207
|
}
|
|
@@ -5229,6 +5321,7 @@ function coerceCsvValue(value, fieldType) {
|
|
|
5229
5321
|
getMomentumAPI,
|
|
5230
5322
|
getSwaggerUIHTML,
|
|
5231
5323
|
getUploadConfig,
|
|
5324
|
+
handleCollectionUpload,
|
|
5232
5325
|
handleFileDelete,
|
|
5233
5326
|
handleFileGet,
|
|
5234
5327
|
handleUpload,
|
|
@@ -5250,5 +5343,6 @@ function coerceCsvValue(value, fieldType) {
|
|
|
5250
5343
|
sanitizeErrorMessage,
|
|
5251
5344
|
sanitizeFilename,
|
|
5252
5345
|
shouldRunSeeding,
|
|
5253
|
-
startPublishScheduler
|
|
5346
|
+
startPublishScheduler,
|
|
5347
|
+
validateMimeType
|
|
5254
5348
|
});
|
package/index.js
CHANGED
|
@@ -263,12 +263,26 @@ var ReferentialIntegrityError = class extends Error {
|
|
|
263
263
|
this.constraint = constraint;
|
|
264
264
|
}
|
|
265
265
|
};
|
|
266
|
+
function isNamedTab(tab) {
|
|
267
|
+
return typeof tab.name === "string" && tab.name.length > 0;
|
|
268
|
+
}
|
|
266
269
|
function flattenDataFields(fields) {
|
|
267
270
|
const result = [];
|
|
268
271
|
for (const field of fields) {
|
|
269
272
|
if (field.type === "tabs") {
|
|
270
273
|
for (const tab of field.tabs) {
|
|
271
|
-
|
|
274
|
+
if (isNamedTab(tab)) {
|
|
275
|
+
const syntheticGroup = {
|
|
276
|
+
name: tab.name,
|
|
277
|
+
type: "group",
|
|
278
|
+
label: tab.label,
|
|
279
|
+
description: tab.description,
|
|
280
|
+
fields: tab.fields
|
|
281
|
+
};
|
|
282
|
+
result.push(syntheticGroup);
|
|
283
|
+
} else {
|
|
284
|
+
result.push(...flattenDataFields(tab.fields));
|
|
285
|
+
}
|
|
272
286
|
}
|
|
273
287
|
} else if (field.type === "collapsible" || field.type === "row") {
|
|
274
288
|
result.push(...flattenDataFields(field.fields));
|
|
@@ -420,6 +434,9 @@ var MediaCollection = defineCollection({
|
|
|
420
434
|
singular: "Media",
|
|
421
435
|
plural: "Media"
|
|
422
436
|
},
|
|
437
|
+
upload: {
|
|
438
|
+
mimeTypes: ["image/*", "application/pdf", "video/*", "audio/*"]
|
|
439
|
+
},
|
|
423
440
|
admin: {
|
|
424
441
|
useAsTitle: "filename",
|
|
425
442
|
defaultColumns: ["filename", "mimeType", "filesize", "createdAt"]
|
|
@@ -440,7 +457,6 @@ var MediaCollection = defineCollection({
|
|
|
440
457
|
description: "File size in bytes"
|
|
441
458
|
}),
|
|
442
459
|
text("path", {
|
|
443
|
-
required: true,
|
|
444
460
|
label: "Storage Path",
|
|
445
461
|
description: "Path/key where the file is stored",
|
|
446
462
|
admin: {
|
|
@@ -721,7 +737,20 @@ async function runFieldHooks(hookType, fields, data, req, operation) {
|
|
|
721
737
|
for (const field of fields) {
|
|
722
738
|
if (field.type === "tabs") {
|
|
723
739
|
for (const tab of field.tabs) {
|
|
724
|
-
|
|
740
|
+
if (isNamedTab(tab)) {
|
|
741
|
+
const nested = processedData[tab.name];
|
|
742
|
+
if (nested && typeof nested === "object" && !Array.isArray(nested)) {
|
|
743
|
+
processedData[tab.name] = await runFieldHooks(
|
|
744
|
+
hookType,
|
|
745
|
+
tab.fields,
|
|
746
|
+
nested,
|
|
747
|
+
req,
|
|
748
|
+
operation
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
} else {
|
|
752
|
+
processedData = await runFieldHooks(hookType, tab.fields, processedData, req, operation);
|
|
753
|
+
}
|
|
725
754
|
}
|
|
726
755
|
continue;
|
|
727
756
|
}
|
|
@@ -731,6 +760,7 @@ async function runFieldHooks(hookType, fields, data, req, operation) {
|
|
|
731
760
|
}
|
|
732
761
|
const hooks = field.hooks?.[hookType];
|
|
733
762
|
if (hooks && hooks.length > 0) {
|
|
763
|
+
const fieldExistsInData = field.name in processedData;
|
|
734
764
|
let value = processedData[field.name];
|
|
735
765
|
for (const hook of hooks) {
|
|
736
766
|
const result = await Promise.resolve(
|
|
@@ -745,7 +775,9 @@ async function runFieldHooks(hookType, fields, data, req, operation) {
|
|
|
745
775
|
value = result;
|
|
746
776
|
}
|
|
747
777
|
}
|
|
748
|
-
|
|
778
|
+
if (fieldExistsInData || value !== void 0) {
|
|
779
|
+
processedData[field.name] = value;
|
|
780
|
+
}
|
|
749
781
|
}
|
|
750
782
|
if (field.type === "group" && processedData[field.name] && typeof processedData[field.name] === "object" && !Array.isArray(processedData[field.name])) {
|
|
751
783
|
processedData[field.name] = await runFieldHooks(
|
|
@@ -4042,6 +4074,64 @@ async function handleUpload(config, request) {
|
|
|
4042
4074
|
};
|
|
4043
4075
|
}
|
|
4044
4076
|
}
|
|
4077
|
+
async function handleCollectionUpload(globalConfig, request) {
|
|
4078
|
+
const { adapter } = globalConfig;
|
|
4079
|
+
const { file, user, fields, collectionSlug, collectionUpload } = request;
|
|
4080
|
+
const maxFileSize = collectionUpload.maxFileSize ?? globalConfig.maxFileSize ?? 10 * 1024 * 1024;
|
|
4081
|
+
const allowedMimeTypes = collectionUpload.mimeTypes ?? globalConfig.allowedMimeTypes ?? [];
|
|
4082
|
+
try {
|
|
4083
|
+
if (!user) {
|
|
4084
|
+
return {
|
|
4085
|
+
status: 401,
|
|
4086
|
+
error: "Authentication required to upload files"
|
|
4087
|
+
};
|
|
4088
|
+
}
|
|
4089
|
+
const sizeError = validateFileSize(file, maxFileSize);
|
|
4090
|
+
if (sizeError) {
|
|
4091
|
+
return { status: 400, error: sizeError };
|
|
4092
|
+
}
|
|
4093
|
+
const mimeError = validateMimeType2(file.mimeType, allowedMimeTypes);
|
|
4094
|
+
if (mimeError) {
|
|
4095
|
+
return { status: 400, error: mimeError };
|
|
4096
|
+
}
|
|
4097
|
+
if (file.buffer && file.buffer.length > 0) {
|
|
4098
|
+
const magicByteResult = validateMimeType(
|
|
4099
|
+
file.buffer,
|
|
4100
|
+
file.mimeType,
|
|
4101
|
+
allowedMimeTypes
|
|
4102
|
+
);
|
|
4103
|
+
if (!magicByteResult.valid) {
|
|
4104
|
+
return {
|
|
4105
|
+
status: 400,
|
|
4106
|
+
error: magicByteResult.error ?? "File content does not match claimed type"
|
|
4107
|
+
};
|
|
4108
|
+
}
|
|
4109
|
+
}
|
|
4110
|
+
const storedFile = await adapter.upload(file);
|
|
4111
|
+
const docData = {
|
|
4112
|
+
...fields,
|
|
4113
|
+
filename: file.originalName,
|
|
4114
|
+
mimeType: file.mimeType,
|
|
4115
|
+
filesize: file.size,
|
|
4116
|
+
path: storedFile.path,
|
|
4117
|
+
url: storedFile.url
|
|
4118
|
+
};
|
|
4119
|
+
const api = getMomentumAPI().setContext({ user });
|
|
4120
|
+
const doc = await api.collection(collectionSlug).create(docData);
|
|
4121
|
+
return {
|
|
4122
|
+
status: 201,
|
|
4123
|
+
doc
|
|
4124
|
+
};
|
|
4125
|
+
} catch (error) {
|
|
4126
|
+
if (error instanceof Error) {
|
|
4127
|
+
if (error.message.includes("Access denied")) {
|
|
4128
|
+
return { status: 403, error: error.message };
|
|
4129
|
+
}
|
|
4130
|
+
return { status: 500, error: `Upload failed: ${error.message}` };
|
|
4131
|
+
}
|
|
4132
|
+
return { status: 500, error: "Upload failed: Unknown error" };
|
|
4133
|
+
}
|
|
4134
|
+
}
|
|
4045
4135
|
async function handleFileDelete(adapter, path) {
|
|
4046
4136
|
return adapter.delete(path);
|
|
4047
4137
|
}
|
|
@@ -5160,6 +5250,7 @@ export {
|
|
|
5160
5250
|
getMomentumAPI,
|
|
5161
5251
|
getSwaggerUIHTML,
|
|
5162
5252
|
getUploadConfig,
|
|
5253
|
+
handleCollectionUpload,
|
|
5163
5254
|
handleFileDelete,
|
|
5164
5255
|
handleFileGet,
|
|
5165
5256
|
handleUpload,
|
|
@@ -5181,5 +5272,6 @@ export {
|
|
|
5181
5272
|
sanitizeErrorMessage,
|
|
5182
5273
|
sanitizeFilename,
|
|
5183
5274
|
shouldRunSeeding,
|
|
5184
|
-
startPublishScheduler
|
|
5275
|
+
startPublishScheduler,
|
|
5276
|
+
validateMimeType2 as validateMimeType
|
|
5185
5277
|
};
|
package/package.json
CHANGED
package/src/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export { buildGraphQLSchema, type GraphQLContext } from './lib/graphql-schema';
|
|
|
12
12
|
export { executeGraphQL, type GraphQLRequestBody, type GraphQLResult } from './lib/graphql-handler';
|
|
13
13
|
export { GraphQLJSON } from './lib/graphql-scalars';
|
|
14
14
|
export { generateApiKey, hashApiKey, getKeyPrefix, isValidApiKeyFormat, generateApiKeyId, createAdapterApiKeyStore, createPostgresApiKeyStore, API_KEYS_TABLE_SQL_POSTGRES, API_KEYS_TABLE_SQL_SQLITE, type ApiKeyRecord, type CreateApiKeyResult, type CreateApiKeyOptions, type ApiKeyStore, } from './lib/api-keys';
|
|
15
|
-
export { handleUpload, handleFileDelete, handleFileGet, getUploadConfig, type UploadRequest, type UploadResponse, type UploadConfig, } from './lib/upload-handler';
|
|
15
|
+
export { handleUpload, handleCollectionUpload, handleFileDelete, handleFileGet, getUploadConfig, validateMimeType, type UploadRequest, type UploadResponse, type UploadConfig, type CollectionUploadRequest, type CollectionUploadResponse, } from './lib/upload-handler';
|
|
16
16
|
export { generateOpenAPISpec, type OpenAPIDocument, type OpenAPIGeneratorOptions, } from './lib/openapi-generator';
|
|
17
17
|
export { getSwaggerUIHTML } from './lib/swagger-ui-html';
|
|
18
18
|
export { renderPreviewHTML, type PreviewRenderOptions } from './lib/preview-renderer';
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Upload Handler for Momentum CMS
|
|
3
3
|
* Framework-agnostic file upload handling
|
|
4
4
|
*/
|
|
5
|
-
import type { StorageAdapter, UploadedFile, MomentumConfig, MediaDocument } from '@momentumcms/core';
|
|
5
|
+
import type { StorageAdapter, UploadedFile, MomentumConfig, MediaDocument, UploadCollectionConfig } from '@momentumcms/core';
|
|
6
6
|
import { type MomentumAPIContext } from './momentum-api';
|
|
7
7
|
/**
|
|
8
8
|
* Upload request from the client.
|
|
@@ -43,6 +43,11 @@ export interface UploadConfig {
|
|
|
43
43
|
* Get upload configuration from MomentumConfig.
|
|
44
44
|
*/
|
|
45
45
|
export declare function getUploadConfig(config: MomentumConfig): UploadConfig | null;
|
|
46
|
+
/**
|
|
47
|
+
* Validate claimed MIME type against an allow-list.
|
|
48
|
+
* Returns an error message if the type is not allowed, or null if OK.
|
|
49
|
+
*/
|
|
50
|
+
export declare function validateMimeType(mimeType: string, allowedTypes: string[]): string | null;
|
|
46
51
|
/**
|
|
47
52
|
* Handle file upload.
|
|
48
53
|
*
|
|
@@ -51,6 +56,45 @@ export declare function getUploadConfig(config: MomentumConfig): UploadConfig |
|
|
|
51
56
|
* @returns Upload response with created media document or error
|
|
52
57
|
*/
|
|
53
58
|
export declare function handleUpload(config: UploadConfig, request: UploadRequest): Promise<UploadResponse>;
|
|
59
|
+
/**
|
|
60
|
+
* Upload request for a collection-level upload.
|
|
61
|
+
* Used when POST /api/{slug} with multipart/form-data hits an upload collection.
|
|
62
|
+
*/
|
|
63
|
+
export interface CollectionUploadRequest {
|
|
64
|
+
/** The uploaded file */
|
|
65
|
+
file: UploadedFile;
|
|
66
|
+
/** User context for access control */
|
|
67
|
+
user?: MomentumAPIContext['user'];
|
|
68
|
+
/** Non-file form fields from multipart body (e.g., alt, title) */
|
|
69
|
+
fields: Record<string, unknown>;
|
|
70
|
+
/** Target collection slug */
|
|
71
|
+
collectionSlug: string;
|
|
72
|
+
/** Collection-level upload config */
|
|
73
|
+
collectionUpload: UploadCollectionConfig;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Response from a collection-level upload.
|
|
77
|
+
*/
|
|
78
|
+
export interface CollectionUploadResponse {
|
|
79
|
+
/** Created document (with auto-populated file metadata) */
|
|
80
|
+
doc?: Record<string, unknown>;
|
|
81
|
+
/** Error message if upload failed */
|
|
82
|
+
error?: string;
|
|
83
|
+
/** HTTP status code */
|
|
84
|
+
status: number;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Handle file upload for an upload collection.
|
|
88
|
+
* Stores the file, auto-populates metadata fields, merges with user-provided fields,
|
|
89
|
+
* and creates the document in the target collection.
|
|
90
|
+
*
|
|
91
|
+
* Collection-level config overrides global config for mimeTypes and maxFileSize.
|
|
92
|
+
*
|
|
93
|
+
* @param globalConfig - Global upload configuration (storage adapter, defaults)
|
|
94
|
+
* @param request - Collection upload request with file, user fields, and collection config
|
|
95
|
+
* @returns Response with created document or error
|
|
96
|
+
*/
|
|
97
|
+
export declare function handleCollectionUpload(globalConfig: UploadConfig, request: CollectionUploadRequest): Promise<CollectionUploadResponse>;
|
|
54
98
|
/**
|
|
55
99
|
* Handle file deletion.
|
|
56
100
|
*
|