mirror of
https://github.com/immich-app/immich.git
synced 2025-04-21 07:26:25 +02:00
feat(server): read-write external assets (#9235)
* refactor: remove isReadOnly and isExternal usages * chore: open api * fix: linting * remove mobile isReadOnly dependency --------- Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
parent
d26ac431b8
commit
5b87abb021
57 changed files with 181 additions and 603 deletions
e2e/src/api/specs
mobile
lib
entities
extensions
modules/asset_viewer/ui
routing
shared/ui/asset_grid
openapi
open-api
server
src
decorators.ts
dtos
entities
interfaces
migrations
queries
asset.repository.sqlperson.repository.sqlsearch.repository.sqlshared.link.repository.sqluser.repository.sql
repositories
services
asset-v1.service.tsasset.service.spec.tsasset.service.tslibrary.service.spec.tslibrary.service.tsmetadata.service.tsstorage-template.service.spec.tsstorage-template.service.ts
utils
test/fixtures
web/src
lib
components
asset-viewer
photos-page
utils
test-data/factories
|
@ -102,7 +102,6 @@ describe('/asset', () => {
|
|||
utils.createAsset(user1.accessToken),
|
||||
utils.createAsset(user1.accessToken, {
|
||||
isFavorite: true,
|
||||
isReadOnly: true,
|
||||
fileCreatedAt: yesterday.toISO(),
|
||||
fileModifiedAt: yesterday.toISO(),
|
||||
assetData: { filename: 'example.mp4' },
|
||||
|
|
|
@ -24,12 +24,12 @@ describe('/search', () => {
|
|||
// let assetRidge: AssetFileUploadResponseDto;
|
||||
// let assetPolemonium: AssetFileUploadResponseDto;
|
||||
// let assetWood: AssetFileUploadResponseDto;
|
||||
// let assetGlarus: AssetFileUploadResponseDto;
|
||||
let assetHeic: AssetFileUploadResponseDto;
|
||||
let assetRocks: AssetFileUploadResponseDto;
|
||||
let assetOneJpg6: AssetFileUploadResponseDto;
|
||||
let assetOneHeic6: AssetFileUploadResponseDto;
|
||||
let assetOneJpg5: AssetFileUploadResponseDto;
|
||||
let assetGlarus: AssetFileUploadResponseDto;
|
||||
let assetSprings: AssetFileUploadResponseDto;
|
||||
let assetLast: AssetFileUploadResponseDto;
|
||||
let cities: string[];
|
||||
|
@ -52,11 +52,12 @@ describe('/search', () => {
|
|||
{ filename: '/formats/motionphoto/Samsung One UI 6.jpg' },
|
||||
{ filename: '/formats/motionphoto/Samsung One UI 6.heic' },
|
||||
{ filename: '/formats/motionphoto/Samsung One UI 5.jpg' },
|
||||
{ filename: '/formats/raw/Nikon/D80/glarus.nef', dto: { isReadOnly: true } },
|
||||
|
||||
{ filename: '/metadata/gps-position/thompson-springs.jpg', dto: { isArchived: true } },
|
||||
|
||||
// used for search suggestions
|
||||
{ filename: '/formats/png/density_plot.png' },
|
||||
{ filename: '/formats/raw/Nikon/D80/glarus.nef' },
|
||||
{ filename: '/formats/raw/Nikon/D700/philadelphia.nef' },
|
||||
{ filename: '/albums/nature/orychophragmus_violaceus.jpg' },
|
||||
{ filename: '/albums/nature/tanners_ridge.jpg' },
|
||||
|
@ -93,9 +94,9 @@ describe('/search', () => {
|
|||
{ latitude: 23.133_02, longitude: -82.383_04 }, // havana
|
||||
{ latitude: 41.694_11, longitude: 44.833_68 }, // tbilisi
|
||||
{ latitude: 31.222_22, longitude: 121.458_06 }, // shanghai
|
||||
{ latitude: 47.040_57, longitude: 9.068_04 }, // glarus
|
||||
{ latitude: 38.9711, longitude: -109.7137 }, // thompson springs
|
||||
{ latitude: 40.714_27, longitude: -74.005_97 }, // new york
|
||||
{ latitude: 47.040_57, longitude: 9.068_04 }, // glarus
|
||||
{ latitude: 32.771_52, longitude: -89.116_73 }, // philadelphia
|
||||
{ latitude: 31.634_16, longitude: -7.999_94 }, // marrakesh
|
||||
{ latitude: 38.523_735_4, longitude: -78.488_619_4 }, // tanners ridge
|
||||
|
@ -123,9 +124,9 @@ describe('/search', () => {
|
|||
assetOneJpg6,
|
||||
assetOneHeic6,
|
||||
assetOneJpg5,
|
||||
assetGlarus,
|
||||
assetSprings,
|
||||
assetDensity,
|
||||
// assetGlarus,
|
||||
// assetPhiladelphia,
|
||||
// assetOrychophragmus,
|
||||
// assetRidge,
|
||||
|
@ -190,16 +191,7 @@ describe('/search', () => {
|
|||
dto: { size: -1.5 },
|
||||
expected: ['size must not be less than 1', 'size must be an integer number'],
|
||||
},
|
||||
...[
|
||||
'isArchived',
|
||||
'isFavorite',
|
||||
'isReadOnly',
|
||||
'isExternal',
|
||||
'isEncoded',
|
||||
'isMotion',
|
||||
'isOffline',
|
||||
'isVisible',
|
||||
].map((value) => ({
|
||||
...['isArchived', 'isFavorite', 'isEncoded', 'isMotion', 'isOffline', 'isVisible'].map((value) => ({
|
||||
should: `should reject ${value} not a boolean`,
|
||||
dto: { [value]: 'immich' },
|
||||
expected: [`${value} must be a boolean value`],
|
||||
|
@ -255,14 +247,6 @@ describe('/search', () => {
|
|||
should: 'should search by isArchived (false)',
|
||||
deferred: () => ({ dto: { size: 1, isArchived: false }, assets: [assetLast] }),
|
||||
},
|
||||
{
|
||||
should: 'should search by isReadOnly (true)',
|
||||
deferred: () => ({ dto: { isReadOnly: true }, assets: [assetGlarus] }),
|
||||
},
|
||||
{
|
||||
should: 'should search by isReadOnly (false)',
|
||||
deferred: () => ({ dto: { size: 1, isReadOnly: false }, assets: [assetLast] }),
|
||||
},
|
||||
{
|
||||
should: 'should search by type (image)',
|
||||
deferred: () => ({ dto: { size: 1, type: 'IMAGE' }, assets: [assetLast] }),
|
||||
|
|
|
@ -34,7 +34,6 @@ describe('/timeline', () => {
|
|||
utils.createAsset(user.accessToken),
|
||||
utils.createAsset(user.accessToken, {
|
||||
isFavorite: true,
|
||||
isReadOnly: true,
|
||||
fileCreatedAt: yesterday.toISO(),
|
||||
fileModifiedAt: yesterday.toISO(),
|
||||
assetData: { filename: 'example.mp4' },
|
||||
|
|
|
@ -32,7 +32,6 @@ class Asset {
|
|||
isFavorite = remote.isFavorite,
|
||||
isArchived = remote.isArchived,
|
||||
isTrashed = remote.isTrashed,
|
||||
isReadOnly = remote.isReadOnly,
|
||||
isOffline = remote.isOffline,
|
||||
// workaround to nullify stackParentId for the parent asset until we refactor the mobile app
|
||||
// stack handling to properly handle it
|
||||
|
@ -55,7 +54,6 @@ class Asset {
|
|||
isFavorite = local.isFavorite,
|
||||
isArchived = false,
|
||||
isTrashed = false,
|
||||
isReadOnly = false,
|
||||
isOffline = false,
|
||||
stackCount = 0,
|
||||
fileCreatedAt = local.createDateTime {
|
||||
|
@ -90,7 +88,6 @@ class Asset {
|
|||
this.isTrashed = false,
|
||||
this.stackParentId,
|
||||
this.stackCount = 0,
|
||||
this.isReadOnly = false,
|
||||
this.isOffline = false,
|
||||
this.thumbhash,
|
||||
});
|
||||
|
@ -161,8 +158,6 @@ class Asset {
|
|||
|
||||
bool isTrashed;
|
||||
|
||||
bool isReadOnly;
|
||||
|
||||
bool isOffline;
|
||||
|
||||
@ignore
|
||||
|
@ -278,7 +273,6 @@ class Asset {
|
|||
isFavorite != a.isFavorite ||
|
||||
isArchived != a.isArchived ||
|
||||
isTrashed != a.isTrashed ||
|
||||
isReadOnly != a.isReadOnly ||
|
||||
isOffline != a.isOffline ||
|
||||
a.exifInfo?.latitude != exifInfo?.latitude ||
|
||||
a.exifInfo?.longitude != exifInfo?.longitude ||
|
||||
|
@ -324,7 +318,6 @@ class Asset {
|
|||
isFavorite: isFavorite,
|
||||
isArchived: isArchived,
|
||||
isTrashed: isTrashed,
|
||||
isReadOnly: isReadOnly,
|
||||
isOffline: isOffline,
|
||||
);
|
||||
}
|
||||
|
@ -345,7 +338,6 @@ class Asset {
|
|||
isFavorite: a.isFavorite,
|
||||
isArchived: a.isArchived,
|
||||
isTrashed: a.isTrashed,
|
||||
isReadOnly: a.isReadOnly,
|
||||
isOffline: a.isOffline,
|
||||
exifInfo: a.exifInfo?.copyWith(id: id) ?? exifInfo,
|
||||
thumbhash: a.thumbhash,
|
||||
|
@ -380,7 +372,6 @@ class Asset {
|
|||
bool? isFavorite,
|
||||
bool? isArchived,
|
||||
bool? isTrashed,
|
||||
bool? isReadOnly,
|
||||
bool? isOffline,
|
||||
ExifInfo? exifInfo,
|
||||
String? stackParentId,
|
||||
|
@ -405,7 +396,6 @@ class Asset {
|
|||
isFavorite: isFavorite ?? this.isFavorite,
|
||||
isArchived: isArchived ?? this.isArchived,
|
||||
isTrashed: isTrashed ?? this.isTrashed,
|
||||
isReadOnly: isReadOnly ?? this.isReadOnly,
|
||||
isOffline: isOffline ?? this.isOffline,
|
||||
exifInfo: exifInfo ?? this.exifInfo,
|
||||
stackParentId: stackParentId ?? this.stackParentId,
|
||||
|
@ -470,7 +460,6 @@ class Asset {
|
|||
"height": ${height ?? "N/A"},
|
||||
"isArchived": $isArchived,
|
||||
"isTrashed": $isTrashed,
|
||||
"isReadOnly": $isReadOnly,
|
||||
"isOffline": $isOffline,
|
||||
}""";
|
||||
}
|
||||
|
|
135
mobile/lib/entities/asset.entity.g.dart
generated
135
mobile/lib/entities/asset.entity.g.dart
generated
|
@ -62,64 +62,59 @@ const AssetSchema = CollectionSchema(
|
|||
name: r'isOffline',
|
||||
type: IsarType.bool,
|
||||
),
|
||||
r'isReadOnly': PropertySchema(
|
||||
id: 9,
|
||||
name: r'isReadOnly',
|
||||
type: IsarType.bool,
|
||||
),
|
||||
r'isTrashed': PropertySchema(
|
||||
id: 10,
|
||||
id: 9,
|
||||
name: r'isTrashed',
|
||||
type: IsarType.bool,
|
||||
),
|
||||
r'livePhotoVideoId': PropertySchema(
|
||||
id: 11,
|
||||
id: 10,
|
||||
name: r'livePhotoVideoId',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'localId': PropertySchema(
|
||||
id: 12,
|
||||
id: 11,
|
||||
name: r'localId',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'ownerId': PropertySchema(
|
||||
id: 13,
|
||||
id: 12,
|
||||
name: r'ownerId',
|
||||
type: IsarType.long,
|
||||
),
|
||||
r'remoteId': PropertySchema(
|
||||
id: 14,
|
||||
id: 13,
|
||||
name: r'remoteId',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'stackCount': PropertySchema(
|
||||
id: 15,
|
||||
id: 14,
|
||||
name: r'stackCount',
|
||||
type: IsarType.long,
|
||||
),
|
||||
r'stackParentId': PropertySchema(
|
||||
id: 16,
|
||||
id: 15,
|
||||
name: r'stackParentId',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'thumbhash': PropertySchema(
|
||||
id: 17,
|
||||
id: 16,
|
||||
name: r'thumbhash',
|
||||
type: IsarType.string,
|
||||
),
|
||||
r'type': PropertySchema(
|
||||
id: 18,
|
||||
id: 17,
|
||||
name: r'type',
|
||||
type: IsarType.byte,
|
||||
enumMap: _AssettypeEnumValueMap,
|
||||
),
|
||||
r'updatedAt': PropertySchema(
|
||||
id: 19,
|
||||
id: 18,
|
||||
name: r'updatedAt',
|
||||
type: IsarType.dateTime,
|
||||
),
|
||||
r'width': PropertySchema(
|
||||
id: 20,
|
||||
id: 19,
|
||||
name: r'width',
|
||||
type: IsarType.int,
|
||||
)
|
||||
|
@ -239,18 +234,17 @@ void _assetSerialize(
|
|||
writer.writeBool(offsets[6], object.isArchived);
|
||||
writer.writeBool(offsets[7], object.isFavorite);
|
||||
writer.writeBool(offsets[8], object.isOffline);
|
||||
writer.writeBool(offsets[9], object.isReadOnly);
|
||||
writer.writeBool(offsets[10], object.isTrashed);
|
||||
writer.writeString(offsets[11], object.livePhotoVideoId);
|
||||
writer.writeString(offsets[12], object.localId);
|
||||
writer.writeLong(offsets[13], object.ownerId);
|
||||
writer.writeString(offsets[14], object.remoteId);
|
||||
writer.writeLong(offsets[15], object.stackCount);
|
||||
writer.writeString(offsets[16], object.stackParentId);
|
||||
writer.writeString(offsets[17], object.thumbhash);
|
||||
writer.writeByte(offsets[18], object.type.index);
|
||||
writer.writeDateTime(offsets[19], object.updatedAt);
|
||||
writer.writeInt(offsets[20], object.width);
|
||||
writer.writeBool(offsets[9], object.isTrashed);
|
||||
writer.writeString(offsets[10], object.livePhotoVideoId);
|
||||
writer.writeString(offsets[11], object.localId);
|
||||
writer.writeLong(offsets[12], object.ownerId);
|
||||
writer.writeString(offsets[13], object.remoteId);
|
||||
writer.writeLong(offsets[14], object.stackCount);
|
||||
writer.writeString(offsets[15], object.stackParentId);
|
||||
writer.writeString(offsets[16], object.thumbhash);
|
||||
writer.writeByte(offsets[17], object.type.index);
|
||||
writer.writeDateTime(offsets[18], object.updatedAt);
|
||||
writer.writeInt(offsets[19], object.width);
|
||||
}
|
||||
|
||||
Asset _assetDeserialize(
|
||||
|
@ -270,19 +264,18 @@ Asset _assetDeserialize(
|
|||
isArchived: reader.readBoolOrNull(offsets[6]) ?? false,
|
||||
isFavorite: reader.readBoolOrNull(offsets[7]) ?? false,
|
||||
isOffline: reader.readBoolOrNull(offsets[8]) ?? false,
|
||||
isReadOnly: reader.readBoolOrNull(offsets[9]) ?? false,
|
||||
isTrashed: reader.readBoolOrNull(offsets[10]) ?? false,
|
||||
livePhotoVideoId: reader.readStringOrNull(offsets[11]),
|
||||
localId: reader.readStringOrNull(offsets[12]),
|
||||
ownerId: reader.readLong(offsets[13]),
|
||||
remoteId: reader.readStringOrNull(offsets[14]),
|
||||
stackCount: reader.readLongOrNull(offsets[15]),
|
||||
stackParentId: reader.readStringOrNull(offsets[16]),
|
||||
thumbhash: reader.readStringOrNull(offsets[17]),
|
||||
type: _AssettypeValueEnumMap[reader.readByteOrNull(offsets[18])] ??
|
||||
isTrashed: reader.readBoolOrNull(offsets[9]) ?? false,
|
||||
livePhotoVideoId: reader.readStringOrNull(offsets[10]),
|
||||
localId: reader.readStringOrNull(offsets[11]),
|
||||
ownerId: reader.readLong(offsets[12]),
|
||||
remoteId: reader.readStringOrNull(offsets[13]),
|
||||
stackCount: reader.readLongOrNull(offsets[14]),
|
||||
stackParentId: reader.readStringOrNull(offsets[15]),
|
||||
thumbhash: reader.readStringOrNull(offsets[16]),
|
||||
type: _AssettypeValueEnumMap[reader.readByteOrNull(offsets[17])] ??
|
||||
AssetType.other,
|
||||
updatedAt: reader.readDateTime(offsets[19]),
|
||||
width: reader.readIntOrNull(offsets[20]),
|
||||
updatedAt: reader.readDateTime(offsets[18]),
|
||||
width: reader.readIntOrNull(offsets[19]),
|
||||
);
|
||||
return object;
|
||||
}
|
||||
|
@ -315,27 +308,25 @@ P _assetDeserializeProp<P>(
|
|||
case 9:
|
||||
return (reader.readBoolOrNull(offset) ?? false) as P;
|
||||
case 10:
|
||||
return (reader.readBoolOrNull(offset) ?? false) as P;
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
case 11:
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
case 12:
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
case 13:
|
||||
return (reader.readLong(offset)) as P;
|
||||
case 14:
|
||||
case 13:
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
case 15:
|
||||
case 14:
|
||||
return (reader.readLongOrNull(offset)) as P;
|
||||
case 15:
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
case 16:
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
case 17:
|
||||
return (reader.readStringOrNull(offset)) as P;
|
||||
case 18:
|
||||
return (_AssettypeValueEnumMap[reader.readByteOrNull(offset)] ??
|
||||
AssetType.other) as P;
|
||||
case 19:
|
||||
case 18:
|
||||
return (reader.readDateTime(offset)) as P;
|
||||
case 20:
|
||||
case 19:
|
||||
return (reader.readIntOrNull(offset)) as P;
|
||||
default:
|
||||
throw IsarError('Unknown property with id $propertyId');
|
||||
|
@ -1366,16 +1357,6 @@ extension AssetQueryFilter on QueryBuilder<Asset, Asset, QFilterCondition> {
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Asset, Asset, QAfterFilterCondition> isReadOnlyEqualTo(
|
||||
bool value) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'isReadOnly',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Asset, Asset, QAfterFilterCondition> isTrashedEqualTo(
|
||||
bool value) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
|
@ -2527,18 +2508,6 @@ extension AssetQuerySortBy on QueryBuilder<Asset, Asset, QSortBy> {
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Asset, Asset, QAfterSortBy> sortByIsReadOnly() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'isReadOnly', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Asset, Asset, QAfterSortBy> sortByIsReadOnlyDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'isReadOnly', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Asset, Asset, QAfterSortBy> sortByIsTrashed() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'isTrashed', Sort.asc);
|
||||
|
@ -2793,18 +2762,6 @@ extension AssetQuerySortThenBy on QueryBuilder<Asset, Asset, QSortThenBy> {
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Asset, Asset, QAfterSortBy> thenByIsReadOnly() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'isReadOnly', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Asset, Asset, QAfterSortBy> thenByIsReadOnlyDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'isReadOnly', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Asset, Asset, QAfterSortBy> thenByIsTrashed() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'isTrashed', Sort.asc);
|
||||
|
@ -2995,12 +2952,6 @@ extension AssetQueryWhereDistinct on QueryBuilder<Asset, Asset, QDistinct> {
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Asset, Asset, QDistinct> distinctByIsReadOnly() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'isReadOnly');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Asset, Asset, QDistinct> distinctByIsTrashed() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'isTrashed');
|
||||
|
@ -3136,12 +3087,6 @@ extension AssetQueryProperty on QueryBuilder<Asset, Asset, QQueryProperty> {
|
|||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Asset, bool, QQueryOperations> isReadOnlyProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'isReadOnly');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Asset, bool, QQueryOperations> isTrashedProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'isTrashed');
|
||||
|
|
|
@ -71,19 +71,6 @@ extension AssetListExtension on Iterable<Asset> {
|
|||
return this;
|
||||
}
|
||||
|
||||
/// Returns the assets that are present on a file system which has write permission
|
||||
/// This filters out assets on readOnly external library to which we cannot perform any write operation
|
||||
Iterable<Asset> writableOnly({
|
||||
void Function()? errorCallback,
|
||||
}) {
|
||||
final bool onlyWritable = every((e) => !e.isReadOnly);
|
||||
if (!onlyWritable) {
|
||||
if (errorCallback != null) errorCallback();
|
||||
return where((a) => !a.isReadOnly);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Filters out offline assets and returns those that are still accessible by the Immich server
|
||||
Iterable<Asset> nonOfflineOnly({
|
||||
void Function()? errorCallback,
|
||||
|
|
|
@ -102,16 +102,6 @@ class BottomGalleryBar extends ConsumerWidget {
|
|||
}
|
||||
|
||||
void handleDelete() async {
|
||||
// Cannot delete readOnly / external assets. They are handled through library offline jobs
|
||||
if (asset.isReadOnly) {
|
||||
ImmichToast.show(
|
||||
durationInSecond: 1,
|
||||
context: context,
|
||||
msg: 'asset_action_delete_err_read_only'.tr(),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
return;
|
||||
}
|
||||
Future<bool> onDelete(bool force) async {
|
||||
final isDeleted = await ref.read(assetProvider.notifier).deleteAssets(
|
||||
{asset},
|
||||
|
|
|
@ -42,7 +42,7 @@ class ExifBottomSheet extends HookConsumerWidget {
|
|||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
if (asset.isRemote && !asset.isReadOnly)
|
||||
if (asset.isRemote)
|
||||
IconButton(
|
||||
onPressed: () => handleEditDateTime(
|
||||
ref,
|
||||
|
|
|
@ -24,7 +24,7 @@ class ExifLocation extends StatelessWidget {
|
|||
final hasCoordinates = exifInfo?.hasCoordinates ?? false;
|
||||
// Guard no lat/lng
|
||||
if (!hasCoordinates) {
|
||||
return asset.isRemote && !asset.isReadOnly
|
||||
return asset.isRemote
|
||||
? ListTile(
|
||||
minLeadingWidth: 0,
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
|
@ -57,7 +57,7 @@ class ExifLocation extends StatelessWidget {
|
|||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
).tr(),
|
||||
if (asset.isRemote && !asset.isReadOnly)
|
||||
if (asset.isRemote)
|
||||
IconButton(
|
||||
onPressed: editLocation,
|
||||
icon: const Icon(Icons.edit_outlined),
|
||||
|
|
|
@ -63,6 +63,12 @@ abstract class _$AppRouter extends RootStackRouter {
|
|||
child: const AllPeoplePage(),
|
||||
);
|
||||
},
|
||||
AllPlacesRoute.name: (routeData) {
|
||||
return AutoRoutePage<dynamic>(
|
||||
routeData: routeData,
|
||||
child: const AllPlacesPage(),
|
||||
);
|
||||
},
|
||||
AllVideosRoute.name: (routeData) {
|
||||
return AutoRoutePage<dynamic>(
|
||||
routeData: routeData,
|
||||
|
@ -138,12 +144,6 @@ abstract class _$AppRouter extends RootStackRouter {
|
|||
),
|
||||
);
|
||||
},
|
||||
AllPlacesRoute.name: (routeData) {
|
||||
return AutoRoutePage<dynamic>(
|
||||
routeData: routeData,
|
||||
child: const AllPlacesPage(),
|
||||
);
|
||||
},
|
||||
FailedBackupStatusRoute.name: (routeData) {
|
||||
return AutoRoutePage<dynamic>(
|
||||
routeData: routeData,
|
||||
|
@ -525,6 +525,20 @@ class AllPeopleRoute extends PageRouteInfo<void> {
|
|||
static const PageInfo<void> page = PageInfo<void>(name);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [AllPlacesPage]
|
||||
class AllPlacesRoute extends PageRouteInfo<void> {
|
||||
const AllPlacesRoute({List<PageRouteInfo>? children})
|
||||
: super(
|
||||
AllPlacesRoute.name,
|
||||
initialChildren: children,
|
||||
);
|
||||
|
||||
static const String name = 'AllPlacesRoute';
|
||||
|
||||
static const PageInfo<void> page = PageInfo<void>(name);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [AllVideosPage]
|
||||
class AllVideosRoute extends PageRouteInfo<void> {
|
||||
|
@ -752,20 +766,6 @@ class CreateAlbumRouteArgs {
|
|||
}
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [AllPlacesPage]
|
||||
class AllPlacesRoute extends PageRouteInfo<void> {
|
||||
const AllPlacesRoute({List<PageRouteInfo>? children})
|
||||
: super(
|
||||
AllPlacesRoute.name,
|
||||
initialChildren: children,
|
||||
);
|
||||
|
||||
static const String name = 'CuratedLocationRoute';
|
||||
|
||||
static const PageInfo<void> page = PageInfo<void>(name);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [FailedBackupStatusPage]
|
||||
class FailedBackupStatusRoute extends PageRouteInfo<void> {
|
||||
|
|
|
@ -184,11 +184,6 @@ class MultiselectGrid extends HookConsumerWidget {
|
|||
currentUser,
|
||||
errorCallback: errorBuilder('home_page_delete_err_partner'.tr()),
|
||||
)
|
||||
// Cannot delete readOnly / external assets. They are handled through library offline jobs
|
||||
.writableOnly(
|
||||
errorCallback:
|
||||
errorBuilder('asset_action_delete_err_read_only'.tr()),
|
||||
)
|
||||
.toList();
|
||||
final isDeleted = await ref
|
||||
.read(assetProvider.notifier)
|
||||
|
@ -238,13 +233,7 @@ class MultiselectGrid extends HookConsumerWidget {
|
|||
final toDelete = ownedRemoteSelection(
|
||||
localErrorMessage: 'home_page_delete_remote_err_local'.tr(),
|
||||
ownerErrorMessage: 'home_page_delete_err_partner'.tr(),
|
||||
)
|
||||
// Cannot delete readOnly / external assets. They are handled through library offline jobs
|
||||
.writableOnly(
|
||||
errorCallback:
|
||||
errorBuilder('asset_action_delete_err_read_only'.tr()),
|
||||
)
|
||||
.toList();
|
||||
).toList();
|
||||
|
||||
final isDeleted = await ref
|
||||
.read(assetProvider.notifier)
|
||||
|
@ -372,12 +361,8 @@ class MultiselectGrid extends HookConsumerWidget {
|
|||
final remoteAssets = ownedRemoteSelection(
|
||||
localErrorMessage: 'home_page_favorite_err_local'.tr(),
|
||||
ownerErrorMessage: 'home_page_favorite_err_partner'.tr(),
|
||||
).writableOnly(
|
||||
// Assume readOnly assets to be present in a read-only mount. So do not write sidecar
|
||||
errorCallback: errorBuilder(
|
||||
'multiselect_grid_edit_date_time_err_read_only'.tr(),
|
||||
),
|
||||
);
|
||||
|
||||
if (remoteAssets.isNotEmpty) {
|
||||
handleEditDateTime(ref, context, remoteAssets.toList());
|
||||
}
|
||||
|
@ -391,12 +376,8 @@ class MultiselectGrid extends HookConsumerWidget {
|
|||
final remoteAssets = ownedRemoteSelection(
|
||||
localErrorMessage: 'home_page_favorite_err_local'.tr(),
|
||||
ownerErrorMessage: 'home_page_favorite_err_partner'.tr(),
|
||||
).writableOnly(
|
||||
// Assume readOnly assets to be present in a read-only mount. So do not write sidecar
|
||||
errorCallback: errorBuilder(
|
||||
'multiselect_grid_edit_gps_err_read_only'.tr(),
|
||||
),
|
||||
);
|
||||
|
||||
if (remoteAssets.isNotEmpty) {
|
||||
handleEditLocation(ref, context, remoteAssets.toList());
|
||||
}
|
||||
|
|
6
mobile/openapi/doc/AssetApi.md
generated
6
mobile/openapi/doc/AssetApi.md
generated
|
@ -955,7 +955,7 @@ void (empty response body)
|
|||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **uploadFile**
|
||||
> AssetFileUploadResponseDto uploadFile(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, key, xImmichChecksum, duration, isArchived, isFavorite, isOffline, isReadOnly, isVisible, libraryId, livePhotoData, sidecarData)
|
||||
> AssetFileUploadResponseDto uploadFile(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, key, xImmichChecksum, duration, isArchived, isFavorite, isOffline, isVisible, libraryId, livePhotoData, sidecarData)
|
||||
|
||||
|
||||
|
||||
|
@ -989,14 +989,13 @@ final duration = duration_example; // String |
|
|||
final isArchived = true; // bool |
|
||||
final isFavorite = true; // bool |
|
||||
final isOffline = true; // bool |
|
||||
final isReadOnly = true; // bool |
|
||||
final isVisible = true; // bool |
|
||||
final libraryId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
|
||||
final livePhotoData = BINARY_DATA_HERE; // MultipartFile |
|
||||
final sidecarData = BINARY_DATA_HERE; // MultipartFile |
|
||||
|
||||
try {
|
||||
final result = api_instance.uploadFile(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, key, xImmichChecksum, duration, isArchived, isFavorite, isOffline, isReadOnly, isVisible, libraryId, livePhotoData, sidecarData);
|
||||
final result = api_instance.uploadFile(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, key, xImmichChecksum, duration, isArchived, isFavorite, isOffline, isVisible, libraryId, livePhotoData, sidecarData);
|
||||
print(result);
|
||||
} catch (e) {
|
||||
print('Exception when calling AssetApi->uploadFile: $e\n');
|
||||
|
@ -1018,7 +1017,6 @@ Name | Type | Description | Notes
|
|||
**isArchived** | **bool**| | [optional]
|
||||
**isFavorite** | **bool**| | [optional]
|
||||
**isOffline** | **bool**| | [optional]
|
||||
**isReadOnly** | **bool**| | [optional]
|
||||
**isVisible** | **bool**| | [optional]
|
||||
**libraryId** | **String**| | [optional]
|
||||
**livePhotoData** | **MultipartFile**| | [optional]
|
||||
|
|
4
mobile/openapi/doc/AssetResponseDto.md
generated
4
mobile/openapi/doc/AssetResponseDto.md
generated
|
@ -18,10 +18,10 @@ Name | Type | Description | Notes
|
|||
**hasMetadata** | **bool** | |
|
||||
**id** | **String** | |
|
||||
**isArchived** | **bool** | |
|
||||
**isExternal** | **bool** | |
|
||||
**isExternal** | **bool** | This property was deprecated in v1.104.0 | [optional]
|
||||
**isFavorite** | **bool** | |
|
||||
**isOffline** | **bool** | |
|
||||
**isReadOnly** | **bool** | |
|
||||
**isReadOnly** | **bool** | This property was deprecated in v1.104.0 | [optional]
|
||||
**isTrashed** | **bool** | |
|
||||
**libraryId** | **String** | |
|
||||
**livePhotoVideoId** | **String** | | [optional]
|
||||
|
|
2
mobile/openapi/doc/MetadataSearchDto.md
generated
2
mobile/openapi/doc/MetadataSearchDto.md
generated
|
@ -19,12 +19,10 @@ Name | Type | Description | Notes
|
|||
**id** | **String** | | [optional]
|
||||
**isArchived** | **bool** | | [optional]
|
||||
**isEncoded** | **bool** | | [optional]
|
||||
**isExternal** | **bool** | | [optional]
|
||||
**isFavorite** | **bool** | | [optional]
|
||||
**isMotion** | **bool** | | [optional]
|
||||
**isNotInAlbum** | **bool** | | [optional]
|
||||
**isOffline** | **bool** | | [optional]
|
||||
**isReadOnly** | **bool** | | [optional]
|
||||
**isVisible** | **bool** | | [optional]
|
||||
**lensModel** | **String** | | [optional]
|
||||
**libraryId** | **String** | | [optional]
|
||||
|
|
2
mobile/openapi/doc/SmartSearchDto.md
generated
2
mobile/openapi/doc/SmartSearchDto.md
generated
|
@ -15,12 +15,10 @@ Name | Type | Description | Notes
|
|||
**deviceId** | **String** | | [optional]
|
||||
**isArchived** | **bool** | | [optional]
|
||||
**isEncoded** | **bool** | | [optional]
|
||||
**isExternal** | **bool** | | [optional]
|
||||
**isFavorite** | **bool** | | [optional]
|
||||
**isMotion** | **bool** | | [optional]
|
||||
**isNotInAlbum** | **bool** | | [optional]
|
||||
**isOffline** | **bool** | | [optional]
|
||||
**isReadOnly** | **bool** | | [optional]
|
||||
**isVisible** | **bool** | | [optional]
|
||||
**lensModel** | **String** | | [optional]
|
||||
**libraryId** | **String** | | [optional]
|
||||
|
|
14
mobile/openapi/lib/api/asset_api.dart
generated
14
mobile/openapi/lib/api/asset_api.dart
generated
|
@ -968,8 +968,6 @@ class AssetApi {
|
|||
///
|
||||
/// * [bool] isOffline:
|
||||
///
|
||||
/// * [bool] isReadOnly:
|
||||
///
|
||||
/// * [bool] isVisible:
|
||||
///
|
||||
/// * [String] libraryId:
|
||||
|
@ -977,7 +975,7 @@ class AssetApi {
|
|||
/// * [MultipartFile] livePhotoData:
|
||||
///
|
||||
/// * [MultipartFile] sidecarData:
|
||||
Future<Response> uploadFileWithHttpInfo(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? xImmichChecksum, String? duration, bool? isArchived, bool? isFavorite, bool? isOffline, bool? isReadOnly, bool? isVisible, String? libraryId, MultipartFile? livePhotoData, MultipartFile? sidecarData, }) async {
|
||||
Future<Response> uploadFileWithHttpInfo(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? xImmichChecksum, String? duration, bool? isArchived, bool? isFavorite, bool? isOffline, bool? isVisible, String? libraryId, MultipartFile? livePhotoData, MultipartFile? sidecarData, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/asset/upload';
|
||||
|
||||
|
@ -1037,10 +1035,6 @@ class AssetApi {
|
|||
hasFields = true;
|
||||
mp.fields[r'isOffline'] = parameterToString(isOffline);
|
||||
}
|
||||
if (isReadOnly != null) {
|
||||
hasFields = true;
|
||||
mp.fields[r'isReadOnly'] = parameterToString(isReadOnly);
|
||||
}
|
||||
if (isVisible != null) {
|
||||
hasFields = true;
|
||||
mp.fields[r'isVisible'] = parameterToString(isVisible);
|
||||
|
@ -1099,8 +1093,6 @@ class AssetApi {
|
|||
///
|
||||
/// * [bool] isOffline:
|
||||
///
|
||||
/// * [bool] isReadOnly:
|
||||
///
|
||||
/// * [bool] isVisible:
|
||||
///
|
||||
/// * [String] libraryId:
|
||||
|
@ -1108,8 +1100,8 @@ class AssetApi {
|
|||
/// * [MultipartFile] livePhotoData:
|
||||
///
|
||||
/// * [MultipartFile] sidecarData:
|
||||
Future<AssetFileUploadResponseDto?> uploadFile(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? xImmichChecksum, String? duration, bool? isArchived, bool? isFavorite, bool? isOffline, bool? isReadOnly, bool? isVisible, String? libraryId, MultipartFile? livePhotoData, MultipartFile? sidecarData, }) async {
|
||||
final response = await uploadFileWithHttpInfo(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, key: key, xImmichChecksum: xImmichChecksum, duration: duration, isArchived: isArchived, isFavorite: isFavorite, isOffline: isOffline, isReadOnly: isReadOnly, isVisible: isVisible, libraryId: libraryId, livePhotoData: livePhotoData, sidecarData: sidecarData, );
|
||||
Future<AssetFileUploadResponseDto?> uploadFile(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? xImmichChecksum, String? duration, bool? isArchived, bool? isFavorite, bool? isOffline, bool? isVisible, String? libraryId, MultipartFile? livePhotoData, MultipartFile? sidecarData, }) async {
|
||||
final response = await uploadFileWithHttpInfo(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, key: key, xImmichChecksum: xImmichChecksum, duration: duration, isArchived: isArchived, isFavorite: isFavorite, isOffline: isOffline, isVisible: isVisible, libraryId: libraryId, livePhotoData: livePhotoData, sidecarData: sidecarData, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
|
|
40
mobile/openapi/lib/model/asset_response_dto.dart
generated
40
mobile/openapi/lib/model/asset_response_dto.dart
generated
|
@ -23,10 +23,10 @@ class AssetResponseDto {
|
|||
required this.hasMetadata,
|
||||
required this.id,
|
||||
required this.isArchived,
|
||||
required this.isExternal,
|
||||
this.isExternal,
|
||||
required this.isFavorite,
|
||||
required this.isOffline,
|
||||
required this.isReadOnly,
|
||||
this.isReadOnly,
|
||||
required this.isTrashed,
|
||||
required this.libraryId,
|
||||
this.livePhotoVideoId,
|
||||
|
@ -74,13 +74,27 @@ class AssetResponseDto {
|
|||
|
||||
bool isArchived;
|
||||
|
||||
bool isExternal;
|
||||
/// This property was deprecated in v1.104.0
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
bool? isExternal;
|
||||
|
||||
bool isFavorite;
|
||||
|
||||
bool isOffline;
|
||||
|
||||
bool isReadOnly;
|
||||
/// This property was deprecated in v1.104.0
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
bool? isReadOnly;
|
||||
|
||||
bool isTrashed;
|
||||
|
||||
|
@ -178,10 +192,10 @@ class AssetResponseDto {
|
|||
(hasMetadata.hashCode) +
|
||||
(id.hashCode) +
|
||||
(isArchived.hashCode) +
|
||||
(isExternal.hashCode) +
|
||||
(isExternal == null ? 0 : isExternal!.hashCode) +
|
||||
(isFavorite.hashCode) +
|
||||
(isOffline.hashCode) +
|
||||
(isReadOnly.hashCode) +
|
||||
(isReadOnly == null ? 0 : isReadOnly!.hashCode) +
|
||||
(isTrashed.hashCode) +
|
||||
(libraryId.hashCode) +
|
||||
(livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) +
|
||||
|
@ -220,10 +234,18 @@ class AssetResponseDto {
|
|||
json[r'hasMetadata'] = this.hasMetadata;
|
||||
json[r'id'] = this.id;
|
||||
json[r'isArchived'] = this.isArchived;
|
||||
if (this.isExternal != null) {
|
||||
json[r'isExternal'] = this.isExternal;
|
||||
} else {
|
||||
// json[r'isExternal'] = null;
|
||||
}
|
||||
json[r'isFavorite'] = this.isFavorite;
|
||||
json[r'isOffline'] = this.isOffline;
|
||||
if (this.isReadOnly != null) {
|
||||
json[r'isReadOnly'] = this.isReadOnly;
|
||||
} else {
|
||||
// json[r'isReadOnly'] = null;
|
||||
}
|
||||
json[r'isTrashed'] = this.isTrashed;
|
||||
json[r'libraryId'] = this.libraryId;
|
||||
if (this.livePhotoVideoId != null) {
|
||||
|
@ -287,10 +309,10 @@ class AssetResponseDto {
|
|||
hasMetadata: mapValueOfType<bool>(json, r'hasMetadata')!,
|
||||
id: mapValueOfType<String>(json, r'id')!,
|
||||
isArchived: mapValueOfType<bool>(json, r'isArchived')!,
|
||||
isExternal: mapValueOfType<bool>(json, r'isExternal')!,
|
||||
isExternal: mapValueOfType<bool>(json, r'isExternal'),
|
||||
isFavorite: mapValueOfType<bool>(json, r'isFavorite')!,
|
||||
isOffline: mapValueOfType<bool>(json, r'isOffline')!,
|
||||
isReadOnly: mapValueOfType<bool>(json, r'isReadOnly')!,
|
||||
isReadOnly: mapValueOfType<bool>(json, r'isReadOnly'),
|
||||
isTrashed: mapValueOfType<bool>(json, r'isTrashed')!,
|
||||
libraryId: mapValueOfType<String>(json, r'libraryId')!,
|
||||
livePhotoVideoId: mapValueOfType<String>(json, r'livePhotoVideoId'),
|
||||
|
@ -365,10 +387,8 @@ class AssetResponseDto {
|
|||
'hasMetadata',
|
||||
'id',
|
||||
'isArchived',
|
||||
'isExternal',
|
||||
'isFavorite',
|
||||
'isOffline',
|
||||
'isReadOnly',
|
||||
'isTrashed',
|
||||
'libraryId',
|
||||
'localDateTime',
|
||||
|
|
36
mobile/openapi/lib/model/metadata_search_dto.dart
generated
36
mobile/openapi/lib/model/metadata_search_dto.dart
generated
|
@ -24,12 +24,10 @@ class MetadataSearchDto {
|
|||
this.id,
|
||||
this.isArchived,
|
||||
this.isEncoded,
|
||||
this.isExternal,
|
||||
this.isFavorite,
|
||||
this.isMotion,
|
||||
this.isNotInAlbum,
|
||||
this.isOffline,
|
||||
this.isReadOnly,
|
||||
this.isVisible,
|
||||
this.lensModel,
|
||||
this.libraryId,
|
||||
|
@ -148,14 +146,6 @@ class MetadataSearchDto {
|
|||
///
|
||||
bool? isEncoded;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
bool? isExternal;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
|
@ -188,14 +178,6 @@ class MetadataSearchDto {
|
|||
///
|
||||
bool? isOffline;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
bool? isReadOnly;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
|
@ -426,12 +408,10 @@ class MetadataSearchDto {
|
|||
other.id == id &&
|
||||
other.isArchived == isArchived &&
|
||||
other.isEncoded == isEncoded &&
|
||||
other.isExternal == isExternal &&
|
||||
other.isFavorite == isFavorite &&
|
||||
other.isMotion == isMotion &&
|
||||
other.isNotInAlbum == isNotInAlbum &&
|
||||
other.isOffline == isOffline &&
|
||||
other.isReadOnly == isReadOnly &&
|
||||
other.isVisible == isVisible &&
|
||||
other.lensModel == lensModel &&
|
||||
other.libraryId == libraryId &&
|
||||
|
@ -475,12 +455,10 @@ class MetadataSearchDto {
|
|||
(id == null ? 0 : id!.hashCode) +
|
||||
(isArchived == null ? 0 : isArchived!.hashCode) +
|
||||
(isEncoded == null ? 0 : isEncoded!.hashCode) +
|
||||
(isExternal == null ? 0 : isExternal!.hashCode) +
|
||||
(isFavorite == null ? 0 : isFavorite!.hashCode) +
|
||||
(isMotion == null ? 0 : isMotion!.hashCode) +
|
||||
(isNotInAlbum == null ? 0 : isNotInAlbum!.hashCode) +
|
||||
(isOffline == null ? 0 : isOffline!.hashCode) +
|
||||
(isReadOnly == null ? 0 : isReadOnly!.hashCode) +
|
||||
(isVisible == null ? 0 : isVisible!.hashCode) +
|
||||
(lensModel == null ? 0 : lensModel!.hashCode) +
|
||||
(libraryId == null ? 0 : libraryId!.hashCode) +
|
||||
|
@ -511,7 +489,7 @@ class MetadataSearchDto {
|
|||
(withStacked == null ? 0 : withStacked!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'MetadataSearchDto[checksum=$checksum, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceAssetId=$deviceAssetId, deviceId=$deviceId, encodedVideoPath=$encodedVideoPath, id=$id, isArchived=$isArchived, isEncoded=$isEncoded, isExternal=$isExternal, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, isReadOnly=$isReadOnly, isVisible=$isVisible, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, order=$order, originalFileName=$originalFileName, originalPath=$originalPath, page=$page, personIds=$personIds, previewPath=$previewPath, resizePath=$resizePath, size=$size, state=$state, takenAfter=$takenAfter, takenBefore=$takenBefore, thumbnailPath=$thumbnailPath, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, webpPath=$webpPath, withArchived=$withArchived, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]';
|
||||
String toString() => 'MetadataSearchDto[checksum=$checksum, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceAssetId=$deviceAssetId, deviceId=$deviceId, encodedVideoPath=$encodedVideoPath, id=$id, isArchived=$isArchived, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, isVisible=$isVisible, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, order=$order, originalFileName=$originalFileName, originalPath=$originalPath, page=$page, personIds=$personIds, previewPath=$previewPath, resizePath=$resizePath, size=$size, state=$state, takenAfter=$takenAfter, takenBefore=$takenBefore, thumbnailPath=$thumbnailPath, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, webpPath=$webpPath, withArchived=$withArchived, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
|
@ -570,11 +548,6 @@ class MetadataSearchDto {
|
|||
} else {
|
||||
// json[r'isEncoded'] = null;
|
||||
}
|
||||
if (this.isExternal != null) {
|
||||
json[r'isExternal'] = this.isExternal;
|
||||
} else {
|
||||
// json[r'isExternal'] = null;
|
||||
}
|
||||
if (this.isFavorite != null) {
|
||||
json[r'isFavorite'] = this.isFavorite;
|
||||
} else {
|
||||
|
@ -595,11 +568,6 @@ class MetadataSearchDto {
|
|||
} else {
|
||||
// json[r'isOffline'] = null;
|
||||
}
|
||||
if (this.isReadOnly != null) {
|
||||
json[r'isReadOnly'] = this.isReadOnly;
|
||||
} else {
|
||||
// json[r'isReadOnly'] = null;
|
||||
}
|
||||
if (this.isVisible != null) {
|
||||
json[r'isVisible'] = this.isVisible;
|
||||
} else {
|
||||
|
@ -754,12 +722,10 @@ class MetadataSearchDto {
|
|||
id: mapValueOfType<String>(json, r'id'),
|
||||
isArchived: mapValueOfType<bool>(json, r'isArchived'),
|
||||
isEncoded: mapValueOfType<bool>(json, r'isEncoded'),
|
||||
isExternal: mapValueOfType<bool>(json, r'isExternal'),
|
||||
isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
|
||||
isMotion: mapValueOfType<bool>(json, r'isMotion'),
|
||||
isNotInAlbum: mapValueOfType<bool>(json, r'isNotInAlbum'),
|
||||
isOffline: mapValueOfType<bool>(json, r'isOffline'),
|
||||
isReadOnly: mapValueOfType<bool>(json, r'isReadOnly'),
|
||||
isVisible: mapValueOfType<bool>(json, r'isVisible'),
|
||||
lensModel: mapValueOfType<String>(json, r'lensModel'),
|
||||
libraryId: mapValueOfType<String>(json, r'libraryId'),
|
||||
|
|
36
mobile/openapi/lib/model/smart_search_dto.dart
generated
36
mobile/openapi/lib/model/smart_search_dto.dart
generated
|
@ -20,12 +20,10 @@ class SmartSearchDto {
|
|||
this.deviceId,
|
||||
this.isArchived,
|
||||
this.isEncoded,
|
||||
this.isExternal,
|
||||
this.isFavorite,
|
||||
this.isMotion,
|
||||
this.isNotInAlbum,
|
||||
this.isOffline,
|
||||
this.isReadOnly,
|
||||
this.isVisible,
|
||||
this.lensModel,
|
||||
this.libraryId,
|
||||
|
@ -104,14 +102,6 @@ class SmartSearchDto {
|
|||
///
|
||||
bool? isEncoded;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
bool? isExternal;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
|
@ -144,14 +134,6 @@ class SmartSearchDto {
|
|||
///
|
||||
bool? isOffline;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
bool? isReadOnly;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
|
@ -306,12 +288,10 @@ class SmartSearchDto {
|
|||
other.deviceId == deviceId &&
|
||||
other.isArchived == isArchived &&
|
||||
other.isEncoded == isEncoded &&
|
||||
other.isExternal == isExternal &&
|
||||
other.isFavorite == isFavorite &&
|
||||
other.isMotion == isMotion &&
|
||||
other.isNotInAlbum == isNotInAlbum &&
|
||||
other.isOffline == isOffline &&
|
||||
other.isReadOnly == isReadOnly &&
|
||||
other.isVisible == isVisible &&
|
||||
other.lensModel == lensModel &&
|
||||
other.libraryId == libraryId &&
|
||||
|
@ -343,12 +323,10 @@ class SmartSearchDto {
|
|||
(deviceId == null ? 0 : deviceId!.hashCode) +
|
||||
(isArchived == null ? 0 : isArchived!.hashCode) +
|
||||
(isEncoded == null ? 0 : isEncoded!.hashCode) +
|
||||
(isExternal == null ? 0 : isExternal!.hashCode) +
|
||||
(isFavorite == null ? 0 : isFavorite!.hashCode) +
|
||||
(isMotion == null ? 0 : isMotion!.hashCode) +
|
||||
(isNotInAlbum == null ? 0 : isNotInAlbum!.hashCode) +
|
||||
(isOffline == null ? 0 : isOffline!.hashCode) +
|
||||
(isReadOnly == null ? 0 : isReadOnly!.hashCode) +
|
||||
(isVisible == null ? 0 : isVisible!.hashCode) +
|
||||
(lensModel == null ? 0 : lensModel!.hashCode) +
|
||||
(libraryId == null ? 0 : libraryId!.hashCode) +
|
||||
|
@ -371,7 +349,7 @@ class SmartSearchDto {
|
|||
(withExif == null ? 0 : withExif!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'SmartSearchDto[city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceId=$deviceId, isArchived=$isArchived, isEncoded=$isEncoded, isExternal=$isExternal, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, isReadOnly=$isReadOnly, isVisible=$isVisible, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, page=$page, personIds=$personIds, query=$query, size=$size, state=$state, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, withArchived=$withArchived, withDeleted=$withDeleted, withExif=$withExif]';
|
||||
String toString() => 'SmartSearchDto[city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceId=$deviceId, isArchived=$isArchived, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, isVisible=$isVisible, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, page=$page, personIds=$personIds, query=$query, size=$size, state=$state, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, withArchived=$withArchived, withDeleted=$withDeleted, withExif=$withExif]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
|
@ -410,11 +388,6 @@ class SmartSearchDto {
|
|||
} else {
|
||||
// json[r'isEncoded'] = null;
|
||||
}
|
||||
if (this.isExternal != null) {
|
||||
json[r'isExternal'] = this.isExternal;
|
||||
} else {
|
||||
// json[r'isExternal'] = null;
|
||||
}
|
||||
if (this.isFavorite != null) {
|
||||
json[r'isFavorite'] = this.isFavorite;
|
||||
} else {
|
||||
|
@ -435,11 +408,6 @@ class SmartSearchDto {
|
|||
} else {
|
||||
// json[r'isOffline'] = null;
|
||||
}
|
||||
if (this.isReadOnly != null) {
|
||||
json[r'isReadOnly'] = this.isReadOnly;
|
||||
} else {
|
||||
// json[r'isReadOnly'] = null;
|
||||
}
|
||||
if (this.isVisible != null) {
|
||||
json[r'isVisible'] = this.isVisible;
|
||||
} else {
|
||||
|
@ -546,12 +514,10 @@ class SmartSearchDto {
|
|||
deviceId: mapValueOfType<String>(json, r'deviceId'),
|
||||
isArchived: mapValueOfType<bool>(json, r'isArchived'),
|
||||
isEncoded: mapValueOfType<bool>(json, r'isEncoded'),
|
||||
isExternal: mapValueOfType<bool>(json, r'isExternal'),
|
||||
isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
|
||||
isMotion: mapValueOfType<bool>(json, r'isMotion'),
|
||||
isNotInAlbum: mapValueOfType<bool>(json, r'isNotInAlbum'),
|
||||
isOffline: mapValueOfType<bool>(json, r'isOffline'),
|
||||
isReadOnly: mapValueOfType<bool>(json, r'isReadOnly'),
|
||||
isVisible: mapValueOfType<bool>(json, r'isVisible'),
|
||||
lensModel: mapValueOfType<String>(json, r'lensModel'),
|
||||
libraryId: mapValueOfType<String>(json, r'libraryId'),
|
||||
|
|
2
mobile/openapi/test/asset_api_test.dart
generated
2
mobile/openapi/test/asset_api_test.dart
generated
|
@ -105,7 +105,7 @@ void main() {
|
|||
// TODO
|
||||
});
|
||||
|
||||
//Future<AssetFileUploadResponseDto> uploadFile(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String key, String xImmichChecksum, String duration, bool isArchived, bool isFavorite, bool isOffline, bool isReadOnly, bool isVisible, String libraryId, MultipartFile livePhotoData, MultipartFile sidecarData }) async
|
||||
//Future<AssetFileUploadResponseDto> uploadFile(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String key, String xImmichChecksum, String duration, bool isArchived, bool isFavorite, bool isOffline, bool isVisible, String libraryId, MultipartFile livePhotoData, MultipartFile sidecarData }) async
|
||||
test('test uploadFile', () async {
|
||||
// TODO
|
||||
});
|
||||
|
|
2
mobile/openapi/test/asset_response_dto_test.dart
generated
2
mobile/openapi/test/asset_response_dto_test.dart
generated
|
@ -67,6 +67,7 @@ void main() {
|
|||
// TODO
|
||||
});
|
||||
|
||||
// This property was deprecated in v1.104.0
|
||||
// bool isExternal
|
||||
test('to test the property `isExternal`', () async {
|
||||
// TODO
|
||||
|
@ -82,6 +83,7 @@ void main() {
|
|||
// TODO
|
||||
});
|
||||
|
||||
// This property was deprecated in v1.104.0
|
||||
// bool isReadOnly
|
||||
test('to test the property `isReadOnly`', () async {
|
||||
// TODO
|
||||
|
|
10
mobile/openapi/test/metadata_search_dto_test.dart
generated
10
mobile/openapi/test/metadata_search_dto_test.dart
generated
|
@ -71,11 +71,6 @@ void main() {
|
|||
// TODO
|
||||
});
|
||||
|
||||
// bool isExternal
|
||||
test('to test the property `isExternal`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// bool isFavorite
|
||||
test('to test the property `isFavorite`', () async {
|
||||
// TODO
|
||||
|
@ -96,11 +91,6 @@ void main() {
|
|||
// TODO
|
||||
});
|
||||
|
||||
// bool isReadOnly
|
||||
test('to test the property `isReadOnly`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// bool isVisible
|
||||
test('to test the property `isVisible`', () async {
|
||||
// TODO
|
||||
|
|
10
mobile/openapi/test/smart_search_dto_test.dart
generated
10
mobile/openapi/test/smart_search_dto_test.dart
generated
|
@ -51,11 +51,6 @@ void main() {
|
|||
// TODO
|
||||
});
|
||||
|
||||
// bool isExternal
|
||||
test('to test the property `isExternal`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// bool isFavorite
|
||||
test('to test the property `isFavorite`', () async {
|
||||
// TODO
|
||||
|
@ -76,11 +71,6 @@ void main() {
|
|||
// TODO
|
||||
});
|
||||
|
||||
// bool isReadOnly
|
||||
test('to test the property `isReadOnly`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// bool isVisible
|
||||
test('to test the property `isVisible`', () async {
|
||||
// TODO
|
||||
|
|
|
@ -7297,6 +7297,8 @@
|
|||
"type": "boolean"
|
||||
},
|
||||
"isExternal": {
|
||||
"deprecated": true,
|
||||
"description": "This property was deprecated in v1.104.0",
|
||||
"type": "boolean"
|
||||
},
|
||||
"isFavorite": {
|
||||
|
@ -7306,6 +7308,8 @@
|
|||
"type": "boolean"
|
||||
},
|
||||
"isReadOnly": {
|
||||
"deprecated": true,
|
||||
"description": "This property was deprecated in v1.104.0",
|
||||
"type": "boolean"
|
||||
},
|
||||
"isTrashed": {
|
||||
|
@ -7388,10 +7392,8 @@
|
|||
"hasMetadata",
|
||||
"id",
|
||||
"isArchived",
|
||||
"isExternal",
|
||||
"isFavorite",
|
||||
"isOffline",
|
||||
"isReadOnly",
|
||||
"isTrashed",
|
||||
"libraryId",
|
||||
"localDateTime",
|
||||
|
@ -7652,9 +7654,6 @@
|
|||
"isOffline": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isReadOnly": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isVisible": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
@ -8599,9 +8598,6 @@
|
|||
"isEncoded": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isExternal": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isFavorite": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
@ -8614,9 +8610,6 @@
|
|||
"isOffline": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isReadOnly": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isVisible": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
@ -9821,9 +9814,6 @@
|
|||
"isEncoded": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isExternal": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isFavorite": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
@ -9836,9 +9826,6 @@
|
|||
"isOffline": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isReadOnly": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isVisible": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
|
|
@ -122,10 +122,12 @@ export type AssetResponseDto = {
|
|||
hasMetadata: boolean;
|
||||
id: string;
|
||||
isArchived: boolean;
|
||||
isExternal: boolean;
|
||||
/** This property was deprecated in v1.104.0 */
|
||||
isExternal?: boolean;
|
||||
isFavorite: boolean;
|
||||
isOffline: boolean;
|
||||
isReadOnly: boolean;
|
||||
/** This property was deprecated in v1.104.0 */
|
||||
isReadOnly?: boolean;
|
||||
isTrashed: boolean;
|
||||
libraryId: string;
|
||||
livePhotoVideoId?: string | null;
|
||||
|
@ -296,7 +298,6 @@ export type CreateAssetDto = {
|
|||
isArchived?: boolean;
|
||||
isFavorite?: boolean;
|
||||
isOffline?: boolean;
|
||||
isReadOnly?: boolean;
|
||||
isVisible?: boolean;
|
||||
libraryId?: string;
|
||||
livePhotoData?: Blob;
|
||||
|
@ -622,12 +623,10 @@ export type MetadataSearchDto = {
|
|||
id?: string;
|
||||
isArchived?: boolean;
|
||||
isEncoded?: boolean;
|
||||
isExternal?: boolean;
|
||||
isFavorite?: boolean;
|
||||
isMotion?: boolean;
|
||||
isNotInAlbum?: boolean;
|
||||
isOffline?: boolean;
|
||||
isReadOnly?: boolean;
|
||||
isVisible?: boolean;
|
||||
lensModel?: string;
|
||||
libraryId?: string;
|
||||
|
@ -699,12 +698,10 @@ export type SmartSearchDto = {
|
|||
deviceId?: string;
|
||||
isArchived?: boolean;
|
||||
isEncoded?: boolean;
|
||||
isExternal?: boolean;
|
||||
isFavorite?: boolean;
|
||||
isMotion?: boolean;
|
||||
isNotInAlbum?: boolean;
|
||||
isOffline?: boolean;
|
||||
isReadOnly?: boolean;
|
||||
isVisible?: boolean;
|
||||
lensModel?: string;
|
||||
libraryId?: string;
|
||||
|
|
|
@ -113,6 +113,7 @@ export const DummyValue = {
|
|||
PAGINATION: { take: 10, skip: 0 },
|
||||
EMAIL: 'user@immich.app',
|
||||
STRING: 'abcdefghi',
|
||||
NUMBER: 50,
|
||||
BUFFER: Buffer.from('abcdefghi'),
|
||||
DATE: new Date(),
|
||||
TIME_BUCKET: '2024-01-01T00:00:00.000Z',
|
||||
|
|
|
@ -36,8 +36,10 @@ export class AssetResponseDto extends SanitizedAssetResponseDto {
|
|||
isArchived!: boolean;
|
||||
isTrashed!: boolean;
|
||||
isOffline!: boolean;
|
||||
isExternal!: boolean;
|
||||
isReadOnly!: boolean;
|
||||
@PropertyLifecycle({ deprecatedAt: 'v1.104.0' })
|
||||
isExternal?: boolean;
|
||||
@PropertyLifecycle({ deprecatedAt: 'v1.104.0' })
|
||||
isReadOnly?: boolean;
|
||||
exifInfo?: ExifResponseDto;
|
||||
smartInfo?: SmartInfoResponseDto;
|
||||
tags?: TagResponseDto[];
|
||||
|
@ -124,9 +126,9 @@ export function mapAsset(entity: AssetEntity, options: AssetMapOptions = {}): As
|
|||
.map((a) => mapAsset(a, { stripMetadata, auth: options.auth }))
|
||||
: undefined,
|
||||
stackCount: entity.stack?.assets?.length ?? null,
|
||||
isExternal: entity.isExternal,
|
||||
isOffline: entity.isOffline,
|
||||
isReadOnly: entity.isReadOnly,
|
||||
isExternal: false,
|
||||
isReadOnly: false,
|
||||
hasMetadata: true,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -97,9 +97,6 @@ export class CreateAssetDto {
|
|||
@ValidateBoolean({ optional: true })
|
||||
isOffline?: boolean;
|
||||
|
||||
@ValidateBoolean({ optional: true })
|
||||
isReadOnly?: boolean;
|
||||
|
||||
// The properties below are added to correctly generate the API docs
|
||||
// and client SDKs. Validation should be handled in the controller.
|
||||
@ApiProperty({ type: 'string', format: 'binary' })
|
||||
|
|
|
@ -33,9 +33,6 @@ class BaseSearchDto {
|
|||
@ValidateBoolean({ optional: true })
|
||||
isEncoded?: boolean;
|
||||
|
||||
@ValidateBoolean({ optional: true })
|
||||
isExternal?: boolean;
|
||||
|
||||
@ValidateBoolean({ optional: true })
|
||||
isFavorite?: boolean;
|
||||
|
||||
|
@ -45,9 +42,6 @@ class BaseSearchDto {
|
|||
@ValidateBoolean({ optional: true })
|
||||
isOffline?: boolean;
|
||||
|
||||
@ValidateBoolean({ optional: true })
|
||||
isReadOnly?: boolean;
|
||||
|
||||
@ValidateBoolean({ optional: true })
|
||||
isVisible?: boolean;
|
||||
|
||||
|
|
|
@ -106,9 +106,6 @@ export class AssetEntity {
|
|||
@Column({ type: 'boolean', default: false })
|
||||
isExternal!: boolean;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
isReadOnly!: boolean;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
isOffline!: boolean;
|
||||
|
||||
|
|
|
@ -108,10 +108,6 @@ export interface IEntityJob extends IBaseJob {
|
|||
source?: 'upload' | 'sidecar-write';
|
||||
}
|
||||
|
||||
export interface IAssetDeletionJob extends IEntityJob {
|
||||
fromExternal?: boolean;
|
||||
}
|
||||
|
||||
export interface ILibraryFileJob extends IEntityJob {
|
||||
ownerId: string;
|
||||
assetPath: string;
|
||||
|
@ -225,7 +221,7 @@ export type JobItem =
|
|||
|
||||
// Asset Deletion
|
||||
| { name: JobName.PERSON_CLEANUP; data?: IBaseJob }
|
||||
| { name: JobName.ASSET_DELETION; data: IAssetDeletionJob }
|
||||
| { name: JobName.ASSET_DELETION; data: IEntityJob }
|
||||
| { name: JobName.ASSET_DELETION_CHECK; data?: IBaseJob }
|
||||
|
||||
// Library Management
|
||||
|
|
|
@ -56,11 +56,9 @@ export type SearchIdOptions = SearchAssetIDOptions & SearchUserIdOptions;
|
|||
export interface SearchStatusOptions {
|
||||
isArchived?: boolean;
|
||||
isEncoded?: boolean;
|
||||
isExternal?: boolean;
|
||||
isFavorite?: boolean;
|
||||
isMotion?: boolean;
|
||||
isOffline?: boolean;
|
||||
isReadOnly?: boolean;
|
||||
isVisible?: boolean;
|
||||
isNotInAlbum?: boolean;
|
||||
type?: AssetType;
|
||||
|
|
14
server/src/migrations/1714698592332-RemoveIsReadOnly.ts
Normal file
14
server/src/migrations/1714698592332-RemoveIsReadOnly.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class RemoveIsReadOnly1714698592332 implements MigrationInterface {
|
||||
name = 'RemoveIsReadOnly1714698592332'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "assets" DROP COLUMN "isReadOnly"`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "assets" ADD "isReadOnly" boolean NOT NULL DEFAULT false`);
|
||||
}
|
||||
|
||||
}
|
|
@ -22,7 +22,6 @@ SELECT
|
|||
"entity"."isFavorite" AS "entity_isFavorite",
|
||||
"entity"."isArchived" AS "entity_isArchived",
|
||||
"entity"."isExternal" AS "entity_isExternal",
|
||||
"entity"."isReadOnly" AS "entity_isReadOnly",
|
||||
"entity"."isOffline" AS "entity_isOffline",
|
||||
"entity"."checksum" AS "entity_checksum",
|
||||
"entity"."duration" AS "entity_duration",
|
||||
|
@ -105,7 +104,6 @@ SELECT
|
|||
"AssetEntity"."isFavorite" AS "AssetEntity_isFavorite",
|
||||
"AssetEntity"."isArchived" AS "AssetEntity_isArchived",
|
||||
"AssetEntity"."isExternal" AS "AssetEntity_isExternal",
|
||||
"AssetEntity"."isReadOnly" AS "AssetEntity_isReadOnly",
|
||||
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
|
||||
"AssetEntity"."checksum" AS "AssetEntity_checksum",
|
||||
"AssetEntity"."duration" AS "AssetEntity_duration",
|
||||
|
@ -141,7 +139,6 @@ SELECT
|
|||
"AssetEntity"."isFavorite" AS "AssetEntity_isFavorite",
|
||||
"AssetEntity"."isArchived" AS "AssetEntity_isArchived",
|
||||
"AssetEntity"."isExternal" AS "AssetEntity_isExternal",
|
||||
"AssetEntity"."isReadOnly" AS "AssetEntity_isReadOnly",
|
||||
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
|
||||
"AssetEntity"."checksum" AS "AssetEntity_checksum",
|
||||
"AssetEntity"."duration" AS "AssetEntity_duration",
|
||||
|
@ -226,7 +223,6 @@ SELECT
|
|||
"bd93d5747511a4dad4923546c51365bf1a803774"."isFavorite" AS "bd93d5747511a4dad4923546c51365bf1a803774_isFavorite",
|
||||
"bd93d5747511a4dad4923546c51365bf1a803774"."isArchived" AS "bd93d5747511a4dad4923546c51365bf1a803774_isArchived",
|
||||
"bd93d5747511a4dad4923546c51365bf1a803774"."isExternal" AS "bd93d5747511a4dad4923546c51365bf1a803774_isExternal",
|
||||
"bd93d5747511a4dad4923546c51365bf1a803774"."isReadOnly" AS "bd93d5747511a4dad4923546c51365bf1a803774_isReadOnly",
|
||||
"bd93d5747511a4dad4923546c51365bf1a803774"."isOffline" AS "bd93d5747511a4dad4923546c51365bf1a803774_isOffline",
|
||||
"bd93d5747511a4dad4923546c51365bf1a803774"."checksum" AS "bd93d5747511a4dad4923546c51365bf1a803774_checksum",
|
||||
"bd93d5747511a4dad4923546c51365bf1a803774"."duration" AS "bd93d5747511a4dad4923546c51365bf1a803774_duration",
|
||||
|
@ -308,7 +304,6 @@ FROM
|
|||
"AssetEntity"."isFavorite" AS "AssetEntity_isFavorite",
|
||||
"AssetEntity"."isArchived" AS "AssetEntity_isArchived",
|
||||
"AssetEntity"."isExternal" AS "AssetEntity_isExternal",
|
||||
"AssetEntity"."isReadOnly" AS "AssetEntity_isReadOnly",
|
||||
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
|
||||
"AssetEntity"."checksum" AS "AssetEntity_checksum",
|
||||
"AssetEntity"."duration" AS "AssetEntity_duration",
|
||||
|
@ -405,7 +400,6 @@ SELECT
|
|||
"AssetEntity"."isFavorite" AS "AssetEntity_isFavorite",
|
||||
"AssetEntity"."isArchived" AS "AssetEntity_isArchived",
|
||||
"AssetEntity"."isExternal" AS "AssetEntity_isExternal",
|
||||
"AssetEntity"."isReadOnly" AS "AssetEntity_isReadOnly",
|
||||
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
|
||||
"AssetEntity"."checksum" AS "AssetEntity_checksum",
|
||||
"AssetEntity"."duration" AS "AssetEntity_duration",
|
||||
|
@ -451,7 +445,6 @@ SELECT
|
|||
"AssetEntity"."isFavorite" AS "AssetEntity_isFavorite",
|
||||
"AssetEntity"."isArchived" AS "AssetEntity_isArchived",
|
||||
"AssetEntity"."isExternal" AS "AssetEntity_isExternal",
|
||||
"AssetEntity"."isReadOnly" AS "AssetEntity_isReadOnly",
|
||||
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
|
||||
"AssetEntity"."checksum" AS "AssetEntity_checksum",
|
||||
"AssetEntity"."duration" AS "AssetEntity_duration",
|
||||
|
@ -519,7 +512,6 @@ SELECT
|
|||
"AssetEntity"."isFavorite" AS "AssetEntity_isFavorite",
|
||||
"AssetEntity"."isArchived" AS "AssetEntity_isArchived",
|
||||
"AssetEntity"."isExternal" AS "AssetEntity_isExternal",
|
||||
"AssetEntity"."isReadOnly" AS "AssetEntity_isReadOnly",
|
||||
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
|
||||
"AssetEntity"."checksum" AS "AssetEntity_checksum",
|
||||
"AssetEntity"."duration" AS "AssetEntity_duration",
|
||||
|
@ -608,7 +600,6 @@ SELECT
|
|||
"asset"."isFavorite" AS "asset_isFavorite",
|
||||
"asset"."isArchived" AS "asset_isArchived",
|
||||
"asset"."isExternal" AS "asset_isExternal",
|
||||
"asset"."isReadOnly" AS "asset_isReadOnly",
|
||||
"asset"."isOffline" AS "asset_isOffline",
|
||||
"asset"."checksum" AS "asset_checksum",
|
||||
"asset"."duration" AS "asset_duration",
|
||||
|
@ -667,7 +658,6 @@ SELECT
|
|||
"stackedAssets"."isFavorite" AS "stackedAssets_isFavorite",
|
||||
"stackedAssets"."isArchived" AS "stackedAssets_isArchived",
|
||||
"stackedAssets"."isExternal" AS "stackedAssets_isExternal",
|
||||
"stackedAssets"."isReadOnly" AS "stackedAssets_isReadOnly",
|
||||
"stackedAssets"."isOffline" AS "stackedAssets_isOffline",
|
||||
"stackedAssets"."checksum" AS "stackedAssets_checksum",
|
||||
"stackedAssets"."duration" AS "stackedAssets_duration",
|
||||
|
@ -784,7 +774,6 @@ SELECT
|
|||
"asset"."isFavorite" AS "asset_isFavorite",
|
||||
"asset"."isArchived" AS "asset_isArchived",
|
||||
"asset"."isExternal" AS "asset_isExternal",
|
||||
"asset"."isReadOnly" AS "asset_isReadOnly",
|
||||
"asset"."isOffline" AS "asset_isOffline",
|
||||
"asset"."checksum" AS "asset_checksum",
|
||||
"asset"."duration" AS "asset_duration",
|
||||
|
@ -843,7 +832,6 @@ SELECT
|
|||
"stackedAssets"."isFavorite" AS "stackedAssets_isFavorite",
|
||||
"stackedAssets"."isArchived" AS "stackedAssets_isArchived",
|
||||
"stackedAssets"."isExternal" AS "stackedAssets_isExternal",
|
||||
"stackedAssets"."isReadOnly" AS "stackedAssets_isReadOnly",
|
||||
"stackedAssets"."isOffline" AS "stackedAssets_isOffline",
|
||||
"stackedAssets"."checksum" AS "stackedAssets_checksum",
|
||||
"stackedAssets"."duration" AS "stackedAssets_duration",
|
||||
|
@ -891,7 +879,6 @@ SELECT
|
|||
"asset"."isFavorite" AS "asset_isFavorite",
|
||||
"asset"."isArchived" AS "asset_isArchived",
|
||||
"asset"."isExternal" AS "asset_isExternal",
|
||||
"asset"."isReadOnly" AS "asset_isReadOnly",
|
||||
"asset"."isOffline" AS "asset_isOffline",
|
||||
"asset"."checksum" AS "asset_checksum",
|
||||
"asset"."duration" AS "asset_duration",
|
||||
|
@ -950,7 +937,6 @@ SELECT
|
|||
"stackedAssets"."isFavorite" AS "stackedAssets_isFavorite",
|
||||
"stackedAssets"."isArchived" AS "stackedAssets_isArchived",
|
||||
"stackedAssets"."isExternal" AS "stackedAssets_isExternal",
|
||||
"stackedAssets"."isReadOnly" AS "stackedAssets_isReadOnly",
|
||||
"stackedAssets"."isOffline" AS "stackedAssets_isOffline",
|
||||
"stackedAssets"."checksum" AS "stackedAssets_checksum",
|
||||
"stackedAssets"."duration" AS "stackedAssets_duration",
|
||||
|
|
|
@ -165,7 +165,6 @@ FROM
|
|||
"AssetFaceEntity__AssetFaceEntity_asset"."isFavorite" AS "AssetFaceEntity__AssetFaceEntity_asset_isFavorite",
|
||||
"AssetFaceEntity__AssetFaceEntity_asset"."isArchived" AS "AssetFaceEntity__AssetFaceEntity_asset_isArchived",
|
||||
"AssetFaceEntity__AssetFaceEntity_asset"."isExternal" AS "AssetFaceEntity__AssetFaceEntity_asset_isExternal",
|
||||
"AssetFaceEntity__AssetFaceEntity_asset"."isReadOnly" AS "AssetFaceEntity__AssetFaceEntity_asset_isReadOnly",
|
||||
"AssetFaceEntity__AssetFaceEntity_asset"."isOffline" AS "AssetFaceEntity__AssetFaceEntity_asset_isOffline",
|
||||
"AssetFaceEntity__AssetFaceEntity_asset"."checksum" AS "AssetFaceEntity__AssetFaceEntity_asset_checksum",
|
||||
"AssetFaceEntity__AssetFaceEntity_asset"."duration" AS "AssetFaceEntity__AssetFaceEntity_asset_duration",
|
||||
|
@ -263,7 +262,6 @@ FROM
|
|||
"AssetEntity"."isFavorite" AS "AssetEntity_isFavorite",
|
||||
"AssetEntity"."isArchived" AS "AssetEntity_isArchived",
|
||||
"AssetEntity"."isExternal" AS "AssetEntity_isExternal",
|
||||
"AssetEntity"."isReadOnly" AS "AssetEntity_isReadOnly",
|
||||
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
|
||||
"AssetEntity"."checksum" AS "AssetEntity_checksum",
|
||||
"AssetEntity"."duration" AS "AssetEntity_duration",
|
||||
|
@ -393,7 +391,6 @@ SELECT
|
|||
"AssetFaceEntity__AssetFaceEntity_asset"."isFavorite" AS "AssetFaceEntity__AssetFaceEntity_asset_isFavorite",
|
||||
"AssetFaceEntity__AssetFaceEntity_asset"."isArchived" AS "AssetFaceEntity__AssetFaceEntity_asset_isArchived",
|
||||
"AssetFaceEntity__AssetFaceEntity_asset"."isExternal" AS "AssetFaceEntity__AssetFaceEntity_asset_isExternal",
|
||||
"AssetFaceEntity__AssetFaceEntity_asset"."isReadOnly" AS "AssetFaceEntity__AssetFaceEntity_asset_isReadOnly",
|
||||
"AssetFaceEntity__AssetFaceEntity_asset"."isOffline" AS "AssetFaceEntity__AssetFaceEntity_asset_isOffline",
|
||||
"AssetFaceEntity__AssetFaceEntity_asset"."checksum" AS "AssetFaceEntity__AssetFaceEntity_asset_checksum",
|
||||
"AssetFaceEntity__AssetFaceEntity_asset"."duration" AS "AssetFaceEntity__AssetFaceEntity_asset_duration",
|
||||
|
|
|
@ -27,7 +27,6 @@ FROM
|
|||
"asset"."isFavorite" AS "asset_isFavorite",
|
||||
"asset"."isArchived" AS "asset_isArchived",
|
||||
"asset"."isExternal" AS "asset_isExternal",
|
||||
"asset"."isReadOnly" AS "asset_isReadOnly",
|
||||
"asset"."isOffline" AS "asset_isOffline",
|
||||
"asset"."checksum" AS "asset_checksum",
|
||||
"asset"."duration" AS "asset_duration",
|
||||
|
@ -58,7 +57,6 @@ FROM
|
|||
"stackedAssets"."isFavorite" AS "stackedAssets_isFavorite",
|
||||
"stackedAssets"."isArchived" AS "stackedAssets_isArchived",
|
||||
"stackedAssets"."isExternal" AS "stackedAssets_isExternal",
|
||||
"stackedAssets"."isReadOnly" AS "stackedAssets_isReadOnly",
|
||||
"stackedAssets"."isOffline" AS "stackedAssets_isOffline",
|
||||
"stackedAssets"."checksum" AS "stackedAssets_checksum",
|
||||
"stackedAssets"."duration" AS "stackedAssets_duration",
|
||||
|
@ -123,7 +121,6 @@ SELECT
|
|||
"asset"."isFavorite" AS "asset_isFavorite",
|
||||
"asset"."isArchived" AS "asset_isArchived",
|
||||
"asset"."isExternal" AS "asset_isExternal",
|
||||
"asset"."isReadOnly" AS "asset_isReadOnly",
|
||||
"asset"."isOffline" AS "asset_isOffline",
|
||||
"asset"."checksum" AS "asset_checksum",
|
||||
"asset"."duration" AS "asset_duration",
|
||||
|
@ -154,7 +151,6 @@ SELECT
|
|||
"stackedAssets"."isFavorite" AS "stackedAssets_isFavorite",
|
||||
"stackedAssets"."isArchived" AS "stackedAssets_isArchived",
|
||||
"stackedAssets"."isExternal" AS "stackedAssets_isExternal",
|
||||
"stackedAssets"."isReadOnly" AS "stackedAssets_isReadOnly",
|
||||
"stackedAssets"."isOffline" AS "stackedAssets_isOffline",
|
||||
"stackedAssets"."checksum" AS "stackedAssets_checksum",
|
||||
"stackedAssets"."duration" AS "stackedAssets_duration",
|
||||
|
@ -333,7 +329,6 @@ SELECT
|
|||
"asset"."isFavorite" AS "asset_isFavorite",
|
||||
"asset"."isArchived" AS "asset_isArchived",
|
||||
"asset"."isExternal" AS "asset_isExternal",
|
||||
"asset"."isReadOnly" AS "asset_isReadOnly",
|
||||
"asset"."isOffline" AS "asset_isOffline",
|
||||
"asset"."checksum" AS "asset_checksum",
|
||||
"asset"."duration" AS "asset_duration",
|
||||
|
|
|
@ -41,7 +41,6 @@ FROM
|
|||
"SharedLinkEntity__SharedLinkEntity_assets"."isFavorite" AS "SharedLinkEntity__SharedLinkEntity_assets_isFavorite",
|
||||
"SharedLinkEntity__SharedLinkEntity_assets"."isArchived" AS "SharedLinkEntity__SharedLinkEntity_assets_isArchived",
|
||||
"SharedLinkEntity__SharedLinkEntity_assets"."isExternal" AS "SharedLinkEntity__SharedLinkEntity_assets_isExternal",
|
||||
"SharedLinkEntity__SharedLinkEntity_assets"."isReadOnly" AS "SharedLinkEntity__SharedLinkEntity_assets_isReadOnly",
|
||||
"SharedLinkEntity__SharedLinkEntity_assets"."isOffline" AS "SharedLinkEntity__SharedLinkEntity_assets_isOffline",
|
||||
"SharedLinkEntity__SharedLinkEntity_assets"."checksum" AS "SharedLinkEntity__SharedLinkEntity_assets_checksum",
|
||||
"SharedLinkEntity__SharedLinkEntity_assets"."duration" AS "SharedLinkEntity__SharedLinkEntity_assets_duration",
|
||||
|
@ -108,7 +107,6 @@ FROM
|
|||
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."isFavorite" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_isFavorite",
|
||||
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."isArchived" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_isArchived",
|
||||
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."isExternal" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_isExternal",
|
||||
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."isReadOnly" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_isReadOnly",
|
||||
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."isOffline" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_isOffline",
|
||||
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."checksum" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_checksum",
|
||||
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."duration" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_duration",
|
||||
|
@ -231,7 +229,6 @@ SELECT
|
|||
"SharedLinkEntity__SharedLinkEntity_assets"."isFavorite" AS "SharedLinkEntity__SharedLinkEntity_assets_isFavorite",
|
||||
"SharedLinkEntity__SharedLinkEntity_assets"."isArchived" AS "SharedLinkEntity__SharedLinkEntity_assets_isArchived",
|
||||
"SharedLinkEntity__SharedLinkEntity_assets"."isExternal" AS "SharedLinkEntity__SharedLinkEntity_assets_isExternal",
|
||||
"SharedLinkEntity__SharedLinkEntity_assets"."isReadOnly" AS "SharedLinkEntity__SharedLinkEntity_assets_isReadOnly",
|
||||
"SharedLinkEntity__SharedLinkEntity_assets"."isOffline" AS "SharedLinkEntity__SharedLinkEntity_assets_isOffline",
|
||||
"SharedLinkEntity__SharedLinkEntity_assets"."checksum" AS "SharedLinkEntity__SharedLinkEntity_assets_checksum",
|
||||
"SharedLinkEntity__SharedLinkEntity_assets"."duration" AS "SharedLinkEntity__SharedLinkEntity_assets_duration",
|
||||
|
|
|
@ -151,6 +151,14 @@ GROUP BY
|
|||
ORDER BY
|
||||
"users"."createdAt" ASC
|
||||
|
||||
-- UserRepository.updateUsage
|
||||
UPDATE "users"
|
||||
SET
|
||||
"quotaUsageInBytes" = "quotaUsageInBytes" + 50,
|
||||
"updatedAt" = CURRENT_TIMESTAMP
|
||||
WHERE
|
||||
"id" = $1
|
||||
|
||||
-- UserRepository.syncUsage
|
||||
UPDATE "users"
|
||||
SET
|
||||
|
|
|
@ -253,7 +253,7 @@ export class AssetRepository implements IAssetRepository {
|
|||
|
||||
@Chunked()
|
||||
async softDeleteAll(ids: string[]): Promise<void> {
|
||||
await this.repository.softDelete({ id: In(ids), isExternal: false });
|
||||
await this.repository.softDelete({ id: In(ids) });
|
||||
}
|
||||
|
||||
@Chunked()
|
||||
|
|
|
@ -112,6 +112,7 @@ export class UserRepository implements IUserRepository {
|
|||
return stats;
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.NUMBER] })
|
||||
async updateUsage(id: string, delta: number): Promise<void> {
|
||||
await this.userRepository.increment({ id }, 'quotaUsageInBytes', delta);
|
||||
}
|
||||
|
|
|
@ -295,7 +295,6 @@ export class AssetServiceV1 {
|
|||
livePhotoVideo: livePhotoAssetId === null ? null : ({ id: livePhotoAssetId } as AssetEntity),
|
||||
originalFileName: file.originalName,
|
||||
sidecarPath: sidecarPath || null,
|
||||
isReadOnly: dto.isReadOnly ?? false,
|
||||
isOffline: dto.isOffline ?? false,
|
||||
});
|
||||
|
||||
|
|
|
@ -685,61 +685,6 @@ describe(AssetService.name, () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should only delete generated files for readonly assets', async () => {
|
||||
assetMock.getById.mockResolvedValue(assetStub.readOnly);
|
||||
|
||||
await sut.handleAssetDeletion({ id: assetStub.readOnly.id });
|
||||
|
||||
expect(jobMock.queue.mock.calls).toEqual([
|
||||
[
|
||||
{
|
||||
name: JobName.DELETE_FILES,
|
||||
data: {
|
||||
files: [
|
||||
assetStub.readOnly.thumbnailPath,
|
||||
assetStub.readOnly.previewPath,
|
||||
assetStub.readOnly.encodedVideoPath,
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
]);
|
||||
|
||||
expect(assetMock.remove).toHaveBeenCalledWith(assetStub.readOnly);
|
||||
});
|
||||
|
||||
it('should not process assets from external library without fromExternal flag', async () => {
|
||||
assetMock.getById.mockResolvedValue(assetStub.external);
|
||||
|
||||
await sut.handleAssetDeletion({ id: assetStub.external.id });
|
||||
|
||||
expect(jobMock.queue).not.toHaveBeenCalled();
|
||||
expect(jobMock.queueAll).not.toHaveBeenCalled();
|
||||
expect(assetMock.remove).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should process assets from external library with fromExternal flag', async () => {
|
||||
assetMock.getById.mockResolvedValue(assetStub.external);
|
||||
|
||||
await sut.handleAssetDeletion({ id: assetStub.external.id, fromExternal: true });
|
||||
|
||||
expect(assetMock.remove).toHaveBeenCalledWith(assetStub.external);
|
||||
expect(jobMock.queue.mock.calls).toEqual([
|
||||
[
|
||||
{
|
||||
name: JobName.DELETE_FILES,
|
||||
data: {
|
||||
files: [
|
||||
assetStub.external.thumbnailPath,
|
||||
assetStub.external.previewPath,
|
||||
assetStub.external.encodedVideoPath,
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should delete a live photo', async () => {
|
||||
assetMock.getById.mockResolvedValue(assetStub.livePhotoStillAsset);
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ import { IAssetStackRepository } from 'src/interfaces/asset-stack.interface';
|
|||
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
||||
import { ClientEvent, IEventRepository } from 'src/interfaces/event.interface';
|
||||
import {
|
||||
IAssetDeletionJob,
|
||||
IEntityJob,
|
||||
IJobRepository,
|
||||
ISidecarWriteJob,
|
||||
JOBS_ASSET_PAGINATION_SIZE,
|
||||
|
@ -371,8 +371,8 @@ export class AssetService {
|
|||
return JobStatus.SUCCESS;
|
||||
}
|
||||
|
||||
async handleAssetDeletion(job: IAssetDeletionJob): Promise<JobStatus> {
|
||||
const { id, fromExternal } = job;
|
||||
async handleAssetDeletion(job: IEntityJob): Promise<JobStatus> {
|
||||
const { id } = job;
|
||||
|
||||
const asset = await this.assetRepository.getById(id, {
|
||||
faces: {
|
||||
|
@ -387,11 +387,6 @@ export class AssetService {
|
|||
return JobStatus.FAILED;
|
||||
}
|
||||
|
||||
// Ignore requests that are not from external library job but is for an external asset
|
||||
if (!fromExternal && (!asset.library || asset.library.type === LibraryType.EXTERNAL)) {
|
||||
return JobStatus.SKIPPED;
|
||||
}
|
||||
|
||||
// Replace the parent of the stack children with a new asset
|
||||
if (asset.stack?.primaryAssetId === id) {
|
||||
const stackAssetIds = asset.stack.assets.map((a) => a.id);
|
||||
|
@ -414,18 +409,15 @@ export class AssetService {
|
|||
|
||||
// TODO refactor this to use cascades
|
||||
if (asset.livePhotoVideoId) {
|
||||
await this.jobRepository.queue({
|
||||
name: JobName.ASSET_DELETION,
|
||||
data: { id: asset.livePhotoVideoId, fromExternal },
|
||||
});
|
||||
await this.jobRepository.queue({ name: JobName.ASSET_DELETION, data: { id: asset.livePhotoVideoId } });
|
||||
}
|
||||
|
||||
const files = [asset.thumbnailPath, asset.previewPath, asset.encodedVideoPath];
|
||||
if (!(asset.isExternal || asset.isReadOnly)) {
|
||||
files.push(asset.sidecarPath, asset.originalPath);
|
||||
}
|
||||
|
||||
await this.jobRepository.queue({ name: JobName.DELETE_FILES, data: { files } });
|
||||
await this.jobRepository.queue({
|
||||
name: JobName.DELETE_FILES,
|
||||
data: {
|
||||
files: [asset.thumbnailPath, asset.previewPath, asset.encodedVideoPath, asset.sidecarPath, asset.originalPath],
|
||||
},
|
||||
});
|
||||
|
||||
return JobStatus.SUCCESS;
|
||||
}
|
||||
|
|
|
@ -368,7 +368,6 @@ describe(LibraryService.name, () => {
|
|||
type: AssetType.IMAGE,
|
||||
originalFileName: 'photo.jpg',
|
||||
sidecarPath: null,
|
||||
isReadOnly: true,
|
||||
isExternal: true,
|
||||
},
|
||||
],
|
||||
|
@ -416,7 +415,6 @@ describe(LibraryService.name, () => {
|
|||
type: AssetType.IMAGE,
|
||||
originalFileName: 'photo.jpg',
|
||||
sidecarPath: '/data/user1/photo.jpg.xmp',
|
||||
isReadOnly: true,
|
||||
isExternal: true,
|
||||
},
|
||||
],
|
||||
|
@ -463,7 +461,6 @@ describe(LibraryService.name, () => {
|
|||
type: AssetType.VIDEO,
|
||||
originalFileName: 'video.mp4',
|
||||
sidecarPath: null,
|
||||
isReadOnly: true,
|
||||
isExternal: true,
|
||||
},
|
||||
],
|
||||
|
@ -1458,10 +1455,7 @@ describe(LibraryService.name, () => {
|
|||
await expect(sut.handleOfflineRemoval({ id: libraryStub.externalLibrary1.id })).resolves.toBe(JobStatus.SUCCESS);
|
||||
|
||||
expect(jobMock.queueAll).toHaveBeenCalledWith([
|
||||
{
|
||||
name: JobName.ASSET_DELETION,
|
||||
data: { id: assetStub.image1.id, fromExternal: true },
|
||||
},
|
||||
{ name: JobName.ASSET_DELETION, data: { id: assetStub.image1.id } },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -387,7 +387,7 @@ export class LibraryService {
|
|||
const assetIds = await this.repository.getAssetIds(job.id, true);
|
||||
this.logger.debug(`Will delete ${assetIds.length} asset(s) in library ${job.id}`);
|
||||
await this.jobRepository.queueAll(
|
||||
assetIds.map((assetId) => ({ name: JobName.ASSET_DELETION, data: { id: assetId, fromExternal: true } })),
|
||||
assetIds.map((assetId) => ({ name: JobName.ASSET_DELETION, data: { id: assetId } })),
|
||||
);
|
||||
|
||||
if (assetIds.length === 0) {
|
||||
|
@ -503,7 +503,6 @@ export class LibraryService {
|
|||
type: assetType,
|
||||
originalFileName,
|
||||
sidecarPath,
|
||||
isReadOnly: true,
|
||||
isExternal: true,
|
||||
});
|
||||
assetId = addedAsset.id;
|
||||
|
@ -580,7 +579,7 @@ export class LibraryService {
|
|||
for await (const assets of assetPagination) {
|
||||
this.logger.debug(`Removing ${assets.length} offline assets`);
|
||||
await this.jobRepository.queueAll(
|
||||
assets.map((asset) => ({ name: JobName.ASSET_DELETION, data: { id: asset.id, fromExternal: true } })),
|
||||
assets.map((asset) => ({ name: JobName.ASSET_DELETION, data: { id: asset.id } })),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -440,7 +440,6 @@ export class MetadataService {
|
|||
originalPath: motionPath,
|
||||
originalFileName: asset.originalFileName,
|
||||
isVisible: false,
|
||||
isReadOnly: false,
|
||||
deviceAssetId: 'NONE',
|
||||
deviceId: 'NONE',
|
||||
});
|
||||
|
|
|
@ -558,26 +558,5 @@ describe(StorageTemplateService.name, () => {
|
|||
);
|
||||
expect(assetMock.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not move read-only asset', async () => {
|
||||
assetMock.getAll.mockResolvedValue({
|
||||
items: [
|
||||
{
|
||||
...assetStub.image,
|
||||
originalPath: 'upload/library/user-id/2023/2023-02-23/asset-id+1.jpg',
|
||||
isReadOnly: true,
|
||||
},
|
||||
],
|
||||
hasNextPage: false,
|
||||
});
|
||||
userMock.getList.mockResolvedValue([userStub.user1]);
|
||||
|
||||
await sut.handleMigration();
|
||||
|
||||
expect(assetMock.getAll).toHaveBeenCalled();
|
||||
expect(storageMock.rename).not.toHaveBeenCalled();
|
||||
expect(storageMock.copyFile).not.toHaveBeenCalled();
|
||||
expect(assetMock.update).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -170,7 +170,7 @@ export class StorageTemplateService {
|
|||
}
|
||||
|
||||
async moveAsset(asset: AssetEntity, metadata: MoveAssetMetadata) {
|
||||
if (asset.isReadOnly || asset.isExternal || StorageCore.isAndroidMotionPath(asset.originalPath)) {
|
||||
if (asset.isExternal || StorageCore.isAndroidMotionPath(asset.originalPath)) {
|
||||
// External assets are not affected by storage template
|
||||
// TODO: shouldn't this only apply to external assets?
|
||||
return;
|
||||
|
|
|
@ -67,7 +67,7 @@ export function searchAssetBuilder(
|
|||
});
|
||||
}
|
||||
|
||||
const status = _.pick(options, ['isExternal', 'isFavorite', 'isOffline', 'isReadOnly', 'isVisible', 'type']);
|
||||
const status = _.pick(options, ['isFavorite', 'isOffline', 'isVisible', 'type']);
|
||||
const {
|
||||
isArchived,
|
||||
isEncoded,
|
||||
|
|
19
server/test/fixtures/asset.stub.ts
vendored
19
server/test/fixtures/asset.stub.ts
vendored
|
@ -45,7 +45,6 @@ export const assetStub = {
|
|||
sharedLinks: [],
|
||||
faces: [],
|
||||
sidecarPath: null,
|
||||
isReadOnly: false,
|
||||
deletedAt: null,
|
||||
isOffline: false,
|
||||
isExternal: false,
|
||||
|
@ -82,7 +81,6 @@ export const assetStub = {
|
|||
originalFileName: 'IMG_456.jpg',
|
||||
faces: [],
|
||||
sidecarPath: null,
|
||||
isReadOnly: false,
|
||||
isOffline: false,
|
||||
isExternal: false,
|
||||
libraryId: 'library-id',
|
||||
|
@ -113,7 +111,6 @@ export const assetStub = {
|
|||
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
|
||||
isFavorite: true,
|
||||
isArchived: false,
|
||||
isReadOnly: false,
|
||||
isOffline: false,
|
||||
libraryId: 'library-id',
|
||||
library: libraryStub.uploadLibrary1,
|
||||
|
@ -150,7 +147,6 @@ export const assetStub = {
|
|||
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
|
||||
isFavorite: true,
|
||||
isArchived: false,
|
||||
isReadOnly: false,
|
||||
duration: null,
|
||||
isVisible: true,
|
||||
isExternal: false,
|
||||
|
@ -195,7 +191,6 @@ export const assetStub = {
|
|||
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
|
||||
isFavorite: true,
|
||||
isArchived: false,
|
||||
isReadOnly: false,
|
||||
duration: null,
|
||||
isVisible: true,
|
||||
isExternal: false,
|
||||
|
@ -235,7 +230,6 @@ export const assetStub = {
|
|||
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
|
||||
isFavorite: true,
|
||||
isArchived: false,
|
||||
isReadOnly: false,
|
||||
isExternal: true,
|
||||
duration: null,
|
||||
isVisible: true,
|
||||
|
@ -275,7 +269,6 @@ export const assetStub = {
|
|||
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
|
||||
isFavorite: true,
|
||||
isArchived: false,
|
||||
isReadOnly: false,
|
||||
isExternal: false,
|
||||
duration: null,
|
||||
isVisible: true,
|
||||
|
@ -315,7 +308,6 @@ export const assetStub = {
|
|||
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
|
||||
isFavorite: true,
|
||||
isArchived: false,
|
||||
isReadOnly: false,
|
||||
isExternal: true,
|
||||
duration: null,
|
||||
isVisible: true,
|
||||
|
@ -356,7 +348,6 @@ export const assetStub = {
|
|||
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
|
||||
isFavorite: true,
|
||||
isArchived: false,
|
||||
isReadOnly: false,
|
||||
duration: null,
|
||||
isVisible: true,
|
||||
livePhotoVideo: null,
|
||||
|
@ -396,7 +387,6 @@ export const assetStub = {
|
|||
isFavorite: true,
|
||||
isArchived: false,
|
||||
isExternal: false,
|
||||
isReadOnly: false,
|
||||
isOffline: false,
|
||||
libraryId: 'library-id',
|
||||
library: libraryStub.uploadLibrary1,
|
||||
|
@ -436,7 +426,6 @@ export const assetStub = {
|
|||
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
|
||||
isFavorite: true,
|
||||
isArchived: false,
|
||||
isReadOnly: false,
|
||||
isExternal: false,
|
||||
isOffline: false,
|
||||
libraryId: 'library-id',
|
||||
|
@ -527,7 +516,6 @@ export const assetStub = {
|
|||
localDateTime: new Date('2023-02-22T05:06:29.716Z'),
|
||||
isFavorite: false,
|
||||
isArchived: false,
|
||||
isReadOnly: false,
|
||||
isExternal: false,
|
||||
isOffline: false,
|
||||
libraryId: 'library-id',
|
||||
|
@ -570,7 +558,6 @@ export const assetStub = {
|
|||
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
|
||||
isFavorite: true,
|
||||
isArchived: false,
|
||||
isReadOnly: false,
|
||||
isExternal: false,
|
||||
isOffline: false,
|
||||
libraryId: 'library-id',
|
||||
|
@ -606,7 +593,6 @@ export const assetStub = {
|
|||
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
|
||||
isFavorite: true,
|
||||
isArchived: false,
|
||||
isReadOnly: false,
|
||||
isExternal: false,
|
||||
isOffline: false,
|
||||
libraryId: 'library-id',
|
||||
|
@ -643,7 +629,6 @@ export const assetStub = {
|
|||
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
|
||||
isFavorite: true,
|
||||
isArchived: false,
|
||||
isReadOnly: true,
|
||||
isExternal: false,
|
||||
isOffline: false,
|
||||
libraryId: 'library-id',
|
||||
|
@ -681,7 +666,6 @@ export const assetStub = {
|
|||
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
|
||||
isFavorite: true,
|
||||
isArchived: false,
|
||||
isReadOnly: false,
|
||||
isExternal: false,
|
||||
isOffline: false,
|
||||
libraryId: 'library-id',
|
||||
|
@ -719,7 +703,6 @@ export const assetStub = {
|
|||
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
|
||||
isFavorite: true,
|
||||
isArchived: false,
|
||||
isReadOnly: false,
|
||||
isExternal: true,
|
||||
duration: null,
|
||||
isVisible: true,
|
||||
|
@ -758,7 +741,6 @@ export const assetStub = {
|
|||
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
|
||||
isFavorite: true,
|
||||
isArchived: false,
|
||||
isReadOnly: false,
|
||||
isExternal: true,
|
||||
duration: null,
|
||||
isVisible: true,
|
||||
|
@ -797,7 +779,6 @@ export const assetStub = {
|
|||
localDateTime: new Date('2023-02-23T05:06:29.716Z'),
|
||||
isFavorite: true,
|
||||
isArchived: false,
|
||||
isReadOnly: false,
|
||||
duration: null,
|
||||
isVisible: true,
|
||||
isExternal: false,
|
||||
|
|
2
server/test/fixtures/shared-link.stub.ts
vendored
2
server/test/fixtures/shared-link.stub.ts
vendored
|
@ -59,7 +59,6 @@ const assetResponse: AssetResponseDto = {
|
|||
thumbhash: null,
|
||||
fileModifiedAt: today,
|
||||
isExternal: false,
|
||||
isReadOnly: false,
|
||||
isOffline: false,
|
||||
fileCreatedAt: today,
|
||||
localDateTime: today,
|
||||
|
@ -210,7 +209,6 @@ export const sharedLinkStub = {
|
|||
isFavorite: false,
|
||||
isArchived: false,
|
||||
isExternal: false,
|
||||
isReadOnly: false,
|
||||
isOffline: false,
|
||||
libraryId: 'library-id',
|
||||
library: libraryStub.uploadLibrary1,
|
||||
|
|
|
@ -193,9 +193,7 @@
|
|||
{/if}
|
||||
|
||||
{#if isOwner}
|
||||
{#if !asset.isReadOnly || !asset.isExternal}
|
||||
<CircleIconButton color="opaque" icon={mdiDeleteOutline} on:click={() => dispatch('delete')} title="Delete" />
|
||||
{/if}
|
||||
<CircleIconButton color="opaque" icon={mdiDeleteOutline} on:click={() => dispatch('delete')} title="Delete" />
|
||||
<div
|
||||
use:clickOutside={{
|
||||
onOutclick: () => (isShowAssetOptions = false),
|
||||
|
|
|
@ -308,23 +308,15 @@
|
|||
{/if}
|
||||
|
||||
<div class="px-4 py-4">
|
||||
{#if !asset.exifInfo && !asset.isExternal}
|
||||
<p class="text-sm">NO EXIF INFO AVAILABLE</p>
|
||||
{:else if !asset.exifInfo && asset.isExternal}
|
||||
<div class="flex gap-4 py-4">
|
||||
<div>
|
||||
<p class="break-all">
|
||||
Metadata not loaded for {asset.originalPath}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
{#if asset.exifInfo}
|
||||
<div class="flex h-10 w-full items-center justify-between text-sm">
|
||||
<h2>DETAILS</h2>
|
||||
</div>
|
||||
{:else}
|
||||
<p class="text-sm">NO EXIF INFO AVAILABLE</p>
|
||||
{/if}
|
||||
|
||||
{#if asset.exifInfo?.dateTimeOriginal && !asset.isReadOnly}
|
||||
{#if asset.exifInfo?.dateTimeOriginal}
|
||||
{@const assetDateTimeOriginal = DateTime.fromISO(asset.exifInfo.dateTimeOriginal, {
|
||||
zone: asset.exifInfo.timeZone ?? undefined,
|
||||
})}
|
||||
|
@ -374,7 +366,7 @@
|
|||
</div>
|
||||
{/if}
|
||||
</button>
|
||||
{:else if !asset.exifInfo?.dateTimeOriginal && !asset.isReadOnly && isOwner}
|
||||
{:else if !asset.exifInfo?.dateTimeOriginal && isOwner}
|
||||
<div class="flex justify-between place-items-start gap-4 py-4">
|
||||
<div class="flex gap-4">
|
||||
<div>
|
||||
|
@ -385,43 +377,6 @@
|
|||
<Icon path={mdiPencil} size="20" />
|
||||
</div>
|
||||
</div>
|
||||
{:else if asset.exifInfo?.dateTimeOriginal && asset.isReadOnly}
|
||||
{@const assetDateTimeOriginal = DateTime.fromISO(asset.exifInfo.dateTimeOriginal, {
|
||||
zone: asset.exifInfo.timeZone ?? undefined,
|
||||
})}
|
||||
<div class="flex justify-between place-items-start gap-4 py-4">
|
||||
<div class="flex gap-4">
|
||||
<div>
|
||||
<Icon path={mdiCalendar} size="24" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p>
|
||||
{assetDateTimeOriginal.toLocaleString(
|
||||
{
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
},
|
||||
{ locale: $locale },
|
||||
)}
|
||||
</p>
|
||||
<div class="flex gap-2 text-sm">
|
||||
<p>
|
||||
{assetDateTimeOriginal.toLocaleString(
|
||||
{
|
||||
weekday: 'short',
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
timeZoneName: 'longOffset',
|
||||
},
|
||||
{ locale: $locale },
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if isShowChangeDate}
|
||||
|
@ -501,7 +456,7 @@
|
|||
</div>
|
||||
{/if}
|
||||
|
||||
{#if asset.exifInfo?.city && !asset.isReadOnly}
|
||||
{#if asset.exifInfo?.city}
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full text-left justify-between place-items-start gap-4 py-4"
|
||||
|
@ -534,7 +489,7 @@
|
|||
</div>
|
||||
{/if}
|
||||
</button>
|
||||
{:else if !asset.exifInfo?.city && !asset.isReadOnly && isOwner}
|
||||
{:else if !asset.exifInfo?.city && isOwner}
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full text-left justify-between place-items-start gap-4 py-4 rounded-lg hover:dark:text-immich-dark-primary hover:text-immich-primary"
|
||||
|
@ -552,26 +507,6 @@
|
|||
<Icon path={mdiPencil} size="20" />
|
||||
</div>
|
||||
</button>
|
||||
{:else if asset.exifInfo?.city && asset.isReadOnly}
|
||||
<div class="flex justify-between place-items-start gap-4 py-4">
|
||||
<div class="flex gap-4">
|
||||
<div><Icon path={mdiMapMarkerOutline} size="24" /></div>
|
||||
|
||||
<div>
|
||||
<p>{asset.exifInfo.city}</p>
|
||||
{#if asset.exifInfo?.state}
|
||||
<div class="flex gap-2 text-sm">
|
||||
<p>{asset.exifInfo.state}</p>
|
||||
</div>
|
||||
{/if}
|
||||
{#if asset.exifInfo?.country}
|
||||
<div class="flex gap-2 text-sm">
|
||||
<p>{asset.exifInfo.country}</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if isShowChangeLocation}
|
||||
<ChangeLocation
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
|
||||
const handleDelete = async () => {
|
||||
loading = true;
|
||||
const ids = [...getOwnedAssets()].filter((a) => !a.isExternal).map((a) => a.id);
|
||||
const ids = [...getOwnedAssets()].map((a) => a.id);
|
||||
await deleteAssets(force, onAssetDelete, ids);
|
||||
clearSelect();
|
||||
isShowConfirmation = false;
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
|
||||
$: timelineY = element?.scrollTop || 0;
|
||||
$: isEmpty = $assetStore.initialized && $assetStore.buckets.length === 0;
|
||||
$: idsSelectedAssets = [...$selectedAssets].filter((a) => !a.isExternal).map((a) => a.id);
|
||||
$: idsSelectedAssets = [...$selectedAssets].map(({ id }) => id);
|
||||
|
||||
$: {
|
||||
void assetStore.updateViewport(viewport);
|
||||
|
|
|
@ -258,9 +258,9 @@ export const getAssetType = (type: AssetTypeEnum) => {
|
|||
};
|
||||
|
||||
export const getSelectedAssets = (assets: Set<AssetResponseDto>, user: UserResponseDto | null): string[] => {
|
||||
const ids = [...assets].filter((a) => !a.isExternal && user && a.ownerId === user.id).map((a) => a.id);
|
||||
const ids = [...assets].filter((a) => user && a.ownerId === user.id).map((a) => a.id);
|
||||
|
||||
const numberOfIssues = [...assets].filter((a) => a.isExternal || (user && a.ownerId !== user.id)).length;
|
||||
const numberOfIssues = [...assets].filter((a) => user && a.ownerId !== user.id).length;
|
||||
if (numberOfIssues > 0) {
|
||||
notificationController.show({
|
||||
message: `Can't change metadata of ${numberOfIssues} asset${numberOfIssues > 1 ? 's' : ''}`,
|
||||
|
|
|
@ -22,9 +22,7 @@ export const assetFactory = Sync.makeFactory<AssetResponseDto>({
|
|||
isTrashed: Sync.each(() => faker.datatype.boolean()),
|
||||
duration: '0:00:00.00000',
|
||||
checksum: Sync.each(() => faker.string.alphanumeric(28)),
|
||||
isExternal: Sync.each(() => faker.datatype.boolean()),
|
||||
isOffline: Sync.each(() => faker.datatype.boolean()),
|
||||
isReadOnly: Sync.each(() => faker.datatype.boolean()),
|
||||
hasMetadata: Sync.each(() => faker.datatype.boolean()),
|
||||
stackCount: null,
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue