From 0130591a0f7fb71da99bc05bc0f319137b1c00bd Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Wed, 1 Nov 2023 11:49:12 -0400 Subject: [PATCH] fix: show/set activity like per user (#4775) * fix: like per user * chore: open api * chore: e2e test for userId filtering --- cli/src/api/open-api/api.ts | 23 ++++++++++++++---- mobile/openapi/doc/ActivityApi.md | Bin 8794 -> 8917 bytes mobile/openapi/lib/api/activity_api.dart | Bin 7160 -> 7364 bytes mobile/openapi/test/activity_api_test.dart | Bin 1091 -> 1106 bytes server/immich-openapi-specs.json | 9 +++++++ server/src/domain/activity/activity.dto.ts | 3 +++ .../src/domain/activity/activity.service.ts | 1 + server/test/e2e/activity.e2e-spec.ts | 23 ++++++++++++++++++ web/src/api/open-api/api.ts | 23 ++++++++++++++---- .../asset-viewer/asset-viewer.svelte | 3 ++- 10 files changed, 74 insertions(+), 11 deletions(-) diff --git a/cli/src/api/open-api/api.ts b/cli/src/api/open-api/api.ts index 8d9181e2f7..b00b0deb5f 100644 --- a/cli/src/api/open-api/api.ts +++ b/cli/src/api/open-api/api.ts @@ -5076,10 +5076,11 @@ export const ActivityApiAxiosParamCreator = function (configuration?: Configurat * @param {string} albumId * @param {string} [assetId] * @param {ReactionType} [type] + * @param {string} [userId] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getActivities: async (albumId: string, assetId?: string, type?: ReactionType, options: AxiosRequestConfig = {}): Promise => { + getActivities: async (albumId: string, assetId?: string, type?: ReactionType, userId?: string, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'albumId' is not null or undefined assertParamExists('getActivities', 'albumId', albumId) const localVarPath = `/activity`; @@ -5115,6 +5116,10 @@ export const ActivityApiAxiosParamCreator = function (configuration?: Configurat localVarQueryParameter['type'] = type; } + if (userId !== undefined) { + localVarQueryParameter['userId'] = userId; + } + setSearchParams(localVarUrlObj, localVarQueryParameter); @@ -5211,11 +5216,12 @@ export const ActivityApiFp = function(configuration?: Configuration) { * @param {string} albumId * @param {string} [assetId] * @param {ReactionType} [type] + * @param {string} [userId] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getActivities(albumId: string, assetId?: string, type?: ReactionType, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getActivities(albumId, assetId, type, options); + async getActivities(albumId: string, assetId?: string, type?: ReactionType, userId?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getActivities(albumId, assetId, type, userId, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -5264,7 +5270,7 @@ export const ActivityApiFactory = function (configuration?: Configuration, baseP * @throws {RequiredError} */ getActivities(requestParameters: ActivityApiGetActivitiesRequest, options?: AxiosRequestConfig): AxiosPromise> { - return localVarFp.getActivities(requestParameters.albumId, requestParameters.assetId, requestParameters.type, options).then((request) => request(axios, basePath)); + return localVarFp.getActivities(requestParameters.albumId, requestParameters.assetId, requestParameters.type, requestParameters.userId, options).then((request) => request(axios, basePath)); }, /** * @@ -5332,6 +5338,13 @@ export interface ActivityApiGetActivitiesRequest { * @memberof ActivityApiGetActivities */ readonly type?: ReactionType + + /** + * + * @type {string} + * @memberof ActivityApiGetActivities + */ + readonly userId?: string } /** @@ -5392,7 +5405,7 @@ export class ActivityApi extends BaseAPI { * @memberof ActivityApi */ public getActivities(requestParameters: ActivityApiGetActivitiesRequest, options?: AxiosRequestConfig) { - return ActivityApiFp(this.configuration).getActivities(requestParameters.albumId, requestParameters.assetId, requestParameters.type, options).then((request) => request(this.axios, this.basePath)); + return ActivityApiFp(this.configuration).getActivities(requestParameters.albumId, requestParameters.assetId, requestParameters.type, requestParameters.userId, options).then((request) => request(this.axios, this.basePath)); } /** diff --git a/mobile/openapi/doc/ActivityApi.md b/mobile/openapi/doc/ActivityApi.md index 8ae91efc29d8ea444a0d1712e516a60e03f214c3..1af3f1f496f70a1d437255561d548627878fcea8 100644 GIT binary patch delta 98 zcmccRa@BRi7D09$h0@~GBG1i^T7YvmdyM~NwMrDf(N=71$8-xsx*ynv5;GKaj{ thZ#K^9or3Er(@0Z1R)bH0C}DiXaE2J diff --git a/mobile/openapi/test/activity_api_test.dart b/mobile/openapi/test/activity_api_test.dart index 401264c2bb7ad85d934eaa94df75a02b6ee7a8d7..9b6fe1a6ca8090386131dad2ce93afb240bb13ab 100644 GIT binary patch delta 28 jcmX@iafxGtH8a1CLU2h@W?s5NX>n?iXUb-4W)4OGii`;C delta 12 Tcmcb_ahPL+HS=aKW)4OG9ufnc diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index a78621c28f..822513720b 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -30,6 +30,15 @@ "schema": { "$ref": "#/components/schemas/ReactionType" } + }, + { + "name": "userId", + "required": false, + "in": "query", + "schema": { + "format": "uuid", + "type": "string" + } } ], "responses": { diff --git a/server/src/domain/activity/activity.dto.ts b/server/src/domain/activity/activity.dto.ts index 894c9b29c0..e1a163b814 100644 --- a/server/src/domain/activity/activity.dto.ts +++ b/server/src/domain/activity/activity.dto.ts @@ -38,6 +38,9 @@ export class ActivitySearchDto extends ActivityDto { @Optional() @ApiProperty({ enumName: 'ReactionType', enum: ReactionType }) type?: ReactionType; + + @ValidateUUID({ optional: true }) + userId?: string; } const isComment = (dto: ActivityCreateDto) => dto.type === 'comment'; diff --git a/server/src/domain/activity/activity.service.ts b/server/src/domain/activity/activity.service.ts index e5af6169a1..bacdab317f 100644 --- a/server/src/domain/activity/activity.service.ts +++ b/server/src/domain/activity/activity.service.ts @@ -28,6 +28,7 @@ export class ActivityService { async getAll(authUser: AuthUserDto, dto: ActivitySearchDto): Promise { await this.access.requirePermission(authUser, Permission.ALBUM_READ, dto.albumId); const activities = await this.repository.search({ + userId: dto.userId, albumId: dto.albumId, assetId: dto.assetId, isLiked: dto.type && dto.type === ReactionType.LIKE, diff --git a/server/test/e2e/activity.e2e-spec.ts b/server/test/e2e/activity.e2e-spec.ts index c488630ec8..5cc86fc6aa 100644 --- a/server/test/e2e/activity.e2e-spec.ts +++ b/server/test/e2e/activity.e2e-spec.ts @@ -134,6 +134,29 @@ describe(`${ActivityController.name} (e2e)`, () => { expect(body[0]).toEqual(reaction); }); + it('should filter by userId', async () => { + const [reaction] = await Promise.all([ + api.activityApi.create(server, admin.accessToken, { albumId: album.id, type: ReactionType.LIKE }), + ]); + + const response1 = await request(server) + .get('/activity') + .query({ albumId: album.id, userId: uuidStub.notFound }) + .set('Authorization', `Bearer ${admin.accessToken}`); + + expect(response1.status).toEqual(200); + expect(response1.body.length).toBe(0); + + const response2 = await request(server) + .get('/activity') + .query({ albumId: album.id, userId: admin.userId }) + .set('Authorization', `Bearer ${admin.accessToken}`); + + expect(response2.status).toEqual(200); + expect(response2.body.length).toBe(1); + expect(response2.body[0]).toEqual(reaction); + }); + it('should filter by assetId', async () => { const [reaction] = await Promise.all([ api.activityApi.create(server, admin.accessToken, { diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index 8d9181e2f7..b00b0deb5f 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -5076,10 +5076,11 @@ export const ActivityApiAxiosParamCreator = function (configuration?: Configurat * @param {string} albumId * @param {string} [assetId] * @param {ReactionType} [type] + * @param {string} [userId] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getActivities: async (albumId: string, assetId?: string, type?: ReactionType, options: AxiosRequestConfig = {}): Promise => { + getActivities: async (albumId: string, assetId?: string, type?: ReactionType, userId?: string, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'albumId' is not null or undefined assertParamExists('getActivities', 'albumId', albumId) const localVarPath = `/activity`; @@ -5115,6 +5116,10 @@ export const ActivityApiAxiosParamCreator = function (configuration?: Configurat localVarQueryParameter['type'] = type; } + if (userId !== undefined) { + localVarQueryParameter['userId'] = userId; + } + setSearchParams(localVarUrlObj, localVarQueryParameter); @@ -5211,11 +5216,12 @@ export const ActivityApiFp = function(configuration?: Configuration) { * @param {string} albumId * @param {string} [assetId] * @param {ReactionType} [type] + * @param {string} [userId] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getActivities(albumId: string, assetId?: string, type?: ReactionType, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getActivities(albumId, assetId, type, options); + async getActivities(albumId: string, assetId?: string, type?: ReactionType, userId?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getActivities(albumId, assetId, type, userId, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -5264,7 +5270,7 @@ export const ActivityApiFactory = function (configuration?: Configuration, baseP * @throws {RequiredError} */ getActivities(requestParameters: ActivityApiGetActivitiesRequest, options?: AxiosRequestConfig): AxiosPromise> { - return localVarFp.getActivities(requestParameters.albumId, requestParameters.assetId, requestParameters.type, options).then((request) => request(axios, basePath)); + return localVarFp.getActivities(requestParameters.albumId, requestParameters.assetId, requestParameters.type, requestParameters.userId, options).then((request) => request(axios, basePath)); }, /** * @@ -5332,6 +5338,13 @@ export interface ActivityApiGetActivitiesRequest { * @memberof ActivityApiGetActivities */ readonly type?: ReactionType + + /** + * + * @type {string} + * @memberof ActivityApiGetActivities + */ + readonly userId?: string } /** @@ -5392,7 +5405,7 @@ export class ActivityApi extends BaseAPI { * @memberof ActivityApi */ public getActivities(requestParameters: ActivityApiGetActivitiesRequest, options?: AxiosRequestConfig) { - return ActivityApiFp(this.configuration).getActivities(requestParameters.albumId, requestParameters.assetId, requestParameters.type, options).then((request) => request(this.axios, this.basePath)); + return ActivityApiFp(this.configuration).getActivities(requestParameters.albumId, requestParameters.assetId, requestParameters.type, requestParameters.userId, options).then((request) => request(this.axios, this.basePath)); } /** diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte index 8ececaf1d2..e4cceb664d 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte @@ -123,9 +123,10 @@ }; const getFavorite = async () => { - if (album) { + if (album && user) { try { const { data } = await api.activityApi.getActivities({ + userId: user.id, assetId: asset.id, albumId: album.id, type: ReactionType.Like,