apps/meteor/app/api/server/v1/groups.ts
import { Team, isMeteorError } from '@rocket.chat/core-services';
import type { IIntegration, IUser, IRoom, RoomType } from '@rocket.chat/core-typings';
import { Integrations, Messages, Rooms, Subscriptions, Uploads, Users } from '@rocket.chat/models';
import { check, Match } from 'meteor/check';
import { Meteor } from 'meteor/meteor';
import type { Filter } from 'mongodb';
import { findUsersOfRoom } from '../../../../server/lib/findUsersOfRoom';
import { hideRoomMethod } from '../../../../server/methods/hideRoom';
import { removeUserFromRoomMethod } from '../../../../server/methods/removeUserFromRoom';
import { canAccessRoomAsync, roomAccessAttributes } from '../../../authorization/server';
import {
hasAllPermissionAsync,
hasAtLeastOnePermissionAsync,
hasPermissionAsync,
} from '../../../authorization/server/functions/hasPermission';
import { saveRoomSettings } from '../../../channel-settings/server/methods/saveRoomSettings';
import { mountIntegrationQueryBasedOnPermissions } from '../../../integrations/server/lib/mountQueriesBasedOnPermission';
import { createPrivateGroupMethod } from '../../../lib/server/methods/createPrivateGroup';
import { leaveRoomMethod } from '../../../lib/server/methods/leaveRoom';
import { normalizeMessagesForUser } from '../../../utils/server/lib/normalizeMessagesForUser';
import { API } from '../api';
import { addUserToFileObj } from '../helpers/addUserToFileObj';
import { composeRoomWithLastMessage } from '../helpers/composeRoomWithLastMessage';
import { getLoggedInUser } from '../helpers/getLoggedInUser';
import { getPaginationItems } from '../helpers/getPaginationItems';
import { getUserFromParams, getUserListFromParams } from '../helpers/getUserFromParams';
async function getRoomFromParams(params: { roomId?: string } | { roomName?: string }): Promise<IRoom> {
if (
(!('roomId' in params) && !('roomName' in params)) ||
('roomId' in params && !(params as { roomId?: string }).roomId && 'roomName' in params && !(params as { roomName?: string }).roomName)
) {
throw new Meteor.Error('error-room-param-not-provided', 'The parameter "roomId" or "roomName" is required');
}
const roomOptions = {
projection: {
...roomAccessAttributes,
t: 1,
ro: 1,
name: 1,
fname: 1,
prid: 1,
archived: 1,
broadcast: 1,
},
};
const room = await (() => {
if ('roomId' in params) {
return Rooms.findOneById(params.roomId || '', roomOptions);
}
if ('roomName' in params) {
return Rooms.findOneByName(params.roomName || '', roomOptions);
}
})();
if (!room || room.t !== 'p') {
throw new Meteor.Error('error-room-not-found', 'The required "roomId" or "roomName" param provided does not match any group');
}
return room;
}
// Returns the private group subscription IF found otherwise it will return the failure of why it didn't. Check the `statusCode` property
async function findPrivateGroupByIdOrName({
params,
checkedArchived = true,
userId,
}: {
params:
| {
roomId?: string;
}
| {
roomName?: string;
};
userId: string;
checkedArchived?: boolean;
}): Promise<{
rid: string;
open: boolean;
ro: boolean;
t: string;
name: string;
broadcast: boolean;
}> {
const room = await getRoomFromParams(params);
const user = await Users.findOneById(userId, { projections: { username: 1 } });
if (!room || !user || !(await canAccessRoomAsync(room, user))) {
throw new Meteor.Error('error-room-not-found', 'The required "roomId" or "roomName" param provided does not match any group');
}
// discussions have their names saved on `fname` property
const roomName = room.prid ? room.fname : room.name;
if (checkedArchived && room.archived) {
throw new Meteor.Error('error-room-archived', `The private group, ${roomName}, is archived`);
}
const sub = await Subscriptions.findOneByRoomIdAndUserId(room._id, userId, { projection: { open: 1 } });
return {
rid: room._id,
open: Boolean(sub?.open),
ro: Boolean(room.ro),
t: room.t,
name: roomName ?? '',
broadcast: Boolean(room.broadcast),
};
}
API.v1.addRoute(
'groups.addAll',
{ authRequired: true },
{
async post() {
const { activeUsersOnly, ...params } = this.bodyParams;
const findResult = await findPrivateGroupByIdOrName({
params,
userId: this.userId,
});
await Meteor.callAsync('addAllUserToRoom', findResult.rid, this.bodyParams.activeUsersOnly);
const room = await Rooms.findOneById(findResult.rid, { projection: API.v1.defaultFieldsToExclude });
if (!room) {
throw new Meteor.Error('error-room-not-found', 'The required "roomId" or "roomName" param provided does not match any group');
}
return API.v1.success({
group: await composeRoomWithLastMessage(room, this.userId),
});
},
},
);
API.v1.addRoute(
'groups.addModerator',
{ authRequired: true },
{
async post() {
const findResult = await findPrivateGroupByIdOrName({
params: this.bodyParams,
userId: this.userId,
});
const user = await getUserFromParams(this.bodyParams);
await Meteor.callAsync('addRoomModerator', findResult.rid, user._id);
return API.v1.success();
},
},
);
API.v1.addRoute(
'groups.addOwner',
{ authRequired: true },
{
async post() {
const findResult = await findPrivateGroupByIdOrName({
params: this.bodyParams,
userId: this.userId,
});
const user = await getUserFromParams(this.bodyParams);
await Meteor.callAsync('addRoomOwner', findResult.rid, user._id);
return API.v1.success();
},
},
);
API.v1.addRoute(
'groups.addLeader',
{ authRequired: true },
{
async post() {
const findResult = await findPrivateGroupByIdOrName({
params: this.bodyParams,
userId: this.userId,
});
const user = await getUserFromParams(this.bodyParams);
await Meteor.callAsync('addRoomLeader', findResult.rid, user._id);
return API.v1.success();
},
},
);
// Archives a private group only if it wasn't
API.v1.addRoute(
'groups.archive',
{ authRequired: true },
{
async post() {
const findResult = await findPrivateGroupByIdOrName({
params: this.bodyParams,
userId: this.userId,
});
await Meteor.callAsync('archiveRoom', findResult.rid);
return API.v1.success();
},
},
);
API.v1.addRoute(
'groups.close',
{ authRequired: true },
{
async post() {
const findResult = await findPrivateGroupByIdOrName({
params: this.bodyParams,
userId: this.userId,
checkedArchived: false,
});
if (!findResult.open) {
return API.v1.failure(`The private group, ${findResult.name}, is already closed to the sender`);
}
await hideRoomMethod(this.userId, findResult.rid);
return API.v1.success();
},
},
);
API.v1.addRoute(
'groups.counters',
{ authRequired: true },
{
async get() {
const access = await hasPermissionAsync(this.userId, 'view-room-administration');
const params = this.queryParams;
let user = this.userId;
let room;
let unreads = null;
let userMentions = null;
let unreadsFrom = null;
let joined = false;
let msgs = null;
let latest = null;
let members = null;
if (('roomId' in params && !params.roomId) || ('roomName' in params && !params.roomName)) {
throw new Meteor.Error('error-room-param-not-provided', 'The parameter "roomId" or "roomName" is required');
}
if ('roomId' in params) {
room = await Rooms.findOneById(params.roomId || '');
} else if ('roomName' in params) {
room = await Rooms.findOneByName(params.roomName || '');
}
if (!room || room.t !== 'p') {
throw new Meteor.Error('error-room-not-found', 'The required "roomId" or "roomName" param provided does not match any group');
}
if (room.archived) {
throw new Meteor.Error('error-room-archived', `The private group, ${room.name}, is archived`);
}
if (params.userId) {
if (!access) {
return API.v1.unauthorized();
}
user = params.userId;
}
const subscription = await Subscriptions.findOneByRoomIdAndUserId(room._id, user);
const lm = room.lm ? room.lm : room._updatedAt;
if (subscription?.open) {
unreads = await Messages.countVisibleByRoomIdBetweenTimestampsInclusive(subscription.rid, subscription.ls || subscription.ts, lm);
unreadsFrom = subscription.ls || subscription.ts;
userMentions = subscription.userMentions;
joined = true;
}
if (access || joined) {
msgs = room.msgs;
latest = lm;
members = room.usersCount;
}
return API.v1.success({
joined,
members,
unreads,
unreadsFrom,
msgs,
latest,
userMentions,
});
},
},
);
// Create Private Group
API.v1.addRoute(
'groups.create',
{ authRequired: true },
{
async post() {
if (!this.bodyParams.name) {
return API.v1.failure('Body param "name" is required');
}
if (this.bodyParams.members && !Array.isArray(this.bodyParams.members)) {
return API.v1.failure('Body param "members" must be an array if provided');
}
if (this.bodyParams.customFields && !(typeof this.bodyParams.customFields === 'object')) {
return API.v1.failure('Body param "customFields" must be an object if provided');
}
if (this.bodyParams.extraData && !(typeof this.bodyParams.extraData === 'object')) {
return API.v1.failure('Body param "extraData" must be an object if provided');
}
const readOnly = typeof this.bodyParams.readOnly !== 'undefined' ? this.bodyParams.readOnly : false;
try {
const result = await createPrivateGroupMethod(
this.user,
this.bodyParams.name,
this.bodyParams.members ? this.bodyParams.members : [],
readOnly,
this.bodyParams.customFields,
this.bodyParams.extraData,
this.bodyParams.excludeSelf ?? false,
);
const room = await Rooms.findOneById(result.rid, { projection: API.v1.defaultFieldsToExclude });
if (!room) {
throw new Meteor.Error('error-room-not-found', 'The required "roomId" or "roomName" param provided does not match any group');
}
return API.v1.success({
group: await composeRoomWithLastMessage(room, this.userId),
});
} catch (error: unknown) {
if (isMeteorError(error) && error.reason === 'error-not-allowed') {
return API.v1.unauthorized();
}
throw error;
}
},
},
);
API.v1.addRoute(
'groups.delete',
{ authRequired: true },
{
async post() {
const findResult = await findPrivateGroupByIdOrName({
params: this.bodyParams,
userId: this.userId,
checkedArchived: false,
});
await Meteor.callAsync('eraseRoom', findResult.rid);
return API.v1.success();
},
},
);
API.v1.addRoute(
'groups.files',
{ authRequired: true },
{
async get() {
const findResult = await findPrivateGroupByIdOrName({
params: this.queryParams,
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 { cursor, totalCount } = await Uploads.findPaginatedWithoutThumbs(ourQuery, {
sort: sort || { name: 1 },
skip: offset,
limit: count,
projection: fields,
});
const [files, total] = await Promise.all([cursor.toArray(), totalCount]);
return API.v1.success({
files: await addUserToFileObj(files),
count: files.length,
offset,
total,
});
},
},
);
API.v1.addRoute(
'groups.getIntegrations',
{ authRequired: true },
{
async get() {
if (
!(await hasAtLeastOnePermissionAsync(this.userId, [
'manage-outgoing-integrations',
'manage-own-outgoing-integrations',
'manage-incoming-integrations',
'manage-own-incoming-integrations',
]))
) {
return API.v1.unauthorized();
}
const findResult = await findPrivateGroupByIdOrName({
params: this.queryParams,
userId: this.userId,
checkedArchived: false,
});
let includeAllPrivateGroups = true;
if (this.queryParams.includeAllPrivateGroups) {
includeAllPrivateGroups = this.queryParams.includeAllPrivateGroups === 'true';
}
const channelsToSearch = [`#${findResult.name}`];
if (includeAllPrivateGroups) {
channelsToSearch.push('all_private_groups');
}
const { offset, count } = await getPaginationItems(this.queryParams);
const { sort, fields: projection, query } = await this.parseJsonQuery();
const ourQuery = Object.assign(await mountIntegrationQueryBasedOnPermissions(this.userId), query, {
channel: { $in: channelsToSearch },
}) as Filter<IIntegration>;
const { cursor, totalCount } = await Integrations.findPaginated(ourQuery, {
sort: sort || { _createdAt: 1 },
skip: offset,
limit: count,
projection,
});
const [integrations, total] = await Promise.all([cursor.toArray(), totalCount]);
return API.v1.success({
integrations,
count: integrations.length,
offset,
total,
});
},
},
);
API.v1.addRoute(
'groups.history',
{ authRequired: true },
{
async get() {
const findResult = await findPrivateGroupByIdOrName({
params: this.queryParams,
userId: this.userId,
checkedArchived: false,
});
let latestDate = new Date();
if (this.queryParams.latest) {
latestDate = new Date(this.queryParams.latest);
}
let oldestDate = undefined;
if (this.queryParams.oldest) {
oldestDate = new Date(this.queryParams.oldest);
}
const inclusive = this.queryParams.inclusive || false;
let count = 20;
if (this.queryParams.count) {
count = parseInt(String(this.queryParams.count));
}
let offset = 0;
if (this.queryParams.offset) {
offset = parseInt(String(this.queryParams.offset));
}
const unreads = this.queryParams.unreads || false;
const showThreadMessages = this.queryParams.showThreadMessages !== 'false';
const result = await Meteor.callAsync('getChannelHistory', {
rid: findResult.rid,
latest: latestDate,
oldest: oldestDate,
inclusive,
offset,
count,
unreads,
showThreadMessages,
});
if (!result) {
return API.v1.unauthorized();
}
return API.v1.success(result);
},
},
);
API.v1.addRoute(
'groups.info',
{ authRequired: true },
{
async get() {
const findResult = await findPrivateGroupByIdOrName({
params: this.queryParams,
userId: this.userId,
checkedArchived: false,
});
const room = await Rooms.findOneById(findResult.rid, { projection: API.v1.defaultFieldsToExclude });
if (!room) {
throw new Meteor.Error('error-room-not-found', 'The required "roomId" or "roomName" param provided does not match any group');
}
return API.v1.success({
group: await composeRoomWithLastMessage(room, this.userId),
});
},
},
);
API.v1.addRoute(
'groups.invite',
{ authRequired: true },
{
async post() {
const roomId = 'roomId' in this.bodyParams ? this.bodyParams.roomId : '';
const roomName = 'roomName' in this.bodyParams ? this.bodyParams.roomName : '';
const idOrName = roomId || roomName;
if (!idOrName?.trim()) {
throw new Meteor.Error('error-room-param-not-provided', 'The parameter "roomId" or "roomName" is required');
}
const { _id: rid, t: type } = (await Rooms.findOneByIdOrName(idOrName)) || {};
if (!rid || type !== 'p') {
throw new Meteor.Error('error-room-not-found', 'The required "roomId" or "roomName" param provided does not match any group');
}
const users = await getUserListFromParams(this.bodyParams);
if (!users.length) {
throw new Meteor.Error('error-empty-invite-list', 'Cannot invite if no valid users are provided');
}
await Meteor.callAsync('addUsersToRoom', { rid, users: users.map((u) => u.username) });
const room = await Rooms.findOneById(rid, { projection: API.v1.defaultFieldsToExclude });
if (!room) {
throw new Meteor.Error('error-room-not-found', 'The required "roomId" or "roomName" param provided does not match any group');
}
return API.v1.success({
group: await composeRoomWithLastMessage(room, this.userId),
});
},
},
);
API.v1.addRoute(
'groups.kick',
{ authRequired: true },
{
async post() {
const room = await getRoomFromParams(this.bodyParams);
const user = await getUserFromParams(this.bodyParams);
if (!user?.username) {
return API.v1.failure('Invalid user');
}
await removeUserFromRoomMethod(this.userId, { rid: room._id, username: user.username });
return API.v1.success();
},
},
);
API.v1.addRoute(
'groups.leave',
{ authRequired: true },
{
async post() {
const findResult = await findPrivateGroupByIdOrName({
params: this.bodyParams,
userId: this.userId,
});
const user = await Users.findOneById(this.userId);
if (!user) {
return API.v1.failure('Invalid user');
}
await leaveRoomMethod(user, findResult.rid);
return API.v1.success();
},
},
);
// List Private Groups a user has access to
API.v1.addRoute(
'groups.list',
{ authRequired: true },
{
async get() {
const { offset, count } = await getPaginationItems(this.queryParams);
const { sort, fields } = await this.parseJsonQuery();
const subs = await Subscriptions.findByUserIdAndTypes(this.userId, ['p'], { projection: { rid: 1 } }).toArray();
const rids = subs.map(({ rid }) => rid).filter(Boolean);
if (rids.length === 0) {
return API.v1.success({
groups: [],
offset,
count: 0,
total: 0,
});
}
const { cursor, totalCount } = await Rooms.findPaginatedByTypeAndIds('p', rids, {
sort: sort || { name: 1 },
skip: offset,
limit: count,
projection: fields,
});
const [groups, total] = await Promise.all([cursor.toArray(), totalCount]);
return API.v1.success({
groups: await Promise.all(groups.map((room) => composeRoomWithLastMessage(room, this.userId))),
offset,
count: groups.length,
total,
});
},
},
);
API.v1.addRoute(
'groups.listAll',
{ authRequired: true },
{
async get() {
if (!(await hasPermissionAsync(this.userId, 'view-room-administration'))) {
return API.v1.unauthorized();
}
const { offset, count } = await getPaginationItems(this.queryParams);
const { sort, fields, query } = await this.parseJsonQuery();
const ourQuery = Object.assign({}, query, { t: 'p' as RoomType });
const { cursor, totalCount } = await Rooms.findPaginated(ourQuery, {
sort: sort || { name: 1 },
skip: offset,
limit: count,
projection: fields,
});
const [rooms, total] = await Promise.all([cursor.toArray(), totalCount]);
return API.v1.success({
groups: await Promise.all(rooms.map((room) => composeRoomWithLastMessage(room, this.userId))),
offset,
count: rooms.length,
total,
});
},
},
);
API.v1.addRoute(
'groups.members',
{ authRequired: true },
{
async get() {
const findResult = await findPrivateGroupByIdOrName({
params: this.queryParams,
userId: this.userId,
});
if (findResult.broadcast && !(await hasPermissionAsync(this.userId, 'view-broadcast-member-list', findResult.rid))) {
return API.v1.unauthorized();
}
const { offset: skip, count: limit } = await getPaginationItems(this.queryParams);
const { sort = {} } = await this.parseJsonQuery();
check(
this.queryParams,
Match.ObjectIncluding({
status: Match.Maybe([String]),
filter: Match.Maybe(String),
}),
);
const { status, filter } = this.queryParams;
const { cursor, totalCount } = await findUsersOfRoom({
rid: findResult.rid,
...(status && { status: { $in: status } }),
skip,
limit,
filter,
...(sort?.username && { sort: { username: sort.username } }),
});
const [members, total] = await Promise.all([cursor.toArray(), totalCount]);
return API.v1.success({
members,
count: members.length,
offset: skip,
total,
});
},
},
);
API.v1.addRoute(
'groups.messages',
{ authRequired: true },
{
async get() {
const findResult = await findPrivateGroupByIdOrName({
params: this.queryParams,
userId: this.userId,
});
const { offset, count } = await getPaginationItems(this.queryParams);
const { sort, fields, query } = await this.parseJsonQuery();
const ourQuery = Object.assign({}, query, { rid: findResult.rid });
const { cursor, totalCount } = await Messages.findPaginated(ourQuery, {
sort: sort || { ts: -1 },
skip: offset,
limit: count,
projection: fields,
});
const [messages, total] = await Promise.all([cursor.toArray(), totalCount]);
return API.v1.success({
messages: await normalizeMessagesForUser(messages, this.userId),
count: messages.length,
offset,
total,
});
},
},
);
// TODO: CACHE: same as channels.online
API.v1.addRoute(
'groups.online',
{ authRequired: true },
{
async get() {
const { query } = await this.parseJsonQuery();
if (!query || Object.keys(query).length === 0) {
return API.v1.failure('Invalid query');
}
const ourQuery = Object.assign({}, query, { t: 'p' });
const room = await Rooms.findOne(ourQuery as Record<string, any>);
if (!room) {
return API.v1.failure('Group does not exists');
}
const user = await getLoggedInUser(this.request);
if (!user) {
return API.v1.failure('User does not exists');
}
if (!(await canAccessRoomAsync(room, user))) {
throw new Meteor.Error('error-not-allowed', 'Not Allowed');
}
const online: Pick<IUser, '_id' | 'username'>[] = await Users.findUsersNotOffline({
projection: {
username: 1,
},
}).toArray();
const onlineInRoom = await Promise.all(
online.map(async (user) => {
const subscription = await Subscriptions.findOneByRoomIdAndUserId(room._id, user._id, {
projection: { _id: 1, username: 1 },
});
if (subscription) {
return {
_id: user._id,
username: user.username,
};
}
}),
);
return API.v1.success({
online: onlineInRoom.filter(Boolean) as IUser[],
});
},
},
);
API.v1.addRoute(
'groups.open',
{ authRequired: true },
{
async post() {
const findResult = await findPrivateGroupByIdOrName({
params: this.bodyParams,
userId: this.userId,
checkedArchived: false,
});
if (findResult.open) {
return API.v1.failure(`The private group, ${findResult.name}, is already open for the sender`);
}
await Meteor.callAsync('openRoom', findResult.rid);
return API.v1.success();
},
},
);
API.v1.addRoute(
'groups.removeModerator',
{ authRequired: true },
{
async post() {
const findResult = await findPrivateGroupByIdOrName({
params: this.bodyParams,
userId: this.userId,
});
const user = await getUserFromParams(this.bodyParams);
await Meteor.callAsync('removeRoomModerator', findResult.rid, user._id);
return API.v1.success();
},
},
);
API.v1.addRoute(
'groups.removeOwner',
{ authRequired: true },
{
async post() {
const findResult = await findPrivateGroupByIdOrName({
params: this.bodyParams,
userId: this.userId,
});
const user = await getUserFromParams(this.bodyParams);
await Meteor.callAsync('removeRoomOwner', findResult.rid, user._id);
return API.v1.success();
},
},
);
API.v1.addRoute(
'groups.removeLeader',
{ authRequired: true },
{
async post() {
const findResult = await findPrivateGroupByIdOrName({
params: this.bodyParams,
userId: this.userId,
});
const user = await getUserFromParams(this.bodyParams);
await Meteor.callAsync('removeRoomLeader', findResult.rid, user._id);
return API.v1.success();
},
},
);
API.v1.addRoute(
'groups.rename',
{ authRequired: true },
{
async post() {
if (!this.bodyParams.name?.trim()) {
return API.v1.failure('The bodyParam "name" is required');
}
const findResult = await findPrivateGroupByIdOrName({
params: this.bodyParams,
userId: this.userId,
});
await saveRoomSettings(this.userId, findResult.rid, 'roomName', this.bodyParams.name);
const room = await Rooms.findOneById(findResult.rid, { projection: API.v1.defaultFieldsToExclude });
if (!room) {
throw new Meteor.Error('error-room-not-found', 'The required "roomId" or "roomName" param provided does not match any group');
}
return API.v1.success({
group: await composeRoomWithLastMessage(room, this.userId),
});
},
},
);
API.v1.addRoute(
'groups.setCustomFields',
{ authRequired: true },
{
async post() {
if (!this.bodyParams.customFields || !(typeof this.bodyParams.customFields === 'object')) {
return API.v1.failure('The bodyParam "customFields" is required with a type like object.');
}
const findResult = await findPrivateGroupByIdOrName({
params: this.bodyParams,
userId: this.userId,
});
await saveRoomSettings(this.userId, findResult.rid, 'roomCustomFields', this.bodyParams.customFields);
const room = await Rooms.findOneById(findResult.rid, { projection: API.v1.defaultFieldsToExclude });
if (!room) {
throw new Meteor.Error('error-room-not-found', 'The required "roomId" or "roomName" param provided does not match any group');
}
return API.v1.success({
group: await composeRoomWithLastMessage(room, this.userId),
});
},
},
);
API.v1.addRoute(
'groups.setDescription',
{ authRequired: true },
{
async post() {
if (!this.bodyParams.hasOwnProperty('description')) {
return API.v1.failure('The bodyParam "description" is required');
}
const findResult = await findPrivateGroupByIdOrName({
params: this.bodyParams,
userId: this.userId,
});
await saveRoomSettings(this.userId, findResult.rid, 'roomDescription', this.bodyParams.description || '');
return API.v1.success({
description: this.bodyParams.description || '',
});
},
},
);
API.v1.addRoute(
'groups.setPurpose',
{ authRequired: true },
{
async post() {
if (!this.bodyParams.hasOwnProperty('purpose')) {
return API.v1.failure('The bodyParam "purpose" is required');
}
const findResult = await findPrivateGroupByIdOrName({
params: this.bodyParams,
userId: this.userId,
});
await saveRoomSettings(this.userId, findResult.rid, 'roomDescription', this.bodyParams.purpose || '');
return API.v1.success({
purpose: this.bodyParams.purpose || '',
});
},
},
);
API.v1.addRoute(
'groups.setReadOnly',
{ authRequired: true },
{
async post() {
if (typeof this.bodyParams.readOnly === 'undefined') {
return API.v1.failure('The bodyParam "readOnly" is required');
}
const findResult = await findPrivateGroupByIdOrName({
params: this.bodyParams,
userId: this.userId,
});
if (findResult.ro === this.bodyParams.readOnly) {
return API.v1.failure('The private group read only setting is the same as what it would be changed to.');
}
await saveRoomSettings(this.userId, findResult.rid, 'readOnly', this.bodyParams.readOnly);
const room = await Rooms.findOneById(findResult.rid, { projection: API.v1.defaultFieldsToExclude });
if (!room) {
throw new Meteor.Error('error-room-not-found', 'The required "roomId" or "roomName" param provided does not match any group');
}
return API.v1.success({
group: await composeRoomWithLastMessage(room, this.userId),
});
},
},
);
API.v1.addRoute(
'groups.setTopic',
{ authRequired: true },
{
async post() {
if (!this.bodyParams.hasOwnProperty('topic')) {
return API.v1.failure('The bodyParam "topic" is required');
}
const findResult = await findPrivateGroupByIdOrName({
params: this.bodyParams,
userId: this.userId,
});
await saveRoomSettings(this.userId, findResult.rid, 'roomTopic', this.bodyParams.topic || '');
return API.v1.success({
topic: this.bodyParams.topic || '',
});
},
},
);
API.v1.addRoute(
'groups.setType',
{ authRequired: true },
{
async post() {
if (!this.bodyParams.type?.trim()) {
return API.v1.failure('The bodyParam "type" is required');
}
const findResult = await findPrivateGroupByIdOrName({
params: this.bodyParams,
userId: this.userId,
});
if (findResult.t === this.bodyParams.type) {
return API.v1.failure('The private group type is the same as what it would be changed to.');
}
await saveRoomSettings(this.userId, findResult.rid, 'roomType', this.bodyParams.type as RoomType);
const room = await Rooms.findOneById(findResult.rid, { projection: API.v1.defaultFieldsToExclude });
if (!room) {
throw new Meteor.Error('error-room-not-found', 'The required "roomId" or "roomName" param provided does not match any group');
}
return API.v1.success({
group: await composeRoomWithLastMessage(room, this.userId),
});
},
},
);
API.v1.addRoute(
'groups.setAnnouncement',
{ authRequired: true },
{
async post() {
if (!this.bodyParams.hasOwnProperty('announcement')) {
return API.v1.failure('The bodyParam "announcement" is required');
}
const findResult = await findPrivateGroupByIdOrName({
params: this.bodyParams,
userId: this.userId,
});
await saveRoomSettings(this.userId, findResult.rid, 'roomAnnouncement', this.bodyParams.announcement || '');
return API.v1.success({
announcement: this.bodyParams.announcement || '',
});
},
},
);
API.v1.addRoute(
'groups.unarchive',
{ authRequired: true },
{
async post() {
const findResult = await findPrivateGroupByIdOrName({
params: this.bodyParams,
userId: this.userId,
checkedArchived: false,
});
await Meteor.callAsync('unarchiveRoom', findResult.rid);
return API.v1.success();
},
},
);
API.v1.addRoute(
'groups.roles',
{ authRequired: true },
{
async get() {
const findResult = await findPrivateGroupByIdOrName({
params: this.queryParams,
userId: this.userId,
});
const roles = await Meteor.callAsync('getRoomRoles', findResult.rid);
return API.v1.success({
roles,
});
},
},
);
API.v1.addRoute(
'groups.moderators',
{ authRequired: true },
{
async get() {
const findResult = await findPrivateGroupByIdOrName({
params: this.queryParams,
userId: this.userId,
});
const moderators = (
await Subscriptions.findByRoomIdAndRoles(findResult.rid, ['moderator'], {
projection: { u: 1 },
}).toArray()
).map((sub: any) => sub.u);
return API.v1.success({
moderators,
});
},
},
);
API.v1.addRoute(
'groups.setEncrypted',
{ authRequired: true },
{
async post() {
if (!Match.test(this.bodyParams, Match.ObjectIncluding({ encrypted: Boolean }))) {
return API.v1.failure('The bodyParam "encrypted" is required');
}
const { encrypted, ...params } = this.bodyParams;
const findResult = await findPrivateGroupByIdOrName({
params,
userId: this.userId,
});
await saveRoomSettings(this.userId, findResult.rid, 'encrypted', encrypted);
const room = await Rooms.findOneById(findResult.rid, { projection: API.v1.defaultFieldsToExclude });
if (!room) {
throw new Meteor.Error('error-room-not-found', 'The required "roomId" or "roomName" param provided does not match any group');
}
return API.v1.success({
group: await composeRoomWithLastMessage(room, this.userId),
});
},
},
);
API.v1.addRoute(
'groups.convertToTeam',
{ authRequired: true },
{
async post() {
if (('roomId' in this.bodyParams && !this.bodyParams.roomId) || ('roomName' in this.bodyParams && !this.bodyParams.roomName)) {
return API.v1.failure('The parameter "roomId" or "roomName" is required');
}
const room = await findPrivateGroupByIdOrName({
params: this.bodyParams,
userId: this.userId,
});
if (!room) {
return API.v1.failure('Private group not found');
}
if (!(await hasAllPermissionAsync(this.userId, ['create-team', 'edit-room'], room.rid))) {
return API.v1.unauthorized();
}
const subscriptions = await Subscriptions.findByRoomId(room.rid, {
projection: { 'u._id': 1 },
}).toArray();
const members = subscriptions.map((s) => s.u?._id);
const teamData = {
team: {
name: room.name,
type: 1,
},
members,
room: {
name: room.name,
id: room.rid,
},
};
const team = await Team.create(this.userId, teamData);
return API.v1.success({ team });
},
},
);