snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
This commit is contained in:
277
apps/meteor/server/meteor-methods/messages/createDiscussion.ts
Normal file
277
apps/meteor/server/meteor-methods/messages/createDiscussion.ts
Normal file
@@ -0,0 +1,277 @@
|
||||
import { Message } from '@rocket.chat/core-services';
|
||||
import type { IMessage, IRoom, IUser, MessageAttachmentDefault } from '@rocket.chat/core-typings';
|
||||
import type { ServerMethods } from '@rocket.chat/ddp-client';
|
||||
import { Messages, Rooms, Users } from '@rocket.chat/models';
|
||||
import { Random } from '@rocket.chat/random';
|
||||
import { check, Match } from 'meteor/check';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
import { afterSaveMessageAsync } from '../../hooks/messages/afterSaveMessage';
|
||||
import { canSendMessageAsync } from '../../lib/authorization/canSendMessage';
|
||||
import { hasAtLeastOnePermissionAsync } from '../../lib/authorization/hasPermission';
|
||||
import { methodDeprecationLogger } from '../../lib/deprecationWarningLogger';
|
||||
import { i18n } from '../../lib/i18n';
|
||||
import { attachMessage } from '../../lib/messages/attachMessage';
|
||||
import { sendMessage } from '../../lib/messages/sendMessage';
|
||||
import { addUserToRoom } from '../../lib/rooms/addUserToRoom';
|
||||
import { createRoom } from '../../lib/rooms/createRoom';
|
||||
import { roomCoordinator } from '../../lib/rooms/roomCoordinator';
|
||||
import { settings } from '../../settings';
|
||||
|
||||
const getParentRoom = async (rid: IRoom['_id']) => {
|
||||
const room = await Rooms.findOne(rid);
|
||||
return room && (room.prid ? Rooms.findOne(room.prid, { projection: { _id: 1 } }) : room);
|
||||
};
|
||||
|
||||
async function createDiscussionMessage(
|
||||
rid: IRoom['_id'],
|
||||
user: IUser,
|
||||
drid: IRoom['_id'],
|
||||
msg: IMessage['msg'],
|
||||
messageEmbedded?: MessageAttachmentDefault,
|
||||
): Promise<IMessage> {
|
||||
return Message.saveSystemMessage('discussion-created', rid, msg, user, {
|
||||
drid,
|
||||
...(messageEmbedded && { attachments: [messageEmbedded] }),
|
||||
});
|
||||
}
|
||||
|
||||
async function mentionMessage(
|
||||
rid: IRoom['_id'],
|
||||
{ _id, username, name }: Pick<IUser, '_id' | 'username' | 'name'>,
|
||||
messageEmbedded?: MessageAttachmentDefault,
|
||||
) {
|
||||
if (!username) {
|
||||
return null;
|
||||
}
|
||||
await Messages.insertOne({
|
||||
rid,
|
||||
msg: '',
|
||||
u: { _id, username, name },
|
||||
ts: new Date(),
|
||||
_updatedAt: new Date(),
|
||||
...(messageEmbedded && { attachments: [messageEmbedded] }),
|
||||
});
|
||||
}
|
||||
|
||||
type CreateDiscussionProperties = {
|
||||
prid: IRoom['_id'];
|
||||
pmid?: IMessage['_id'];
|
||||
t_name: string;
|
||||
reply?: string;
|
||||
users: Array<Exclude<IUser['username'], undefined>>;
|
||||
user: IUser;
|
||||
encrypted?: boolean;
|
||||
topic?: string;
|
||||
};
|
||||
|
||||
const create = async ({
|
||||
prid,
|
||||
pmid,
|
||||
t_name: discussionName,
|
||||
reply,
|
||||
users,
|
||||
user,
|
||||
encrypted,
|
||||
topic,
|
||||
}: CreateDiscussionProperties): Promise<IRoom & { rid: string }> => {
|
||||
// if you set both, prid and pmid, and the rooms dont match... should throw an error)
|
||||
let message: null | IMessage = null;
|
||||
if (pmid) {
|
||||
message = await Messages.findOneById(pmid);
|
||||
if (!message) {
|
||||
throw new Meteor.Error('error-invalid-message', 'Invalid message', {
|
||||
method: 'DiscussionCreation',
|
||||
});
|
||||
}
|
||||
if (prid) {
|
||||
const parentRoom = await getParentRoom(message.rid);
|
||||
if (prid !== parentRoom?._id) {
|
||||
throw new Meteor.Error('error-invalid-arguments', 'Root message room ID does not match parent room ID ', {
|
||||
method: 'DiscussionCreation',
|
||||
});
|
||||
}
|
||||
} else {
|
||||
prid = message.rid;
|
||||
}
|
||||
}
|
||||
|
||||
if (!prid) {
|
||||
throw new Meteor.Error('error-invalid-arguments', 'Missing parent room ID', { method: 'DiscussionCreation' });
|
||||
}
|
||||
|
||||
let parentRoom;
|
||||
try {
|
||||
parentRoom = await canSendMessageAsync(prid, { uid: user._id, username: user.username, type: user.type });
|
||||
} catch (error) {
|
||||
throw new Meteor.Error((error as Error).message);
|
||||
}
|
||||
|
||||
if (parentRoom.prid) {
|
||||
throw new Meteor.Error('error-nested-discussion', 'Cannot create nested discussions', {
|
||||
method: 'DiscussionCreation',
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof encrypted !== 'boolean') {
|
||||
encrypted = Boolean(parentRoom.encrypted);
|
||||
}
|
||||
|
||||
if (encrypted && reply) {
|
||||
throw new Meteor.Error('error-invalid-arguments', 'Encrypted discussions must not receive an initial reply.', {
|
||||
method: 'DiscussionCreation',
|
||||
});
|
||||
}
|
||||
|
||||
if (pmid) {
|
||||
// No projection: the full room is spread into the returned discussion below.
|
||||
const discussionAlreadyExists = await Rooms.findOne({
|
||||
prid,
|
||||
pmid,
|
||||
});
|
||||
if (discussionAlreadyExists) {
|
||||
// do not allow multiple discussions to the same message'\
|
||||
await addUserToRoom(discussionAlreadyExists._id, user);
|
||||
return { ...discussionAlreadyExists, rid: discussionAlreadyExists._id };
|
||||
}
|
||||
}
|
||||
|
||||
const name = Random.id();
|
||||
|
||||
// auto invite the replied message owner
|
||||
const invitedUsers = message ? [message.u.username, ...users] : users;
|
||||
|
||||
const type = await roomCoordinator.getRoomDirectives(parentRoom.t).getDiscussionType(parentRoom);
|
||||
const description = parentRoom.encrypted ? '' : message?.msg;
|
||||
const discussionTopic = topic || parentRoom.name;
|
||||
|
||||
if (!type) {
|
||||
throw new Meteor.Error('error-invalid-type', 'Cannot define discussion room type', {
|
||||
method: 'DiscussionCreation',
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
type === 'p' &&
|
||||
!encrypted &&
|
||||
settings.get<boolean>('E2E_Enable') &&
|
||||
settings.get<boolean>('E2E_Force_Encryption_For_Private_Rooms')
|
||||
) {
|
||||
throw new Meteor.Error(
|
||||
'error-encrypted-private-rooms-enforced-discussion',
|
||||
'Workspace policy requires all private rooms to be encrypted. To create this discussion, make the parent channel public or enable encryption on it.',
|
||||
{ method: 'DiscussionCreation' },
|
||||
);
|
||||
}
|
||||
|
||||
const discussion = await createRoom(
|
||||
type,
|
||||
name,
|
||||
user,
|
||||
[...new Set(invitedUsers)].filter(Boolean),
|
||||
false,
|
||||
false,
|
||||
{
|
||||
fname: discussionName,
|
||||
description, // TODO discussions remove
|
||||
topic: discussionTopic,
|
||||
prid,
|
||||
encrypted,
|
||||
},
|
||||
{
|
||||
creator: user._id,
|
||||
},
|
||||
);
|
||||
|
||||
let discussionMsg;
|
||||
if (message) {
|
||||
if (parentRoom.encrypted) {
|
||||
message.msg = i18n.t('Encrypted_message');
|
||||
}
|
||||
await mentionMessage(discussion._id, user, attachMessage(message, parentRoom));
|
||||
|
||||
discussionMsg = await createDiscussionMessage(message.rid, user, discussion._id, discussionName, attachMessage(message, parentRoom));
|
||||
} else {
|
||||
discussionMsg = await createDiscussionMessage(prid, user, discussion._id, discussionName);
|
||||
}
|
||||
|
||||
if (reply) {
|
||||
await sendMessage(user, { msg: reply }, discussion);
|
||||
}
|
||||
|
||||
if (discussionMsg) {
|
||||
afterSaveMessageAsync(discussionMsg, parentRoom, user);
|
||||
}
|
||||
|
||||
return discussion;
|
||||
};
|
||||
|
||||
declare module '@rocket.chat/ddp-client' {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
interface ServerMethods {
|
||||
createDiscussion: typeof create;
|
||||
}
|
||||
}
|
||||
|
||||
export const createDiscussion = async (
|
||||
userId: string,
|
||||
{ prid, pmid, t_name: discussionName, reply, users, encrypted, topic }: Omit<CreateDiscussionProperties, 'user'>,
|
||||
): Promise<
|
||||
IRoom & {
|
||||
rid: string;
|
||||
}
|
||||
> => {
|
||||
if (!settings.get('Discussion_enabled')) {
|
||||
throw new Meteor.Error('error-action-not-allowed', 'You are not allowed to create a discussion', { method: 'createDiscussion' });
|
||||
}
|
||||
|
||||
if (!userId) {
|
||||
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
|
||||
method: 'DiscussionCreation',
|
||||
});
|
||||
}
|
||||
|
||||
if (!(await hasAtLeastOnePermissionAsync(userId, ['start-discussion', 'start-discussion-other-user'], prid))) {
|
||||
throw new Meteor.Error('error-action-not-allowed', 'You are not allowed to create a discussion', { method: 'createDiscussion' });
|
||||
}
|
||||
const user = await Users.findOneById(userId, { projection: { services: 0 } });
|
||||
|
||||
if (!user) {
|
||||
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
|
||||
method: 'createDiscussion',
|
||||
});
|
||||
}
|
||||
|
||||
return create({ prid, pmid, t_name: discussionName, reply, users, user, encrypted, topic });
|
||||
};
|
||||
|
||||
Meteor.methods<ServerMethods>({
|
||||
/**
|
||||
* Create discussion by room or message
|
||||
* @constructor
|
||||
* @param {string} prid - Parent Room Id - The room id, optional if you send pmid.
|
||||
* @param {string} pmid - Parent Message Id - Create the discussion by a message, optional.
|
||||
* @param {string} reply - The reply, optional
|
||||
* @param {string} t_name - discussion name
|
||||
* @param {string[]} users - users to be added
|
||||
* @param {boolean} encrypted - if the discussion's e2e encryption should be enabled.
|
||||
*/
|
||||
async createDiscussion({ prid, pmid, t_name: discussionName, reply, users, encrypted }: CreateDiscussionProperties) {
|
||||
methodDeprecationLogger.method('createDiscussion', '9.0.0', '/v1/rooms.createDiscussion');
|
||||
check(prid, Match.Maybe(String));
|
||||
check(pmid, Match.Maybe(String));
|
||||
check(reply, Match.Maybe(String));
|
||||
check(discussionName, String);
|
||||
check(users, [String]);
|
||||
check(encrypted, Match.Maybe(Boolean));
|
||||
|
||||
const uid = Meteor.userId();
|
||||
if (!uid) {
|
||||
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
|
||||
method: 'DiscussionCreation',
|
||||
});
|
||||
}
|
||||
|
||||
return createDiscussion(uid, { prid, pmid, t_name: discussionName, reply, users, encrypted });
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user