1
0
mirror of https://github.com/immich-app/immich.git synced 2024-12-25 10:43:13 +02:00

fix(server,web): correctly show album level like (#4916)

* fix: like in global activity

* refactor: rename isGlobal to ReactionLevel.Album

* chore: open api

* chore: e2e test for album vs comment duplicate like checking

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
This commit is contained in:
martin 2023-11-10 03:32:31 +01:00 committed by GitHub
parent 986bbfa831
commit 92ec1ce77f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 120 additions and 20 deletions

View File

@ -2570,6 +2570,20 @@ export interface QueueStatusDto {
*/
'isPaused': boolean;
}
/**
*
* @export
* @enum {string}
*/
export const ReactionLevel = {
Album: 'album',
Asset: 'asset'
} as const;
export type ReactionLevel = typeof ReactionLevel[keyof typeof ReactionLevel];
/**
*
* @export
@ -5065,11 +5079,12 @@ export const ActivityApiAxiosParamCreator = function (configuration?: Configurat
* @param {string} albumId
* @param {string} [assetId]
* @param {ReactionType} [type]
* @param {ReactionLevel} [level]
* @param {string} [userId]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getActivities: async (albumId: string, assetId?: string, type?: ReactionType, userId?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
getActivities: async (albumId: string, assetId?: string, type?: ReactionType, level?: ReactionLevel, userId?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'albumId' is not null or undefined
assertParamExists('getActivities', 'albumId', albumId)
const localVarPath = `/activity`;
@ -5105,6 +5120,10 @@ export const ActivityApiAxiosParamCreator = function (configuration?: Configurat
localVarQueryParameter['type'] = type;
}
if (level !== undefined) {
localVarQueryParameter['level'] = level;
}
if (userId !== undefined) {
localVarQueryParameter['userId'] = userId;
}
@ -5205,12 +5224,13 @@ export const ActivityApiFp = function(configuration?: Configuration) {
* @param {string} albumId
* @param {string} [assetId]
* @param {ReactionType} [type]
* @param {ReactionLevel} [level]
* @param {string} [userId]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async getActivities(albumId: string, assetId?: string, type?: ReactionType, userId?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<ActivityResponseDto>>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getActivities(albumId, assetId, type, userId, options);
async getActivities(albumId: string, assetId?: string, type?: ReactionType, level?: ReactionLevel, userId?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<ActivityResponseDto>>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getActivities(albumId, assetId, type, level, userId, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
@ -5259,7 +5279,7 @@ export const ActivityApiFactory = function (configuration?: Configuration, baseP
* @throws {RequiredError}
*/
getActivities(requestParameters: ActivityApiGetActivitiesRequest, options?: AxiosRequestConfig): AxiosPromise<Array<ActivityResponseDto>> {
return localVarFp.getActivities(requestParameters.albumId, requestParameters.assetId, requestParameters.type, requestParameters.userId, options).then((request) => request(axios, basePath));
return localVarFp.getActivities(requestParameters.albumId, requestParameters.assetId, requestParameters.type, requestParameters.level, requestParameters.userId, options).then((request) => request(axios, basePath));
},
/**
*
@ -5328,6 +5348,13 @@ export interface ActivityApiGetActivitiesRequest {
*/
readonly type?: ReactionType
/**
*
* @type {ReactionLevel}
* @memberof ActivityApiGetActivities
*/
readonly level?: ReactionLevel
/**
*
* @type {string}
@ -5394,7 +5421,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, requestParameters.userId, options).then((request) => request(this.axios, this.basePath));
return ActivityApiFp(this.configuration).getActivities(requestParameters.albumId, requestParameters.assetId, requestParameters.type, requestParameters.level, requestParameters.userId, options).then((request) => request(this.axios, this.basePath));
}
/**

View File

@ -101,6 +101,7 @@ doc/PersonResponseDto.md
doc/PersonStatisticsResponseDto.md
doc/PersonUpdateDto.md
doc/QueueStatusDto.md
doc/ReactionLevel.md
doc/ReactionType.md
doc/RecognitionConfig.md
doc/ScanLibraryDto.md
@ -281,6 +282,7 @@ lib/model/person_response_dto.dart
lib/model/person_statistics_response_dto.dart
lib/model/person_update_dto.dart
lib/model/queue_status_dto.dart
lib/model/reaction_level.dart
lib/model/reaction_type.dart
lib/model/recognition_config.dart
lib/model/scan_library_dto.dart
@ -440,6 +442,7 @@ test/person_response_dto_test.dart
test/person_statistics_response_dto_test.dart
test/person_update_dto_test.dart
test/queue_status_dto_test.dart
test/reaction_level_test.dart
test/reaction_type_test.dart
test/recognition_config_test.dart
test/scan_library_dto_test.dart

BIN
mobile/openapi/README.md generated

Binary file not shown.

Binary file not shown.

BIN
mobile/openapi/doc/ReactionLevel.md generated Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -31,6 +31,14 @@
"$ref": "#/components/schemas/ReactionType"
}
},
{
"name": "level",
"required": false,
"in": "query",
"schema": {
"$ref": "#/components/schemas/ReactionLevel"
}
},
{
"name": "userId",
"required": false,
@ -7728,6 +7736,13 @@
],
"type": "object"
},
"ReactionLevel": {
"enum": [
"album",
"asset"
],
"type": "string"
},
"ReactionType": {
"enum": [
"comment",

View File

@ -9,6 +9,11 @@ export enum ReactionType {
LIKE = 'like',
}
export enum ReactionLevel {
ALBUM = 'album',
ASSET = 'asset',
}
export type MaybeDuplicate<T> = { duplicate: boolean; value: T };
export class ActivityResponseDto {
@ -39,6 +44,11 @@ export class ActivitySearchDto extends ActivityDto {
@ApiProperty({ enumName: 'ReactionType', enum: ReactionType })
type?: ReactionType;
@IsEnum(ReactionLevel)
@Optional()
@ApiProperty({ enumName: 'ReactionLevel', enum: ReactionLevel })
level?: ReactionLevel;
@ValidateUUID({ optional: true })
userId?: string;
}

View File

@ -10,6 +10,7 @@ import {
ActivitySearchDto,
ActivityStatisticsResponseDto,
MaybeDuplicate,
ReactionLevel,
ReactionType,
mapActivity,
} from './activity.dto';
@ -30,7 +31,7 @@ export class ActivityService {
const activities = await this.repository.search({
userId: dto.userId,
albumId: dto.albumId,
assetId: dto.assetId,
assetId: dto.level === ReactionLevel.ALBUM ? null : dto.assetId,
isLiked: dto.type && dto.type === ReactionType.LIKE,
});
@ -54,11 +55,12 @@ export class ActivityService {
let activity: ActivityEntity | null = null;
let duplicate = false;
if (dto.type === 'like') {
if (dto.type === ReactionType.LIKE) {
delete dto.comment;
[activity] = await this.repository.search({
...common,
isGlobal: !dto.assetId,
// `null` will search for an album like
assetId: dto.assetId ?? null,
isLiked: true,
});
duplicate = !!activity;

View File

@ -6,10 +6,9 @@ import { ActivityEntity } from '../entities/activity.entity';
export interface ActivitySearch {
albumId?: string;
assetId?: string;
assetId?: string | null;
userId?: string;
isLiked?: boolean;
isGlobal?: boolean;
}
@Injectable()
@ -17,11 +16,11 @@ export class ActivityRepository implements IActivityRepository {
constructor(@InjectRepository(ActivityEntity) private repository: Repository<ActivityEntity>) {}
search(options: ActivitySearch): Promise<ActivityEntity[]> {
const { userId, assetId, albumId, isLiked, isGlobal } = options;
const { userId, assetId, albumId, isLiked } = options;
return this.repository.find({
where: {
userId,
assetId: isGlobal ? IsNull() : assetId,
assetId: assetId === null ? IsNull() : assetId,
albumId,
isLiked,
},

View File

@ -247,6 +247,20 @@ describe(`${ActivityController.name} (e2e)`, () => {
expect(body).toEqual(reaction);
});
it('should not confuse an album like with an asset like', async () => {
const reaction = await api.activityApi.create(server, admin.accessToken, {
albumId: album.id,
assetId: asset.id,
type: ReactionType.LIKE,
});
const { status, body } = await request(server)
.post('/activity')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ albumId: album.id, type: 'like' });
expect(status).toEqual(201);
expect(body.id).not.toEqual(reaction.id);
});
it('should add a comment to an asset', async () => {
const { status, body } = await request(server)
.post('/activity')

View File

@ -2570,6 +2570,20 @@ export interface QueueStatusDto {
*/
'isPaused': boolean;
}
/**
*
* @export
* @enum {string}
*/
export const ReactionLevel = {
Album: 'album',
Asset: 'asset'
} as const;
export type ReactionLevel = typeof ReactionLevel[keyof typeof ReactionLevel];
/**
*
* @export
@ -5065,11 +5079,12 @@ export const ActivityApiAxiosParamCreator = function (configuration?: Configurat
* @param {string} albumId
* @param {string} [assetId]
* @param {ReactionType} [type]
* @param {ReactionLevel} [level]
* @param {string} [userId]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getActivities: async (albumId: string, assetId?: string, type?: ReactionType, userId?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
getActivities: async (albumId: string, assetId?: string, type?: ReactionType, level?: ReactionLevel, userId?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'albumId' is not null or undefined
assertParamExists('getActivities', 'albumId', albumId)
const localVarPath = `/activity`;
@ -5105,6 +5120,10 @@ export const ActivityApiAxiosParamCreator = function (configuration?: Configurat
localVarQueryParameter['type'] = type;
}
if (level !== undefined) {
localVarQueryParameter['level'] = level;
}
if (userId !== undefined) {
localVarQueryParameter['userId'] = userId;
}
@ -5205,12 +5224,13 @@ export const ActivityApiFp = function(configuration?: Configuration) {
* @param {string} albumId
* @param {string} [assetId]
* @param {ReactionType} [type]
* @param {ReactionLevel} [level]
* @param {string} [userId]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async getActivities(albumId: string, assetId?: string, type?: ReactionType, userId?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<ActivityResponseDto>>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getActivities(albumId, assetId, type, userId, options);
async getActivities(albumId: string, assetId?: string, type?: ReactionType, level?: ReactionLevel, userId?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<ActivityResponseDto>>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getActivities(albumId, assetId, type, level, userId, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
@ -5259,7 +5279,7 @@ export const ActivityApiFactory = function (configuration?: Configuration, baseP
* @throws {RequiredError}
*/
getActivities(requestParameters: ActivityApiGetActivitiesRequest, options?: AxiosRequestConfig): AxiosPromise<Array<ActivityResponseDto>> {
return localVarFp.getActivities(requestParameters.albumId, requestParameters.assetId, requestParameters.type, requestParameters.userId, options).then((request) => request(axios, basePath));
return localVarFp.getActivities(requestParameters.albumId, requestParameters.assetId, requestParameters.type, requestParameters.level, requestParameters.userId, options).then((request) => request(axios, basePath));
},
/**
*
@ -5328,6 +5348,13 @@ export interface ActivityApiGetActivitiesRequest {
*/
readonly type?: ReactionType
/**
*
* @type {ReactionLevel}
* @memberof ActivityApiGetActivities
*/
readonly level?: ReactionLevel
/**
*
* @type {string}
@ -5394,7 +5421,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, requestParameters.userId, options).then((request) => request(this.axios, this.basePath));
return ActivityApiFp(this.configuration).getActivities(requestParameters.albumId, requestParameters.assetId, requestParameters.type, requestParameters.level, requestParameters.userId, options).then((request) => request(this.axios, this.basePath));
}
/**

View File

@ -39,6 +39,7 @@
export let assetType: AssetTypeEnum | undefined = undefined;
export let albumOwnerId: string;
export let disabled: boolean;
export let isLiked: ActivityResponseDto | null;
let textArea: HTMLTextAreaElement;
let innerHeight: number;
@ -105,7 +106,7 @@
reactions.splice(index, 1);
showDeleteReaction.splice(index, 1);
reactions = reactions;
if (reaction.type === 'like' && reaction.user.id === user.id) {
if (isLiked && reaction.type === 'like' && reaction.id == isLiked.id) {
dispatch('deleteLike');
} else {
dispatch('deleteComment');

View File

@ -756,6 +756,7 @@
albumOwnerId={album.ownerId}
albumId={album.id}
assetId={asset.id}
{isLiked}
bind:reactions
on:addComment={handleAddComment}
on:deleteComment={handleRemoveComment}

View File

@ -35,7 +35,7 @@
import { downloadArchive } from '$lib/utils/asset-utils';
import { openFileUploadDialog } from '$lib/utils/file-uploader';
import { handleError } from '$lib/utils/handle-error';
import { ActivityResponseDto, ReactionType, UserResponseDto, api } from '@api';
import { ActivityResponseDto, ReactionLevel, ReactionType, UserResponseDto, api } from '@api';
import Icon from '$lib/components/elements/icon.svelte';
import type { PageData } from './$types';
import { clickOutside } from '$lib/utils/click-outside';
@ -167,7 +167,6 @@
const { data } = await api.activityApi.createActivity({
activityCreateDto: { albumId: album.id, type: ReactionType.Like },
});
isLiked = data;
reactions = [...reactions, isLiked];
}
@ -183,6 +182,7 @@
userId: user.id,
albumId: album.id,
type: ReactionType.Like,
level: ReactionLevel.Album,
});
if (data.length > 0) {
isLiked = data[0];
@ -687,6 +687,7 @@
disabled={!album.isActivityEnabled}
albumOwnerId={album.ownerId}
albumId={album.id}
{isLiked}
bind:reactions
on:addComment={() => updateNumberOfComments(1)}
on:deleteComment={() => updateNumberOfComments(-1)}