Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: New endpoint for listing rooms & discussions from teams #33177

Merged
merged 29 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
6609b8a
new endpoint for listing rooms and discussions for a team
KevLehman Aug 28, 2024
ff0775d
dum
KevLehman Aug 28, 2024
c325f97
eslint could you please give me all errors at once
KevLehman Aug 28, 2024
7081a74
Delete apps/meteor/eslint.txt
KevLehman Aug 28, 2024
6761d8d
Delete apps/meteor/eslintfiles.txt
KevLehman Aug 28, 2024
8f9f5b8
cr
KevLehman Aug 28, 2024
1202a2f
eslint
KevLehman Aug 28, 2024
fb38195
Create soft-mirrors-remember.md
KevLehman Aug 28, 2024
00833ea
cr
KevLehman Aug 29, 2024
43d2534
change 3 queries to use an aggregation
KevLehman Sep 2, 2024
6cbf0c9
thanks notifylistener
KevLehman Sep 2, 2024
fdc8eec
rename endpoint
KevLehman Sep 2, 2024
868d7cb
Update .changeset/soft-mirrors-remember.md
KevLehman Sep 2, 2024
bb280f2
lint
KevLehman Sep 2, 2024
13ffd24
filter by team main room id
KevLehman Sep 2, 2024
3eb78c3
im dum
KevLehman Sep 2, 2024
456b8e3
Update teams.ts
KevLehman Sep 2, 2024
6020c6a
add capability for filtering by type of channel or return both
KevLehman Sep 3, 2024
e7c2c01
lint
KevLehman Sep 3, 2024
3ab5bc2
plural
KevLehman Sep 3, 2024
e90194a
cr
KevLehman Sep 3, 2024
213c577
lint
KevLehman Sep 3, 2024
9c049d6
block non member usage
KevLehman Sep 4, 2024
53aa8f0
Update apps/meteor/tests/end-to-end/api/teams.ts
KevLehman Sep 4, 2024
70aeebd
Update apps/meteor/server/models/raw/Rooms.ts
KevLehman Sep 4, 2024
b088144
cr
KevLehman Sep 4, 2024
3fac30c
eslint
KevLehman Sep 4, 2024
0eb3cb4
Merge branch 'develop' into feat/teams-and-discs
kodiakhq[bot] Sep 16, 2024
fdfd5ca
Merge branch 'develop' into feat/teams-and-discs
KevLehman Sep 17, 2024
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
8 changes: 8 additions & 0 deletions .changeset/soft-mirrors-remember.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@rocket.chat/meteor": minor
"@rocket.chat/core-services": minor
"@rocket.chat/model-typings": minor
"@rocket.chat/rest-typings": minor
---

New `teams.listChildren` endpoint that allows users listing rooms & discussions from teams. Only the discussions from the team's main room are returned.
39 changes: 39 additions & 0 deletions apps/meteor/app/api/server/v1/teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
isTeamsDeleteProps,
isTeamsLeaveProps,
isTeamsUpdateProps,
isTeamsListChildrenProps,
} from '@rocket.chat/rest-typings';
import { escapeRegExp } from '@rocket.chat/string-helpers';
import { Match, check } from 'meteor/check';
Expand Down Expand Up @@ -375,6 +376,44 @@ API.v1.addRoute(
},
);

const getTeamByIdOrNameOrParentRoom = async (
params: { teamId: string } | { teamName: string } | { roomId: string },
): Promise<Pick<ITeam, 'type' | 'roomId' | '_id'> | null> => {
if ('teamId' in params && params.teamId) {
KevLehman marked this conversation as resolved.
Show resolved Hide resolved
return Team.getOneById<ITeam>(params.teamId, { projection: { type: 1, roomId: 1 } });
}
if ('teamName' in params && params.teamName) {
return Team.getOneByName(params.teamName, { projection: { type: 1, roomId: 1 } });
}
KevLehman marked this conversation as resolved.
Show resolved Hide resolved
if ('roomId' in params && params.roomId) {
return Team.getOneByRoomId(params.roomId, { projection: { type: 1, roomId: 1 } });
}
return null;
};

// This should accept a teamId, filter (search by name on rooms collection) and sort/pagination
// should return a list of rooms/discussions from the team. the discussions will only be returned from the main room
API.v1.addRoute(
'teams.listChildren',
{ authRequired: true, validateParams: isTeamsListChildrenProps },
{
async get() {
const { offset, count } = await getPaginationItems(this.queryParams);
const { sort } = await this.parseJsonQuery();
const { filter, type } = this.queryParams;

const team = await getTeamByIdOrNameOrParentRoom(this.queryParams);
if (!team) {
return API.v1.notFound();
}

const data = await Team.listChildren(this.userId, team, filter, type, sort, offset, count);

return API.v1.success({ ...data, offset, count });
},
},
);

API.v1.addRoute(
'teams.members',
{ authRequired: true },
Expand Down
81 changes: 81 additions & 0 deletions apps/meteor/server/models/raw/Rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2063,4 +2063,85 @@ export class RoomsRaw extends BaseRaw<IRoom> implements IRoomsModel {

return this.updateMany(query, update);
}

findChildrenOfTeam(
teamId: string,
teamRoomId: string,
userId: string,
filter?: string,
type?: 'channels' | 'discussions',
options?: FindOptions<IRoom>,
): AggregationCursor<{ totalCount: { count: number }[]; paginatedResults: IRoom[] }> {
const nameFilter = filter ? new RegExp(escapeRegExp(filter), 'i') : undefined;
return this.col.aggregate<{ totalCount: { count: number }[]; paginatedResults: IRoom[] }>([
{
$match: {
$and: [
{
$or: [
...(!type || type === 'channels' ? [{ teamId }] : []),
...(!type || type === 'discussions' ? [{ prid: teamRoomId }] : []),
],
},
...(nameFilter ? [{ $or: [{ fname: nameFilter }, { name: nameFilter }] }] : []),
],
},
},
{
$lookup: {
from: 'rocketchat_subscription',
let: {
roomId: '$_id',
},
pipeline: [
{
$match: {
$and: [
{
KevLehman marked this conversation as resolved.
Show resolved Hide resolved
$expr: {
$eq: ['$rid', '$$roomId'],
},
},
{
$expr: {
$eq: ['$u._id', userId],
},
},
{
$expr: {
$ne: ['$t', 'c'],
},
},
],
},
},
{
$project: { _id: 1 },
},
],
as: 'subscription',
},
},
{
$match: {
$or: [
{ t: 'c' },
{
$expr: {
$ne: [{ $size: '$subscription' }, 0],
},
},
],
},
},
{ $project: { subscription: 0 } },
{ $sort: options?.sort || { ts: 1 } },
{
$facet: {
totalCount: [{ $count: 'count' }],
paginatedResults: [{ $skip: options?.skip || 0 }, { $limit: options?.limit || 50 }],
},
},
]);
}
}
39 changes: 36 additions & 3 deletions apps/meteor/server/services/team/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -913,8 +913,8 @@ export class TeamService extends ServiceClassInternal implements ITeamService {
});
}

async getOneByRoomId(roomId: string): Promise<ITeam | null> {
const room = await Rooms.findOneById(roomId);
async getOneByRoomId(roomId: string, options?: FindOptions<ITeam>): Promise<ITeam | null> {
const room = await Rooms.findOneById(roomId, { projection: { teamId: 1 } });

if (!room) {
throw new Error('invalid-room');
Expand All @@ -924,7 +924,7 @@ export class TeamService extends ServiceClassInternal implements ITeamService {
throw new Error('room-not-on-team');
}

return Team.findOneById(room.teamId);
return Team.findOneById(room.teamId, options);
}

async addRolesToMember(teamId: string, userId: string, roles: Array<string>): Promise<boolean> {
Expand Down Expand Up @@ -1078,4 +1078,37 @@ export class TeamService extends ServiceClassInternal implements ITeamService {
const parentRoom = await this.getParentRoom(team);
return { team, ...(parentRoom && { parentRoom }) };
}

// Returns the list of rooms and discussions a user has access to inside a team
// Rooms returned are a composition of the rooms the user is in + public rooms + discussions from the main room (if any)
async listChildren(
userId: string,
team: AtLeast<ITeam, '_id' | 'roomId' | 'type'>,
filter?: string,
type?: 'channels' | 'discussions',
sort?: Record<string, 1 | -1>,
skip = 0,
limit = 10,
): Promise<{ total: number; data: IRoom[] }> {
const mainRoom = await Rooms.findOneById(team.roomId, { projection: { _id: 1 } });
if (!mainRoom) {
throw new Error('error-invalid-team-no-main-room');
}

const isMember = await TeamMember.findOneByUserIdAndTeamId(userId, team._id, {
projection: { _id: 1 },
});

if (!isMember) {
throw new Error('error-invalid-team-not-a-member');
}
MarcosSpessatto marked this conversation as resolved.
Show resolved Hide resolved

const [{ totalCount: [{ count: total }] = [], paginatedResults: data = [] }] =
(await Rooms.findChildrenOfTeam(team._id, mainRoom._id, userId, filter, type, { skip, limit, sort }).toArray()) || [];

return {
total,
data,
};
}
}
19 changes: 14 additions & 5 deletions apps/meteor/tests/data/teams.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,20 @@ import type { ITeam, TEAM_TYPE } from '@rocket.chat/core-typings';

import { api, request } from './api-data';

export const createTeam = async (credentials: Record<string, any>, teamName: string, type: TEAM_TYPE): Promise<ITeam> => {
const response = await request.post(api('teams.create')).set(credentials).send({
name: teamName,
type,
});
export const createTeam = async (
credentials: Record<string, any>,
teamName: string,
type: TEAM_TYPE,
members?: string[],
): Promise<ITeam> => {
const response = await request
.post(api('teams.create'))
.set(credentials)
.send({
name: teamName,
type,
...(members && { members }),
});

return response.body.team;
};
Expand Down
Loading
Loading