mirror of
https://github.com/immich-app/immich.git
synced 2025-05-24 01:12:58 -04:00
feat(web/server) Add more options to public shared link (#1348)
* Added migration files * Added logic for shared album level * Added permission for EXIF * Update shared link response dto * Added condition to show download button * Create and edit link with new parameter: * Remove deadcode * PR feedback * More refactor * Move logic of allow original file to service * Simplify * Wording
This commit is contained in:
parent
4cfac47674
commit
b07891089f
@ -99,7 +99,7 @@ After making any changes in the `server/libs/database/src/entities`, a database
|
|||||||
2. Run
|
2. Run
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run typeorm -- migration:generate ./libs/database/src/<migration-name> -d libs/database/src/config/database.config.ts
|
npm run typeorm -- migration:generate ./libs/infra/src/db/<migration-name> -d ./libs/infra/src/db/config/database.config.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Check if the migration file makes sense.
|
3. Check if the migration file makes sense.
|
||||||
|
2
mobile/openapi/doc/CreateAlbumShareLinkDto.md
generated
2
mobile/openapi/doc/CreateAlbumShareLinkDto.md
generated
@ -11,6 +11,8 @@ Name | Type | Description | Notes
|
|||||||
**albumId** | **String** | |
|
**albumId** | **String** | |
|
||||||
**expiredAt** | **String** | | [optional]
|
**expiredAt** | **String** | | [optional]
|
||||||
**allowUpload** | **bool** | | [optional]
|
**allowUpload** | **bool** | | [optional]
|
||||||
|
**allowDownload** | **bool** | | [optional]
|
||||||
|
**showExif** | **bool** | | [optional]
|
||||||
**description** | **String** | | [optional]
|
**description** | **String** | | [optional]
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
|
2
mobile/openapi/doc/CreateAssetsShareLinkDto.md
generated
2
mobile/openapi/doc/CreateAssetsShareLinkDto.md
generated
@ -11,6 +11,8 @@ Name | Type | Description | Notes
|
|||||||
**assetIds** | **List<String>** | | [default to const []]
|
**assetIds** | **List<String>** | | [default to const []]
|
||||||
**expiredAt** | **String** | | [optional]
|
**expiredAt** | **String** | | [optional]
|
||||||
**allowUpload** | **bool** | | [optional]
|
**allowUpload** | **bool** | | [optional]
|
||||||
|
**allowDownload** | **bool** | | [optional]
|
||||||
|
**showExif** | **bool** | | [optional]
|
||||||
**description** | **String** | | [optional]
|
**description** | **String** | | [optional]
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
|
2
mobile/openapi/doc/EditSharedLinkDto.md
generated
2
mobile/openapi/doc/EditSharedLinkDto.md
generated
@ -11,6 +11,8 @@ Name | Type | Description | Notes
|
|||||||
**description** | **String** | | [optional]
|
**description** | **String** | | [optional]
|
||||||
**expiredAt** | **String** | | [optional]
|
**expiredAt** | **String** | | [optional]
|
||||||
**allowUpload** | **bool** | | [optional]
|
**allowUpload** | **bool** | | [optional]
|
||||||
|
**allowDownload** | **bool** | | [optional]
|
||||||
|
**showExif** | **bool** | | [optional]
|
||||||
**isEditExpireTime** | **bool** | | [optional]
|
**isEditExpireTime** | **bool** | | [optional]
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
|
2
mobile/openapi/doc/SharedLinkResponseDto.md
generated
2
mobile/openapi/doc/SharedLinkResponseDto.md
generated
@ -18,6 +18,8 @@ Name | Type | Description | Notes
|
|||||||
**assets** | [**List<AssetResponseDto>**](AssetResponseDto.md) | | [default to const []]
|
**assets** | [**List<AssetResponseDto>**](AssetResponseDto.md) | | [default to const []]
|
||||||
**album** | [**AlbumResponseDto**](AlbumResponseDto.md) | | [optional]
|
**album** | [**AlbumResponseDto**](AlbumResponseDto.md) | | [optional]
|
||||||
**allowUpload** | **bool** | |
|
**allowUpload** | **bool** | |
|
||||||
|
**allowDownload** | **bool** | |
|
||||||
|
**showExif** | **bool** | |
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
|
|
||||||
|
@ -16,6 +16,8 @@ class CreateAlbumShareLinkDto {
|
|||||||
required this.albumId,
|
required this.albumId,
|
||||||
this.expiredAt,
|
this.expiredAt,
|
||||||
this.allowUpload,
|
this.allowUpload,
|
||||||
|
this.allowDownload,
|
||||||
|
this.showExif,
|
||||||
this.description,
|
this.description,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -37,6 +39,22 @@ class CreateAlbumShareLinkDto {
|
|||||||
///
|
///
|
||||||
bool? allowUpload;
|
bool? allowUpload;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// 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? allowDownload;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// 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? showExif;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Please note: This property should have been non-nullable! Since the specification file
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
/// does not include a default value (using the "default:" property), however, the generated
|
/// does not include a default value (using the "default:" property), however, the generated
|
||||||
@ -50,6 +68,8 @@ class CreateAlbumShareLinkDto {
|
|||||||
other.albumId == albumId &&
|
other.albumId == albumId &&
|
||||||
other.expiredAt == expiredAt &&
|
other.expiredAt == expiredAt &&
|
||||||
other.allowUpload == allowUpload &&
|
other.allowUpload == allowUpload &&
|
||||||
|
other.allowDownload == allowDownload &&
|
||||||
|
other.showExif == showExif &&
|
||||||
other.description == description;
|
other.description == description;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -58,10 +78,12 @@ class CreateAlbumShareLinkDto {
|
|||||||
(albumId.hashCode) +
|
(albumId.hashCode) +
|
||||||
(expiredAt == null ? 0 : expiredAt!.hashCode) +
|
(expiredAt == null ? 0 : expiredAt!.hashCode) +
|
||||||
(allowUpload == null ? 0 : allowUpload!.hashCode) +
|
(allowUpload == null ? 0 : allowUpload!.hashCode) +
|
||||||
|
(allowDownload == null ? 0 : allowDownload!.hashCode) +
|
||||||
|
(showExif == null ? 0 : showExif!.hashCode) +
|
||||||
(description == null ? 0 : description!.hashCode);
|
(description == null ? 0 : description!.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'CreateAlbumShareLinkDto[albumId=$albumId, expiredAt=$expiredAt, allowUpload=$allowUpload, description=$description]';
|
String toString() => 'CreateAlbumShareLinkDto[albumId=$albumId, expiredAt=$expiredAt, allowUpload=$allowUpload, allowDownload=$allowDownload, showExif=$showExif, description=$description]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
@ -76,6 +98,16 @@ class CreateAlbumShareLinkDto {
|
|||||||
} else {
|
} else {
|
||||||
// json[r'allowUpload'] = null;
|
// json[r'allowUpload'] = null;
|
||||||
}
|
}
|
||||||
|
if (this.allowDownload != null) {
|
||||||
|
json[r'allowDownload'] = this.allowDownload;
|
||||||
|
} else {
|
||||||
|
// json[r'allowDownload'] = null;
|
||||||
|
}
|
||||||
|
if (this.showExif != null) {
|
||||||
|
json[r'showExif'] = this.showExif;
|
||||||
|
} else {
|
||||||
|
// json[r'showExif'] = null;
|
||||||
|
}
|
||||||
if (this.description != null) {
|
if (this.description != null) {
|
||||||
json[r'description'] = this.description;
|
json[r'description'] = this.description;
|
||||||
} else {
|
} else {
|
||||||
@ -106,6 +138,8 @@ class CreateAlbumShareLinkDto {
|
|||||||
albumId: mapValueOfType<String>(json, r'albumId')!,
|
albumId: mapValueOfType<String>(json, r'albumId')!,
|
||||||
expiredAt: mapValueOfType<String>(json, r'expiredAt'),
|
expiredAt: mapValueOfType<String>(json, r'expiredAt'),
|
||||||
allowUpload: mapValueOfType<bool>(json, r'allowUpload'),
|
allowUpload: mapValueOfType<bool>(json, r'allowUpload'),
|
||||||
|
allowDownload: mapValueOfType<bool>(json, r'allowDownload'),
|
||||||
|
showExif: mapValueOfType<bool>(json, r'showExif'),
|
||||||
description: mapValueOfType<String>(json, r'description'),
|
description: mapValueOfType<String>(json, r'description'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,8 @@ class CreateAssetsShareLinkDto {
|
|||||||
this.assetIds = const [],
|
this.assetIds = const [],
|
||||||
this.expiredAt,
|
this.expiredAt,
|
||||||
this.allowUpload,
|
this.allowUpload,
|
||||||
|
this.allowDownload,
|
||||||
|
this.showExif,
|
||||||
this.description,
|
this.description,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -37,6 +39,22 @@ class CreateAssetsShareLinkDto {
|
|||||||
///
|
///
|
||||||
bool? allowUpload;
|
bool? allowUpload;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// 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? allowDownload;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// 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? showExif;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Please note: This property should have been non-nullable! Since the specification file
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
/// does not include a default value (using the "default:" property), however, the generated
|
/// does not include a default value (using the "default:" property), however, the generated
|
||||||
@ -50,6 +68,8 @@ class CreateAssetsShareLinkDto {
|
|||||||
other.assetIds == assetIds &&
|
other.assetIds == assetIds &&
|
||||||
other.expiredAt == expiredAt &&
|
other.expiredAt == expiredAt &&
|
||||||
other.allowUpload == allowUpload &&
|
other.allowUpload == allowUpload &&
|
||||||
|
other.allowDownload == allowDownload &&
|
||||||
|
other.showExif == showExif &&
|
||||||
other.description == description;
|
other.description == description;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -58,10 +78,12 @@ class CreateAssetsShareLinkDto {
|
|||||||
(assetIds.hashCode) +
|
(assetIds.hashCode) +
|
||||||
(expiredAt == null ? 0 : expiredAt!.hashCode) +
|
(expiredAt == null ? 0 : expiredAt!.hashCode) +
|
||||||
(allowUpload == null ? 0 : allowUpload!.hashCode) +
|
(allowUpload == null ? 0 : allowUpload!.hashCode) +
|
||||||
|
(allowDownload == null ? 0 : allowDownload!.hashCode) +
|
||||||
|
(showExif == null ? 0 : showExif!.hashCode) +
|
||||||
(description == null ? 0 : description!.hashCode);
|
(description == null ? 0 : description!.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'CreateAssetsShareLinkDto[assetIds=$assetIds, expiredAt=$expiredAt, allowUpload=$allowUpload, description=$description]';
|
String toString() => 'CreateAssetsShareLinkDto[assetIds=$assetIds, expiredAt=$expiredAt, allowUpload=$allowUpload, allowDownload=$allowDownload, showExif=$showExif, description=$description]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
@ -76,6 +98,16 @@ class CreateAssetsShareLinkDto {
|
|||||||
} else {
|
} else {
|
||||||
// json[r'allowUpload'] = null;
|
// json[r'allowUpload'] = null;
|
||||||
}
|
}
|
||||||
|
if (this.allowDownload != null) {
|
||||||
|
json[r'allowDownload'] = this.allowDownload;
|
||||||
|
} else {
|
||||||
|
// json[r'allowDownload'] = null;
|
||||||
|
}
|
||||||
|
if (this.showExif != null) {
|
||||||
|
json[r'showExif'] = this.showExif;
|
||||||
|
} else {
|
||||||
|
// json[r'showExif'] = null;
|
||||||
|
}
|
||||||
if (this.description != null) {
|
if (this.description != null) {
|
||||||
json[r'description'] = this.description;
|
json[r'description'] = this.description;
|
||||||
} else {
|
} else {
|
||||||
@ -108,6 +140,8 @@ class CreateAssetsShareLinkDto {
|
|||||||
: const [],
|
: const [],
|
||||||
expiredAt: mapValueOfType<String>(json, r'expiredAt'),
|
expiredAt: mapValueOfType<String>(json, r'expiredAt'),
|
||||||
allowUpload: mapValueOfType<bool>(json, r'allowUpload'),
|
allowUpload: mapValueOfType<bool>(json, r'allowUpload'),
|
||||||
|
allowDownload: mapValueOfType<bool>(json, r'allowDownload'),
|
||||||
|
showExif: mapValueOfType<bool>(json, r'showExif'),
|
||||||
description: mapValueOfType<String>(json, r'description'),
|
description: mapValueOfType<String>(json, r'description'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
36
mobile/openapi/lib/model/edit_shared_link_dto.dart
generated
36
mobile/openapi/lib/model/edit_shared_link_dto.dart
generated
@ -16,6 +16,8 @@ class EditSharedLinkDto {
|
|||||||
this.description,
|
this.description,
|
||||||
this.expiredAt,
|
this.expiredAt,
|
||||||
this.allowUpload,
|
this.allowUpload,
|
||||||
|
this.allowDownload,
|
||||||
|
this.showExif,
|
||||||
this.isEditExpireTime,
|
this.isEditExpireTime,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -43,6 +45,22 @@ class EditSharedLinkDto {
|
|||||||
///
|
///
|
||||||
bool? allowUpload;
|
bool? allowUpload;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// 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? allowDownload;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// 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? showExif;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Please note: This property should have been non-nullable! Since the specification file
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
/// does not include a default value (using the "default:" property), however, the generated
|
/// does not include a default value (using the "default:" property), however, the generated
|
||||||
@ -56,6 +74,8 @@ class EditSharedLinkDto {
|
|||||||
other.description == description &&
|
other.description == description &&
|
||||||
other.expiredAt == expiredAt &&
|
other.expiredAt == expiredAt &&
|
||||||
other.allowUpload == allowUpload &&
|
other.allowUpload == allowUpload &&
|
||||||
|
other.allowDownload == allowDownload &&
|
||||||
|
other.showExif == showExif &&
|
||||||
other.isEditExpireTime == isEditExpireTime;
|
other.isEditExpireTime == isEditExpireTime;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -64,10 +84,12 @@ class EditSharedLinkDto {
|
|||||||
(description == null ? 0 : description!.hashCode) +
|
(description == null ? 0 : description!.hashCode) +
|
||||||
(expiredAt == null ? 0 : expiredAt!.hashCode) +
|
(expiredAt == null ? 0 : expiredAt!.hashCode) +
|
||||||
(allowUpload == null ? 0 : allowUpload!.hashCode) +
|
(allowUpload == null ? 0 : allowUpload!.hashCode) +
|
||||||
|
(allowDownload == null ? 0 : allowDownload!.hashCode) +
|
||||||
|
(showExif == null ? 0 : showExif!.hashCode) +
|
||||||
(isEditExpireTime == null ? 0 : isEditExpireTime!.hashCode);
|
(isEditExpireTime == null ? 0 : isEditExpireTime!.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'EditSharedLinkDto[description=$description, expiredAt=$expiredAt, allowUpload=$allowUpload, isEditExpireTime=$isEditExpireTime]';
|
String toString() => 'EditSharedLinkDto[description=$description, expiredAt=$expiredAt, allowUpload=$allowUpload, allowDownload=$allowDownload, showExif=$showExif, isEditExpireTime=$isEditExpireTime]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
@ -86,6 +108,16 @@ class EditSharedLinkDto {
|
|||||||
} else {
|
} else {
|
||||||
// json[r'allowUpload'] = null;
|
// json[r'allowUpload'] = null;
|
||||||
}
|
}
|
||||||
|
if (this.allowDownload != null) {
|
||||||
|
json[r'allowDownload'] = this.allowDownload;
|
||||||
|
} else {
|
||||||
|
// json[r'allowDownload'] = null;
|
||||||
|
}
|
||||||
|
if (this.showExif != null) {
|
||||||
|
json[r'showExif'] = this.showExif;
|
||||||
|
} else {
|
||||||
|
// json[r'showExif'] = null;
|
||||||
|
}
|
||||||
if (this.isEditExpireTime != null) {
|
if (this.isEditExpireTime != null) {
|
||||||
json[r'isEditExpireTime'] = this.isEditExpireTime;
|
json[r'isEditExpireTime'] = this.isEditExpireTime;
|
||||||
} else {
|
} else {
|
||||||
@ -116,6 +148,8 @@ class EditSharedLinkDto {
|
|||||||
description: mapValueOfType<String>(json, r'description'),
|
description: mapValueOfType<String>(json, r'description'),
|
||||||
expiredAt: mapValueOfType<String>(json, r'expiredAt'),
|
expiredAt: mapValueOfType<String>(json, r'expiredAt'),
|
||||||
allowUpload: mapValueOfType<bool>(json, r'allowUpload'),
|
allowUpload: mapValueOfType<bool>(json, r'allowUpload'),
|
||||||
|
allowDownload: mapValueOfType<bool>(json, r'allowDownload'),
|
||||||
|
showExif: mapValueOfType<bool>(json, r'showExif'),
|
||||||
isEditExpireTime: mapValueOfType<bool>(json, r'isEditExpireTime'),
|
isEditExpireTime: mapValueOfType<bool>(json, r'isEditExpireTime'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,8 @@ class SharedLinkResponseDto {
|
|||||||
this.assets = const [],
|
this.assets = const [],
|
||||||
this.album,
|
this.album,
|
||||||
required this.allowUpload,
|
required this.allowUpload,
|
||||||
|
required this.allowDownload,
|
||||||
|
required this.showExif,
|
||||||
});
|
});
|
||||||
|
|
||||||
SharedLinkType type;
|
SharedLinkType type;
|
||||||
@ -57,6 +59,10 @@ class SharedLinkResponseDto {
|
|||||||
|
|
||||||
bool allowUpload;
|
bool allowUpload;
|
||||||
|
|
||||||
|
bool allowDownload;
|
||||||
|
|
||||||
|
bool showExif;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) => identical(this, other) || other is SharedLinkResponseDto &&
|
bool operator ==(Object other) => identical(this, other) || other is SharedLinkResponseDto &&
|
||||||
other.type == type &&
|
other.type == type &&
|
||||||
@ -68,7 +74,9 @@ class SharedLinkResponseDto {
|
|||||||
other.expiresAt == expiresAt &&
|
other.expiresAt == expiresAt &&
|
||||||
other.assets == assets &&
|
other.assets == assets &&
|
||||||
other.album == album &&
|
other.album == album &&
|
||||||
other.allowUpload == allowUpload;
|
other.allowUpload == allowUpload &&
|
||||||
|
other.allowDownload == allowDownload &&
|
||||||
|
other.showExif == showExif;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
@ -82,10 +90,12 @@ class SharedLinkResponseDto {
|
|||||||
(expiresAt == null ? 0 : expiresAt!.hashCode) +
|
(expiresAt == null ? 0 : expiresAt!.hashCode) +
|
||||||
(assets.hashCode) +
|
(assets.hashCode) +
|
||||||
(album == null ? 0 : album!.hashCode) +
|
(album == null ? 0 : album!.hashCode) +
|
||||||
(allowUpload.hashCode);
|
(allowUpload.hashCode) +
|
||||||
|
(allowDownload.hashCode) +
|
||||||
|
(showExif.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'SharedLinkResponseDto[type=$type, id=$id, description=$description, userId=$userId, key=$key, createdAt=$createdAt, expiresAt=$expiresAt, assets=$assets, album=$album, allowUpload=$allowUpload]';
|
String toString() => 'SharedLinkResponseDto[type=$type, id=$id, description=$description, userId=$userId, key=$key, createdAt=$createdAt, expiresAt=$expiresAt, assets=$assets, album=$album, allowUpload=$allowUpload, allowDownload=$allowDownload, showExif=$showExif]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
@ -111,6 +121,8 @@ class SharedLinkResponseDto {
|
|||||||
// json[r'album'] = null;
|
// json[r'album'] = null;
|
||||||
}
|
}
|
||||||
json[r'allowUpload'] = this.allowUpload;
|
json[r'allowUpload'] = this.allowUpload;
|
||||||
|
json[r'allowDownload'] = this.allowDownload;
|
||||||
|
json[r'showExif'] = this.showExif;
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,6 +155,8 @@ class SharedLinkResponseDto {
|
|||||||
assets: AssetResponseDto.listFromJson(json[r'assets'])!,
|
assets: AssetResponseDto.listFromJson(json[r'assets'])!,
|
||||||
album: AlbumResponseDto.fromJson(json[r'album']),
|
album: AlbumResponseDto.fromJson(json[r'album']),
|
||||||
allowUpload: mapValueOfType<bool>(json, r'allowUpload')!,
|
allowUpload: mapValueOfType<bool>(json, r'allowUpload')!,
|
||||||
|
allowDownload: mapValueOfType<bool>(json, r'allowDownload')!,
|
||||||
|
showExif: mapValueOfType<bool>(json, r'showExif')!,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@ -200,6 +214,8 @@ class SharedLinkResponseDto {
|
|||||||
'expiresAt',
|
'expiresAt',
|
||||||
'assets',
|
'assets',
|
||||||
'allowUpload',
|
'allowUpload',
|
||||||
|
'allowDownload',
|
||||||
|
'showExif',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,6 +31,16 @@ void main() {
|
|||||||
// TODO
|
// TODO
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// bool allowDownload
|
||||||
|
test('to test the property `allowDownload`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
// bool showExif
|
||||||
|
test('to test the property `showExif`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
// String description
|
// String description
|
||||||
test('to test the property `description`', () async {
|
test('to test the property `description`', () async {
|
||||||
// TODO
|
// TODO
|
||||||
|
@ -31,6 +31,16 @@ void main() {
|
|||||||
// TODO
|
// TODO
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// bool allowDownload
|
||||||
|
test('to test the property `allowDownload`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
// bool showExif
|
||||||
|
test('to test the property `showExif`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
// String description
|
// String description
|
||||||
test('to test the property `description`', () async {
|
test('to test the property `description`', () async {
|
||||||
// TODO
|
// TODO
|
||||||
|
10
mobile/openapi/test/edit_shared_link_dto_test.dart
generated
10
mobile/openapi/test/edit_shared_link_dto_test.dart
generated
@ -31,6 +31,16 @@ void main() {
|
|||||||
// TODO
|
// TODO
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// bool allowDownload
|
||||||
|
test('to test the property `allowDownload`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
// bool showExif
|
||||||
|
test('to test the property `showExif`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
// bool isEditExpireTime
|
// bool isEditExpireTime
|
||||||
test('to test the property `isEditExpireTime`', () async {
|
test('to test the property `isEditExpireTime`', () async {
|
||||||
// TODO
|
// TODO
|
||||||
|
@ -66,6 +66,16 @@ void main() {
|
|||||||
// TODO
|
// TODO
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// bool allowDownload
|
||||||
|
test('to test the property `allowDownload`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
// bool showExif
|
||||||
|
test('to test the property `showExif`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -140,6 +140,8 @@ export class AlbumController {
|
|||||||
@Query(new ValidationPipe({ transform: true })) dto: DownloadDto,
|
@Query(new ValidationPipe({ transform: true })) dto: DownloadDto,
|
||||||
@Response({ passthrough: true }) res: Res,
|
@Response({ passthrough: true }) res: Res,
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
|
this.albumService.checkDownloadAccess(authUser);
|
||||||
|
|
||||||
const { stream, fileName, fileSize, fileCount, complete } = await this.albumService.downloadArchive(
|
const { stream, fileName, fileSize, fileCount, complete } = await this.albumService.downloadArchive(
|
||||||
authUser,
|
authUser,
|
||||||
albumId,
|
albumId,
|
||||||
|
@ -15,7 +15,7 @@ import { DownloadService } from '../../modules/download/download.service';
|
|||||||
import { DownloadDto } from '../asset/dto/download-library.dto';
|
import { DownloadDto } from '../asset/dto/download-library.dto';
|
||||||
import { ShareCore } from '../share/share.core';
|
import { ShareCore } from '../share/share.core';
|
||||||
import { ISharedLinkRepository } from '../share/shared-link.repository';
|
import { ISharedLinkRepository } from '../share/shared-link.repository';
|
||||||
import { mapSharedLinkToResponseDto, SharedLinkResponseDto } from '../share/response-dto/shared-link-response.dto';
|
import { mapSharedLink, SharedLinkResponseDto } from '../share/response-dto/shared-link-response.dto';
|
||||||
import { CreateAlbumShareLinkDto } from './dto/create-album-shared-link.dto';
|
import { CreateAlbumShareLinkDto } from './dto/create-album-shared-link.dto';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
@ -210,8 +210,14 @@ export class AlbumService {
|
|||||||
album: album,
|
album: album,
|
||||||
assets: [],
|
assets: [],
|
||||||
description: dto.description,
|
description: dto.description,
|
||||||
|
allowDownload: dto.allowDownload,
|
||||||
|
showExif: dto.showExif,
|
||||||
});
|
});
|
||||||
|
|
||||||
return mapSharedLinkToResponseDto(sharedLink);
|
return mapSharedLink(sharedLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkDownloadAccess(authUser: AuthUserDto) {
|
||||||
|
this.shareCore.checkDownloadAccess(authUser);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,14 @@ export class CreateAlbumShareLinkDto {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
allowUpload?: boolean;
|
allowUpload?: boolean;
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
@IsOptional()
|
||||||
|
allowDownload?: boolean;
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
@IsOptional()
|
||||||
|
showExif?: boolean;
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
description?: string;
|
description?: string;
|
||||||
|
@ -97,6 +97,7 @@ export class AssetController {
|
|||||||
@Query(new ValidationPipe({ transform: true })) query: ServeFileDto,
|
@Query(new ValidationPipe({ transform: true })) query: ServeFileDto,
|
||||||
@Param('assetId') assetId: string,
|
@Param('assetId') assetId: string,
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
|
this.assetService.checkDownloadAccess(authUser);
|
||||||
await this.assetService.checkAssetsAccess(authUser, [assetId]);
|
await this.assetService.checkAssetsAccess(authUser, [assetId]);
|
||||||
return this.assetService.downloadFile(query, assetId, res);
|
return this.assetService.downloadFile(query, assetId, res);
|
||||||
}
|
}
|
||||||
@ -108,6 +109,7 @@ export class AssetController {
|
|||||||
@Response({ passthrough: true }) res: Res,
|
@Response({ passthrough: true }) res: Res,
|
||||||
@Body(new ValidationPipe()) dto: DownloadFilesDto,
|
@Body(new ValidationPipe()) dto: DownloadFilesDto,
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
|
this.assetService.checkDownloadAccess(authUser);
|
||||||
await this.assetService.checkAssetsAccess(authUser, [...dto.assetIds]);
|
await this.assetService.checkAssetsAccess(authUser, [...dto.assetIds]);
|
||||||
const { stream, fileName, fileSize, fileCount, complete } = await this.assetService.downloadFiles(dto);
|
const { stream, fileName, fileSize, fileCount, complete } = await this.assetService.downloadFiles(dto);
|
||||||
res.attachment(fileName);
|
res.attachment(fileName);
|
||||||
@ -117,6 +119,9 @@ export class AssetController {
|
|||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current this is not used in any UI element
|
||||||
|
*/
|
||||||
@Authenticated({ isShared: true })
|
@Authenticated({ isShared: true })
|
||||||
@Get('/download-library')
|
@Get('/download-library')
|
||||||
async downloadLibrary(
|
async downloadLibrary(
|
||||||
@ -124,6 +129,7 @@ export class AssetController {
|
|||||||
@Query(new ValidationPipe({ transform: true })) dto: DownloadDto,
|
@Query(new ValidationPipe({ transform: true })) dto: DownloadDto,
|
||||||
@Response({ passthrough: true }) res: Res,
|
@Response({ passthrough: true }) res: Res,
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
|
this.assetService.checkDownloadAccess(authUser);
|
||||||
const { stream, fileName, fileSize, fileCount, complete } = await this.assetService.downloadLibrary(authUser, dto);
|
const { stream, fileName, fileSize, fileCount, complete } = await this.assetService.downloadLibrary(authUser, dto);
|
||||||
res.attachment(fileName);
|
res.attachment(fileName);
|
||||||
res.setHeader(IMMICH_CONTENT_LENGTH_HINT, fileSize);
|
res.setHeader(IMMICH_CONTENT_LENGTH_HINT, fileSize);
|
||||||
@ -143,7 +149,7 @@ export class AssetController {
|
|||||||
@Param('assetId') assetId: string,
|
@Param('assetId') assetId: string,
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
await this.assetService.checkAssetsAccess(authUser, [assetId]);
|
await this.assetService.checkAssetsAccess(authUser, [assetId]);
|
||||||
return this.assetService.serveFile(assetId, query, res, headers);
|
return this.assetService.serveFile(authUser, assetId, query, res, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Authenticated({ isShared: true })
|
@Authenticated({ isShared: true })
|
||||||
@ -246,7 +252,7 @@ export class AssetController {
|
|||||||
@Param('assetId') assetId: string,
|
@Param('assetId') assetId: string,
|
||||||
): Promise<AssetResponseDto> {
|
): Promise<AssetResponseDto> {
|
||||||
await this.assetService.checkAssetsAccess(authUser, [assetId]);
|
await this.assetService.checkAssetsAccess(authUser, [assetId]);
|
||||||
return await this.assetService.getAssetById(assetId);
|
return await this.assetService.getAssetById(authUser, assetId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -274,14 +280,14 @@ export class AssetController {
|
|||||||
const deleteAssetList: AssetResponseDto[] = [];
|
const deleteAssetList: AssetResponseDto[] = [];
|
||||||
|
|
||||||
for (const id of assetIds.ids) {
|
for (const id of assetIds.ids) {
|
||||||
const assets = await this.assetService.getAssetById(id);
|
const assets = await this.assetService.getAssetById(authUser, id);
|
||||||
if (!assets) {
|
if (!assets) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
deleteAssetList.push(assets);
|
deleteAssetList.push(assets);
|
||||||
|
|
||||||
if (assets.livePhotoVideoId) {
|
if (assets.livePhotoVideoId) {
|
||||||
const livePhotoVideo = await this.assetService.getAssetById(assets.livePhotoVideoId);
|
const livePhotoVideo = await this.assetService.getAssetById(authUser, assets.livePhotoVideoId);
|
||||||
if (livePhotoVideo) {
|
if (livePhotoVideo) {
|
||||||
deleteAssetList.push(livePhotoVideo);
|
deleteAssetList.push(livePhotoVideo);
|
||||||
assetIds.ids = [...assetIds.ids, livePhotoVideo.id];
|
assetIds.ids = [...assetIds.ids, livePhotoVideo.id];
|
||||||
|
@ -23,7 +23,7 @@ import { SearchAssetDto } from './dto/search-asset.dto';
|
|||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import { CheckDuplicateAssetDto } from './dto/check-duplicate-asset.dto';
|
import { CheckDuplicateAssetDto } from './dto/check-duplicate-asset.dto';
|
||||||
import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.dto';
|
import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.dto';
|
||||||
import { AssetResponseDto, mapAsset } from './response-dto/asset-response.dto';
|
import { AssetResponseDto, mapAsset, mapAssetWithoutExif } from './response-dto/asset-response.dto';
|
||||||
import { CreateAssetDto } from './dto/create-asset.dto';
|
import { CreateAssetDto } from './dto/create-asset.dto';
|
||||||
import { DeleteAssetResponseDto, DeleteAssetStatusEnum } from './response-dto/delete-asset-response.dto';
|
import { DeleteAssetResponseDto, DeleteAssetStatusEnum } from './response-dto/delete-asset-response.dto';
|
||||||
import { GetAssetThumbnailDto, GetAssetThumbnailFormatEnum } from './dto/get-asset-thumbnail.dto';
|
import { GetAssetThumbnailDto, GetAssetThumbnailFormatEnum } from './dto/get-asset-thumbnail.dto';
|
||||||
@ -52,7 +52,7 @@ import { ShareCore } from '../share/share.core';
|
|||||||
import { ISharedLinkRepository } from '../share/shared-link.repository';
|
import { ISharedLinkRepository } from '../share/shared-link.repository';
|
||||||
import { DownloadFilesDto } from './dto/download-files.dto';
|
import { DownloadFilesDto } from './dto/download-files.dto';
|
||||||
import { CreateAssetsShareLinkDto } from './dto/create-asset-shared-link.dto';
|
import { CreateAssetsShareLinkDto } from './dto/create-asset-shared-link.dto';
|
||||||
import { mapSharedLinkToResponseDto, SharedLinkResponseDto } from '../share/response-dto/shared-link-response.dto';
|
import { mapSharedLink, SharedLinkResponseDto } from '../share/response-dto/shared-link-response.dto';
|
||||||
import { UpdateAssetsToSharedLinkDto } from './dto/add-assets-to-shared-link.dto';
|
import { UpdateAssetsToSharedLinkDto } from './dto/add-assets-to-shared-link.dto';
|
||||||
|
|
||||||
const fileInfo = promisify(stat);
|
const fileInfo = promisify(stat);
|
||||||
@ -215,10 +215,15 @@ export class AssetService {
|
|||||||
return assets.map((asset) => mapAsset(asset));
|
return assets.map((asset) => mapAsset(asset));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAssetById(assetId: string): Promise<AssetResponseDto> {
|
public async getAssetById(authUser: AuthUserDto, assetId: string): Promise<AssetResponseDto> {
|
||||||
|
const allowExif = this.getExifPermission(authUser);
|
||||||
const asset = await this._assetRepository.getById(assetId);
|
const asset = await this._assetRepository.getById(assetId);
|
||||||
|
|
||||||
return mapAsset(asset);
|
if (allowExif) {
|
||||||
|
return mapAsset(asset);
|
||||||
|
} else {
|
||||||
|
return mapAssetWithoutExif(asset);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateAsset(authUser: AuthUserDto, assetId: string, dto: UpdateAssetDto): Promise<AssetResponseDto> {
|
public async updateAsset(authUser: AuthUserDto, assetId: string, dto: UpdateAssetDto): Promise<AssetResponseDto> {
|
||||||
@ -356,7 +361,15 @@ export class AssetService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async serveFile(assetId: string, query: ServeFileDto, res: Res, headers: Record<string, string>) {
|
public async serveFile(
|
||||||
|
authUser: AuthUserDto,
|
||||||
|
assetId: string,
|
||||||
|
query: ServeFileDto,
|
||||||
|
res: Res,
|
||||||
|
headers: Record<string, string>,
|
||||||
|
) {
|
||||||
|
const allowOriginalFile = !authUser.isPublicUser || authUser.isAllowDownload;
|
||||||
|
|
||||||
let fileReadStream: ReadStream;
|
let fileReadStream: ReadStream;
|
||||||
const asset = await this._assetRepository.getById(assetId);
|
const asset = await this._assetRepository.getById(assetId);
|
||||||
|
|
||||||
@ -390,7 +403,7 @@ export class AssetService {
|
|||||||
/**
|
/**
|
||||||
* Serve thumbnail image for both web and mobile app
|
* Serve thumbnail image for both web and mobile app
|
||||||
*/
|
*/
|
||||||
if (!query.isThumb) {
|
if (!query.isThumb && allowOriginalFile) {
|
||||||
res.set({
|
res.set({
|
||||||
'Content-Type': asset.mimeType,
|
'Content-Type': asset.mimeType,
|
||||||
});
|
});
|
||||||
@ -676,6 +689,10 @@ export class AssetService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkDownloadAccess(authUser: AuthUserDto) {
|
||||||
|
this.shareCore.checkDownloadAccess(authUser);
|
||||||
|
}
|
||||||
|
|
||||||
async createAssetsSharedLink(authUser: AuthUserDto, dto: CreateAssetsShareLinkDto): Promise<SharedLinkResponseDto> {
|
async createAssetsSharedLink(authUser: AuthUserDto, dto: CreateAssetsShareLinkDto): Promise<SharedLinkResponseDto> {
|
||||||
const assets = [];
|
const assets = [];
|
||||||
|
|
||||||
@ -691,9 +708,11 @@ export class AssetService {
|
|||||||
allowUpload: dto.allowUpload,
|
allowUpload: dto.allowUpload,
|
||||||
assets: assets,
|
assets: assets,
|
||||||
description: dto.description,
|
description: dto.description,
|
||||||
|
allowDownload: dto.allowDownload,
|
||||||
|
showExif: dto.showExif,
|
||||||
});
|
});
|
||||||
|
|
||||||
return mapSharedLinkToResponseDto(sharedLink);
|
return mapSharedLink(sharedLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateAssetsInSharedLink(
|
async updateAssetsInSharedLink(
|
||||||
@ -709,7 +728,11 @@ export class AssetService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updatedLink = await this.shareCore.updateAssetsInSharedLink(authUser.sharedLinkId, assets);
|
const updatedLink = await this.shareCore.updateAssetsInSharedLink(authUser.sharedLinkId, assets);
|
||||||
return mapSharedLinkToResponseDto(updatedLink);
|
return mapSharedLink(updatedLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
getExifPermission(authUser: AuthUserDto) {
|
||||||
|
return !authUser.isPublicUser || authUser.isShowExif;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +25,14 @@ export class CreateAssetsShareLinkDto {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
allowUpload?: boolean;
|
allowUpload?: boolean;
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
@IsOptional()
|
||||||
|
allowDownload?: boolean;
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
@IsOptional()
|
||||||
|
showExif?: boolean;
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
description?: string;
|
description?: string;
|
||||||
|
@ -49,3 +49,26 @@ export function mapAsset(entity: AssetEntity): AssetResponseDto {
|
|||||||
tags: entity.tags?.map(mapTag),
|
tags: entity.tags?.map(mapTag),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function mapAssetWithoutExif(entity: AssetEntity): AssetResponseDto {
|
||||||
|
return {
|
||||||
|
id: entity.id,
|
||||||
|
deviceAssetId: entity.deviceAssetId,
|
||||||
|
ownerId: entity.userId,
|
||||||
|
deviceId: entity.deviceId,
|
||||||
|
type: entity.type,
|
||||||
|
originalPath: entity.originalPath,
|
||||||
|
resizePath: entity.resizePath,
|
||||||
|
createdAt: entity.createdAt,
|
||||||
|
modifiedAt: entity.modifiedAt,
|
||||||
|
isFavorite: entity.isFavorite,
|
||||||
|
mimeType: entity.mimeType,
|
||||||
|
webpPath: entity.webpPath,
|
||||||
|
encodedVideoPath: entity.encodedVideoPath,
|
||||||
|
duration: entity.duration ?? '0:00:00.00000',
|
||||||
|
exifInfo: undefined,
|
||||||
|
smartInfo: entity.smartInfo ? mapSmartInfo(entity.smartInfo) : undefined,
|
||||||
|
livePhotoVideoId: entity.livePhotoVideoId,
|
||||||
|
tags: entity.tags?.map(mapTag),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -8,4 +8,6 @@ export class CreateSharedLinkDto {
|
|||||||
assets!: AssetEntity[];
|
assets!: AssetEntity[];
|
||||||
album?: AlbumEntity;
|
album?: AlbumEntity;
|
||||||
allowUpload?: boolean;
|
allowUpload?: boolean;
|
||||||
|
allowDownload?: boolean;
|
||||||
|
showExif?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,12 @@ export class EditSharedLinkDto {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
allowUpload?: boolean;
|
allowUpload?: boolean;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
allowDownload?: boolean;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
showExif?: boolean;
|
||||||
|
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
isEditExpireTime?: boolean;
|
isEditExpireTime?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import { SharedLinkEntity, SharedLinkType } from '@app/infra';
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { AlbumResponseDto, mapAlbumExcludeAssetInfo } from '../../album/response-dto/album-response.dto';
|
import { AlbumResponseDto, mapAlbumExcludeAssetInfo } from '../../album/response-dto/album-response.dto';
|
||||||
import { AssetResponseDto, mapAsset } from '../../asset/response-dto/asset-response.dto';
|
import { AssetResponseDto, mapAsset, mapAssetWithoutExif } from '../../asset/response-dto/asset-response.dto';
|
||||||
|
|
||||||
export class SharedLinkResponseDto {
|
export class SharedLinkResponseDto {
|
||||||
id!: string;
|
id!: string;
|
||||||
@ -17,9 +17,11 @@ export class SharedLinkResponseDto {
|
|||||||
assets!: AssetResponseDto[];
|
assets!: AssetResponseDto[];
|
||||||
album?: AlbumResponseDto;
|
album?: AlbumResponseDto;
|
||||||
allowUpload!: boolean;
|
allowUpload!: boolean;
|
||||||
|
allowDownload!: boolean;
|
||||||
|
showExif!: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mapSharedLinkToResponseDto(sharedLink: SharedLinkEntity): SharedLinkResponseDto {
|
export function mapSharedLink(sharedLink: SharedLinkEntity): SharedLinkResponseDto {
|
||||||
const linkAssets = sharedLink.assets || [];
|
const linkAssets = sharedLink.assets || [];
|
||||||
const albumAssets = (sharedLink?.album?.assets || []).map((albumAsset) => albumAsset.assetInfo);
|
const albumAssets = (sharedLink?.album?.assets || []).map((albumAsset) => albumAsset.assetInfo);
|
||||||
|
|
||||||
@ -36,5 +38,29 @@ export function mapSharedLinkToResponseDto(sharedLink: SharedLinkEntity): Shared
|
|||||||
assets: assets.map(mapAsset),
|
assets: assets.map(mapAsset),
|
||||||
album: sharedLink.album ? mapAlbumExcludeAssetInfo(sharedLink.album) : undefined,
|
album: sharedLink.album ? mapAlbumExcludeAssetInfo(sharedLink.album) : undefined,
|
||||||
allowUpload: sharedLink.allowUpload,
|
allowUpload: sharedLink.allowUpload,
|
||||||
|
allowDownload: sharedLink.allowDownload,
|
||||||
|
showExif: sharedLink.showExif,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mapSharedLinkWithNoExif(sharedLink: SharedLinkEntity): SharedLinkResponseDto {
|
||||||
|
const linkAssets = sharedLink.assets || [];
|
||||||
|
const albumAssets = (sharedLink?.album?.assets || []).map((albumAsset) => albumAsset.assetInfo);
|
||||||
|
|
||||||
|
const assets = _.uniqBy([...linkAssets, ...albumAssets], (asset) => asset.id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: sharedLink.id,
|
||||||
|
description: sharedLink.description,
|
||||||
|
userId: sharedLink.userId,
|
||||||
|
key: sharedLink.key.toString('hex'),
|
||||||
|
type: sharedLink.type,
|
||||||
|
createdAt: sharedLink.createdAt,
|
||||||
|
expiresAt: sharedLink.expiresAt,
|
||||||
|
assets: assets.map(mapAssetWithoutExif),
|
||||||
|
album: sharedLink.album ? mapAlbumExcludeAssetInfo(sharedLink.album) : undefined,
|
||||||
|
allowUpload: sharedLink.allowUpload,
|
||||||
|
allowDownload: sharedLink.allowDownload,
|
||||||
|
showExif: sharedLink.showExif,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ export class ShareController {
|
|||||||
@Authenticated()
|
@Authenticated()
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
getSharedLinkById(@Param('id') id: string): Promise<SharedLinkResponseDto> {
|
getSharedLinkById(@Param('id') id: string): Promise<SharedLinkResponseDto> {
|
||||||
return this.shareService.getById(id);
|
return this.shareService.getById(id, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Authenticated()
|
@Authenticated()
|
||||||
|
@ -2,9 +2,10 @@ import { SharedLinkEntity } from '@app/infra';
|
|||||||
import { CreateSharedLinkDto } from './dto/create-shared-link.dto';
|
import { CreateSharedLinkDto } from './dto/create-shared-link.dto';
|
||||||
import { ISharedLinkRepository } from './shared-link.repository';
|
import { ISharedLinkRepository } from './shared-link.repository';
|
||||||
import crypto from 'node:crypto';
|
import crypto from 'node:crypto';
|
||||||
import { BadRequestException, InternalServerErrorException, Logger } from '@nestjs/common';
|
import { BadRequestException, ForbiddenException, InternalServerErrorException, Logger } from '@nestjs/common';
|
||||||
import { AssetEntity } from '@app/infra';
|
import { AssetEntity } from '@app/infra';
|
||||||
import { EditSharedLinkDto } from './dto/edit-shared-link.dto';
|
import { EditSharedLinkDto } from './dto/edit-shared-link.dto';
|
||||||
|
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
||||||
|
|
||||||
export class ShareCore {
|
export class ShareCore {
|
||||||
readonly logger = new Logger(ShareCore.name);
|
readonly logger = new Logger(ShareCore.name);
|
||||||
@ -24,6 +25,8 @@ export class ShareCore {
|
|||||||
sharedLink.assets = dto.assets;
|
sharedLink.assets = dto.assets;
|
||||||
sharedLink.album = dto.album;
|
sharedLink.album = dto.album;
|
||||||
sharedLink.allowUpload = dto.allowUpload ?? false;
|
sharedLink.allowUpload = dto.allowUpload ?? false;
|
||||||
|
sharedLink.allowDownload = dto.allowDownload ?? true;
|
||||||
|
sharedLink.showExif = dto.showExif ?? true;
|
||||||
|
|
||||||
return this.sharedLinkRepository.create(sharedLink);
|
return this.sharedLinkRepository.create(sharedLink);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@ -74,6 +77,8 @@ export class ShareCore {
|
|||||||
|
|
||||||
link.description = dto.description ?? link.description;
|
link.description = dto.description ?? link.description;
|
||||||
link.allowUpload = dto.allowUpload ?? link.allowUpload;
|
link.allowUpload = dto.allowUpload ?? link.allowUpload;
|
||||||
|
link.allowDownload = dto.allowDownload ?? link.allowDownload;
|
||||||
|
link.showExif = dto.showExif ?? link.showExif;
|
||||||
|
|
||||||
if (dto.isEditExpireTime && dto.expiredAt) {
|
if (dto.isEditExpireTime && dto.expiredAt) {
|
||||||
link.expiresAt = dto.expiredAt;
|
link.expiresAt = dto.expiredAt;
|
||||||
@ -87,4 +92,10 @@ export class ShareCore {
|
|||||||
async hasAssetAccess(id: string, assetId: string): Promise<boolean> {
|
async hasAssetAccess(id: string, assetId: string): Promise<boolean> {
|
||||||
return this.sharedLinkRepository.hasAssetAccess(id, assetId);
|
return this.sharedLinkRepository.hasAssetAccess(id, assetId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkDownloadAccess(user: AuthUserDto) {
|
||||||
|
if (user.isPublicUser && !user.isAllowDownload) {
|
||||||
|
throw new ForbiddenException();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
import { UserService } from '@app/domain';
|
import { UserService } from '@app/domain';
|
||||||
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
||||||
import { EditSharedLinkDto } from './dto/edit-shared-link.dto';
|
import { EditSharedLinkDto } from './dto/edit-shared-link.dto';
|
||||||
import { mapSharedLinkToResponseDto, SharedLinkResponseDto } from './response-dto/shared-link-response.dto';
|
import { mapSharedLink, mapSharedLinkWithNoExif, SharedLinkResponseDto } from './response-dto/shared-link-response.dto';
|
||||||
import { ShareCore } from './share.core';
|
import { ShareCore } from './share.core';
|
||||||
import { ISharedLinkRepository } from './shared-link.repository';
|
import { ISharedLinkRepository } from './shared-link.repository';
|
||||||
|
|
||||||
@ -39,6 +39,8 @@ export class ShareService {
|
|||||||
isPublicUser: true,
|
isPublicUser: true,
|
||||||
sharedLinkId: link.id,
|
sharedLinkId: link.id,
|
||||||
isAllowUpload: link.allowUpload,
|
isAllowUpload: link.allowUpload,
|
||||||
|
isAllowDownload: link.allowDownload,
|
||||||
|
isShowExif: link.showExif,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -48,7 +50,7 @@ export class ShareService {
|
|||||||
|
|
||||||
async getAll(authUser: AuthUserDto): Promise<SharedLinkResponseDto[]> {
|
async getAll(authUser: AuthUserDto): Promise<SharedLinkResponseDto[]> {
|
||||||
const links = await this.shareCore.getSharedLinks(authUser.id);
|
const links = await this.shareCore.getSharedLinks(authUser.id);
|
||||||
return links.map(mapSharedLinkToResponseDto);
|
return links.map(mapSharedLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMine(authUser: AuthUserDto): Promise<SharedLinkResponseDto> {
|
async getMine(authUser: AuthUserDto): Promise<SharedLinkResponseDto> {
|
||||||
@ -56,15 +58,25 @@ export class ShareService {
|
|||||||
throw new ForbiddenException();
|
throw new ForbiddenException();
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.getById(authUser.sharedLinkId);
|
let allowExif = true;
|
||||||
|
if (authUser.isShowExif != undefined) {
|
||||||
|
allowExif = authUser.isShowExif;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.getById(authUser.sharedLinkId, allowExif);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getById(id: string): Promise<SharedLinkResponseDto> {
|
async getById(id: string, allowExif: boolean): Promise<SharedLinkResponseDto> {
|
||||||
const link = await this.shareCore.getSharedLinkById(id);
|
const link = await this.shareCore.getSharedLinkById(id);
|
||||||
if (!link) {
|
if (!link) {
|
||||||
throw new BadRequestException('Shared link not found');
|
throw new BadRequestException('Shared link not found');
|
||||||
}
|
}
|
||||||
return mapSharedLinkToResponseDto(link);
|
|
||||||
|
if (allowExif) {
|
||||||
|
return mapSharedLink(link);
|
||||||
|
} else {
|
||||||
|
return mapSharedLinkWithNoExif(link);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async remove(id: string, userId: string): Promise<string> {
|
async remove(id: string, userId: string): Promise<string> {
|
||||||
@ -77,11 +89,11 @@ export class ShareService {
|
|||||||
if (!link) {
|
if (!link) {
|
||||||
throw new BadRequestException('Shared link not found');
|
throw new BadRequestException('Shared link not found');
|
||||||
}
|
}
|
||||||
return mapSharedLinkToResponseDto(link);
|
return mapSharedLink(link);
|
||||||
}
|
}
|
||||||
|
|
||||||
async edit(id: string, authUser: AuthUserDto, dto: EditSharedLinkDto) {
|
async edit(id: string, authUser: AuthUserDto, dto: EditSharedLinkDto) {
|
||||||
const link = await this.shareCore.updateSharedLink(id, authUser.id, dto);
|
const link = await this.shareCore.updateSharedLink(id, authUser.id, dto);
|
||||||
return mapSharedLinkToResponseDto(link);
|
return mapSharedLink(link);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -139,7 +139,6 @@ export class MetadataExtractionProcessor {
|
|||||||
async extractExifInfo(job: Job<IExifExtractionProcessor>) {
|
async extractExifInfo(job: Job<IExifExtractionProcessor>) {
|
||||||
try {
|
try {
|
||||||
const { asset, fileName }: { asset: AssetEntity; fileName: string } = job.data;
|
const { asset, fileName }: { asset: AssetEntity; fileName: string } = job.data;
|
||||||
|
|
||||||
const exifData = await exiftool.read(asset.originalPath).catch((e) => {
|
const exifData = await exiftool.read(asset.originalPath).catch((e) => {
|
||||||
this.logger.warn(`The exifData parsing failed due to: ${e} on file ${asset.originalPath}`);
|
this.logger.warn(`The exifData parsing failed due to: ${e} on file ${asset.originalPath}`);
|
||||||
return null;
|
return null;
|
||||||
|
@ -736,7 +736,7 @@
|
|||||||
"/asset/download-library": {
|
"/asset/download-library": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "downloadLibrary",
|
"operationId": "downloadLibrary",
|
||||||
"description": "",
|
"description": "Current this is not used in any UI element",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "skip",
|
"name": "skip",
|
||||||
@ -3786,6 +3786,12 @@
|
|||||||
"allowUpload": {
|
"allowUpload": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"allowDownload": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"showExif": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
@ -3887,6 +3893,12 @@
|
|||||||
},
|
},
|
||||||
"allowUpload": {
|
"allowUpload": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"allowDownload": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"showExif": {
|
||||||
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
@ -3897,7 +3909,9 @@
|
|||||||
"createdAt",
|
"createdAt",
|
||||||
"expiresAt",
|
"expiresAt",
|
||||||
"assets",
|
"assets",
|
||||||
"allowUpload"
|
"allowUpload",
|
||||||
|
"allowDownload",
|
||||||
|
"showExif"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"UpdateAssetsToSharedLinkDto": {
|
"UpdateAssetsToSharedLinkDto": {
|
||||||
@ -3926,6 +3940,12 @@
|
|||||||
"allowUpload": {
|
"allowUpload": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"allowDownload": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"showExif": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"isEditExpireTime": {
|
"isEditExpireTime": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
@ -4085,6 +4105,12 @@
|
|||||||
"allowUpload": {
|
"allowUpload": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"allowDownload": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"showExif": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
@ -5,4 +5,6 @@ export class AuthUserDto {
|
|||||||
isPublicUser?: boolean;
|
isPublicUser?: boolean;
|
||||||
sharedLinkId?: string;
|
sharedLinkId?: string;
|
||||||
isAllowUpload?: boolean;
|
isAllowUpload?: boolean;
|
||||||
|
isAllowDownload?: boolean;
|
||||||
|
isShowExif?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,12 @@ export class SharedLinkEntity {
|
|||||||
@Column({ type: 'boolean', default: false })
|
@Column({ type: 'boolean', default: false })
|
||||||
allowUpload!: boolean;
|
allowUpload!: boolean;
|
||||||
|
|
||||||
|
@Column({ type: 'boolean', default: true })
|
||||||
|
allowDownload!: boolean;
|
||||||
|
|
||||||
|
@Column({ type: 'boolean', default: true })
|
||||||
|
showExif!: boolean;
|
||||||
|
|
||||||
@ManyToMany(() => AssetEntity, (asset) => asset.sharedLinks)
|
@ManyToMany(() => AssetEntity, (asset) => asset.sharedLinks)
|
||||||
assets!: AssetEntity[];
|
assets!: AssetEntity[];
|
||||||
|
|
||||||
@ -47,4 +53,4 @@ export enum SharedLinkType {
|
|||||||
INDIVIDUAL = 'INDIVIDUAL',
|
INDIVIDUAL = 'INDIVIDUAL',
|
||||||
}
|
}
|
||||||
|
|
||||||
// npm run typeorm -- migration:generate ./libs/database/src/AddSharedLinkTable -d libs/database/src/config/database.config.ts
|
// npm run typeorm -- migration:generate ./libs/infra/src/db/AddMorePermissionToSharedLink -d ./libs/infra/src/db/config/database.config.ts
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class AddMorePermissionToSharedLink1673907194740 implements MigrationInterface {
|
||||||
|
name = 'AddMorePermissionToSharedLink1673907194740';
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "shared_links" ADD "allowDownload" boolean NOT NULL DEFAULT true`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "shared_links" ADD "showExif" boolean NOT NULL DEFAULT true`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "shared_links" DROP COLUMN "showExif"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "shared_links" DROP COLUMN "allowDownload"`);
|
||||||
|
}
|
||||||
|
}
|
48
web/src/api/open-api/api.ts
generated
48
web/src/api/open-api/api.ts
generated
@ -665,6 +665,18 @@ export interface CreateAlbumShareLinkDto {
|
|||||||
* @memberof CreateAlbumShareLinkDto
|
* @memberof CreateAlbumShareLinkDto
|
||||||
*/
|
*/
|
||||||
'allowUpload'?: boolean;
|
'allowUpload'?: boolean;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof CreateAlbumShareLinkDto
|
||||||
|
*/
|
||||||
|
'allowDownload'?: boolean;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof CreateAlbumShareLinkDto
|
||||||
|
*/
|
||||||
|
'showExif'?: boolean;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {string}
|
* @type {string}
|
||||||
@ -696,6 +708,18 @@ export interface CreateAssetsShareLinkDto {
|
|||||||
* @memberof CreateAssetsShareLinkDto
|
* @memberof CreateAssetsShareLinkDto
|
||||||
*/
|
*/
|
||||||
'allowUpload'?: boolean;
|
'allowUpload'?: boolean;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof CreateAssetsShareLinkDto
|
||||||
|
*/
|
||||||
|
'allowDownload'?: boolean;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof CreateAssetsShareLinkDto
|
||||||
|
*/
|
||||||
|
'showExif'?: boolean;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {string}
|
* @type {string}
|
||||||
@ -987,6 +1011,18 @@ export interface EditSharedLinkDto {
|
|||||||
* @memberof EditSharedLinkDto
|
* @memberof EditSharedLinkDto
|
||||||
*/
|
*/
|
||||||
'allowUpload'?: boolean;
|
'allowUpload'?: boolean;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof EditSharedLinkDto
|
||||||
|
*/
|
||||||
|
'allowDownload'?: boolean;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof EditSharedLinkDto
|
||||||
|
*/
|
||||||
|
'showExif'?: boolean;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
@ -1612,6 +1648,18 @@ export interface SharedLinkResponseDto {
|
|||||||
* @memberof SharedLinkResponseDto
|
* @memberof SharedLinkResponseDto
|
||||||
*/
|
*/
|
||||||
'allowUpload': boolean;
|
'allowUpload': boolean;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof SharedLinkResponseDto
|
||||||
|
*/
|
||||||
|
'allowDownload': boolean;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof SharedLinkResponseDto
|
||||||
|
*/
|
||||||
|
'showExif': boolean;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -320,6 +320,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
$downloadAssets = {};
|
||||||
console.error('Error downloading file ', e);
|
console.error('Error downloading file ', e);
|
||||||
notificationController.show({
|
notificationController.show({
|
||||||
type: NotificationType.Error,
|
type: NotificationType.Error,
|
||||||
@ -460,11 +461,13 @@
|
|||||||
<CircleIconButton title="Remove album" on:click={removeAlbum} logo={DeleteOutline} />
|
<CircleIconButton title="Remove album" on:click={removeAlbum} logo={DeleteOutline} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<CircleIconButton
|
{#if !isPublicShared || (isPublicShared && sharedLink?.allowDownload)}
|
||||||
title="Download"
|
<CircleIconButton
|
||||||
on:click={() => downloadAlbum()}
|
title="Download"
|
||||||
logo={FolderDownloadOutline}
|
on:click={() => downloadAlbum()}
|
||||||
/>
|
logo={FolderDownloadOutline}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if !isPublicShared}
|
{#if !isPublicShared}
|
||||||
<CircleIconButton
|
<CircleIconButton
|
||||||
@ -534,11 +537,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if album.assetCount > 0}
|
{#if album.assetCount > 0}
|
||||||
<GalleryViewer
|
<GalleryViewer assets={album.assets} {sharedLink} bind:selectedAssets={multiSelectAsset} />
|
||||||
assets={album.assets}
|
|
||||||
key={sharedLink?.key ?? ''}
|
|
||||||
bind:selectedAssets={multiSelectAsset}
|
|
||||||
/>
|
|
||||||
{:else}
|
{:else}
|
||||||
<!-- Album is empty - Show asset selectection buttons -->
|
<!-- Album is empty - Show asset selectection buttons -->
|
||||||
<section id="empty-album" class=" mt-[200px] flex place-content-center place-items-center">
|
<section id="empty-album" class=" mt-[200px] flex place-content-center place-items-center">
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
export let showCopyButton: boolean;
|
export let showCopyButton: boolean;
|
||||||
export let showMotionPlayButton: boolean;
|
export let showMotionPlayButton: boolean;
|
||||||
export let isMotionPhotoPlaying = false;
|
export let isMotionPhotoPlaying = false;
|
||||||
|
export let showDownloadButton: boolean;
|
||||||
|
|
||||||
const isOwner = asset.ownerId === $page.data.user?.id;
|
const isOwner = asset.ownerId === $page.data.user?.id;
|
||||||
|
|
||||||
@ -77,11 +78,14 @@
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<CircleIconButton
|
|
||||||
logo={CloudDownloadOutline}
|
{#if showDownloadButton}
|
||||||
on:click={() => dispatch('download')}
|
<CircleIconButton
|
||||||
title="Download"
|
logo={CloudDownloadOutline}
|
||||||
/>
|
on:click={() => dispatch('download')}
|
||||||
|
title="Download"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
<CircleIconButton
|
<CircleIconButton
|
||||||
logo={InformationOutline}
|
logo={InformationOutline}
|
||||||
on:click={() => dispatch('showDetail')}
|
on:click={() => dispatch('showDetail')}
|
||||||
|
@ -10,7 +10,13 @@
|
|||||||
import { downloadAssets } from '$lib/stores/download';
|
import { downloadAssets } from '$lib/stores/download';
|
||||||
import VideoViewer from './video-viewer.svelte';
|
import VideoViewer from './video-viewer.svelte';
|
||||||
import AlbumSelectionModal from '../shared-components/album-selection-modal.svelte';
|
import AlbumSelectionModal from '../shared-components/album-selection-modal.svelte';
|
||||||
import { api, AssetResponseDto, AssetTypeEnum, AlbumResponseDto } from '@api';
|
import {
|
||||||
|
api,
|
||||||
|
AssetResponseDto,
|
||||||
|
AssetTypeEnum,
|
||||||
|
AlbumResponseDto,
|
||||||
|
SharedLinkResponseDto
|
||||||
|
} from '@api';
|
||||||
import {
|
import {
|
||||||
notificationController,
|
notificationController,
|
||||||
NotificationType
|
NotificationType
|
||||||
@ -22,6 +28,7 @@
|
|||||||
export let asset: AssetResponseDto;
|
export let asset: AssetResponseDto;
|
||||||
export let publicSharedKey = '';
|
export let publicSharedKey = '';
|
||||||
export let showNavigation = true;
|
export let showNavigation = true;
|
||||||
|
export let sharedLink: SharedLinkResponseDto | undefined = undefined;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
let halfLeftHover = false;
|
let halfLeftHover = false;
|
||||||
@ -31,6 +38,7 @@
|
|||||||
let isShowAlbumPicker = false;
|
let isShowAlbumPicker = false;
|
||||||
let addToSharedAlbum = true;
|
let addToSharedAlbum = true;
|
||||||
let shouldPlayMotionPhoto = false;
|
let shouldPlayMotionPhoto = false;
|
||||||
|
let shouldShowDownloadButton = sharedLink ? sharedLink.allowDownload : true;
|
||||||
const onKeyboardPress = (keyInfo: KeyboardEvent) => handleKeyboardPress(keyInfo.key);
|
const onKeyboardPress = (keyInfo: KeyboardEvent) => handleKeyboardPress(keyInfo.key);
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
@ -166,6 +174,7 @@
|
|||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
$downloadAssets = {};
|
||||||
console.error('Error downloading file ', e);
|
console.error('Error downloading file ', e);
|
||||||
notificationController.show({
|
notificationController.show({
|
||||||
type: NotificationType.Error,
|
type: NotificationType.Error,
|
||||||
@ -247,6 +256,7 @@
|
|||||||
isMotionPhotoPlaying={shouldPlayMotionPhoto}
|
isMotionPhotoPlaying={shouldPlayMotionPhoto}
|
||||||
showCopyButton={asset.type === AssetTypeEnum.Image}
|
showCopyButton={asset.type === AssetTypeEnum.Image}
|
||||||
showMotionPlayButton={!!asset.livePhotoVideoId}
|
showMotionPlayButton={!!asset.livePhotoVideoId}
|
||||||
|
showDownloadButton={shouldShowDownloadButton}
|
||||||
on:goBack={closeViewer}
|
on:goBack={closeViewer}
|
||||||
on:showDetail={showDetailInfoHandler}
|
on:showDetail={showDetailInfoHandler}
|
||||||
on:download={handleDownload}
|
on:download={handleDownload}
|
||||||
|
@ -136,15 +136,17 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<CircleIconButton
|
{#if sharedLink?.allowDownload}
|
||||||
title="Download"
|
<CircleIconButton
|
||||||
on:click={() => downloadAssets(true)}
|
title="Download"
|
||||||
logo={FolderDownloadOutline}
|
on:click={() => downloadAssets(true)}
|
||||||
/>
|
logo={FolderDownloadOutline}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</ControlAppBar>
|
</ControlAppBar>
|
||||||
{/if}
|
{/if}
|
||||||
<section class="flex flex-col my-[160px] px-6 sm:px-12 md:px-24 lg:px-40">
|
<section class="flex flex-col my-[160px] px-6 sm:px-12 md:px-24 lg:px-40">
|
||||||
<GalleryViewer {assets} key={sharedLink.key} bind:selectedAssets />
|
<GalleryViewer {assets} {sharedLink} bind:selectedAssets />
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
<div
|
<div
|
||||||
use:clickOutside
|
use:clickOutside
|
||||||
on:outclick={() => dispatch('close')}
|
on:outclick={() => dispatch('close')}
|
||||||
class="bg-immich-bg dark:bg-immich-dark-gray dark:text-immich-dark-fg w-[450px] min-h-[200px] max-h-[500px] rounded-lg shadow-md"
|
class="bg-immich-bg dark:bg-immich-dark-gray dark:text-immich-dark-fg w-[450px] min-h-[200px] max-h-[600px] rounded-lg shadow-md"
|
||||||
>
|
>
|
||||||
<div class="flex justify-between place-items-center px-5 py-3">
|
<div class="flex justify-between place-items-center px-5 py-3">
|
||||||
<div>
|
<div>
|
||||||
|
@ -29,6 +29,8 @@
|
|||||||
let sharedLink = '';
|
let sharedLink = '';
|
||||||
let description = '';
|
let description = '';
|
||||||
let shouldChangeExpirationTime = false;
|
let shouldChangeExpirationTime = false;
|
||||||
|
let isAllowDownload = true;
|
||||||
|
let shouldShowExif = true;
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
const expiredDateOption: ImmichDropDownOption = {
|
const expiredDateOption: ImmichDropDownOption = {
|
||||||
@ -42,6 +44,8 @@
|
|||||||
description = editingLink.description;
|
description = editingLink.description;
|
||||||
}
|
}
|
||||||
isAllowUpload = editingLink.allowUpload;
|
isAllowUpload = editingLink.allowUpload;
|
||||||
|
isAllowDownload = editingLink.allowDownload;
|
||||||
|
shouldShowExif = editingLink.showExif;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -58,7 +62,9 @@
|
|||||||
albumId: album.id,
|
albumId: album.id,
|
||||||
expiredAt: expirationDate,
|
expiredAt: expirationDate,
|
||||||
allowUpload: isAllowUpload,
|
allowUpload: isAllowUpload,
|
||||||
description: description
|
description: description,
|
||||||
|
allowDownload: isAllowDownload,
|
||||||
|
showExif: shouldShowExif
|
||||||
});
|
});
|
||||||
buildSharedLink(data);
|
buildSharedLink(data);
|
||||||
} else {
|
} else {
|
||||||
@ -66,7 +72,9 @@
|
|||||||
assetIds: sharedAssets.map((a) => a.id),
|
assetIds: sharedAssets.map((a) => a.id),
|
||||||
expiredAt: expirationDate,
|
expiredAt: expirationDate,
|
||||||
allowUpload: isAllowUpload,
|
allowUpload: isAllowUpload,
|
||||||
description: description
|
description: description,
|
||||||
|
allowDownload: isAllowDownload,
|
||||||
|
showExif: shouldShowExif
|
||||||
});
|
});
|
||||||
buildSharedLink(data);
|
buildSharedLink(data);
|
||||||
}
|
}
|
||||||
@ -132,7 +140,9 @@
|
|||||||
description: description,
|
description: description,
|
||||||
expiredAt: expirationDate,
|
expiredAt: expirationDate,
|
||||||
allowUpload: isAllowUpload,
|
allowUpload: isAllowUpload,
|
||||||
isEditExpireTime: shouldChangeExpirationTime
|
isEditExpireTime: shouldChangeExpirationTime,
|
||||||
|
allowDownload: isAllowDownload,
|
||||||
|
showExif: shouldShowExif
|
||||||
});
|
});
|
||||||
|
|
||||||
notificationController.show({
|
notificationController.show({
|
||||||
@ -185,12 +195,12 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="mt-6 mb-2">
|
<div class="mt-4 mb-2">
|
||||||
<p class="text-xs">LINK OPTIONS</p>
|
<p class="text-xs">LINK OPTIONS</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-4 bg-gray-100 dark:bg-black/40 rounded-lg">
|
<div class="p-4 bg-gray-100 dark:bg-black/40 rounded-lg">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="mb-4">
|
<div class="mb-2">
|
||||||
<SettingInputField
|
<SettingInputField
|
||||||
inputType={SettingInputFieldType.TEXT}
|
inputType={SettingInputFieldType.TEXT}
|
||||||
label="Description"
|
label="Description"
|
||||||
@ -198,9 +208,19 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SettingSwitch bind:checked={isAllowUpload} title={'Allow public user to upload'} />
|
<div class="my-3">
|
||||||
|
<SettingSwitch bind:checked={shouldShowExif} title={'Show metadata'} />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="text-sm mt-4">
|
<div class="my-3">
|
||||||
|
<SettingSwitch bind:checked={isAllowDownload} title={'Allow public user to download'} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="my-3">
|
||||||
|
<SettingSwitch bind:checked={isAllowUpload} title={'Allow public user to upload'} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-sm">
|
||||||
{#if editingLink}
|
{#if editingLink}
|
||||||
<p class="my-2 immich-form-label">
|
<p class="my-2 immich-form-label">
|
||||||
<SettingSwitch
|
<SettingSwitch
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
{disabled}
|
{disabled}
|
||||||
on:click={toggle}
|
on:click={toggle}
|
||||||
aria-expanded={isOpen}
|
aria-expanded={isOpen}
|
||||||
class="bg-gray-200 w-full flex p-2 rounded-lg dark:bg-gray-600 place-items-center justify-between disabled:cursor-not-allowed dark:disabled:bg-gray-300 disabled:bg-gray-600 "
|
class="bg-gray-200 w-full flex p-2 rounded-lg dark:bg-gray-600 place-items-center justify-between disabled:cursor-not-allowed dark:disabled:bg-gray-300 disabled:bg-gray-600"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
{selected}
|
{selected}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { AssetResponseDto, ThumbnailFormat } from '@api';
|
import { AssetResponseDto, SharedLinkResponseDto, ThumbnailFormat } from '@api';
|
||||||
|
|
||||||
import AssetViewer from '../../asset-viewer/asset-viewer.svelte';
|
import AssetViewer from '../../asset-viewer/asset-viewer.svelte';
|
||||||
import ImmichThumbnail from '../../shared-components/immich-thumbnail.svelte';
|
import ImmichThumbnail from '../../shared-components/immich-thumbnail.svelte';
|
||||||
|
|
||||||
export let assets: AssetResponseDto[];
|
export let assets: AssetResponseDto[];
|
||||||
export let key: string;
|
export let sharedLink: SharedLinkResponseDto | undefined = undefined;
|
||||||
export let selectedAssets: Set<AssetResponseDto> = new Set();
|
export let selectedAssets: Set<AssetResponseDto> = new Set();
|
||||||
|
|
||||||
let isShowAssetViewer = false;
|
let isShowAssetViewer = false;
|
||||||
@ -96,7 +96,7 @@
|
|||||||
<ImmichThumbnail
|
<ImmichThumbnail
|
||||||
{asset}
|
{asset}
|
||||||
{thumbnailSize}
|
{thumbnailSize}
|
||||||
publicSharedKey={key}
|
publicSharedKey={sharedLink?.key}
|
||||||
format={assets.length < 7 ? ThumbnailFormat.Jpeg : ThumbnailFormat.Webp}
|
format={assets.length < 7 ? ThumbnailFormat.Jpeg : ThumbnailFormat.Webp}
|
||||||
on:click={(e) => (isMultiSelectionMode ? selectAssetHandler(e) : viewAssetHandler(e))}
|
on:click={(e) => (isMultiSelectionMode ? selectAssetHandler(e) : viewAssetHandler(e))}
|
||||||
on:select={selectAssetHandler}
|
on:select={selectAssetHandler}
|
||||||
@ -110,7 +110,8 @@
|
|||||||
{#if isShowAssetViewer}
|
{#if isShowAssetViewer}
|
||||||
<AssetViewer
|
<AssetViewer
|
||||||
asset={selectedAsset}
|
asset={selectedAsset}
|
||||||
publicSharedKey={key}
|
publicSharedKey={sharedLink?.key}
|
||||||
|
{sharedLink}
|
||||||
on:navigate-previous={navigateAssetBackward}
|
on:navigate-previous={navigateAssetBackward}
|
||||||
on:navigate-next={navigateAssetForward}
|
on:navigate-next={navigateAssetForward}
|
||||||
on:close={closeViewer}
|
on:close={closeViewer}
|
||||||
|
@ -122,12 +122,28 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="info-bottom">
|
<div class="info-bottom flex gap-4">
|
||||||
{#if link.allowUpload}
|
{#if link.allowUpload}
|
||||||
|
<div
|
||||||
|
class="text-xs px-2 py-1 bg-immich-primary dark:bg-immich-dark-primary text-white dark:text-immich-dark-gray flex place-items-center place-content-center rounded-full w-[80px]"
|
||||||
|
>
|
||||||
|
Upload
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if link.allowDownload}
|
||||||
<div
|
<div
|
||||||
class="text-xs px-2 py-1 bg-immich-primary dark:bg-immich-dark-primary text-white dark:text-immich-dark-gray flex place-items-center place-content-center rounded-full w-[100px]"
|
class="text-xs px-2 py-1 bg-immich-primary dark:bg-immich-dark-primary text-white dark:text-immich-dark-gray flex place-items-center place-content-center rounded-full w-[100px]"
|
||||||
>
|
>
|
||||||
Allow upload
|
Download
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if link.showExif}
|
||||||
|
<div
|
||||||
|
class="text-xs px-2 py-1 bg-immich-primary dark:bg-immich-dark-primary text-white dark:text-immich-dark-gray flex place-items-center place-content-center rounded-full w-[60px]"
|
||||||
|
>
|
||||||
|
EXIF
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user