Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions .changeset/chat-get-message-by-file-id.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@rocket.chat/rest-typings': minor
'@rocket.chat/meteor': minor
---

Adds a `GET /v1/chat.getMessageByFileId` endpoint that returns the message a file was sent in, given the file id
42 changes: 42 additions & 0 deletions apps/meteor/server/api/v1/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
isChatDeleteProps,
isChatSyncMessagesProps,
isChatGetMessageProps,
isChatGetMessageByFileIdProps,
isChatPostMessageProps,
isChatSearchProps,
isChatSendMessageProps,
Expand All @@ -25,6 +26,8 @@ import {
isChatGetStarredMessagesProps,
isChatGetDiscussionsProps,
validateBadRequestErrorResponse,
validateForbiddenErrorResponse,
validateNotFoundErrorResponse,
validateUnauthorizedErrorResponse,
} from '@rocket.chat/rest-typings';
import { escapeRegExp } from '@rocket.chat/tools';
Expand Down Expand Up @@ -791,6 +794,45 @@ const chatEndpoints = API.v1
});
},
)
.get(
'chat.getMessageByFileId',
{
authRequired: true,
query: isChatGetMessageByFileIdProps,
response: {
200: ajv.compile<{ message: IMessage }>({
type: 'object',
properties: {
message: { $ref: '#/components/schemas/IMessage' },
success: { type: 'boolean', enum: [true] },
},
required: ['message', 'success'],
additionalProperties: false,
}),
400: validateBadRequestErrorResponse,
401: validateUnauthorizedErrorResponse,
403: validateForbiddenErrorResponse,
404: validateNotFoundErrorResponse,
},
},
async function action() {
const msg = await Messages.getMessageByFileId(this.queryParams.fileId);

if (!msg?.rid) {
return API.v1.notFound();
}

if (!(await canAccessRoomIdAsync(msg.rid, this.userId))) {
return API.v1.forbidden();
}

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

return API.v1.success({
message,
});
},
)
.post(
'chat.postMessage',
{
Expand Down
92 changes: 92 additions & 0 deletions apps/meteor/tests/end-to-end/api/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -935,6 +935,98 @@ describe('[Chat]', () => {
});
});

describe('/chat.getMessageByFileId', () => {
let fileRoom: IRoom;
let fileId: string;
let fileMessageId: IMessage['_id'];
let otherUser: TestUser<IUser>;
let otherUserCredentials: Credentials;

before(async () => {
// private, so the access check is meaningful: any user can preview a public channel
fileRoom = (await createRoom({ type: 'p', name: `chat-file-message-id-${Date.now()}` })).body.group;

otherUser = await createUser();
otherUserCredentials = await login(otherUser.username, password);

await request
.post(api(`rooms.media/${fileRoom._id}`))
.set(credentials)
.attach('file', imgURL)
.expect(200)
.expect((res: Response) => {
fileId = res.body.file._id;
});

await request
.post(api(`rooms.mediaConfirm/${fileRoom._id}/${fileId}`))
.set(credentials)
.expect(200)
.expect((res: Response) => {
fileMessageId = res.body.message._id;
});
});

after(() => Promise.all([deleteRoom({ type: 'p', roomId: fileRoom._id }), deleteUser(otherUser)]));

it('should return the message the file was sent in', async () => {
const res = await request
.get(api('chat.getMessageByFileId'))
.set(credentials)
.query({ fileId })
.expect('Content-Type', 'application/json')
.expect(200);

expect(res.body).to.have.property('success', true);
expect(res.body).to.have.nested.property('message._id', fileMessageId);
expect(res.body).to.have.nested.property('message.rid', fileRoom._id);
expect(res.body).to.have.nested.property('message.files[0]._id', fileId);
});

it("should fail when 'fileId' is not sent", async () => {
const res = await request.get(api('chat.getMessageByFileId')).set(credentials).expect('Content-Type', 'application/json').expect(400);

expect(res.body).to.have.property('success', false);
});

it("should fail when 'fileId' is empty", async () => {
const res = await request
.get(api('chat.getMessageByFileId'))
.set(credentials)
.query({ fileId: '' })
.expect('Content-Type', 'application/json')
.expect(400);

expect(res.body).to.have.property('success', false);
});

it('should return 404 when no message references the file', async () => {
const res = await request
.get(api('chat.getMessageByFileId'))
.set(credentials)
.query({ fileId: 'invalid-file-id' })
.expect('Content-Type', 'application/json')
.expect(404);

expect(res.body).to.have.property('success', false);
});

it('should not return the message to a user without access to the room', async () => {
const res = await request
.get(api('chat.getMessageByFileId'))
.set(otherUserCredentials)
.query({ fileId })
.expect('Content-Type', 'application/json')
.expect(403);

expect(res.body).to.have.property('success', false);
});

it('should fail when not authenticated', async () => {
await request.get(api('chat.getMessageByFileId')).query({ fileId }).expect(401);
});
});

describe('/chat.sendMessage', () => {
it("should throw an error when the required param 'rid' is not sent", async () => {
const res = await request
Expand Down
25 changes: 25 additions & 0 deletions packages/rest-typings/src/v1/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,26 @@ const ChatGetMessageSchema = {

export const isChatGetMessageProps = ajv.compile<ChatGetMessage>(ChatGetMessageSchema);

type ChatGetMessageByFileId = {
fileId: string;
};

const ChatGetMessageByFileIdSchema = {
type: 'object',
description: 'Fetch the message a file was sent in.',
properties: {
fileId: {
type: 'string',
minLength: 1,
description: 'The file id.',
},
},
required: ['fileId'],
additionalProperties: false,
};

export const isChatGetMessageByFileIdProps = ajv.compile<ChatGetMessageByFileId>(ChatGetMessageByFileIdSchema);

type ChatGetDiscussions = PaginatedRequest<{
roomId: IRoom['_id'];
text?: string;
Expand Down Expand Up @@ -939,6 +959,11 @@ export type ChatEndpoints = {
message: IMessage;
};
};
'/v1/chat.getMessageByFileId': {
GET: (params: ChatGetMessageByFileId) => {
message: IMessage;
};
};
'/v1/chat.reportMessage': {
POST: (params: ChatReportMessage) => void;
};
Expand Down
Loading