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:
33
apps/meteor/server/services/room/getNameForDMs.ts
Normal file
33
apps/meteor/server/services/room/getNameForDMs.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { AtLeast, IUser } from '@rocket.chat/core-typings';
|
||||
|
||||
const getFname = (members: AtLeast<IUser, 'name' | 'username'>[]): string | undefined => {
|
||||
if (members.length === 0) {
|
||||
return;
|
||||
}
|
||||
return members.map(({ name, username }) => name || username).join(', ');
|
||||
};
|
||||
const getName = (members: AtLeast<IUser, 'name' | 'username'>[]): string | undefined => {
|
||||
if (members.length === 0) {
|
||||
return;
|
||||
}
|
||||
return members.map(({ username }) => username).join(', ');
|
||||
};
|
||||
|
||||
type NameMap = { [userId: string]: { fname: string; name: string } };
|
||||
|
||||
export function getNameForDMs(members: AtLeast<IUser, '_id' | 'name' | 'username'>[]): NameMap {
|
||||
const nameMap: NameMap = {};
|
||||
|
||||
const sortedMembers = members.sort((u1, u2) => (u1.name! || u1.username!).localeCompare(u2.name! || u2.username!));
|
||||
|
||||
for (const member of sortedMembers) {
|
||||
const otherMembers = sortedMembers.filter((m) => m._id !== member._id);
|
||||
|
||||
nameMap[member._id] = {
|
||||
fname: getFname(otherMembers) || member.name || member.username || '',
|
||||
name: getName(otherMembers) || member.username || '',
|
||||
};
|
||||
}
|
||||
|
||||
return nameMap;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { isRoomFederated, isRoomNativeFederated } from '@rocket.chat/core-typings';
|
||||
import type { IRoomNativeFederated, IRoom } from '@rocket.chat/core-typings';
|
||||
|
||||
import { throwIfFederationNotEnabled } from '../../federation/utils';
|
||||
|
||||
export class FederationActions {
|
||||
public static shouldPerformFederationAction(room: IRoom): room is IRoomNativeFederated {
|
||||
if (!isRoomFederated(room)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isRoomNativeFederated(room)) {
|
||||
throw new Error('Room is federated but its not native implementation');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static blockIfRoomFederatedButServiceNotReady(room: IRoom) {
|
||||
if (!isRoomFederated(room)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isRoomNativeFederated(room)) {
|
||||
throw new Error('Room is federated but its not native implementation');
|
||||
}
|
||||
|
||||
throwIfFederationNotEnabled();
|
||||
}
|
||||
}
|
||||
394
apps/meteor/server/services/room/service.ts
Normal file
394
apps/meteor/server/services/room/service.ts
Normal file
@@ -0,0 +1,394 @@
|
||||
import { ServiceClassInternal, Authorization, Message, MeteorError, FederationMatrix } from '@rocket.chat/core-services';
|
||||
import type { ICreateRoomParams, IRoomService } from '@rocket.chat/core-services';
|
||||
import {
|
||||
type AtLeast,
|
||||
type IRoom,
|
||||
type IUser,
|
||||
type MessageTypesValues,
|
||||
type ISubscription,
|
||||
isOmnichannelRoom,
|
||||
isRoomWithJoinCode,
|
||||
} from '@rocket.chat/core-typings';
|
||||
import { isUserNativeFederated } from '@rocket.chat/core-typings';
|
||||
import { Rooms, Subscriptions, Users } from '@rocket.chat/models';
|
||||
|
||||
import { getNameForDMs } from './getNameForDMs';
|
||||
import { FederationActions } from './hooks/BeforeFederationActions';
|
||||
import { RoomMemberActions } from '../../../definition/IRoomTypeConfig';
|
||||
import { getSubscriptionAutotranslateDefaultConfig } from '../../lib/getSubscriptionAutotranslateDefaultConfig';
|
||||
import { readThread } from '../../lib/messaging/threads/functions';
|
||||
import {
|
||||
notifyOnSubscriptionChanged,
|
||||
notifyOnSubscriptionChangedById,
|
||||
notifyOnSubscriptionChangedByRoomIdAndUserId,
|
||||
} from '../../lib/notifyListener';
|
||||
import { readMessages } from '../../lib/readMessages';
|
||||
import { performAcceptRoomInvite } from '../../lib/rooms/acceptRoomInvite';
|
||||
import { addUserToRoom } from '../../lib/rooms/addUserToRoom';
|
||||
import { performUserBan } from '../../lib/rooms/banUserFromRoom';
|
||||
import { createRoom } from '../../lib/rooms/createRoom'; // TODO remove this import
|
||||
import { executeUnbanUserFromRoom } from '../../lib/rooms/executeUnbanUserFromRoom';
|
||||
import { removeUserFromRoom, performUserRemoval } from '../../lib/rooms/removeUserFromRoom';
|
||||
import { roomCoordinator } from '../../lib/rooms/roomCoordinator';
|
||||
import { saveRoomName } from '../../lib/rooms/settings';
|
||||
import { saveRoomTopic } from '../../lib/rooms/settings/saveRoomTopic';
|
||||
import { getDefaultSubscriptionPref } from '../../lib/utils/lib/getDefaultSubscriptionPref';
|
||||
import { getValidRoomName } from '../../lib/utils/lib/getValidRoomName';
|
||||
import { createDirectMessage } from '../../meteor-methods/messages/createDirectMessage';
|
||||
import { addRoomLeader } from '../../meteor-methods/rooms/addRoomLeader';
|
||||
import { addRoomModerator } from '../../meteor-methods/rooms/addRoomModerator';
|
||||
import { addRoomOwner } from '../../meteor-methods/rooms/addRoomOwner';
|
||||
import { removeRoomLeader } from '../../meteor-methods/rooms/removeRoomLeader';
|
||||
import { removeRoomModerator } from '../../meteor-methods/rooms/removeRoomModerator';
|
||||
import { removeRoomOwner } from '../../meteor-methods/rooms/removeRoomOwner';
|
||||
|
||||
export class RoomService extends ServiceClassInternal implements IRoomService {
|
||||
protected name = 'room';
|
||||
|
||||
async updateDirectMessageRoomName(
|
||||
room: IRoom,
|
||||
ignoreStatusFromSubs?: string[],
|
||||
updatedNames?: AtLeast<IUser, '_id' | 'name' | 'username'>[],
|
||||
): Promise<boolean> {
|
||||
if (room.t !== 'd') {
|
||||
throw new Error('Invalid room type');
|
||||
}
|
||||
const subs = await Subscriptions.findByRoomId(room._id, { projection: { u: 1, status: 1 } }).toArray();
|
||||
|
||||
const uids = subs.map((sub) => sub.u._id).filter((uid) => !updatedNames?.some((user) => user._id === uid));
|
||||
|
||||
const roomMembers = await Users.findUsersByIds(uids, { projection: { name: 1, username: 1 } }).toArray();
|
||||
|
||||
const roomNames = getNameForDMs([...roomMembers, ...(updatedNames ?? [])]);
|
||||
|
||||
for await (const sub of subs) {
|
||||
// don't update the name if the user is invited but hasn't accepted yet
|
||||
if (!ignoreStatusFromSubs?.includes(sub._id) && sub.status === 'INVITED') {
|
||||
continue;
|
||||
}
|
||||
await Subscriptions.updateOne({ _id: sub._id }, { $set: roomNames[sub.u._id] });
|
||||
|
||||
void notifyOnSubscriptionChangedByRoomIdAndUserId(room._id, sub.u._id, 'updated');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async create(uid: string, params: ICreateRoomParams): Promise<IRoom> {
|
||||
const { type, name, members = [], readOnly, extraData, options } = params;
|
||||
|
||||
const hasPermission = await Authorization.hasPermission(uid, `create-${type}`);
|
||||
if (!hasPermission) {
|
||||
throw new Error('no-permission');
|
||||
}
|
||||
|
||||
const user = await Users.findOneById(uid);
|
||||
if (!user?.username) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
// TODO convert `createRoom` function to "raw" and move to here
|
||||
return createRoom(type, name, user, members, false, readOnly, extraData, options) as unknown as IRoom;
|
||||
}
|
||||
|
||||
async createDirectMessage({ to, from }: { to: string; from: string }): Promise<{ rid: string }> {
|
||||
const [toUser, fromUser] = await Promise.all([
|
||||
Users.findOneById(to, { projection: { username: 1 } }),
|
||||
Users.findOneById(from, { projection: { _id: 1 } }),
|
||||
]);
|
||||
|
||||
if (!toUser?.username || !fromUser) {
|
||||
throw new Error('error-invalid-user');
|
||||
}
|
||||
return this.createDirectMessageWithMultipleUsers([toUser.username], fromUser._id);
|
||||
}
|
||||
|
||||
async createDirectMessageWithMultipleUsers(members: string[], creatorId: string): Promise<{ rid: string }> {
|
||||
return createDirectMessage(members, creatorId);
|
||||
}
|
||||
|
||||
async addMember(uid: string, rid: string): Promise<boolean> {
|
||||
const hasPermission = await Authorization.hasPermission(uid, 'add-user-to-joined-room', rid);
|
||||
if (!hasPermission) {
|
||||
throw new Error('no-permission');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async addUserToRoom(
|
||||
roomId: string,
|
||||
user: Pick<IUser, '_id'>,
|
||||
inviter?: Pick<IUser, '_id' | 'username'>,
|
||||
options?: {
|
||||
skipSystemMessage?: boolean;
|
||||
skipAlertSound?: boolean;
|
||||
},
|
||||
): Promise<boolean | undefined> {
|
||||
return addUserToRoom(roomId, user, inviter, options);
|
||||
}
|
||||
|
||||
async removeUserFromRoom(
|
||||
roomId: string,
|
||||
user: IUser,
|
||||
options?: { byUser?: IUser; skipAppPreEvents?: boolean; customSystemMessage?: MessageTypesValues },
|
||||
): Promise<void> {
|
||||
return removeUserFromRoom(roomId, user, options);
|
||||
}
|
||||
|
||||
async performUserRemoval(room: IRoom, user: IUser, options?: { byUser?: IUser }): Promise<void> {
|
||||
return performUserRemoval(room, user, options);
|
||||
}
|
||||
|
||||
async performUserBan(room: IRoom, user: IUser, byUser: IUser): Promise<void> {
|
||||
return performUserBan(room, user, byUser);
|
||||
}
|
||||
|
||||
async performUserUnban(room: IRoom, user: IUser, byUser: IUser): Promise<void> {
|
||||
return executeUnbanUserFromRoom(room._id, user, byUser);
|
||||
}
|
||||
|
||||
async performAcceptRoomInvite(room: IRoom, subscription: ISubscription, user: IUser & { username: string }): Promise<void> {
|
||||
return performAcceptRoomInvite(room, subscription, user);
|
||||
}
|
||||
|
||||
async revokeInvite(room: IRoom, user: IUser): Promise<void> {
|
||||
const subscription = await Subscriptions.removeInvitedByRoomIdAndUserId(room._id, user._id);
|
||||
if (!subscription) {
|
||||
return;
|
||||
}
|
||||
|
||||
void notifyOnSubscriptionChanged(subscription, 'removed');
|
||||
}
|
||||
|
||||
async getValidRoomName(displayName: string, roomId = '', options: { allowDuplicates?: boolean } = {}): Promise<string> {
|
||||
return getValidRoomName(displayName, roomId, options);
|
||||
}
|
||||
|
||||
async saveRoomTopic(
|
||||
roomId: string,
|
||||
roomTopic: string | undefined,
|
||||
user: Pick<IUser, 'username' | '_id' | 'federation' | 'federated'>,
|
||||
sendMessage = true,
|
||||
): Promise<void> {
|
||||
await saveRoomTopic(roomId, roomTopic, user, sendMessage);
|
||||
}
|
||||
|
||||
async getRouteLink(room: AtLeast<IRoom, '_id' | 't' | 'name'>): Promise<string | boolean> {
|
||||
return roomCoordinator.getRouteLink(room.t as string, { rid: room._id, name: room.name });
|
||||
}
|
||||
|
||||
/**
|
||||
* Method called by users to join a room.
|
||||
*/
|
||||
async join({ room, user, joinCode }: { room: IRoom; user: IUser; joinCode?: string }) {
|
||||
if (!(await roomCoordinator.getRoomDirectives(room.t)?.allowMemberAction(room, RoomMemberActions.JOIN, user._id))) {
|
||||
throw new MeteorError('error-not-allowed', 'Not allowed', { method: 'joinRoom' });
|
||||
}
|
||||
|
||||
if (isOmnichannelRoom(room) && !room.open) {
|
||||
throw new MeteorError('room-closed', 'Room is closed', { method: 'joinRoom' });
|
||||
}
|
||||
|
||||
if (!(await Authorization.canAccessRoom(room, user))) {
|
||||
throw new MeteorError('error-not-allowed', 'Not allowed', { method: 'joinRoom' });
|
||||
}
|
||||
|
||||
if (
|
||||
FederationActions.shouldPerformFederationAction(room) &&
|
||||
!isUserNativeFederated(user) &&
|
||||
!(await FederationMatrix.canUserAccessFederation(user))
|
||||
) {
|
||||
throw new MeteorError('error-not-authorized-federation', 'Not authorized to access federation', { method: 'joinRoom' });
|
||||
}
|
||||
|
||||
if (isRoomWithJoinCode(room) && !(await Authorization.hasPermission(user._id, 'join-without-join-code'))) {
|
||||
if (!joinCode) {
|
||||
throw new MeteorError('error-code-required', 'Code required', { method: 'joinRoom' });
|
||||
}
|
||||
|
||||
const isCorrectJoinCode = !!(await Rooms.findOneByJoinCodeAndId(joinCode, room._id, {
|
||||
projection: { _id: 1 },
|
||||
}));
|
||||
|
||||
if (!isCorrectJoinCode) {
|
||||
throw new MeteorError('error-code-invalid', 'Invalid code', { method: 'joinRoom' });
|
||||
}
|
||||
}
|
||||
|
||||
return addUserToRoom(room._id, user);
|
||||
}
|
||||
|
||||
async beforeLeave(room: IRoom): Promise<void> {
|
||||
FederationActions.blockIfRoomFederatedButServiceNotReady(room);
|
||||
}
|
||||
|
||||
async beforeUserRemoved(room: IRoom): Promise<void> {
|
||||
FederationActions.blockIfRoomFederatedButServiceNotReady(room);
|
||||
}
|
||||
|
||||
async beforeNameChange(room: IRoom): Promise<void> {
|
||||
FederationActions.blockIfRoomFederatedButServiceNotReady(room);
|
||||
}
|
||||
|
||||
async beforeTopicChange(room: IRoom): Promise<void> {
|
||||
FederationActions.blockIfRoomFederatedButServiceNotReady(room);
|
||||
}
|
||||
|
||||
async saveRoomName(roomId: string, userId: string, name: string) {
|
||||
const user = await Users.findOneById(userId);
|
||||
if (!user) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
await saveRoomName(roomId, name, user);
|
||||
}
|
||||
|
||||
public async addUserRoleRoomScoped(
|
||||
fromUserId: string,
|
||||
userId: string,
|
||||
roomId: string,
|
||||
role: 'moderator' | 'owner' | 'leader' | 'user',
|
||||
): Promise<void> {
|
||||
if (role === 'moderator') {
|
||||
await addRoomModerator(fromUserId, roomId, userId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (role === 'owner') {
|
||||
await addRoomOwner(fromUserId, roomId, userId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (role === 'leader') {
|
||||
await addRoomLeader(fromUserId, roomId, userId);
|
||||
return;
|
||||
}
|
||||
|
||||
const sub = await Subscriptions.findByUserIdAndRoomIds(userId, [roomId], { projection: { roles: 1 } }).next();
|
||||
if (!sub) {
|
||||
throw new Error('user and room subsciption not found');
|
||||
}
|
||||
|
||||
if (!sub.roles) {
|
||||
return; // 'user' role essentially
|
||||
}
|
||||
|
||||
for await (const currentRole of sub.roles) {
|
||||
if (currentRole === 'owner') {
|
||||
await removeRoomOwner(fromUserId, roomId, userId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentRole === 'leader') {
|
||||
await removeRoomLeader(fromUserId, roomId, userId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentRole === 'moderator') {
|
||||
await removeRoomModerator(fromUserId, roomId, userId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async createUserSubscription({
|
||||
room,
|
||||
ts,
|
||||
userToBeAdded,
|
||||
inviter,
|
||||
createAsHidden = false,
|
||||
skipAlertSound = false,
|
||||
skipSystemMessage = false,
|
||||
status,
|
||||
roles,
|
||||
}: {
|
||||
room: IRoom;
|
||||
ts: Date;
|
||||
userToBeAdded: IUser;
|
||||
inviter?: Pick<IUser, '_id' | 'username' | 'name'>;
|
||||
createAsHidden?: boolean;
|
||||
skipAlertSound?: boolean;
|
||||
skipSystemMessage?: boolean;
|
||||
status?: 'INVITED';
|
||||
roles?: ISubscription['roles'];
|
||||
}): Promise<string | undefined> {
|
||||
const autoTranslateConfig = getSubscriptionAutotranslateDefaultConfig(userToBeAdded);
|
||||
|
||||
const { insertedId } = await Subscriptions.createWithRoomAndUser(room, userToBeAdded, {
|
||||
ts,
|
||||
open: !createAsHidden,
|
||||
alert: createAsHidden ? false : !skipAlertSound,
|
||||
unread: 1,
|
||||
userMentions: 1,
|
||||
groupMentions: 0,
|
||||
...(roles && { roles }),
|
||||
...(status && { status }),
|
||||
...(inviter && { inviter: { _id: inviter._id, username: inviter.username!, ...(inviter.name && { name: inviter.name }) } }),
|
||||
...autoTranslateConfig,
|
||||
...getDefaultSubscriptionPref(userToBeAdded),
|
||||
// `name` is optional for users, so fall back to the username like `getNameForDMs` does
|
||||
...(room.t === 'd' && inviter && { fname: inviter.name || inviter.username, name: inviter.username }),
|
||||
});
|
||||
|
||||
if (insertedId) {
|
||||
void notifyOnSubscriptionChangedById(insertedId, 'inserted');
|
||||
}
|
||||
|
||||
if (!skipSystemMessage && userToBeAdded.username) {
|
||||
if (inviter) {
|
||||
const extraData = {
|
||||
ts,
|
||||
u: {
|
||||
_id: inviter._id,
|
||||
username: inviter.username,
|
||||
},
|
||||
};
|
||||
if (room.teamMain) {
|
||||
await Message.saveSystemMessage('added-user-to-team', room._id, userToBeAdded.username, userToBeAdded, extraData);
|
||||
} else if (status === 'INVITED') {
|
||||
await Message.saveSystemMessage('ui', room._id, userToBeAdded.username, userToBeAdded, {
|
||||
u: { _id: inviter._id, username: inviter.username },
|
||||
});
|
||||
} else {
|
||||
await Message.saveSystemMessage('au', room._id, userToBeAdded.username, userToBeAdded, extraData);
|
||||
}
|
||||
} else if (room.prid) {
|
||||
await Message.saveSystemMessage('ut', room._id, userToBeAdded.username, userToBeAdded, { ts });
|
||||
} else if (room.teamMain) {
|
||||
await Message.saveSystemMessage('ujt', room._id, userToBeAdded.username, userToBeAdded, { ts });
|
||||
} else {
|
||||
await Message.saveSystemMessage('uj', room._id, userToBeAdded.username, userToBeAdded, { ts });
|
||||
}
|
||||
}
|
||||
|
||||
return insertedId;
|
||||
}
|
||||
|
||||
async markAsRead(room: IRoom, userId: string, readThreads = false): Promise<void> {
|
||||
await readMessages(room, userId, readThreads);
|
||||
}
|
||||
|
||||
async readThread({ user, room, tmid }: { user: IUser; room: IRoom; tmid: string }): Promise<void> {
|
||||
await readThread({
|
||||
user,
|
||||
room,
|
||||
tmid,
|
||||
});
|
||||
}
|
||||
|
||||
async unbanAndInviteUser(
|
||||
subscription: ISubscription,
|
||||
inviteeUser: Pick<IUser, '_id' | 'username' | 'name'>,
|
||||
inviterUser: Required<Pick<IUser, '_id' | 'username'>> & Pick<IUser, 'name'>,
|
||||
): Promise<void> {
|
||||
await Subscriptions.unbanToInvitedById(subscription._id, inviterUser);
|
||||
|
||||
void notifyOnSubscriptionChangedById(subscription._id, 'updated');
|
||||
|
||||
if (inviteeUser?.username) {
|
||||
await Message.saveSystemMessage('ui', subscription.rid, inviteeUser.username, inviteeUser, {
|
||||
u: { _id: inviterUser._id, username: inviterUser.username },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user