mirror of
https://github.com/KevinMidboe/immich.git
synced 2025-10-29 17:40:28 +00:00
feat(mobile) duplicated asset upload handling mechanism (#853)
This commit is contained in:
@@ -137,6 +137,7 @@ describe('Album service', () => {
|
||||
getAssetWithNoEXIF: jest.fn(),
|
||||
getAssetWithNoThumbnail: jest.fn(),
|
||||
getAssetWithNoSmartInfo: jest.fn(),
|
||||
getExistingAssets: jest.fn(),
|
||||
};
|
||||
|
||||
sut = new AlbumService(albumRepositoryMock, assetRepositoryMock);
|
||||
|
||||
@@ -10,6 +10,9 @@ import { AssetCountByTimeBucket } from './response-dto/asset-count-by-time-group
|
||||
import { TimeGroupEnum } from './dto/get-asset-count-by-time-bucket.dto';
|
||||
import { GetAssetByTimeBucketDto } from './dto/get-asset-by-time-bucket.dto';
|
||||
import { AssetCountByUserIdResponseDto } from './response-dto/asset-count-by-user-id-response.dto';
|
||||
import { CheckExistingAssetsDto } from './dto/check-existing-assets.dto';
|
||||
import { CheckExistingAssetsResponseDto } from './response-dto/check-existing-assets-response.dto';
|
||||
import { In } from 'typeorm/find-options/operator/In';
|
||||
|
||||
export interface IAssetRepository {
|
||||
create(
|
||||
@@ -32,6 +35,7 @@ export interface IAssetRepository {
|
||||
getAssetWithNoThumbnail(): Promise<AssetEntity[]>;
|
||||
getAssetWithNoEXIF(): Promise<AssetEntity[]>;
|
||||
getAssetWithNoSmartInfo(): Promise<AssetEntity[]>;
|
||||
getExistingAssets(userId: string, checkDuplicateAssetDto: CheckExistingAssetsDto): Promise<CheckExistingAssetsResponseDto>;
|
||||
}
|
||||
|
||||
export const ASSET_REPOSITORY = 'ASSET_REPOSITORY';
|
||||
@@ -279,4 +283,17 @@ export class AssetRepository implements IAssetRepository {
|
||||
relations: ['exifInfo'],
|
||||
});
|
||||
}
|
||||
|
||||
async getExistingAssets(userId: string, checkDuplicateAssetDto: CheckExistingAssetsDto): Promise<CheckExistingAssetsResponseDto> {
|
||||
const existingAssets = await this.assetRepository.find({
|
||||
select: {deviceAssetId: true},
|
||||
where: {
|
||||
deviceAssetId: In(checkDuplicateAssetDto.deviceAssetIds),
|
||||
deviceId: checkDuplicateAssetDto.deviceId,
|
||||
userId,
|
||||
},
|
||||
});
|
||||
return new CheckExistingAssetsResponseDto(existingAssets.map(a => a.deviceAssetId));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -48,6 +48,8 @@ import { GetAssetCountByTimeBucketDto } from './dto/get-asset-count-by-time-buck
|
||||
import { GetAssetByTimeBucketDto } from './dto/get-asset-by-time-bucket.dto';
|
||||
import { QueryFailedError } from 'typeorm';
|
||||
import { AssetCountByUserIdResponseDto } from './response-dto/asset-count-by-user-id-response.dto';
|
||||
import { CheckExistingAssetsDto } from './dto/check-existing-assets.dto';
|
||||
import { CheckExistingAssetsResponseDto } from './response-dto/check-existing-assets-response.dto';
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@@ -74,6 +76,7 @@ export class AssetController {
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@UploadedFile() file: Express.Multer.File,
|
||||
@Body(ValidationPipe) assetInfo: CreateAssetDto,
|
||||
@Response({ passthrough: true }) res: Res,
|
||||
): Promise<AssetFileUploadResponseDto> {
|
||||
const checksum = await this.assetService.calculateChecksum(file.path);
|
||||
|
||||
@@ -111,6 +114,7 @@ export class AssetController {
|
||||
|
||||
if (err instanceof QueryFailedError && (err as any).constraint === 'UQ_userid_checksum') {
|
||||
const existedAsset = await this.assetService.getAssetByChecksum(authUser.id, checksum);
|
||||
res.status(200); // normal POST is 201. we use 200 to indicate the asset already exists
|
||||
return new AssetFileUploadResponseDto(existedAsset.id);
|
||||
}
|
||||
|
||||
@@ -254,4 +258,16 @@ export class AssetController {
|
||||
): Promise<CheckDuplicateAssetResponseDto> {
|
||||
return await this.assetService.checkDuplicatedAsset(authUser, checkDuplicateAssetDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if multiple assets exist on the server and returns all existing - used by background backup
|
||||
*/
|
||||
@Post('/exist')
|
||||
@HttpCode(200)
|
||||
async checkExistingAssets(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@Body(ValidationPipe) checkExistingAssetsDto: CheckExistingAssetsDto,
|
||||
): Promise<CheckExistingAssetsResponseDto> {
|
||||
return await this.assetService.checkExistingAssets(authUser, checkExistingAssetsDto);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,6 +110,7 @@ describe('AssetService', () => {
|
||||
getAssetWithNoEXIF: jest.fn(),
|
||||
getAssetWithNoThumbnail: jest.fn(),
|
||||
getAssetWithNoSmartInfo: jest.fn(),
|
||||
getExistingAssets: jest.fn(),
|
||||
};
|
||||
|
||||
sui = new AssetService(assetRepositoryMock, a);
|
||||
|
||||
@@ -37,6 +37,8 @@ import { GetAssetCountByTimeBucketDto } from './dto/get-asset-count-by-time-buck
|
||||
import { GetAssetByTimeBucketDto } from './dto/get-asset-by-time-bucket.dto';
|
||||
import { AssetCountByUserIdResponseDto } from './response-dto/asset-count-by-user-id-response.dto';
|
||||
import { timeUtils } from '@app/common/utils';
|
||||
import { CheckExistingAssetsDto } from './dto/check-existing-assets.dto';
|
||||
import { CheckExistingAssetsResponseDto } from './response-dto/check-existing-assets-response.dto';
|
||||
|
||||
const fileInfo = promisify(stat);
|
||||
|
||||
@@ -466,6 +468,13 @@ export class AssetService {
|
||||
return new CheckDuplicateAssetResponseDto(isDuplicated, res?.id);
|
||||
}
|
||||
|
||||
async checkExistingAssets(
|
||||
authUser: AuthUserDto,
|
||||
checkExistingAssetsDto: CheckExistingAssetsDto,
|
||||
): Promise<CheckExistingAssetsResponseDto> {
|
||||
return this._assetRepository.getExistingAssets(authUser.id, checkExistingAssetsDto);
|
||||
}
|
||||
|
||||
async getAssetCountByTimeBucket(
|
||||
authUser: AuthUserDto,
|
||||
getAssetCountByTimeBucketDto: GetAssetCountByTimeBucketDto,
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { IsNotEmpty } from 'class-validator';
|
||||
|
||||
export class CheckExistingAssetsDto {
|
||||
@IsNotEmpty()
|
||||
deviceAssetIds!: string[];
|
||||
|
||||
@IsNotEmpty()
|
||||
deviceId!: string;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export class CheckExistingAssetsResponseDto {
|
||||
constructor(existingIds: string[]) {
|
||||
this.existingIds = existingIds;
|
||||
}
|
||||
existingIds: string[];
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user