The communications platform that puts data protection first.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
Rocket.Chat/app/api/server/v1/channels.js

1394 lines
33 KiB

import { Meteor } from 'meteor/meteor';
import { Match, check } from 'meteor/check';
import _ from 'underscore';
import { Rooms, Subscriptions, Messages, Users } from '../../../models/server';
import { Integrations, Uploads } from '../../../models/server/raw';
import { canAccessRoom, hasPermission, hasAtLeastOnePermission, hasAllPermission } from '../../../authorization/server';
import { mountIntegrationQueryBasedOnPermissions } from '../../../integrations/server/lib/mountQueriesBasedOnPermission';
import { normalizeMessagesForUser } from '../../../utils/server/lib/normalizeMessagesForUser';
import { API } from '../api';
import { settings } from '../../../settings/server';
import { Team } from '../../../../server/sdk';
import { findUsersOfRoom } from '../../../../server/lib/findUsersOfRoom';
// Returns the channel IF found otherwise it will return the failure of why it didn't. Check the `statusCode` property
function findChannelByIdOrName({ params, checkedArchived = true, userId }) {
if ((!params.roomId || !params.roomId.trim()) && (!params.roomName || !params.roomName.trim())) {
throw new Meteor.Error('error-roomid-param-not-provided', 'The parameter "roomId" or "roomName" is required');
}
const fields = { ...API.v1.defaultFieldsToExclude };
let room;
if (params.roomId) {
room = Rooms.findOneById(params.roomId, { fields });
} else if (params.roomName) {
room = Rooms.findOneByName(params.roomName, { fields });
}
if (!room || (room.t !== 'c' && room.t !== 'l')) {
throw new Meteor.Error('error-room-not-found', 'The required "roomId" or "roomName" param provided does not match any channel');
}
if (checkedArchived && room.archived) {
throw new Meteor.Error('error-room-archived', `The channel, ${room.name}, is archived`);
}
if (userId && room.lastMessage) {
const [lastMessage] = normalizeMessagesForUser([room.lastMessage], userId);
room.lastMessage = lastMessage;
}
return room;
}
API.v1.addRoute(
'channels.addAll',
{ authRequired: true },
{
post() {
const findResult = findChannelByIdOrName({ params: this.requestParams() });
Meteor.runAsUser(this.userId, () => {
Meteor.call('addAllUserToRoom', findResult._id, this.bodyParams.activeUsersOnly);
});
return API.v1.success({
channel: findChannelByIdOrName({ params: this.requestParams(), userId: this.userId }),
});
},
},
);
API.v1.addRoute(
'channels.addModerator',
{ authRequired: true },
{
post() {
const findResult = findChannelByIdOrName({ params: this.requestParams() });
const user = this.getUserFromParams();
Meteor.runAsUser(this.userId, () => {
Meteor.call('addRoomModerator', findResult._id, user._id);
});
return API.v1.success();
},
},
);
API.v1.addRoute(
'channels.addOwner',
{ authRequired: true },
{
post() {
const findResult = findChannelByIdOrName({ params: this.requestParams() });
const user = this.getUserFromParams();
Meteor.runAsUser(this.userId, () => {
Meteor.call('addRoomOwner', findResult._id, user._id);
});
return API.v1.success();
},
},
);
API.v1.addRoute(
'channels.archive',
{ authRequired: true },
{
post() {
const findResult = findChannelByIdOrName({ params: this.requestParams() });
Meteor.runAsUser(this.userId, () => {
Meteor.call('archiveRoom', findResult._id);
});
return API.v1.success();
},
},
);
API.v1.addRoute(
'channels.close',
{ authRequired: true },
{
post() {
const findResult = findChannelByIdOrName({
params: this.requestParams(),
checkedArchived: false,
});
const sub = Subscriptions.findOneByRoomIdAndUserId(findResult._id, this.userId);
if (!sub) {
return API.v1.failure(`The user/callee is not in the channel "${findResult.name}.`);
}
if (!sub.open) {
return API.v1.failure(`The channel, ${findResult.name}, is already closed to the sender`);
}
Meteor.runAsUser(this.userId, () => {
Meteor.call('hideRoom', findResult._id);
});
return API.v1.success();
},
},
);
API.v1.addRoute(
'channels.counters',
{ authRequired: true },
{
get() {
const access = hasPermission(this.userId, 'view-room-administration');
const { userId } = this.requestParams();
let user = this.userId;
let unreads = null;
let userMentions = null;
let unreadsFrom = null;
let joined = false;
let msgs = null;
let latest = null;
let members = null;
if (userId) {
if (!access) {
return API.v1.unauthorized();
}
user = userId;
}
const room = findChannelByIdOrName({
params: this.requestParams(),
returnUsernames: true,
});
const subscription = Subscriptions.findOneByRoomIdAndUserId(room._id, user);
const lm = room.lm ? room.lm : room._updatedAt;
if (typeof subscription !== 'undefined' && subscription.open) {
unreads = Messages.countVisibleByRoomIdBetweenTimestampsInclusive(subscription.rid, subscription.ls, 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,
});
},
},
);
// Channel -> create
function createChannelValidator(params) {
if (!hasPermission(params.user.value, 'create-c')) {
throw new Error('unauthorized');
}
if (!params.name || !params.name.value) {
throw new Error(`Param "${params.name.key}" is required`);
}
if (params.members && params.members.value && !_.isArray(params.members.value)) {
throw new Error(`Param "${params.members.key}" must be an array if provided`);
}
if (params.customFields && params.customFields.value && !(typeof params.customFields.value === 'object')) {
throw new Error(`Param "${params.customFields.key}" must be an object if provided`);
}
if (params.teams.value && !Array.isArray(params.teams.value)) {
throw new Error(`Param ${params.teams.key} must be an array`);
}
}
function createChannel(userId, params) {
const readOnly = typeof params.readOnly !== 'undefined' ? params.readOnly : false;
const id = Meteor.runAsUser(userId, () =>
Meteor.call('createChannel', params.name, params.members ? params.members : [], readOnly, params.customFields, params.extraData),
);
return {
channel: findChannelByIdOrName({ params: { roomId: id.rid }, userId: this.userId }),
};
}
API.channels = {};
API.channels.create = {
validate: createChannelValidator,
execute: createChannel,
};
API.v1.addRoute(
'channels.create',
{ authRequired: true },
{
post() {
const { userId, bodyParams } = this;
let error;
try {
API.channels.create.validate({
user: {
value: userId,
},
name: {
value: bodyParams.name,
key: 'name',
},
members: {
value: bodyParams.members,
key: 'members',
},
teams: {
value: bodyParams.teams,
key: 'teams',
},
});
} catch (e) {
if (e.message === 'unauthorized') {
error = API.v1.unauthorized();
} else {
error = API.v1.failure(e.message);
}
}
if (error) {
return error;
}
if (bodyParams.teams) {
const canSeeAllTeams = hasPermission(this.userId, 'view-all-teams');
const teams = Promise.await(Team.listByNames(bodyParams.teams, { projection: { _id: 1 } }));
const teamMembers = [];
for (const team of teams) {
const { records: members } = Promise.await(
Team.members(this.userId, team._id, canSeeAllTeams, {
offset: 0,
count: Number.MAX_SAFE_INTEGER,
}),
);
const uids = members.map((member) => member.user.username);
teamMembers.push(...uids);
}
const membersToAdd = new Set([...teamMembers, ...bodyParams.members]);
bodyParams.members = [...membersToAdd];
}
return API.v1.success(API.channels.create.execute(userId, bodyParams));
},
},
);
API.v1.addRoute(
'channels.delete',
{ authRequired: true },
{
post() {
const room = findChannelByIdOrName({
params: this.requestParams(),
checkedArchived: false,
});
Meteor.runAsUser(this.userId, () => {
Meteor.call('eraseRoom', room._id);
});
return API.v1.success();
},
},
);
API.v1.addRoute(
'channels.files',
{ authRequired: true },
{
get() {
const findResult = findChannelByIdOrName({
params: this.requestParams(),
checkedArchived: false,
});
const addUserObjectToEveryObject = (file) => {
if (file.userId) {
file = this.insertUserObject({ object: file, userId: file.userId });
}
return file;
};
if (!canAccessRoom(findResult, { _id: this.userId })) {
return API.v1.unauthorized();
}
const { offset, count } = this.getPaginationItems();
const { sort, fields, query } = this.parseJsonQuery();
const ourQuery = Object.assign({}, query, { rid: findResult._id });
const files = Promise.await(
Uploads.find(ourQuery, {
sort: sort || { name: 1 },
skip: offset,
limit: count,
fields,
}).toArray(),
);
return API.v1.success({
files: files.map(addUserObjectToEveryObject),
count: files.length,
offset,
total: Promise.await(Uploads.find(ourQuery).count()),
});
},
},
);
API.v1.addRoute(
'channels.getIntegrations',
{ authRequired: true },
{
get() {
if (
!hasAtLeastOnePermission(this.userId, [
'manage-outgoing-integrations',
'manage-own-outgoing-integrations',
'manage-incoming-integrations',
'manage-own-incoming-integrations',
])
) {
return API.v1.unauthorized();
}
const findResult = findChannelByIdOrName({
params: this.requestParams(),
checkedArchived: false,
});
let includeAllPublicChannels = true;
if (typeof this.queryParams.includeAllPublicChannels !== 'undefined') {
includeAllPublicChannels = this.queryParams.includeAllPublicChannels === 'true';
}
let ourQuery = {
channel: `#${findResult.name}`,
};
if (includeAllPublicChannels) {
ourQuery.channel = {
$in: [ourQuery.channel, 'all_public_channels'],
};
}
const { offset, count } = this.getPaginationItems();
const { sort, fields: projection, query } = this.parseJsonQuery();
ourQuery = Object.assign(mountIntegrationQueryBasedOnPermissions(this.userId), query, ourQuery);
const cursor = Integrations.find(ourQuery, {
sort: sort || { _createdAt: 1 },
skip: offset,
limit: count,
projection,
});
const integrations = Promise.await(cursor.toArray());
const total = Promise.await(cursor.count());
return API.v1.success({
integrations,
count: integrations.length,
offset,
total,
});
},
},
);
API.v1.addRoute(
'channels.history',
{ authRequired: true },
{
get() {
const findResult = findChannelByIdOrName({
params: this.requestParams(),
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(this.queryParams.count);
}
let offset = 0;
if (this.queryParams.offset) {
offset = parseInt(this.queryParams.offset);
}
const unreads = this.queryParams.unreads || false;
const showThreadMessages = this.queryParams.showThreadMessages !== 'false';
const result = Meteor.call('getChannelHistory', {
rid: findResult._id,
latest: latestDate,
oldest: oldestDate,
inclusive,
offset,
count,
unreads,
showThreadMessages,
});
if (!result) {
return API.v1.unauthorized();
}
return API.v1.success(result);
},
},
);
API.v1.addRoute(
'channels.info',
{ authRequired: true },
{
get() {
return API.v1.success({
channel: findChannelByIdOrName({
params: this.requestParams(),
checkedArchived: false,
userId: this.userId,
}),
});
},
},
);
API.v1.addRoute(
'channels.invite',
{ authRequired: true },
{
post() {
const findResult = findChannelByIdOrName({ params: this.requestParams() });
const users = this.getUserListFromParams();
if (!users.length) {
return API.v1.failure('invalid-user-invite-list', 'Cannot invite if no users are provided');
}
Meteor.runAsUser(this.userId, () => {
Meteor.call('addUsersToRoom', { rid: findResult._id, users: users.map((u) => u.username) });
});
return API.v1.success({
channel: findChannelByIdOrName({ params: this.requestParams(), userId: this.userId }),
});
},
},
);
API.v1.addRoute(
'channels.join',
{ authRequired: true },
{
post() {
const findResult = findChannelByIdOrName({ params: this.requestParams() });
Meteor.runAsUser(this.userId, () => {
Meteor.call('joinRoom', findResult._id, this.bodyParams.joinCode);
});
return API.v1.success({
channel: findChannelByIdOrName({ params: this.requestParams(), userId: this.userId }),
});
},
},
);
API.v1.addRoute(
'channels.kick',
{ authRequired: true },
{
post() {
const findResult = findChannelByIdOrName({ params: this.requestParams() });
const user = this.getUserFromParams();
Meteor.runAsUser(this.userId, () => {
Meteor.call('removeUserFromRoom', { rid: findResult._id, username: user.username });
});
return API.v1.success({
channel: findChannelByIdOrName({ params: this.requestParams(), userId: this.userId }),
});
},
},
);
API.v1.addRoute(
'channels.leave',
{ authRequired: true },
{
post() {
const findResult = findChannelByIdOrName({ params: this.requestParams() });
Meteor.runAsUser(this.userId, () => {
Meteor.call('leaveRoom', findResult._id);
});
return API.v1.success({
channel: findChannelByIdOrName({ params: this.requestParams(), userId: this.userId }),
});
},
},
);
API.v1.addRoute(
'channels.list',
{ authRequired: true },
{
get: {
// This is defined as such only to provide an example of how the routes can be defined :X
action() {
const { offset, count } = this.getPaginationItems();
const { sort, fields, query } = this.parseJsonQuery();
const hasPermissionToSeeAllPublicChannels = hasPermission(this.userId, 'view-c-room');
const ourQuery = { ...query, t: 'c' };
if (!hasPermissionToSeeAllPublicChannels) {
if (!hasPermission(this.userId, 'view-joined-room')) {
return API.v1.unauthorized();
}
const roomIds = Subscriptions.findByUserIdAndType(this.userId, 'c', {
fields: { rid: 1 },
})
.fetch()
.map((s) => s.rid);
ourQuery._id = { $in: roomIds };
}
// teams filter - I would love to have a way to apply this filter @ db level :(
const ids = Subscriptions.cachedFindByUserId(this.userId, { fields: { rid: 1 } })
.fetch()
.map((item) => item.rid);
ourQuery.$or = [
{
teamId: {
$exists: false,
},
},
{
teamId: {
$exists: true,
},
_id: {
$in: ids,
},
},
];
const cursor = Rooms.find(ourQuery, {
sort: sort || { name: 1 },
skip: offset,
limit: count,
fields,
});
const total = cursor.count();
const rooms = cursor.fetch();
return API.v1.success({
channels: rooms.map((room) => this.composeRoomWithLastMessage(room, this.userId)),
count: rooms.length,
offset,
total,
});
},
},
},
);
API.v1.addRoute(
'channels.list.joined',
{ authRequired: true },
{
get() {
const { offset, count } = this.getPaginationItems();
const { sort, fields } = this.parseJsonQuery();
// TODO: CACHE: Add Breacking notice since we removed the query param
const cursor = Rooms.findBySubscriptionTypeAndUserId('c', this.userId, {
sort: sort || { name: 1 },
skip: offset,
limit: count,
fields,
});
const totalCount = cursor.count();
const rooms = cursor.fetch();
return API.v1.success({
channels: rooms.map((room) => this.composeRoomWithLastMessage(room, this.userId)),
offset,
count: rooms.length,
total: totalCount,
});
},
},
);
API.v1.addRoute(
'channels.members',
{ authRequired: true },
{
get() {
const findResult = findChannelByIdOrName({
params: this.requestParams(),
checkedArchived: false,
});
if (findResult.broadcast && !hasPermission(this.userId, 'view-broadcast-member-list')) {
return API.v1.unauthorized();
}
const { offset: skip, count: limit } = this.getPaginationItems();
const { sort = {} } = this.parseJsonQuery();
check(
this.queryParams,
Match.ObjectIncluding({
status: Match.Maybe([String]),
filter: Match.Maybe(String),
}),
);
const { status, filter } = this.queryParams;
const cursor = findUsersOfRoom({
rid: findResult._id,
...(status && { status: { $in: status } }),
skip,
limit,
filter,
...(sort?.username && { sort: { username: sort.username } }),
});
const total = cursor.count();
const members = cursor.fetch();
return API.v1.success({
members,
count: members.length,
offset: skip,
total,
});
},
},
);
API.v1.addRoute(
'channels.messages',
{ authRequired: true },
{
get() {
const findResult = findChannelByIdOrName({
params: this.requestParams(),
checkedArchived: false,
});
const { offset, count } = this.getPaginationItems();
const { sort, fields, query } = this.parseJsonQuery();
const ourQuery = Object.assign({}, query, { rid: findResult._id });
// Special check for the permissions
if (
hasPermission(this.userId, 'view-joined-room') &&
!Subscriptions.findOneByRoomIdAndUserId(findResult._id, this.userId, { fields: { _id: 1 } })
) {
return API.v1.unauthorized();
}
if (!hasPermission(this.userId, 'view-c-room')) {
return API.v1.unauthorized();
}
const cursor = Messages.find(ourQuery, {
sort: sort || { ts: -1 },
skip: offset,
limit: count,
fields,
});
const total = cursor.count();
const messages = cursor.fetch();
return API.v1.success({
messages: normalizeMessagesForUser(messages, this.userId),
count: messages.length,
offset,
total,
});
},
},
);
// TODO: CACHE: I dont like this method( functionality and how we implemented ) its very expensive
// TODO check if this code is better or not
// RocketChat.API.v1.addRoute('channels.online', { authRequired: true }, {
// get() {
// const { query } = this.parseJsonQuery();
// const ourQuery = Object.assign({}, query, { t: 'c' });
// const room = RocketChat.models.Rooms.findOne(ourQuery);
// if (room == null) {
// return RocketChat.API.v1.failure('Channel does not exists');
// }
// const ids = RocketChat.models.Subscriptions.find({ rid: room._id }, { fields: { 'u._id': 1 } }).fetch().map(sub => sub.u._id);
// const online = RocketChat.models.Users.find({
// username: { $exists: 1 },
// _id: { $in: ids },
// status: { $in: ['online', 'away', 'busy'] }
// }, {
// fields: { username: 1 }
// }).fetch();
// return RocketChat.API.v1.success({
// online
// });
// }
// });
API.v1.addRoute(
'channels.online',
{ authRequired: true },
{
get() {
const { query } = this.parseJsonQuery();
if (!query || Object.keys(query).length === 0) {
return API.v1.failure('Invalid query');
}
const ourQuery = Object.assign({}, query, { t: 'c' });
const room = Rooms.findOne(ourQuery);
if (room == null) {
return API.v1.failure('Channel does not exists');
}
const user = this.getLoggedInUser();
if (!canAccessRoom(room, user)) {
throw new Meteor.Error('error-not-allowed', 'Not Allowed');
}
const online = Users.findUsersNotOffline({
fields: { username: 1 },
}).fetch();
const onlineInRoom = [];
online.forEach((user) => {
const subscription = Subscriptions.findOneByRoomIdAndUserId(room._id, user._id, {
fields: { _id: 1 },
});
if (subscription) {
onlineInRoom.push({
_id: user._id,
username: user.username,
});
}
});
return API.v1.success({
online: onlineInRoom,
});
},
},
);
API.v1.addRoute(
'channels.open',
{ authRequired: true },
{
post() {
const findResult = findChannelByIdOrName({
params: this.requestParams(),
checkedArchived: false,
});
const sub = Subscriptions.findOneByRoomIdAndUserId(findResult._id, this.userId);
if (!sub) {
return API.v1.failure(`The user/callee is not in the channel "${findResult.name}".`);
}
if (sub.open) {
return API.v1.failure(`The channel, ${findResult.name}, is already open to the sender`);
}
Meteor.runAsUser(this.userId, () => {
Meteor.call('openRoom', findResult._id);
});
return API.v1.success();
},
},
);
API.v1.addRoute(
'channels.removeModerator',
{ authRequired: true },
{
post() {
const findResult = findChannelByIdOrName({ params: this.requestParams() });
const user = this.getUserFromParams();
Meteor.runAsUser(this.userId, () => {
Meteor.call('removeRoomModerator', findResult._id, user._id);
});
return API.v1.success();
},
},
);
API.v1.addRoute(
'channels.removeOwner',
{ authRequired: true },
{
post() {
const findResult = findChannelByIdOrName({ params: this.requestParams() });
const user = this.getUserFromParams();
Meteor.runAsUser(this.userId, () => {
Meteor.call('removeRoomOwner', findResult._id, user._id);
});
return API.v1.success();
},
},
);
API.v1.addRoute(
'channels.rename',
{ authRequired: true },
{
post() {
if (!this.bodyParams.name || !this.bodyParams.name.trim()) {
return API.v1.failure('The bodyParam "name" is required');
}
const findResult = findChannelByIdOrName({ params: { roomId: this.bodyParams.roomId } });
if (findResult.name === this.bodyParams.name) {
return API.v1.failure('The channel name is the same as what it would be renamed to.');
}
Meteor.runAsUser(this.userId, () => {
Meteor.call('saveRoomSettings', findResult._id, 'roomName', this.bodyParams.name);
});
return API.v1.success({
channel: findChannelByIdOrName({
params: { roomId: this.bodyParams.roomId },
userId: this.userId,
}),
});
},
},
);
API.v1.addRoute(
'channels.setCustomFields',
{ authRequired: true },
{
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 = findChannelByIdOrName({ params: this.requestParams() });
Meteor.runAsUser(this.userId, () => {
Meteor.call('saveRoomSettings', findResult._id, 'roomCustomFields', this.bodyParams.customFields);
});
return API.v1.success({
channel: findChannelByIdOrName({ params: this.requestParams(), userId: this.userId }),
});
},
},
);
API.v1.addRoute(
'channels.setDefault',
{ authRequired: true },
{
post() {
if (typeof this.bodyParams.default === 'undefined') {
return API.v1.failure('The bodyParam "default" is required', 'error-channels-setdefault-is-same');
}
const findResult = findChannelByIdOrName({ params: this.requestParams() });
if (findResult.default === this.bodyParams.default) {
return API.v1.failure(
'The channel default setting is the same as what it would be changed to.',
'error-channels-setdefault-missing-default-param',
);
}
Meteor.runAsUser(this.userId, () => {
Meteor.call(
'saveRoomSettings',
findResult._id,
'default',
['true', '1'].includes(this.bodyParams.default.toString().toLowerCase()),
);
});
return API.v1.success({
channel: findChannelByIdOrName({ params: this.requestParams(), userId: this.userId }),
});
},
},
);
API.v1.addRoute(
'channels.setDescription',
{ authRequired: true },
{
post() {
if (!this.bodyParams.hasOwnProperty('description')) {
return API.v1.failure('The bodyParam "description" is required');
}
const findResult = findChannelByIdOrName({ params: this.requestParams() });
if (findResult.description === this.bodyParams.description) {
return API.v1.failure('The channel description is the same as what it would be changed to.');
}
Meteor.runAsUser(this.userId, () => {
Meteor.call('saveRoomSettings', findResult._id, 'roomDescription', this.bodyParams.description);
});
return API.v1.success({
description: this.bodyParams.description,
});
},
},
);
API.v1.addRoute(
'channels.setJoinCode',
{ authRequired: true },
{
post() {
if (!this.bodyParams.joinCode || !this.bodyParams.joinCode.trim()) {
return API.v1.failure('The bodyParam "joinCode" is required');
}
const findResult = findChannelByIdOrName({ params: this.requestParams() });
Meteor.runAsUser(this.userId, () => {
Meteor.call('saveRoomSettings', findResult._id, 'joinCode', this.bodyParams.joinCode);
});
return API.v1.success({
channel: findChannelByIdOrName({ params: this.requestParams(), userId: this.userId }),
});
},
},
);
API.v1.addRoute(
'channels.setPurpose',
{ authRequired: true },
{
post() {
if (!this.bodyParams.hasOwnProperty('purpose')) {
return API.v1.failure('The bodyParam "purpose" is required');
}
const findResult = findChannelByIdOrName({ params: this.requestParams() });
if (findResult.description === this.bodyParams.purpose) {
return API.v1.failure('The channel purpose (description) is the same as what it would be changed to.');
}
Meteor.runAsUser(this.userId, () => {
Meteor.call('saveRoomSettings', findResult._id, 'roomDescription', this.bodyParams.purpose);
});
return API.v1.success({
purpose: this.bodyParams.purpose,
});
},
},
);
API.v1.addRoute(
'channels.setReadOnly',
{ authRequired: true },
{
post() {
if (typeof this.bodyParams.readOnly === 'undefined') {
return API.v1.failure('The bodyParam "readOnly" is required');
}
const findResult = findChannelByIdOrName({ params: this.requestParams() });
if (findResult.ro === this.bodyParams.readOnly) {
return API.v1.failure('The channel read only setting is the same as what it would be changed to.');
}
Meteor.runAsUser(this.userId, () => {
Meteor.call('saveRoomSettings', findResult._id, 'readOnly', this.bodyParams.readOnly);
});
return API.v1.success({
channel: findChannelByIdOrName({ params: this.requestParams(), userId: this.userId }),
});
},
},
);
API.v1.addRoute(
'channels.setTopic',
{ authRequired: true },
{
post() {
if (!this.bodyParams.hasOwnProperty('topic')) {
return API.v1.failure('The bodyParam "topic" is required');
}
const findResult = findChannelByIdOrName({ params: this.requestParams() });
if (findResult.topic === this.bodyParams.topic) {
return API.v1.failure('The channel topic is the same as what it would be changed to.');
}
Meteor.runAsUser(this.userId, () => {
Meteor.call('saveRoomSettings', findResult._id, 'roomTopic', this.bodyParams.topic);
});
return API.v1.success({
topic: this.bodyParams.topic,
});
},
},
);
API.v1.addRoute(
'channels.setAnnouncement',
{ authRequired: true },
{
post() {
if (!this.bodyParams.hasOwnProperty('announcement')) {
return API.v1.failure('The bodyParam "announcement" is required');
}
const findResult = findChannelByIdOrName({ params: this.requestParams() });
Meteor.runAsUser(this.userId, () => {
Meteor.call('saveRoomSettings', findResult._id, 'roomAnnouncement', this.bodyParams.announcement);
});
return API.v1.success({
announcement: this.bodyParams.announcement,
});
},
},
);
API.v1.addRoute(
'channels.setType',
{ authRequired: true },
{
post() {
if (!this.bodyParams.type || !this.bodyParams.type.trim()) {
return API.v1.failure('The bodyParam "type" is required');
}
const findResult = findChannelByIdOrName({ params: this.requestParams() });
if (findResult.t === this.bodyParams.type) {
return API.v1.failure('The channel type is the same as what it would be changed to.');
}
Meteor.runAsUser(this.userId, () => {
Meteor.call('saveRoomSettings', findResult._id, 'roomType', this.bodyParams.type);
});
return API.v1.success({
channel: this.composeRoomWithLastMessage(Rooms.findOneById(findResult._id, { fields: API.v1.defaultFieldsToExclude }), this.userId),
});
},
},
);
API.v1.addRoute(
'channels.unarchive',
{ authRequired: true },
{
post() {
const findResult = findChannelByIdOrName({
params: this.requestParams(),
checkedArchived: false,
});
if (!findResult.archived) {
return API.v1.failure(`The channel, ${findResult.name}, is not archived`);
}
Meteor.runAsUser(this.userId, () => {
Meteor.call('unarchiveRoom', findResult._id);
});
return API.v1.success();
},
},
);
API.v1.addRoute(
'channels.getAllUserMentionsByChannel',
{ authRequired: true },
{
get() {
const { roomId } = this.requestParams();
const { offset, count } = this.getPaginationItems();
const { sort } = this.parseJsonQuery();
if (!roomId) {
return API.v1.failure('The request param "roomId" is required');
}
const mentions = Meteor.runAsUser(this.userId, () =>
Meteor.call('getUserMentionsByChannel', {
roomId,
options: {
sort: sort || { ts: 1 },
skip: offset,
limit: count,
},
}),
);
const allMentions = Meteor.runAsUser(this.userId, () =>
Meteor.call('getUserMentionsByChannel', {
roomId,
options: {},
}),
);
return API.v1.success({
mentions,
count: mentions.length,
offset,
total: allMentions.length,
});
},
},
);
API.v1.addRoute(
'channels.roles',
{ authRequired: true },
{
get() {
const findResult = findChannelByIdOrName({ params: this.requestParams() });
const roles = Meteor.runAsUser(this.userId, () => Meteor.call('getRoomRoles', findResult._id));
return API.v1.success({
roles,
});
},
},
);
API.v1.addRoute(
'channels.moderators',
{ authRequired: true },
{
get() {
const findResult = findChannelByIdOrName({ params: this.requestParams() });
const moderators = Subscriptions.findByRoomIdAndRoles(findResult._id, ['moderator'], {
fields: { u: 1 },
})
.fetch()
.map((sub) => sub.u);
return API.v1.success({
moderators,
});
},
},
);
API.v1.addRoute(
'channels.addLeader',
{ authRequired: true },
{
post() {
const findResult = findChannelByIdOrName({ params: this.requestParams() });
const user = this.getUserFromParams();
Meteor.runAsUser(this.userId, () => {
Meteor.call('addRoomLeader', findResult._id, user._id);
});
return API.v1.success();
},
},
);
API.v1.addRoute(
'channels.removeLeader',
{ authRequired: true },
{
post() {
const findResult = findChannelByIdOrName({ params: this.requestParams() });
const user = this.getUserFromParams();
Meteor.runAsUser(this.userId, () => {
Meteor.call('removeRoomLeader', findResult._id, user._id);
});
return API.v1.success();
},
},
);
API.v1.addRoute(
'channels.anonymousread',
{ authRequired: false },
{
get() {
const findResult = findChannelByIdOrName({
params: this.requestParams(),
checkedArchived: false,
});
const { offset, count } = this.getPaginationItems();
const { sort, fields, query } = this.parseJsonQuery();
const ourQuery = Object.assign({}, query, { rid: findResult._id });
if (!settings.get('Accounts_AllowAnonymousRead')) {
throw new Meteor.Error('error-not-allowed', 'Enable "Allow Anonymous Read"', {
method: 'channels.anonymousread',
});
}
const cursor = Messages.find(ourQuery, {
sort: sort || { ts: -1 },
skip: offset,
limit: count,
fields,
});
const total = cursor.count();
const messages = cursor.fetch();
return API.v1.success({
messages: normalizeMessagesForUser(messages, this.userId),
count: messages.length,
offset,
total,
});
},
},
);
API.v1.addRoute(
'channels.convertToTeam',
{ authRequired: true },
{
post() {
if (!hasAllPermission(this.userId, ['create-team', 'edit-room'])) {
return API.v1.unauthorized();
}
const { channelId, channelName } = this.bodyParams;
if (!channelId && !channelName) {
return API.v1.failure('The parameter "channelId" or "channelName" is required');
}
const room = findChannelByIdOrName({
params: {
roomId: channelId,
roomName: channelName,
},
userId: this.userId,
});
if (!room) {
return API.v1.failure('Channel not found');
}
const subscriptions = Subscriptions.findByRoomId(room._id, {
fields: { 'u._id': 1 },
});
const members = subscriptions.fetch().map((s) => s.u && s.u._id);
const teamData = {
team: {
name: room.name,
type: room.t === 'c' ? 0 : 1,
},
members,
room: {
name: room.name,
id: room._id,
},
};
const team = Promise.await(Team.create(this.userId, teamData));
return API.v1.success({ team });
},
},
);