From b4fa60d4fdd6e70cb5f21e7b4a05cfc194d2020a Mon Sep 17 00:00:00 2001 From: Maarten Rijke Date: Wed, 6 Sep 2023 05:14:44 +0200 Subject: [PATCH] feat(web): show original uploader in shared album photo details (#3977) * feat(web): show original uploader in shared album photo details * feat: send owner in asset by id response * chore: open api * fix: linting * fix: change to Shared By * openapi * openapi * api * styling --------- Co-authored-by: Jason Rasmussen Co-authored-by: Alex --- cli/src/api/open-api/api.ts | 6 +++ mobile/openapi/doc/AssetResponseDto.md | 1 + .../openapi/lib/model/asset_response_dto.dart | 19 ++++++++- .../openapi/test/asset_response_dto_test.dart | 5 +++ server/immich-openapi-specs.json | 5 ++- .../asset/response-dto/asset-response.dto.ts | 37 +++++----------- .../immich/api-v1/asset/asset-repository.ts | 1 + .../src/immich/api-v1/asset/asset.service.ts | 4 ++ server/test/fixtures/shared-link.stub.ts | 4 +- web/src/api/open-api/api.ts | 6 +++ .../asset-viewer/detail-panel.svelte | 42 +++++++++++++------ 11 files changed, 87 insertions(+), 43 deletions(-) diff --git a/cli/src/api/open-api/api.ts b/cli/src/api/open-api/api.ts index 2301c02e1c..35a769d283 100644 --- a/cli/src/api/open-api/api.ts +++ b/cli/src/api/open-api/api.ts @@ -645,6 +645,12 @@ export interface AssetResponseDto { * @memberof AssetResponseDto */ 'originalPath': string; + /** + * + * @type {UserResponseDto} + * @memberof AssetResponseDto + */ + 'owner'?: UserResponseDto; /** * * @type {string} diff --git a/mobile/openapi/doc/AssetResponseDto.md b/mobile/openapi/doc/AssetResponseDto.md index b122147ff1..929031a3a3 100644 --- a/mobile/openapi/doc/AssetResponseDto.md +++ b/mobile/openapi/doc/AssetResponseDto.md @@ -21,6 +21,7 @@ Name | Type | Description | Notes **livePhotoVideoId** | **String** | | [optional] **originalFileName** | **String** | | **originalPath** | **String** | | +**owner** | [**UserResponseDto**](UserResponseDto.md) | | [optional] **ownerId** | **String** | | **people** | [**List**](PersonResponseDto.md) | | [optional] [default to const []] **resized** | **bool** | | diff --git a/mobile/openapi/lib/model/asset_response_dto.dart b/mobile/openapi/lib/model/asset_response_dto.dart index 02bb376cc2..078389c549 100644 --- a/mobile/openapi/lib/model/asset_response_dto.dart +++ b/mobile/openapi/lib/model/asset_response_dto.dart @@ -26,6 +26,7 @@ class AssetResponseDto { this.livePhotoVideoId, required this.originalFileName, required this.originalPath, + this.owner, required this.ownerId, this.people = const [], required this.resized, @@ -69,6 +70,14 @@ class AssetResponseDto { String originalPath; + /// + /// 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. + /// + UserResponseDto? owner; + String ownerId; List people; @@ -107,6 +116,7 @@ class AssetResponseDto { other.livePhotoVideoId == livePhotoVideoId && other.originalFileName == originalFileName && other.originalPath == originalPath && + other.owner == owner && other.ownerId == ownerId && other.people == people && other.resized == resized && @@ -132,6 +142,7 @@ class AssetResponseDto { (livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) + (originalFileName.hashCode) + (originalPath.hashCode) + + (owner == null ? 0 : owner!.hashCode) + (ownerId.hashCode) + (people.hashCode) + (resized.hashCode) + @@ -142,7 +153,7 @@ class AssetResponseDto { (updatedAt.hashCode); @override - String toString() => 'AssetResponseDto[checksum=$checksum, deviceAssetId=$deviceAssetId, deviceId=$deviceId, duration=$duration, exifInfo=$exifInfo, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, id=$id, isArchived=$isArchived, isFavorite=$isFavorite, livePhotoVideoId=$livePhotoVideoId, originalFileName=$originalFileName, originalPath=$originalPath, ownerId=$ownerId, people=$people, resized=$resized, smartInfo=$smartInfo, tags=$tags, thumbhash=$thumbhash, type=$type, updatedAt=$updatedAt]'; + String toString() => 'AssetResponseDto[checksum=$checksum, deviceAssetId=$deviceAssetId, deviceId=$deviceId, duration=$duration, exifInfo=$exifInfo, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, id=$id, isArchived=$isArchived, isFavorite=$isFavorite, livePhotoVideoId=$livePhotoVideoId, originalFileName=$originalFileName, originalPath=$originalPath, owner=$owner, ownerId=$ownerId, people=$people, resized=$resized, smartInfo=$smartInfo, tags=$tags, thumbhash=$thumbhash, type=$type, updatedAt=$updatedAt]'; Map toJson() { final json = {}; @@ -167,6 +178,11 @@ class AssetResponseDto { } json[r'originalFileName'] = this.originalFileName; json[r'originalPath'] = this.originalPath; + if (this.owner != null) { + json[r'owner'] = this.owner; + } else { + // json[r'owner'] = null; + } json[r'ownerId'] = this.ownerId; json[r'people'] = this.people; json[r'resized'] = this.resized; @@ -207,6 +223,7 @@ class AssetResponseDto { livePhotoVideoId: mapValueOfType(json, r'livePhotoVideoId'), originalFileName: mapValueOfType(json, r'originalFileName')!, originalPath: mapValueOfType(json, r'originalPath')!, + owner: UserResponseDto.fromJson(json[r'owner']), ownerId: mapValueOfType(json, r'ownerId')!, people: PersonResponseDto.listFromJson(json[r'people']), resized: mapValueOfType(json, r'resized')!, diff --git a/mobile/openapi/test/asset_response_dto_test.dart b/mobile/openapi/test/asset_response_dto_test.dart index f89eacf986..d0985f429b 100644 --- a/mobile/openapi/test/asset_response_dto_test.dart +++ b/mobile/openapi/test/asset_response_dto_test.dart @@ -82,6 +82,11 @@ void main() { // TODO }); + // UserResponseDto owner + test('to test the property `owner`', () async { + // TODO + }); + // String ownerId test('to test the property `ownerId`', () async { // TODO diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index 2856819c58..0aa62f43c5 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -5208,6 +5208,9 @@ "originalPath": { "type": "string" }, + "owner": { + "$ref": "#/components/schemas/UserResponseDto" + }, "ownerId": { "type": "string" }, @@ -5246,8 +5249,8 @@ "type", "id", "deviceAssetId", - "ownerId", "deviceId", + "ownerId", "originalPath", "originalFileName", "resized", diff --git a/server/src/domain/asset/response-dto/asset-response.dto.ts b/server/src/domain/asset/response-dto/asset-response.dto.ts index 8079d9fd18..41e7f20522 100644 --- a/server/src/domain/asset/response-dto/asset-response.dto.ts +++ b/server/src/domain/asset/response-dto/asset-response.dto.ts @@ -2,14 +2,16 @@ import { AssetEntity, AssetType } from '@app/infra/entities'; import { ApiProperty } from '@nestjs/swagger'; import { PersonResponseDto, mapFace } from '../../person/person.dto'; import { TagResponseDto, mapTag } from '../../tag'; +import { UserResponseDto, mapUser } from '../../user/response-dto/user-response.dto'; import { ExifResponseDto, mapExif } from './exif-response.dto'; import { SmartInfoResponseDto, mapSmartInfo } from './smart-info-response.dto'; export class AssetResponseDto { id!: string; deviceAssetId!: string; - ownerId!: string; deviceId!: string; + ownerId!: string; + owner?: UserResponseDto; @ApiProperty({ enumName: 'AssetTypeEnum', enum: AssetType }) type!: AssetType; @@ -33,11 +35,12 @@ export class AssetResponseDto { checksum!: string; } -export function mapAsset(entity: AssetEntity): AssetResponseDto { +function _map(entity: AssetEntity, withExif: boolean): AssetResponseDto { return { id: entity.id, deviceAssetId: entity.deviceAssetId, ownerId: entity.ownerId, + owner: entity.owner ? mapUser(entity.owner) : undefined, deviceId: entity.deviceId, type: entity.type, originalPath: entity.originalPath, @@ -50,7 +53,7 @@ export function mapAsset(entity: AssetEntity): AssetResponseDto { isFavorite: entity.isFavorite, isArchived: entity.isArchived, duration: entity.duration ?? '0:00:00.00000', - exifInfo: entity.exifInfo ? mapExif(entity.exifInfo) : undefined, + exifInfo: withExif ? (entity.exifInfo ? mapExif(entity.exifInfo) : undefined) : undefined, smartInfo: entity.smartInfo ? mapSmartInfo(entity.smartInfo) : undefined, livePhotoVideoId: entity.livePhotoVideoId, tags: entity.tags?.map(mapTag), @@ -59,30 +62,12 @@ export function mapAsset(entity: AssetEntity): AssetResponseDto { }; } +export function mapAsset(entity: AssetEntity): AssetResponseDto { + return _map(entity, true); +} + export function mapAssetWithoutExif(entity: AssetEntity): AssetResponseDto { - return { - id: entity.id, - deviceAssetId: entity.deviceAssetId, - ownerId: entity.ownerId, - deviceId: entity.deviceId, - type: entity.type, - originalPath: entity.originalPath, - originalFileName: entity.originalFileName, - resized: !!entity.resizePath, - thumbhash: entity.thumbhash?.toString('base64') || null, - fileCreatedAt: entity.fileCreatedAt, - fileModifiedAt: entity.fileModifiedAt, - updatedAt: entity.updatedAt, - isFavorite: entity.isFavorite, - isArchived: entity.isArchived, - duration: entity.duration ?? '0:00:00.00000', - exifInfo: undefined, - smartInfo: entity.smartInfo ? mapSmartInfo(entity.smartInfo) : undefined, - livePhotoVideoId: entity.livePhotoVideoId, - tags: entity.tags?.map(mapTag), - people: entity.faces?.map(mapFace), - checksum: entity.checksum.toString('base64'), - }; + return _map(entity, false); } export class MemoryLaneResponseDto { diff --git a/server/src/immich/api-v1/asset/asset-repository.ts b/server/src/immich/api-v1/asset/asset-repository.ts index 0466b3668c..c666e88bdf 100644 --- a/server/src/immich/api-v1/asset/asset-repository.ts +++ b/server/src/immich/api-v1/asset/asset-repository.ts @@ -107,6 +107,7 @@ export class AssetRepository implements IAssetRepository { tags: true, sharedLinks: true, smartInfo: true, + owner: true, faces: { person: true, }, diff --git a/server/src/immich/api-v1/asset/asset.service.ts b/server/src/immich/api-v1/asset/asset.service.ts index 77b3f73e09..c6013e2f7e 100644 --- a/server/src/immich/api-v1/asset/asset.service.ts +++ b/server/src/immich/api-v1/asset/asset.service.ts @@ -199,6 +199,10 @@ export class AssetService { data.people = []; } + if (authUser.isPublicUser) { + delete data.owner; + } + return data; } diff --git a/server/test/fixtures/shared-link.stub.ts b/server/test/fixtures/shared-link.stub.ts index e85b000ce1..31a9459850 100644 --- a/server/test/fixtures/shared-link.stub.ts +++ b/server/test/fixtures/shared-link.stub.ts @@ -1,5 +1,5 @@ import { AlbumResponseDto, AssetResponseDto, ExifResponseDto, mapUser, SharedLinkResponseDto } from '@app/domain'; -import { AssetType, SharedLinkEntity, SharedLinkType } from '@app/infra/entities'; +import { AssetType, SharedLinkEntity, SharedLinkType, UserEntity } from '@app/infra/entities'; import { assetStub } from './asset.stub'; import { authStub } from './auth.stub'; import { userStub } from './user.stub'; @@ -158,7 +158,7 @@ export const sharedLinkStub = { assets: [ { id: 'id_1', - owner: userStub.user1, + owner: undefined as unknown as UserEntity, ownerId: 'user_id_1', deviceAssetId: 'device_asset_id_1', deviceId: 'device_id_1', diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index 2301c02e1c..35a769d283 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -645,6 +645,12 @@ export interface AssetResponseDto { * @memberof AssetResponseDto */ 'originalPath': string; + /** + * + * @type {UserResponseDto} + * @memberof AssetResponseDto + */ + 'owner'?: UserResponseDto; /** * * @type {string} diff --git a/web/src/lib/components/asset-viewer/detail-panel.svelte b/web/src/lib/components/asset-viewer/detail-panel.svelte index fc4526a465..53bff125fb 100644 --- a/web/src/lib/components/asset-viewer/detail-panel.svelte +++ b/web/src/lib/components/asset-viewer/detail-panel.svelte @@ -13,6 +13,7 @@ import { asByteUnitString } from '../../utils/byte-units'; import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte'; import { getAssetFilename } from '$lib/utils/asset-utils'; + import UserAvatar from '../shared-components/user-avatar.svelte'; export let asset: AssetResponseDto; export let albums: AlbumResponseDto[] = []; @@ -20,6 +21,8 @@ let textarea: HTMLTextAreaElement; let description: string; + $: isOwner = $page?.data?.user?.id === asset.ownerId; + $: { // Get latest description from server if (asset.id && !api.isSharedLink) { @@ -93,20 +96,17 @@

Info

-
+