- 
                Notifications
    You must be signed in to change notification settings 
- Fork 12.4k
chore: initiate call history with internal calls #37316
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feat/voip-room-messages
Are you sure you want to change the base?
Changes from all commits
0489d42
              b1fb6b7
              616f4a7
              0a419d2
              52d33ac
              9fc98dc
              8c18b98
              File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,11 +1,13 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { api, ServiceClassInternal, type IMediaCallService, Authorization } from '@rocket.chat/core-services'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { IUser } from '@rocket.chat/core-typings'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { IMediaCall, IUser, IRoom, IInternalMediaCallHistoryItem, CallHistoryItemState } from '@rocket.chat/core-typings'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Logger } from '@rocket.chat/logger'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { callServer, type IMediaCallServerSettings } from '@rocket.chat/media-calls'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { isClientMediaSignal, type ClientMediaSignal, type ServerMediaSignal } from '@rocket.chat/media-signaling'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { MediaCalls } from '@rocket.chat/models'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { InsertionModel } from '@rocket.chat/model-typings'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { CallHistory, MediaCalls, Rooms, Users } from '@rocket.chat/models'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { settings } from '../../../app/settings/server'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { createDirectMessage } from '../../methods/createDirectMessage'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const logger = new Logger('media-call service'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | @@ -16,6 +18,7 @@ export class MediaCallService extends ServiceClassInternal implements IMediaCall | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| super(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| callServer.emitter.on('signalRequest', ({ toUid, signal }) => this.sendSignal(toUid, signal)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| callServer.emitter.on('callUpdated', (params) => api.broadcast('media-call.updated', params)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| callServer.emitter.on('historyUpdate', ({ callId }) => setImmediate(() => this.saveCallToHistory(callId))); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.onEvent('media-call.updated', (params) => callServer.receiveCallUpdate(params)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.onEvent('watch.settings', async ({ setting }): Promise<void> => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | @@ -62,6 +65,123 @@ export class MediaCallService extends ServiceClassInternal implements IMediaCall | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private async saveCallToHistory(callId: IMediaCall['_id']): Promise<void> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logger.info({ msg: 'saving media call to history', callId }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const call = await MediaCalls.findOneById(callId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!call) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logger.warn({ msg: 'Attempt to save an invalid call to history', callId }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!call.ended) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logger.warn({ msg: 'Attempt to save a pending call to history', callId }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // TODO: save external media calls to history | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (call.uids.length !== 2) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return this.saveInternalCallToHistory(call); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private async saveInternalCallToHistory(call: IMediaCall): Promise<void> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (call.caller.type !== 'user' || call.callee.type !== 'user') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logger.warn({ msg: 'Attempt to save an internal call history with a call that is not internal', callId: call._id }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const rid = await this.getRoomIdForInternalCall(call).catch(() => undefined); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const state = this.getCallHistoryItemState(call); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const duration = this.getCallDuration(call); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const sharedData: Omit<InsertionModel<IInternalMediaCallHistoryItem>, 'uid' | 'direction' | 'contactId'> = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ts: call.createdAt, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| callId: call._id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| state, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: 'media-call', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| duration, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| endedAt: call.endedAt || new Date(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| external: false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...(rid && { rid }), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 
      Comment on lines
    
      +95
     to 
      +108
    
   There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do not swallow DM resolution errors 
 -		const rid = await this.getRoomIdForInternalCall(call).catch(() => undefined);
+		let rid: IRoom['_id'] | undefined;
+		try {
+			rid = await this.getRoomIdForInternalCall(call);
+		} catch (error) {
+			logger.error({ msg: 'Failed to resolve DM room for internal call history', callId: call._id, error });
+			throw error;
+		}🤖 Prompt for AI Agents | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await Promise.allSettled([ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CallHistory.insertOne({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...sharedData, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uid: call.caller.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| direction: 'outbound', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| contactId: call.callee.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CallHistory.insertOne({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...sharedData, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uid: call.callee.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| direction: 'inbound', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| contactId: call.caller.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 
      Comment on lines
    
      +110
     to 
      +123
    
   There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Surface CallHistory insertion failures 
 -		await Promise.allSettled([
-			CallHistory.insertOne({
-				...sharedData,
-				uid: call.caller.id,
-				direction: 'outbound',
-				contactId: call.callee.id,
-			}),
-			CallHistory.insertOne({
-				...sharedData,
-				uid: call.callee.id,
-				direction: 'inbound',
-				contactId: call.caller.id,
-			}),
-		]);
+		const results = await Promise.allSettled([
+			CallHistory.insertOne({
+				...sharedData,
+				uid: call.caller.id,
+				direction: 'outbound',
+				contactId: call.callee.id,
+			}),
+			CallHistory.insertOne({
+				...sharedData,
+				uid: call.callee.id,
+				direction: 'inbound',
+				contactId: call.caller.id,
+			}),
+		]);
+
+		results.forEach((result) => {
+			if (result.status === 'rejected') {
+				logger.error({ msg: 'Failed to insert call history entry', callId: call._id, error: result.reason });
+				throw result.reason;
+			}
+		});📝 Committable suggestion
 
        Suggested change
       
 🤖 Prompt for AI Agents | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // TODO: If there's a `rid`, send a message in that room - planned for 7.13 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private getCallDuration(call: IMediaCall): number { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { activatedAt, endedAt = new Date() } = call; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!activatedAt) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const diff = endedAt.valueOf() - activatedAt.valueOf(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Math.floor(diff / 1000); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private getCallHistoryItemState(call: IMediaCall): CallHistoryItemState { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (call.transferredBy) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return 'transferred'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (call.hangupReason?.includes('error')) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!call.activatedAt) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return 'failed'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return 'error'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!call.acceptedAt) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return 'not-answered'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!call.activatedAt) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return 'failed'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return 'ended'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private async getRoomIdForInternalCall(call: IMediaCall): Promise<IRoom['_id']> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const room = await Rooms.findOneDirectRoomContainingAllUserIDs(call.uids, { projection: { _id: 1 } }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (room) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return room._id; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const requesterId = call.createdBy.type === 'user' && call.createdBy.id; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const callerId = call.caller.type === 'user' && call.caller.id; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const dmCreatorId = requesterId || callerId || call.uids[0]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const usernames = (await Users.findByIds(call.uids, { projection: { username: 1 } }).toArray()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .map(({ username }) => username) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .filter((username) => username); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (usernames.length !== 2) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error('Invalid usernames for DM.'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const newRoom = await createDirectMessage(usernames, dmCreatorId, true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return newRoom.rid; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private async sendSignal(toUid: IUser['_id'], signal: ServerMediaSignal): Promise<void> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void api.broadcast('user.media-signal', { userId: toUid, signal }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|  | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import type { IRocketChatRecord } from './IRocketChatRecord'; | ||
| import type { IRoom } from './IRoom'; | ||
| import type { IUser } from './IUser'; | ||
|  | ||
| export type CallHistoryItemState = | ||
| /** One of the users ended the call */ | ||
| | 'ended' | ||
| /** Call was not answered */ | ||
| | 'not-answered' | ||
| /** The call could not be established */ | ||
| | 'failed' | ||
| /** The call was established, but it ended due to an error */ | ||
| | 'error' | ||
| /** The call ended due to a transfer */ | ||
| | 'transferred'; | ||
|  | ||
| interface ICallHistoryItem extends IRocketChatRecord { | ||
| uid: IUser['_id']; | ||
| ts: Date; | ||
|  | ||
| callId: string; | ||
|  | ||
| direction: 'inbound' | 'outbound'; | ||
| state: CallHistoryItemState; | ||
| } | ||
|  | ||
| interface IMediaCallHistoryItem extends ICallHistoryItem { | ||
| type: 'media-call'; | ||
| external: boolean; | ||
|  | ||
| /* The call's duration, in seconds */ | ||
| duration: number; | ||
| endedAt: Date; | ||
| } | ||
|  | ||
| export interface IInternalMediaCallHistoryItem extends IMediaCallHistoryItem { | ||
| external: false; | ||
| contactId: IUser['_id']; | ||
|  | ||
| rid?: IRoom['_id']; | ||
| } | ||
|  | ||
| // TODO: IExternalMediaCallHistoryItem, planned for 8.0 | ||
| // TODO: IVideoConfHistoryItem, expected in the future but not yet on the roadmap | ||
|  | ||
| export type CallHistoryItem = IInternalMediaCallHistoryItem; | 
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| import type { CallHistoryItem } from '@rocket.chat/core-typings'; | ||
|  | ||
| import type { IBaseModel } from './IBaseModel'; | ||
|  | ||
| export type ICallHistoryModel = IBaseModel<CallHistoryItem>; | 
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import type { CallHistoryItem } from '@rocket.chat/core-typings'; | ||
| import type { ICallHistoryModel } from '@rocket.chat/model-typings'; | ||
| import type { Db, IndexDescription } from 'mongodb'; | ||
|  | ||
| import { BaseRaw } from './BaseRaw'; | ||
|  | ||
| export class CallHistoryRaw extends BaseRaw<CallHistoryItem> implements ICallHistoryModel { | ||
| constructor(db: Db) { | ||
| super(db, 'call_history'); | ||
| } | ||
|  | ||
| protected modelIndexes(): IndexDescription[] { | ||
| return [ | ||
| { key: { uid: 1, callId: 1 }, unique: true }, | ||
| { key: { uid: 1, ts: -1 }, unique: false }, | ||
| ]; | ||
| } | ||
| } | 
Uh oh!
There was an error while loading. Please reload this page.