refactor: library type (#9525)
This commit is contained in:
@@ -32,7 +32,6 @@ const _getCreateAssetDto = (): CreateAssetDto => {
|
||||
createAssetDto.isFavorite = false;
|
||||
createAssetDto.isArchived = false;
|
||||
createAssetDto.duration = '0:00:00.000000';
|
||||
createAssetDto.libraryId = 'libraryId';
|
||||
|
||||
return createAssetDto;
|
||||
};
|
||||
@@ -121,7 +120,6 @@ describe('AssetService', () => {
|
||||
const dto = _getCreateAssetDto();
|
||||
|
||||
assetMock.create.mockResolvedValue(assetEntity);
|
||||
accessMock.library.checkOwnerAccess.mockResolvedValue(new Set([dto.libraryId!]));
|
||||
|
||||
await expect(sut.uploadFile(authStub.user1, dto, file)).resolves.toEqual({ duplicate: false, id: 'id_1' });
|
||||
|
||||
@@ -149,7 +147,6 @@ describe('AssetService', () => {
|
||||
|
||||
assetMock.create.mockRejectedValue(error);
|
||||
assetRepositoryMockV1.getAssetsByChecksums.mockResolvedValue([_getAsset_1()]);
|
||||
accessMock.library.checkOwnerAccess.mockResolvedValue(new Set([dto.libraryId!]));
|
||||
|
||||
await expect(sut.uploadFile(authStub.user1, dto, file)).resolves.toEqual({ duplicate: true, id: 'id_1' });
|
||||
|
||||
@@ -167,7 +164,6 @@ describe('AssetService', () => {
|
||||
|
||||
assetMock.create.mockResolvedValueOnce(assetStub.livePhotoMotionAsset);
|
||||
assetMock.create.mockResolvedValueOnce(assetStub.livePhotoStillAsset);
|
||||
accessMock.library.checkOwnerAccess.mockResolvedValue(new Set([dto.libraryId!]));
|
||||
|
||||
await expect(
|
||||
sut.uploadFile(authStub.user1, dto, fileStub.livePhotoStill, fileStub.livePhotoMotion),
|
||||
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
} from 'src/dtos/asset-v1.dto';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { ASSET_CHECKSUM_CONSTRAINT, AssetEntity, AssetType } from 'src/entities/asset.entity';
|
||||
import { LibraryType } from 'src/entities/library.entity';
|
||||
import { IAccessRepository } from 'src/interfaces/access.interface';
|
||||
import { IAssetRepositoryV1 } from 'src/interfaces/asset-v1.interface';
|
||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
||||
@@ -76,15 +75,20 @@ export class AssetServiceV1 {
|
||||
let livePhotoAsset: AssetEntity | null = null;
|
||||
|
||||
try {
|
||||
const libraryId = await this.getLibraryId(auth, dto.libraryId);
|
||||
await this.access.requirePermission(auth, Permission.ASSET_UPLOAD, libraryId);
|
||||
await this.access.requirePermission(
|
||||
auth,
|
||||
Permission.ASSET_UPLOAD,
|
||||
// do not need an id here, but the interface requires it
|
||||
auth.user.id,
|
||||
);
|
||||
|
||||
this.requireQuota(auth, file.size);
|
||||
if (livePhotoFile) {
|
||||
const livePhotoDto = { ...dto, assetType: AssetType.VIDEO, isVisible: false, libraryId };
|
||||
const livePhotoDto = { ...dto, assetType: AssetType.VIDEO, isVisible: false };
|
||||
livePhotoAsset = await this.create(auth, livePhotoDto, livePhotoFile);
|
||||
}
|
||||
|
||||
const asset = await this.create(auth, { ...dto, libraryId }, file, livePhotoAsset?.id, sidecarFile?.originalPath);
|
||||
const asset = await this.create(auth, dto, file, livePhotoAsset?.id, sidecarFile?.originalPath);
|
||||
|
||||
await this.userRepository.updateUsage(auth.user.id, (livePhotoFile?.size || 0) + file.size);
|
||||
|
||||
@@ -245,36 +249,16 @@ export class AssetServiceV1 {
|
||||
return asset.previewPath;
|
||||
}
|
||||
|
||||
private async getLibraryId(auth: AuthDto, libraryId?: string) {
|
||||
if (libraryId) {
|
||||
return libraryId;
|
||||
}
|
||||
|
||||
let library = await this.libraryRepository.getDefaultUploadLibrary(auth.user.id);
|
||||
if (!library) {
|
||||
library = await this.libraryRepository.create({
|
||||
ownerId: auth.user.id,
|
||||
name: 'Default Library',
|
||||
assets: [],
|
||||
type: LibraryType.UPLOAD,
|
||||
importPaths: [],
|
||||
exclusionPatterns: [],
|
||||
});
|
||||
}
|
||||
|
||||
return library.id;
|
||||
}
|
||||
|
||||
private async create(
|
||||
auth: AuthDto,
|
||||
dto: CreateAssetDto & { libraryId: string },
|
||||
dto: CreateAssetDto,
|
||||
file: UploadFile,
|
||||
livePhotoAssetId?: string,
|
||||
sidecarPath?: string,
|
||||
): Promise<AssetEntity> {
|
||||
const asset = await this.assetRepository.create({
|
||||
ownerId: auth.user.id,
|
||||
libraryId: dto.libraryId,
|
||||
libraryId: null,
|
||||
|
||||
checksum: file.checksum,
|
||||
originalPath: file.originalPath,
|
||||
|
||||
@@ -27,7 +27,6 @@ import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { MapMarkerDto, MapMarkerResponseDto, MemoryLaneDto } from 'src/dtos/search.dto';
|
||||
import { UpdateStackParentDto } from 'src/dtos/stack.dto';
|
||||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { LibraryType } from 'src/entities/library.entity';
|
||||
import { IAccessRepository } from 'src/interfaces/access.interface';
|
||||
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
||||
import { IAssetStackRepository } from 'src/interfaces/asset-stack.interface';
|
||||
@@ -424,7 +423,7 @@ export class AssetService {
|
||||
}
|
||||
|
||||
await this.assetRepository.remove(asset);
|
||||
if (asset.library.type === LibraryType.UPLOAD) {
|
||||
if (!asset.libraryId) {
|
||||
await this.userRepository.updateUsage(asset.ownerId, -(asset.exifInfo?.fileSizeInByte || 0));
|
||||
}
|
||||
this.eventRepository.clientSend(ClientEvent.ASSET_DELETE, asset.ownerId, id);
|
||||
@@ -436,7 +435,7 @@ export class AssetService {
|
||||
|
||||
const files = [asset.thumbnailPath, asset.previewPath, asset.encodedVideoPath];
|
||||
// skip originals if the user deleted the whole library
|
||||
if (!asset.library.deletedAt) {
|
||||
if (!asset.library?.deletedAt) {
|
||||
files.push(asset.sidecarPath, asset.originalPath);
|
||||
}
|
||||
|
||||
|
||||
@@ -180,7 +180,6 @@ describe(DownloadService.name, () => {
|
||||
});
|
||||
|
||||
it('should return a list of archives (userId)', async () => {
|
||||
accessMock.library.checkOwnerAccess.mockResolvedValue(new Set([authStub.admin.user.id]));
|
||||
assetMock.getByUserId.mockResolvedValue({
|
||||
items: [assetStub.image, assetStub.video],
|
||||
hasNextPage: false,
|
||||
@@ -196,8 +195,6 @@ describe(DownloadService.name, () => {
|
||||
});
|
||||
|
||||
it('should split archives by size', async () => {
|
||||
accessMock.library.checkOwnerAccess.mockResolvedValue(new Set([authStub.admin.user.id]));
|
||||
|
||||
assetMock.getByUserId.mockResolvedValue({
|
||||
items: [
|
||||
{ ...assetStub.image, id: 'asset-1' },
|
||||
|
||||
@@ -4,7 +4,6 @@ import { SystemConfig } from 'src/config';
|
||||
import { SystemConfigCore } from 'src/cores/system-config.core';
|
||||
import { mapLibrary } from 'src/dtos/library.dto';
|
||||
import { AssetType } from 'src/entities/asset.entity';
|
||||
import { LibraryType } from 'src/entities/library.entity';
|
||||
import { UserEntity } from 'src/entities/user.entity';
|
||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||
@@ -213,18 +212,6 @@ describe(LibraryService.name, () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not scan upload libraries', async () => {
|
||||
const mockLibraryJob: ILibraryRefreshJob = {
|
||||
id: libraryStub.externalLibrary1.id,
|
||||
refreshModifiedFiles: false,
|
||||
refreshAllFiles: false,
|
||||
};
|
||||
|
||||
libraryMock.get.mockResolvedValue(libraryStub.uploadLibrary1);
|
||||
|
||||
await expect(sut.handleQueueAssetRefresh(mockLibraryJob)).resolves.toBe(JobStatus.FAILED);
|
||||
});
|
||||
|
||||
it('should ignore import paths that do not exist', async () => {
|
||||
storageMock.stat.mockImplementation((path): Promise<Stats> => {
|
||||
if (path === libraryStub.externalLibraryWithImportPaths1.importPaths[0]) {
|
||||
@@ -707,7 +694,6 @@ describe(LibraryService.name, () => {
|
||||
describe('delete', () => {
|
||||
it('should delete a library', async () => {
|
||||
assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.image);
|
||||
libraryMock.getUploadLibraryCount.mockResolvedValue(2);
|
||||
libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1);
|
||||
|
||||
await sut.delete(libraryStub.externalLibrary1.id);
|
||||
@@ -720,21 +706,8 @@ describe(LibraryService.name, () => {
|
||||
expect(libraryMock.softDelete).toHaveBeenCalledWith(libraryStub.externalLibrary1.id);
|
||||
});
|
||||
|
||||
it('should throw error if the last upload library is deleted', async () => {
|
||||
assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.image);
|
||||
libraryMock.getUploadLibraryCount.mockResolvedValue(1);
|
||||
libraryMock.get.mockResolvedValue(libraryStub.uploadLibrary1);
|
||||
|
||||
await expect(sut.delete(libraryStub.uploadLibrary1.id)).rejects.toBeInstanceOf(BadRequestException);
|
||||
|
||||
expect(jobMock.queue).not.toHaveBeenCalled();
|
||||
expect(jobMock.queueAll).not.toHaveBeenCalled();
|
||||
expect(libraryMock.softDelete).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should allow an external library to be deleted', async () => {
|
||||
assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.image);
|
||||
libraryMock.getUploadLibraryCount.mockResolvedValue(1);
|
||||
libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1);
|
||||
|
||||
await sut.delete(libraryStub.externalLibrary1.id);
|
||||
@@ -749,7 +722,6 @@ describe(LibraryService.name, () => {
|
||||
|
||||
it('should unwatch an external library when deleted', async () => {
|
||||
assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.image);
|
||||
libraryMock.getUploadLibraryCount.mockResolvedValue(1);
|
||||
libraryMock.get.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1);
|
||||
libraryMock.getAll.mockResolvedValue([libraryStub.externalLibraryWithImportPaths1]);
|
||||
|
||||
@@ -767,37 +739,37 @@ describe(LibraryService.name, () => {
|
||||
|
||||
describe('get', () => {
|
||||
it('should return a library', async () => {
|
||||
libraryMock.get.mockResolvedValue(libraryStub.uploadLibrary1);
|
||||
await expect(sut.get(libraryStub.uploadLibrary1.id)).resolves.toEqual(
|
||||
libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1);
|
||||
await expect(sut.get(libraryStub.externalLibrary1.id)).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
id: libraryStub.uploadLibrary1.id,
|
||||
name: libraryStub.uploadLibrary1.name,
|
||||
ownerId: libraryStub.uploadLibrary1.ownerId,
|
||||
id: libraryStub.externalLibrary1.id,
|
||||
name: libraryStub.externalLibrary1.name,
|
||||
ownerId: libraryStub.externalLibrary1.ownerId,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(libraryMock.get).toHaveBeenCalledWith(libraryStub.uploadLibrary1.id);
|
||||
expect(libraryMock.get).toHaveBeenCalledWith(libraryStub.externalLibrary1.id);
|
||||
});
|
||||
|
||||
it('should throw an error when a library is not found', async () => {
|
||||
libraryMock.get.mockResolvedValue(null);
|
||||
await expect(sut.get(libraryStub.uploadLibrary1.id)).rejects.toBeInstanceOf(BadRequestException);
|
||||
expect(libraryMock.get).toHaveBeenCalledWith(libraryStub.uploadLibrary1.id);
|
||||
await expect(sut.get(libraryStub.externalLibrary1.id)).rejects.toBeInstanceOf(BadRequestException);
|
||||
expect(libraryMock.get).toHaveBeenCalledWith(libraryStub.externalLibrary1.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getStatistics', () => {
|
||||
it('should return library statistics', async () => {
|
||||
libraryMock.get.mockResolvedValue(libraryStub.uploadLibrary1);
|
||||
libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1);
|
||||
libraryMock.getStatistics.mockResolvedValue({ photos: 10, videos: 0, total: 10, usage: 1337 });
|
||||
await expect(sut.getStatistics(libraryStub.uploadLibrary1.id)).resolves.toEqual({
|
||||
await expect(sut.getStatistics(libraryStub.externalLibrary1.id)).resolves.toEqual({
|
||||
photos: 10,
|
||||
videos: 0,
|
||||
total: 10,
|
||||
usage: 1337,
|
||||
});
|
||||
|
||||
expect(libraryMock.getStatistics).toHaveBeenCalledWith(libraryStub.uploadLibrary1.id);
|
||||
expect(libraryMock.getStatistics).toHaveBeenCalledWith(libraryStub.externalLibrary1.id);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -805,10 +777,9 @@ describe(LibraryService.name, () => {
|
||||
describe('external library', () => {
|
||||
it('should create with default settings', async () => {
|
||||
libraryMock.create.mockResolvedValue(libraryStub.externalLibrary1);
|
||||
await expect(sut.create({ ownerId: authStub.admin.user.id, type: LibraryType.EXTERNAL })).resolves.toEqual(
|
||||
await expect(sut.create({ ownerId: authStub.admin.user.id })).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
id: libraryStub.externalLibrary1.id,
|
||||
type: LibraryType.EXTERNAL,
|
||||
name: libraryStub.externalLibrary1.name,
|
||||
ownerId: libraryStub.externalLibrary1.ownerId,
|
||||
assetCount: 0,
|
||||
@@ -823,7 +794,6 @@ describe(LibraryService.name, () => {
|
||||
expect(libraryMock.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name: expect.any(String),
|
||||
type: LibraryType.EXTERNAL,
|
||||
importPaths: [],
|
||||
exclusionPatterns: [],
|
||||
}),
|
||||
@@ -832,12 +802,9 @@ describe(LibraryService.name, () => {
|
||||
|
||||
it('should create with name', async () => {
|
||||
libraryMock.create.mockResolvedValue(libraryStub.externalLibrary1);
|
||||
await expect(
|
||||
sut.create({ ownerId: authStub.admin.user.id, type: LibraryType.EXTERNAL, name: 'My Awesome Library' }),
|
||||
).resolves.toEqual(
|
||||
await expect(sut.create({ ownerId: authStub.admin.user.id, name: 'My Awesome Library' })).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
id: libraryStub.externalLibrary1.id,
|
||||
type: LibraryType.EXTERNAL,
|
||||
name: libraryStub.externalLibrary1.name,
|
||||
ownerId: libraryStub.externalLibrary1.ownerId,
|
||||
assetCount: 0,
|
||||
@@ -852,7 +819,6 @@ describe(LibraryService.name, () => {
|
||||
expect(libraryMock.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name: 'My Awesome Library',
|
||||
type: LibraryType.EXTERNAL,
|
||||
importPaths: [],
|
||||
exclusionPatterns: [],
|
||||
}),
|
||||
@@ -864,13 +830,11 @@ describe(LibraryService.name, () => {
|
||||
await expect(
|
||||
sut.create({
|
||||
ownerId: authStub.admin.user.id,
|
||||
type: LibraryType.EXTERNAL,
|
||||
importPaths: ['/data/images', '/data/videos'],
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
id: libraryStub.externalLibrary1.id,
|
||||
type: LibraryType.EXTERNAL,
|
||||
name: libraryStub.externalLibrary1.name,
|
||||
ownerId: libraryStub.externalLibrary1.ownerId,
|
||||
assetCount: 0,
|
||||
@@ -885,7 +849,6 @@ describe(LibraryService.name, () => {
|
||||
expect(libraryMock.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name: expect.any(String),
|
||||
type: LibraryType.EXTERNAL,
|
||||
importPaths: ['/data/images', '/data/videos'],
|
||||
exclusionPatterns: [],
|
||||
}),
|
||||
@@ -901,7 +864,6 @@ describe(LibraryService.name, () => {
|
||||
await sut.init();
|
||||
await sut.create({
|
||||
ownerId: authStub.admin.user.id,
|
||||
type: LibraryType.EXTERNAL,
|
||||
importPaths: libraryStub.externalLibraryWithImportPaths1.importPaths,
|
||||
});
|
||||
});
|
||||
@@ -911,13 +873,11 @@ describe(LibraryService.name, () => {
|
||||
await expect(
|
||||
sut.create({
|
||||
ownerId: authStub.admin.user.id,
|
||||
type: LibraryType.EXTERNAL,
|
||||
exclusionPatterns: ['*.tmp', '*.bak'],
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
id: libraryStub.externalLibrary1.id,
|
||||
type: LibraryType.EXTERNAL,
|
||||
name: libraryStub.externalLibrary1.name,
|
||||
ownerId: libraryStub.externalLibrary1.ownerId,
|
||||
assetCount: 0,
|
||||
@@ -932,105 +892,22 @@ describe(LibraryService.name, () => {
|
||||
expect(libraryMock.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name: expect.any(String),
|
||||
type: LibraryType.EXTERNAL,
|
||||
importPaths: [],
|
||||
exclusionPatterns: ['*.tmp', '*.bak'],
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('upload library', () => {
|
||||
it('should create with default settings', async () => {
|
||||
libraryMock.create.mockResolvedValue(libraryStub.uploadLibrary1);
|
||||
await expect(sut.create({ ownerId: authStub.admin.user.id, type: LibraryType.UPLOAD })).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
id: libraryStub.uploadLibrary1.id,
|
||||
type: LibraryType.UPLOAD,
|
||||
name: libraryStub.uploadLibrary1.name,
|
||||
ownerId: libraryStub.uploadLibrary1.ownerId,
|
||||
assetCount: 0,
|
||||
importPaths: [],
|
||||
exclusionPatterns: [],
|
||||
createdAt: libraryStub.uploadLibrary1.createdAt,
|
||||
updatedAt: libraryStub.uploadLibrary1.updatedAt,
|
||||
refreshedAt: null,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(libraryMock.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name: 'New Upload Library',
|
||||
type: LibraryType.UPLOAD,
|
||||
importPaths: [],
|
||||
exclusionPatterns: [],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should create with name', async () => {
|
||||
libraryMock.create.mockResolvedValue(libraryStub.uploadLibrary1);
|
||||
await expect(
|
||||
sut.create({ ownerId: authStub.admin.user.id, type: LibraryType.UPLOAD, name: 'My Awesome Library' }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
id: libraryStub.uploadLibrary1.id,
|
||||
type: LibraryType.UPLOAD,
|
||||
name: libraryStub.uploadLibrary1.name,
|
||||
ownerId: libraryStub.uploadLibrary1.ownerId,
|
||||
assetCount: 0,
|
||||
importPaths: [],
|
||||
exclusionPatterns: [],
|
||||
createdAt: libraryStub.uploadLibrary1.createdAt,
|
||||
updatedAt: libraryStub.uploadLibrary1.updatedAt,
|
||||
refreshedAt: null,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(libraryMock.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name: 'My Awesome Library',
|
||||
type: LibraryType.UPLOAD,
|
||||
importPaths: [],
|
||||
exclusionPatterns: [],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should not create with import paths', async () => {
|
||||
await expect(
|
||||
sut.create({
|
||||
ownerId: authStub.admin.user.id,
|
||||
type: LibraryType.UPLOAD,
|
||||
importPaths: ['/data/images', '/data/videos'],
|
||||
}),
|
||||
).rejects.toBeInstanceOf(BadRequestException);
|
||||
|
||||
expect(libraryMock.create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not create with exclusion patterns', async () => {
|
||||
await expect(
|
||||
sut.create({
|
||||
ownerId: authStub.admin.user.id,
|
||||
type: LibraryType.UPLOAD,
|
||||
exclusionPatterns: ['*.tmp', '*.bak'],
|
||||
}),
|
||||
).rejects.toBeInstanceOf(BadRequestException);
|
||||
|
||||
expect(libraryMock.create).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleQueueCleanup', () => {
|
||||
it('should queue cleanup jobs', async () => {
|
||||
libraryMock.getAllDeleted.mockResolvedValue([libraryStub.uploadLibrary1, libraryStub.externalLibrary1]);
|
||||
libraryMock.getAllDeleted.mockResolvedValue([libraryStub.externalLibrary1, libraryStub.externalLibrary2]);
|
||||
await expect(sut.handleQueueCleanup()).resolves.toBe(JobStatus.SUCCESS);
|
||||
|
||||
expect(jobMock.queueAll).toHaveBeenCalledWith([
|
||||
{ name: JobName.LIBRARY_DELETE, data: { id: libraryStub.uploadLibrary1.id } },
|
||||
{ name: JobName.LIBRARY_DELETE, data: { id: libraryStub.externalLibrary1.id } },
|
||||
{ name: JobName.LIBRARY_DELETE, data: { id: libraryStub.externalLibrary2.id } },
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -1044,9 +921,9 @@ describe(LibraryService.name, () => {
|
||||
});
|
||||
|
||||
it('should update library', async () => {
|
||||
libraryMock.update.mockResolvedValue(libraryStub.uploadLibrary1);
|
||||
libraryMock.get.mockResolvedValue(libraryStub.uploadLibrary1);
|
||||
await expect(sut.update('library-id', {})).resolves.toEqual(mapLibrary(libraryStub.uploadLibrary1));
|
||||
libraryMock.update.mockResolvedValue(libraryStub.externalLibrary1);
|
||||
libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1);
|
||||
await expect(sut.update('library-id', {})).resolves.toEqual(mapLibrary(libraryStub.externalLibrary1));
|
||||
expect(libraryMock.update).toHaveBeenCalledWith(expect.objectContaining({ id: 'library-id' }));
|
||||
});
|
||||
});
|
||||
@@ -1109,15 +986,6 @@ describe(LibraryService.name, () => {
|
||||
expect(storageMock.watch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should throw error when watching upload library', async () => {
|
||||
libraryMock.get.mockResolvedValue(libraryStub.uploadLibrary1);
|
||||
libraryMock.getAll.mockResolvedValue([libraryStub.uploadLibrary1]);
|
||||
|
||||
await expect(sut.watchAll()).rejects.toThrow('Can only watch external libraries');
|
||||
|
||||
expect(storageMock.watch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle a new file event', async () => {
|
||||
libraryMock.get.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1);
|
||||
libraryMock.getAll.mockResolvedValue([libraryStub.externalLibraryWithImportPaths1]);
|
||||
@@ -1253,25 +1121,25 @@ describe(LibraryService.name, () => {
|
||||
libraryMock.getAssetIds.mockResolvedValue([]);
|
||||
libraryMock.delete.mockImplementation(async () => {});
|
||||
|
||||
await expect(sut.handleDeleteLibrary({ id: libraryStub.uploadLibrary1.id })).resolves.toBe(JobStatus.FAILED);
|
||||
await expect(sut.handleDeleteLibrary({ id: libraryStub.externalLibrary1.id })).resolves.toBe(JobStatus.FAILED);
|
||||
});
|
||||
|
||||
it('should delete an empty library', async () => {
|
||||
libraryMock.get.mockResolvedValue(libraryStub.uploadLibrary1);
|
||||
libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1);
|
||||
libraryMock.getAssetIds.mockResolvedValue([]);
|
||||
libraryMock.delete.mockImplementation(async () => {});
|
||||
|
||||
await expect(sut.handleDeleteLibrary({ id: libraryStub.uploadLibrary1.id })).resolves.toBe(JobStatus.SUCCESS);
|
||||
await expect(sut.handleDeleteLibrary({ id: libraryStub.externalLibrary1.id })).resolves.toBe(JobStatus.SUCCESS);
|
||||
});
|
||||
|
||||
it('should delete a library with assets', async () => {
|
||||
libraryMock.get.mockResolvedValue(libraryStub.uploadLibrary1);
|
||||
libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1);
|
||||
libraryMock.getAssetIds.mockResolvedValue([assetStub.image1.id]);
|
||||
libraryMock.delete.mockImplementation(async () => {});
|
||||
|
||||
assetMock.getById.mockResolvedValue(assetStub.image1);
|
||||
|
||||
await expect(sut.handleDeleteLibrary({ id: libraryStub.uploadLibrary1.id })).resolves.toBe(JobStatus.SUCCESS);
|
||||
await expect(sut.handleDeleteLibrary({ id: libraryStub.externalLibrary1.id })).resolves.toBe(JobStatus.SUCCESS);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1295,14 +1163,6 @@ describe(LibraryService.name, () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not queue a library scan of upload library', async () => {
|
||||
libraryMock.get.mockResolvedValue(libraryStub.uploadLibrary1);
|
||||
|
||||
await expect(sut.queueScan(libraryStub.uploadLibrary1.id, {})).rejects.toBeInstanceOf(BadRequestException);
|
||||
|
||||
expect(jobMock.queue).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should queue a library scan of all modified assets', async () => {
|
||||
libraryMock.get.mockResolvedValue(libraryStub.externalLibrary1);
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
LibraryResponseDto,
|
||||
LibraryStatsResponseDto,
|
||||
ScanLibraryDto,
|
||||
SearchLibraryDto,
|
||||
UpdateLibraryDto,
|
||||
ValidateLibraryDto,
|
||||
ValidateLibraryImportPathResponseDto,
|
||||
@@ -20,7 +19,7 @@ import {
|
||||
mapLibrary,
|
||||
} from 'src/dtos/library.dto';
|
||||
import { AssetType } from 'src/entities/asset.entity';
|
||||
import { LibraryEntity, LibraryType } from 'src/entities/library.entity';
|
||||
import { LibraryEntity } from 'src/entities/library.entity';
|
||||
import { IAssetRepository, WithProperty } from 'src/interfaces/asset.interface';
|
||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||
import { DatabaseLock, IDatabaseRepository } from 'src/interfaces/database.interface';
|
||||
@@ -118,10 +117,7 @@ export class LibraryService {
|
||||
}
|
||||
|
||||
const library = await this.findOrFail(id);
|
||||
|
||||
if (library.type !== LibraryType.EXTERNAL) {
|
||||
throw new BadRequestException('Can only watch external libraries');
|
||||
} else if (library.importPaths.length === 0) {
|
||||
if (library.importPaths.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -212,8 +208,7 @@ export class LibraryService {
|
||||
return false;
|
||||
}
|
||||
|
||||
const libraries = await this.repository.getAll(false, LibraryType.EXTERNAL);
|
||||
|
||||
const libraries = await this.repository.getAll(false);
|
||||
for (const library of libraries) {
|
||||
await this.watch(library.id);
|
||||
}
|
||||
@@ -229,8 +224,8 @@ export class LibraryService {
|
||||
return mapLibrary(library);
|
||||
}
|
||||
|
||||
async getAll(dto: SearchLibraryDto): Promise<LibraryResponseDto[]> {
|
||||
const libraries = await this.repository.getAll(false, dto.type);
|
||||
async getAll(): Promise<LibraryResponseDto[]> {
|
||||
const libraries = await this.repository.getAll(false);
|
||||
return libraries.map((library) => mapLibrary(library));
|
||||
}
|
||||
|
||||
@@ -244,37 +239,12 @@ export class LibraryService {
|
||||
}
|
||||
|
||||
async create(dto: CreateLibraryDto): Promise<LibraryResponseDto> {
|
||||
switch (dto.type) {
|
||||
case LibraryType.EXTERNAL: {
|
||||
if (!dto.name) {
|
||||
dto.name = 'New External Library';
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LibraryType.UPLOAD: {
|
||||
if (!dto.name) {
|
||||
dto.name = 'New Upload Library';
|
||||
}
|
||||
if (dto.importPaths && dto.importPaths.length > 0) {
|
||||
throw new BadRequestException('Upload libraries cannot have import paths');
|
||||
}
|
||||
if (dto.exclusionPatterns && dto.exclusionPatterns.length > 0) {
|
||||
throw new BadRequestException('Upload libraries cannot have exclusion patterns');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const library = await this.repository.create({
|
||||
ownerId: dto.ownerId,
|
||||
name: dto.name,
|
||||
type: dto.type,
|
||||
name: dto.name ?? 'New External Library',
|
||||
importPaths: dto.importPaths ?? [],
|
||||
exclusionPatterns: dto.exclusionPatterns ?? [],
|
||||
});
|
||||
|
||||
this.logger.log(`Creating ${dto.type} library for ${dto.ownerId}}`);
|
||||
|
||||
return mapLibrary(library);
|
||||
}
|
||||
|
||||
@@ -362,11 +332,7 @@ export class LibraryService {
|
||||
}
|
||||
|
||||
async delete(id: string) {
|
||||
const library = await this.findOrFail(id);
|
||||
const uploadCount = await this.repository.getUploadLibraryCount(library.ownerId);
|
||||
if (library.type === LibraryType.UPLOAD && uploadCount <= 1) {
|
||||
throw new BadRequestException('Cannot delete the last upload library');
|
||||
}
|
||||
await this.findOrFail(id);
|
||||
|
||||
if (this.watchLibraries) {
|
||||
await this.unwatch(id);
|
||||
@@ -529,10 +495,7 @@ export class LibraryService {
|
||||
}
|
||||
|
||||
async queueScan(id: string, dto: ScanLibraryDto) {
|
||||
const library = await this.findOrFail(id);
|
||||
if (library.type !== LibraryType.EXTERNAL) {
|
||||
throw new BadRequestException('Can only refresh external libraries');
|
||||
}
|
||||
await this.findOrFail(id);
|
||||
|
||||
await this.jobRepository.queue({
|
||||
name: JobName.LIBRARY_SCAN,
|
||||
@@ -556,7 +519,7 @@ export class LibraryService {
|
||||
await this.jobRepository.queue({ name: JobName.LIBRARY_QUEUE_CLEANUP, data: {} });
|
||||
|
||||
// Queue all library refresh
|
||||
const libraries = await this.repository.getAll(true, LibraryType.EXTERNAL);
|
||||
const libraries = await this.repository.getAll(true);
|
||||
await this.jobRepository.queueAll(
|
||||
libraries.map((library) => ({
|
||||
name: JobName.LIBRARY_SCAN,
|
||||
@@ -587,8 +550,8 @@ export class LibraryService {
|
||||
|
||||
async handleQueueAssetRefresh(job: ILibraryRefreshJob): Promise<JobStatus> {
|
||||
const library = await this.repository.get(job.id);
|
||||
if (!library || library.type !== LibraryType.EXTERNAL) {
|
||||
this.logger.warn('Can only refresh external libraries');
|
||||
if (!library) {
|
||||
this.logger.warn('Library not found');
|
||||
return JobStatus.FAILED;
|
||||
}
|
||||
|
||||
|
||||
@@ -409,7 +409,7 @@ export class MetadataService {
|
||||
}
|
||||
const checksum = this.cryptoRepository.hashSha1(video);
|
||||
|
||||
let motionAsset = await this.assetRepository.getByChecksum(asset.libraryId, checksum);
|
||||
let motionAsset = await this.assetRepository.getByChecksum(asset.libraryId ?? null, checksum);
|
||||
if (motionAsset) {
|
||||
this.logger.debug(
|
||||
`Asset ${asset.id}'s motion photo video with checksum ${checksum.toString(
|
||||
|
||||
Reference in New Issue
Block a user