@solidxai/core 0.1.14 → 0.1.15-beta.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 +597 -0
- package/dist/constants/media-file-types.d.ts +1 -0
- package/dist/constants/media-file-types.d.ts.map +1 -1
- package/dist/constants/media-file-types.js +10 -1
- package/dist/constants/media-file-types.js.map +1 -1
- package/dist/helpers/field-crud-managers/MediaFieldCrudManager.d.ts.map +1 -1
- package/dist/helpers/field-crud-managers/MediaFieldCrudManager.js +5 -5
- package/dist/helpers/field-crud-managers/MediaFieldCrudManager.js.map +1 -1
- package/dist/helpers/user-helper.d.ts.map +1 -1
- package/dist/helpers/user-helper.js +2 -0
- package/dist/helpers/user-helper.js.map +1 -1
- package/dist/services/import-transaction.service.d.ts +1 -0
- package/dist/services/import-transaction.service.d.ts.map +1 -1
- package/dist/services/import-transaction.service.js +21 -0
- package/dist/services/import-transaction.service.js.map +1 -1
- package/package.json +1 -1
- package/src/constants/media-file-types.ts +18 -2
- package/src/helpers/field-crud-managers/MediaFieldCrudManager.ts +14 -6
- package/src/helpers/user-helper.ts +2 -0
- package/src/services/import-transaction.service.ts +35 -0
package/package.json
CHANGED
|
@@ -238,9 +238,25 @@ export function isDangerousMediaFile(file?: UploadedFileLike | null): boolean {
|
|
|
238
238
|
* Single source of truth for extension -> coarse media category. Shared by upload-time
|
|
239
239
|
* validation (MediaFieldCrudManager) and serve-time header hardening (ServeStaticModule), so
|
|
240
240
|
* both agree on which extensions belong to which category and neither drifts out of sync.
|
|
241
|
-
*
|
|
242
|
-
*
|
|
241
|
+
* Some extensions, such as ogg and webm, are valid for more than one category.
|
|
242
|
+
* Keep every matching category instead of allowing a later entry to overwrite an earlier one.
|
|
243
243
|
*/
|
|
244
|
+
// Example: { ogg: ['audio', 'video'], mp3: ['audio'], jpg: ['image'] }
|
|
245
|
+
export const EXT_TO_MEDIA_TYPES: Record<string, MediaCategory[]> = MEDIA_FILE_TYPES.reduce((acc, fileType) => {
|
|
246
|
+
// Create an empty category list the first time we see an extension.
|
|
247
|
+
if (!acc[fileType.extension]) {
|
|
248
|
+
acc[fileType.extension] = [];
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Add the category for this extension, but do not add duplicates.
|
|
252
|
+
if (!acc[fileType.extension].includes(fileType.mediaType)) {
|
|
253
|
+
acc[fileType.extension].push(fileType.mediaType);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Return the map so the next media definition can be added to it.
|
|
257
|
+
return acc;
|
|
258
|
+
}, {} as Record<string, MediaCategory[]>);
|
|
259
|
+
|
|
244
260
|
export const EXT_TO_MEDIA_TYPE: Record<string, MediaCategory> = {
|
|
245
261
|
...Object.fromEntries(MEDIA_FILE_TYPES.map((fileType) => [fileType.extension, fileType.mediaType])),
|
|
246
262
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FieldCrudManager, ValidationError } from "src/interfaces";
|
|
2
|
-
import {
|
|
2
|
+
import { EXT_TO_MEDIA_TYPES, getLowercaseFileExtension, getUploadedFileName, isDangerousMediaFile, MediaCategory, MIME_TO_MEDIA_TYPE } from "src/constants/media-file-types";
|
|
3
3
|
|
|
4
4
|
export enum SolidMediaType {
|
|
5
5
|
mediaSingle = 'mediaSingle',
|
|
@@ -40,9 +40,15 @@ export class MediaFieldCrudManager implements FieldCrudManager {
|
|
|
40
40
|
// 'image', since the mimetype match short-circuited before the extension was ever
|
|
41
41
|
// checked). The extension must independently resolve to a known-safe category before
|
|
42
42
|
// anything is accepted - not merely "not on the denylist".
|
|
43
|
+
// Read the file extension from the uploaded filename, for example "song.ogg" -> "ogg".
|
|
43
44
|
const ext = getLowercaseFileExtension(getUploadedFileName(file));
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
|
|
46
|
+
// Find every media category that supports this extension.
|
|
47
|
+
// An extension can belong to multiple categories, for example .ogg can be audio or video.
|
|
48
|
+
const extToMediaTypes = ext ? EXT_TO_MEDIA_TYPES[ext] : undefined;
|
|
49
|
+
|
|
50
|
+
// Reject files whose extension is not part of the supported media definitions.
|
|
51
|
+
if (!extToMediaTypes || extToMediaTypes.length === 0) {
|
|
46
52
|
return null;
|
|
47
53
|
}
|
|
48
54
|
|
|
@@ -50,13 +56,15 @@ export class MediaFieldCrudManager implements FieldCrudManager {
|
|
|
50
56
|
// grant a category on its own. A mimetype that doesn't resolve to anything (e.g.
|
|
51
57
|
// application/octet-stream) has no opinion and is treated as agreeing.
|
|
52
58
|
const mt = (file.mimetype || '').toLowerCase().trim();
|
|
53
|
-
const
|
|
59
|
+
const mimeToMediaTypes = MIME_TO_MEDIA_TYPE[mt]
|
|
54
60
|
?? (mt.startsWith('image/') ? 'image' : mt.startsWith('audio/') ? 'audio' : mt.startsWith('video/') ? 'video' : undefined);
|
|
55
|
-
|
|
61
|
+
// When the extension is shared, the MIME type identifies the actual category:
|
|
62
|
+
// audio/ogg becomes audio, while video/ogg becomes video.
|
|
63
|
+
if (mimeToMediaTypes && !extToMediaTypes.includes(mimeToMediaTypes)) {
|
|
56
64
|
return null;
|
|
57
65
|
}
|
|
58
66
|
|
|
59
|
-
return
|
|
67
|
+
return mimeToMediaTypes || extToMediaTypes[0];
|
|
60
68
|
}
|
|
61
69
|
|
|
62
70
|
validate(dto: any, files: Array<Express.Multer.File>): ValidationError[] {
|
|
@@ -643,9 +643,44 @@ export class ImportTransactionService extends CRUDService<ImportTransaction> {
|
|
|
643
643
|
}
|
|
644
644
|
}
|
|
645
645
|
}
|
|
646
|
+
|
|
647
|
+
// Some fields are hidden from the import file because users should not
|
|
648
|
+
// have to enter internal values such as passwordSchemeVersion. The
|
|
649
|
+
// backend still checks those fields as required, so fill in their default
|
|
650
|
+
// values here before validation. This lets users import normal details
|
|
651
|
+
// without exposing or requiring internal fields in the template.
|
|
652
|
+
for (const field of modelMetadataWithFields.fields) {
|
|
653
|
+
if (
|
|
654
|
+
field.required &&
|
|
655
|
+
field.defaultValue !== null &&
|
|
656
|
+
field.defaultValue !== undefined &&
|
|
657
|
+
!(field.name in dtoRecord)
|
|
658
|
+
) {
|
|
659
|
+
dtoRecord[field.name] = this.parseImportDefaultValue(field);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
646
663
|
return dtoRecord;
|
|
647
664
|
}
|
|
648
665
|
|
|
666
|
+
// Defaults come from metadata as text. Convert them to the type expected by
|
|
667
|
+
// validation, so values like "1" and "true" are handled as a number and a
|
|
668
|
+
// boolean instead of being treated as plain text.
|
|
669
|
+
private parseImportDefaultValue(field: FieldMetadata): any {
|
|
670
|
+
const defaultValue = field.defaultValue;
|
|
671
|
+
|
|
672
|
+
switch (field.type) {
|
|
673
|
+
case SolidFieldType.boolean:
|
|
674
|
+
return ['true', '1', 'yes'].includes(String(defaultValue).toLowerCase());
|
|
675
|
+
case SolidFieldType.int:
|
|
676
|
+
case SolidFieldType.bigint:
|
|
677
|
+
case SolidFieldType.decimal:
|
|
678
|
+
return Number(defaultValue);
|
|
679
|
+
default:
|
|
680
|
+
return defaultValue;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
649
684
|
private async populateDtoForACell(dtoRecord: Record<string, any>, fieldMetadata: FieldMetadata, record: Record<string, any>, key: string): Promise<Record<string, any>> {
|
|
650
685
|
const fieldType = fieldMetadata.type;
|
|
651
686
|
// const userKeyFieldName = userKeyField?.name || 'id'; // Default to 'id' if not found
|