apps/meteor/server/models/raw/VideoConference.ts
import type {
VideoConference,
IGroupVideoConference,
ILivechatVideoConference,
IUser,
IRoom,
RocketChatRecordDeleted,
} from '@rocket.chat/core-typings';
import { VideoConferenceStatus } from '@rocket.chat/core-typings';
import type { FindPaginated, InsertionModel, IVideoConferenceModel } from '@rocket.chat/model-typings';
import type { FindCursor, UpdateOptions, UpdateFilter, UpdateResult, IndexDescription, Collection, Db, FindOptions } from 'mongodb';
import { BaseRaw } from './BaseRaw';
export class VideoConferenceRaw extends BaseRaw<VideoConference> implements IVideoConferenceModel {
constructor(db: Db, trash?: Collection<RocketChatRecordDeleted<VideoConference>>) {
super(db, 'video_conference', trash);
}
protected modelIndexes(): IndexDescription[] {
return [
{ key: { rid: 1, createdAt: 1 }, unique: false },
{ key: { type: 1, status: 1 }, unique: false },
{ key: { discussionRid: 1 }, unique: false },
];
}
public findPaginatedByRoomId(
rid: IRoom['_id'],
{ offset, count }: { offset?: number; count?: number } = {},
): FindPaginated<FindCursor<VideoConference>> {
return this.findPaginated(
{ rid },
{
sort: { createdAt: -1 },
skip: offset,
limit: count,
projection: {
providerData: 0,
},
},
);
}
public async findAllLongRunning(minDate: Date): Promise<FindCursor<Pick<VideoConference, '_id'>>> {
return this.find(
{
createdAt: {
$lte: minDate,
},
endedAt: {
$exists: false,
},
},
{
projection: {
_id: 1,
},
},
);
}
public async countByTypeAndStatus(
type: VideoConference['type'],
status: VideoConferenceStatus,
options: FindOptions<VideoConference>,
): Promise<number> {
return this.find(
{
type,
status,
},
options,
).count();
}
public async createDirect({
providerName,
...callDetails
}: Pick<VideoConference, 'rid' | 'createdBy' | 'providerName'>): Promise<string> {
const call: InsertionModel<VideoConference> = {
type: 'direct',
users: [],
messages: {},
status: VideoConferenceStatus.CALLING,
createdAt: new Date(),
providerName: providerName.toLowerCase(),
ringing: true,
...callDetails,
};
return (await this.insertOne(call)).insertedId;
}
public async createGroup({
providerName,
...callDetails
}: Required<Pick<IGroupVideoConference, 'rid' | 'title' | 'createdBy' | 'providerName' | 'ringing'>>): Promise<string> {
const call: InsertionModel<IGroupVideoConference> = {
type: 'videoconference',
users: [],
messages: {},
status: VideoConferenceStatus.STARTED,
anonymousUsers: 0,
createdAt: new Date(),
providerName: providerName.toLowerCase(),
...callDetails,
};
return (await this.insertOne(call)).insertedId;
}
public async createLivechat({
providerName,
...callDetails
}: Required<Pick<ILivechatVideoConference, 'rid' | 'createdBy' | 'providerName'>>): Promise<string> {
const call: InsertionModel<ILivechatVideoConference> = {
type: 'livechat',
users: [],
messages: {},
status: VideoConferenceStatus.STARTED,
createdAt: new Date(),
providerName: providerName.toLowerCase(),
...callDetails,
};
return (await this.insertOne(call)).insertedId;
}
public updateOneById(
_id: string,
update: UpdateFilter<VideoConference> | Partial<VideoConference>,
options?: UpdateOptions,
): Promise<UpdateResult> {
return this.updateOne({ _id }, update, options);
}
public async setEndedById(callId: string, endedBy?: { _id: string; name: string; username: string }, endedAt?: Date): Promise<void> {
await this.updateOneById(callId, {
$set: {
endedBy,
endedAt: endedAt || new Date(),
},
});
}
public async setDataById(callId: string, data: Partial<Omit<VideoConference, '_id'>>): Promise<void> {
await this.updateOneById(callId, {
$set: data,
});
}
public async setRingingById(callId: string, ringing: boolean): Promise<void> {
await this.updateOneById(callId, {
$set: {
ringing,
},
});
}
public async setStatusById(callId: string, status: VideoConference['status']): Promise<void> {
await this.updateOneById(callId, {
$set: {
status,
},
});
}
public async setUrlById(callId: string, url: string): Promise<void> {
await this.updateOneById(callId, {
$set: {
url,
},
});
}
public async setProviderDataById(callId: string, providerData: Record<string, any> | undefined): Promise<void> {
await this.updateOneById(callId, {
...(providerData
? {
$set: {
providerData,
},
}
: {
$unset: {
providerData: 1 as const,
},
}),
});
}
public async addUserById(
callId: string,
user: Required<Pick<IUser, '_id' | 'name' | 'username' | 'avatarETag'>> & { ts?: Date },
): Promise<void> {
await this.updateOneById(callId, {
$addToSet: {
users: {
_id: user._id,
username: user.username,
name: user.name,
avatarETag: user.avatarETag,
ts: user.ts || new Date(),
},
},
});
}
public async setMessageById(callId: string, messageType: keyof VideoConference['messages'], messageId: string): Promise<void> {
await this.updateOneById(callId, {
$set: {
[`messages.${messageType}`]: messageId,
},
} as UpdateFilter<VideoConference>); // TODO: Remove this cast when TypeScript is updated
// TypeScript is not smart enough to infer that `messages.${'start' | 'end'}` matches two keys of `VideoConference`
}
public async updateUserReferences(userId: IUser['_id'], username: IUser['username'], name: IUser['name']): Promise<void> {
await this.updateMany(
{
'users._id': userId,
},
{
$set: {
'users.$.name': name,
'users.$.username': username,
},
},
);
await this.updateMany(
{
'createdBy._id': userId,
},
{
$set: {
'createdBy.name': name,
'createdBy.username': username,
},
},
);
await this.updateMany(
{
'endedBy._id': userId,
},
{
$set: {
'endedBy.name': name,
'endedBy.username': username,
},
},
);
}
public async increaseAnonymousCount(callId: IGroupVideoConference['_id']): Promise<void> {
await this.updateOne(
{ _id: callId },
{
$inc: {
anonymousUsers: 1,
},
},
);
}
public async setDiscussionRidById(callId: string, discussionRid: IRoom['_id']): Promise<void> {
await this.updateOne({ _id: callId }, { $set: { discussionRid } });
}
public async unsetDiscussionRidById(callId: string): Promise<void> {
await this.updateOne({ _id: callId }, { $unset: { discussionRid: true } });
}
public async unsetDiscussionRid(discussionRid: IRoom['_id']): Promise<void> {
await this.updateMany(
{
discussionRid,
},
{
$unset: {
discussionRid: 1,
},
},
);
}
}