From 532f08819efbcb18d42817ea1e839062130c9cc7 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Thu, 5 Sep 2024 14:04:14 -0300 Subject: [PATCH] feat: add references to room.info endpoint (#33011) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: JĂșlia Jaeger Foresti <60678893+juliajforesti@users.noreply.github.com> Co-authored-by: Kevin Aleman <11577696+KevLehman@users.noreply.github.com> --- .changeset/quiet-cherries-punch.md | 7 + apps/meteor/app/api/server/v1/rooms.ts | 16 +- apps/meteor/server/services/team/service.ts | 25 +++ apps/meteor/tests/end-to-end/api/rooms.ts | 158 +++++++++++++++++- .../core-services/src/types/ITeamService.ts | 4 + packages/rest-typings/src/v1/rooms.ts | 4 +- 6 files changed, 210 insertions(+), 4 deletions(-) create mode 100644 .changeset/quiet-cherries-punch.md diff --git a/.changeset/quiet-cherries-punch.md b/.changeset/quiet-cherries-punch.md new file mode 100644 index 000000000000..25c08506db41 --- /dev/null +++ b/.changeset/quiet-cherries-punch.md @@ -0,0 +1,7 @@ +--- +"@rocket.chat/meteor": minor +"@rocket.chat/core-services": minor +"@rocket.chat/rest-typings": minor +--- + +Return `parent` and `team` information when calling `rooms.info` endpoint diff --git a/apps/meteor/app/api/server/v1/rooms.ts b/apps/meteor/app/api/server/v1/rooms.ts index a0ed75ceea30..5da59d977fb1 100644 --- a/apps/meteor/app/api/server/v1/rooms.ts +++ b/apps/meteor/app/api/server/v1/rooms.ts @@ -1,4 +1,4 @@ -import { Media } from '@rocket.chat/core-services'; +import { Media, Team } from '@rocket.chat/core-services'; import type { IRoom, IUpload } from '@rocket.chat/core-typings'; import { Messages, Rooms, Users, Uploads, Subscriptions } from '@rocket.chat/models'; import type { Notifications } from '@rocket.chat/rest-typings'; @@ -417,7 +417,19 @@ API.v1.addRoute( return API.v1.failure('not-allowed', 'Not Allowed'); } - return API.v1.success({ room: (await Rooms.findOneByIdOrName(room._id, { projection: fields })) ?? undefined }); + const discussionParent = + room.prid && + (await Rooms.findOneById>(room.prid, { + projection: { name: 1, fname: 1, t: 1, prid: 1, u: 1 }, + })); + const { team, parentRoom } = await Team.getRoomInfo(room); + const parent = discussionParent || parentRoom; + + return API.v1.success({ + room: (await Rooms.findOneByIdOrName(room._id, { projection: fields })) ?? undefined, + ...(team && { team }), + ...(parent && { parent }), + }); }, }, ); diff --git a/apps/meteor/server/services/team/service.ts b/apps/meteor/server/services/team/service.ts index 190464f48da4..27f7af1f1b1c 100644 --- a/apps/meteor/server/services/team/service.ts +++ b/apps/meteor/server/services/team/service.ts @@ -20,6 +20,7 @@ import type { ITeam, ITeamMember, ITeamStats, + AtLeast, } from '@rocket.chat/core-typings'; import type { InsertionModel } from '@rocket.chat/model-typings'; import { Team, Rooms, Subscriptions, Users, TeamMember } from '@rocket.chat/models'; @@ -1053,4 +1054,28 @@ export class TeamService extends ServiceClassInternal implements ITeamService { return rooms; } + + private getParentRoom(team: AtLeast): Promise | null> { + return Rooms.findOneById>(team.roomId, { projection: { name: 1, fname: 1, t: 1 } }); + } + + async getRoomInfo( + room: AtLeast, + ): Promise<{ team?: Pick; parentRoom?: Pick }> { + if (!room.teamId) { + return {}; + } + + const team = await Team.findOneById(room.teamId, { projection: { _id: 1, name: 1, roomId: 1, type: 1 } }); + if (!team) { + return {}; + } + + if (room.teamMain) { + return { team }; + } + + const parentRoom = await this.getParentRoom(team); + return { team, ...(parentRoom && { parentRoom }) }; + } } diff --git a/apps/meteor/tests/end-to-end/api/rooms.ts b/apps/meteor/tests/end-to-end/api/rooms.ts index bb4cbb8fb196..15f85964ffff 100644 --- a/apps/meteor/tests/end-to-end/api/rooms.ts +++ b/apps/meteor/tests/end-to-end/api/rooms.ts @@ -1264,8 +1264,164 @@ describe('[Rooms]', () => { }) .end(done); }); - }); + it('should not return parent & team for room thats not on a team nor is a discussion', async () => { + await request + .get(api('rooms.info')) + .set(credentials) + .query({ + roomId: testChannel._id, + }) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('room').and.to.be.an('object'); + expect(res.body.room).to.not.have.property('team'); + expect(res.body.room).to.not.have.property('prid'); + }); + }); + + describe('with team and parent data', () => { + const testChannelName = `channel.test.${Date.now()}-${Math.random()}`; + const teamName = `test-team-${Date.now()}`; + const discussionName = `test-discussion-${Date.now()}`; + const testChannelOutsideTeamname = `channel.test.outside.${Date.now()}-${Math.random()}`; + let testChannel: IRoom; + let testDiscussion: IRoom; + let testDiscussionMainRoom: IRoom; + let testTeam: ITeam; + let testChannelOutside: IRoom; + let testDiscussionOutsideTeam: IRoom; + + before(async () => { + testChannel = (await createRoom({ type: 'c', name: testChannelName })).body.channel; + + const teamResponse = await request.post(api('teams.create')).set(credentials).send({ name: teamName, type: 1 }).expect(200); + testTeam = teamResponse.body.team; + + const resDiscussion = await request.post(api('rooms.createDiscussion')).set(credentials).send({ + prid: testChannel._id, + t_name: discussionName, + }); + testDiscussion = resDiscussion.body.discussion; + + testDiscussionMainRoom = ( + await request + .post(api('rooms.createDiscussion')) + .set(credentials) + .send({ + prid: testTeam.roomId, + t_name: `test-discussion-${Date.now()}-team`, + }) + ).body.discussion; + + await request + .post(api('teams.addRooms')) + .set(credentials) + .send({ rooms: [testChannel._id], teamId: testTeam._id }); + }); + + before(async () => { + testChannelOutside = (await createRoom({ type: 'c', name: testChannelOutsideTeamname })).body.channel; + testDiscussionOutsideTeam = ( + await request + .post(api('rooms.createDiscussion')) + .set(credentials) + .send({ + prid: testChannelOutside._id, + t_name: `test-discussion-${Date.now()}`, + }) + ).body.discussion; + }); + + after(() => + Promise.all([ + deleteRoom({ type: 'c', roomId: testChannel._id }), + deleteRoom({ type: 'p', roomId: testDiscussion._id }), + deleteRoom({ type: 'c', roomId: testChannelOutside._id }), + deleteRoom({ type: 'p', roomId: testDiscussionOutsideTeam._id }), + deleteRoom({ type: 'p', roomId: testDiscussionMainRoom._id }), + deleteTeam(credentials, teamName), + ]), + ); + + it('should return the channel info, team and parent info', async () => { + const result = await request.get(api('rooms.info')).set(credentials).query({ roomId: testChannel._id }).expect(200); + + expect(result.body).to.have.property('success', true); + expect(result.body).to.have.property('team'); + expect(result.body).to.have.property('parent'); + expect(result.body.parent).to.have.property('_id').and.to.equal(testTeam.roomId); + }); + + it('should return the dicsussion room info and parent info', async () => { + await request + .get(api('rooms.info')) + .set(credentials) + .query({ roomId: testDiscussion._id }) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('parent').and.to.be.an('object'); + expect(res.body.parent).to.have.property('_id').and.to.be.equal(testChannel._id); + }); + }); + + it('should not return parent info for the main room of the team', async () => { + await request + .get(api('rooms.info')) + .set(credentials) + .query({ roomId: testTeam.roomId }) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.not.have.property('parent'); + expect(res.body).to.have.property('team'); + }); + }); + + it('should not return team for room outside team', async () => { + await request + .get(api('rooms.info')) + .set(credentials) + .query({ roomId: testChannelOutside._id }) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.not.have.property('team'); + expect(res.body).to.not.have.property('parent'); + }); + }); + + it('should return the parent for discussion outside team', async () => { + await request + .get(api('rooms.info')) + .set(credentials) + .query({ roomId: testDiscussionOutsideTeam._id }) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('parent').and.to.be.an('object'); + expect(res.body.parent).to.have.property('_id').and.to.be.equal(testChannelOutside._id); + expect(res.body).to.not.have.property('team'); + }); + }); + + it('should return the parent for a discussion created from team main room', async () => { + await request + .get(api('rooms.info')) + .set(credentials) + .query({ roomId: testDiscussionMainRoom._id }) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('parent').and.to.be.an('object'); + expect(res.body.parent).to.have.property('_id').and.to.be.equal(testTeam.roomId); + expect(res.body).to.not.have.property('team'); + }); + }); + }); + }); describe('[/rooms.leave]', () => { let testChannel: IRoom; let testGroup: IRoom; diff --git a/packages/core-services/src/types/ITeamService.ts b/packages/core-services/src/types/ITeamService.ts index 2d67bf515fca..3caa6a2e97df 100644 --- a/packages/core-services/src/types/ITeamService.ts +++ b/packages/core-services/src/types/ITeamService.ts @@ -9,6 +9,7 @@ import type { IRoom, IUser, IRole, + AtLeast, } from '@rocket.chat/core-typings'; import type { Document, Filter, FindOptions } from 'mongodb'; @@ -125,4 +126,7 @@ export interface ITeamService { getStatistics(): Promise; findBySubscribedUserIds(userId: string, callerId?: string): Promise; addRolesToMember(teamId: string, userId: string, roles: Array): Promise; + getRoomInfo( + room: AtLeast, + ): Promise<{ team?: Pick; parentRoom?: Pick }>; } diff --git a/packages/rest-typings/src/v1/rooms.ts b/packages/rest-typings/src/v1/rooms.ts index 2172b5663afc..c837ba7186bd 100644 --- a/packages/rest-typings/src/v1/rooms.ts +++ b/packages/rest-typings/src/v1/rooms.ts @@ -1,4 +1,4 @@ -import type { IMessage, IRoom, IUser, RoomAdminFieldsType, IUpload, IE2EEMessage } from '@rocket.chat/core-typings'; +import type { IMessage, IRoom, IUser, RoomAdminFieldsType, IUpload, IE2EEMessage, ITeam } from '@rocket.chat/core-typings'; import type { PaginatedRequest } from '../helpers/PaginatedRequest'; import type { PaginatedResult } from '../helpers/PaginatedResult'; @@ -626,6 +626,8 @@ export type RoomsEndpoints = { '/v1/rooms.info': { GET: (params: RoomsInfoProps) => { room: IRoom | undefined; + parent?: Pick; + team?: Pick; }; };