diff --git a/packages/apps-engine/deno-runtime/handlers/app/handleOnUpdate.ts b/packages/apps-engine/deno-runtime/handlers/app/handleOnUpdate.ts new file mode 100644 index 000000000000..f21e4f947d5d --- /dev/null +++ b/packages/apps-engine/deno-runtime/handlers/app/handleOnUpdate.ts @@ -0,0 +1,30 @@ +import type { App } from '@rocket.chat/apps-engine/definition/App.ts'; + +import { AppObjectRegistry } from '../../AppObjectRegistry.ts'; +import { AppAccessorsInstance } from '../../lib/accessors/mod.ts'; + +export default async function handleOnUpdate(params: unknown): Promise { + const app = AppObjectRegistry.get('app'); + + if (typeof app?.onUpdate !== 'function') { + throw new Error('App must contain an onUpdate function', { + cause: 'invalid_app', + }); + } + + if (!Array.isArray(params)) { + throw new Error('Invalid params', { cause: 'invalid_param_type' }); + } + + const [context] = params as [Record]; + + await app.onUpdate( + context, + AppAccessorsInstance.getReader(), + AppAccessorsInstance.getHttp(), + AppAccessorsInstance.getPersistence(), + AppAccessorsInstance.getModifier(), + ); + + return true; +} diff --git a/packages/apps-engine/deno-runtime/handlers/app/handler.ts b/packages/apps-engine/deno-runtime/handlers/app/handler.ts index 16144ce6d875..2a44f34cb7fe 100644 --- a/packages/apps-engine/deno-runtime/handlers/app/handler.ts +++ b/packages/apps-engine/deno-runtime/handlers/app/handler.ts @@ -14,6 +14,7 @@ import handleOnSettingUpdated from './handleOnSettingUpdated.ts'; import handleListener from '../listener/handler.ts'; import handleUIKitInteraction, { uikitInteractions } from '../uikit/handler.ts'; import { AppObjectRegistry } from '../../AppObjectRegistry.ts'; +import handleOnUpdate from './handleOnUpdate.ts'; export default async function handleApp(method: string, params: unknown): Promise { const [, appMethod] = method.split(':'); @@ -83,6 +84,9 @@ export default async function handleApp(method: string, params: unknown): Promis case 'onSettingUpdated': result = await handleOnSettingUpdated(params); break; + case 'onUpdate': + result = await handleOnUpdate(params); + break; default: throw new JsonRpcError('Method not found', -32601); } diff --git a/packages/apps-engine/deno-runtime/lib/accessors/builders/VideoConferenceBuilder.ts b/packages/apps-engine/deno-runtime/lib/accessors/builders/VideoConferenceBuilder.ts index f29c6250437c..e617cdddf154 100644 --- a/packages/apps-engine/deno-runtime/lib/accessors/builders/VideoConferenceBuilder.ts +++ b/packages/apps-engine/deno-runtime/lib/accessors/builders/VideoConferenceBuilder.ts @@ -9,7 +9,7 @@ const { RocketChatAssociationModel } = require('@rocket.chat/apps-engine/definit RocketChatAssociationModel: typeof _RocketChatAssociationModel; }; -export type AppVideoConference = Pick & { +export type AppVideoConference = Pick & { createdBy: IGroupVideoConference['createdBy']['_id']; }; @@ -28,6 +28,7 @@ export class VideoConferenceBuilder implements IVideoConferenceBuilder { createdBy: data.createdBy, providerName: data.providerName!, title: data.title!, + discussionRid: data.discussionRid, }; return this; @@ -78,6 +79,15 @@ export class VideoConferenceBuilder implements IVideoConferenceBuilder { return this.call.title; } + public setDiscussionRid(rid: AppVideoConference['discussionRid']): IVideoConferenceBuilder { + this.call.discussionRid = rid; + return this; + } + + public getDiscussionRid(): AppVideoConference['discussionRid'] { + return this.call.discussionRid; + } + public getVideoConference(): AppVideoConference { return this.call; } diff --git a/packages/apps-engine/deno-runtime/lib/accessors/extenders/VideoConferenceExtend.ts b/packages/apps-engine/deno-runtime/lib/accessors/extenders/VideoConferenceExtend.ts index ff7265ce740b..9616bf619067 100644 --- a/packages/apps-engine/deno-runtime/lib/accessors/extenders/VideoConferenceExtend.ts +++ b/packages/apps-engine/deno-runtime/lib/accessors/extenders/VideoConferenceExtend.ts @@ -57,6 +57,12 @@ export class VideoConferenceExtender implements IVideoConferenceExtender { return this; } + public setDiscussionRid(rid: VideoConference['discussionRid']): IVideoConferenceExtender { + this.videoConference.discussionRid = rid; + + return this; + } + public getVideoConference(): VideoConference { return structuredClone(this.videoConference); } diff --git a/packages/apps-engine/src/definition/App.ts b/packages/apps-engine/src/definition/App.ts index f0ca41664e68..6637a25c1d0e 100644 --- a/packages/apps-engine/src/definition/App.ts +++ b/packages/apps-engine/src/definition/App.ts @@ -10,6 +10,7 @@ import type { IModify, IPersistence, IRead, + IAppUpdateContext, } from './accessors'; import { AppStatus } from './AppStatus'; import type { IApp } from './IApp'; @@ -181,6 +182,13 @@ export abstract class App implements IApp { */ public async onInstall(context: IAppInstallationContext, read: IRead, http: IHttp, persistence: IPersistence, modify: IModify): Promise {} + /** + * Method which is called when the App is updated and it is called one single time. + * + * This method is NOT called when the App is installed. + */ + public async onUpdate(context: IAppUpdateContext, read: IRead, http: IHttp, persistence: IPersistence, modify: IModify): Promise {} + /** * Method which is called whenever a setting which belongs to this App has been updated * by an external system and not this App itself. The setting passed is the newly updated one. diff --git a/packages/apps-engine/src/definition/accessors/IAppUpdateContext.ts b/packages/apps-engine/src/definition/accessors/IAppUpdateContext.ts new file mode 100644 index 000000000000..d0bcf7ea280b --- /dev/null +++ b/packages/apps-engine/src/definition/accessors/IAppUpdateContext.ts @@ -0,0 +1,6 @@ +import type { IUser } from '../users'; + +export interface IAppUpdateContext { + user?: IUser; + oldAppVersion: string; +} diff --git a/packages/apps-engine/src/definition/accessors/ILivechatCreator.ts b/packages/apps-engine/src/definition/accessors/ILivechatCreator.ts index def2a9857964..56a3ec17ec27 100644 --- a/packages/apps-engine/src/definition/accessors/ILivechatCreator.ts +++ b/packages/apps-engine/src/definition/accessors/ILivechatCreator.ts @@ -3,6 +3,9 @@ import type { IUser } from '../users'; export interface IExtraRoomParams { source?: ILivechatRoom['source']; + customFields?: { + [key: string]: unknown; + }; } export interface ILivechatCreator { @@ -16,13 +19,22 @@ export interface ILivechatCreator { * @param agent The agent responsible for the room */ createRoom(visitor: IVisitor, agent: IUser, extraParams?: IExtraRoomParams): Promise; + /** + * @deprecated Use `createAndReturnVisitor` instead. * Creates a Livechat visitor * * @param visitor Data of the visitor to be created */ createVisitor(visitor: IVisitor): Promise; + /** + * Creates a Livechat visitor + * + * @param visitor Data of the visitor to be created + */ + createAndReturnVisitor(visitor: IVisitor): Promise; + /** * Creates a token to be used when * creating a new livechat visitor diff --git a/packages/apps-engine/src/definition/accessors/IModifyDeleter.ts b/packages/apps-engine/src/definition/accessors/IModifyDeleter.ts index 01b4c2a7835c..7d1103ba13f3 100644 --- a/packages/apps-engine/src/definition/accessors/IModifyDeleter.ts +++ b/packages/apps-engine/src/definition/accessors/IModifyDeleter.ts @@ -7,4 +7,6 @@ export interface IModifyDeleter { deleteUsers(appId: Exclude, userType: UserType.APP | UserType.BOT): Promise; deleteMessage(message: IMessage, user: IUser): Promise; + + removeUsersFromRoom(roomId: string, usernames: Array): Promise; } diff --git a/packages/apps-engine/src/definition/accessors/IRoomRead.ts b/packages/apps-engine/src/definition/accessors/IRoomRead.ts index 58e0583c75da..f4e0df33239d 100644 --- a/packages/apps-engine/src/definition/accessors/IRoomRead.ts +++ b/packages/apps-engine/src/definition/accessors/IRoomRead.ts @@ -1,4 +1,5 @@ -import type { IMessage } from '../messages/index'; +import type { GetMessagesOptions } from '../../server/bridges/RoomBridge'; +import type { IMessageRaw } from '../messages/index'; import type { IRoom } from '../rooms/index'; import type { IUser } from '../users/index'; @@ -40,12 +41,16 @@ export interface IRoomRead { getCreatorUserByName(name: string): Promise; /** - * Gets an iterator for all of the messages in the provided room. + * Retrieves an array of messages from the specified room. * - * @param roomId the room's id - * @returns an iterator for messages + * @param roomId The unique identifier of the room from which to retrieve messages. + * @param options Optional parameters for retrieving messages: + * - limit: The maximum number of messages to retrieve. Maximum 100 + * - skip: The number of messages to skip (for pagination). + * - sort: An object defining the sorting order of the messages. Each key is a field to sort by, and the value is either "asc" for ascending order or "desc" for descending order. + * @returns A Promise that resolves to an array of IMessage objects representing the messages in the room. */ - getMessages(roomId: string): Promise>; + getMessages(roomId: string, options?: Partial): Promise>; /** * Gets an iterator for all of the users in the provided room. diff --git a/packages/apps-engine/src/definition/accessors/IVideoConferenceBuilder.ts b/packages/apps-engine/src/definition/accessors/IVideoConferenceBuilder.ts index d49278e457a0..11b96da0e4ef 100644 --- a/packages/apps-engine/src/definition/accessors/IVideoConferenceBuilder.ts +++ b/packages/apps-engine/src/definition/accessors/IVideoConferenceBuilder.ts @@ -26,5 +26,9 @@ export interface IVideoConferenceBuilder { getTitle(): string; + setDiscussionRid(rid: string | undefined): IVideoConferenceBuilder; + + getDiscussionRid(): string | undefined; + getVideoConference(): AppVideoConference; } diff --git a/packages/apps-engine/src/definition/accessors/IVideoConferenceExtend.ts b/packages/apps-engine/src/definition/accessors/IVideoConferenceExtend.ts index 5b1eab057355..d9b7e5838368 100644 --- a/packages/apps-engine/src/definition/accessors/IVideoConferenceExtend.ts +++ b/packages/apps-engine/src/definition/accessors/IVideoConferenceExtend.ts @@ -15,5 +15,7 @@ export interface IVideoConferenceExtender { addUser(userId: VideoConferenceMember['_id'], ts?: VideoConferenceMember['ts']): IVideoConferenceExtender; + setDiscussionRid(rid: VideoConference['discussionRid']): IVideoConferenceExtender; + getVideoConference(): VideoConference; } diff --git a/packages/apps-engine/src/definition/accessors/index.ts b/packages/apps-engine/src/definition/accessors/index.ts index 64485ab0ae47..fbc5a65409af 100644 --- a/packages/apps-engine/src/definition/accessors/index.ts +++ b/packages/apps-engine/src/definition/accessors/index.ts @@ -1,6 +1,7 @@ export * from './IApiExtend'; export * from './IAppAccessors'; export * from './IAppInstallationContext'; +export * from './IAppUpdateContext'; export * from './IAppUninstallationContext'; export * from './ICloudWorkspaceRead'; export * from './IConfigurationExtend'; diff --git a/packages/apps-engine/src/definition/messages/IMessageRaw.ts b/packages/apps-engine/src/definition/messages/IMessageRaw.ts new file mode 100644 index 000000000000..1706b2639f1c --- /dev/null +++ b/packages/apps-engine/src/definition/messages/IMessageRaw.ts @@ -0,0 +1,40 @@ +import type { IBlock, Block } from '@rocket.chat/ui-kit'; + +import type { IRoom } from '../rooms'; +import type { IUserLookup } from '../users'; +import type { IMessageAttachment } from './IMessageAttachment'; +import type { IMessageFile } from './IMessageFile'; +import type { IMessageReactions } from './IMessageReaction'; + +/** + * The raw version of a message, without resolved information for relationship fields, i.e. + * `room`, `sender` and `editor` are not the complete entity like they are in `IMessage` + * + * This is used in methods that fetch multiple messages at the same time, as resolving the relationship + * fields require additional queries to the database and would hit the system's performance significantly. + */ +export interface IMessageRaw { + id: string; + roomId: IRoom['id']; + sender: IUserLookup; + createdAt: Date; + threadId?: string; + text?: string; + updatedAt?: Date; + editor?: IUserLookup; + editedAt?: Date; + emoji?: string; + avatarUrl?: string; + alias?: string; + file?: IMessageFile; + attachments?: Array; + reactions?: IMessageReactions; + groupable?: boolean; + parseUrls?: boolean; + customFields?: { [key: string]: any }; + blocks?: Array; + starred?: Array<{ _id: string }>; + pinned?: boolean; + pinnedAt?: Date; + pinnedBy?: IUserLookup; +} diff --git a/packages/apps-engine/src/definition/messages/index.ts b/packages/apps-engine/src/definition/messages/index.ts index a46a0310e016..1ed02d7b2305 100644 --- a/packages/apps-engine/src/definition/messages/index.ts +++ b/packages/apps-engine/src/definition/messages/index.ts @@ -8,6 +8,7 @@ import { IMessageDeleteContext } from './IMessageDeleteContext'; import { IMessageFile } from './IMessageFile'; import { IMessageFollowContext } from './IMessageFollowContext'; import { IMessagePinContext } from './IMessagePinContext'; +import { IMessageRaw } from './IMessageRaw'; import { IMessageReaction, IMessageReactions } from './IMessageReaction'; import { IMessageReactionContext } from './IMessageReactionContext'; import { IMessageReportContext } from './IMessageReportContext'; @@ -39,6 +40,7 @@ export { IMessageAttachmentField, IMessageAction, IMessageFile, + IMessageRaw, IMessageReactions, IMessageReaction, IPostMessageDeleted, diff --git a/packages/apps-engine/src/definition/metadata/AppMethod.ts b/packages/apps-engine/src/definition/metadata/AppMethod.ts index ffe80c4d6d2d..71a6d0e914cc 100644 --- a/packages/apps-engine/src/definition/metadata/AppMethod.ts +++ b/packages/apps-engine/src/definition/metadata/AppMethod.ts @@ -17,6 +17,7 @@ export enum AppMethod { ONDISABLE = 'onDisable', ONINSTALL = 'onInstall', ONUNINSTALL = 'onUninstall', + ONUPDATE = 'onUpdate', ON_PRE_SETTING_UPDATE = 'onPreSettingUpdate', ONSETTINGUPDATED = 'onSettingUpdated', SETSTATUS = 'setStatus', diff --git a/packages/apps-engine/src/definition/rooms/IRoomUserLeaveContext.ts b/packages/apps-engine/src/definition/rooms/IRoomUserLeaveContext.ts index 0c9caab0a033..9fe153bc8e0a 100644 --- a/packages/apps-engine/src/definition/rooms/IRoomUserLeaveContext.ts +++ b/packages/apps-engine/src/definition/rooms/IRoomUserLeaveContext.ts @@ -15,4 +15,9 @@ export interface IRoomUserLeaveContext { * The room that the user is leaving */ room: IRoom; + + /** + * The user that removed the room member + */ + removedBy?: IUser; } diff --git a/packages/apps-engine/src/definition/users/IUserLookup.ts b/packages/apps-engine/src/definition/users/IUserLookup.ts index 271560b86908..9b5f09b0498f 100644 --- a/packages/apps-engine/src/definition/users/IUserLookup.ts +++ b/packages/apps-engine/src/definition/users/IUserLookup.ts @@ -1,4 +1,5 @@ export interface IUserLookup { _id: string; username: string; + name?: string; } diff --git a/packages/apps-engine/src/definition/videoConfProviders/IVideoConfProvider.ts b/packages/apps-engine/src/definition/videoConfProviders/IVideoConfProvider.ts index 66395b960b6a..00ca57de8966 100644 --- a/packages/apps-engine/src/definition/videoConfProviders/IVideoConfProvider.ts +++ b/packages/apps-engine/src/definition/videoConfProviders/IVideoConfProvider.ts @@ -18,6 +18,8 @@ export interface IVideoConfProvider { cam?: boolean; // Indicates if Rocket.Chat can send a custom title for the video conferences title?: boolean; + // Indicates if the provider supports Rocket.Chat's Persistent Chat feature on its conferences. + persistentChat?: boolean; }; // Optional function that can be used to determine if the provider is ready to use or still needs to be configured diff --git a/packages/apps-engine/src/definition/videoConfProviders/VideoConfData.ts b/packages/apps-engine/src/definition/videoConfProviders/VideoConfData.ts index 1ab46e3aeaea..dc6f2cc6d47c 100644 --- a/packages/apps-engine/src/definition/videoConfProviders/VideoConfData.ts +++ b/packages/apps-engine/src/definition/videoConfProviders/VideoConfData.ts @@ -1,5 +1,7 @@ import type { IGroupVideoConference, IVideoConference } from '../videoConferences/IVideoConference'; -export type VideoConfData = Pick & { title?: IGroupVideoConference['title'] }; +export type VideoConfData = Pick & { + title?: IGroupVideoConference['title']; +}; export type VideoConfDataExtended = VideoConfData & Required>; diff --git a/packages/apps-engine/src/definition/videoConferences/AppVideoConference.ts b/packages/apps-engine/src/definition/videoConferences/AppVideoConference.ts index 92c3f97c5c16..86640e000782 100644 --- a/packages/apps-engine/src/definition/videoConferences/AppVideoConference.ts +++ b/packages/apps-engine/src/definition/videoConferences/AppVideoConference.ts @@ -1,6 +1,6 @@ import type { IGroupVideoConference } from './IVideoConference'; // Type for video conferences being created by an app -export type AppVideoConference = Pick & { +export type AppVideoConference = Pick & { createdBy: IGroupVideoConference['createdBy']['_id']; }; diff --git a/packages/apps-engine/src/definition/videoConferences/IVideoConference.ts b/packages/apps-engine/src/definition/videoConferences/IVideoConference.ts index 106bddc68012..6f0286ae29cb 100644 --- a/packages/apps-engine/src/definition/videoConferences/IVideoConference.ts +++ b/packages/apps-engine/src/definition/videoConferences/IVideoConference.ts @@ -36,6 +36,7 @@ export interface IVideoConference { providerData?: Record; ringing?: boolean; + discussionRid?: string; } export interface IDirectVideoConference extends IVideoConference { diff --git a/packages/apps-engine/src/server/AppManager.ts b/packages/apps-engine/src/server/AppManager.ts index 11709c6c01c3..29ac3fc7d9e7 100644 --- a/packages/apps-engine/src/server/AppManager.ts +++ b/packages/apps-engine/src/server/AppManager.ts @@ -653,7 +653,11 @@ export class AppManager { this.apps.delete(app.getID()); } - public async update(appPackage: Buffer, permissionsGranted: Array, updateOptions = { loadApp: true }): Promise { + public async update( + appPackage: Buffer, + permissionsGranted: Array, + updateOptions: { loadApp?: boolean; user?: IUser } = { loadApp: true }, + ): Promise { const aff = new AppFabricationFulfillment(); const result = await this.getParser().unpackageApp(appPackage); @@ -724,6 +728,8 @@ export class AppManager { .catch(() => {}); } + await this.updateApp(app, updateOptions.user, old.info.version); + return aff; } @@ -934,6 +940,24 @@ export class AppManager { return result; } + private async updateApp(app: ProxiedApp, user: IUser | null, oldAppVersion: string): Promise { + let result: boolean; + + try { + await app.call(AppMethod.ONUPDATE, { oldAppVersion, user }); + + result = true; + } catch (e) { + const status = AppStatus.ERROR_DISABLED; + + result = false; + + await app.setStatus(status); + } + + return result; + } + private async initializeApp(storageItem: IAppStorageItem, app: ProxiedApp, saveToDb = true, silenceStatus = false): Promise { let result: boolean; diff --git a/packages/apps-engine/src/server/accessors/LivechatCreator.ts b/packages/apps-engine/src/server/accessors/LivechatCreator.ts index 937827cd8773..8cd527159662 100644 --- a/packages/apps-engine/src/server/accessors/LivechatCreator.ts +++ b/packages/apps-engine/src/server/accessors/LivechatCreator.ts @@ -12,10 +12,17 @@ export class LivechatCreator implements ILivechatCreator { return this.bridges.getLivechatBridge().doCreateRoom(visitor, agent, this.appId, extraParams); } + /** + * @deprecated Use `createAndReturnVisitor` instead. + */ public createVisitor(visitor: IVisitor): Promise { return this.bridges.getLivechatBridge().doCreateVisitor(visitor, this.appId); } + public createAndReturnVisitor(visitor: IVisitor): Promise { + return this.bridges.getLivechatBridge().doCreateAndReturnVisitor(visitor, this.appId); + } + public createToken(): string { return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); } diff --git a/packages/apps-engine/src/server/accessors/ModifyDeleter.ts b/packages/apps-engine/src/server/accessors/ModifyDeleter.ts index 8c4b441a8d65..81c4f0fa1d37 100644 --- a/packages/apps-engine/src/server/accessors/ModifyDeleter.ts +++ b/packages/apps-engine/src/server/accessors/ModifyDeleter.ts @@ -17,4 +17,19 @@ export class ModifyDeleter implements IModifyDeleter { public async deleteMessage(message: IMessage, user: IUser): Promise { return this.bridges.getMessageBridge().doDelete(message, user, this.appId); } + + /** + * Removes `usernames` from the room's member list + * + * For performance reasons, it is only possible to remove 50 users in one + * call to this method. Removing users is an expensive operation due to the + * amount of entity relationships that need to be modified. + */ + public async removeUsersFromRoom(roomId: string, usernames: Array) { + if (usernames.length > 50) { + throw new Error('A maximum of 50 members can be removed in a single call'); + } + + return this.bridges.getRoomBridge().doRemoveUsers(roomId, usernames, this.appId); + } } diff --git a/packages/apps-engine/src/server/accessors/RoomRead.ts b/packages/apps-engine/src/server/accessors/RoomRead.ts index da4273f53671..30b231af6b83 100644 --- a/packages/apps-engine/src/server/accessors/RoomRead.ts +++ b/packages/apps-engine/src/server/accessors/RoomRead.ts @@ -1,8 +1,9 @@ import type { IRoomRead } from '../../definition/accessors'; -import type { IMessage } from '../../definition/messages'; +import type { IMessageRaw } from '../../definition/messages'; import type { IRoom } from '../../definition/rooms'; import type { IUser } from '../../definition/users'; import type { RoomBridge } from '../bridges'; +import { type GetMessagesOptions, GetMessagesSortableFields } from '../bridges/RoomBridge'; export class RoomRead implements IRoomRead { constructor(private roomBridge: RoomBridge, private appId: string) {} @@ -23,8 +24,18 @@ export class RoomRead implements IRoomRead { return this.roomBridge.doGetCreatorByName(name, this.appId); } - public getMessages(roomId: string): Promise> { - throw new Error('Method not implemented.'); + public getMessages(roomId: string, options: Partial = {}): Promise { + if (typeof options.limit !== 'undefined' && (!Number.isFinite(options.limit) || options.limit > 100)) { + throw new Error(`Invalid limit provided. Expected number <= 100, got ${options.limit}`); + } + + options.limit ??= 100; + + if (options.sort) { + this.validateSort(options.sort); + } + + return this.roomBridge.doGetMessages(roomId, options as GetMessagesOptions, this.appId); } public getMembers(roomId: string): Promise> { @@ -46,4 +57,17 @@ export class RoomRead implements IRoomRead { public getLeaders(roomId: string): Promise> { return this.roomBridge.doGetLeaders(roomId, this.appId); } + + // If there are any invalid fields or values, throw + private validateSort(sort: Record) { + Object.entries(sort).forEach(([key, value]) => { + if (!GetMessagesSortableFields.includes(key as typeof GetMessagesSortableFields[number])) { + throw new Error(`Invalid key "${key}" used in sort. Available keys for sorting are ${GetMessagesSortableFields.join(', ')}`); + } + + if (value !== 'asc' && value !== 'desc') { + throw new Error(`Invalid sort direction for field "${key}". Expected "asc" or "desc", got ${value}`); + } + }); + } } diff --git a/packages/apps-engine/src/server/accessors/VideoConferenceBuilder.ts b/packages/apps-engine/src/server/accessors/VideoConferenceBuilder.ts index a0a474143ede..bd12028b7853 100644 --- a/packages/apps-engine/src/server/accessors/VideoConferenceBuilder.ts +++ b/packages/apps-engine/src/server/accessors/VideoConferenceBuilder.ts @@ -17,6 +17,7 @@ export class VideoConferenceBuilder implements IVideoConferenceBuilder { createdBy: data.createdBy, providerName: data.providerName, title: data.title, + discussionRid: data.discussionRid, }; return this; @@ -67,6 +68,15 @@ export class VideoConferenceBuilder implements IVideoConferenceBuilder { return this.call.title; } + public setDiscussionRid(rid: AppVideoConference['discussionRid']): IVideoConferenceBuilder { + this.call.discussionRid = rid; + return this; + } + + public getDiscussionRid(): AppVideoConference['discussionRid'] { + return this.call.discussionRid; + } + public getVideoConference(): AppVideoConference { return this.call; } diff --git a/packages/apps-engine/src/server/accessors/VideoConferenceExtend.ts b/packages/apps-engine/src/server/accessors/VideoConferenceExtend.ts index 4afe4d2d0164..fd3692614aea 100644 --- a/packages/apps-engine/src/server/accessors/VideoConferenceExtend.ts +++ b/packages/apps-engine/src/server/accessors/VideoConferenceExtend.ts @@ -52,6 +52,12 @@ export class VideoConferenceExtender implements IVideoConferenceExtender { return this; } + public setDiscussionRid(rid: VideoConference['discussionRid']): IVideoConferenceExtender { + this.videoConference.discussionRid = rid; + + return this; + } + public getVideoConference(): VideoConference { return Utilities.deepClone(this.videoConference); } diff --git a/packages/apps-engine/src/server/bridges/LivechatBridge.ts b/packages/apps-engine/src/server/bridges/LivechatBridge.ts index ea9e50f1b6b7..9fc830767545 100644 --- a/packages/apps-engine/src/server/bridges/LivechatBridge.ts +++ b/packages/apps-engine/src/server/bridges/LivechatBridge.ts @@ -50,12 +50,21 @@ export abstract class LivechatBridge extends BaseBridge { } } + /** + * @deprecated please use the `doCreateAndReturnVisitor` method instead. + */ public async doCreateVisitor(visitor: IVisitor, appId: string): Promise { if (this.hasWritePermission(appId, 'livechat-visitor')) { return this.createVisitor(visitor, appId); } } + public async doCreateAndReturnVisitor(visitor: IVisitor, appId: string): Promise { + if (this.hasWritePermission(appId, 'livechat-visitor')) { + return this.createAndReturnVisitor(visitor, appId); + } + } + public async doFindVisitors(query: object, appId: string): Promise> { if (this.hasReadPermission(appId, 'livechat-visitor')) { return this.findVisitors(query, appId); @@ -160,8 +169,14 @@ export abstract class LivechatBridge extends BaseBridge { protected abstract updateMessage(message: ILivechatMessage, appId: string): Promise; + /** + * @deprecated please use `createAndReturnVisitor` instead. + * It returns the created record rather than the ID. + */ protected abstract createVisitor(visitor: IVisitor, appId: string): Promise; + protected abstract createAndReturnVisitor(visitor: IVisitor, appId: string): Promise; + /** * @deprecated This method does not adhere to the conversion practices applied * elsewhere in the Apps-Engine and will be removed in the next major version. diff --git a/packages/apps-engine/src/server/bridges/RoomBridge.ts b/packages/apps-engine/src/server/bridges/RoomBridge.ts index 6776202838f4..1ac9de464bf8 100644 --- a/packages/apps-engine/src/server/bridges/RoomBridge.ts +++ b/packages/apps-engine/src/server/bridges/RoomBridge.ts @@ -1,4 +1,4 @@ -import type { IMessage } from '../../definition/messages'; +import type { IMessage, IMessageRaw } from '../../definition/messages'; import type { IRoom } from '../../definition/rooms'; import type { IUser } from '../../definition/users'; import { PermissionDeniedError } from '../errors/PermissionDeniedError'; @@ -6,6 +6,14 @@ import { AppPermissionManager } from '../managers/AppPermissionManager'; import { AppPermissions } from '../permissions/AppPermissions'; import { BaseBridge } from './BaseBridge'; +export const GetMessagesSortableFields = ['createdAt'] as const; + +export type GetMessagesOptions = { + limit: number; + skip: number; + sort: Record; +}; + export abstract class RoomBridge extends BaseBridge { public async doCreate(room: IRoom, members: Array, appId: string): Promise { if (this.hasWritePermission(appId)) { @@ -91,6 +99,18 @@ export abstract class RoomBridge extends BaseBridge { } } + public async doGetMessages(roomId: string, options: GetMessagesOptions, appId: string): Promise { + if (this.hasReadPermission(appId)) { + return this.getMessages(roomId, options, appId); + } + } + + public async doRemoveUsers(roomId: string, usernames: Array, appId: string): Promise { + if (this.hasWritePermission(appId)) { + return this.removeUsers(roomId, usernames, appId); + } + } + protected abstract create(room: IRoom, members: Array, appId: string): Promise; protected abstract getById(roomId: string, appId: string): Promise; @@ -123,6 +143,10 @@ export abstract class RoomBridge extends BaseBridge { protected abstract getLeaders(roomId: string, appId: string): Promise>; + protected abstract getMessages(roomId: string, options: GetMessagesOptions, appId: string): Promise; + + protected abstract removeUsers(roomId: string, usernames: Array, appId: string): Promise; + private hasWritePermission(appId: string): boolean { if (AppPermissionManager.hasPermission(appId, AppPermissions.room.write)) { return true;