Skip to content
Merged
6 changes: 6 additions & 0 deletions .changeset/nasty-countries-live.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@rocket.chat/meteor": patch
"@rocket.chat/rest-typings": patch
---

Fixes file filtering by name or type not working for non-private channels.
17 changes: 12 additions & 5 deletions apps/meteor/app/api/server/v1/groups.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Team, isMeteorError } from '@rocket.chat/core-services';
import type { IIntegration, IUser, IRoom, RoomType, UserStatus } from '@rocket.chat/core-typings';
import { Integrations, Messages, Rooms, Subscriptions, Uploads, Users } from '@rocket.chat/models';
import { isGroupsOnlineProps, isGroupsMessagesProps } from '@rocket.chat/rest-typings';
import { isGroupsOnlineProps, isGroupsMessagesProps, isGroupsFilesProps } from '@rocket.chat/rest-typings';
import { check, Match } from 'meteor/check';
import { Meteor } from 'meteor/meteor';
import type { Filter } from 'mongodb';
Expand Down Expand Up @@ -389,21 +389,28 @@ API.v1.addRoute(

API.v1.addRoute(
'groups.files',
{ authRequired: true },
{ authRequired: true, validateParams: isGroupsFilesProps },
{
async get() {
const { typeGroup, name, roomId, roomName } = this.queryParams;

const findResult = await findPrivateGroupByIdOrName({
params: this.queryParams,
params: roomId ? { roomId } : { roomName },
userId: this.userId,
checkedArchived: false,
});

const { offset, count } = await getPaginationItems(this.queryParams);
const { sort, fields, query } = await this.parseJsonQuery();

const ourQuery = Object.assign({}, query, { rid: findResult.rid });
const filter = {
...query,
rid: findResult.rid,
...(name ? { name: { $regex: name || '', $options: 'i' } } : {}),
...(typeGroup ? { typeGroup } : {}),
};

const { cursor, totalCount } = await Uploads.findPaginatedWithoutThumbs(ourQuery, {
const { cursor, totalCount } = await Uploads.findPaginatedWithoutThumbs(filter, {
sort: sort || { name: 1 },
skip: offset,
limit: count,
Expand Down
30 changes: 14 additions & 16 deletions apps/meteor/app/api/server/v1/im.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,12 @@ import { addUserToFileObj } from '../helpers/addUserToFileObj';
import { composeRoomWithLastMessage } from '../helpers/composeRoomWithLastMessage';
import { getPaginationItems } from '../helpers/getPaginationItems';

// TODO: Refact or remove

type findDirectMessageRoomProps =
| {
roomId: string;
}
| {
username: string;
};

const findDirectMessageRoom = async (
keys: findDirectMessageRoomProps,
keys: { roomId?: string; username?: string },
uid: string,
): Promise<{ room: IRoom; subscription: ISubscription | null }> => {
if (!('roomId' in keys) && !('username' in keys)) {
const nameOrId = 'roomId' in keys ? keys.roomId : keys.username;
if (typeof nameOrId !== 'string') {
throw new Meteor.Error('error-room-param-not-provided', 'Query param "roomId" or "username" is required');
}

Expand All @@ -58,7 +49,7 @@ const findDirectMessageRoom = async (

const room = await getRoomByNameOrIdWithOptionToJoin({
user,
nameOrId: 'roomId' in keys ? keys.roomId : keys.username,
nameOrId,
type: 'd',
});

Expand Down Expand Up @@ -234,19 +225,26 @@ API.v1.addRoute(
},
{
async get() {
const { typeGroup, name, roomId, username } = this.queryParams;

const { offset, count } = await getPaginationItems(this.queryParams);
const { sort, fields, query } = await this.parseJsonQuery();

const { room } = await findDirectMessageRoom(this.queryParams, this.userId);
const { room } = await findDirectMessageRoom(roomId ? { roomId } : { username }, this.userId);

const canAccess = await canAccessRoomIdAsync(room._id, this.userId);
if (!canAccess) {
return API.v1.forbidden();
}

const ourQuery = query ? { rid: room._id, ...query } : { rid: room._id };
const filter = {
...query,
rid: room._id,
...(name ? { name: { $regex: name || '', $options: 'i' } } : {}),
...(typeGroup ? { typeGroup } : {}),
};

const { cursor, totalCount } = Uploads.findPaginatedWithoutThumbs(ourQuery, {
const { cursor, totalCount } = Uploads.findPaginatedWithoutThumbs(filter, {
sort: sort || { name: 1 },
skip: offset,
limit: count,
Expand Down
1 change: 1 addition & 0 deletions apps/meteor/tests/data/interactions.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export const imgURL = './public/images/logo/1024x1024.png';
export const soundURL = './public/sounds/beep.mp3';
85 changes: 84 additions & 1 deletion apps/meteor/tests/data/uploads.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { after, before, it } from 'mocha';
import type { Response } from 'supertest';

import { api, request, credentials } from './api-data';
import { imgURL } from './interactions';
import { imgURL, soundURL } from './interactions';
import { createVisitor } from './livechat/rooms';
import { updateSetting } from './permissions.helper';
import { createRoom, deleteRoom } from './rooms.helper';
Expand Down Expand Up @@ -241,4 +241,87 @@ export async function testFileUploads(
});
});
});

it('should properly filter files by name or typeGroup', async () => {
const fileOneName = 'image-zyxwv.png';
const fileTwoName = 'sound-abcde.png';
const fileIdsToConfirm: string[] = [];

// Post 2 files, one image and one audio
await Promise.all([
request
.post(api(`rooms.media/${testRoom._id}`))
.set(credentials)
.attach('file', imgURL, { filename: fileOneName })
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(typeof res.body?.file?._id).to.equal('string');
fileIdsToConfirm.push(res.body.file._id);
}),
request
.post(api(`rooms.media/${testRoom._id}`))
.set(credentials)
.attach('file', soundURL, { filename: fileTwoName })
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(typeof res.body?.file?._id).to.equal('string');
fileIdsToConfirm.push(res.body.file._id);
}),
]);

// Confirm the files
await Promise.all(
fileIdsToConfirm.map((fileId) =>
request
.post(api(`rooms.mediaConfirm/${testRoom._id}/${fileId}`))
.set(credentials)
.expect('Content-Type', 'application/json')
.expect(200),
),
);

// test filtering by name
const nameFilterTest = request
.get(api(filesEndpoint))
.set(credentials)
.query({
roomId: testRoom._id,
name: fileOneName,
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('files').and.to.be.an('array').with.lengthOf(1);

const { files } = res.body;

expect(files[0].name).to.equal(fileOneName);
});

// test filtering by typeGroup
const typeGroupFilterTest = request
.get(api(filesEndpoint))
.set(credentials)
.query({
roomId: testRoom._id,
typeGroup: 'audio',
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('files').and.to.be.an('array').with.lengthOf(1);

const { files } = res.body;

expect(files[0].name).to.equal(fileTwoName);
});

await Promise.all([nameFilterTest, typeGroupFilterTest]);
});
}
95 changes: 41 additions & 54 deletions packages/rest-typings/src/v1/dm/DmFileProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,61 +5,48 @@ import type { PaginatedRequest } from '../../helpers/PaginatedRequest';
const ajv = new Ajv({ coerceTypes: true });

export type DmFileProps = PaginatedRequest<
(
| {
roomId: string;
}
| {
username: string;
}
) & { fields?: string }
({ roomId: string; username?: string } | { roomId?: string; username: string }) & { name?: string; typeGroup?: string; query?: string }
>;

export const isDmFileProps = ajv.compile<DmFileProps>({
oneOf: [
{
type: 'object',
properties: {
roomId: {
type: 'string',
},
query: {
type: 'string',
},
sort: {
type: 'string',
},
count: {
type: 'number',
},
offset: {
type: 'number',
},
},
required: ['roomId'],
additionalProperties: false,
const dmFilesListPropsSchema = {
type: 'object',
properties: {
roomId: {
type: 'string',
nullable: true,
},
{
type: 'object',
properties: {
username: {
type: 'string',
},
query: {
type: 'string',
},
sort: {
type: 'string',
},
count: {
type: 'number',
},
offset: {
type: 'number',
},
},
required: ['username'],
additionalProperties: false,
username: {
type: 'string',
nullable: true,
},
],
});
offset: {
type: 'number',
nullable: true,
},
count: {
type: 'number',
nullable: true,
},
sort: {
type: 'string',
nullable: true,
},
name: {
type: 'string',
nullable: true,
},
typeGroup: {
type: 'string',
nullable: true,
},
query: {
type: 'string',
nullable: true,
},
},
oneOf: [{ required: ['roomId'] }, { required: ['username'] }],
required: [],
additionalProperties: false,
};

export const isDmFileProps = ajv.compile<DmFileProps>(dmFilesListPropsSchema);
2 changes: 1 addition & 1 deletion packages/rest-typings/src/v1/groups/BaseProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const ajv = new Ajv({
coerceTypes: true,
});

export type GroupsBaseProps = { roomId: string } | { roomName: string };
export type GroupsBaseProps = { roomId: string; roomName?: string } | { roomId?: string; roomName: string };

export const withGroupBaseProperties = (properties: Record<string, any> = {}, required: string[] = []) => ({
oneOf: [
Expand Down
Loading
Loading