Skip to content

Commit

Permalink
Merge pull request #21099 from RocketChat/new/teams-back-add-members
Browse files Browse the repository at this point in the history
[NEW] Add Team Members' endpoints
  • Loading branch information
pierre-lehnen-rc authored Mar 16, 2021
2 parents ec4869d + b89d0f4 commit 806c3f0
Show file tree
Hide file tree
Showing 5 changed files with 354 additions and 22 deletions.
71 changes: 65 additions & 6 deletions app/api/server/v1/teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Promise } from 'meteor/promise';

import { API } from '../api';
import { Team } from '../../../../server/sdk';
import { hasPermission } from '../../../authorization/server';
import { hasAtLeastOnePermission, hasPermission } from '../../../authorization/server';

API.v1.addRoute('teams.list', { authRequired: true }, {
get() {
Expand Down Expand Up @@ -40,6 +40,9 @@ API.v1.addRoute('teams.listAll', { authRequired: true }, {

API.v1.addRoute('teams.create', { authRequired: true }, {
post() {
if (!hasPermission(this.userId, 'create-team')) {
return API.v1.unauthorized();
}
const { name, type, members, room, owner } = this.bodyParams;

if (!name) {
Expand All @@ -62,15 +65,71 @@ API.v1.addRoute('teams.create', { authRequired: true }, {

API.v1.addRoute('teams.members', { authRequired: true }, {
get() {
const { teamId } = this.queryParams;
const { offset, count } = this.getPaginationItems();
const { teamId, teamName } = this.queryParams;

const { records, total } = Promise.await(Team.members(teamId, teamName, { offset, count }));

return API.v1.success({
members: records,
total,
count: records.length,
offset,
});
},
});

API.v1.addRoute('teams.addMembers', { authRequired: true }, {
post() {
if (!hasAtLeastOnePermission(this.userId, ['add-team-member', 'edit-team-member'])) {
return API.v1.unauthorized();
}

const { teamId, teamName, members } = this.bodyParams;

Promise.await(Team.addMembers(this.userId, teamId, teamName, members));

return API.v1.success();
},
});

API.v1.addRoute('teams.updateMember', { authRequired: true }, {
post() {
if (!hasAtLeastOnePermission(this.userId, ['edit-team-member'])) {
return API.v1.unauthorized();
}

const { teamId, teamName, member } = this.bodyParams;

Promise.await(Team.updateMember(teamId, teamName, member));

return API.v1.success();
},
});

if (!teamId) {
return API.v1.failure('Team ID is required');
API.v1.addRoute('teams.removeMembers', { authRequired: true }, {
post() {
if (!hasAtLeastOnePermission(this.userId, ['edit-team-member'])) {
return API.v1.unauthorized();
}

const members = Promise.await(Team.members(this.userId, teamId));
const { teamId, teamName, members } = this.bodyParams;

Promise.await(Team.removeMembers(teamId, teamName, members));

return API.v1.success();
},
});

API.v1.addRoute('teams.leave', { authRequired: true }, {
post() {
const { teamId, teamName } = this.bodyParams;

Promise.await(Team.removeMembers(teamId, teamName, [{
userId: this.userId,
}]));

return API.v1.success({ members });
return API.v1.success();
},
});

Expand Down
4 changes: 4 additions & 0 deletions app/models/server/raw/TeamMember.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ export class TeamMemberRaw extends BaseRaw<T> {
return this.col.find({ teamId }, options);
}

updateOneByUserIdAndTeamId(userId: string, teamId: string, update: Partial<T>): Promise<UpdateWriteOpResult> {
return this.col.updateOne({ userId, teamId }, { $set: update });
}

createOneByTeamIdAndUserId(teamId: string, userId: string, createdBy: Pick<IUser, '_id' | 'username'>): Promise<InsertOneWriteOpResult<T>> {
return this.insertOne({
teamId,
Expand Down
11 changes: 10 additions & 1 deletion server/sdk/types/ITeamService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,20 @@ export interface ITeamCreateParams {
owner?: string; // the team owner. If not present, owner = requester
}

export interface ITeamMemberParams {
userId?: string;
userName?: string;
roles?: Array<string>;
}

export interface ITeamService {
create(uid: string, params: ITeamCreateParams): Promise<ITeam>;
list(uid: string, options?: IPaginationOptions): Promise<IRecordsWithTotal<ITeam>>;
listAll(options?: IPaginationOptions): Promise<IRecordsWithTotal<ITeam>>;
members(uid: string, teamId: string): Promise<Array<ITeamMember>>;
members(teamId: string, teamName: string, options?: IPaginationOptions): Promise<IRecordsWithTotal<ITeamMember>>;
addMembers(uid: string, teamId: string, teamName: string, members: Array<ITeamMemberParams>): Promise<void>;
updateMember(teamId: string, teamName: string, members: ITeamMemberParams): Promise<void>;
removeMembers(teamId: string, teamName: string, members: Array<ITeamMemberParams>): Promise<void>;
getInfoByName(teamName: string): Promise<Partial<ITeam> | undefined>;
getInfoById(teamId: string): Promise<Partial<ITeam> | undefined>;
}
111 changes: 96 additions & 15 deletions server/services/team/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { Db } from 'mongodb';

import { TeamRaw } from '../../../app/models/server/raw/Team';
import { ITeam, ITeamMember, TEAM_TYPE, IRecordsWithTotal, IPaginationOptions } from '../../../definition/ITeam';
import { Authorization, Room } from '../../sdk';
import { ITeamCreateParams, ITeamService } from '../../sdk/types/ITeamService';
import { Room } from '../../sdk';
import { ITeamCreateParams, ITeamMemberParams, ITeamService } from '../../sdk/types/ITeamService';
import { IUser } from '../../../definition/IUser';
import { ServiceClass } from '../../sdk/types/ServiceClass';
import { UsersRaw } from '../../../app/models/server/raw/Users';
Expand Down Expand Up @@ -33,11 +33,6 @@ export class TeamService extends ServiceClass implements ITeamService {
}

async create(uid: string, { team, room = { name: team.name, extraData: {} }, members, owner }: ITeamCreateParams): Promise<ITeam> {
const hasPermission = await Authorization.hasPermission(uid, 'create-team');
if (!hasPermission) {
throw new Error('no-permission');
}

const existingTeam = await this.TeamModel.findOneByName(team.name, { projection: { _id: 1 } });
if (existingTeam) {
throw new Error('team-name-already-exists');
Expand Down Expand Up @@ -155,18 +150,104 @@ export class TeamService extends ServiceClass implements ITeamService {
};
}

async members(userId: string, teamId: string): Promise<Array<ITeamMember>> {
const isMember = await this.TeamMembersModel.findOneByUserIdAndTeamId(userId, teamId);
const hasPermission = await Authorization.hasAtLeastOnePermission(userId, ['add-team-member', 'edit-team-member', 'view-all-teams']);
if (!hasPermission) {
throw new Error('no-permission');
async members(teamId: string, teamName: string, { offset, count }: IPaginationOptions = { offset: 0, count: 50 }): Promise<IRecordsWithTotal<ITeamMember>> {
if (!teamId) {
const teamIdName = await this.TeamModel.findOneByName(teamName, { projection: { _id: 1 } });
if (!teamIdName) {
throw new Error('team-does-not-exist');
}

teamId = teamIdName._id;
}

const cursor = this.TeamMembersModel.findByTeamId(teamId, {
limit: count,
skip: offset,
});

return {
total: await cursor.count(),
records: await cursor.toArray(),
};
}

async addMembers(uid: string, teamId: string, teamName: string, members: Array<ITeamMemberParams>): Promise<void> {
const createdBy = await this.Users.findOneById(uid, { projection: { username: 1 } });
if (!createdBy) {
throw new Error('invalid-user');
}

if (!teamId) {
const teamIdName = await this.TeamModel.findOneByName(teamName, { projection: { _id: 1 } });
if (!teamIdName) {
throw new Error('team-does-not-exist');
}

teamId = teamIdName._id;
}

const membersList: Array<Omit<ITeamMember, '_id'>> = members?.map((member) => ({
teamId,
userId: member.userId ? member.userId : '',
roles: member.roles ? member.roles : [],
createdAt: new Date(),
createdBy,
_updatedAt: new Date(), // TODO how to avoid having to do this?
})) || [];

await this.TeamMembersModel.insertMany(membersList);
}

async updateMember(teamId: string, teamName: string, member: ITeamMemberParams): Promise<void> {
if (!teamId) {
const teamIdName = await this.TeamModel.findOneByName(teamName, { projection: { _id: 1 } });
if (!teamIdName) {
throw new Error('team-does-not-exist');
}

teamId = teamIdName._id;
}

if (!isMember && !hasPermission) {
return [];
if (!member.userId) {
member.userId = await this.Users.findOneByUsername(member.userName);
if (!member.userId) {
throw new Error('invalid-user');
}
}

return this.TeamMembersModel.findByTeamId(teamId).toArray();
const memberUpdate: Partial<ITeamMember> = {
roles: member.roles ? member.roles : [],
_updatedAt: new Date(),
};

await this.TeamMembersModel.updateOneByUserIdAndTeamId(member.userId, teamId, memberUpdate);
}

async removeMembers(teamId: string, teamName: string, members: Array<ITeamMemberParams>): Promise<void> {
if (!teamId) {
const teamIdName = await this.TeamModel.findOneByName(teamName, { projection: { _id: 1 } });
if (!teamIdName) {
throw new Error('team-does-not-exist');
}

teamId = teamIdName._id;
}

for await (const member of members) {
if (!member.userId) {
member.userId = await this.Users.findOneByUsername(member.userName);
if (!member.userId) {
throw new Error('invalid-user');
}
}

const existingMember = await this.TeamMembersModel.findOneByUserIdAndTeamId(member.userId, teamId);
if (!existingMember) {
throw new Error('member-does-not-exist');
}

this.TeamMembersModel.removeById(existingMember._id);
}
}

async addMember({ _id, username }: IUser, userId: string, teamId: string): Promise<boolean | ITeamMember> {
Expand Down
Loading

0 comments on commit 806c3f0

Please sign in to comment.