feat(server): return asset checksum (#2582)
* feat: return asset checksum * chore: generate open api * chore: coverage * feat(server): support base64 hashes in bulk upload check: * chore: generate open api
This commit is contained in:
parent
7f0ad8e2d2
commit
bca4626708
17 changed files with 111 additions and 103 deletions
2
mobile/openapi/doc/AssetBulkUploadCheckItem.md
generated
2
mobile/openapi/doc/AssetBulkUploadCheckItem.md
generated
|
@ -9,7 +9,7 @@ import 'package:openapi/api.dart';
|
||||||
Name | Type | Description | Notes
|
Name | Type | Description | Notes
|
||||||
------------ | ------------- | ------------- | -------------
|
------------ | ------------- | ------------- | -------------
|
||||||
**id** | **String** | |
|
**id** | **String** | |
|
||||||
**checksum** | **String** | |
|
**checksum** | **String** | base64 or hex encoded sha1 hash |
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
|
|
||||||
|
|
5
mobile/openapi/doc/AssetResponseDto.md
generated
5
mobile/openapi/doc/AssetResponseDto.md
generated
|
@ -15,7 +15,7 @@ Name | Type | Description | Notes
|
||||||
**deviceId** | **String** | |
|
**deviceId** | **String** | |
|
||||||
**originalPath** | **String** | |
|
**originalPath** | **String** | |
|
||||||
**originalFileName** | **String** | |
|
**originalFileName** | **String** | |
|
||||||
**resizePath** | **String** | |
|
**resized** | **bool** | |
|
||||||
**fileCreatedAt** | **String** | |
|
**fileCreatedAt** | **String** | |
|
||||||
**fileModifiedAt** | **String** | |
|
**fileModifiedAt** | **String** | |
|
||||||
**updatedAt** | **String** | |
|
**updatedAt** | **String** | |
|
||||||
|
@ -23,13 +23,12 @@ Name | Type | Description | Notes
|
||||||
**isArchived** | **bool** | |
|
**isArchived** | **bool** | |
|
||||||
**mimeType** | **String** | |
|
**mimeType** | **String** | |
|
||||||
**duration** | **String** | |
|
**duration** | **String** | |
|
||||||
**webpPath** | **String** | |
|
|
||||||
**encodedVideoPath** | **String** | | [optional]
|
|
||||||
**exifInfo** | [**ExifResponseDto**](ExifResponseDto.md) | | [optional]
|
**exifInfo** | [**ExifResponseDto**](ExifResponseDto.md) | | [optional]
|
||||||
**smartInfo** | [**SmartInfoResponseDto**](SmartInfoResponseDto.md) | | [optional]
|
**smartInfo** | [**SmartInfoResponseDto**](SmartInfoResponseDto.md) | | [optional]
|
||||||
**livePhotoVideoId** | **String** | | [optional]
|
**livePhotoVideoId** | **String** | | [optional]
|
||||||
**tags** | [**List<TagResponseDto>**](TagResponseDto.md) | | [optional] [default to const []]
|
**tags** | [**List<TagResponseDto>**](TagResponseDto.md) | | [optional] [default to const []]
|
||||||
**people** | [**List<PersonResponseDto>**](PersonResponseDto.md) | | [optional] [default to const []]
|
**people** | [**List<PersonResponseDto>**](PersonResponseDto.md) | | [optional] [default to const []]
|
||||||
|
**checksum** | **String** | base64 encoded sha1 hash |
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ class AssetBulkUploadCheckItem {
|
||||||
|
|
||||||
String id;
|
String id;
|
||||||
|
|
||||||
|
/// base64 or hex encoded sha1 hash
|
||||||
String checksum;
|
String checksum;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
56
mobile/openapi/lib/model/asset_response_dto.dart
generated
56
mobile/openapi/lib/model/asset_response_dto.dart
generated
|
@ -20,7 +20,7 @@ class AssetResponseDto {
|
||||||
required this.deviceId,
|
required this.deviceId,
|
||||||
required this.originalPath,
|
required this.originalPath,
|
||||||
required this.originalFileName,
|
required this.originalFileName,
|
||||||
required this.resizePath,
|
required this.resized,
|
||||||
required this.fileCreatedAt,
|
required this.fileCreatedAt,
|
||||||
required this.fileModifiedAt,
|
required this.fileModifiedAt,
|
||||||
required this.updatedAt,
|
required this.updatedAt,
|
||||||
|
@ -28,13 +28,12 @@ class AssetResponseDto {
|
||||||
required this.isArchived,
|
required this.isArchived,
|
||||||
required this.mimeType,
|
required this.mimeType,
|
||||||
required this.duration,
|
required this.duration,
|
||||||
required this.webpPath,
|
|
||||||
this.encodedVideoPath,
|
|
||||||
this.exifInfo,
|
this.exifInfo,
|
||||||
this.smartInfo,
|
this.smartInfo,
|
||||||
this.livePhotoVideoId,
|
this.livePhotoVideoId,
|
||||||
this.tags = const [],
|
this.tags = const [],
|
||||||
this.people = const [],
|
this.people = const [],
|
||||||
|
required this.checksum,
|
||||||
});
|
});
|
||||||
|
|
||||||
AssetTypeEnum type;
|
AssetTypeEnum type;
|
||||||
|
@ -51,7 +50,7 @@ class AssetResponseDto {
|
||||||
|
|
||||||
String originalFileName;
|
String originalFileName;
|
||||||
|
|
||||||
String? resizePath;
|
bool resized;
|
||||||
|
|
||||||
String fileCreatedAt;
|
String fileCreatedAt;
|
||||||
|
|
||||||
|
@ -67,10 +66,6 @@ class AssetResponseDto {
|
||||||
|
|
||||||
String duration;
|
String duration;
|
||||||
|
|
||||||
String? webpPath;
|
|
||||||
|
|
||||||
String? encodedVideoPath;
|
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Please note: This property should have been non-nullable! Since the specification file
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
/// does not include a default value (using the "default:" property), however, the generated
|
/// does not include a default value (using the "default:" property), however, the generated
|
||||||
|
@ -93,6 +88,9 @@ class AssetResponseDto {
|
||||||
|
|
||||||
List<PersonResponseDto> people;
|
List<PersonResponseDto> people;
|
||||||
|
|
||||||
|
/// base64 encoded sha1 hash
|
||||||
|
String checksum;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) => identical(this, other) || other is AssetResponseDto &&
|
bool operator ==(Object other) => identical(this, other) || other is AssetResponseDto &&
|
||||||
other.type == type &&
|
other.type == type &&
|
||||||
|
@ -102,7 +100,7 @@ class AssetResponseDto {
|
||||||
other.deviceId == deviceId &&
|
other.deviceId == deviceId &&
|
||||||
other.originalPath == originalPath &&
|
other.originalPath == originalPath &&
|
||||||
other.originalFileName == originalFileName &&
|
other.originalFileName == originalFileName &&
|
||||||
other.resizePath == resizePath &&
|
other.resized == resized &&
|
||||||
other.fileCreatedAt == fileCreatedAt &&
|
other.fileCreatedAt == fileCreatedAt &&
|
||||||
other.fileModifiedAt == fileModifiedAt &&
|
other.fileModifiedAt == fileModifiedAt &&
|
||||||
other.updatedAt == updatedAt &&
|
other.updatedAt == updatedAt &&
|
||||||
|
@ -110,13 +108,12 @@ class AssetResponseDto {
|
||||||
other.isArchived == isArchived &&
|
other.isArchived == isArchived &&
|
||||||
other.mimeType == mimeType &&
|
other.mimeType == mimeType &&
|
||||||
other.duration == duration &&
|
other.duration == duration &&
|
||||||
other.webpPath == webpPath &&
|
|
||||||
other.encodedVideoPath == encodedVideoPath &&
|
|
||||||
other.exifInfo == exifInfo &&
|
other.exifInfo == exifInfo &&
|
||||||
other.smartInfo == smartInfo &&
|
other.smartInfo == smartInfo &&
|
||||||
other.livePhotoVideoId == livePhotoVideoId &&
|
other.livePhotoVideoId == livePhotoVideoId &&
|
||||||
other.tags == tags &&
|
other.tags == tags &&
|
||||||
other.people == people;
|
other.people == people &&
|
||||||
|
other.checksum == checksum;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
|
@ -128,7 +125,7 @@ class AssetResponseDto {
|
||||||
(deviceId.hashCode) +
|
(deviceId.hashCode) +
|
||||||
(originalPath.hashCode) +
|
(originalPath.hashCode) +
|
||||||
(originalFileName.hashCode) +
|
(originalFileName.hashCode) +
|
||||||
(resizePath == null ? 0 : resizePath!.hashCode) +
|
(resized.hashCode) +
|
||||||
(fileCreatedAt.hashCode) +
|
(fileCreatedAt.hashCode) +
|
||||||
(fileModifiedAt.hashCode) +
|
(fileModifiedAt.hashCode) +
|
||||||
(updatedAt.hashCode) +
|
(updatedAt.hashCode) +
|
||||||
|
@ -136,16 +133,15 @@ class AssetResponseDto {
|
||||||
(isArchived.hashCode) +
|
(isArchived.hashCode) +
|
||||||
(mimeType == null ? 0 : mimeType!.hashCode) +
|
(mimeType == null ? 0 : mimeType!.hashCode) +
|
||||||
(duration.hashCode) +
|
(duration.hashCode) +
|
||||||
(webpPath == null ? 0 : webpPath!.hashCode) +
|
|
||||||
(encodedVideoPath == null ? 0 : encodedVideoPath!.hashCode) +
|
|
||||||
(exifInfo == null ? 0 : exifInfo!.hashCode) +
|
(exifInfo == null ? 0 : exifInfo!.hashCode) +
|
||||||
(smartInfo == null ? 0 : smartInfo!.hashCode) +
|
(smartInfo == null ? 0 : smartInfo!.hashCode) +
|
||||||
(livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) +
|
(livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) +
|
||||||
(tags.hashCode) +
|
(tags.hashCode) +
|
||||||
(people.hashCode);
|
(people.hashCode) +
|
||||||
|
(checksum.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'AssetResponseDto[type=$type, id=$id, deviceAssetId=$deviceAssetId, ownerId=$ownerId, deviceId=$deviceId, originalPath=$originalPath, originalFileName=$originalFileName, resizePath=$resizePath, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, updatedAt=$updatedAt, isFavorite=$isFavorite, isArchived=$isArchived, mimeType=$mimeType, duration=$duration, webpPath=$webpPath, encodedVideoPath=$encodedVideoPath, exifInfo=$exifInfo, smartInfo=$smartInfo, livePhotoVideoId=$livePhotoVideoId, tags=$tags, people=$people]';
|
String toString() => 'AssetResponseDto[type=$type, id=$id, deviceAssetId=$deviceAssetId, ownerId=$ownerId, deviceId=$deviceId, originalPath=$originalPath, originalFileName=$originalFileName, resized=$resized, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, updatedAt=$updatedAt, isFavorite=$isFavorite, isArchived=$isArchived, mimeType=$mimeType, duration=$duration, exifInfo=$exifInfo, smartInfo=$smartInfo, livePhotoVideoId=$livePhotoVideoId, tags=$tags, people=$people, checksum=$checksum]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
|
@ -156,11 +152,7 @@ class AssetResponseDto {
|
||||||
json[r'deviceId'] = this.deviceId;
|
json[r'deviceId'] = this.deviceId;
|
||||||
json[r'originalPath'] = this.originalPath;
|
json[r'originalPath'] = this.originalPath;
|
||||||
json[r'originalFileName'] = this.originalFileName;
|
json[r'originalFileName'] = this.originalFileName;
|
||||||
if (this.resizePath != null) {
|
json[r'resized'] = this.resized;
|
||||||
json[r'resizePath'] = this.resizePath;
|
|
||||||
} else {
|
|
||||||
// json[r'resizePath'] = null;
|
|
||||||
}
|
|
||||||
json[r'fileCreatedAt'] = this.fileCreatedAt;
|
json[r'fileCreatedAt'] = this.fileCreatedAt;
|
||||||
json[r'fileModifiedAt'] = this.fileModifiedAt;
|
json[r'fileModifiedAt'] = this.fileModifiedAt;
|
||||||
json[r'updatedAt'] = this.updatedAt;
|
json[r'updatedAt'] = this.updatedAt;
|
||||||
|
@ -172,16 +164,6 @@ class AssetResponseDto {
|
||||||
// json[r'mimeType'] = null;
|
// json[r'mimeType'] = null;
|
||||||
}
|
}
|
||||||
json[r'duration'] = this.duration;
|
json[r'duration'] = this.duration;
|
||||||
if (this.webpPath != null) {
|
|
||||||
json[r'webpPath'] = this.webpPath;
|
|
||||||
} else {
|
|
||||||
// json[r'webpPath'] = null;
|
|
||||||
}
|
|
||||||
if (this.encodedVideoPath != null) {
|
|
||||||
json[r'encodedVideoPath'] = this.encodedVideoPath;
|
|
||||||
} else {
|
|
||||||
// json[r'encodedVideoPath'] = null;
|
|
||||||
}
|
|
||||||
if (this.exifInfo != null) {
|
if (this.exifInfo != null) {
|
||||||
json[r'exifInfo'] = this.exifInfo;
|
json[r'exifInfo'] = this.exifInfo;
|
||||||
} else {
|
} else {
|
||||||
|
@ -199,6 +181,7 @@ class AssetResponseDto {
|
||||||
}
|
}
|
||||||
json[r'tags'] = this.tags;
|
json[r'tags'] = this.tags;
|
||||||
json[r'people'] = this.people;
|
json[r'people'] = this.people;
|
||||||
|
json[r'checksum'] = this.checksum;
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,7 +211,7 @@ class AssetResponseDto {
|
||||||
deviceId: mapValueOfType<String>(json, r'deviceId')!,
|
deviceId: mapValueOfType<String>(json, r'deviceId')!,
|
||||||
originalPath: mapValueOfType<String>(json, r'originalPath')!,
|
originalPath: mapValueOfType<String>(json, r'originalPath')!,
|
||||||
originalFileName: mapValueOfType<String>(json, r'originalFileName')!,
|
originalFileName: mapValueOfType<String>(json, r'originalFileName')!,
|
||||||
resizePath: mapValueOfType<String>(json, r'resizePath'),
|
resized: mapValueOfType<bool>(json, r'resized')!,
|
||||||
fileCreatedAt: mapValueOfType<String>(json, r'fileCreatedAt')!,
|
fileCreatedAt: mapValueOfType<String>(json, r'fileCreatedAt')!,
|
||||||
fileModifiedAt: mapValueOfType<String>(json, r'fileModifiedAt')!,
|
fileModifiedAt: mapValueOfType<String>(json, r'fileModifiedAt')!,
|
||||||
updatedAt: mapValueOfType<String>(json, r'updatedAt')!,
|
updatedAt: mapValueOfType<String>(json, r'updatedAt')!,
|
||||||
|
@ -236,13 +219,12 @@ class AssetResponseDto {
|
||||||
isArchived: mapValueOfType<bool>(json, r'isArchived')!,
|
isArchived: mapValueOfType<bool>(json, r'isArchived')!,
|
||||||
mimeType: mapValueOfType<String>(json, r'mimeType'),
|
mimeType: mapValueOfType<String>(json, r'mimeType'),
|
||||||
duration: mapValueOfType<String>(json, r'duration')!,
|
duration: mapValueOfType<String>(json, r'duration')!,
|
||||||
webpPath: mapValueOfType<String>(json, r'webpPath'),
|
|
||||||
encodedVideoPath: mapValueOfType<String>(json, r'encodedVideoPath'),
|
|
||||||
exifInfo: ExifResponseDto.fromJson(json[r'exifInfo']),
|
exifInfo: ExifResponseDto.fromJson(json[r'exifInfo']),
|
||||||
smartInfo: SmartInfoResponseDto.fromJson(json[r'smartInfo']),
|
smartInfo: SmartInfoResponseDto.fromJson(json[r'smartInfo']),
|
||||||
livePhotoVideoId: mapValueOfType<String>(json, r'livePhotoVideoId'),
|
livePhotoVideoId: mapValueOfType<String>(json, r'livePhotoVideoId'),
|
||||||
tags: TagResponseDto.listFromJson(json[r'tags']),
|
tags: TagResponseDto.listFromJson(json[r'tags']),
|
||||||
people: PersonResponseDto.listFromJson(json[r'people']),
|
people: PersonResponseDto.listFromJson(json[r'people']),
|
||||||
|
checksum: mapValueOfType<String>(json, r'checksum')!,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -297,7 +279,7 @@ class AssetResponseDto {
|
||||||
'deviceId',
|
'deviceId',
|
||||||
'originalPath',
|
'originalPath',
|
||||||
'originalFileName',
|
'originalFileName',
|
||||||
'resizePath',
|
'resized',
|
||||||
'fileCreatedAt',
|
'fileCreatedAt',
|
||||||
'fileModifiedAt',
|
'fileModifiedAt',
|
||||||
'updatedAt',
|
'updatedAt',
|
||||||
|
@ -305,7 +287,7 @@ class AssetResponseDto {
|
||||||
'isArchived',
|
'isArchived',
|
||||||
'mimeType',
|
'mimeType',
|
||||||
'duration',
|
'duration',
|
||||||
'webpPath',
|
'checksum',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ void main() {
|
||||||
// TODO
|
// TODO
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// base64 or hex encoded sha1 hash
|
||||||
// String checksum
|
// String checksum
|
||||||
test('to test the property `checksum`', () async {
|
test('to test the property `checksum`', () async {
|
||||||
// TODO
|
// TODO
|
||||||
|
|
20
mobile/openapi/test/asset_response_dto_test.dart
generated
20
mobile/openapi/test/asset_response_dto_test.dart
generated
|
@ -51,8 +51,8 @@ void main() {
|
||||||
// TODO
|
// TODO
|
||||||
});
|
});
|
||||||
|
|
||||||
// String resizePath
|
// bool resized
|
||||||
test('to test the property `resizePath`', () async {
|
test('to test the property `resized`', () async {
|
||||||
// TODO
|
// TODO
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -91,16 +91,6 @@ void main() {
|
||||||
// TODO
|
// TODO
|
||||||
});
|
});
|
||||||
|
|
||||||
// String webpPath
|
|
||||||
test('to test the property `webpPath`', () async {
|
|
||||||
// TODO
|
|
||||||
});
|
|
||||||
|
|
||||||
// String encodedVideoPath
|
|
||||||
test('to test the property `encodedVideoPath`', () async {
|
|
||||||
// TODO
|
|
||||||
});
|
|
||||||
|
|
||||||
// ExifResponseDto exifInfo
|
// ExifResponseDto exifInfo
|
||||||
test('to test the property `exifInfo`', () async {
|
test('to test the property `exifInfo`', () async {
|
||||||
// TODO
|
// TODO
|
||||||
|
@ -126,6 +116,12 @@ void main() {
|
||||||
// TODO
|
// TODO
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// base64 encoded sha1 hash
|
||||||
|
// String checksum
|
||||||
|
test('to test the property `checksum`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ import {
|
||||||
import { CreateAssetsShareLinkDto } from './dto/create-asset-shared-link.dto';
|
import { CreateAssetsShareLinkDto } from './dto/create-asset-shared-link.dto';
|
||||||
import { BadRequestException, ForbiddenException } from '@nestjs/common';
|
import { BadRequestException, ForbiddenException } from '@nestjs/common';
|
||||||
import { when } from 'jest-when';
|
import { when } from 'jest-when';
|
||||||
|
import { AssetRejectReason, AssetUploadAction } from './response-dto/asset-check-response.dto';
|
||||||
|
|
||||||
const _getCreateAssetDto = (): CreateAssetDto => {
|
const _getCreateAssetDto = (): CreateAssetDto => {
|
||||||
const createAssetDto = new CreateAssetDto();
|
const createAssetDto = new CreateAssetDto();
|
||||||
|
@ -504,4 +505,32 @@ describe('AssetService', () => {
|
||||||
expect(storageMock.createReadStream).toHaveBeenCalledWith('fake_path/asset_1.jpeg', 'image/jpeg');
|
expect(storageMock.createReadStream).toHaveBeenCalledWith('fake_path/asset_1.jpeg', 'image/jpeg');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('bulkUploadCheck', () => {
|
||||||
|
it('should accept hex and base64 checksums', async () => {
|
||||||
|
const file1 = Buffer.from('d2947b871a706081be194569951b7db246907957', 'hex');
|
||||||
|
const file2 = Buffer.from('53be335e99f18a66ff12e9a901c7a6171dd76573', 'hex');
|
||||||
|
|
||||||
|
assetRepositoryMock.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(assetRepositoryMock.getAssetsByChecksums).toHaveBeenCalledWith(authStub.admin.id, [file1, file2]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -486,17 +486,24 @@ export class AssetService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async bulkUploadCheck(authUser: AuthUserDto, dto: AssetBulkUploadCheckDto): Promise<AssetBulkUploadCheckResponseDto> {
|
async bulkUploadCheck(authUser: AuthUserDto, dto: AssetBulkUploadCheckDto): Promise<AssetBulkUploadCheckResponseDto> {
|
||||||
|
// support base64 and hex checksums
|
||||||
|
for (const asset of dto.assets) {
|
||||||
|
if (asset.checksum.length === 28) {
|
||||||
|
asset.checksum = Buffer.from(asset.checksum, 'base64').toString('hex');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const checksums: Buffer[] = dto.assets.map((asset) => Buffer.from(asset.checksum, 'hex'));
|
const checksums: Buffer[] = dto.assets.map((asset) => Buffer.from(asset.checksum, 'hex'));
|
||||||
const results = await this._assetRepository.getAssetsByChecksums(authUser.id, checksums);
|
const results = await this._assetRepository.getAssetsByChecksums(authUser.id, checksums);
|
||||||
const resultsMap: Record<string, string> = {};
|
const checksumMap: Record<string, string> = {};
|
||||||
|
|
||||||
for (const { id, checksum } of results) {
|
for (const { id, checksum } of results) {
|
||||||
resultsMap[checksum.toString('hex')] = id;
|
checksumMap[checksum.toString('hex')] = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
results: dto.assets.map(({ id, checksum }) => {
|
results: dto.assets.map(({ id, checksum }) => {
|
||||||
const duplicate = resultsMap[checksum];
|
const duplicate = checksumMap[checksum];
|
||||||
if (duplicate) {
|
if (duplicate) {
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
|
|
|
@ -6,6 +6,7 @@ export class AssetBulkUploadCheckItem {
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
id!: string;
|
id!: string;
|
||||||
|
|
||||||
|
/** base64 or hex encoded sha1 hash */
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
checksum!: string;
|
checksum!: string;
|
||||||
|
|
|
@ -4446,9 +4446,8 @@
|
||||||
"originalFileName": {
|
"originalFileName": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"resizePath": {
|
"resized": {
|
||||||
"type": "string",
|
"type": "boolean"
|
||||||
"nullable": true
|
|
||||||
},
|
},
|
||||||
"fileCreatedAt": {
|
"fileCreatedAt": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
@ -4472,14 +4471,6 @@
|
||||||
"duration": {
|
"duration": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"webpPath": {
|
|
||||||
"type": "string",
|
|
||||||
"nullable": true
|
|
||||||
},
|
|
||||||
"encodedVideoPath": {
|
|
||||||
"type": "string",
|
|
||||||
"nullable": true
|
|
||||||
},
|
|
||||||
"exifInfo": {
|
"exifInfo": {
|
||||||
"$ref": "#/components/schemas/ExifResponseDto"
|
"$ref": "#/components/schemas/ExifResponseDto"
|
||||||
},
|
},
|
||||||
|
@ -4501,6 +4492,10 @@
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/components/schemas/PersonResponseDto"
|
"$ref": "#/components/schemas/PersonResponseDto"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"checksum": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "base64 encoded sha1 hash"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
@ -4511,7 +4506,7 @@
|
||||||
"deviceId",
|
"deviceId",
|
||||||
"originalPath",
|
"originalPath",
|
||||||
"originalFileName",
|
"originalFileName",
|
||||||
"resizePath",
|
"resized",
|
||||||
"fileCreatedAt",
|
"fileCreatedAt",
|
||||||
"fileModifiedAt",
|
"fileModifiedAt",
|
||||||
"updatedAt",
|
"updatedAt",
|
||||||
|
@ -4519,7 +4514,7 @@
|
||||||
"isArchived",
|
"isArchived",
|
||||||
"mimeType",
|
"mimeType",
|
||||||
"duration",
|
"duration",
|
||||||
"webpPath"
|
"checksum"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"AlbumResponseDto": {
|
"AlbumResponseDto": {
|
||||||
|
@ -6173,7 +6168,8 @@
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"checksum": {
|
"checksum": {
|
||||||
"type": "string"
|
"type": "string",
|
||||||
|
"description": "base64 or hex encoded sha1 hash"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { AlbumEntity, AssetEntity, UserEntity } from '@app/infra/entities';
|
import { AlbumEntity, AssetEntity, UserEntity } from '@app/infra/entities';
|
||||||
import { BadRequestException, ForbiddenException, Inject, Injectable } from '@nestjs/common';
|
import { BadRequestException, ForbiddenException, Inject, Injectable } from '@nestjs/common';
|
||||||
import { IAssetRepository } from '../asset';
|
import { IAssetRepository, mapAsset } from '../asset';
|
||||||
import { AuthUserDto } from '../auth';
|
import { AuthUserDto } from '../auth';
|
||||||
import { IJobRepository, JobName } from '../job';
|
import { IJobRepository, JobName } from '../job';
|
||||||
import { IAlbumRepository } from './album.repository';
|
import { IAlbumRepository } from './album.repository';
|
||||||
|
@ -40,6 +40,7 @@ export class AlbumService {
|
||||||
return albums.map((album) => {
|
return albums.map((album) => {
|
||||||
return {
|
return {
|
||||||
...album,
|
...album,
|
||||||
|
assets: album?.assets?.map(mapAsset),
|
||||||
sharedLinks: undefined, // Don't return shared links
|
sharedLinks: undefined, // Don't return shared links
|
||||||
shared: album.sharedLinks?.length > 0 || album.sharedUsers?.length > 0,
|
shared: album.sharedLinks?.length > 0 || album.sharedUsers?.length > 0,
|
||||||
assetCount: albumsAssetCountObj[album.id],
|
assetCount: albumsAssetCountObj[album.id],
|
||||||
|
|
|
@ -15,7 +15,7 @@ export class AssetResponseDto {
|
||||||
type!: AssetType;
|
type!: AssetType;
|
||||||
originalPath!: string;
|
originalPath!: string;
|
||||||
originalFileName!: string;
|
originalFileName!: string;
|
||||||
resizePath!: string | null;
|
resized!: boolean;
|
||||||
fileCreatedAt!: string;
|
fileCreatedAt!: string;
|
||||||
fileModifiedAt!: string;
|
fileModifiedAt!: string;
|
||||||
updatedAt!: string;
|
updatedAt!: string;
|
||||||
|
@ -23,13 +23,13 @@ export class AssetResponseDto {
|
||||||
isArchived!: boolean;
|
isArchived!: boolean;
|
||||||
mimeType!: string | null;
|
mimeType!: string | null;
|
||||||
duration!: string;
|
duration!: string;
|
||||||
webpPath!: string | null;
|
|
||||||
encodedVideoPath?: string | null;
|
|
||||||
exifInfo?: ExifResponseDto;
|
exifInfo?: ExifResponseDto;
|
||||||
smartInfo?: SmartInfoResponseDto;
|
smartInfo?: SmartInfoResponseDto;
|
||||||
livePhotoVideoId?: string | null;
|
livePhotoVideoId?: string | null;
|
||||||
tags?: TagResponseDto[];
|
tags?: TagResponseDto[];
|
||||||
people?: PersonResponseDto[];
|
people?: PersonResponseDto[];
|
||||||
|
/**base64 encoded sha1 hash */
|
||||||
|
checksum!: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mapAsset(entity: AssetEntity): AssetResponseDto {
|
export function mapAsset(entity: AssetEntity): AssetResponseDto {
|
||||||
|
@ -41,21 +41,20 @@ export function mapAsset(entity: AssetEntity): AssetResponseDto {
|
||||||
type: entity.type,
|
type: entity.type,
|
||||||
originalPath: entity.originalPath,
|
originalPath: entity.originalPath,
|
||||||
originalFileName: entity.originalFileName,
|
originalFileName: entity.originalFileName,
|
||||||
resizePath: entity.resizePath,
|
resized: !!entity.resizePath,
|
||||||
fileCreatedAt: entity.fileCreatedAt,
|
fileCreatedAt: entity.fileCreatedAt,
|
||||||
fileModifiedAt: entity.fileModifiedAt,
|
fileModifiedAt: entity.fileModifiedAt,
|
||||||
updatedAt: entity.updatedAt,
|
updatedAt: entity.updatedAt,
|
||||||
isFavorite: entity.isFavorite,
|
isFavorite: entity.isFavorite,
|
||||||
isArchived: entity.isArchived,
|
isArchived: entity.isArchived,
|
||||||
mimeType: entity.mimeType,
|
mimeType: entity.mimeType,
|
||||||
webpPath: entity.webpPath,
|
|
||||||
encodedVideoPath: entity.encodedVideoPath,
|
|
||||||
duration: entity.duration ?? '0:00:00.00000',
|
duration: entity.duration ?? '0:00:00.00000',
|
||||||
exifInfo: entity.exifInfo ? mapExif(entity.exifInfo) : undefined,
|
exifInfo: entity.exifInfo ? mapExif(entity.exifInfo) : undefined,
|
||||||
smartInfo: entity.smartInfo ? mapSmartInfo(entity.smartInfo) : undefined,
|
smartInfo: entity.smartInfo ? mapSmartInfo(entity.smartInfo) : undefined,
|
||||||
livePhotoVideoId: entity.livePhotoVideoId,
|
livePhotoVideoId: entity.livePhotoVideoId,
|
||||||
tags: entity.tags?.map(mapTag),
|
tags: entity.tags?.map(mapTag),
|
||||||
people: entity.faces?.map(mapFace),
|
people: entity.faces?.map(mapFace),
|
||||||
|
checksum: entity.checksum.toString('base64'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,20 +67,19 @@ export function mapAssetWithoutExif(entity: AssetEntity): AssetResponseDto {
|
||||||
type: entity.type,
|
type: entity.type,
|
||||||
originalPath: entity.originalPath,
|
originalPath: entity.originalPath,
|
||||||
originalFileName: entity.originalFileName,
|
originalFileName: entity.originalFileName,
|
||||||
resizePath: entity.resizePath,
|
resized: !!entity.resizePath,
|
||||||
fileCreatedAt: entity.fileCreatedAt,
|
fileCreatedAt: entity.fileCreatedAt,
|
||||||
fileModifiedAt: entity.fileModifiedAt,
|
fileModifiedAt: entity.fileModifiedAt,
|
||||||
updatedAt: entity.updatedAt,
|
updatedAt: entity.updatedAt,
|
||||||
isFavorite: entity.isFavorite,
|
isFavorite: entity.isFavorite,
|
||||||
isArchived: entity.isArchived,
|
isArchived: entity.isArchived,
|
||||||
mimeType: entity.mimeType,
|
mimeType: entity.mimeType,
|
||||||
webpPath: entity.webpPath,
|
|
||||||
encodedVideoPath: entity.encodedVideoPath,
|
|
||||||
duration: entity.duration ?? '0:00:00.00000',
|
duration: entity.duration ?? '0:00:00.00000',
|
||||||
exifInfo: undefined,
|
exifInfo: undefined,
|
||||||
smartInfo: entity.smartInfo ? mapSmartInfo(entity.smartInfo) : undefined,
|
smartInfo: entity.smartInfo ? mapSmartInfo(entity.smartInfo) : undefined,
|
||||||
livePhotoVideoId: entity.livePhotoVideoId,
|
livePhotoVideoId: entity.livePhotoVideoId,
|
||||||
tags: entity.tags?.map(mapTag),
|
tags: entity.tags?.map(mapTag),
|
||||||
people: entity.faces?.map(mapFace),
|
people: entity.faces?.map(mapFace),
|
||||||
|
checksum: entity.checksum.toString('base64'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { BadRequestException, Inject, Injectable, Logger } from '@nestjs/common'
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { mapAlbum } from '../album';
|
import { mapAlbum } from '../album';
|
||||||
import { IAlbumRepository } from '../album/album.repository';
|
import { IAlbumRepository } from '../album/album.repository';
|
||||||
import { mapAsset } from '../asset';
|
import { AssetResponseDto, mapAsset } from '../asset';
|
||||||
import { IAssetRepository } from '../asset/asset.repository';
|
import { IAssetRepository } from '../asset/asset.repository';
|
||||||
import { AuthUserDto } from '../auth';
|
import { AuthUserDto } from '../auth';
|
||||||
import { MACHINE_LEARNING_ENABLED } from '../domain.constant';
|
import { MACHINE_LEARNING_ENABLED } from '../domain.constant';
|
||||||
|
@ -103,9 +103,13 @@ export class SearchService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getExploreData(authUser: AuthUserDto): Promise<SearchExploreItem<AssetEntity>[]> {
|
async getExploreData(authUser: AuthUserDto): Promise<SearchExploreItem<AssetResponseDto>[]> {
|
||||||
this.assertEnabled();
|
this.assertEnabled();
|
||||||
return this.searchRepository.explore(authUser.id);
|
const results = await this.searchRepository.explore(authUser.id);
|
||||||
|
return results.map(({ fieldName, items }) => ({
|
||||||
|
fieldName,
|
||||||
|
items: items.map(({ value, data }) => ({ value, data: mapAsset(data) })),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
async search(authUser: AuthUserDto, dto: SearchDto): Promise<SearchResponseDto> {
|
async search(authUser: AuthUserDto, dto: SearchDto): Promise<SearchResponseDto> {
|
||||||
|
|
|
@ -446,7 +446,7 @@ const assetResponse: AssetResponseDto = {
|
||||||
type: AssetType.VIDEO,
|
type: AssetType.VIDEO,
|
||||||
originalPath: 'fake_path/jpeg',
|
originalPath: 'fake_path/jpeg',
|
||||||
originalFileName: 'asset_1.jpeg',
|
originalFileName: 'asset_1.jpeg',
|
||||||
resizePath: '',
|
resized: false,
|
||||||
fileModifiedAt: today.toISOString(),
|
fileModifiedAt: today.toISOString(),
|
||||||
fileCreatedAt: today.toISOString(),
|
fileCreatedAt: today.toISOString(),
|
||||||
updatedAt: today.toISOString(),
|
updatedAt: today.toISOString(),
|
||||||
|
@ -457,13 +457,12 @@ const assetResponse: AssetResponseDto = {
|
||||||
tags: [],
|
tags: [],
|
||||||
objects: ['a', 'b', 'c'],
|
objects: ['a', 'b', 'c'],
|
||||||
},
|
},
|
||||||
webpPath: '',
|
|
||||||
encodedVideoPath: '',
|
|
||||||
duration: '0:00:00.00000',
|
duration: '0:00:00.00000',
|
||||||
exifInfo: assetInfo,
|
exifInfo: assetInfo,
|
||||||
livePhotoVideoId: null,
|
livePhotoVideoId: null,
|
||||||
tags: [],
|
tags: [],
|
||||||
people: [],
|
people: [],
|
||||||
|
checksum: 'ZmlsZSBoYXNo',
|
||||||
};
|
};
|
||||||
|
|
||||||
const albumResponse: AlbumResponseDto = {
|
const albumResponse: AlbumResponseDto = {
|
||||||
|
|
24
web/src/api/open-api/api.ts
generated
24
web/src/api/open-api/api.ts
generated
|
@ -378,7 +378,7 @@ export interface AssetBulkUploadCheckItem {
|
||||||
*/
|
*/
|
||||||
'id': string;
|
'id': string;
|
||||||
/**
|
/**
|
||||||
*
|
* base64 or hex encoded sha1 hash
|
||||||
* @type {string}
|
* @type {string}
|
||||||
* @memberof AssetBulkUploadCheckItem
|
* @memberof AssetBulkUploadCheckItem
|
||||||
*/
|
*/
|
||||||
|
@ -586,10 +586,10 @@ export interface AssetResponseDto {
|
||||||
'originalFileName': string;
|
'originalFileName': string;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {string}
|
* @type {boolean}
|
||||||
* @memberof AssetResponseDto
|
* @memberof AssetResponseDto
|
||||||
*/
|
*/
|
||||||
'resizePath': string | null;
|
'resized': boolean;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {string}
|
* @type {string}
|
||||||
|
@ -632,18 +632,6 @@ export interface AssetResponseDto {
|
||||||
* @memberof AssetResponseDto
|
* @memberof AssetResponseDto
|
||||||
*/
|
*/
|
||||||
'duration': string;
|
'duration': string;
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof AssetResponseDto
|
|
||||||
*/
|
|
||||||
'webpPath': string | null;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof AssetResponseDto
|
|
||||||
*/
|
|
||||||
'encodedVideoPath'?: string | null;
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {ExifResponseDto}
|
* @type {ExifResponseDto}
|
||||||
|
@ -674,6 +662,12 @@ export interface AssetResponseDto {
|
||||||
* @memberof AssetResponseDto
|
* @memberof AssetResponseDto
|
||||||
*/
|
*/
|
||||||
'people'?: Array<PersonResponseDto>;
|
'people'?: Array<PersonResponseDto>;
|
||||||
|
/**
|
||||||
|
* base64 encoded sha1 hash
|
||||||
|
* @type {string}
|
||||||
|
* @memberof AssetResponseDto
|
||||||
|
*/
|
||||||
|
'checksum': string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -350,7 +350,7 @@
|
||||||
|
|
||||||
<div class="row-start-1 row-span-full col-start-1 col-span-4">
|
<div class="row-start-1 row-span-full col-start-1 col-span-4">
|
||||||
{#key asset.id}
|
{#key asset.id}
|
||||||
{#if !asset.resizePath}
|
{#if !asset.resized}
|
||||||
<div class="h-full w-full flex justify-center">
|
<div class="h-full w-full flex justify-center">
|
||||||
<div
|
<div
|
||||||
class="h-full bg-gray-100 dark:bg-immich-dark-gray flex items-center justify-center aspect-square px-auto"
|
class="h-full bg-gray-100 dark:bg-immich-dark-gray flex items-center justify-center aspect-square px-auto"
|
||||||
|
|
|
@ -123,7 +123,7 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if asset.resizePath}
|
{#if asset.resized}
|
||||||
<ImageThumbnail
|
<ImageThumbnail
|
||||||
url={api.getAssetThumbnailUrl(asset.id, format, publicSharedKey)}
|
url={api.getAssetThumbnailUrl(asset.id, format, publicSharedKey)}
|
||||||
altText={asset.originalFileName}
|
altText={asset.originalFileName}
|
||||||
|
|
Loading…
Reference in a new issue