mirror of
https://github.com/immich-app/immich.git
synced 2024-12-26 10:50:29 +02:00
fix(server): add missing extensions and mime types (#3318)
Add extensions and mime types which were accidentally removed in #3197. Fixes: #3300
This commit is contained in:
parent
4b8cc7b533
commit
f0302670d2
@ -12,7 +12,6 @@ import {
|
|||||||
import { when } from 'jest-when';
|
import { when } from 'jest-when';
|
||||||
import { Readable } from 'stream';
|
import { Readable } from 'stream';
|
||||||
import { ICryptoRepository } from '../crypto';
|
import { ICryptoRepository } from '../crypto';
|
||||||
import { mimeTypes } from '../domain.constant';
|
|
||||||
import { IStorageRepository } from '../storage';
|
import { IStorageRepository } from '../storage';
|
||||||
import { AssetStats, IAssetRepository } from './asset.repository';
|
import { AssetStats, IAssetRepository } from './asset.repository';
|
||||||
import { AssetService, UploadFieldName } from './asset.service';
|
import { AssetService, UploadFieldName } from './asset.service';
|
||||||
@ -66,30 +65,78 @@ const uploadFile = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const validImages = [
|
||||||
|
'.3fr',
|
||||||
|
'.ari',
|
||||||
|
'.arw',
|
||||||
|
'.avif',
|
||||||
|
'.cap',
|
||||||
|
'.cin',
|
||||||
|
'.cr2',
|
||||||
|
'.cr3',
|
||||||
|
'.crw',
|
||||||
|
'.dcr',
|
||||||
|
'.dng',
|
||||||
|
'.erf',
|
||||||
|
'.fff',
|
||||||
|
'.gif',
|
||||||
|
'.heic',
|
||||||
|
'.heif',
|
||||||
|
'.iiq',
|
||||||
|
'.jpeg',
|
||||||
|
'.jpg',
|
||||||
|
'.jxl',
|
||||||
|
'.k25',
|
||||||
|
'.kdc',
|
||||||
|
'.mrw',
|
||||||
|
'.nef',
|
||||||
|
'.orf',
|
||||||
|
'.ori',
|
||||||
|
'.pef',
|
||||||
|
'.png',
|
||||||
|
'.raf',
|
||||||
|
'.raw',
|
||||||
|
'.rwl',
|
||||||
|
'.sr2',
|
||||||
|
'.srf',
|
||||||
|
'.srw',
|
||||||
|
'.tiff',
|
||||||
|
'.webp',
|
||||||
|
'.x3f',
|
||||||
|
];
|
||||||
|
|
||||||
|
const validVideos = ['.3gp', '.avi', '.flv', '.m2ts', '.mkv', '.mov', '.mp4', '.mpg', '.mts', '.webm', '.wmv'];
|
||||||
|
|
||||||
const uploadTests = [
|
const uploadTests = [
|
||||||
{
|
{
|
||||||
label: 'asset',
|
label: 'asset images',
|
||||||
fieldName: UploadFieldName.ASSET_DATA,
|
fieldName: UploadFieldName.ASSET_DATA,
|
||||||
filetypes: Object.keys({ ...mimeTypes.image, ...mimeTypes.video }),
|
valid: validImages,
|
||||||
invalid: ['.xml', '.html'],
|
invalid: ['.html', '.xml'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'asset videos',
|
||||||
|
fieldName: UploadFieldName.ASSET_DATA,
|
||||||
|
valid: validVideos,
|
||||||
|
invalid: ['.html', '.xml'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'live photo',
|
label: 'live photo',
|
||||||
fieldName: UploadFieldName.LIVE_PHOTO_DATA,
|
fieldName: UploadFieldName.LIVE_PHOTO_DATA,
|
||||||
filetypes: Object.keys(mimeTypes.video),
|
valid: validVideos,
|
||||||
invalid: ['.xml', '.html', '.jpg', '.jpeg'],
|
invalid: ['.html', '.jpeg', '.jpg', '.xml'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'sidecar',
|
label: 'sidecar',
|
||||||
fieldName: UploadFieldName.SIDECAR_DATA,
|
fieldName: UploadFieldName.SIDECAR_DATA,
|
||||||
filetypes: Object.keys(mimeTypes.sidecar),
|
valid: ['.xmp'],
|
||||||
invalid: ['.xml', '.html', '.jpg', '.jpeg', '.mov', '.mp4'],
|
invalid: ['.html', '.jpeg', '.jpg', '.mov', '.mp4', '.xml'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'profile',
|
label: 'profile',
|
||||||
fieldName: UploadFieldName.PROFILE_DATA,
|
fieldName: UploadFieldName.PROFILE_DATA,
|
||||||
filetypes: Object.keys(mimeTypes.profile),
|
valid: ['.avif', '.dng', '.heic', '.heif', '.jpeg', '.jpg', '.png', '.webp'],
|
||||||
invalid: ['.xml', '.html', '.cr2', '.arf', '.mov', '.mp4'],
|
invalid: ['.arf', '.cr2', '.html', '.mov', '.mp4', '.xml'],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -117,9 +164,9 @@ describe(AssetService.name, () => {
|
|||||||
expect(() => sut.canUploadFile(uploadFile.nullAuth)).toThrowError(UnauthorizedException);
|
expect(() => sut.canUploadFile(uploadFile.nullAuth)).toThrowError(UnauthorizedException);
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const { fieldName, filetypes, invalid } of uploadTests) {
|
for (const { fieldName, valid, invalid } of uploadTests) {
|
||||||
describe(`${fieldName}`, () => {
|
describe(fieldName, () => {
|
||||||
for (const filetype of filetypes) {
|
for (const filetype of valid) {
|
||||||
it(`should accept ${filetype}`, () => {
|
it(`should accept ${filetype}`, () => {
|
||||||
expect(sut.canUploadFile(uploadFile.filename(fieldName, `asset${filetype}`))).toEqual(true);
|
expect(sut.canUploadFile(uploadFile.filename(fieldName, `asset${filetype}`))).toEqual(true);
|
||||||
});
|
});
|
||||||
@ -132,6 +179,16 @@ describe(AssetService.name, () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
it('should be sorted (valid)', () => {
|
||||||
|
// TODO: use toSorted in NodeJS 20.
|
||||||
|
expect(valid).toEqual([...valid].sort());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be sorted (invalid)', () => {
|
||||||
|
// TODO: use toSorted in NodeJS 20.
|
||||||
|
expect(invalid).toEqual([...invalid].sort());
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
191
server/src/domain/domain.constant.spec.ts
Normal file
191
server/src/domain/domain.constant.spec.ts
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
import { mimeTypes } from '@app/domain';
|
||||||
|
|
||||||
|
describe('mimeTypes', () => {
|
||||||
|
for (const { mimetype, extension } of [
|
||||||
|
// Please ensure this list is sorted.
|
||||||
|
{ mimetype: 'image/3fr', extension: '.3fr' },
|
||||||
|
{ mimetype: 'image/ari', extension: '.ari' },
|
||||||
|
{ mimetype: 'image/arw', extension: '.arw' },
|
||||||
|
{ mimetype: 'image/avif', extension: '.avif' },
|
||||||
|
{ mimetype: 'image/cap', extension: '.cap' },
|
||||||
|
{ mimetype: 'image/cin', extension: '.cin' },
|
||||||
|
{ mimetype: 'image/cr2', extension: '.cr2' },
|
||||||
|
{ mimetype: 'image/cr3', extension: '.cr3' },
|
||||||
|
{ mimetype: 'image/crw', extension: '.crw' },
|
||||||
|
{ mimetype: 'image/dcr', extension: '.dcr' },
|
||||||
|
{ mimetype: 'image/dng', extension: '.dng' },
|
||||||
|
{ mimetype: 'image/erf', extension: '.erf' },
|
||||||
|
{ mimetype: 'image/fff', extension: '.fff' },
|
||||||
|
{ mimetype: 'image/gif', extension: '.gif' },
|
||||||
|
{ mimetype: 'image/heic', extension: '.heic' },
|
||||||
|
{ mimetype: 'image/heif', extension: '.heif' },
|
||||||
|
{ mimetype: 'image/iiq', extension: '.iiq' },
|
||||||
|
{ mimetype: 'image/jpeg', extension: '.jpeg' },
|
||||||
|
{ mimetype: 'image/jpeg', extension: '.jpg' },
|
||||||
|
{ mimetype: 'image/jxl', extension: '.jxl' },
|
||||||
|
{ mimetype: 'image/k25', extension: '.k25' },
|
||||||
|
{ mimetype: 'image/kdc', extension: '.kdc' },
|
||||||
|
{ mimetype: 'image/mrw', extension: '.mrw' },
|
||||||
|
{ mimetype: 'image/nef', extension: '.nef' },
|
||||||
|
{ mimetype: 'image/orf', extension: '.orf' },
|
||||||
|
{ mimetype: 'image/ori', extension: '.ori' },
|
||||||
|
{ mimetype: 'image/pef', extension: '.pef' },
|
||||||
|
{ mimetype: 'image/png', extension: '.png' },
|
||||||
|
{ mimetype: 'image/raf', extension: '.raf' },
|
||||||
|
{ mimetype: 'image/raw', extension: '.raw' },
|
||||||
|
{ mimetype: 'image/rwl', extension: '.rwl' },
|
||||||
|
{ mimetype: 'image/sr2', extension: '.sr2' },
|
||||||
|
{ mimetype: 'image/srf', extension: '.srf' },
|
||||||
|
{ mimetype: 'image/srw', extension: '.srw' },
|
||||||
|
{ mimetype: 'image/tiff', extension: '.tiff' },
|
||||||
|
{ mimetype: 'image/webp', extension: '.webp' },
|
||||||
|
{ mimetype: 'image/x-adobe-dng', extension: '.dng' },
|
||||||
|
{ mimetype: 'image/x-arriflex-ari', extension: '.ari' },
|
||||||
|
{ mimetype: 'image/x-canon-cr2', extension: '.cr2' },
|
||||||
|
{ mimetype: 'image/x-canon-cr3', extension: '.cr3' },
|
||||||
|
{ mimetype: 'image/x-canon-crw', extension: '.crw' },
|
||||||
|
{ mimetype: 'image/x-epson-erf', extension: '.erf' },
|
||||||
|
{ mimetype: 'image/x-fuji-raf', extension: '.raf' },
|
||||||
|
{ mimetype: 'image/x-hasselblad-3fr', extension: '.3fr' },
|
||||||
|
{ mimetype: 'image/x-hasselblad-fff', extension: '.fff' },
|
||||||
|
{ mimetype: 'image/x-kodak-dcr', extension: '.dcr' },
|
||||||
|
{ mimetype: 'image/x-kodak-k25', extension: '.k25' },
|
||||||
|
{ mimetype: 'image/x-kodak-kdc', extension: '.kdc' },
|
||||||
|
{ mimetype: 'image/x-leica-rwl', extension: '.rwl' },
|
||||||
|
{ mimetype: 'image/x-minolta-mrw', extension: '.mrw' },
|
||||||
|
{ mimetype: 'image/x-nikon-nef', extension: '.nef' },
|
||||||
|
{ mimetype: 'image/x-olympus-orf', extension: '.orf' },
|
||||||
|
{ mimetype: 'image/x-olympus-ori', extension: '.ori' },
|
||||||
|
{ mimetype: 'image/x-panasonic-raw', extension: '.raw' },
|
||||||
|
{ mimetype: 'image/x-pentax-pef', extension: '.pef' },
|
||||||
|
{ mimetype: 'image/x-phantom-cin', extension: '.cin' },
|
||||||
|
{ mimetype: 'image/x-phaseone-cap', extension: '.cap' },
|
||||||
|
{ mimetype: 'image/x-phaseone-iiq', extension: '.iiq' },
|
||||||
|
{ mimetype: 'image/x-samsung-srw', extension: '.srw' },
|
||||||
|
{ mimetype: 'image/x-sigma-x3f', extension: '.x3f' },
|
||||||
|
{ mimetype: 'image/x-sony-arw', extension: '.arw' },
|
||||||
|
{ mimetype: 'image/x-sony-sr2', extension: '.sr2' },
|
||||||
|
{ mimetype: 'image/x-sony-srf', extension: '.srf' },
|
||||||
|
{ mimetype: 'image/x3f', extension: '.x3f' },
|
||||||
|
{ mimetype: 'video/3gpp', extension: '.3gp' },
|
||||||
|
{ mimetype: 'video/avi', extension: '.avi' },
|
||||||
|
{ mimetype: 'video/mp2t', extension: '.m2ts' },
|
||||||
|
{ mimetype: 'video/mp2t', extension: '.mts' },
|
||||||
|
{ mimetype: 'video/mp4', extension: '.mp4' },
|
||||||
|
{ mimetype: 'video/mpeg', extension: '.mpg' },
|
||||||
|
{ mimetype: 'video/msvideo', extension: '.avi' },
|
||||||
|
{ mimetype: 'video/quicktime', extension: '.mov' },
|
||||||
|
{ mimetype: 'video/vnd.avi', extension: '.avi' },
|
||||||
|
{ mimetype: 'video/webm', extension: '.webm' },
|
||||||
|
{ mimetype: 'video/x-flv', extension: '.flv' },
|
||||||
|
{ mimetype: 'video/x-matroska', extension: '.mkv' },
|
||||||
|
{ mimetype: 'video/x-ms-wmv', extension: '.wmv' },
|
||||||
|
{ mimetype: 'video/x-msvideo', extension: '.avi' },
|
||||||
|
]) {
|
||||||
|
it(`should map ${extension} to ${mimetype}`, async () => {
|
||||||
|
expect({ ...mimeTypes.image, ...mimeTypes.video }[extension]).toContain(mimetype);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('profile', () => {
|
||||||
|
it('should contain only lowercase mime types', () => {
|
||||||
|
const keys = Object.keys(mimeTypes.profile);
|
||||||
|
expect(keys).toEqual(keys.map((mimeType) => mimeType.toLowerCase()));
|
||||||
|
|
||||||
|
const values = Object.values(mimeTypes.profile).flat();
|
||||||
|
expect(values).toEqual(values.map((mimeType) => mimeType.toLowerCase()));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be a sorted list', () => {
|
||||||
|
const keys = Object.keys(mimeTypes.profile);
|
||||||
|
// TODO: use toSorted in NodeJS 20.
|
||||||
|
expect(keys).toEqual([...keys].sort());
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const [ext, v] of Object.entries(mimeTypes.profile)) {
|
||||||
|
it(`should lookup ${ext}`, () => {
|
||||||
|
expect(mimeTypes.lookup(`test.${ext}`)).toEqual(v[0]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('image', () => {
|
||||||
|
it('should contain only lowercase mime types', () => {
|
||||||
|
const keys = Object.keys(mimeTypes.image);
|
||||||
|
expect(keys).toEqual(keys.map((mimeType) => mimeType.toLowerCase()));
|
||||||
|
|
||||||
|
const values = Object.values(mimeTypes.image).flat();
|
||||||
|
expect(values).toEqual(values.map((mimeType) => mimeType.toLowerCase()));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be a sorted list', () => {
|
||||||
|
const keys = Object.keys(mimeTypes.image);
|
||||||
|
// TODO: use toSorted in NodeJS 20.
|
||||||
|
expect(keys).toEqual([...keys].sort());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain only image mime types', () => {
|
||||||
|
const values = Object.values(mimeTypes.image).flat();
|
||||||
|
expect(values).toEqual(values.filter((mimeType) => mimeType.startsWith('image/')));
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const [ext, v] of Object.entries(mimeTypes.image)) {
|
||||||
|
it(`should lookup ${ext}`, () => {
|
||||||
|
expect(mimeTypes.lookup(`test.${ext}`)).toEqual(v[0]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('video', () => {
|
||||||
|
it('should contain only lowercase mime types', () => {
|
||||||
|
const keys = Object.keys(mimeTypes.video);
|
||||||
|
expect(keys).toEqual(keys.map((mimeType) => mimeType.toLowerCase()));
|
||||||
|
|
||||||
|
const values = Object.values(mimeTypes.video).flat();
|
||||||
|
expect(values).toEqual(values.map((mimeType) => mimeType.toLowerCase()));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be a sorted list', () => {
|
||||||
|
const keys = Object.keys(mimeTypes.video);
|
||||||
|
// TODO: use toSorted in NodeJS 20.
|
||||||
|
expect(keys).toEqual([...keys].sort());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain only video mime types', () => {
|
||||||
|
const values = Object.values(mimeTypes.video).flat();
|
||||||
|
expect(values).toEqual(values.filter((mimeType) => mimeType.startsWith('video/')));
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const [ext, v] of Object.entries(mimeTypes.video)) {
|
||||||
|
it(`should lookup ${ext}`, () => {
|
||||||
|
expect(mimeTypes.lookup(`test.${ext}`)).toEqual(v[0]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('sidecar', () => {
|
||||||
|
it('should contain only lowercase mime types', () => {
|
||||||
|
const keys = Object.keys(mimeTypes.sidecar);
|
||||||
|
expect(keys).toEqual(keys.map((mimeType) => mimeType.toLowerCase()));
|
||||||
|
|
||||||
|
const values = Object.values(mimeTypes.sidecar).flat();
|
||||||
|
expect(values).toEqual(values.map((mimeType) => mimeType.toLowerCase()));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be a sorted list', () => {
|
||||||
|
const keys = Object.keys(mimeTypes.sidecar);
|
||||||
|
// TODO: use toSorted in NodeJS 20.
|
||||||
|
expect(keys).toEqual([...keys].sort());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain only xml mime types', () => {
|
||||||
|
expect(Object.values(mimeTypes.sidecar).flat()).toEqual(['application/xml', 'text/xml']);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const [ext, v] of Object.entries(mimeTypes.sidecar)) {
|
||||||
|
it(`should lookup ${ext}`, () => {
|
||||||
|
expect(mimeTypes.lookup(`test.${ext}`)).toEqual(v[0]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
@ -30,70 +30,73 @@ export function assertMachineLearningEnabled() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const profile: Record<string, string> = {
|
const image: Record<string, string[]> = {
|
||||||
'.avif': 'image/avif',
|
'.3fr': ['image/3fr', 'image/x-hasselblad-3fr'],
|
||||||
'.dng': 'image/x-adobe-dng',
|
'.ari': ['image/ari', 'image/x-arriflex-ari'],
|
||||||
'.heic': 'image/heic',
|
'.arw': ['image/arw', 'image/x-sony-arw'],
|
||||||
'.heif': 'image/heif',
|
'.avif': ['image/avif'],
|
||||||
'.jpeg': 'image/jpeg',
|
'.cap': ['image/cap', 'image/x-phaseone-cap'],
|
||||||
'.jpg': 'image/jpeg',
|
'.cin': ['image/cin', 'image/x-phantom-cin'],
|
||||||
'.png': 'image/png',
|
'.cr2': ['image/cr2', 'image/x-canon-cr2'],
|
||||||
'.webp': 'image/webp',
|
'.cr3': ['image/cr3', 'image/x-canon-cr3'],
|
||||||
|
'.crw': ['image/crw', 'image/x-canon-crw'],
|
||||||
|
'.dcr': ['image/dcr', 'image/x-kodak-dcr'],
|
||||||
|
'.dng': ['image/dng', 'image/x-adobe-dng'],
|
||||||
|
'.erf': ['image/erf', 'image/x-epson-erf'],
|
||||||
|
'.fff': ['image/fff', 'image/x-hasselblad-fff'],
|
||||||
|
'.gif': ['image/gif'],
|
||||||
|
'.heic': ['image/heic'],
|
||||||
|
'.heif': ['image/heif'],
|
||||||
|
'.iiq': ['image/iiq', 'image/x-phaseone-iiq'],
|
||||||
|
'.jpeg': ['image/jpeg'],
|
||||||
|
'.jpg': ['image/jpeg'],
|
||||||
|
'.jxl': ['image/jxl'],
|
||||||
|
'.k25': ['image/k25', 'image/x-kodak-k25'],
|
||||||
|
'.kdc': ['image/kdc', 'image/x-kodak-kdc'],
|
||||||
|
'.mrw': ['image/mrw', 'image/x-minolta-mrw'],
|
||||||
|
'.nef': ['image/nef', 'image/x-nikon-nef'],
|
||||||
|
'.orf': ['image/orf', 'image/x-olympus-orf'],
|
||||||
|
'.ori': ['image/ori', 'image/x-olympus-ori'],
|
||||||
|
'.pef': ['image/pef', 'image/x-pentax-pef'],
|
||||||
|
'.png': ['image/png'],
|
||||||
|
'.raf': ['image/raf', 'image/x-fuji-raf'],
|
||||||
|
'.raw': ['image/raw', 'image/x-panasonic-raw'],
|
||||||
|
'.rwl': ['image/rwl', 'image/x-leica-rwl'],
|
||||||
|
'.sr2': ['image/sr2', 'image/x-sony-sr2'],
|
||||||
|
'.srf': ['image/srf', 'image/x-sony-srf'],
|
||||||
|
'.srw': ['image/srw', 'image/x-samsung-srw'],
|
||||||
|
'.tiff': ['image/tiff'],
|
||||||
|
'.webp': ['image/webp'],
|
||||||
|
'.x3f': ['image/x3f', 'image/x-sigma-x3f'],
|
||||||
};
|
};
|
||||||
|
|
||||||
const image: Record<string, string> = {
|
const profileExtensions = ['.avif', '.dng', '.heic', '.heif', '.jpeg', '.jpg', '.png', '.webp'];
|
||||||
...profile,
|
const profile: Record<string, string[]> = Object.fromEntries(
|
||||||
'.3fr': 'image/x-hasselblad-3fr',
|
Object.entries(image).filter(([key]) => profileExtensions.includes(key)),
|
||||||
'.ari': 'image/x-arriflex-ari',
|
);
|
||||||
'.arw': 'image/x-sony-arw',
|
|
||||||
'.cap': 'image/x-phaseone-cap',
|
const video: Record<string, string[]> = {
|
||||||
'.cin': 'image/x-phantom-cin',
|
'.3gp': ['video/3gpp'],
|
||||||
'.cr2': 'image/x-canon-cr2',
|
'.avi': ['video/avi', 'video/msvideo', 'video/vnd.avi', 'video/x-msvideo'],
|
||||||
'.cr3': 'image/x-canon-cr3',
|
'.flv': ['video/x-flv'],
|
||||||
'.crw': 'image/x-canon-crw',
|
'.m2ts': ['video/mp2t'],
|
||||||
'.dcr': 'image/x-kodak-dcr',
|
'.mkv': ['video/x-matroska'],
|
||||||
'.erf': 'image/x-epson-erf',
|
'.mov': ['video/quicktime'],
|
||||||
'.fff': 'image/x-hasselblad-fff',
|
'.mp4': ['video/mp4'],
|
||||||
'.gif': 'image/gif',
|
'.mpg': ['video/mpeg'],
|
||||||
'.iiq': 'image/x-phaseone-iiq',
|
'.mts': ['video/mp2t'],
|
||||||
'.k25': 'image/x-kodak-k25',
|
'.webm': ['video/webm'],
|
||||||
'.kdc': 'image/x-kodak-kdc',
|
'.wmv': ['video/x-ms-wmv'],
|
||||||
'.mrw': 'image/x-minolta-mrw',
|
|
||||||
'.nef': 'image/x-nikon-nef',
|
|
||||||
'.orf': 'image/x-olympus-orf',
|
|
||||||
'.ori': 'image/x-olympus-ori',
|
|
||||||
'.pef': 'image/x-pentax-pef',
|
|
||||||
'.raf': 'image/x-fuji-raf',
|
|
||||||
'.raw': 'image/x-panasonic-raw',
|
|
||||||
'.rwl': 'image/x-leica-rwl',
|
|
||||||
'.sr2': 'image/x-sony-sr2',
|
|
||||||
'.srf': 'image/x-sony-srf',
|
|
||||||
'.srw': 'image/x-samsung-srw',
|
|
||||||
'.tiff': 'image/tiff',
|
|
||||||
'.x3f': 'image/x-sigma-x3f',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const video: Record<string, string> = {
|
const sidecar: Record<string, string[]> = {
|
||||||
'.3gp': 'video/3gpp',
|
'.xmp': ['application/xml', 'text/xml'],
|
||||||
'.avi': 'video/x-msvideo',
|
|
||||||
'.flv': 'video/x-flv',
|
|
||||||
'.mkv': 'video/x-matroska',
|
|
||||||
'.mov': 'video/quicktime',
|
|
||||||
'.mp2t': 'video/mp2t',
|
|
||||||
'.mp4': 'video/mp4',
|
|
||||||
'.mpeg': 'video/mpeg',
|
|
||||||
'.webm': 'video/webm',
|
|
||||||
'.wmv': 'video/x-ms-wmv',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const sidecar: Record<string, string> = {
|
const isType = (filename: string, r: Record<string, string[]>) => extname(filename).toLowerCase() in r;
|
||||||
'.xmp': 'application/xml',
|
|
||||||
};
|
|
||||||
|
|
||||||
const isType = (filename: string, lookup: Record<string, string>) => !!lookup[extname(filename).toLowerCase()];
|
|
||||||
const getType = (filename: string, lookup: Record<string, string>) => lookup[extname(filename).toLowerCase()];
|
|
||||||
const lookup = (filename: string) =>
|
const lookup = (filename: string) =>
|
||||||
getType(filename, { ...image, ...video, ...sidecar }) || 'application/octet-stream';
|
({ ...image, ...video, ...sidecar }[extname(filename).toLowerCase()]?.[0] ?? 'application/octet-stream');
|
||||||
|
|
||||||
export const mimeTypes = {
|
export const mimeTypes = {
|
||||||
image,
|
image,
|
||||||
@ -107,14 +110,12 @@ export const mimeTypes = {
|
|||||||
isVideo: (filename: string) => isType(filename, video),
|
isVideo: (filename: string) => isType(filename, video),
|
||||||
lookup,
|
lookup,
|
||||||
assetType: (filename: string) => {
|
assetType: (filename: string) => {
|
||||||
const contentType = lookup(filename).split('/')[0];
|
const contentType = lookup(filename);
|
||||||
switch (contentType) {
|
if (contentType.startsWith('image/')) {
|
||||||
case 'image':
|
|
||||||
return AssetType.IMAGE;
|
return AssetType.IMAGE;
|
||||||
case 'video':
|
} else if (contentType.startsWith('video/')) {
|
||||||
return AssetType.VIDEO;
|
return AssetType.VIDEO;
|
||||||
default:
|
|
||||||
return AssetType.OTHER;
|
|
||||||
}
|
}
|
||||||
|
return AssetType.OTHER;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ICryptoRepository, IJobRepository, IStorageRepository, JobName, mimeTypes } from '@app/domain';
|
import { ICryptoRepository, IJobRepository, IStorageRepository, JobName } from '@app/domain';
|
||||||
import { AssetEntity, AssetType, ExifEntity } from '@app/infra/entities';
|
import { AssetEntity, AssetType, ExifEntity } from '@app/infra/entities';
|
||||||
import { BadRequestException } from '@nestjs/common';
|
import { BadRequestException } from '@nestjs/common';
|
||||||
import {
|
import {
|
||||||
@ -139,84 +139,6 @@ describe('AssetService', () => {
|
|||||||
.mockResolvedValue(assetEntityStub.livePhotoMotionAsset);
|
.mockResolvedValue(assetEntityStub.livePhotoMotionAsset);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('mime types linting', () => {
|
|
||||||
describe('profile', () => {
|
|
||||||
it('should contain only lowercase mime types', () => {
|
|
||||||
const keys = Object.keys(mimeTypes.profile);
|
|
||||||
expect(keys).toEqual(keys.map((mimeType) => mimeType.toLowerCase()));
|
|
||||||
const values = Object.values(mimeTypes.profile);
|
|
||||||
expect(values).toEqual(values.map((mimeType) => mimeType.toLowerCase()));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be a sorted list', () => {
|
|
||||||
const keys = Object.keys(mimeTypes.profile);
|
|
||||||
expect(keys).toEqual([...keys].sort());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('image', () => {
|
|
||||||
it('should contain only lowercase mime types', () => {
|
|
||||||
const keys = Object.keys(mimeTypes.image);
|
|
||||||
expect(keys).toEqual(keys.map((mimeType) => mimeType.toLowerCase()));
|
|
||||||
const values = Object.values(mimeTypes.image);
|
|
||||||
expect(values).toEqual(values.map((mimeType) => mimeType.toLowerCase()));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be a sorted list', () => {
|
|
||||||
const keys = Object.keys(mimeTypes.image).filter((key) => key in mimeTypes.profile === false);
|
|
||||||
expect(keys).toEqual([...keys].sort());
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should contain only image mime types', () => {
|
|
||||||
expect(Object.values(mimeTypes.image)).toEqual(
|
|
||||||
Object.values(mimeTypes.image).filter((mimeType) => mimeType.startsWith('image/')),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('video', () => {
|
|
||||||
it('should contain only lowercase mime types', () => {
|
|
||||||
const keys = Object.keys(mimeTypes.video);
|
|
||||||
expect(keys).toEqual(keys.map((mimeType) => mimeType.toLowerCase()));
|
|
||||||
const values = Object.values(mimeTypes.video);
|
|
||||||
expect(values).toEqual(values.map((mimeType) => mimeType.toLowerCase()));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be a sorted list', () => {
|
|
||||||
const keys = Object.keys(mimeTypes.video);
|
|
||||||
expect(keys).toEqual([...keys].sort());
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should contain only video mime types', () => {
|
|
||||||
expect(Object.values(mimeTypes.video)).toEqual(
|
|
||||||
Object.values(mimeTypes.video).filter((mimeType) => mimeType.startsWith('video/')),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('sidecar', () => {
|
|
||||||
it('should contain only lowercase mime types', () => {
|
|
||||||
const keys = Object.keys(mimeTypes.sidecar);
|
|
||||||
expect(keys).toEqual(keys.map((mimeType) => mimeType.toLowerCase()));
|
|
||||||
const values = Object.values(mimeTypes.sidecar);
|
|
||||||
expect(values).toEqual(values.map((mimeType) => mimeType.toLowerCase()));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be a sorted list', () => {
|
|
||||||
const keys = Object.keys(mimeTypes.sidecar);
|
|
||||||
expect(keys).toEqual([...keys].sort());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('sidecar', () => {
|
|
||||||
it('should contain only be xml mime type', () => {
|
|
||||||
expect(Object.values(mimeTypes.sidecar)).toEqual(
|
|
||||||
Object.values(mimeTypes.sidecar).filter((mimeType) => mimeType === 'application/xml'),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('uploadFile', () => {
|
describe('uploadFile', () => {
|
||||||
it('should handle a file upload', async () => {
|
it('should handle a file upload', async () => {
|
||||||
const assetEntity = _getAsset_1();
|
const assetEntity = _getAsset_1();
|
||||||
|
Loading…
Reference in New Issue
Block a user