Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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