Skip to content

Commit

Permalink
refactor: pinMessage method (RocketChat#33852)
Browse files Browse the repository at this point in the history
  • Loading branch information
KevLehman authored Nov 19, 2024
1 parent 162d1d2 commit c49808b
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 86 deletions.
3 changes: 2 additions & 1 deletion apps/meteor/app/api/server/v1/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { processWebhookMessage } from '../../../lib/server/functions/processWebh
import { executeSendMessage } from '../../../lib/server/methods/sendMessage';
import { executeUpdateMessage } from '../../../lib/server/methods/updateMessage';
import { applyAirGappedRestrictionsValidation } from '../../../license/server/airGappedRestrictionsWrapper';
import { pinMessage } from '../../../message-pin/server/pinMessage';
import { OEmbed } from '../../../oembed/server/server';
import { executeSetReaction } from '../../../reactions/server/setReaction';
import { settings } from '../../../settings/server';
Expand Down Expand Up @@ -145,7 +146,7 @@ API.v1.addRoute(
throw new Meteor.Error('error-message-not-found', 'The provided "messageId" does not match any existing message.');
}

const pinnedMessage = await Meteor.callAsync('pinMessage', msg);
const pinnedMessage = await pinMessage(msg, this.userId);

const [message] = await normalizeMessagesForUser([pinnedMessage], this.userId);

Expand Down
163 changes: 78 additions & 85 deletions apps/meteor/app/message-pin/server/pinMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,108 +43,101 @@ declare module '@rocket.chat/ddp-client' {
}
}

Meteor.methods<ServerMethods>({
async pinMessage(message, pinnedAt) {
check(message._id, String);

const userId = Meteor.userId();
if (!userId) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
method: 'pinMessage',
});
}
export async function pinMessage(originalMessage: IMessage, userId: string, pinnedAt?: Date) {
if (!settings.get('Message_AllowPinning')) {
throw new Meteor.Error('error-action-not-allowed', 'Message pinning not allowed', {
method: 'pinMessage',
action: 'Message_pinning',
});
}

if (!settings.get('Message_AllowPinning')) {
throw new Meteor.Error('error-action-not-allowed', 'Message pinning not allowed', {
method: 'pinMessage',
action: 'Message_pinning',
});
}
if (!(await hasPermissionAsync(userId, 'pin-message', originalMessage.rid))) {
throw new Meteor.Error('not-authorized', 'Not Authorized', { method: 'pinMessage' });
}

let originalMessage = await Messages.findOneById(message._id);
if (originalMessage == null || originalMessage._id == null) {
throw new Meteor.Error('error-invalid-message', 'Message you are pinning was not found', {
method: 'pinMessage',
action: 'Message_pinning',
});
}
const room = await Rooms.findOneById(originalMessage.rid);
if (!room) {
throw new Meteor.Error('not-authorized', 'Not Authorized', { method: 'pinMessage' });
}

const subscription = await Subscriptions.findOneByRoomIdAndUserId(originalMessage.rid, userId, { projection: { _id: 1 } });
if (!subscription) {
// If it's a valid message but on a room that the user is not subscribed to, report that the message was not found.
throw new Meteor.Error('error-invalid-message', 'Message you are pinning was not found', {
method: 'pinMessage',
action: 'Message_pinning',
});
}
if (!(await canAccessRoomAsync(room, { _id: userId }))) {
throw new Meteor.Error('not-authorized', 'Not Authorized', { method: 'pinMessage' });
}

if (!(await hasPermissionAsync(userId, 'pin-message', originalMessage.rid))) {
throw new Meteor.Error('not-authorized', 'Not Authorized', { method: 'pinMessage' });
}
if (originalMessage.pinned) {
return originalMessage;
}

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

// If we keep history of edits, insert a new message to store history information
if (settings.get('Message_KeepHistory') && isRegisterUser(me)) {
await Messages.cloneAndSaveAsHistoryById(message._id, me);
}
originalMessage.pinned = true;
originalMessage.pinnedAt = pinnedAt || new Date();
originalMessage.pinnedBy = {
_id: userId,
username: me.username,
};

const room = await Rooms.findOneById(originalMessage.rid);
if (!room) {
throw new Meteor.Error('not-authorized', 'Not Authorized', { method: 'pinMessage' });
}
originalMessage = await Message.beforeSave({ message: originalMessage, room, user: me });

if (!(await canAccessRoomAsync(room, { _id: userId }))) {
throw new Meteor.Error('not-authorized', 'Not Authorized', { method: 'pinMessage' });
}
await Messages.setPinnedByIdAndUserId(originalMessage._id, originalMessage.pinnedBy, originalMessage.pinned);
if (settings.get('Message_Read_Receipt_Store_Users')) {
await ReadReceipts.setPinnedByMessageId(originalMessage._id, originalMessage.pinned);
}
if (isTheLastMessage(room, originalMessage)) {
await Rooms.setLastMessagePinned(room._id, originalMessage.pinnedBy, originalMessage.pinned);
}

originalMessage.pinned = true;
originalMessage.pinnedAt = pinnedAt || new Date();
originalMessage.pinnedBy = {
_id: userId,
username: me.username,
};
const attachments: MessageAttachment[] = [];

originalMessage = await Message.beforeSave({ message: originalMessage, room, user: me });
if (Array.isArray(originalMessage.attachments)) {
originalMessage.attachments.forEach((attachment) => {
if (!isQuoteAttachment(attachment) || shouldAdd(attachments, attachment)) {
attachments.push(attachment);
}
});
}

await Messages.setPinnedByIdAndUserId(originalMessage._id, originalMessage.pinnedBy, originalMessage.pinned);
if (settings.get('Message_Read_Receipt_Store_Users')) {
await ReadReceipts.setPinnedByMessageId(message._id, originalMessage.pinned);
}
if (isTheLastMessage(room, message)) {
await Rooms.setLastMessagePinned(room._id, originalMessage.pinnedBy, originalMessage.pinned);
void notifyOnRoomChangedById(room._id);
}
// App IPostMessagePinned event hook
await Apps.self?.triggerEvent(AppEvents.IPostMessagePinned, originalMessage, await Meteor.userAsync(), originalMessage.pinned);

const pinMessageType = originalMessage.t === 'e2e' ? 'message_pinned_e2e' : 'message_pinned';

return Message.saveSystemMessage(pinMessageType, originalMessage.rid, '', me, {
attachments: [
{
text: originalMessage.msg,
author_name: originalMessage.u.username,
author_icon: getUserAvatarURL(originalMessage.u.username),
ts: originalMessage.ts,
attachments: attachments.map(recursiveRemove),
},
],
});
}

const attachments: MessageAttachment[] = [];
Meteor.methods<ServerMethods>({
async pinMessage(message, pinnedAt) {
check(message._id, String);

if (Array.isArray(originalMessage.attachments)) {
originalMessage.attachments.forEach((attachment) => {
if (!isQuoteAttachment(attachment) || shouldAdd(attachments, attachment)) {
attachments.push(attachment);
}
const userId = Meteor.userId();
if (!userId) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
method: 'pinMessage',
});
}

// App IPostMessagePinned event hook
await Apps.self?.triggerEvent(AppEvents.IPostMessagePinned, originalMessage, await Meteor.userAsync(), originalMessage.pinned);
const originalMessage = await Messages.findOneById(message._id);
if (!originalMessage?.rid) {
throw new Meteor.Error('error-invalid-message', 'Message you are pinning was not found', {
method: 'pinMessage',
action: 'Message_pinning',
});
}

const pinMessageType = originalMessage.t === 'e2e' ? 'message_pinned_e2e' : 'message_pinned';

return Message.saveSystemMessage(pinMessageType, originalMessage.rid, '', me, {
attachments: [
{
text: originalMessage.msg,
author_name: originalMessage.u.username,
author_icon: getUserAvatarURL(originalMessage.u.username),
ts: originalMessage.ts,
attachments: attachments.map(recursiveRemove),
},
],
});
return pinMessage(originalMessage, userId, pinnedAt);
},
async unpinMessage(message) {
check(message._id, String);
Expand Down
30 changes: 30 additions & 0 deletions apps/meteor/tests/end-to-end/api/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2507,6 +2507,21 @@ describe('[Chat]', () => {
});
});

it('should return an error when messageId does not exist', async () => {
await request
.post(api('chat.pinMessage'))
.set(credentials)
.send({
messageId: 'test',
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error');
});
});

it('should pin Message successfully', (done) => {
void updatePermission('pin-message', ['admin']).then(() => {
void request
Expand All @@ -2524,6 +2539,21 @@ describe('[Chat]', () => {
.end(done);
});
});

it('should return message when its already pinned', async () => {
await request
.post(api('chat.pinMessage'))
.set(credentials)
.send({
messageId: message._id,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.not.have.property('error');
});
});
});

describe('[/chat.unPinMessage]', () => {
Expand Down

0 comments on commit c49808b

Please sign in to comment.