mirror of
https://github.com/immich-app/immich.git
synced 2024-09-21 10:37:20 +00:00
refactor: add back isOffline and remove trashReason from asset, change sync job flow
This commit is contained in:
parent
e071cc9ca8
commit
ba0d5410cd
@ -1,4 +1,4 @@
|
|||||||
import { LibraryResponseDto, LoginResponseDto, TrashReason, getAllLibraries, scanLibrary } from '@immich/sdk';
|
import { LibraryResponseDto, LoginResponseDto, getAllLibraries, scanLibrary } from '@immich/sdk';
|
||||||
import { cpSync, existsSync } from 'node:fs';
|
import { cpSync, existsSync } from 'node:fs';
|
||||||
import { Socket } from 'socket.io-client';
|
import { Socket } from 'socket.io-client';
|
||||||
import { userDto, uuidDto } from 'src/fixtures';
|
import { userDto, uuidDto } from 'src/fixtures';
|
||||||
@ -411,7 +411,7 @@ describe('/libraries', () => {
|
|||||||
expect(assets.count).toBe(0);
|
expect(assets.count).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should trash an asset if its file is missing', async () => {
|
it('should set an asset offline if its file is missing', async () => {
|
||||||
const library = await utils.createLibrary(admin.accessToken, {
|
const library = await utils.createLibrary(admin.accessToken, {
|
||||||
ownerId: admin.userId,
|
ownerId: admin.userId,
|
||||||
importPaths: [`${testAssetDirInternal}/temp/offline`],
|
importPaths: [`${testAssetDirInternal}/temp/offline`],
|
||||||
@ -436,15 +436,14 @@ describe('/libraries', () => {
|
|||||||
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
||||||
|
|
||||||
const trashedAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id);
|
const trashedAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id);
|
||||||
expect(trashedAsset.isTrashed).toBe(true);
|
|
||||||
expect(trashedAsset.originalPath).toBe(`${testAssetDirInternal}/temp/offline/offline.png`);
|
expect(trashedAsset.originalPath).toBe(`${testAssetDirInternal}/temp/offline/offline.png`);
|
||||||
expect(trashedAsset.trashReason).toEqual(TrashReason.Offline);
|
expect(trashedAsset.isOffline).toEqual(true);
|
||||||
|
|
||||||
const { assets: newAssets } = await utils.metadataSearch(admin.accessToken, { libraryId: library.id });
|
const { assets: newAssets } = await utils.metadataSearch(admin.accessToken, { libraryId: library.id });
|
||||||
expect(newAssets.items).toEqual([]);
|
expect(newAssets.items).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should trash an asset if its file is not in any import path', async () => {
|
it('should set an asset offline its file is not in any import path', async () => {
|
||||||
const library = await utils.createLibrary(admin.accessToken, {
|
const library = await utils.createLibrary(admin.accessToken, {
|
||||||
ownerId: admin.userId,
|
ownerId: admin.userId,
|
||||||
importPaths: [`${testAssetDirInternal}/temp/offline`],
|
importPaths: [`${testAssetDirInternal}/temp/offline`],
|
||||||
@ -474,9 +473,8 @@ describe('/libraries', () => {
|
|||||||
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
||||||
|
|
||||||
const trashedAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id);
|
const trashedAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id);
|
||||||
expect(trashedAsset.isTrashed).toBe(true);
|
|
||||||
expect(trashedAsset.originalPath).toBe(`${testAssetDirInternal}/temp/offline/offline.png`);
|
expect(trashedAsset.originalPath).toBe(`${testAssetDirInternal}/temp/offline/offline.png`);
|
||||||
expect(trashedAsset.trashReason).toEqual(TrashReason.Offline);
|
expect(trashedAsset.isOffline).toBe(true);
|
||||||
|
|
||||||
const { assets: newAssets } = await utils.metadataSearch(admin.accessToken, { libraryId: library.id });
|
const { assets: newAssets } = await utils.metadataSearch(admin.accessToken, { libraryId: library.id });
|
||||||
|
|
||||||
@ -486,7 +484,7 @@ describe('/libraries', () => {
|
|||||||
utils.removeDirectory(`${testAssetDir}/temp/another-path/`);
|
utils.removeDirectory(`${testAssetDir}/temp/another-path/`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should trash an asset if its file is covered by an exclusion pattern', async () => {
|
it('should set an asset offline if its file is covered by an exclusion pattern', async () => {
|
||||||
const library = await utils.createLibrary(admin.accessToken, {
|
const library = await utils.createLibrary(admin.accessToken, {
|
||||||
ownerId: admin.userId,
|
ownerId: admin.userId,
|
||||||
importPaths: [`${testAssetDirInternal}/temp`],
|
importPaths: [`${testAssetDirInternal}/temp`],
|
||||||
@ -512,7 +510,7 @@ describe('/libraries', () => {
|
|||||||
const trashedAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id);
|
const trashedAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id);
|
||||||
expect(trashedAsset.isTrashed).toBe(true);
|
expect(trashedAsset.isTrashed).toBe(true);
|
||||||
expect(trashedAsset.originalPath).toBe(`${testAssetDirInternal}/temp/directoryB/assetB.png`);
|
expect(trashedAsset.originalPath).toBe(`${testAssetDirInternal}/temp/directoryB/assetB.png`);
|
||||||
expect(trashedAsset.trashReason).toEqual(TrashReason.Offline);
|
expect(trashedAsset.isOffline).toBe(true);
|
||||||
|
|
||||||
const { assets: newAssets } = await utils.metadataSearch(admin.accessToken, { libraryId: library.id });
|
const { assets: newAssets } = await utils.metadataSearch(admin.accessToken, { libraryId: library.id });
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { LoginResponseDto, TrashReason, getAssetInfo, getAssetStatistics, scanLibrary } from '@immich/sdk';
|
import { LoginResponseDto, getAssetInfo, getAssetStatistics, scanLibrary } from '@immich/sdk';
|
||||||
import { existsSync } from 'node:fs';
|
import { existsSync } from 'node:fs';
|
||||||
import { Socket } from 'socket.io-client';
|
import { Socket } from 'socket.io-client';
|
||||||
import { errorDto } from 'src/responses';
|
import { errorDto } from 'src/responses';
|
||||||
@ -35,9 +35,7 @@ describe('/trash', () => {
|
|||||||
await utils.deleteAssets(admin.accessToken, [assetId]);
|
await utils.deleteAssets(admin.accessToken, [assetId]);
|
||||||
|
|
||||||
const before = await getAssetInfo({ id: assetId }, { headers: asBearerAuth(admin.accessToken) });
|
const before = await getAssetInfo({ id: assetId }, { headers: asBearerAuth(admin.accessToken) });
|
||||||
expect(before).toStrictEqual(
|
expect(before).toStrictEqual(expect.objectContaining({ id: assetId, isTrashed: true }));
|
||||||
expect.objectContaining({ id: assetId, isTrashed: true, trashReason: TrashReason.Deleted }),
|
|
||||||
);
|
|
||||||
|
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.post('/trash/empty')
|
.post('/trash/empty')
|
||||||
@ -59,9 +57,7 @@ describe('/trash', () => {
|
|||||||
await utils.deleteAssets(admin.accessToken, [assetId]);
|
await utils.deleteAssets(admin.accessToken, [assetId]);
|
||||||
|
|
||||||
const before = await getAssetInfo({ id: assetId }, { headers: asBearerAuth(admin.accessToken) });
|
const before = await getAssetInfo({ id: assetId }, { headers: asBearerAuth(admin.accessToken) });
|
||||||
expect(before).toStrictEqual(
|
expect(before).toStrictEqual(expect.objectContaining({ id: assetId, isTrashed: true, isArchived: true }));
|
||||||
expect.objectContaining({ id: assetId, isTrashed: true, isArchived: true, trashReason: TrashReason.Deleted }),
|
|
||||||
);
|
|
||||||
|
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.post('/trash/empty')
|
.post('/trash/empty')
|
||||||
@ -160,17 +156,13 @@ describe('/trash', () => {
|
|||||||
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
await utils.waitForQueueFinish(admin.accessToken, 'library');
|
||||||
|
|
||||||
const before = await getAssetInfo({ id: assetId }, { headers: asBearerAuth(admin.accessToken) });
|
const before = await getAssetInfo({ id: assetId }, { headers: asBearerAuth(admin.accessToken) });
|
||||||
expect(before).toStrictEqual(
|
expect(before).toStrictEqual(expect.objectContaining({ id: assetId, isOffline: true }));
|
||||||
expect.objectContaining({ id: assetId, isTrashed: true, trashReason: TrashReason.Offline }),
|
|
||||||
);
|
|
||||||
|
|
||||||
const { status } = await request(app).post('/trash/restore').set('Authorization', `Bearer ${admin.accessToken}`);
|
const { status } = await request(app).post('/trash/restore').set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
expect(status).toBe(204);
|
expect(status).toBe(204);
|
||||||
|
|
||||||
const after = await getAssetInfo({ id: assetId }, { headers: asBearerAuth(admin.accessToken) });
|
const after = await getAssetInfo({ id: assetId }, { headers: asBearerAuth(admin.accessToken) });
|
||||||
expect(after).toStrictEqual(
|
expect(after).toStrictEqual(expect.objectContaining({ id: assetId, isOffline: true }));
|
||||||
expect.objectContaining({ id: assetId, isTrashed: true, trashReason: TrashReason.Offline }),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
2
mobile/openapi/README.md
generated
2
mobile/openapi/README.md
generated
@ -132,7 +132,6 @@ Class | Method | HTTP request | Description
|
|||||||
*LibrariesApi* | [**getAllLibraries**](doc//LibrariesApi.md#getalllibraries) | **GET** /libraries |
|
*LibrariesApi* | [**getAllLibraries**](doc//LibrariesApi.md#getalllibraries) | **GET** /libraries |
|
||||||
*LibrariesApi* | [**getLibrary**](doc//LibrariesApi.md#getlibrary) | **GET** /libraries/{id} |
|
*LibrariesApi* | [**getLibrary**](doc//LibrariesApi.md#getlibrary) | **GET** /libraries/{id} |
|
||||||
*LibrariesApi* | [**getLibraryStatistics**](doc//LibrariesApi.md#getlibrarystatistics) | **GET** /libraries/{id}/statistics |
|
*LibrariesApi* | [**getLibraryStatistics**](doc//LibrariesApi.md#getlibrarystatistics) | **GET** /libraries/{id}/statistics |
|
||||||
*LibrariesApi* | [**removeOfflineFiles**](doc//LibrariesApi.md#removeofflinefiles) | **POST** /libraries/{id}/removeOffline |
|
|
||||||
*LibrariesApi* | [**scanLibrary**](doc//LibrariesApi.md#scanlibrary) | **POST** /libraries/{id}/scan |
|
*LibrariesApi* | [**scanLibrary**](doc//LibrariesApi.md#scanlibrary) | **POST** /libraries/{id}/scan |
|
||||||
*LibrariesApi* | [**updateLibrary**](doc//LibrariesApi.md#updatelibrary) | **PUT** /libraries/{id} |
|
*LibrariesApi* | [**updateLibrary**](doc//LibrariesApi.md#updatelibrary) | **PUT** /libraries/{id} |
|
||||||
*LibrariesApi* | [**validate**](doc//LibrariesApi.md#validate) | **POST** /libraries/{id}/validate |
|
*LibrariesApi* | [**validate**](doc//LibrariesApi.md#validate) | **POST** /libraries/{id}/validate |
|
||||||
@ -384,7 +383,6 @@ Class | Method | HTTP request | Description
|
|||||||
- [ReactionLevel](doc//ReactionLevel.md)
|
- [ReactionLevel](doc//ReactionLevel.md)
|
||||||
- [ReactionType](doc//ReactionType.md)
|
- [ReactionType](doc//ReactionType.md)
|
||||||
- [ReverseGeocodingStateResponseDto](doc//ReverseGeocodingStateResponseDto.md)
|
- [ReverseGeocodingStateResponseDto](doc//ReverseGeocodingStateResponseDto.md)
|
||||||
- [ScanLibraryDto](doc//ScanLibraryDto.md)
|
|
||||||
- [SearchAlbumResponseDto](doc//SearchAlbumResponseDto.md)
|
- [SearchAlbumResponseDto](doc//SearchAlbumResponseDto.md)
|
||||||
- [SearchAssetResponseDto](doc//SearchAssetResponseDto.md)
|
- [SearchAssetResponseDto](doc//SearchAssetResponseDto.md)
|
||||||
- [SearchExploreItem](doc//SearchExploreItem.md)
|
- [SearchExploreItem](doc//SearchExploreItem.md)
|
||||||
|
1
mobile/openapi/lib/api.dart
generated
1
mobile/openapi/lib/api.dart
generated
@ -197,7 +197,6 @@ part 'model/ratings_update.dart';
|
|||||||
part 'model/reaction_level.dart';
|
part 'model/reaction_level.dart';
|
||||||
part 'model/reaction_type.dart';
|
part 'model/reaction_type.dart';
|
||||||
part 'model/reverse_geocoding_state_response_dto.dart';
|
part 'model/reverse_geocoding_state_response_dto.dart';
|
||||||
part 'model/scan_library_dto.dart';
|
|
||||||
part 'model/search_album_response_dto.dart';
|
part 'model/search_album_response_dto.dart';
|
||||||
part 'model/search_asset_response_dto.dart';
|
part 'model/search_asset_response_dto.dart';
|
||||||
part 'model/search_explore_item.dart';
|
part 'model/search_explore_item.dart';
|
||||||
|
2
mobile/openapi/lib/api_client.dart
generated
2
mobile/openapi/lib/api_client.dart
generated
@ -449,8 +449,6 @@ class ApiClient {
|
|||||||
return ReactionTypeTypeTransformer().decode(value);
|
return ReactionTypeTypeTransformer().decode(value);
|
||||||
case 'ReverseGeocodingStateResponseDto':
|
case 'ReverseGeocodingStateResponseDto':
|
||||||
return ReverseGeocodingStateResponseDto.fromJson(value);
|
return ReverseGeocodingStateResponseDto.fromJson(value);
|
||||||
case 'ScanLibraryDto':
|
|
||||||
return ScanLibraryDto.fromJson(value);
|
|
||||||
case 'SearchAlbumResponseDto':
|
case 'SearchAlbumResponseDto':
|
||||||
return SearchAlbumResponseDto.fromJson(value);
|
return SearchAlbumResponseDto.fromJson(value);
|
||||||
case 'SearchAssetResponseDto':
|
case 'SearchAssetResponseDto':
|
||||||
|
91
mobile/openapi/lib/model/asset_bulk_delete_dto.dart
generated
91
mobile/openapi/lib/model/asset_bulk_delete_dto.dart
generated
@ -15,7 +15,6 @@ class AssetBulkDeleteDto {
|
|||||||
AssetBulkDeleteDto({
|
AssetBulkDeleteDto({
|
||||||
this.force,
|
this.force,
|
||||||
this.ids = const [],
|
this.ids = const [],
|
||||||
this.trashReason,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
///
|
///
|
||||||
@ -28,23 +27,19 @@ class AssetBulkDeleteDto {
|
|||||||
|
|
||||||
List<String> ids;
|
List<String> ids;
|
||||||
|
|
||||||
AssetBulkDeleteDtoTrashReasonEnum? trashReason;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) => identical(this, other) || other is AssetBulkDeleteDto &&
|
bool operator ==(Object other) => identical(this, other) || other is AssetBulkDeleteDto &&
|
||||||
other.force == force &&
|
other.force == force &&
|
||||||
_deepEquality.equals(other.ids, ids) &&
|
_deepEquality.equals(other.ids, ids);
|
||||||
other.trashReason == trashReason;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
// ignore: unnecessary_parenthesis
|
// ignore: unnecessary_parenthesis
|
||||||
(force == null ? 0 : force!.hashCode) +
|
(force == null ? 0 : force!.hashCode) +
|
||||||
(ids.hashCode) +
|
(ids.hashCode);
|
||||||
(trashReason == null ? 0 : trashReason!.hashCode);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'AssetBulkDeleteDto[force=$force, ids=$ids, trashReason=$trashReason]';
|
String toString() => 'AssetBulkDeleteDto[force=$force, ids=$ids]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
@ -54,11 +49,6 @@ class AssetBulkDeleteDto {
|
|||||||
// json[r'force'] = null;
|
// json[r'force'] = null;
|
||||||
}
|
}
|
||||||
json[r'ids'] = this.ids;
|
json[r'ids'] = this.ids;
|
||||||
if (this.trashReason != null) {
|
|
||||||
json[r'trashReason'] = this.trashReason;
|
|
||||||
} else {
|
|
||||||
// json[r'trashReason'] = null;
|
|
||||||
}
|
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,7 +64,6 @@ class AssetBulkDeleteDto {
|
|||||||
ids: json[r'ids'] is Iterable
|
ids: json[r'ids'] is Iterable
|
||||||
? (json[r'ids'] as Iterable).cast<String>().toList(growable: false)
|
? (json[r'ids'] as Iterable).cast<String>().toList(growable: false)
|
||||||
: const [],
|
: const [],
|
||||||
trashReason: AssetBulkDeleteDtoTrashReasonEnum.fromJson(json[r'trashReason']),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@ -126,77 +115,3 @@ class AssetBulkDeleteDto {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class AssetBulkDeleteDtoTrashReasonEnum {
|
|
||||||
/// Instantiate a new enum with the provided [value].
|
|
||||||
const AssetBulkDeleteDtoTrashReasonEnum._(this.value);
|
|
||||||
|
|
||||||
/// The underlying value of this enum member.
|
|
||||||
final String value;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => value;
|
|
||||||
|
|
||||||
String toJson() => value;
|
|
||||||
|
|
||||||
static const deleted = AssetBulkDeleteDtoTrashReasonEnum._(r'deleted');
|
|
||||||
static const offline = AssetBulkDeleteDtoTrashReasonEnum._(r'offline');
|
|
||||||
|
|
||||||
/// List of all possible values in this [enum][AssetBulkDeleteDtoTrashReasonEnum].
|
|
||||||
static const values = <AssetBulkDeleteDtoTrashReasonEnum>[
|
|
||||||
deleted,
|
|
||||||
offline,
|
|
||||||
];
|
|
||||||
|
|
||||||
static AssetBulkDeleteDtoTrashReasonEnum? fromJson(dynamic value) => AssetBulkDeleteDtoTrashReasonEnumTypeTransformer().decode(value);
|
|
||||||
|
|
||||||
static List<AssetBulkDeleteDtoTrashReasonEnum> listFromJson(dynamic json, {bool growable = false,}) {
|
|
||||||
final result = <AssetBulkDeleteDtoTrashReasonEnum>[];
|
|
||||||
if (json is List && json.isNotEmpty) {
|
|
||||||
for (final row in json) {
|
|
||||||
final value = AssetBulkDeleteDtoTrashReasonEnum.fromJson(row);
|
|
||||||
if (value != null) {
|
|
||||||
result.add(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result.toList(growable: growable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Transformation class that can [encode] an instance of [AssetBulkDeleteDtoTrashReasonEnum] to String,
|
|
||||||
/// and [decode] dynamic data back to [AssetBulkDeleteDtoTrashReasonEnum].
|
|
||||||
class AssetBulkDeleteDtoTrashReasonEnumTypeTransformer {
|
|
||||||
factory AssetBulkDeleteDtoTrashReasonEnumTypeTransformer() => _instance ??= const AssetBulkDeleteDtoTrashReasonEnumTypeTransformer._();
|
|
||||||
|
|
||||||
const AssetBulkDeleteDtoTrashReasonEnumTypeTransformer._();
|
|
||||||
|
|
||||||
String encode(AssetBulkDeleteDtoTrashReasonEnum data) => data.value;
|
|
||||||
|
|
||||||
/// Decodes a [dynamic value][data] to a AssetBulkDeleteDtoTrashReasonEnum.
|
|
||||||
///
|
|
||||||
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
|
|
||||||
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
|
|
||||||
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
|
|
||||||
///
|
|
||||||
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
|
|
||||||
/// and users are still using an old app with the old code.
|
|
||||||
AssetBulkDeleteDtoTrashReasonEnum? decode(dynamic data, {bool allowNull = true}) {
|
|
||||||
if (data != null) {
|
|
||||||
switch (data) {
|
|
||||||
case r'deleted': return AssetBulkDeleteDtoTrashReasonEnum.deleted;
|
|
||||||
case r'offline': return AssetBulkDeleteDtoTrashReasonEnum.offline;
|
|
||||||
default:
|
|
||||||
if (!allowNull) {
|
|
||||||
throw ArgumentError('Unknown enum value to decode: $data');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Singleton [AssetBulkDeleteDtoTrashReasonEnumTypeTransformer] instance.
|
|
||||||
static AssetBulkDeleteDtoTrashReasonEnumTypeTransformer? _instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
21
mobile/openapi/lib/model/asset_response_dto.dart
generated
21
mobile/openapi/lib/model/asset_response_dto.dart
generated
@ -25,6 +25,7 @@ class AssetResponseDto {
|
|||||||
required this.id,
|
required this.id,
|
||||||
required this.isArchived,
|
required this.isArchived,
|
||||||
required this.isFavorite,
|
required this.isFavorite,
|
||||||
|
required this.isOffline,
|
||||||
required this.isTrashed,
|
required this.isTrashed,
|
||||||
this.libraryId,
|
this.libraryId,
|
||||||
this.livePhotoVideoId,
|
this.livePhotoVideoId,
|
||||||
@ -40,7 +41,6 @@ class AssetResponseDto {
|
|||||||
this.stack,
|
this.stack,
|
||||||
this.tags = const [],
|
this.tags = const [],
|
||||||
required this.thumbhash,
|
required this.thumbhash,
|
||||||
this.trashReason,
|
|
||||||
required this.type,
|
required this.type,
|
||||||
this.unassignedFaces = const [],
|
this.unassignedFaces = const [],
|
||||||
required this.updatedAt,
|
required this.updatedAt,
|
||||||
@ -77,6 +77,8 @@ class AssetResponseDto {
|
|||||||
|
|
||||||
bool isFavorite;
|
bool isFavorite;
|
||||||
|
|
||||||
|
bool isOffline;
|
||||||
|
|
||||||
bool isTrashed;
|
bool isTrashed;
|
||||||
|
|
||||||
/// This property was deprecated in v1.106.0
|
/// This property was deprecated in v1.106.0
|
||||||
@ -133,8 +135,6 @@ class AssetResponseDto {
|
|||||||
|
|
||||||
String? thumbhash;
|
String? thumbhash;
|
||||||
|
|
||||||
String? trashReason;
|
|
||||||
|
|
||||||
AssetTypeEnum type;
|
AssetTypeEnum type;
|
||||||
|
|
||||||
List<AssetFaceWithoutPersonResponseDto> unassignedFaces;
|
List<AssetFaceWithoutPersonResponseDto> unassignedFaces;
|
||||||
@ -155,6 +155,7 @@ class AssetResponseDto {
|
|||||||
other.id == id &&
|
other.id == id &&
|
||||||
other.isArchived == isArchived &&
|
other.isArchived == isArchived &&
|
||||||
other.isFavorite == isFavorite &&
|
other.isFavorite == isFavorite &&
|
||||||
|
other.isOffline == isOffline &&
|
||||||
other.isTrashed == isTrashed &&
|
other.isTrashed == isTrashed &&
|
||||||
other.libraryId == libraryId &&
|
other.libraryId == libraryId &&
|
||||||
other.livePhotoVideoId == livePhotoVideoId &&
|
other.livePhotoVideoId == livePhotoVideoId &&
|
||||||
@ -170,7 +171,6 @@ class AssetResponseDto {
|
|||||||
other.stack == stack &&
|
other.stack == stack &&
|
||||||
_deepEquality.equals(other.tags, tags) &&
|
_deepEquality.equals(other.tags, tags) &&
|
||||||
other.thumbhash == thumbhash &&
|
other.thumbhash == thumbhash &&
|
||||||
other.trashReason == trashReason &&
|
|
||||||
other.type == type &&
|
other.type == type &&
|
||||||
_deepEquality.equals(other.unassignedFaces, unassignedFaces) &&
|
_deepEquality.equals(other.unassignedFaces, unassignedFaces) &&
|
||||||
other.updatedAt == updatedAt;
|
other.updatedAt == updatedAt;
|
||||||
@ -190,6 +190,7 @@ class AssetResponseDto {
|
|||||||
(id.hashCode) +
|
(id.hashCode) +
|
||||||
(isArchived.hashCode) +
|
(isArchived.hashCode) +
|
||||||
(isFavorite.hashCode) +
|
(isFavorite.hashCode) +
|
||||||
|
(isOffline.hashCode) +
|
||||||
(isTrashed.hashCode) +
|
(isTrashed.hashCode) +
|
||||||
(libraryId == null ? 0 : libraryId!.hashCode) +
|
(libraryId == null ? 0 : libraryId!.hashCode) +
|
||||||
(livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) +
|
(livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) +
|
||||||
@ -205,13 +206,12 @@ class AssetResponseDto {
|
|||||||
(stack == null ? 0 : stack!.hashCode) +
|
(stack == null ? 0 : stack!.hashCode) +
|
||||||
(tags.hashCode) +
|
(tags.hashCode) +
|
||||||
(thumbhash == null ? 0 : thumbhash!.hashCode) +
|
(thumbhash == null ? 0 : thumbhash!.hashCode) +
|
||||||
(trashReason == null ? 0 : trashReason!.hashCode) +
|
|
||||||
(type.hashCode) +
|
(type.hashCode) +
|
||||||
(unassignedFaces.hashCode) +
|
(unassignedFaces.hashCode) +
|
||||||
(updatedAt.hashCode);
|
(updatedAt.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'AssetResponseDto[checksum=$checksum, deviceAssetId=$deviceAssetId, deviceId=$deviceId, duplicateId=$duplicateId, duration=$duration, exifInfo=$exifInfo, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, hasMetadata=$hasMetadata, id=$id, isArchived=$isArchived, isFavorite=$isFavorite, isTrashed=$isTrashed, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, originalMimeType=$originalMimeType, originalPath=$originalPath, owner=$owner, ownerId=$ownerId, people=$people, resized=$resized, smartInfo=$smartInfo, stack=$stack, tags=$tags, thumbhash=$thumbhash, trashReason=$trashReason, type=$type, unassignedFaces=$unassignedFaces, updatedAt=$updatedAt]';
|
String toString() => 'AssetResponseDto[checksum=$checksum, deviceAssetId=$deviceAssetId, deviceId=$deviceId, duplicateId=$duplicateId, duration=$duration, exifInfo=$exifInfo, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, hasMetadata=$hasMetadata, id=$id, isArchived=$isArchived, isFavorite=$isFavorite, isOffline=$isOffline, isTrashed=$isTrashed, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, originalMimeType=$originalMimeType, originalPath=$originalPath, owner=$owner, ownerId=$ownerId, people=$people, resized=$resized, smartInfo=$smartInfo, stack=$stack, tags=$tags, thumbhash=$thumbhash, type=$type, unassignedFaces=$unassignedFaces, updatedAt=$updatedAt]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
@ -235,6 +235,7 @@ class AssetResponseDto {
|
|||||||
json[r'id'] = this.id;
|
json[r'id'] = this.id;
|
||||||
json[r'isArchived'] = this.isArchived;
|
json[r'isArchived'] = this.isArchived;
|
||||||
json[r'isFavorite'] = this.isFavorite;
|
json[r'isFavorite'] = this.isFavorite;
|
||||||
|
json[r'isOffline'] = this.isOffline;
|
||||||
json[r'isTrashed'] = this.isTrashed;
|
json[r'isTrashed'] = this.isTrashed;
|
||||||
if (this.libraryId != null) {
|
if (this.libraryId != null) {
|
||||||
json[r'libraryId'] = this.libraryId;
|
json[r'libraryId'] = this.libraryId;
|
||||||
@ -281,11 +282,6 @@ class AssetResponseDto {
|
|||||||
json[r'thumbhash'] = this.thumbhash;
|
json[r'thumbhash'] = this.thumbhash;
|
||||||
} else {
|
} else {
|
||||||
// json[r'thumbhash'] = null;
|
// json[r'thumbhash'] = null;
|
||||||
}
|
|
||||||
if (this.trashReason != null) {
|
|
||||||
json[r'trashReason'] = this.trashReason;
|
|
||||||
} else {
|
|
||||||
// json[r'trashReason'] = null;
|
|
||||||
}
|
}
|
||||||
json[r'type'] = this.type;
|
json[r'type'] = this.type;
|
||||||
json[r'unassignedFaces'] = this.unassignedFaces;
|
json[r'unassignedFaces'] = this.unassignedFaces;
|
||||||
@ -313,6 +309,7 @@ class AssetResponseDto {
|
|||||||
id: mapValueOfType<String>(json, r'id')!,
|
id: mapValueOfType<String>(json, r'id')!,
|
||||||
isArchived: mapValueOfType<bool>(json, r'isArchived')!,
|
isArchived: mapValueOfType<bool>(json, r'isArchived')!,
|
||||||
isFavorite: mapValueOfType<bool>(json, r'isFavorite')!,
|
isFavorite: mapValueOfType<bool>(json, r'isFavorite')!,
|
||||||
|
isOffline: mapValueOfType<bool>(json, r'isOffline')!,
|
||||||
isTrashed: mapValueOfType<bool>(json, r'isTrashed')!,
|
isTrashed: mapValueOfType<bool>(json, r'isTrashed')!,
|
||||||
libraryId: mapValueOfType<String>(json, r'libraryId'),
|
libraryId: mapValueOfType<String>(json, r'libraryId'),
|
||||||
livePhotoVideoId: mapValueOfType<String>(json, r'livePhotoVideoId'),
|
livePhotoVideoId: mapValueOfType<String>(json, r'livePhotoVideoId'),
|
||||||
@ -328,7 +325,6 @@ class AssetResponseDto {
|
|||||||
stack: AssetStackResponseDto.fromJson(json[r'stack']),
|
stack: AssetStackResponseDto.fromJson(json[r'stack']),
|
||||||
tags: TagResponseDto.listFromJson(json[r'tags']),
|
tags: TagResponseDto.listFromJson(json[r'tags']),
|
||||||
thumbhash: mapValueOfType<String>(json, r'thumbhash'),
|
thumbhash: mapValueOfType<String>(json, r'thumbhash'),
|
||||||
trashReason: mapValueOfType<String>(json, r'trashReason'),
|
|
||||||
type: AssetTypeEnum.fromJson(json[r'type'])!,
|
type: AssetTypeEnum.fromJson(json[r'type'])!,
|
||||||
unassignedFaces: AssetFaceWithoutPersonResponseDto.listFromJson(json[r'unassignedFaces']),
|
unassignedFaces: AssetFaceWithoutPersonResponseDto.listFromJson(json[r'unassignedFaces']),
|
||||||
updatedAt: mapDateTime(json, r'updatedAt', r'')!,
|
updatedAt: mapDateTime(json, r'updatedAt', r'')!,
|
||||||
@ -389,6 +385,7 @@ class AssetResponseDto {
|
|||||||
'id',
|
'id',
|
||||||
'isArchived',
|
'isArchived',
|
||||||
'isFavorite',
|
'isFavorite',
|
||||||
|
'isOffline',
|
||||||
'isTrashed',
|
'isTrashed',
|
||||||
'localDateTime',
|
'localDateTime',
|
||||||
'originalFileName',
|
'originalFileName',
|
||||||
|
@ -7832,13 +7832,6 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
|
||||||
"trashReason": {
|
|
||||||
"enum": [
|
|
||||||
"deleted",
|
|
||||||
"offline"
|
|
||||||
],
|
|
||||||
"type": "string"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
@ -8376,6 +8369,9 @@
|
|||||||
"isFavorite": {
|
"isFavorite": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"isOffline": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"isTrashed": {
|
"isTrashed": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
@ -8440,10 +8436,6 @@
|
|||||||
"nullable": true,
|
"nullable": true,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"trashReason": {
|
|
||||||
"nullable": true,
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"type": {
|
"type": {
|
||||||
"$ref": "#/components/schemas/AssetTypeEnum"
|
"$ref": "#/components/schemas/AssetTypeEnum"
|
||||||
},
|
},
|
||||||
@ -8469,6 +8461,7 @@
|
|||||||
"id",
|
"id",
|
||||||
"isArchived",
|
"isArchived",
|
||||||
"isFavorite",
|
"isFavorite",
|
||||||
|
"isOffline",
|
||||||
"isTrashed",
|
"isTrashed",
|
||||||
"localDateTime",
|
"localDateTime",
|
||||||
"originalFileName",
|
"originalFileName",
|
||||||
|
@ -253,6 +253,7 @@ export type AssetResponseDto = {
|
|||||||
id: string;
|
id: string;
|
||||||
isArchived: boolean;
|
isArchived: boolean;
|
||||||
isFavorite: boolean;
|
isFavorite: boolean;
|
||||||
|
isOffline: boolean;
|
||||||
isTrashed: boolean;
|
isTrashed: boolean;
|
||||||
/** This property was deprecated in v1.106.0 */
|
/** This property was deprecated in v1.106.0 */
|
||||||
libraryId?: string | null;
|
libraryId?: string | null;
|
||||||
@ -270,7 +271,6 @@ export type AssetResponseDto = {
|
|||||||
stack?: (AssetStackResponseDto) | null;
|
stack?: (AssetStackResponseDto) | null;
|
||||||
tags?: TagResponseDto[];
|
tags?: TagResponseDto[];
|
||||||
thumbhash: string | null;
|
thumbhash: string | null;
|
||||||
trashReason?: string | null;
|
|
||||||
"type": AssetTypeEnum;
|
"type": AssetTypeEnum;
|
||||||
unassignedFaces?: AssetFaceWithoutPersonResponseDto[];
|
unassignedFaces?: AssetFaceWithoutPersonResponseDto[];
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
@ -356,7 +356,6 @@ export type ApiKeyUpdateDto = {
|
|||||||
export type AssetBulkDeleteDto = {
|
export type AssetBulkDeleteDto = {
|
||||||
force?: boolean;
|
force?: boolean;
|
||||||
ids: string[];
|
ids: string[];
|
||||||
trashReason?: TrashReason;
|
|
||||||
};
|
};
|
||||||
export type AssetMediaCreateDto = {
|
export type AssetMediaCreateDto = {
|
||||||
assetData: Blob;
|
assetData: Blob;
|
||||||
@ -3350,10 +3349,6 @@ export enum Permission {
|
|||||||
AdminUserUpdate = "admin.user.update",
|
AdminUserUpdate = "admin.user.update",
|
||||||
AdminUserDelete = "admin.user.delete"
|
AdminUserDelete = "admin.user.delete"
|
||||||
}
|
}
|
||||||
export enum TrashReason {
|
|
||||||
Deleted = "deleted",
|
|
||||||
Offline = "offline"
|
|
||||||
}
|
|
||||||
export enum AssetMediaStatus {
|
export enum AssetMediaStatus {
|
||||||
Created = "created",
|
Created = "created",
|
||||||
Replaced = "replaced",
|
Replaced = "replaced",
|
||||||
|
@ -43,7 +43,7 @@ export class AssetResponseDto extends SanitizedAssetResponseDto {
|
|||||||
isFavorite!: boolean;
|
isFavorite!: boolean;
|
||||||
isArchived!: boolean;
|
isArchived!: boolean;
|
||||||
isTrashed!: boolean;
|
isTrashed!: boolean;
|
||||||
trashReason?: string | null;
|
isOffline!: boolean;
|
||||||
exifInfo?: ExifResponseDto;
|
exifInfo?: ExifResponseDto;
|
||||||
smartInfo?: SmartInfoResponseDto;
|
smartInfo?: SmartInfoResponseDto;
|
||||||
tags?: TagResponseDto[];
|
tags?: TagResponseDto[];
|
||||||
@ -139,7 +139,7 @@ export function mapAsset(entity: AssetEntity, options: AssetMapOptions = {}): As
|
|||||||
isFavorite: options.auth?.user.id === entity.ownerId ? entity.isFavorite : false,
|
isFavorite: options.auth?.user.id === entity.ownerId ? entity.isFavorite : false,
|
||||||
isArchived: entity.isArchived,
|
isArchived: entity.isArchived,
|
||||||
isTrashed: !!entity.deletedAt,
|
isTrashed: !!entity.deletedAt,
|
||||||
trashReason: entity.trashReason,
|
isOffline: entity.isOffline,
|
||||||
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,
|
||||||
|
@ -84,9 +84,6 @@ export class RandomAssetsDto {
|
|||||||
export class AssetBulkDeleteDto extends BulkIdsDto {
|
export class AssetBulkDeleteDto extends BulkIdsDto {
|
||||||
@ValidateBoolean({ optional: true })
|
@ValidateBoolean({ optional: true })
|
||||||
force?: boolean;
|
force?: boolean;
|
||||||
|
|
||||||
@Optional()
|
|
||||||
trashReason?: AssetTrashReason;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AssetIdsDto {
|
export class AssetIdsDto {
|
||||||
@ -94,11 +91,6 @@ export class AssetIdsDto {
|
|||||||
assetIds!: string[];
|
assetIds!: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AssetTrashReason {
|
|
||||||
DELETED = 'deleted',
|
|
||||||
OFFLINE = 'offline',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum AssetJobName {
|
export enum AssetJobName {
|
||||||
REGENERATE_THUMBNAIL = 'regenerate-thumbnail',
|
REGENERATE_THUMBNAIL = 'regenerate-thumbnail',
|
||||||
REFRESH_METADATA = 'refresh-metadata',
|
REFRESH_METADATA = 'refresh-metadata',
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { AssetTrashReason } from 'src/dtos/asset.dto';
|
|
||||||
import { AlbumEntity } from 'src/entities/album.entity';
|
import { AlbumEntity } from 'src/entities/album.entity';
|
||||||
import { AssetFaceEntity } from 'src/entities/asset-face.entity';
|
import { AssetFaceEntity } from 'src/entities/asset-face.entity';
|
||||||
import { AssetFileEntity } from 'src/entities/asset-files.entity';
|
import { AssetFileEntity } from 'src/entities/asset-files.entity';
|
||||||
@ -171,12 +170,8 @@ export class AssetEntity {
|
|||||||
@OneToOne(() => AssetJobStatusEntity, (jobStatus) => jobStatus.asset, { nullable: true })
|
@OneToOne(() => AssetJobStatusEntity, (jobStatus) => jobStatus.asset, { nullable: true })
|
||||||
jobStatus?: AssetJobStatusEntity;
|
jobStatus?: AssetJobStatusEntity;
|
||||||
|
|
||||||
@Column({
|
@Column({ type: 'boolean', default: false })
|
||||||
type: 'enum',
|
isOffline!: boolean;
|
||||||
enum: AssetTrashReason,
|
|
||||||
nullable: true,
|
|
||||||
})
|
|
||||||
trashReason!: AssetTrashReason | null;
|
|
||||||
|
|
||||||
@Index('IDX_assets_duplicateId')
|
@Index('IDX_assets_duplicateId')
|
||||||
@Column({ type: 'uuid', nullable: true })
|
@Column({ type: 'uuid', nullable: true })
|
||||||
|
@ -146,8 +146,6 @@ export type AssetPathEntity = Pick<AssetEntity, 'id' | 'originalPath'>;
|
|||||||
export const IAssetRepository = 'IAssetRepository';
|
export const IAssetRepository = 'IAssetRepository';
|
||||||
|
|
||||||
export interface IAssetRepository {
|
export interface IAssetRepository {
|
||||||
getAssetsByOriginalPath(userId: string, partialPath: string): Promise<AssetEntity[]>;
|
|
||||||
getUniqueOriginalPaths(userId: string): Promise<string[]>;
|
|
||||||
create(asset: AssetCreate): Promise<AssetEntity>;
|
create(asset: AssetCreate): Promise<AssetEntity>;
|
||||||
getByIds(
|
getByIds(
|
||||||
ids: string[],
|
ids: string[],
|
||||||
@ -156,13 +154,6 @@ export interface IAssetRepository {
|
|||||||
): Promise<AssetEntity[]>;
|
): Promise<AssetEntity[]>;
|
||||||
getByIdsWithAllRelations(ids: string[]): Promise<AssetEntity[]>;
|
getByIdsWithAllRelations(ids: string[]): Promise<AssetEntity[]>;
|
||||||
getByDayOfYear(ownerIds: string[], monthDay: MonthDay): Promise<AssetEntity[]>;
|
getByDayOfYear(ownerIds: string[], monthDay: MonthDay): Promise<AssetEntity[]>;
|
||||||
getByIds(
|
|
||||||
ids: string[],
|
|
||||||
relations?: FindOptionsRelations<AssetEntity>,
|
|
||||||
select?: FindOptionsSelect<AssetEntity>,
|
|
||||||
): Promise<AssetEntity[]>;
|
|
||||||
getByIdsWithAllRelations(ids: string[]): Promise<AssetEntity[]>;
|
|
||||||
getByDayOfYear(ownerIds: string[], monthDay: MonthDay): Promise<AssetEntity[]>;
|
|
||||||
getByChecksum(options: { ownerId: string; checksum: Buffer; libraryId?: string }): Promise<AssetEntity | null>;
|
getByChecksum(options: { ownerId: string; checksum: Buffer; libraryId?: string }): Promise<AssetEntity | null>;
|
||||||
getByChecksums(userId: string, checksums: Buffer[]): Promise<AssetEntity[]>;
|
getByChecksums(userId: string, checksums: Buffer[]): Promise<AssetEntity[]>;
|
||||||
getUploadAssetIdByChecksum(ownerId: string, checksum: Buffer): Promise<string | undefined>;
|
getUploadAssetIdByChecksum(ownerId: string, checksum: Buffer): Promise<string | undefined>;
|
||||||
@ -181,15 +172,6 @@ export interface IAssetRepository {
|
|||||||
libraryId?: string,
|
libraryId?: string,
|
||||||
withDeleted?: boolean,
|
withDeleted?: boolean,
|
||||||
): Paginated<AssetEntity>;
|
): Paginated<AssetEntity>;
|
||||||
getRandom(userId: string, count: number): Promise<AssetEntity[]>;
|
|
||||||
getFirstAssetForAlbumId(albumId: string): Promise<AssetEntity | null>;
|
|
||||||
getWithout(pagination: PaginationOptions, property: WithoutProperty): Paginated<AssetEntity>;
|
|
||||||
getWith(
|
|
||||||
pagination: PaginationOptions,
|
|
||||||
property: WithProperty,
|
|
||||||
libraryId?: string,
|
|
||||||
withDeleted?: boolean,
|
|
||||||
): Paginated<AssetEntity>;
|
|
||||||
getRandom(userIds: string[], count: number): Promise<AssetEntity[]>;
|
getRandom(userIds: string[], count: number): Promise<AssetEntity[]>;
|
||||||
getLastUpdatedAssetForAlbumId(albumId: string): Promise<AssetEntity | null>;
|
getLastUpdatedAssetForAlbumId(albumId: string): Promise<AssetEntity | null>;
|
||||||
getExternalLibraryAssetPaths(pagination: PaginationOptions, libraryId: string): Paginated<AssetPathEntity>;
|
getExternalLibraryAssetPaths(pagination: PaginationOptions, libraryId: string): Paginated<AssetPathEntity>;
|
||||||
@ -198,31 +180,12 @@ export interface IAssetRepository {
|
|||||||
getAll(pagination: PaginationOptions, options?: AssetSearchOptions): Paginated<AssetEntity>;
|
getAll(pagination: PaginationOptions, options?: AssetSearchOptions): Paginated<AssetEntity>;
|
||||||
getAllByDeviceId(userId: string, deviceId: string): Promise<string[]>;
|
getAllByDeviceId(userId: string, deviceId: string): Promise<string[]>;
|
||||||
getLivePhotoCount(motionId: string): Promise<number>;
|
getLivePhotoCount(motionId: string): Promise<number>;
|
||||||
getRandom(userId: string, count: number): Promise<AssetEntity[]>;
|
|
||||||
updateAll(ids: string[], options: Partial<AssetUpdateAllOptions>): Promise<void>;
|
updateAll(ids: string[], options: Partial<AssetUpdateAllOptions>): Promise<void>;
|
||||||
updateDuplicates(options: AssetUpdateDuplicateOptions): Promise<void>;
|
updateDuplicates(options: AssetUpdateDuplicateOptions): Promise<void>;
|
||||||
update(asset: AssetUpdateOptions): Promise<void>;
|
update(asset: AssetUpdateOptions): Promise<void>;
|
||||||
remove(asset: AssetEntity): Promise<void>;
|
remove(asset: AssetEntity): Promise<void>;
|
||||||
findLivePhotoMatch(options: LivePhotoSearchOptions): Promise<AssetEntity | null>;
|
findLivePhotoMatch(options: LivePhotoSearchOptions): Promise<AssetEntity | null>;
|
||||||
getStatistics(ownerId: string, options: AssetStatsOptions): Promise<AssetStats>;
|
getStatistics(ownerId: string, options: AssetStatsOptions): Promise<AssetStats>;
|
||||||
getTimeBucket(timeBucket: string, options: TimeBucketOptions): Promise<AssetEntity[]>;
|
|
||||||
getTimeBuckets(options: TimeBucketOptions): Promise<TimeBucketItem[]>;
|
|
||||||
getUniqueOriginalPaths(userId: string): Promise<string[]>;
|
|
||||||
getUploadAssetIdByChecksum(ownerId: string, checksum: Buffer): Promise<string | undefined>;
|
|
||||||
getWithout(pagination: PaginationOptions, property: WithoutProperty): Paginated<AssetEntity>;
|
|
||||||
getWith(pagination: PaginationOptions, property: WithProperty, libraryId?: string): Paginated<AssetEntity>;
|
|
||||||
remove(asset: AssetEntity): Promise<void>;
|
|
||||||
restoreAll(ids: string[]): Promise<void>;
|
|
||||||
softDeleteAll(ids: string[]): Promise<void>;
|
|
||||||
update(asset: AssetUpdateOptions): Promise<void>;
|
|
||||||
updateAll(ids: string[], options: Partial<AssetUpdateAllOptions>): Promise<void>;
|
|
||||||
updateDuplicates(options: AssetUpdateDuplicateOptions): Promise<void>;
|
|
||||||
update(asset: AssetUpdateOptions): Promise<void>;
|
|
||||||
remove(asset: AssetEntity): Promise<void>;
|
|
||||||
softDeleteAll(ids: string[]): Promise<void>;
|
|
||||||
restoreAll(ids: string[]): Promise<void>;
|
|
||||||
findLivePhotoMatch(options: LivePhotoSearchOptions): Promise<AssetEntity | null>;
|
|
||||||
getStatistics(ownerId: string, options: AssetStatsOptions): Promise<AssetStats>;
|
|
||||||
getTimeBuckets(options: TimeBucketOptions): Promise<TimeBucketItem[]>;
|
getTimeBuckets(options: TimeBucketOptions): Promise<TimeBucketItem[]>;
|
||||||
getTimeBucket(timeBucket: string, options: TimeBucketOptions): Promise<AssetEntity[]>;
|
getTimeBucket(timeBucket: string, options: TimeBucketOptions): Promise<AssetEntity[]>;
|
||||||
upsertExif(exif: Partial<ExifEntity>): Promise<void>;
|
upsertExif(exif: Partial<ExifEntity>): Promise<void>;
|
||||||
@ -233,6 +196,4 @@ export interface IAssetRepository {
|
|||||||
getAllForUserFullSync(options: AssetFullSyncOptions): Promise<AssetEntity[]>;
|
getAllForUserFullSync(options: AssetFullSyncOptions): Promise<AssetEntity[]>;
|
||||||
getChangedDeltaSync(options: AssetDeltaSyncOptions): Promise<AssetEntity[]>;
|
getChangedDeltaSync(options: AssetDeltaSyncOptions): Promise<AssetEntity[]>;
|
||||||
upsertFile(options: { assetId: string; type: AssetFileType; path: string }): Promise<void>;
|
upsertFile(options: { assetId: string; type: AssetFileType; path: string }): Promise<void>;
|
||||||
restoreAllDeleted(userId: string): Promise<void>;
|
|
||||||
restoreAllDeletedById(ids: string[]): Promise<void>;
|
|
||||||
}
|
}
|
||||||
|
@ -76,12 +76,12 @@ export enum JobName {
|
|||||||
FACIAL_RECOGNITION = 'facial-recognition',
|
FACIAL_RECOGNITION = 'facial-recognition',
|
||||||
|
|
||||||
// library management
|
// library management
|
||||||
LIBRARY_QUEUE_SCAN = 'library-scan-new',
|
LIBRARY_QUEUE_SYNC_FILES = 'library-scan-new',
|
||||||
LIBRARY_QUEUE_OFFLINE_CHECK = 'library-queue-remove-deleted',
|
LIBRARY_QUEUE_SYNC_ASSETS = 'library-queue-remove-deleted',
|
||||||
LIBRARY_REFRESH_ASSET = 'library-refresh-asset',
|
LIBRARY_SYNC_FILE = 'library-refresh-asset',
|
||||||
LIBRARY_OFFLINE_CHECK = 'library-remove-deleted',
|
LIBRARY_SYNC_ASSET = 'library-remove-deleted',
|
||||||
LIBRARY_DELETE = 'library-delete',
|
LIBRARY_DELETE = 'library-delete',
|
||||||
LIBRARY_QUEUE_SCAN_ALL = 'library-queue-all-refresh',
|
LIBRARY_QUEUE_SYNC_ALL = 'library-queue-all-refresh',
|
||||||
LIBRARY_QUEUE_CLEANUP = 'library-queue-cleanup',
|
LIBRARY_QUEUE_CLEANUP = 'library-queue-cleanup',
|
||||||
|
|
||||||
// cleanup
|
// cleanup
|
||||||
@ -272,12 +272,12 @@ export type JobItem =
|
|||||||
| { name: JobName.ASSET_DELETION_CHECK; data?: IBaseJob }
|
| { name: JobName.ASSET_DELETION_CHECK; data?: IBaseJob }
|
||||||
|
|
||||||
// Library Management
|
// Library Management
|
||||||
| { name: JobName.LIBRARY_REFRESH_ASSET; data: ILibraryFileJob }
|
| { name: JobName.LIBRARY_SYNC_FILE; data: ILibraryFileJob }
|
||||||
| { name: JobName.LIBRARY_QUEUE_SCAN; data: IEntityJob }
|
| { name: JobName.LIBRARY_QUEUE_SYNC_FILES; data: IEntityJob }
|
||||||
| { name: JobName.LIBRARY_QUEUE_OFFLINE_CHECK; data: IEntityJob }
|
| { name: JobName.LIBRARY_QUEUE_SYNC_ASSETS; data: IEntityJob }
|
||||||
| { name: JobName.LIBRARY_OFFLINE_CHECK; data: IEntityJob }
|
| { name: JobName.LIBRARY_SYNC_ASSET; data: IEntityJob }
|
||||||
| { name: JobName.LIBRARY_DELETE; data: IEntityJob }
|
| { name: JobName.LIBRARY_DELETE; data: IEntityJob }
|
||||||
| { name: JobName.LIBRARY_QUEUE_SCAN_ALL; data?: IBaseJob }
|
| { name: JobName.LIBRARY_QUEUE_SYNC_ALL; data?: IBaseJob }
|
||||||
| { name: JobName.LIBRARY_QUEUE_CLEANUP; data: IBaseJob }
|
| { name: JobName.LIBRARY_QUEUE_CLEANUP; data: IBaseJob }
|
||||||
|
|
||||||
// Notification
|
// Notification
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { AssetTrashReason } from 'src/dtos/asset.dto';
|
|
||||||
import { AssetFaceEntity } from 'src/entities/asset-face.entity';
|
import { AssetFaceEntity } from 'src/entities/asset-face.entity';
|
||||||
import { AssetEntity } from 'src/entities/asset.entity';
|
import { AssetEntity } from 'src/entities/asset.entity';
|
||||||
import { GeodataPlacesEntity } from 'src/entities/geodata-places.entity';
|
import { GeodataPlacesEntity } from 'src/entities/geodata-places.entity';
|
||||||
@ -58,13 +57,13 @@ export interface SearchStatusOptions {
|
|||||||
isEncoded?: boolean;
|
isEncoded?: boolean;
|
||||||
isFavorite?: boolean;
|
isFavorite?: boolean;
|
||||||
isMotion?: boolean;
|
isMotion?: boolean;
|
||||||
|
isOffline?: boolean;
|
||||||
isVisible?: boolean;
|
isVisible?: boolean;
|
||||||
isNotInAlbum?: boolean;
|
isNotInAlbum?: boolean;
|
||||||
type?: AssetType;
|
type?: AssetType;
|
||||||
status?: AssetStatus;
|
status?: AssetStatus;
|
||||||
withArchived?: boolean;
|
withArchived?: boolean;
|
||||||
withDeleted?: boolean;
|
withDeleted?: boolean;
|
||||||
trashReason?: AssetTrashReason;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchOneToOneRelationOptions {
|
export interface SearchOneToOneRelationOptions {
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
||||||
|
|
||||||
export class RemoveOfflineField1725282595231 implements MigrationInterface {
|
|
||||||
name = 'RemoveOfflineField1725282595231'
|
|
||||||
|
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
await queryRunner.query(`ALTER TABLE "assets" DROP COLUMN "isOffline"`);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
await queryRunner.query(`ALTER TABLE "assets" ADD "isOffline" boolean NOT NULL DEFAULT false`);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
|
||||||
|
|
||||||
export class AddTrashReason1725444664102 implements MigrationInterface {
|
|
||||||
name = 'AddTrashReason1725444664102';
|
|
||||||
|
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
await queryRunner.query(`CREATE TYPE "public"."assets_trashreason_enum" AS ENUM('deleted', 'offline')`);
|
|
||||||
await queryRunner.query(`ALTER TABLE "assets" ADD "trashReason" "public"."assets_trashreason_enum"`);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
await queryRunner.query(`ALTER TABLE "assets" DROP COLUMN "trashReason"`);
|
|
||||||
await queryRunner.query(`DROP TYPE "public"."assets_trashreason_enum"`);
|
|
||||||
}
|
|
||||||
}
|
|
@ -21,7 +21,6 @@ SELECT
|
|||||||
"entity"."isFavorite" AS "entity_isFavorite",
|
"entity"."isFavorite" AS "entity_isFavorite",
|
||||||
"entity"."isArchived" AS "entity_isArchived",
|
"entity"."isArchived" AS "entity_isArchived",
|
||||||
"entity"."isExternal" AS "entity_isExternal",
|
"entity"."isExternal" AS "entity_isExternal",
|
||||||
"entity"."isOffline" AS "entity_isOffline",
|
|
||||||
"entity"."checksum" AS "entity_checksum",
|
"entity"."checksum" AS "entity_checksum",
|
||||||
"entity"."duration" AS "entity_duration",
|
"entity"."duration" AS "entity_duration",
|
||||||
"entity"."isVisible" AS "entity_isVisible",
|
"entity"."isVisible" AS "entity_isVisible",
|
||||||
@ -29,6 +28,7 @@ SELECT
|
|||||||
"entity"."originalFileName" AS "entity_originalFileName",
|
"entity"."originalFileName" AS "entity_originalFileName",
|
||||||
"entity"."sidecarPath" AS "entity_sidecarPath",
|
"entity"."sidecarPath" AS "entity_sidecarPath",
|
||||||
"entity"."stackId" AS "entity_stackId",
|
"entity"."stackId" AS "entity_stackId",
|
||||||
|
"entity"."isOffline" AS "entity_isOffline",
|
||||||
"entity"."duplicateId" AS "entity_duplicateId",
|
"entity"."duplicateId" AS "entity_duplicateId",
|
||||||
"exifInfo"."assetId" AS "exifInfo_assetId",
|
"exifInfo"."assetId" AS "exifInfo_assetId",
|
||||||
"exifInfo"."description" AS "exifInfo_description",
|
"exifInfo"."description" AS "exifInfo_description",
|
||||||
@ -110,7 +110,6 @@ SELECT
|
|||||||
"AssetEntity"."isFavorite" AS "AssetEntity_isFavorite",
|
"AssetEntity"."isFavorite" AS "AssetEntity_isFavorite",
|
||||||
"AssetEntity"."isArchived" AS "AssetEntity_isArchived",
|
"AssetEntity"."isArchived" AS "AssetEntity_isArchived",
|
||||||
"AssetEntity"."isExternal" AS "AssetEntity_isExternal",
|
"AssetEntity"."isExternal" AS "AssetEntity_isExternal",
|
||||||
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
|
|
||||||
"AssetEntity"."checksum" AS "AssetEntity_checksum",
|
"AssetEntity"."checksum" AS "AssetEntity_checksum",
|
||||||
"AssetEntity"."duration" AS "AssetEntity_duration",
|
"AssetEntity"."duration" AS "AssetEntity_duration",
|
||||||
"AssetEntity"."isVisible" AS "AssetEntity_isVisible",
|
"AssetEntity"."isVisible" AS "AssetEntity_isVisible",
|
||||||
@ -118,6 +117,7 @@ SELECT
|
|||||||
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
||||||
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
||||||
"AssetEntity"."stackId" AS "AssetEntity_stackId",
|
"AssetEntity"."stackId" AS "AssetEntity_stackId",
|
||||||
|
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
|
||||||
"AssetEntity"."duplicateId" AS "AssetEntity_duplicateId"
|
"AssetEntity"."duplicateId" AS "AssetEntity_duplicateId"
|
||||||
FROM
|
FROM
|
||||||
"assets" "AssetEntity"
|
"assets" "AssetEntity"
|
||||||
@ -145,7 +145,6 @@ SELECT
|
|||||||
"AssetEntity"."isFavorite" AS "AssetEntity_isFavorite",
|
"AssetEntity"."isFavorite" AS "AssetEntity_isFavorite",
|
||||||
"AssetEntity"."isArchived" AS "AssetEntity_isArchived",
|
"AssetEntity"."isArchived" AS "AssetEntity_isArchived",
|
||||||
"AssetEntity"."isExternal" AS "AssetEntity_isExternal",
|
"AssetEntity"."isExternal" AS "AssetEntity_isExternal",
|
||||||
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
|
|
||||||
"AssetEntity"."checksum" AS "AssetEntity_checksum",
|
"AssetEntity"."checksum" AS "AssetEntity_checksum",
|
||||||
"AssetEntity"."duration" AS "AssetEntity_duration",
|
"AssetEntity"."duration" AS "AssetEntity_duration",
|
||||||
"AssetEntity"."isVisible" AS "AssetEntity_isVisible",
|
"AssetEntity"."isVisible" AS "AssetEntity_isVisible",
|
||||||
@ -153,6 +152,7 @@ SELECT
|
|||||||
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
||||||
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
||||||
"AssetEntity"."stackId" AS "AssetEntity_stackId",
|
"AssetEntity"."stackId" AS "AssetEntity_stackId",
|
||||||
|
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
|
||||||
"AssetEntity"."duplicateId" AS "AssetEntity_duplicateId",
|
"AssetEntity"."duplicateId" AS "AssetEntity_duplicateId",
|
||||||
"AssetEntity__AssetEntity_exifInfo"."assetId" AS "AssetEntity__AssetEntity_exifInfo_assetId",
|
"AssetEntity__AssetEntity_exifInfo"."assetId" AS "AssetEntity__AssetEntity_exifInfo_assetId",
|
||||||
"AssetEntity__AssetEntity_exifInfo"."description" AS "AssetEntity__AssetEntity_exifInfo_description",
|
"AssetEntity__AssetEntity_exifInfo"."description" AS "AssetEntity__AssetEntity_exifInfo_description",
|
||||||
@ -234,7 +234,6 @@ SELECT
|
|||||||
"bd93d5747511a4dad4923546c51365bf1a803774"."isFavorite" AS "bd93d5747511a4dad4923546c51365bf1a803774_isFavorite",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."isFavorite" AS "bd93d5747511a4dad4923546c51365bf1a803774_isFavorite",
|
||||||
"bd93d5747511a4dad4923546c51365bf1a803774"."isArchived" AS "bd93d5747511a4dad4923546c51365bf1a803774_isArchived",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."isArchived" AS "bd93d5747511a4dad4923546c51365bf1a803774_isArchived",
|
||||||
"bd93d5747511a4dad4923546c51365bf1a803774"."isExternal" AS "bd93d5747511a4dad4923546c51365bf1a803774_isExternal",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."isExternal" AS "bd93d5747511a4dad4923546c51365bf1a803774_isExternal",
|
||||||
"bd93d5747511a4dad4923546c51365bf1a803774"."isOffline" AS "bd93d5747511a4dad4923546c51365bf1a803774_isOffline",
|
|
||||||
"bd93d5747511a4dad4923546c51365bf1a803774"."checksum" AS "bd93d5747511a4dad4923546c51365bf1a803774_checksum",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."checksum" AS "bd93d5747511a4dad4923546c51365bf1a803774_checksum",
|
||||||
"bd93d5747511a4dad4923546c51365bf1a803774"."duration" AS "bd93d5747511a4dad4923546c51365bf1a803774_duration",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."duration" AS "bd93d5747511a4dad4923546c51365bf1a803774_duration",
|
||||||
"bd93d5747511a4dad4923546c51365bf1a803774"."isVisible" AS "bd93d5747511a4dad4923546c51365bf1a803774_isVisible",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."isVisible" AS "bd93d5747511a4dad4923546c51365bf1a803774_isVisible",
|
||||||
@ -242,6 +241,7 @@ SELECT
|
|||||||
"bd93d5747511a4dad4923546c51365bf1a803774"."originalFileName" AS "bd93d5747511a4dad4923546c51365bf1a803774_originalFileName",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."originalFileName" AS "bd93d5747511a4dad4923546c51365bf1a803774_originalFileName",
|
||||||
"bd93d5747511a4dad4923546c51365bf1a803774"."sidecarPath" AS "bd93d5747511a4dad4923546c51365bf1a803774_sidecarPath",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."sidecarPath" AS "bd93d5747511a4dad4923546c51365bf1a803774_sidecarPath",
|
||||||
"bd93d5747511a4dad4923546c51365bf1a803774"."stackId" AS "bd93d5747511a4dad4923546c51365bf1a803774_stackId",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."stackId" AS "bd93d5747511a4dad4923546c51365bf1a803774_stackId",
|
||||||
|
"bd93d5747511a4dad4923546c51365bf1a803774"."isOffline" AS "bd93d5747511a4dad4923546c51365bf1a803774_isOffline",
|
||||||
"bd93d5747511a4dad4923546c51365bf1a803774"."duplicateId" AS "bd93d5747511a4dad4923546c51365bf1a803774_duplicateId",
|
"bd93d5747511a4dad4923546c51365bf1a803774"."duplicateId" AS "bd93d5747511a4dad4923546c51365bf1a803774_duplicateId",
|
||||||
"AssetEntity__AssetEntity_files"."id" AS "AssetEntity__AssetEntity_files_id",
|
"AssetEntity__AssetEntity_files"."id" AS "AssetEntity__AssetEntity_files_id",
|
||||||
"AssetEntity__AssetEntity_files"."assetId" AS "AssetEntity__AssetEntity_files_assetId",
|
"AssetEntity__AssetEntity_files"."assetId" AS "AssetEntity__AssetEntity_files_assetId",
|
||||||
@ -275,8 +275,7 @@ FROM
|
|||||||
(
|
(
|
||||||
SELECT
|
SELECT
|
||||||
"AssetEntity"."id" AS "AssetEntity_id",
|
"AssetEntity"."id" AS "AssetEntity_id",
|
||||||
"AssetEntity"."originalPath" AS "AssetEntity_originalPath",
|
"AssetEntity"."originalPath" AS "AssetEntity_originalPath"
|
||||||
"AssetEntity"."isOffline" AS "AssetEntity_isOffline"
|
|
||||||
FROM
|
FROM
|
||||||
"assets" "AssetEntity"
|
"assets" "AssetEntity"
|
||||||
LEFT JOIN "libraries" "AssetEntity__AssetEntity_library" ON "AssetEntity__AssetEntity_library"."id" = "AssetEntity"."libraryId"
|
LEFT JOIN "libraries" "AssetEntity__AssetEntity_library" ON "AssetEntity__AssetEntity_library"."id" = "AssetEntity"."libraryId"
|
||||||
@ -322,7 +321,6 @@ FROM
|
|||||||
"AssetEntity"."isFavorite" AS "AssetEntity_isFavorite",
|
"AssetEntity"."isFavorite" AS "AssetEntity_isFavorite",
|
||||||
"AssetEntity"."isArchived" AS "AssetEntity_isArchived",
|
"AssetEntity"."isArchived" AS "AssetEntity_isArchived",
|
||||||
"AssetEntity"."isExternal" AS "AssetEntity_isExternal",
|
"AssetEntity"."isExternal" AS "AssetEntity_isExternal",
|
||||||
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
|
|
||||||
"AssetEntity"."checksum" AS "AssetEntity_checksum",
|
"AssetEntity"."checksum" AS "AssetEntity_checksum",
|
||||||
"AssetEntity"."duration" AS "AssetEntity_duration",
|
"AssetEntity"."duration" AS "AssetEntity_duration",
|
||||||
"AssetEntity"."isVisible" AS "AssetEntity_isVisible",
|
"AssetEntity"."isVisible" AS "AssetEntity_isVisible",
|
||||||
@ -330,6 +328,7 @@ FROM
|
|||||||
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
||||||
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
||||||
"AssetEntity"."stackId" AS "AssetEntity_stackId",
|
"AssetEntity"."stackId" AS "AssetEntity_stackId",
|
||||||
|
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
|
||||||
"AssetEntity"."duplicateId" AS "AssetEntity_duplicateId"
|
"AssetEntity"."duplicateId" AS "AssetEntity_duplicateId"
|
||||||
FROM
|
FROM
|
||||||
"assets" "AssetEntity"
|
"assets" "AssetEntity"
|
||||||
@ -366,18 +365,6 @@ WHERE
|
|||||||
AND "originalPath" = path
|
AND "originalPath" = path
|
||||||
);
|
);
|
||||||
|
|
||||||
-- AssetRepository.updateOfflineLibraryAssets
|
|
||||||
UPDATE "assets"
|
|
||||||
SET
|
|
||||||
"isOffline" = $1,
|
|
||||||
"updatedAt" = CURRENT_TIMESTAMP
|
|
||||||
WHERE
|
|
||||||
(
|
|
||||||
"libraryId" = $2
|
|
||||||
AND NOT ("originalPath" IN ($3))
|
|
||||||
AND "isOffline" = $4
|
|
||||||
)
|
|
||||||
|
|
||||||
-- AssetRepository.getAllByDeviceId
|
-- AssetRepository.getAllByDeviceId
|
||||||
SELECT
|
SELECT
|
||||||
"AssetEntity"."deviceAssetId" AS "AssetEntity_deviceAssetId",
|
"AssetEntity"."deviceAssetId" AS "AssetEntity_deviceAssetId",
|
||||||
@ -420,7 +407,6 @@ SELECT
|
|||||||
"AssetEntity"."isFavorite" AS "AssetEntity_isFavorite",
|
"AssetEntity"."isFavorite" AS "AssetEntity_isFavorite",
|
||||||
"AssetEntity"."isArchived" AS "AssetEntity_isArchived",
|
"AssetEntity"."isArchived" AS "AssetEntity_isArchived",
|
||||||
"AssetEntity"."isExternal" AS "AssetEntity_isExternal",
|
"AssetEntity"."isExternal" AS "AssetEntity_isExternal",
|
||||||
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
|
|
||||||
"AssetEntity"."checksum" AS "AssetEntity_checksum",
|
"AssetEntity"."checksum" AS "AssetEntity_checksum",
|
||||||
"AssetEntity"."duration" AS "AssetEntity_duration",
|
"AssetEntity"."duration" AS "AssetEntity_duration",
|
||||||
"AssetEntity"."isVisible" AS "AssetEntity_isVisible",
|
"AssetEntity"."isVisible" AS "AssetEntity_isVisible",
|
||||||
@ -428,6 +414,7 @@ SELECT
|
|||||||
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
||||||
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
||||||
"AssetEntity"."stackId" AS "AssetEntity_stackId",
|
"AssetEntity"."stackId" AS "AssetEntity_stackId",
|
||||||
|
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
|
||||||
"AssetEntity"."duplicateId" AS "AssetEntity_duplicateId"
|
"AssetEntity"."duplicateId" AS "AssetEntity_duplicateId"
|
||||||
FROM
|
FROM
|
||||||
"assets" "AssetEntity"
|
"assets" "AssetEntity"
|
||||||
@ -474,7 +461,6 @@ SELECT
|
|||||||
"AssetEntity"."isFavorite" AS "AssetEntity_isFavorite",
|
"AssetEntity"."isFavorite" AS "AssetEntity_isFavorite",
|
||||||
"AssetEntity"."isArchived" AS "AssetEntity_isArchived",
|
"AssetEntity"."isArchived" AS "AssetEntity_isArchived",
|
||||||
"AssetEntity"."isExternal" AS "AssetEntity_isExternal",
|
"AssetEntity"."isExternal" AS "AssetEntity_isExternal",
|
||||||
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
|
|
||||||
"AssetEntity"."checksum" AS "AssetEntity_checksum",
|
"AssetEntity"."checksum" AS "AssetEntity_checksum",
|
||||||
"AssetEntity"."duration" AS "AssetEntity_duration",
|
"AssetEntity"."duration" AS "AssetEntity_duration",
|
||||||
"AssetEntity"."isVisible" AS "AssetEntity_isVisible",
|
"AssetEntity"."isVisible" AS "AssetEntity_isVisible",
|
||||||
@ -482,6 +468,7 @@ SELECT
|
|||||||
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
||||||
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
||||||
"AssetEntity"."stackId" AS "AssetEntity_stackId",
|
"AssetEntity"."stackId" AS "AssetEntity_stackId",
|
||||||
|
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
|
||||||
"AssetEntity"."duplicateId" AS "AssetEntity_duplicateId"
|
"AssetEntity"."duplicateId" AS "AssetEntity_duplicateId"
|
||||||
FROM
|
FROM
|
||||||
"assets" "AssetEntity"
|
"assets" "AssetEntity"
|
||||||
@ -547,7 +534,6 @@ SELECT
|
|||||||
"AssetEntity"."isFavorite" AS "AssetEntity_isFavorite",
|
"AssetEntity"."isFavorite" AS "AssetEntity_isFavorite",
|
||||||
"AssetEntity"."isArchived" AS "AssetEntity_isArchived",
|
"AssetEntity"."isArchived" AS "AssetEntity_isArchived",
|
||||||
"AssetEntity"."isExternal" AS "AssetEntity_isExternal",
|
"AssetEntity"."isExternal" AS "AssetEntity_isExternal",
|
||||||
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
|
|
||||||
"AssetEntity"."checksum" AS "AssetEntity_checksum",
|
"AssetEntity"."checksum" AS "AssetEntity_checksum",
|
||||||
"AssetEntity"."duration" AS "AssetEntity_duration",
|
"AssetEntity"."duration" AS "AssetEntity_duration",
|
||||||
"AssetEntity"."isVisible" AS "AssetEntity_isVisible",
|
"AssetEntity"."isVisible" AS "AssetEntity_isVisible",
|
||||||
@ -555,6 +541,7 @@ SELECT
|
|||||||
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
||||||
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
||||||
"AssetEntity"."stackId" AS "AssetEntity_stackId",
|
"AssetEntity"."stackId" AS "AssetEntity_stackId",
|
||||||
|
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
|
||||||
"AssetEntity"."duplicateId" AS "AssetEntity_duplicateId"
|
"AssetEntity"."duplicateId" AS "AssetEntity_duplicateId"
|
||||||
FROM
|
FROM
|
||||||
"assets" "AssetEntity"
|
"assets" "AssetEntity"
|
||||||
@ -602,7 +589,6 @@ SELECT
|
|||||||
"asset"."isFavorite" AS "asset_isFavorite",
|
"asset"."isFavorite" AS "asset_isFavorite",
|
||||||
"asset"."isArchived" AS "asset_isArchived",
|
"asset"."isArchived" AS "asset_isArchived",
|
||||||
"asset"."isExternal" AS "asset_isExternal",
|
"asset"."isExternal" AS "asset_isExternal",
|
||||||
"asset"."isOffline" AS "asset_isOffline",
|
|
||||||
"asset"."checksum" AS "asset_checksum",
|
"asset"."checksum" AS "asset_checksum",
|
||||||
"asset"."duration" AS "asset_duration",
|
"asset"."duration" AS "asset_duration",
|
||||||
"asset"."isVisible" AS "asset_isVisible",
|
"asset"."isVisible" AS "asset_isVisible",
|
||||||
@ -610,6 +596,7 @@ SELECT
|
|||||||
"asset"."originalFileName" AS "asset_originalFileName",
|
"asset"."originalFileName" AS "asset_originalFileName",
|
||||||
"asset"."sidecarPath" AS "asset_sidecarPath",
|
"asset"."sidecarPath" AS "asset_sidecarPath",
|
||||||
"asset"."stackId" AS "asset_stackId",
|
"asset"."stackId" AS "asset_stackId",
|
||||||
|
"asset"."isOffline" AS "asset_isOffline",
|
||||||
"asset"."duplicateId" AS "asset_duplicateId",
|
"asset"."duplicateId" AS "asset_duplicateId",
|
||||||
"exifInfo"."assetId" AS "exifInfo_assetId",
|
"exifInfo"."assetId" AS "exifInfo_assetId",
|
||||||
"exifInfo"."description" AS "exifInfo_description",
|
"exifInfo"."description" AS "exifInfo_description",
|
||||||
@ -662,7 +649,6 @@ SELECT
|
|||||||
"stackedAssets"."isFavorite" AS "stackedAssets_isFavorite",
|
"stackedAssets"."isFavorite" AS "stackedAssets_isFavorite",
|
||||||
"stackedAssets"."isArchived" AS "stackedAssets_isArchived",
|
"stackedAssets"."isArchived" AS "stackedAssets_isArchived",
|
||||||
"stackedAssets"."isExternal" AS "stackedAssets_isExternal",
|
"stackedAssets"."isExternal" AS "stackedAssets_isExternal",
|
||||||
"stackedAssets"."isOffline" AS "stackedAssets_isOffline",
|
|
||||||
"stackedAssets"."checksum" AS "stackedAssets_checksum",
|
"stackedAssets"."checksum" AS "stackedAssets_checksum",
|
||||||
"stackedAssets"."duration" AS "stackedAssets_duration",
|
"stackedAssets"."duration" AS "stackedAssets_duration",
|
||||||
"stackedAssets"."isVisible" AS "stackedAssets_isVisible",
|
"stackedAssets"."isVisible" AS "stackedAssets_isVisible",
|
||||||
@ -670,6 +656,7 @@ SELECT
|
|||||||
"stackedAssets"."originalFileName" AS "stackedAssets_originalFileName",
|
"stackedAssets"."originalFileName" AS "stackedAssets_originalFileName",
|
||||||
"stackedAssets"."sidecarPath" AS "stackedAssets_sidecarPath",
|
"stackedAssets"."sidecarPath" AS "stackedAssets_sidecarPath",
|
||||||
"stackedAssets"."stackId" AS "stackedAssets_stackId",
|
"stackedAssets"."stackId" AS "stackedAssets_stackId",
|
||||||
|
"stackedAssets"."isOffline" AS "stackedAssets_isOffline",
|
||||||
"stackedAssets"."duplicateId" AS "stackedAssets_duplicateId"
|
"stackedAssets"."duplicateId" AS "stackedAssets_duplicateId"
|
||||||
FROM
|
FROM
|
||||||
"assets" "asset"
|
"assets" "asset"
|
||||||
@ -742,7 +729,6 @@ SELECT
|
|||||||
"asset"."isFavorite" AS "asset_isFavorite",
|
"asset"."isFavorite" AS "asset_isFavorite",
|
||||||
"asset"."isArchived" AS "asset_isArchived",
|
"asset"."isArchived" AS "asset_isArchived",
|
||||||
"asset"."isExternal" AS "asset_isExternal",
|
"asset"."isExternal" AS "asset_isExternal",
|
||||||
"asset"."isOffline" AS "asset_isOffline",
|
|
||||||
"asset"."checksum" AS "asset_checksum",
|
"asset"."checksum" AS "asset_checksum",
|
||||||
"asset"."duration" AS "asset_duration",
|
"asset"."duration" AS "asset_duration",
|
||||||
"asset"."isVisible" AS "asset_isVisible",
|
"asset"."isVisible" AS "asset_isVisible",
|
||||||
@ -750,6 +736,7 @@ SELECT
|
|||||||
"asset"."originalFileName" AS "asset_originalFileName",
|
"asset"."originalFileName" AS "asset_originalFileName",
|
||||||
"asset"."sidecarPath" AS "asset_sidecarPath",
|
"asset"."sidecarPath" AS "asset_sidecarPath",
|
||||||
"asset"."stackId" AS "asset_stackId",
|
"asset"."stackId" AS "asset_stackId",
|
||||||
|
"asset"."isOffline" AS "asset_isOffline",
|
||||||
"asset"."duplicateId" AS "asset_duplicateId",
|
"asset"."duplicateId" AS "asset_duplicateId",
|
||||||
"exifInfo"."assetId" AS "exifInfo_assetId",
|
"exifInfo"."assetId" AS "exifInfo_assetId",
|
||||||
"exifInfo"."description" AS "exifInfo_description",
|
"exifInfo"."description" AS "exifInfo_description",
|
||||||
@ -802,7 +789,6 @@ SELECT
|
|||||||
"stackedAssets"."isFavorite" AS "stackedAssets_isFavorite",
|
"stackedAssets"."isFavorite" AS "stackedAssets_isFavorite",
|
||||||
"stackedAssets"."isArchived" AS "stackedAssets_isArchived",
|
"stackedAssets"."isArchived" AS "stackedAssets_isArchived",
|
||||||
"stackedAssets"."isExternal" AS "stackedAssets_isExternal",
|
"stackedAssets"."isExternal" AS "stackedAssets_isExternal",
|
||||||
"stackedAssets"."isOffline" AS "stackedAssets_isOffline",
|
|
||||||
"stackedAssets"."checksum" AS "stackedAssets_checksum",
|
"stackedAssets"."checksum" AS "stackedAssets_checksum",
|
||||||
"stackedAssets"."duration" AS "stackedAssets_duration",
|
"stackedAssets"."duration" AS "stackedAssets_duration",
|
||||||
"stackedAssets"."isVisible" AS "stackedAssets_isVisible",
|
"stackedAssets"."isVisible" AS "stackedAssets_isVisible",
|
||||||
@ -810,6 +796,7 @@ SELECT
|
|||||||
"stackedAssets"."originalFileName" AS "stackedAssets_originalFileName",
|
"stackedAssets"."originalFileName" AS "stackedAssets_originalFileName",
|
||||||
"stackedAssets"."sidecarPath" AS "stackedAssets_sidecarPath",
|
"stackedAssets"."sidecarPath" AS "stackedAssets_sidecarPath",
|
||||||
"stackedAssets"."stackId" AS "stackedAssets_stackId",
|
"stackedAssets"."stackId" AS "stackedAssets_stackId",
|
||||||
|
"stackedAssets"."isOffline" AS "stackedAssets_isOffline",
|
||||||
"stackedAssets"."duplicateId" AS "stackedAssets_duplicateId"
|
"stackedAssets"."duplicateId" AS "stackedAssets_duplicateId"
|
||||||
FROM
|
FROM
|
||||||
"assets" "asset"
|
"assets" "asset"
|
||||||
@ -858,7 +845,6 @@ SELECT
|
|||||||
"asset"."isFavorite" AS "asset_isFavorite",
|
"asset"."isFavorite" AS "asset_isFavorite",
|
||||||
"asset"."isArchived" AS "asset_isArchived",
|
"asset"."isArchived" AS "asset_isArchived",
|
||||||
"asset"."isExternal" AS "asset_isExternal",
|
"asset"."isExternal" AS "asset_isExternal",
|
||||||
"asset"."isOffline" AS "asset_isOffline",
|
|
||||||
"asset"."checksum" AS "asset_checksum",
|
"asset"."checksum" AS "asset_checksum",
|
||||||
"asset"."duration" AS "asset_duration",
|
"asset"."duration" AS "asset_duration",
|
||||||
"asset"."isVisible" AS "asset_isVisible",
|
"asset"."isVisible" AS "asset_isVisible",
|
||||||
@ -866,6 +852,7 @@ SELECT
|
|||||||
"asset"."originalFileName" AS "asset_originalFileName",
|
"asset"."originalFileName" AS "asset_originalFileName",
|
||||||
"asset"."sidecarPath" AS "asset_sidecarPath",
|
"asset"."sidecarPath" AS "asset_sidecarPath",
|
||||||
"asset"."stackId" AS "asset_stackId",
|
"asset"."stackId" AS "asset_stackId",
|
||||||
|
"asset"."isOffline" AS "asset_isOffline",
|
||||||
"asset"."duplicateId" AS "asset_duplicateId",
|
"asset"."duplicateId" AS "asset_duplicateId",
|
||||||
"exifInfo"."assetId" AS "exifInfo_assetId",
|
"exifInfo"."assetId" AS "exifInfo_assetId",
|
||||||
"exifInfo"."description" AS "exifInfo_description",
|
"exifInfo"."description" AS "exifInfo_description",
|
||||||
@ -918,7 +905,6 @@ SELECT
|
|||||||
"stackedAssets"."isFavorite" AS "stackedAssets_isFavorite",
|
"stackedAssets"."isFavorite" AS "stackedAssets_isFavorite",
|
||||||
"stackedAssets"."isArchived" AS "stackedAssets_isArchived",
|
"stackedAssets"."isArchived" AS "stackedAssets_isArchived",
|
||||||
"stackedAssets"."isExternal" AS "stackedAssets_isExternal",
|
"stackedAssets"."isExternal" AS "stackedAssets_isExternal",
|
||||||
"stackedAssets"."isOffline" AS "stackedAssets_isOffline",
|
|
||||||
"stackedAssets"."checksum" AS "stackedAssets_checksum",
|
"stackedAssets"."checksum" AS "stackedAssets_checksum",
|
||||||
"stackedAssets"."duration" AS "stackedAssets_duration",
|
"stackedAssets"."duration" AS "stackedAssets_duration",
|
||||||
"stackedAssets"."isVisible" AS "stackedAssets_isVisible",
|
"stackedAssets"."isVisible" AS "stackedAssets_isVisible",
|
||||||
@ -926,6 +912,7 @@ SELECT
|
|||||||
"stackedAssets"."originalFileName" AS "stackedAssets_originalFileName",
|
"stackedAssets"."originalFileName" AS "stackedAssets_originalFileName",
|
||||||
"stackedAssets"."sidecarPath" AS "stackedAssets_sidecarPath",
|
"stackedAssets"."sidecarPath" AS "stackedAssets_sidecarPath",
|
||||||
"stackedAssets"."stackId" AS "stackedAssets_stackId",
|
"stackedAssets"."stackId" AS "stackedAssets_stackId",
|
||||||
|
"stackedAssets"."isOffline" AS "stackedAssets_isOffline",
|
||||||
"stackedAssets"."duplicateId" AS "stackedAssets_duplicateId"
|
"stackedAssets"."duplicateId" AS "stackedAssets_duplicateId"
|
||||||
FROM
|
FROM
|
||||||
"assets" "asset"
|
"assets" "asset"
|
||||||
@ -1024,7 +1011,6 @@ SELECT
|
|||||||
"asset"."isFavorite" AS "asset_isFavorite",
|
"asset"."isFavorite" AS "asset_isFavorite",
|
||||||
"asset"."isArchived" AS "asset_isArchived",
|
"asset"."isArchived" AS "asset_isArchived",
|
||||||
"asset"."isExternal" AS "asset_isExternal",
|
"asset"."isExternal" AS "asset_isExternal",
|
||||||
"asset"."isOffline" AS "asset_isOffline",
|
|
||||||
"asset"."checksum" AS "asset_checksum",
|
"asset"."checksum" AS "asset_checksum",
|
||||||
"asset"."duration" AS "asset_duration",
|
"asset"."duration" AS "asset_duration",
|
||||||
"asset"."isVisible" AS "asset_isVisible",
|
"asset"."isVisible" AS "asset_isVisible",
|
||||||
@ -1032,6 +1018,7 @@ SELECT
|
|||||||
"asset"."originalFileName" AS "asset_originalFileName",
|
"asset"."originalFileName" AS "asset_originalFileName",
|
||||||
"asset"."sidecarPath" AS "asset_sidecarPath",
|
"asset"."sidecarPath" AS "asset_sidecarPath",
|
||||||
"asset"."stackId" AS "asset_stackId",
|
"asset"."stackId" AS "asset_stackId",
|
||||||
|
"asset"."isOffline" AS "asset_isOffline",
|
||||||
"asset"."duplicateId" AS "asset_duplicateId",
|
"asset"."duplicateId" AS "asset_duplicateId",
|
||||||
"exifInfo"."assetId" AS "exifInfo_assetId",
|
"exifInfo"."assetId" AS "exifInfo_assetId",
|
||||||
"exifInfo"."description" AS "exifInfo_description",
|
"exifInfo"."description" AS "exifInfo_description",
|
||||||
@ -1100,7 +1087,6 @@ SELECT
|
|||||||
"asset"."isFavorite" AS "asset_isFavorite",
|
"asset"."isFavorite" AS "asset_isFavorite",
|
||||||
"asset"."isArchived" AS "asset_isArchived",
|
"asset"."isArchived" AS "asset_isArchived",
|
||||||
"asset"."isExternal" AS "asset_isExternal",
|
"asset"."isExternal" AS "asset_isExternal",
|
||||||
"asset"."isOffline" AS "asset_isOffline",
|
|
||||||
"asset"."checksum" AS "asset_checksum",
|
"asset"."checksum" AS "asset_checksum",
|
||||||
"asset"."duration" AS "asset_duration",
|
"asset"."duration" AS "asset_duration",
|
||||||
"asset"."isVisible" AS "asset_isVisible",
|
"asset"."isVisible" AS "asset_isVisible",
|
||||||
@ -1108,6 +1094,7 @@ SELECT
|
|||||||
"asset"."originalFileName" AS "asset_originalFileName",
|
"asset"."originalFileName" AS "asset_originalFileName",
|
||||||
"asset"."sidecarPath" AS "asset_sidecarPath",
|
"asset"."sidecarPath" AS "asset_sidecarPath",
|
||||||
"asset"."stackId" AS "asset_stackId",
|
"asset"."stackId" AS "asset_stackId",
|
||||||
|
"asset"."isOffline" AS "asset_isOffline",
|
||||||
"asset"."duplicateId" AS "asset_duplicateId",
|
"asset"."duplicateId" AS "asset_duplicateId",
|
||||||
"exifInfo"."assetId" AS "exifInfo_assetId",
|
"exifInfo"."assetId" AS "exifInfo_assetId",
|
||||||
"exifInfo"."description" AS "exifInfo_description",
|
"exifInfo"."description" AS "exifInfo_description",
|
||||||
@ -1173,15 +1160,3 @@ RETURNING
|
|||||||
"id",
|
"id",
|
||||||
"createdAt",
|
"createdAt",
|
||||||
"updatedAt"
|
"updatedAt"
|
||||||
|
|
||||||
-- AssetRepository.restoreAllDeleted
|
|
||||||
UPDATE "assets"
|
|
||||||
SET
|
|
||||||
"deletedAt" = $1,
|
|
||||||
"trashReason" = $2,
|
|
||||||
"updatedAt" = CURRENT_TIMESTAMP
|
|
||||||
WHERE
|
|
||||||
(
|
|
||||||
"ownerId" = $3
|
|
||||||
AND "trashReason" = $4
|
|
||||||
)
|
|
||||||
|
@ -179,6 +179,7 @@ FROM
|
|||||||
"AssetFaceEntity__AssetFaceEntity_asset"."originalFileName" AS "AssetFaceEntity__AssetFaceEntity_asset_originalFileName",
|
"AssetFaceEntity__AssetFaceEntity_asset"."originalFileName" AS "AssetFaceEntity__AssetFaceEntity_asset_originalFileName",
|
||||||
"AssetFaceEntity__AssetFaceEntity_asset"."sidecarPath" AS "AssetFaceEntity__AssetFaceEntity_asset_sidecarPath",
|
"AssetFaceEntity__AssetFaceEntity_asset"."sidecarPath" AS "AssetFaceEntity__AssetFaceEntity_asset_sidecarPath",
|
||||||
"AssetFaceEntity__AssetFaceEntity_asset"."stackId" AS "AssetFaceEntity__AssetFaceEntity_asset_stackId",
|
"AssetFaceEntity__AssetFaceEntity_asset"."stackId" AS "AssetFaceEntity__AssetFaceEntity_asset_stackId",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."isOffline" AS "AssetFaceEntity__AssetFaceEntity_asset_isOffline",
|
||||||
"AssetFaceEntity__AssetFaceEntity_asset"."duplicateId" AS "AssetFaceEntity__AssetFaceEntity_asset_duplicateId"
|
"AssetFaceEntity__AssetFaceEntity_asset"."duplicateId" AS "AssetFaceEntity__AssetFaceEntity_asset_duplicateId"
|
||||||
FROM
|
FROM
|
||||||
"asset_faces" "AssetFaceEntity"
|
"asset_faces" "AssetFaceEntity"
|
||||||
@ -280,6 +281,7 @@ FROM
|
|||||||
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
||||||
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
||||||
"AssetEntity"."stackId" AS "AssetEntity_stackId",
|
"AssetEntity"."stackId" AS "AssetEntity_stackId",
|
||||||
|
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
|
||||||
"AssetEntity"."duplicateId" AS "AssetEntity_duplicateId",
|
"AssetEntity"."duplicateId" AS "AssetEntity_duplicateId",
|
||||||
"AssetEntity__AssetEntity_faces"."id" AS "AssetEntity__AssetEntity_faces_id",
|
"AssetEntity__AssetEntity_faces"."id" AS "AssetEntity__AssetEntity_faces_id",
|
||||||
"AssetEntity__AssetEntity_faces"."assetId" AS "AssetEntity__AssetEntity_faces_assetId",
|
"AssetEntity__AssetEntity_faces"."assetId" AS "AssetEntity__AssetEntity_faces_assetId",
|
||||||
@ -411,6 +413,7 @@ SELECT
|
|||||||
"AssetFaceEntity__AssetFaceEntity_asset"."originalFileName" AS "AssetFaceEntity__AssetFaceEntity_asset_originalFileName",
|
"AssetFaceEntity__AssetFaceEntity_asset"."originalFileName" AS "AssetFaceEntity__AssetFaceEntity_asset_originalFileName",
|
||||||
"AssetFaceEntity__AssetFaceEntity_asset"."sidecarPath" AS "AssetFaceEntity__AssetFaceEntity_asset_sidecarPath",
|
"AssetFaceEntity__AssetFaceEntity_asset"."sidecarPath" AS "AssetFaceEntity__AssetFaceEntity_asset_sidecarPath",
|
||||||
"AssetFaceEntity__AssetFaceEntity_asset"."stackId" AS "AssetFaceEntity__AssetFaceEntity_asset_stackId",
|
"AssetFaceEntity__AssetFaceEntity_asset"."stackId" AS "AssetFaceEntity__AssetFaceEntity_asset_stackId",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."isOffline" AS "AssetFaceEntity__AssetFaceEntity_asset_isOffline",
|
||||||
"AssetFaceEntity__AssetFaceEntity_asset"."duplicateId" AS "AssetFaceEntity__AssetFaceEntity_asset_duplicateId"
|
"AssetFaceEntity__AssetFaceEntity_asset"."duplicateId" AS "AssetFaceEntity__AssetFaceEntity_asset_duplicateId"
|
||||||
FROM
|
FROM
|
||||||
"asset_faces" "AssetFaceEntity"
|
"asset_faces" "AssetFaceEntity"
|
||||||
|
@ -33,6 +33,7 @@ FROM
|
|||||||
"asset"."originalFileName" AS "asset_originalFileName",
|
"asset"."originalFileName" AS "asset_originalFileName",
|
||||||
"asset"."sidecarPath" AS "asset_sidecarPath",
|
"asset"."sidecarPath" AS "asset_sidecarPath",
|
||||||
"asset"."stackId" AS "asset_stackId",
|
"asset"."stackId" AS "asset_stackId",
|
||||||
|
"asset"."isOffline" AS "asset_isOffline",
|
||||||
"asset"."duplicateId" AS "asset_duplicateId",
|
"asset"."duplicateId" AS "asset_duplicateId",
|
||||||
"stack"."id" AS "stack_id",
|
"stack"."id" AS "stack_id",
|
||||||
"stack"."ownerId" AS "stack_ownerId",
|
"stack"."ownerId" AS "stack_ownerId",
|
||||||
@ -63,6 +64,7 @@ FROM
|
|||||||
"stackedAssets"."originalFileName" AS "stackedAssets_originalFileName",
|
"stackedAssets"."originalFileName" AS "stackedAssets_originalFileName",
|
||||||
"stackedAssets"."sidecarPath" AS "stackedAssets_sidecarPath",
|
"stackedAssets"."sidecarPath" AS "stackedAssets_sidecarPath",
|
||||||
"stackedAssets"."stackId" AS "stackedAssets_stackId",
|
"stackedAssets"."stackId" AS "stackedAssets_stackId",
|
||||||
|
"stackedAssets"."isOffline" AS "stackedAssets_isOffline",
|
||||||
"stackedAssets"."duplicateId" AS "stackedAssets_duplicateId"
|
"stackedAssets"."duplicateId" AS "stackedAssets_duplicateId"
|
||||||
FROM
|
FROM
|
||||||
"assets" "asset"
|
"assets" "asset"
|
||||||
@ -126,6 +128,7 @@ SELECT
|
|||||||
"asset"."originalFileName" AS "asset_originalFileName",
|
"asset"."originalFileName" AS "asset_originalFileName",
|
||||||
"asset"."sidecarPath" AS "asset_sidecarPath",
|
"asset"."sidecarPath" AS "asset_sidecarPath",
|
||||||
"asset"."stackId" AS "asset_stackId",
|
"asset"."stackId" AS "asset_stackId",
|
||||||
|
"asset"."isOffline" AS "asset_isOffline",
|
||||||
"asset"."duplicateId" AS "asset_duplicateId",
|
"asset"."duplicateId" AS "asset_duplicateId",
|
||||||
"stack"."id" AS "stack_id",
|
"stack"."id" AS "stack_id",
|
||||||
"stack"."ownerId" AS "stack_ownerId",
|
"stack"."ownerId" AS "stack_ownerId",
|
||||||
@ -156,6 +159,7 @@ SELECT
|
|||||||
"stackedAssets"."originalFileName" AS "stackedAssets_originalFileName",
|
"stackedAssets"."originalFileName" AS "stackedAssets_originalFileName",
|
||||||
"stackedAssets"."sidecarPath" AS "stackedAssets_sidecarPath",
|
"stackedAssets"."sidecarPath" AS "stackedAssets_sidecarPath",
|
||||||
"stackedAssets"."stackId" AS "stackedAssets_stackId",
|
"stackedAssets"."stackId" AS "stackedAssets_stackId",
|
||||||
|
"stackedAssets"."isOffline" AS "stackedAssets_isOffline",
|
||||||
"stackedAssets"."duplicateId" AS "stackedAssets_duplicateId"
|
"stackedAssets"."duplicateId" AS "stackedAssets_duplicateId"
|
||||||
FROM
|
FROM
|
||||||
"assets" "asset"
|
"assets" "asset"
|
||||||
@ -365,6 +369,7 @@ SELECT
|
|||||||
"asset"."originalFileName" AS "asset_originalFileName",
|
"asset"."originalFileName" AS "asset_originalFileName",
|
||||||
"asset"."sidecarPath" AS "asset_sidecarPath",
|
"asset"."sidecarPath" AS "asset_sidecarPath",
|
||||||
"asset"."stackId" AS "asset_stackId",
|
"asset"."stackId" AS "asset_stackId",
|
||||||
|
"asset"."isOffline" AS "asset_isOffline",
|
||||||
"asset"."duplicateId" AS "asset_duplicateId",
|
"asset"."duplicateId" AS "asset_duplicateId",
|
||||||
"exif"."assetId" AS "exif_assetId",
|
"exif"."assetId" AS "exif_assetId",
|
||||||
"exif"."description" AS "exif_description",
|
"exif"."description" AS "exif_description",
|
||||||
|
@ -47,6 +47,7 @@ FROM
|
|||||||
"SharedLinkEntity__SharedLinkEntity_assets"."originalFileName" AS "SharedLinkEntity__SharedLinkEntity_assets_originalFileName",
|
"SharedLinkEntity__SharedLinkEntity_assets"."originalFileName" AS "SharedLinkEntity__SharedLinkEntity_assets_originalFileName",
|
||||||
"SharedLinkEntity__SharedLinkEntity_assets"."sidecarPath" AS "SharedLinkEntity__SharedLinkEntity_assets_sidecarPath",
|
"SharedLinkEntity__SharedLinkEntity_assets"."sidecarPath" AS "SharedLinkEntity__SharedLinkEntity_assets_sidecarPath",
|
||||||
"SharedLinkEntity__SharedLinkEntity_assets"."stackId" AS "SharedLinkEntity__SharedLinkEntity_assets_stackId",
|
"SharedLinkEntity__SharedLinkEntity_assets"."stackId" AS "SharedLinkEntity__SharedLinkEntity_assets_stackId",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."isOffline" AS "SharedLinkEntity__SharedLinkEntity_assets_isOffline",
|
||||||
"SharedLinkEntity__SharedLinkEntity_assets"."duplicateId" AS "SharedLinkEntity__SharedLinkEntity_assets_duplicateId",
|
"SharedLinkEntity__SharedLinkEntity_assets"."duplicateId" AS "SharedLinkEntity__SharedLinkEntity_assets_duplicateId",
|
||||||
"9b1d35b344d838023994a3233afd6ffe098be6d8"."assetId" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_assetId",
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."assetId" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_assetId",
|
||||||
"9b1d35b344d838023994a3233afd6ffe098be6d8"."description" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_description",
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."description" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_description",
|
||||||
@ -113,6 +114,7 @@ FROM
|
|||||||
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."originalFileName" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_originalFileName",
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."originalFileName" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_originalFileName",
|
||||||
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."sidecarPath" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_sidecarPath",
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."sidecarPath" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_sidecarPath",
|
||||||
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."stackId" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_stackId",
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."stackId" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_stackId",
|
||||||
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."isOffline" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_isOffline",
|
||||||
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."duplicateId" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_duplicateId",
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."duplicateId" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_duplicateId",
|
||||||
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."assetId" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_assetId",
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."assetId" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_assetId",
|
||||||
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."description" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_description",
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."description" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_description",
|
||||||
@ -234,6 +236,7 @@ SELECT
|
|||||||
"SharedLinkEntity__SharedLinkEntity_assets"."originalFileName" AS "SharedLinkEntity__SharedLinkEntity_assets_originalFileName",
|
"SharedLinkEntity__SharedLinkEntity_assets"."originalFileName" AS "SharedLinkEntity__SharedLinkEntity_assets_originalFileName",
|
||||||
"SharedLinkEntity__SharedLinkEntity_assets"."sidecarPath" AS "SharedLinkEntity__SharedLinkEntity_assets_sidecarPath",
|
"SharedLinkEntity__SharedLinkEntity_assets"."sidecarPath" AS "SharedLinkEntity__SharedLinkEntity_assets_sidecarPath",
|
||||||
"SharedLinkEntity__SharedLinkEntity_assets"."stackId" AS "SharedLinkEntity__SharedLinkEntity_assets_stackId",
|
"SharedLinkEntity__SharedLinkEntity_assets"."stackId" AS "SharedLinkEntity__SharedLinkEntity_assets_stackId",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."isOffline" AS "SharedLinkEntity__SharedLinkEntity_assets_isOffline",
|
||||||
"SharedLinkEntity__SharedLinkEntity_assets"."duplicateId" AS "SharedLinkEntity__SharedLinkEntity_assets_duplicateId",
|
"SharedLinkEntity__SharedLinkEntity_assets"."duplicateId" AS "SharedLinkEntity__SharedLinkEntity_assets_duplicateId",
|
||||||
"SharedLinkEntity__SharedLinkEntity_album"."id" AS "SharedLinkEntity__SharedLinkEntity_album_id",
|
"SharedLinkEntity__SharedLinkEntity_album"."id" AS "SharedLinkEntity__SharedLinkEntity_album_id",
|
||||||
"SharedLinkEntity__SharedLinkEntity_album"."ownerId" AS "SharedLinkEntity__SharedLinkEntity_album_ownerId",
|
"SharedLinkEntity__SharedLinkEntity_album"."ownerId" AS "SharedLinkEntity__SharedLinkEntity_album_ownerId",
|
||||||
|
@ -21,7 +21,6 @@ SELECT
|
|||||||
"asset"."isFavorite" AS "asset_isFavorite",
|
"asset"."isFavorite" AS "asset_isFavorite",
|
||||||
"asset"."isArchived" AS "asset_isArchived",
|
"asset"."isArchived" AS "asset_isArchived",
|
||||||
"asset"."isExternal" AS "asset_isExternal",
|
"asset"."isExternal" AS "asset_isExternal",
|
||||||
"asset"."isOffline" AS "asset_isOffline",
|
|
||||||
"asset"."checksum" AS "asset_checksum",
|
"asset"."checksum" AS "asset_checksum",
|
||||||
"asset"."duration" AS "asset_duration",
|
"asset"."duration" AS "asset_duration",
|
||||||
"asset"."isVisible" AS "asset_isVisible",
|
"asset"."isVisible" AS "asset_isVisible",
|
||||||
@ -29,6 +28,7 @@ SELECT
|
|||||||
"asset"."originalFileName" AS "asset_originalFileName",
|
"asset"."originalFileName" AS "asset_originalFileName",
|
||||||
"asset"."sidecarPath" AS "asset_sidecarPath",
|
"asset"."sidecarPath" AS "asset_sidecarPath",
|
||||||
"asset"."stackId" AS "asset_stackId",
|
"asset"."stackId" AS "asset_stackId",
|
||||||
|
"asset"."isOffline" AS "asset_isOffline",
|
||||||
"asset"."duplicateId" AS "asset_duplicateId",
|
"asset"."duplicateId" AS "asset_duplicateId",
|
||||||
"exifInfo"."assetId" AS "exifInfo_assetId",
|
"exifInfo"."assetId" AS "exifInfo_assetId",
|
||||||
"exifInfo"."description" AS "exifInfo_description",
|
"exifInfo"."description" AS "exifInfo_description",
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Chunked, ChunkedArray, DummyValue, GenerateSql } from 'src/decorators';
|
import { Chunked, ChunkedArray, DummyValue, GenerateSql } from 'src/decorators';
|
||||||
import { AssetTrashReason } from 'src/dtos/asset.dto';
|
|
||||||
import { AssetFileEntity } from 'src/entities/asset-files.entity';
|
import { AssetFileEntity } from 'src/entities/asset-files.entity';
|
||||||
import { AssetJobStatusEntity } from 'src/entities/asset-job-status.entity';
|
import { AssetJobStatusEntity } from 'src/entities/asset-job-status.entity';
|
||||||
import { AssetEntity } from 'src/entities/asset.entity';
|
import { AssetEntity } from 'src/entities/asset.entity';
|
||||||
@ -199,10 +198,11 @@ export class AssetRepository implements IAssetRepository {
|
|||||||
async getPathsNotInLibrary(libraryId: string, originalPaths: string[]): Promise<string[]> {
|
async getPathsNotInLibrary(libraryId: string, originalPaths: string[]): Promise<string[]> {
|
||||||
const result = await this.repository.query(
|
const result = await this.repository.query(
|
||||||
`
|
`
|
||||||
WITH paths AS (SELECT unnest($2::text[]) AS path)
|
WITH paths AS (SELECT unnest($2::text[]) AS path)
|
||||||
SELECT path FROM paths
|
SELECT path
|
||||||
WHERE NOT EXISTS (SELECT 1 FROM assets WHERE "libraryId" = $1 AND "originalPath" = path);
|
FROM paths
|
||||||
`,
|
WHERE NOT EXISTS (SELECT 1 FROM assets WHERE "libraryId" = $1 AND "originalPath" = path);
|
||||||
|
`,
|
||||||
[libraryId, originalPaths],
|
[libraryId, originalPaths],
|
||||||
);
|
);
|
||||||
return result.map((row: { path: string }) => row.path);
|
return result.map((row: { path: string }) => row.path);
|
||||||
@ -287,16 +287,6 @@ export class AssetRepository implements IAssetRepository {
|
|||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Chunked()
|
|
||||||
async softDeleteAll(ids: string[]): Promise<void> {
|
|
||||||
await this.repository.softDelete({ id: In(ids) });
|
|
||||||
}
|
|
||||||
|
|
||||||
@Chunked()
|
|
||||||
async restoreAll(ids: string[]): Promise<void> {
|
|
||||||
await this.updateAll(ids, { trashReason: null, deletedAt: null });
|
|
||||||
}
|
|
||||||
|
|
||||||
async update(asset: AssetUpdateOptions): Promise<void> {
|
async update(asset: AssetUpdateOptions): Promise<void> {
|
||||||
await this.repository.update(asset.id, asset);
|
await this.repository.update(asset.id, asset);
|
||||||
}
|
}
|
||||||
@ -820,32 +810,4 @@ export class AssetRepository implements IAssetRepository {
|
|||||||
async upsertFile({ assetId, type, path }: { assetId: string; type: AssetFileType; path: string }): Promise<void> {
|
async upsertFile({ assetId, type, path }: { assetId: string; type: AssetFileType; path: string }): Promise<void> {
|
||||||
await this.fileRepository.upsert({ assetId, type, path }, { conflictPaths: ['assetId', 'type'] });
|
await this.fileRepository.upsert({ assetId, type, path }, { conflictPaths: ['assetId', 'type'] });
|
||||||
}
|
}
|
||||||
|
|
||||||
@GenerateSql({
|
|
||||||
params: [
|
|
||||||
{
|
|
||||||
ownerId: DummyValue.UUID,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
async restoreAllDeleted(ownerId?: string): Promise<void> {
|
|
||||||
await this.repository.update(
|
|
||||||
{ ownerId, trashReason: AssetTrashReason.DELETED },
|
|
||||||
{ deletedAt: null, trashReason: null },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GenerateSql({
|
|
||||||
params: [
|
|
||||||
{
|
|
||||||
ownerId: DummyValue.UUID,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
async restoreAllDeletedById(ids: string[]): Promise<void> {
|
|
||||||
await this.repository.update(
|
|
||||||
{ id: In(ids), trashReason: AssetTrashReason.DELETED },
|
|
||||||
{ deletedAt: null, trashReason: null },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -79,12 +79,12 @@ export const JOBS_TO_QUEUE: Record<JobName, QueueName> = {
|
|||||||
[JobName.SIDECAR_WRITE]: QueueName.SIDECAR,
|
[JobName.SIDECAR_WRITE]: QueueName.SIDECAR,
|
||||||
|
|
||||||
// Library management
|
// Library management
|
||||||
[JobName.LIBRARY_REFRESH_ASSET]: QueueName.LIBRARY,
|
[JobName.LIBRARY_SYNC_FILE]: QueueName.LIBRARY,
|
||||||
[JobName.LIBRARY_QUEUE_SCAN]: QueueName.LIBRARY,
|
[JobName.LIBRARY_QUEUE_SYNC_FILES]: QueueName.LIBRARY,
|
||||||
[JobName.LIBRARY_QUEUE_OFFLINE_CHECK]: QueueName.LIBRARY,
|
[JobName.LIBRARY_QUEUE_SYNC_ASSETS]: QueueName.LIBRARY,
|
||||||
[JobName.LIBRARY_DELETE]: QueueName.LIBRARY,
|
[JobName.LIBRARY_DELETE]: QueueName.LIBRARY,
|
||||||
[JobName.LIBRARY_OFFLINE_CHECK]: QueueName.LIBRARY,
|
[JobName.LIBRARY_SYNC_ASSET]: QueueName.LIBRARY,
|
||||||
[JobName.LIBRARY_QUEUE_SCAN_ALL]: QueueName.LIBRARY,
|
[JobName.LIBRARY_QUEUE_SYNC_ALL]: QueueName.LIBRARY,
|
||||||
[JobName.LIBRARY_QUEUE_CLEANUP]: QueueName.LIBRARY,
|
[JobName.LIBRARY_QUEUE_CLEANUP]: QueueName.LIBRARY,
|
||||||
|
|
||||||
// Notification
|
// Notification
|
||||||
|
@ -300,7 +300,6 @@ export class AssetService {
|
|||||||
|
|
||||||
async deleteAll(auth: AuthDto, dto: AssetBulkDeleteDto): Promise<void> {
|
async deleteAll(auth: AuthDto, dto: AssetBulkDeleteDto): Promise<void> {
|
||||||
const { ids, force } = dto;
|
const { ids, force } = dto;
|
||||||
let { trashReason } = dto;
|
|
||||||
|
|
||||||
await requireAccess(this.access, { auth, permission: Permission.ASSET_DELETE, ids });
|
await requireAccess(this.access, { auth, permission: Permission.ASSET_DELETE, ids });
|
||||||
await this.assetRepository.updateAll(ids, {
|
await this.assetRepository.updateAll(ids, {
|
||||||
|
@ -164,7 +164,7 @@ export class JobService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case QueueName.LIBRARY: {
|
case QueueName.LIBRARY: {
|
||||||
return this.jobRepository.queue({ name: JobName.LIBRARY_QUEUE_SCAN_ALL, data: { force } });
|
return this.jobRepository.queue({ name: JobName.LIBRARY_QUEUE_SYNC_ALL, data: { force } });
|
||||||
}
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
|
@ -2,7 +2,6 @@ import { BadRequestException } from '@nestjs/common';
|
|||||||
import { Stats } from 'node:fs';
|
import { Stats } from 'node:fs';
|
||||||
import { SystemConfig } from 'src/config';
|
import { SystemConfig } from 'src/config';
|
||||||
import { SystemConfigCore } from 'src/cores/system-config.core';
|
import { SystemConfigCore } from 'src/cores/system-config.core';
|
||||||
import { AssetTrashReason } from 'src/dtos/asset.dto';
|
|
||||||
import { mapLibrary } from 'src/dtos/library.dto';
|
import { mapLibrary } from 'src/dtos/library.dto';
|
||||||
import { UserEntity } from 'src/entities/user.entity';
|
import { UserEntity } from 'src/entities/user.entity';
|
||||||
import { AssetType } from 'src/enum';
|
import { AssetType } from 'src/enum';
|
||||||
@ -172,11 +171,11 @@ describe(LibraryService.name, () => {
|
|||||||
});
|
});
|
||||||
assetMock.getExternalLibraryAssetPaths.mockResolvedValue({ items: [], hasNextPage: false });
|
assetMock.getExternalLibraryAssetPaths.mockResolvedValue({ items: [], hasNextPage: false });
|
||||||
|
|
||||||
await sut.handleQueueAssetRefresh({ id: libraryStub.externalLibrary1.id });
|
await sut.handleQueueSyncFiles({ id: libraryStub.externalLibrary1.id });
|
||||||
|
|
||||||
expect(jobMock.queueAll).toHaveBeenCalledWith([
|
expect(jobMock.queueAll).toHaveBeenCalledWith([
|
||||||
{
|
{
|
||||||
name: JobName.LIBRARY_REFRESH_ASSET,
|
name: JobName.LIBRARY_SYNC_FILE,
|
||||||
data: {
|
data: {
|
||||||
id: libraryStub.externalLibrary1.id,
|
id: libraryStub.externalLibrary1.id,
|
||||||
ownerId: libraryStub.externalLibrary1.owner.id,
|
ownerId: libraryStub.externalLibrary1.owner.id,
|
||||||
@ -189,9 +188,7 @@ describe(LibraryService.name, () => {
|
|||||||
it("should fail when library can't be found", async () => {
|
it("should fail when library can't be found", async () => {
|
||||||
libraryMock.get.mockResolvedValue(null);
|
libraryMock.get.mockResolvedValue(null);
|
||||||
|
|
||||||
await expect(sut.handleQueueAssetRefresh({ id: libraryStub.externalLibrary1.id })).resolves.toBe(
|
await expect(sut.handleQueueSyncFiles({ id: libraryStub.externalLibrary1.id })).resolves.toBe(JobStatus.SKIPPED);
|
||||||
JobStatus.SKIPPED,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should ignore import paths that do not exist', async () => {
|
it('should ignore import paths that do not exist', async () => {
|
||||||
@ -212,7 +209,7 @@ describe(LibraryService.name, () => {
|
|||||||
libraryMock.get.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1);
|
libraryMock.get.mockResolvedValue(libraryStub.externalLibraryWithImportPaths1);
|
||||||
assetMock.getExternalLibraryAssetPaths.mockResolvedValue({ items: [], hasNextPage: false });
|
assetMock.getExternalLibraryAssetPaths.mockResolvedValue({ items: [], hasNextPage: false });
|
||||||
|
|
||||||
await sut.handleQueueAssetRefresh({ id: libraryStub.externalLibraryWithImportPaths1.id });
|
await sut.handleQueueSyncFiles({ id: libraryStub.externalLibraryWithImportPaths1.id });
|
||||||
|
|
||||||
expect(storageMock.walk).toHaveBeenCalledWith({
|
expect(storageMock.walk).toHaveBeenCalledWith({
|
||||||
pathsToCrawl: [libraryStub.externalLibraryWithImportPaths1.importPaths[1]],
|
pathsToCrawl: [libraryStub.externalLibraryWithImportPaths1.importPaths[1]],
|
||||||
@ -229,11 +226,11 @@ describe(LibraryService.name, () => {
|
|||||||
storageMock.walk.mockImplementation(async function* generator() {});
|
storageMock.walk.mockImplementation(async function* generator() {});
|
||||||
assetMock.getAll.mockResolvedValue({ items: [assetStub.external], hasNextPage: false });
|
assetMock.getAll.mockResolvedValue({ items: [assetStub.external], hasNextPage: false });
|
||||||
|
|
||||||
await sut.handleQueueAssetOfflineCheck({ id: libraryStub.externalLibrary1.id });
|
await sut.handleQueueSyncAssets({ id: libraryStub.externalLibrary1.id });
|
||||||
|
|
||||||
expect(jobMock.queueAll).toHaveBeenCalledWith([
|
expect(jobMock.queueAll).toHaveBeenCalledWith([
|
||||||
{
|
{
|
||||||
name: JobName.LIBRARY_OFFLINE_CHECK,
|
name: JobName.LIBRARY_SYNC_ASSET,
|
||||||
data: {
|
data: {
|
||||||
id: assetStub.external.id,
|
id: assetStub.external.id,
|
||||||
importPaths: libraryStub.externalLibrary1.importPaths,
|
importPaths: libraryStub.externalLibrary1.importPaths,
|
||||||
@ -246,9 +243,7 @@ describe(LibraryService.name, () => {
|
|||||||
it("should fail when library can't be found", async () => {
|
it("should fail when library can't be found", async () => {
|
||||||
libraryMock.get.mockResolvedValue(null);
|
libraryMock.get.mockResolvedValue(null);
|
||||||
|
|
||||||
await expect(sut.handleQueueAssetOfflineCheck({ id: libraryStub.externalLibrary1.id })).resolves.toBe(
|
await expect(sut.handleQueueSyncAssets({ id: libraryStub.externalLibrary1.id })).resolves.toBe(JobStatus.SKIPPED);
|
||||||
JobStatus.SKIPPED,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -262,7 +257,7 @@ describe(LibraryService.name, () => {
|
|||||||
|
|
||||||
assetMock.getById.mockResolvedValue(null);
|
assetMock.getById.mockResolvedValue(null);
|
||||||
|
|
||||||
await expect(sut.handleAssetOfflineCheck(mockAssetJob)).resolves.toBe(JobStatus.SKIPPED);
|
await expect(sut.handleSyncAsset(mockAssetJob)).resolves.toBe(JobStatus.SKIPPED);
|
||||||
|
|
||||||
expect(assetMock.remove).not.toHaveBeenCalled();
|
expect(assetMock.remove).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
@ -276,10 +271,10 @@ describe(LibraryService.name, () => {
|
|||||||
|
|
||||||
assetMock.getById.mockResolvedValue(assetStub.external);
|
assetMock.getById.mockResolvedValue(assetStub.external);
|
||||||
|
|
||||||
await expect(sut.handleAssetOfflineCheck(mockAssetJob)).resolves.toBe(JobStatus.SUCCESS);
|
await expect(sut.handleSyncAsset(mockAssetJob)).resolves.toBe(JobStatus.SUCCESS);
|
||||||
|
|
||||||
expect(assetMock.updateAll).toHaveBeenCalledWith([assetStub.external.id], {
|
expect(assetMock.updateAll).toHaveBeenCalledWith([assetStub.external.id], {
|
||||||
trashReason: AssetTrashReason.OFFLINE,
|
isOffline: true,
|
||||||
});
|
});
|
||||||
expect(assetMock.softDeleteAll).toHaveBeenCalledWith([assetStub.external.id]);
|
expect(assetMock.softDeleteAll).toHaveBeenCalledWith([assetStub.external.id]);
|
||||||
});
|
});
|
||||||
@ -293,9 +288,9 @@ describe(LibraryService.name, () => {
|
|||||||
|
|
||||||
assetMock.getById.mockResolvedValue(assetStub.external);
|
assetMock.getById.mockResolvedValue(assetStub.external);
|
||||||
|
|
||||||
await expect(sut.handleAssetOfflineCheck(mockAssetJob)).resolves.toBe(JobStatus.SUCCESS);
|
await expect(sut.handleSyncAsset(mockAssetJob)).resolves.toBe(JobStatus.SUCCESS);
|
||||||
expect(assetMock.updateAll).toHaveBeenCalledWith([assetStub.external.id], {
|
expect(assetMock.updateAll).toHaveBeenCalledWith([assetStub.external.id], {
|
||||||
trashReason: AssetTrashReason.OFFLINE,
|
isOffline: true,
|
||||||
});
|
});
|
||||||
expect(assetMock.softDeleteAll).toHaveBeenCalledWith([assetStub.external.id]);
|
expect(assetMock.softDeleteAll).toHaveBeenCalledWith([assetStub.external.id]);
|
||||||
});
|
});
|
||||||
@ -310,10 +305,10 @@ describe(LibraryService.name, () => {
|
|||||||
assetMock.getById.mockResolvedValue(assetStub.external);
|
assetMock.getById.mockResolvedValue(assetStub.external);
|
||||||
storageMock.checkFileExists.mockResolvedValue(true);
|
storageMock.checkFileExists.mockResolvedValue(true);
|
||||||
|
|
||||||
await expect(sut.handleAssetOfflineCheck(mockAssetJob)).resolves.toBe(JobStatus.SUCCESS);
|
await expect(sut.handleSyncAsset(mockAssetJob)).resolves.toBe(JobStatus.SUCCESS);
|
||||||
|
|
||||||
expect(assetMock.updateAll).toHaveBeenCalledWith([assetStub.external.id], {
|
expect(assetMock.updateAll).toHaveBeenCalledWith([assetStub.external.id], {
|
||||||
trashReason: AssetTrashReason.OFFLINE,
|
isOffline: true,
|
||||||
});
|
});
|
||||||
expect(assetMock.softDeleteAll).toHaveBeenCalledWith([assetStub.external.id]);
|
expect(assetMock.softDeleteAll).toHaveBeenCalledWith([assetStub.external.id]);
|
||||||
});
|
});
|
||||||
@ -328,7 +323,7 @@ describe(LibraryService.name, () => {
|
|||||||
assetMock.getById.mockResolvedValue(assetStub.external);
|
assetMock.getById.mockResolvedValue(assetStub.external);
|
||||||
storageMock.checkFileExists.mockResolvedValue(true);
|
storageMock.checkFileExists.mockResolvedValue(true);
|
||||||
|
|
||||||
await expect(sut.handleAssetOfflineCheck(mockAssetJob)).resolves.toBe(JobStatus.SUCCESS);
|
await expect(sut.handleSyncAsset(mockAssetJob)).resolves.toBe(JobStatus.SUCCESS);
|
||||||
|
|
||||||
expect(assetMock.remove).not.toHaveBeenCalled();
|
expect(assetMock.remove).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
@ -356,7 +351,7 @@ describe(LibraryService.name, () => {
|
|||||||
|
|
||||||
assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(null);
|
assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(null);
|
||||||
|
|
||||||
await expect(sut.handleAssetRefresh(mockLibraryJob)).rejects.toBeInstanceOf(BadRequestException);
|
await expect(sut.handleSyncFile(mockLibraryJob)).rejects.toBeInstanceOf(BadRequestException);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reject an unknown file type', async () => {
|
it('should reject an unknown file type', async () => {
|
||||||
@ -366,7 +361,7 @@ describe(LibraryService.name, () => {
|
|||||||
assetPath: '/data/user1/file.xyz',
|
assetPath: '/data/user1/file.xyz',
|
||||||
};
|
};
|
||||||
|
|
||||||
await expect(sut.handleAssetRefresh(mockLibraryJob)).rejects.toBeInstanceOf(BadRequestException);
|
await expect(sut.handleSyncFile(mockLibraryJob)).rejects.toBeInstanceOf(BadRequestException);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should import a new asset', async () => {
|
it('should import a new asset', async () => {
|
||||||
@ -379,7 +374,7 @@ describe(LibraryService.name, () => {
|
|||||||
assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(null);
|
assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(null);
|
||||||
assetMock.create.mockResolvedValue(assetStub.image);
|
assetMock.create.mockResolvedValue(assetStub.image);
|
||||||
|
|
||||||
await expect(sut.handleAssetRefresh(mockLibraryJob)).resolves.toBe(JobStatus.SUCCESS);
|
await expect(sut.handleSyncFile(mockLibraryJob)).resolves.toBe(JobStatus.SUCCESS);
|
||||||
|
|
||||||
expect(assetMock.create.mock.calls).toEqual([
|
expect(assetMock.create.mock.calls).toEqual([
|
||||||
[
|
[
|
||||||
@ -425,7 +420,7 @@ describe(LibraryService.name, () => {
|
|||||||
assetMock.create.mockResolvedValue(assetStub.image);
|
assetMock.create.mockResolvedValue(assetStub.image);
|
||||||
storageMock.checkFileExists.mockResolvedValue(true);
|
storageMock.checkFileExists.mockResolvedValue(true);
|
||||||
|
|
||||||
await expect(sut.handleAssetRefresh(mockLibraryJob)).resolves.toBe(JobStatus.SUCCESS);
|
await expect(sut.handleSyncFile(mockLibraryJob)).resolves.toBe(JobStatus.SUCCESS);
|
||||||
|
|
||||||
expect(assetMock.create.mock.calls).toEqual([
|
expect(assetMock.create.mock.calls).toEqual([
|
||||||
[
|
[
|
||||||
@ -470,7 +465,7 @@ describe(LibraryService.name, () => {
|
|||||||
assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(null);
|
assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(null);
|
||||||
assetMock.create.mockResolvedValue(assetStub.video);
|
assetMock.create.mockResolvedValue(assetStub.video);
|
||||||
|
|
||||||
await expect(sut.handleAssetRefresh(mockLibraryJob)).resolves.toBe(JobStatus.SUCCESS);
|
await expect(sut.handleSyncFile(mockLibraryJob)).resolves.toBe(JobStatus.SUCCESS);
|
||||||
|
|
||||||
expect(assetMock.create.mock.calls).toEqual([
|
expect(assetMock.create.mock.calls).toEqual([
|
||||||
[
|
[
|
||||||
@ -524,7 +519,7 @@ describe(LibraryService.name, () => {
|
|||||||
assetMock.create.mockResolvedValue(assetStub.image);
|
assetMock.create.mockResolvedValue(assetStub.image);
|
||||||
libraryMock.get.mockResolvedValue({ ...libraryStub.externalLibrary1, deletedAt: new Date() });
|
libraryMock.get.mockResolvedValue({ ...libraryStub.externalLibrary1, deletedAt: new Date() });
|
||||||
|
|
||||||
await expect(sut.handleAssetRefresh(mockLibraryJob)).resolves.toBe(JobStatus.FAILED);
|
await expect(sut.handleSyncFile(mockLibraryJob)).resolves.toBe(JobStatus.FAILED);
|
||||||
|
|
||||||
expect(assetMock.create.mock.calls).toEqual([]);
|
expect(assetMock.create.mock.calls).toEqual([]);
|
||||||
});
|
});
|
||||||
@ -544,7 +539,7 @@ describe(LibraryService.name, () => {
|
|||||||
|
|
||||||
assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.hasFileExtension);
|
assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.hasFileExtension);
|
||||||
|
|
||||||
await expect(sut.handleAssetRefresh(mockLibraryJob)).resolves.toBe(JobStatus.SKIPPED);
|
await expect(sut.handleSyncFile(mockLibraryJob)).resolves.toBe(JobStatus.SKIPPED);
|
||||||
|
|
||||||
expect(jobMock.queue).not.toHaveBeenCalled();
|
expect(jobMock.queue).not.toHaveBeenCalled();
|
||||||
expect(jobMock.queueAll).not.toHaveBeenCalled();
|
expect(jobMock.queueAll).not.toHaveBeenCalled();
|
||||||
@ -560,7 +555,7 @@ describe(LibraryService.name, () => {
|
|||||||
assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.image);
|
assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.image);
|
||||||
assetMock.create.mockResolvedValue(assetStub.image);
|
assetMock.create.mockResolvedValue(assetStub.image);
|
||||||
|
|
||||||
await expect(sut.handleAssetRefresh(mockLibraryJob)).resolves.toBe(JobStatus.SUCCESS);
|
await expect(sut.handleSyncFile(mockLibraryJob)).resolves.toBe(JobStatus.SUCCESS);
|
||||||
|
|
||||||
expect(jobMock.queue).toHaveBeenCalledWith({
|
expect(jobMock.queue).toHaveBeenCalledWith({
|
||||||
name: JobName.METADATA_EXTRACTION,
|
name: JobName.METADATA_EXTRACTION,
|
||||||
@ -587,7 +582,7 @@ describe(LibraryService.name, () => {
|
|||||||
|
|
||||||
assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.trashed);
|
assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.trashed);
|
||||||
|
|
||||||
await expect(sut.handleAssetRefresh(mockLibraryJob)).resolves.toBe(JobStatus.SKIPPED);
|
await expect(sut.handleSyncFile(mockLibraryJob)).resolves.toBe(JobStatus.SKIPPED);
|
||||||
|
|
||||||
expect(jobMock.queue).not.toHaveBeenCalled();
|
expect(jobMock.queue).not.toHaveBeenCalled();
|
||||||
expect(jobMock.queueAll).not.toHaveBeenCalled();
|
expect(jobMock.queueAll).not.toHaveBeenCalled();
|
||||||
@ -602,7 +597,7 @@ describe(LibraryService.name, () => {
|
|||||||
|
|
||||||
assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.trashedOffline);
|
assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(assetStub.trashedOffline);
|
||||||
|
|
||||||
await expect(sut.handleAssetRefresh(mockLibraryJob)).resolves.toBe(JobStatus.SUCCESS);
|
await expect(sut.handleSyncFile(mockLibraryJob)).resolves.toBe(JobStatus.SUCCESS);
|
||||||
|
|
||||||
expect(assetMock.restoreAll).toHaveBeenCalledWith([assetStub.trashedOffline.id]);
|
expect(assetMock.restoreAll).toHaveBeenCalledWith([assetStub.trashedOffline.id]);
|
||||||
});
|
});
|
||||||
@ -619,7 +614,7 @@ describe(LibraryService.name, () => {
|
|||||||
assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(null);
|
assetMock.getByLibraryIdAndOriginalPath.mockResolvedValue(null);
|
||||||
assetMock.create.mockResolvedValue(assetStub.image);
|
assetMock.create.mockResolvedValue(assetStub.image);
|
||||||
|
|
||||||
await expect(sut.handleAssetRefresh(mockLibraryJob)).rejects.toBeInstanceOf(BadRequestException);
|
await expect(sut.handleSyncFile(mockLibraryJob)).rejects.toBeInstanceOf(BadRequestException);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -926,7 +921,7 @@ describe(LibraryService.name, () => {
|
|||||||
|
|
||||||
expect(jobMock.queueAll).toHaveBeenCalledWith([
|
expect(jobMock.queueAll).toHaveBeenCalledWith([
|
||||||
{
|
{
|
||||||
name: JobName.LIBRARY_REFRESH_ASSET,
|
name: JobName.LIBRARY_SYNC_FILE,
|
||||||
data: {
|
data: {
|
||||||
id: libraryStub.externalLibraryWithImportPaths1.id,
|
id: libraryStub.externalLibraryWithImportPaths1.id,
|
||||||
assetPath: '/foo/photo.jpg',
|
assetPath: '/foo/photo.jpg',
|
||||||
@ -947,7 +942,7 @@ describe(LibraryService.name, () => {
|
|||||||
|
|
||||||
expect(jobMock.queueAll).toHaveBeenCalledWith([
|
expect(jobMock.queueAll).toHaveBeenCalledWith([
|
||||||
{
|
{
|
||||||
name: JobName.LIBRARY_REFRESH_ASSET,
|
name: JobName.LIBRARY_SYNC_FILE,
|
||||||
data: {
|
data: {
|
||||||
id: libraryStub.externalLibraryWithImportPaths1.id,
|
id: libraryStub.externalLibraryWithImportPaths1.id,
|
||||||
assetPath: '/foo/photo.jpg',
|
assetPath: '/foo/photo.jpg',
|
||||||
@ -1059,7 +1054,7 @@ describe(LibraryService.name, () => {
|
|||||||
expect(jobMock.queue.mock.calls).toEqual([
|
expect(jobMock.queue.mock.calls).toEqual([
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
name: JobName.LIBRARY_QUEUE_SCAN,
|
name: JobName.LIBRARY_QUEUE_SYNC_FILES,
|
||||||
data: {
|
data: {
|
||||||
id: libraryStub.externalLibrary1.id,
|
id: libraryStub.externalLibrary1.id,
|
||||||
},
|
},
|
||||||
@ -1067,24 +1062,7 @@ describe(LibraryService.name, () => {
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
name: JobName.LIBRARY_QUEUE_OFFLINE_CHECK,
|
name: JobName.LIBRARY_QUEUE_SYNC_ASSETS,
|
||||||
data: {
|
|
||||||
id: libraryStub.externalLibrary1.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('queueOfflineCheck', () => {
|
|
||||||
it('should queue the trash job', async () => {
|
|
||||||
await sut.queueOfflineCheck(libraryStub.externalLibrary1.id);
|
|
||||||
|
|
||||||
expect(jobMock.queue.mock.calls).toEqual([
|
|
||||||
[
|
|
||||||
{
|
|
||||||
name: JobName.LIBRARY_OFFLINE_CHECK,
|
|
||||||
data: {
|
data: {
|
||||||
id: libraryStub.externalLibrary1.id,
|
id: libraryStub.externalLibrary1.id,
|
||||||
},
|
},
|
||||||
@ -1098,7 +1076,7 @@ describe(LibraryService.name, () => {
|
|||||||
it('should queue the refresh job', async () => {
|
it('should queue the refresh job', async () => {
|
||||||
libraryMock.getAll.mockResolvedValue([libraryStub.externalLibrary1]);
|
libraryMock.getAll.mockResolvedValue([libraryStub.externalLibrary1]);
|
||||||
|
|
||||||
await expect(sut.handleQueueAllScan()).resolves.toBe(JobStatus.SUCCESS);
|
await expect(sut.handleQueueSyncAll()).resolves.toBe(JobStatus.SUCCESS);
|
||||||
|
|
||||||
expect(jobMock.queue.mock.calls).toEqual([
|
expect(jobMock.queue.mock.calls).toEqual([
|
||||||
[
|
[
|
||||||
@ -1110,7 +1088,7 @@ describe(LibraryService.name, () => {
|
|||||||
]);
|
]);
|
||||||
expect(jobMock.queueAll).toHaveBeenCalledWith([
|
expect(jobMock.queueAll).toHaveBeenCalledWith([
|
||||||
{
|
{
|
||||||
name: JobName.LIBRARY_QUEUE_SCAN,
|
name: JobName.LIBRARY_QUEUE_SYNC_FILES,
|
||||||
data: {
|
data: {
|
||||||
id: libraryStub.externalLibrary1.id,
|
id: libraryStub.externalLibrary1.id,
|
||||||
},
|
},
|
||||||
@ -1125,13 +1103,11 @@ describe(LibraryService.name, () => {
|
|||||||
assetMock.getAll.mockResolvedValue({ items: [assetStub.image1], hasNextPage: false });
|
assetMock.getAll.mockResolvedValue({ items: [assetStub.image1], hasNextPage: false });
|
||||||
assetMock.getById.mockResolvedValue(assetStub.image1);
|
assetMock.getById.mockResolvedValue(assetStub.image1);
|
||||||
|
|
||||||
await expect(sut.handleQueueAssetOfflineCheck({ id: libraryStub.externalLibrary1.id })).resolves.toBe(
|
await expect(sut.handleQueueSyncAssets({ id: libraryStub.externalLibrary1.id })).resolves.toBe(JobStatus.SUCCESS);
|
||||||
JobStatus.SUCCESS,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(jobMock.queueAll).toHaveBeenCalledWith([
|
expect(jobMock.queueAll).toHaveBeenCalledWith([
|
||||||
{
|
{
|
||||||
name: JobName.LIBRARY_OFFLINE_CHECK,
|
name: JobName.LIBRARY_SYNC_ASSET,
|
||||||
data: {
|
data: {
|
||||||
id: assetStub.image1.id,
|
id: assetStub.image1.id,
|
||||||
importPaths: libraryStub.externalLibrary1.importPaths,
|
importPaths: libraryStub.externalLibrary1.importPaths,
|
||||||
|
@ -5,16 +5,15 @@ import picomatch from 'picomatch';
|
|||||||
import { StorageCore } from 'src/cores/storage.core';
|
import { StorageCore } from 'src/cores/storage.core';
|
||||||
import { SystemConfigCore } from 'src/cores/system-config.core';
|
import { SystemConfigCore } from 'src/cores/system-config.core';
|
||||||
import { OnEmit } from 'src/decorators';
|
import { OnEmit } from 'src/decorators';
|
||||||
import { AssetTrashReason } from 'src/dtos/asset.dto';
|
|
||||||
import {
|
import {
|
||||||
CreateLibraryDto,
|
CreateLibraryDto,
|
||||||
LibraryResponseDto,
|
LibraryResponseDto,
|
||||||
LibraryStatsResponseDto,
|
LibraryStatsResponseDto,
|
||||||
|
mapLibrary,
|
||||||
UpdateLibraryDto,
|
UpdateLibraryDto,
|
||||||
ValidateLibraryDto,
|
ValidateLibraryDto,
|
||||||
ValidateLibraryImportPathResponseDto,
|
ValidateLibraryImportPathResponseDto,
|
||||||
ValidateLibraryResponseDto,
|
ValidateLibraryResponseDto,
|
||||||
mapLibrary,
|
|
||||||
} from 'src/dtos/library.dto';
|
} from 'src/dtos/library.dto';
|
||||||
import { AssetEntity } from 'src/entities/asset.entity';
|
import { AssetEntity } from 'src/entities/asset.entity';
|
||||||
import { AssetType } from 'src/enum';
|
import { AssetType } from 'src/enum';
|
||||||
@ -27,8 +26,8 @@ import {
|
|||||||
IJobRepository,
|
IJobRepository,
|
||||||
ILibraryFileJob,
|
ILibraryFileJob,
|
||||||
ILibraryOfflineJob,
|
ILibraryOfflineJob,
|
||||||
JOBS_LIBRARY_PAGINATION_SIZE,
|
|
||||||
JobName,
|
JobName,
|
||||||
|
JOBS_LIBRARY_PAGINATION_SIZE,
|
||||||
JobStatus,
|
JobStatus,
|
||||||
} from 'src/interfaces/job.interface';
|
} from 'src/interfaces/job.interface';
|
||||||
import { ILibraryRepository } from 'src/interfaces/library.interface';
|
import { ILibraryRepository } from 'src/interfaces/library.interface';
|
||||||
@ -76,7 +75,7 @@ export class LibraryService {
|
|||||||
this.jobRepository.addCronJob(
|
this.jobRepository.addCronJob(
|
||||||
'libraryScan',
|
'libraryScan',
|
||||||
scan.cronExpression,
|
scan.cronExpression,
|
||||||
() => handlePromiseError(this.jobRepository.queue({ name: JobName.LIBRARY_QUEUE_SCAN_ALL }), this.logger),
|
() => handlePromiseError(this.jobRepository.queue({ name: JobName.LIBRARY_QUEUE_SYNC_ALL }), this.logger),
|
||||||
scan.enabled,
|
scan.enabled,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -247,7 +246,7 @@ export class LibraryService {
|
|||||||
private async scanAssets(libraryId: string, assetPaths: string[], ownerId: string) {
|
private async scanAssets(libraryId: string, assetPaths: string[], ownerId: string) {
|
||||||
await this.jobRepository.queueAll(
|
await this.jobRepository.queueAll(
|
||||||
assetPaths.map((assetPath) => ({
|
assetPaths.map((assetPath) => ({
|
||||||
name: JobName.LIBRARY_REFRESH_ASSET,
|
name: JobName.LIBRARY_SYNC_FILE,
|
||||||
data: {
|
data: {
|
||||||
id: libraryId,
|
id: libraryId,
|
||||||
assetPath,
|
assetPath,
|
||||||
@ -359,134 +358,94 @@ export class LibraryService {
|
|||||||
return JobStatus.SUCCESS;
|
return JobStatus.SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getMtime(path: string): Promise<Date> {
|
async handleSyncFile(job: ILibraryFileJob): Promise<JobStatus> {
|
||||||
try {
|
// Only needs to handle new assets
|
||||||
const stat = await this.storageRepository.stat(path);
|
|
||||||
return stat.mtime;
|
|
||||||
} catch (error: Error | any) {
|
|
||||||
throw new BadRequestException(`Cannot access file ${path}`, { cause: error });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async refreshExistingAsset(asset: AssetEntity) {
|
|
||||||
if (asset.trashReason == AssetTrashReason.DELETED) {
|
|
||||||
this.logger.debug(`Asset is previously trashed by user, won't refresh: ${asset.originalPath}`);
|
|
||||||
return JobStatus.SKIPPED;
|
|
||||||
} else if (asset.trashReason == AssetTrashReason.OFFLINE) {
|
|
||||||
this.logger.debug(`Asset is previously trashed as offline, restoring from trash: ${asset.originalPath}`);
|
|
||||||
await this.assetRepository.restoreAll([asset.id]);
|
|
||||||
return JobStatus.SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mtime = await this.getMtime(asset.originalPath);
|
|
||||||
if (mtime.toISOString() === asset.fileModifiedAt.toISOString()) {
|
|
||||||
this.logger.debug(`Asset already exists in database and on disk, will not import: ${asset.originalPath}`);
|
|
||||||
return JobStatus.SKIPPED;
|
|
||||||
}
|
|
||||||
this.logger.debug(
|
|
||||||
`File modification time has changed, re-importing asset: ${asset.originalPath}. Old mtime: ${asset.fileModifiedAt}. New mtime: ${mtime}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.assetRepository.updateAll([asset.id], {
|
|
||||||
fileCreatedAt: mtime,
|
|
||||||
fileModifiedAt: mtime,
|
|
||||||
originalFileName: parse(asset.originalPath).base,
|
|
||||||
deletedAt: null,
|
|
||||||
trashReason: null,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleAssetRefresh(job: ILibraryFileJob): Promise<JobStatus> {
|
|
||||||
const assetPath = path.normalize(job.assetPath);
|
const assetPath = path.normalize(job.assetPath);
|
||||||
|
|
||||||
let asset = await this.assetRepository.getByLibraryIdAndOriginalPath(job.id, assetPath);
|
let asset = await this.assetRepository.getByLibraryIdAndOriginalPath(job.id, assetPath);
|
||||||
|
|
||||||
if (asset) {
|
if (asset) {
|
||||||
const status = await this.refreshExistingAsset(asset);
|
return JobStatus.SKIPPED;
|
||||||
|
|
||||||
if (status) {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// This asset is new to us, read it from disk
|
|
||||||
this.logger.log(`Importing new library asset: ${assetPath}`);
|
|
||||||
|
|
||||||
const library = await this.repository.get(job.id, true);
|
|
||||||
if (library?.deletedAt) {
|
|
||||||
this.logger.error('Cannot import asset into deleted library');
|
|
||||||
return JobStatus.FAILED;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: device asset id is deprecated, remove it
|
|
||||||
const deviceAssetId = `${basename(assetPath)}`.replaceAll(/\s+/g, '');
|
|
||||||
|
|
||||||
const pathHash = this.cryptoRepository.hashSha1(`path:${assetPath}`);
|
|
||||||
|
|
||||||
// TODO: doesn't xmp replace the file extension? Will need investigation
|
|
||||||
let sidecarPath: string | null = null;
|
|
||||||
if (await this.storageRepository.checkFileExists(`${assetPath}.xmp`, R_OK)) {
|
|
||||||
sidecarPath = `${assetPath}.xmp`;
|
|
||||||
}
|
|
||||||
|
|
||||||
let assetType: AssetType;
|
|
||||||
|
|
||||||
if (mimeTypes.isImage(assetPath)) {
|
|
||||||
assetType = AssetType.IMAGE;
|
|
||||||
} else if (mimeTypes.isVideo(assetPath)) {
|
|
||||||
assetType = AssetType.VIDEO;
|
|
||||||
} else {
|
|
||||||
throw new BadRequestException(`Unsupported file type ${assetPath}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mtime = await this.getMtime(assetPath);
|
|
||||||
|
|
||||||
// TODO: In wait of refactoring the domain asset service, this function is just manually written like this
|
|
||||||
asset = await this.assetRepository.create({
|
|
||||||
ownerId: job.ownerId,
|
|
||||||
libraryId: job.id,
|
|
||||||
checksum: pathHash,
|
|
||||||
originalPath: assetPath,
|
|
||||||
deviceAssetId,
|
|
||||||
deviceId: 'Library Import',
|
|
||||||
fileCreatedAt: mtime,
|
|
||||||
fileModifiedAt: mtime,
|
|
||||||
localDateTime: mtime,
|
|
||||||
type: assetType,
|
|
||||||
originalFileName: parse(assetPath).base,
|
|
||||||
sidecarPath,
|
|
||||||
isExternal: true,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.debug(`Queueing metadata extraction for: ${assetPath}`);
|
let stat;
|
||||||
|
try {
|
||||||
|
stat = await this.storageRepository.stat(assetPath);
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error.code === 'ENOENT') {
|
||||||
|
this.logger.error(`File not found: ${assetPath}`);
|
||||||
|
return JobStatus.SKIPPED;
|
||||||
|
}
|
||||||
|
this.logger.error(`Error reading file: ${assetPath}. Error: ${error}`);
|
||||||
|
return JobStatus.FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.log(`Importing new library asset: ${assetPath}`);
|
||||||
|
|
||||||
|
const library = await this.repository.get(job.id, true);
|
||||||
|
if (library?.deletedAt) {
|
||||||
|
this.logger.error('Cannot import asset into deleted library');
|
||||||
|
return JobStatus.FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: device asset id is deprecated, remove it
|
||||||
|
const deviceAssetId = `${basename(assetPath)}`.replaceAll(/\s+/g, '');
|
||||||
|
|
||||||
|
const pathHash = this.cryptoRepository.hashSha1(`path:${assetPath}`);
|
||||||
|
|
||||||
|
// TODO: doesn't xmp replace the file extension? Will need investigation
|
||||||
|
let sidecarPath: string | null = null;
|
||||||
|
if (await this.storageRepository.checkFileExists(`${assetPath}.xmp`, R_OK)) {
|
||||||
|
sidecarPath = `${assetPath}.xmp`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const assetType = mimeTypes.isVideo(assetPath) ? AssetType.VIDEO : AssetType.IMAGE;
|
||||||
|
|
||||||
|
const mtime = stat.mtime;
|
||||||
|
|
||||||
|
asset = await this.assetRepository.create({
|
||||||
|
ownerId: job.ownerId,
|
||||||
|
libraryId: job.id,
|
||||||
|
checksum: pathHash,
|
||||||
|
originalPath: assetPath,
|
||||||
|
deviceAssetId,
|
||||||
|
deviceId: 'Library Import',
|
||||||
|
fileCreatedAt: mtime,
|
||||||
|
fileModifiedAt: mtime,
|
||||||
|
localDateTime: mtime,
|
||||||
|
type: assetType,
|
||||||
|
originalFileName: parse(assetPath).base,
|
||||||
|
sidecarPath,
|
||||||
|
isExternal: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.queuePostSyncJobs(asset);
|
||||||
|
|
||||||
|
return JobStatus.SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
async queuePostSyncJobs(asset: AssetEntity) {
|
||||||
|
this.logger.debug(`Queueing metadata extraction for: ${asset.originalPath}`);
|
||||||
|
|
||||||
await this.jobRepository.queue({ name: JobName.METADATA_EXTRACTION, data: { id: asset.id, source: 'upload' } });
|
await this.jobRepository.queue({ name: JobName.METADATA_EXTRACTION, data: { id: asset.id, source: 'upload' } });
|
||||||
|
|
||||||
if (asset.type === AssetType.VIDEO) {
|
if (asset.type === AssetType.VIDEO) {
|
||||||
await this.jobRepository.queue({ name: JobName.VIDEO_CONVERSION, data: { id: asset.id } });
|
await this.jobRepository.queue({ name: JobName.VIDEO_CONVERSION, data: { id: asset.id } });
|
||||||
}
|
}
|
||||||
|
|
||||||
return JobStatus.SUCCESS;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async queueScan(id: string) {
|
async queueScan(id: string) {
|
||||||
await this.findOrFail(id);
|
await this.findOrFail(id);
|
||||||
|
|
||||||
await this.jobRepository.queue({
|
await this.jobRepository.queue({
|
||||||
name: JobName.LIBRARY_QUEUE_SCAN,
|
name: JobName.LIBRARY_QUEUE_SYNC_FILES,
|
||||||
data: {
|
data: {
|
||||||
id,
|
id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
await this.jobRepository.queue({ name: JobName.LIBRARY_QUEUE_OFFLINE_CHECK, data: { id } });
|
await this.jobRepository.queue({ name: JobName.LIBRARY_QUEUE_SYNC_ASSETS, data: { id } });
|
||||||
}
|
}
|
||||||
|
|
||||||
async queueOfflineCheck(id: string) {
|
async handleQueueSyncAll(): Promise<JobStatus> {
|
||||||
this.logger.verbose(`Queueing offline file removal from library ${id}`);
|
|
||||||
await this.jobRepository.queue({ name: JobName.LIBRARY_OFFLINE_CHECK, data: { id } });
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleQueueAllScan(): Promise<JobStatus> {
|
|
||||||
this.logger.debug(`Refreshing all external libraries`);
|
this.logger.debug(`Refreshing all external libraries`);
|
||||||
|
|
||||||
await this.jobRepository.queue({ name: JobName.LIBRARY_QUEUE_CLEANUP, data: {} });
|
await this.jobRepository.queue({ name: JobName.LIBRARY_QUEUE_CLEANUP, data: {} });
|
||||||
@ -494,7 +453,7 @@ export class LibraryService {
|
|||||||
const libraries = await this.repository.getAll(true);
|
const libraries = await this.repository.getAll(true);
|
||||||
await this.jobRepository.queueAll(
|
await this.jobRepository.queueAll(
|
||||||
libraries.map((library) => ({
|
libraries.map((library) => ({
|
||||||
name: JobName.LIBRARY_QUEUE_SCAN,
|
name: JobName.LIBRARY_QUEUE_SYNC_FILES,
|
||||||
data: {
|
data: {
|
||||||
id: library.id,
|
id: library.id,
|
||||||
},
|
},
|
||||||
@ -502,7 +461,7 @@ export class LibraryService {
|
|||||||
);
|
);
|
||||||
await this.jobRepository.queueAll(
|
await this.jobRepository.queueAll(
|
||||||
libraries.map((library) => ({
|
libraries.map((library) => ({
|
||||||
name: JobName.LIBRARY_QUEUE_OFFLINE_CHECK,
|
name: JobName.LIBRARY_QUEUE_SYNC_ASSETS,
|
||||||
data: {
|
data: {
|
||||||
id: library.id,
|
id: library.id,
|
||||||
},
|
},
|
||||||
@ -511,20 +470,17 @@ export class LibraryService {
|
|||||||
return JobStatus.SUCCESS;
|
return JobStatus.SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleAssetOfflineCheck(job: ILibraryOfflineJob): Promise<JobStatus> {
|
async handleSyncAsset(job: ILibraryOfflineJob): Promise<JobStatus> {
|
||||||
const asset = await this.assetRepository.getById(job.id);
|
const asset = await this.assetRepository.getById(job.id);
|
||||||
|
if (!asset) {
|
||||||
if (!asset || asset.trashReason) {
|
|
||||||
// Skip if asset is missing or already trashed
|
|
||||||
// We don't want to trash an asset that has already been trashed because it can otherwise re-appear on the timeline if an asset is re-imported
|
|
||||||
return JobStatus.SKIPPED;
|
return JobStatus.SKIPPED;
|
||||||
}
|
}
|
||||||
|
|
||||||
const markOffline = async (explanation: string) => {
|
const markOffline = async (explanation: string) => {
|
||||||
this.logger.debug(`${explanation}, removing: ${asset.originalPath}`);
|
if (!asset.isOffline) {
|
||||||
|
this.logger.debug(`${explanation}, removing: ${asset.originalPath}`);
|
||||||
await this.assetRepository.updateAll([asset.id], { trashReason: AssetTrashReason.OFFLINE });
|
await this.assetRepository.updateAll([asset.id], { isOffline: true, deletedAt: new Date() });
|
||||||
await this.assetRepository.softDeleteAll([asset.id]);
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const isInPath = job.importPaths.find((path) => asset.originalPath.startsWith(path));
|
const isInPath = job.importPaths.find((path) => asset.originalPath.startsWith(path));
|
||||||
@ -539,20 +495,37 @@ export class LibraryService {
|
|||||||
return JobStatus.SUCCESS;
|
return JobStatus.SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileExists = await this.storageRepository.checkFileExists(asset.originalPath, R_OK);
|
let stat;
|
||||||
if (!fileExists) {
|
try {
|
||||||
await markOffline('Asset is no longer on disk');
|
stat = await this.storageRepository.stat(asset.originalPath);
|
||||||
|
} catch {
|
||||||
|
await markOffline('Asset is no longer on disk or is inaccessible because of permissions');
|
||||||
return JobStatus.SUCCESS;
|
return JobStatus.SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.verbose(
|
const mtime = stat.mtime;
|
||||||
`Asset is found on disk, not covered by an exclusion pattern, and is in an import path, doing nothing: ${asset.originalPath}`,
|
const isAssetModified = mtime.toISOString() !== asset.fileModifiedAt.toISOString();
|
||||||
);
|
|
||||||
|
|
||||||
|
if (asset.isOffline || isAssetModified) {
|
||||||
|
this.logger.debug(`Asset was offline or modified, updating asset record ${asset.originalPath}`);
|
||||||
|
//TODO: When we have asset status, we need to leave deletedAt as is when status is trashed
|
||||||
|
await this.assetRepository.updateAll([asset.id], {
|
||||||
|
isOffline: false,
|
||||||
|
deletedAt: null,
|
||||||
|
fileCreatedAt: mtime,
|
||||||
|
fileModifiedAt: mtime,
|
||||||
|
originalFileName: parse(asset.originalPath).base,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAssetModified) {
|
||||||
|
this.logger.debug(`Asset was modified, queuing metadata extraction for: ${asset.originalPath}`);
|
||||||
|
await this.queuePostSyncJobs(asset);
|
||||||
|
}
|
||||||
return JobStatus.SUCCESS;
|
return JobStatus.SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleQueueAssetRefresh(job: IEntityJob): Promise<JobStatus> {
|
async handleQueueSyncFiles(job: IEntityJob): Promise<JobStatus> {
|
||||||
const library = await this.repository.get(job.id);
|
const library = await this.repository.get(job.id);
|
||||||
if (!library) {
|
if (!library) {
|
||||||
this.logger.debug(`Library ${job.id} not found, skipping refresh`);
|
this.logger.debug(`Library ${job.id} not found, skipping refresh`);
|
||||||
@ -603,7 +576,7 @@ export class LibraryService {
|
|||||||
return JobStatus.SUCCESS;
|
return JobStatus.SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleQueueAssetOfflineCheck(job: IEntityJob): Promise<JobStatus> {
|
async handleQueueSyncAssets(job: IEntityJob): Promise<JobStatus> {
|
||||||
const library = await this.repository.get(job.id);
|
const library = await this.repository.get(job.id);
|
||||||
if (!library) {
|
if (!library) {
|
||||||
return JobStatus.SKIPPED;
|
return JobStatus.SKIPPED;
|
||||||
@ -621,7 +594,7 @@ export class LibraryService {
|
|||||||
this.logger.debug(`Discovered ${assetCount} asset(s) in library ${library.id}...`);
|
this.logger.debug(`Discovered ${assetCount} asset(s) in library ${library.id}...`);
|
||||||
await this.jobRepository.queueAll(
|
await this.jobRepository.queueAll(
|
||||||
assets.map((asset) => ({
|
assets.map((asset) => ({
|
||||||
name: JobName.LIBRARY_OFFLINE_CHECK,
|
name: JobName.LIBRARY_SYNC_ASSET,
|
||||||
data: { id: asset.id, importPaths: library.importPaths, exclusionPatterns: library.exclusionPatterns },
|
data: { id: asset.id, importPaths: library.importPaths, exclusionPatterns: library.exclusionPatterns },
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
@ -86,12 +86,12 @@ export class MicroservicesService {
|
|||||||
[JobName.SIDECAR_DISCOVERY]: (data) => this.metadataService.handleSidecarDiscovery(data),
|
[JobName.SIDECAR_DISCOVERY]: (data) => this.metadataService.handleSidecarDiscovery(data),
|
||||||
[JobName.SIDECAR_SYNC]: (data) => this.metadataService.handleSidecarSync(data),
|
[JobName.SIDECAR_SYNC]: (data) => this.metadataService.handleSidecarSync(data),
|
||||||
[JobName.SIDECAR_WRITE]: (data) => this.metadataService.handleSidecarWrite(data),
|
[JobName.SIDECAR_WRITE]: (data) => this.metadataService.handleSidecarWrite(data),
|
||||||
[JobName.LIBRARY_REFRESH_ASSET]: (data) => this.libraryService.handleAssetRefresh(data),
|
[JobName.LIBRARY_QUEUE_SYNC_ALL]: () => this.libraryService.handleQueueSyncAll(),
|
||||||
[JobName.LIBRARY_QUEUE_SCAN]: (data) => this.libraryService.handleQueueAssetRefresh(data),
|
[JobName.LIBRARY_QUEUE_SYNC_FILES]: (data) => this.libraryService.handleQueueSyncFiles(data), //Queues all files paths on disk
|
||||||
[JobName.LIBRARY_QUEUE_OFFLINE_CHECK]: (data) => this.libraryService.handleQueueAssetOfflineCheck(data),
|
[JobName.LIBRARY_SYNC_FILE]: (data) => this.libraryService.handleSyncFile(data), //Handles a single path on disk //Watcher calls for new files
|
||||||
|
[JobName.LIBRARY_QUEUE_SYNC_ASSETS]: (data) => this.libraryService.handleQueueSyncAssets(data), //Queues all library assets
|
||||||
|
[JobName.LIBRARY_SYNC_ASSET]: (data) => this.libraryService.handleSyncAsset(data), //Handles all library assets // Watcher calls for unlink and changed
|
||||||
[JobName.LIBRARY_DELETE]: (data) => this.libraryService.handleDeleteLibrary(data),
|
[JobName.LIBRARY_DELETE]: (data) => this.libraryService.handleDeleteLibrary(data),
|
||||||
[JobName.LIBRARY_OFFLINE_CHECK]: (data) => this.libraryService.handleAssetOfflineCheck(data),
|
|
||||||
[JobName.LIBRARY_QUEUE_SCAN_ALL]: () => this.libraryService.handleQueueAllScan(),
|
|
||||||
[JobName.LIBRARY_QUEUE_CLEANUP]: () => this.libraryService.handleQueueCleanup(),
|
[JobName.LIBRARY_QUEUE_CLEANUP]: () => this.libraryService.handleQueueCleanup(),
|
||||||
[JobName.SEND_EMAIL]: (data) => this.notificationService.handleSendEmail(data),
|
[JobName.SEND_EMAIL]: (data) => this.notificationService.handleSendEmail(data),
|
||||||
[JobName.NOTIFY_ALBUM_INVITE]: (data) => this.notificationService.handleAlbumInvite(data),
|
[JobName.NOTIFY_ALBUM_INVITE]: (data) => this.notificationService.handleAlbumInvite(data),
|
||||||
|
@ -6,7 +6,7 @@ import { TrashResponseDto } from 'src/dtos/trash.dto';
|
|||||||
import { Permission } from 'src/enum';
|
import { Permission } from 'src/enum';
|
||||||
import { IAccessRepository } from 'src/interfaces/access.interface';
|
import { IAccessRepository } from 'src/interfaces/access.interface';
|
||||||
import { IEventRepository } from 'src/interfaces/event.interface';
|
import { IEventRepository } from 'src/interfaces/event.interface';
|
||||||
import { IJobRepository, JOBS_ASSET_PAGINATION_SIZE, JobName } from 'src/interfaces/job.interface';
|
import { IJobRepository, JOBS_ASSET_PAGINATION_SIZE, JobName, JobStatus } from 'src/interfaces/job.interface';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { ITrashRepository } from 'src/interfaces/trash.interface';
|
import { ITrashRepository } from 'src/interfaces/trash.interface';
|
||||||
import { requireAccess } from 'src/utils/access';
|
import { requireAccess } from 'src/utils/access';
|
||||||
|
44
server/test/fixtures/asset.stub.ts
vendored
44
server/test/fixtures/asset.stub.ts
vendored
@ -1,4 +1,3 @@
|
|||||||
import { AssetTrashReason } from 'src/dtos/asset.dto';
|
|
||||||
import { AssetFileEntity } from 'src/entities/asset-files.entity';
|
import { AssetFileEntity } from 'src/entities/asset-files.entity';
|
||||||
import { AssetEntity } from 'src/entities/asset.entity';
|
import { AssetEntity } from 'src/entities/asset.entity';
|
||||||
import { ExifEntity } from 'src/entities/exif.entity';
|
import { ExifEntity } from 'src/entities/exif.entity';
|
||||||
@ -73,7 +72,7 @@ export const assetStub = {
|
|||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
isExternal: false,
|
isExternal: false,
|
||||||
duplicateId: null,
|
duplicateId: null,
|
||||||
trashReason: null,
|
isOffline: false,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
noWebpPath: Object.freeze<AssetEntity>({
|
noWebpPath: Object.freeze<AssetEntity>({
|
||||||
@ -111,7 +110,7 @@ export const assetStub = {
|
|||||||
} as ExifEntity,
|
} as ExifEntity,
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
duplicateId: null,
|
duplicateId: null,
|
||||||
trashReason: null,
|
isOffline: false,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
noThumbhash: Object.freeze<AssetEntity>({
|
noThumbhash: Object.freeze<AssetEntity>({
|
||||||
@ -146,7 +145,7 @@ export const assetStub = {
|
|||||||
sidecarPath: null,
|
sidecarPath: null,
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
duplicateId: null,
|
duplicateId: null,
|
||||||
trashReason: null,
|
isOffline: false,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
primaryImage: Object.freeze<AssetEntity>({
|
primaryImage: Object.freeze<AssetEntity>({
|
||||||
@ -191,7 +190,7 @@ export const assetStub = {
|
|||||||
{ id: 'stack-child-asset-2' } as AssetEntity,
|
{ id: 'stack-child-asset-2' } as AssetEntity,
|
||||||
]),
|
]),
|
||||||
duplicateId: null,
|
duplicateId: null,
|
||||||
trashReason: null,
|
isOffline: false,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
image: Object.freeze<AssetEntity>({
|
image: Object.freeze<AssetEntity>({
|
||||||
@ -231,7 +230,7 @@ export const assetStub = {
|
|||||||
exifImageWidth: 2160,
|
exifImageWidth: 2160,
|
||||||
} as ExifEntity,
|
} as ExifEntity,
|
||||||
duplicateId: null,
|
duplicateId: null,
|
||||||
trashReason: null,
|
isOffline: false,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
trashed: Object.freeze<AssetEntity>({
|
trashed: Object.freeze<AssetEntity>({
|
||||||
@ -251,7 +250,6 @@ export const assetStub = {
|
|||||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||||
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||||
deletedAt: new Date('2023-02-24T05:06:29.716Z'),
|
deletedAt: new Date('2023-02-24T05:06:29.716Z'),
|
||||||
trashReason: AssetTrashReason.DELETED,
|
|
||||||
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
|
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
|
||||||
isFavorite: false,
|
isFavorite: false,
|
||||||
isArchived: false,
|
isArchived: false,
|
||||||
@ -271,6 +269,8 @@ export const assetStub = {
|
|||||||
exifImageWidth: 2160,
|
exifImageWidth: 2160,
|
||||||
} as ExifEntity,
|
} as ExifEntity,
|
||||||
duplicateId: null,
|
duplicateId: null,
|
||||||
|
isOffline: false,
|
||||||
|
status: AssetStatus.TRASHED,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
trashedOffline: Object.freeze<AssetEntity>({
|
trashedOffline: Object.freeze<AssetEntity>({
|
||||||
@ -291,7 +291,6 @@ export const assetStub = {
|
|||||||
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
createdAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||||
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
|
||||||
deletedAt: new Date('2023-02-24T05:06:29.716Z'),
|
deletedAt: new Date('2023-02-24T05:06:29.716Z'),
|
||||||
trashReason: AssetTrashReason.OFFLINE,
|
|
||||||
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
|
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
|
||||||
isFavorite: false,
|
isFavorite: false,
|
||||||
isArchived: false,
|
isArchived: false,
|
||||||
@ -311,6 +310,7 @@ export const assetStub = {
|
|||||||
exifImageWidth: 2160,
|
exifImageWidth: 2160,
|
||||||
} as ExifEntity,
|
} as ExifEntity,
|
||||||
duplicateId: null,
|
duplicateId: null,
|
||||||
|
isOffline: true,
|
||||||
}),
|
}),
|
||||||
archived: Object.freeze<AssetEntity>({
|
archived: Object.freeze<AssetEntity>({
|
||||||
id: 'asset-id',
|
id: 'asset-id',
|
||||||
@ -349,7 +349,7 @@ export const assetStub = {
|
|||||||
exifImageWidth: 2160,
|
exifImageWidth: 2160,
|
||||||
} as ExifEntity,
|
} as ExifEntity,
|
||||||
duplicateId: null,
|
duplicateId: null,
|
||||||
trashReason: null,
|
isOffline: false,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
external: Object.freeze<AssetEntity>({
|
external: Object.freeze<AssetEntity>({
|
||||||
@ -389,7 +389,7 @@ export const assetStub = {
|
|||||||
fileSizeInByte: 5000,
|
fileSizeInByte: 5000,
|
||||||
} as ExifEntity,
|
} as ExifEntity,
|
||||||
duplicateId: null,
|
duplicateId: null,
|
||||||
trashReason: null,
|
isOffline: false,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
image1: Object.freeze<AssetEntity>({
|
image1: Object.freeze<AssetEntity>({
|
||||||
@ -427,7 +427,7 @@ export const assetStub = {
|
|||||||
fileSizeInByte: 5000,
|
fileSizeInByte: 5000,
|
||||||
} as ExifEntity,
|
} as ExifEntity,
|
||||||
duplicateId: null,
|
duplicateId: null,
|
||||||
trashReason: null,
|
isOffline: false,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
imageFrom2015: Object.freeze<AssetEntity>({
|
imageFrom2015: Object.freeze<AssetEntity>({
|
||||||
@ -465,7 +465,7 @@ export const assetStub = {
|
|||||||
} as ExifEntity,
|
} as ExifEntity,
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
duplicateId: null,
|
duplicateId: null,
|
||||||
trashReason: null,
|
isOffline: false,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
video: Object.freeze<AssetEntity>({
|
video: Object.freeze<AssetEntity>({
|
||||||
@ -505,7 +505,7 @@ export const assetStub = {
|
|||||||
} as ExifEntity,
|
} as ExifEntity,
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
duplicateId: null,
|
duplicateId: null,
|
||||||
trashReason: null,
|
isOffline: false,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
livePhotoMotionAsset: Object.freeze({
|
livePhotoMotionAsset: Object.freeze({
|
||||||
@ -643,7 +643,7 @@ export const assetStub = {
|
|||||||
} as ExifEntity,
|
} as ExifEntity,
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
duplicateId: null,
|
duplicateId: null,
|
||||||
trashReason: null,
|
isOffline: false,
|
||||||
}),
|
}),
|
||||||
sidecar: Object.freeze<AssetEntity>({
|
sidecar: Object.freeze<AssetEntity>({
|
||||||
id: 'asset-id',
|
id: 'asset-id',
|
||||||
@ -677,7 +677,7 @@ export const assetStub = {
|
|||||||
sidecarPath: '/original/path.ext.xmp',
|
sidecarPath: '/original/path.ext.xmp',
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
duplicateId: null,
|
duplicateId: null,
|
||||||
trashReason: null,
|
isOffline: false,
|
||||||
}),
|
}),
|
||||||
sidecarWithoutExt: Object.freeze<AssetEntity>({
|
sidecarWithoutExt: Object.freeze<AssetEntity>({
|
||||||
id: 'asset-id',
|
id: 'asset-id',
|
||||||
@ -711,7 +711,7 @@ export const assetStub = {
|
|||||||
sidecarPath: '/original/path.xmp',
|
sidecarPath: '/original/path.xmp',
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
duplicateId: null,
|
duplicateId: null,
|
||||||
trashReason: null,
|
isOffline: false,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
hasEncodedVideo: Object.freeze<AssetEntity>({
|
hasEncodedVideo: Object.freeze<AssetEntity>({
|
||||||
@ -749,7 +749,7 @@ export const assetStub = {
|
|||||||
} as ExifEntity,
|
} as ExifEntity,
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
duplicateId: null,
|
duplicateId: null,
|
||||||
trashReason: null,
|
isOffline: false,
|
||||||
}),
|
}),
|
||||||
missingFileExtension: Object.freeze<AssetEntity>({
|
missingFileExtension: Object.freeze<AssetEntity>({
|
||||||
id: 'asset-id',
|
id: 'asset-id',
|
||||||
@ -788,7 +788,7 @@ export const assetStub = {
|
|||||||
fileSizeInByte: 5000,
|
fileSizeInByte: 5000,
|
||||||
} as ExifEntity,
|
} as ExifEntity,
|
||||||
duplicateId: null,
|
duplicateId: null,
|
||||||
trashReason: null,
|
isOffline: false,
|
||||||
}),
|
}),
|
||||||
hasFileExtension: Object.freeze<AssetEntity>({
|
hasFileExtension: Object.freeze<AssetEntity>({
|
||||||
id: 'asset-id',
|
id: 'asset-id',
|
||||||
@ -827,7 +827,7 @@ export const assetStub = {
|
|||||||
fileSizeInByte: 5000,
|
fileSizeInByte: 5000,
|
||||||
} as ExifEntity,
|
} as ExifEntity,
|
||||||
duplicateId: null,
|
duplicateId: null,
|
||||||
trashReason: null,
|
isOffline: false,
|
||||||
}),
|
}),
|
||||||
imageDng: Object.freeze<AssetEntity>({
|
imageDng: Object.freeze<AssetEntity>({
|
||||||
id: 'asset-id',
|
id: 'asset-id',
|
||||||
@ -866,7 +866,7 @@ export const assetStub = {
|
|||||||
bitsPerSample: 14,
|
bitsPerSample: 14,
|
||||||
} as ExifEntity,
|
} as ExifEntity,
|
||||||
duplicateId: null,
|
duplicateId: null,
|
||||||
trashReason: null,
|
isOffline: false,
|
||||||
}),
|
}),
|
||||||
hasEmbedding: Object.freeze<AssetEntity>({
|
hasEmbedding: Object.freeze<AssetEntity>({
|
||||||
id: 'asset-id-embedding',
|
id: 'asset-id-embedding',
|
||||||
@ -907,7 +907,7 @@ export const assetStub = {
|
|||||||
assetId: 'asset-id',
|
assetId: 'asset-id',
|
||||||
embedding: Array.from({ length: 512 }, Math.random),
|
embedding: Array.from({ length: 512 }, Math.random),
|
||||||
},
|
},
|
||||||
trashReason: null,
|
isOffline: false,
|
||||||
}),
|
}),
|
||||||
hasDupe: Object.freeze<AssetEntity>({
|
hasDupe: Object.freeze<AssetEntity>({
|
||||||
id: 'asset-id-dupe',
|
id: 'asset-id-dupe',
|
||||||
@ -948,6 +948,6 @@ export const assetStub = {
|
|||||||
assetId: 'asset-id',
|
assetId: 'asset-id',
|
||||||
embedding: Array.from({ length: 512 }, Math.random),
|
embedding: Array.from({ length: 512 }, Math.random),
|
||||||
},
|
},
|
||||||
trashReason: null,
|
isOffline: false,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
3
server/test/fixtures/shared-link.stub.ts
vendored
3
server/test/fixtures/shared-link.stub.ts
vendored
@ -74,6 +74,7 @@ const assetResponse: AssetResponseDto = {
|
|||||||
isTrashed: false,
|
isTrashed: false,
|
||||||
libraryId: 'library-id',
|
libraryId: 'library-id',
|
||||||
hasMetadata: true,
|
hasMetadata: true,
|
||||||
|
isOffline: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const assetResponseWithoutMetadata = {
|
const assetResponseWithoutMetadata = {
|
||||||
@ -255,7 +256,7 @@ export const sharedLinkStub = {
|
|||||||
sidecarPath: null,
|
sidecarPath: null,
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
duplicateId: null,
|
duplicateId: null,
|
||||||
trashReason: null,
|
isOffline: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -42,7 +42,5 @@ export const newAssetRepositoryMock = (): Mocked<IAssetRepository> => {
|
|||||||
upsertFile: vitest.fn(),
|
upsertFile: vitest.fn(),
|
||||||
getAssetsByOriginalPath: vitest.fn(),
|
getAssetsByOriginalPath: vitest.fn(),
|
||||||
getUniqueOriginalPaths: vitest.fn(),
|
getUniqueOriginalPaths: vitest.fn(),
|
||||||
restoreAllDeleted: vitest.fn(),
|
|
||||||
restoreAllDeletedById: vitest.fn(),
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -24,7 +24,6 @@
|
|||||||
import {
|
import {
|
||||||
AssetJobName,
|
AssetJobName,
|
||||||
AssetTypeEnum,
|
AssetTypeEnum,
|
||||||
TrashReason,
|
|
||||||
type AlbumResponseDto,
|
type AlbumResponseDto,
|
||||||
type AssetResponseDto,
|
type AssetResponseDto,
|
||||||
type StackResponseDto,
|
type StackResponseDto,
|
||||||
@ -60,7 +59,7 @@
|
|||||||
export let onClose: () => void;
|
export let onClose: () => void;
|
||||||
|
|
||||||
const sharedLink = getSharedLink();
|
const sharedLink = getSharedLink();
|
||||||
$: isOffline = asset.trashReason === TrashReason.Offline;
|
$: isOffline = asset.isOffline;
|
||||||
$: isOwner = $user && asset.ownerId === $user?.id;
|
$: isOwner = $user && asset.ownerId === $user?.id;
|
||||||
$: showDownloadButton = sharedLink ? sharedLink.allowDownload : !isOffline;
|
$: showDownloadButton = sharedLink ? sharedLink.allowDownload : !isOffline;
|
||||||
// $: showEditorButton =
|
// $: showEditorButton =
|
||||||
@ -137,7 +136,7 @@
|
|||||||
{#if showDownloadButton}
|
{#if showDownloadButton}
|
||||||
<DownloadAction {asset} menuItem />
|
<DownloadAction {asset} menuItem />
|
||||||
{/if}
|
{/if}
|
||||||
{#if asset.trashReason === TrashReason.Deleted}
|
{#if asset.status === AssetStatus.TRASHED}
|
||||||
<RestoreAction {asset} {onAction} />
|
<RestoreAction {asset} {onAction} />
|
||||||
{:else}
|
{:else}
|
||||||
<AddToAlbumAction {asset} {onAction} />
|
<AddToAlbumAction {asset} {onAction} />
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
import {
|
import {
|
||||||
AssetMediaSize,
|
AssetMediaSize,
|
||||||
getAssetInfo,
|
getAssetInfo,
|
||||||
TrashReason,
|
|
||||||
updateAsset,
|
updateAsset,
|
||||||
type AlbumResponseDto,
|
type AlbumResponseDto,
|
||||||
type AssetResponseDto,
|
type AssetResponseDto,
|
||||||
@ -73,7 +72,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: isOffline = asset.trashReason === TrashReason.Offline;
|
$: isOffline = asset.isOffline;
|
||||||
$: isOwner = $user?.id === asset.ownerId;
|
$: isOwner = $user?.id === asset.ownerId;
|
||||||
|
|
||||||
const handleNewAsset = async (newAsset: AssetResponseDto) => {
|
const handleNewAsset = async (newAsset: AssetResponseDto) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user