Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Reactions set/unset #32994

Merged
merged 27 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
6d47822
Refactor setReaction
KevLehman Aug 5, 2024
26d44f5
Change find with countDocuments and add tests
KevLehman Aug 6, 2024
b6bda01
ts
KevLehman Aug 6, 2024
17ced60
lint
KevLehman Aug 6, 2024
1aa1cd5
cr
KevLehman Aug 12, 2024
3cc2055
lint
KevLehman Aug 12, 2024
f6243d4
Merge branch 'develop' into refactor/set-reactions
KevLehman Aug 20, 2024
d2a6873
Merge branch 'develop' into refactor/set-reactions
KevLehman Aug 20, 2024
013db55
Merge branch 'develop' into refactor/set-reactions
KevLehman Aug 20, 2024
9b838f5
Merge branch 'develop' into refactor/set-reactions
KevLehman Aug 21, 2024
c00fb3a
Merge branch 'refactor/set-reactions' of github.com:RocketChat/Rocket…
KevLehman Aug 21, 2024
4a0d65d
accept at least federated key
KevLehman Aug 21, 2024
08fe9aa
Merge branch 'develop' into refactor/set-reactions
KevLehman Aug 21, 2024
0349cf4
Update afterUnsetReaction.js
KevLehman Sep 16, 2024
9d6077f
Update isTheLastMessage.ts
KevLehman Sep 17, 2024
b1d3b60
Update BeforeFederationActions.ts
KevLehman Sep 17, 2024
c3629a0
Update service.ts
KevLehman Sep 17, 2024
0a304be
Update IMessageService.ts
KevLehman Sep 17, 2024
9e502b1
Discard changes to apps/meteor/app/reactions/client/methods/setReacti…
KevLehman Sep 18, 2024
995e7c3
Discard changes to apps/meteor/app/slackbridge/server/RocketAdapter.js
KevLehman Sep 18, 2024
b318215
Discard changes to apps/meteor/lib/callbacks.ts
KevLehman Sep 18, 2024
263a8b8
Merge branch 'develop' into refactor/set-reactions
KevLehman Sep 18, 2024
0cf7b67
Update setReaction.ts
KevLehman Sep 19, 2024
8784d7f
Update setReaction.ts
KevLehman Sep 19, 2024
71cc1aa
Update setReaction.ts
KevLehman Sep 19, 2024
9a2fc13
Update setReaction.spec.ts
KevLehman Sep 19, 2024
1accdaf
Merge branch 'develop' into refactor/set-reactions
kodiakhq[bot] Sep 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/meteor/app/api/server/v1/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ API.v1.addRoute(
throw new Meteor.Error('error-emoji-param-not-provided', 'The required "emoji" param is missing.');
}

await executeSetReaction(this.userId, emoji, msg._id, this.bodyParams.shouldReact);
await executeSetReaction(this.userId, emoji, msg, this.bodyParams.shouldReact);

return API.v1.success();
},
Expand Down
109 changes: 61 additions & 48 deletions apps/meteor/app/reactions/server/setReaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,46 @@ import type { IMessage, IRoom, IUser } from '@rocket.chat/core-typings';
import type { ServerMethods } from '@rocket.chat/ddp-client';
import { Messages, EmojiCustom, Rooms, Users } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';
import _ from 'underscore';

import { callbacks } from '../../../lib/callbacks';
import { i18n } from '../../../server/lib/i18n';
import { canAccessRoomAsync } from '../../authorization/server';
import { hasPermissionAsync } from '../../authorization/server/functions/hasPermission';
import { emoji } from '../../emoji/server';
import { isTheLastMessage } from '../../lib/server/functions/isTheLastMessage';
import { notifyOnRoomChangedById, notifyOnMessageChange } from '../../lib/server/lib/notifyListener';
import { notifyOnMessageChange } from '../../lib/server/lib/notifyListener';

const removeUserReaction = (message: IMessage, reaction: string, username: string) => {
export const removeUserReaction = (message: IMessage, reaction: string, username: string) => {
if (!message.reactions) {
return message;
}

message.reactions[reaction].usernames.splice(message.reactions[reaction].usernames.indexOf(username), 1);
if (message.reactions[reaction].usernames.length === 0) {
const idx = message.reactions[reaction].usernames.indexOf(username);

// user not found in reaction array
if (idx === -1) {
return message;
}

message.reactions[reaction].usernames.splice(idx, 1);
if (!message.reactions[reaction].usernames.length) {
delete message.reactions[reaction];
}
return message;
};

async function setReaction(room: IRoom, user: IUser, message: IMessage, reaction: string, shouldReact?: boolean) {
reaction = `:${reaction.replace(/:/g, '')}:`;
export async function setReaction(
room: Pick<IRoom, '_id' | 'muted' | 'unmuted' | 'reactWhenReadOnly' | 'ro' | 'lastMessage' | 'federated'>,
user: IUser,
message: IMessage,
reaction: string,
userAlreadyReacted?: boolean,
) {
await Message.beforeReacted(message, room);

if (!emoji.list[reaction] && (await EmojiCustom.findByNameOrAlias(reaction, {}).count()) === 0) {
throw new Meteor.Error('error-not-allowed', 'Invalid emoji provided.', {
method: 'setReaction',
if (Array.isArray(room.muted) && room.muted.includes(user.username as string)) {
throw new Meteor.Error('error-not-allowed', i18n.t('You_have_been_muted', { lng: user.language }), {
rid: room._id,
});
}

Expand All @@ -42,50 +54,23 @@ async function setReaction(room: IRoom, user: IUser, message: IMessage, reaction
}
}

if (Array.isArray(room.muted) && room.muted.indexOf(user.username as string) !== -1) {
throw new Meteor.Error('error-not-allowed', i18n.t('You_have_been_muted', { lng: user.language }), {
rid: room._id,
});
}

// if (!('reactions' in message)) {
// return;
// }

await Message.beforeReacted(message, room);

const userAlreadyReacted =
message.reactions &&
Boolean(message.reactions[reaction]) &&
message.reactions[reaction].usernames.indexOf(user.username as string) !== -1;
// When shouldReact was not informed, toggle the reaction.
if (shouldReact === undefined) {
shouldReact = !userAlreadyReacted;
}

if (userAlreadyReacted === shouldReact) {
return;
}

let isReacted;

if (userAlreadyReacted) {
const oldMessage = JSON.parse(JSON.stringify(message));
removeUserReaction(message, reaction, user.username as string);
if (_.isEmpty(message.reactions)) {
if (Object.keys(message.reactions || {}).length === 0) {
delete message.reactions;
await Messages.unsetReactions(message._id);
if (isTheLastMessage(room, message)) {
await Rooms.unsetReactionsInLastMessage(room._id);
void notifyOnRoomChangedById(room._id);
}
await Messages.unsetReactions(message._id);
} else {
await Messages.setReactions(message._id, message.reactions);
if (isTheLastMessage(room, message)) {
await Rooms.setReactionsInLastMessage(room._id, message.reactions);
}
}
await callbacks.run('afterUnsetReaction', message, { user, reaction, shouldReact, oldMessage });
void callbacks.run('afterUnsetReaction', message, { user, reaction, shouldReact: false, oldMessage });

isReacted = false;
} else {
Expand All @@ -101,33 +86,61 @@ async function setReaction(room: IRoom, user: IUser, message: IMessage, reaction
await Messages.setReactions(message._id, message.reactions);
if (isTheLastMessage(room, message)) {
await Rooms.setReactionsInLastMessage(room._id, message.reactions);
void notifyOnRoomChangedById(room._id);
}
await callbacks.run('afterSetReaction', message, { user, reaction, shouldReact });

void callbacks.run('afterSetReaction', message, { user, reaction, shouldReact: true });

isReacted = true;
}

await Apps.self?.triggerEvent(AppEvents.IPostMessageReacted, message, user, reaction, isReacted);
void Apps.self?.triggerEvent(AppEvents.IPostMessageReacted, message, user, reaction, isReacted);
KevLehman marked this conversation as resolved.
Show resolved Hide resolved

void notifyOnMessageChange({
id: message._id,
});
}

export async function executeSetReaction(userId: string, reaction: string, messageId: IMessage['_id'], shouldReact?: boolean) {
const user = await Users.findOneById(userId);
export async function executeSetReaction(
userId: string,
reaction: string,
messageParam: IMessage['_id'] | IMessage,
shouldReact?: boolean,
) {
// Check if the emoji is valid before proceeding
const reactionWithoutColons = reaction.replace(/:/g, '');
reaction = `:${reactionWithoutColons}:`;

if (!emoji.list[reaction] && (await EmojiCustom.countByNameOrAlias(reactionWithoutColons)) === 0) {
throw new Meteor.Error('error-not-allowed', 'Invalid emoji provided.', {
method: 'setReaction',
});
}

const user = await Users.findOneById(userId);
if (!user) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'setReaction' });
}

const message = await Messages.findOneById(messageId);
const message = typeof messageParam === 'string' ? await Messages.findOneById(messageParam) : messageParam;
if (!message) {
throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'setReaction' });
}

const room = await Rooms.findOneById(message.rid);
const userAlreadyReacted =
message.reactions && Boolean(message.reactions[reaction]) && message.reactions[reaction].usernames.includes(user.username as string);

// When shouldReact was not informed, toggle the reaction.
if (shouldReact === undefined) {
shouldReact = !userAlreadyReacted;
}

if (userAlreadyReacted === shouldReact) {
return;
}

const room = await Rooms.findOneById<
Pick<IRoom, '_id' | 'ro' | 'muted' | 'reactWhenReadOnly' | 'lastMessage' | 't' | 'prid' | 'federated'>
>(message.rid, { projection: { _id: 1, ro: 1, muted: 1, reactWhenReadOnly: 1, lastMessage: 1, t: 1, prid: 1, federated: 1 } });
if (!room) {
throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'setReaction' });
}
Expand All @@ -136,7 +149,7 @@ export async function executeSetReaction(userId: string, reaction: string, messa
throw new Meteor.Error('not-authorized', 'Not Authorized', { method: 'setReaction' });
}

return setReaction(room, user, message, reaction, shouldReact);
return setReaction(room, user, message, reaction, userAlreadyReacted);
}

declare module '@rocket.chat/ddp-client' {
Expand Down
8 changes: 8 additions & 0 deletions apps/meteor/server/models/raw/EmojiCustom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,12 @@ export class EmojiCustomRaw extends BaseRaw<IEmojiCustom> implements IEmojiCusto
create(data: InsertionModel<IEmojiCustom>): Promise<InsertOneResult<WithId<IEmojiCustom>>> {
return this.insertOne(data);
}

countByNameOrAlias(name: string): Promise<number> {
const query = {
$or: [{ name }, { aliases: name }],
};

return this.countDocuments(query);
}
}
Loading
Loading