mirror of
https://github.com/immich-app/immich.git
synced 2025-12-25 17:24:58 +03:00
refactor(server): move checkExistingAssets(), checkBulkUpdate() remove getAllAssets() (#9715)
* Refactor controller methods, non-breaking change * Remove getAllAssets * used imports * sync:sql * missing mock * Removing remaining references * chore: remove unused code --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { Stats } from 'node:fs';
|
||||
import { AssetMediaStatusEnum } from 'src/dtos/asset-media-response.dto';
|
||||
import { AssetMediaStatusEnum, AssetRejectReason, AssetUploadAction } from 'src/dtos/asset-media-response.dto';
|
||||
import { AssetMediaReplaceDto } from 'src/dtos/asset-media.dto';
|
||||
import { ASSET_CHECKSUM_CONSTRAINT, AssetEntity, AssetType } from 'src/entities/asset.entity';
|
||||
import { ExifEntity } from 'src/entities/exif.entity';
|
||||
@@ -277,4 +277,31 @@ describe('AssetMediaService', () => {
|
||||
expect(userMock.updateUsage).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
describe('bulkUploadCheck', () => {
|
||||
it('should accept hex and base64 checksums', async () => {
|
||||
const file1 = Buffer.from('d2947b871a706081be194569951b7db246907957', 'hex');
|
||||
const file2 = Buffer.from('53be335e99f18a66ff12e9a901c7a6171dd76573', 'hex');
|
||||
|
||||
assetMock.getByChecksums.mockResolvedValue([
|
||||
{ id: 'asset-1', checksum: file1 } as AssetEntity,
|
||||
{ id: 'asset-2', checksum: file2 } as AssetEntity,
|
||||
]);
|
||||
|
||||
await expect(
|
||||
sut.bulkUploadCheck(authStub.admin, {
|
||||
assets: [
|
||||
{ id: '1', checksum: file1.toString('hex') },
|
||||
{ id: '2', checksum: file2.toString('base64') },
|
||||
],
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
results: [
|
||||
{ id: '1', assetId: 'asset-1', action: AssetUploadAction.REJECT, reason: AssetRejectReason.DUPLICATE },
|
||||
{ id: '2', assetId: 'asset-2', action: AssetUploadAction.REJECT, reason: AssetRejectReason.DUPLICATE },
|
||||
],
|
||||
});
|
||||
|
||||
expect(assetMock.getByChecksums).toHaveBeenCalledWith(authStub.admin.user.id, [file1, file2]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,19 @@
|
||||
import { BadRequestException, Inject, Injectable, InternalServerErrorException } from '@nestjs/common';
|
||||
import { AccessCore, Permission } from 'src/cores/access.core';
|
||||
import { AssetMediaResponseDto, AssetMediaStatusEnum } from 'src/dtos/asset-media-response.dto';
|
||||
import { AssetMediaReplaceDto, UploadFieldName } from 'src/dtos/asset-media.dto';
|
||||
import {
|
||||
AssetBulkUploadCheckResponseDto,
|
||||
AssetMediaResponseDto,
|
||||
AssetMediaStatusEnum,
|
||||
AssetRejectReason,
|
||||
AssetUploadAction,
|
||||
CheckExistingAssetsResponseDto,
|
||||
} from 'src/dtos/asset-media-response.dto';
|
||||
import {
|
||||
AssetBulkUploadCheckDto,
|
||||
AssetMediaReplaceDto,
|
||||
CheckExistingAssetsDto,
|
||||
UploadFieldName,
|
||||
} from 'src/dtos/asset-media.dto';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { ASSET_CHECKSUM_CONSTRAINT, AssetEntity } from 'src/entities/asset.entity';
|
||||
import { IAccessRepository } from 'src/interfaces/access.interface';
|
||||
@@ -12,8 +24,8 @@ import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||
import { mimeTypes } from 'src/utils/mime-types';
|
||||
import { fromChecksum } from 'src/utils/request';
|
||||
import { QueryFailedError } from 'typeorm';
|
||||
|
||||
export interface UploadRequest {
|
||||
auth: AuthDto | null;
|
||||
fieldName: UploadFieldName;
|
||||
@@ -174,4 +186,49 @@ export class AssetMediaService {
|
||||
throw new BadRequestException('Quota has been exceeded!');
|
||||
}
|
||||
}
|
||||
|
||||
async checkExistingAssets(
|
||||
auth: AuthDto,
|
||||
checkExistingAssetsDto: CheckExistingAssetsDto,
|
||||
): Promise<CheckExistingAssetsResponseDto> {
|
||||
const assets = await this.assetRepository.getByDeviceIds(
|
||||
auth.user.id,
|
||||
checkExistingAssetsDto.deviceId,
|
||||
checkExistingAssetsDto.deviceAssetIds,
|
||||
);
|
||||
return {
|
||||
existingIds: assets.map((asset) => asset.id),
|
||||
};
|
||||
}
|
||||
|
||||
async bulkUploadCheck(auth: AuthDto, dto: AssetBulkUploadCheckDto): Promise<AssetBulkUploadCheckResponseDto> {
|
||||
const checksums: Buffer[] = dto.assets.map((asset) => fromChecksum(asset.checksum));
|
||||
const results = await this.assetRepository.getByChecksums(auth.user.id, checksums);
|
||||
const checksumMap: Record<string, string> = {};
|
||||
|
||||
for (const { id, checksum } of results) {
|
||||
checksumMap[checksum.toString('hex')] = id;
|
||||
}
|
||||
|
||||
return {
|
||||
results: dto.assets.map(({ id, checksum }) => {
|
||||
const duplicate = checksumMap[fromChecksum(checksum).toString('hex')];
|
||||
if (duplicate) {
|
||||
return {
|
||||
id,
|
||||
assetId: duplicate,
|
||||
action: AssetUploadAction.REJECT,
|
||||
reason: AssetRejectReason.DUPLICATE,
|
||||
};
|
||||
}
|
||||
|
||||
// TODO mime-check
|
||||
|
||||
return {
|
||||
id,
|
||||
action: AssetUploadAction.ACCEPT,
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { AssetRejectReason, AssetUploadAction } from 'src/dtos/asset-v1-response.dto';
|
||||
import { CreateAssetDto } from 'src/dtos/asset-v1.dto';
|
||||
import { ASSET_CHECKSUM_CONSTRAINT, AssetEntity, AssetType } from 'src/entities/asset.entity';
|
||||
import { ExifEntity } from 'src/entities/exif.entity';
|
||||
@@ -74,10 +73,7 @@ describe('AssetService', () => {
|
||||
beforeEach(() => {
|
||||
assetRepositoryMockV1 = {
|
||||
get: vitest.fn(),
|
||||
getAllByUserId: vitest.fn(),
|
||||
getAssetsByChecksums: vitest.fn(),
|
||||
getExistingAssets: vitest.fn(),
|
||||
getByOriginalPath: vitest.fn(),
|
||||
};
|
||||
|
||||
accessMock = newAccessRepositoryMock();
|
||||
@@ -194,32 +190,4 @@ describe('AssetService', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('bulkUploadCheck', () => {
|
||||
it('should accept hex and base64 checksums', async () => {
|
||||
const file1 = Buffer.from('d2947b871a706081be194569951b7db246907957', 'hex');
|
||||
const file2 = Buffer.from('53be335e99f18a66ff12e9a901c7a6171dd76573', 'hex');
|
||||
|
||||
assetRepositoryMockV1.getAssetsByChecksums.mockResolvedValue([
|
||||
{ id: 'asset-1', checksum: file1 },
|
||||
{ id: 'asset-2', checksum: file2 },
|
||||
]);
|
||||
|
||||
await expect(
|
||||
sut.bulkUploadCheck(authStub.admin, {
|
||||
assets: [
|
||||
{ id: '1', checksum: file1.toString('hex') },
|
||||
{ id: '2', checksum: file2.toString('base64') },
|
||||
],
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
results: [
|
||||
{ id: '1', assetId: 'asset-1', action: AssetUploadAction.REJECT, reason: AssetRejectReason.DUPLICATE },
|
||||
{ id: '2', assetId: 'asset-2', action: AssetUploadAction.REJECT, reason: AssetRejectReason.DUPLICATE },
|
||||
],
|
||||
});
|
||||
|
||||
expect(assetRepositoryMockV1.getAssetsByChecksums).toHaveBeenCalledWith(authStub.admin.user.id, [file1, file2]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,23 +6,8 @@ import {
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { AccessCore, Permission } from 'src/cores/access.core';
|
||||
import { AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto';
|
||||
import {
|
||||
AssetBulkUploadCheckResponseDto,
|
||||
AssetFileUploadResponseDto,
|
||||
AssetRejectReason,
|
||||
AssetUploadAction,
|
||||
CheckExistingAssetsResponseDto,
|
||||
} from 'src/dtos/asset-v1-response.dto';
|
||||
import {
|
||||
AssetBulkUploadCheckDto,
|
||||
AssetSearchDto,
|
||||
CheckExistingAssetsDto,
|
||||
CreateAssetDto,
|
||||
GetAssetThumbnailDto,
|
||||
GetAssetThumbnailFormatEnum,
|
||||
ServeFileDto,
|
||||
} from 'src/dtos/asset-v1.dto';
|
||||
import { AssetFileUploadResponseDto } from 'src/dtos/asset-v1-response.dto';
|
||||
import { CreateAssetDto, GetAssetThumbnailDto, GetAssetThumbnailFormatEnum, ServeFileDto } 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 { IAccessRepository } from 'src/interfaces/access.interface';
|
||||
@@ -36,7 +21,6 @@ import { IUserRepository } from 'src/interfaces/user.interface';
|
||||
import { UploadFile } from 'src/services/asset-media.service';
|
||||
import { CacheControl, ImmichFileResponse, getLivePhotoMotionFilename } from 'src/utils/file';
|
||||
import { mimeTypes } from 'src/utils/mime-types';
|
||||
import { fromChecksum } from 'src/utils/request';
|
||||
import { QueryFailedError } from 'typeorm';
|
||||
|
||||
@Injectable()
|
||||
@@ -112,13 +96,6 @@ export class AssetServiceV1 {
|
||||
}
|
||||
}
|
||||
|
||||
public async getAllAssets(auth: AuthDto, dto: AssetSearchDto): Promise<AssetResponseDto[]> {
|
||||
const userId = dto.userId || auth.user.id;
|
||||
await this.access.requirePermission(auth, Permission.TIMELINE_READ, userId);
|
||||
const assets = await this.assetRepositoryV1.getAllByUserId(userId, dto);
|
||||
return assets.map((asset) => mapAsset(asset, { withStack: true, auth }));
|
||||
}
|
||||
|
||||
async serveThumbnail(auth: AuthDto, assetId: string, dto: GetAssetThumbnailDto): Promise<ImmichFileResponse> {
|
||||
await this.access.requirePermission(auth, Permission.ASSET_VIEW, assetId);
|
||||
|
||||
@@ -159,46 +136,6 @@ export class AssetServiceV1 {
|
||||
});
|
||||
}
|
||||
|
||||
async checkExistingAssets(
|
||||
auth: AuthDto,
|
||||
checkExistingAssetsDto: CheckExistingAssetsDto,
|
||||
): Promise<CheckExistingAssetsResponseDto> {
|
||||
return {
|
||||
existingIds: await this.assetRepositoryV1.getExistingAssets(auth.user.id, checkExistingAssetsDto),
|
||||
};
|
||||
}
|
||||
|
||||
async bulkUploadCheck(auth: AuthDto, dto: AssetBulkUploadCheckDto): Promise<AssetBulkUploadCheckResponseDto> {
|
||||
const checksums: Buffer[] = dto.assets.map((asset) => fromChecksum(asset.checksum));
|
||||
const results = await this.assetRepositoryV1.getAssetsByChecksums(auth.user.id, checksums);
|
||||
const checksumMap: Record<string, string> = {};
|
||||
|
||||
for (const { id, checksum } of results) {
|
||||
checksumMap[checksum.toString('hex')] = id;
|
||||
}
|
||||
|
||||
return {
|
||||
results: dto.assets.map(({ id, checksum }) => {
|
||||
const duplicate = checksumMap[fromChecksum(checksum).toString('hex')];
|
||||
if (duplicate) {
|
||||
return {
|
||||
id,
|
||||
assetId: duplicate,
|
||||
action: AssetUploadAction.REJECT,
|
||||
reason: AssetRejectReason.DUPLICATE,
|
||||
};
|
||||
}
|
||||
|
||||
// TODO mime-check
|
||||
|
||||
return {
|
||||
id,
|
||||
action: AssetUploadAction.ACCEPT,
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
private getThumbnailPath(asset: AssetEntity, format: GetAssetThumbnailFormatEnum) {
|
||||
switch (format) {
|
||||
case GetAssetThumbnailFormatEnum.WEBP: {
|
||||
|
||||
Reference in New Issue
Block a user