refactor: library type (#9525)

This commit is contained in:
Jason Rasmussen
2024-05-20 18:09:10 -04:00
committed by GitHub
parent 4353153fe6
commit 84d824d6a7
66 changed files with 183 additions and 984 deletions

View File

@@ -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),

View File

@@ -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,

View File

@@ -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);
}

View File

@@ -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' },

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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(