From 620f8adc14f46ad0f0207a34ab684ed22b51f879 Mon Sep 17 00:00:00 2001 From: NAME-ASHWANIYADAV <22ashwaniyadav@gmail.com> Date: Mon, 23 Mar 2026 16:55:30 +0000 Subject: [PATCH 1/3] refactor: migrate livechat/agent.info and livechat/agent.next to OpenAPI chained pattern --- .../app/livechat/server/api/v1/agent.ts | 73 +++++++++++-------- packages/rest-typings/src/v1/omnichannel.ts | 38 ++++++++++ 2 files changed, 82 insertions(+), 29 deletions(-) diff --git a/apps/meteor/app/livechat/server/api/v1/agent.ts b/apps/meteor/app/livechat/server/api/v1/agent.ts index 3c271e832b07f..86c0608f2a6e7 100644 --- a/apps/meteor/app/livechat/server/api/v1/agent.ts +++ b/apps/meteor/app/livechat/server/api/v1/agent.ts @@ -2,6 +2,8 @@ import type { ILivechatAgent } from '@rocket.chat/core-typings'; import { ILivechatAgentStatus } from '@rocket.chat/core-typings'; import { Users } from '@rocket.chat/models'; import { + GETAgentInfoSuccessResponse, + GETAgentNextSuccessResponse, isGETAgentNextToken, isPOSTLivechatAgentSaveInfoParams, isPOSTLivechatAgentStatusProps, @@ -21,8 +23,15 @@ import { saveAgentInfo } from '../../lib/omni-users'; import { setUserStatusLivechat, allowAgentChangeServiceStatus } from '../../lib/utils'; import { findRoom, findGuest, findAgent, findOpenRoom } from '../lib/livechat'; -API.v1.addRoute('livechat/agent.info/:rid/:token', { - async get() { +const livechatAgentInfoEndpoint = API.v1.get( + 'livechat/agent.info/:rid/:token', + { + response: { + 200: GETAgentInfoSuccessResponse, + 400: validateBadRequestErrorResponse, + }, + }, + async function action() { const visitor = await findGuest(this.urlParams.token); if (!visitor) { throw new Error('invalid-token'); @@ -40,39 +49,43 @@ API.v1.addRoute('livechat/agent.info/:rid/:token', { return API.v1.success({ agent }); }, -}); +); -API.v1.addRoute( +const livechatAgentNextEndpoint = API.v1.get( 'livechat/agent.next/:token', - { validateParams: isGETAgentNextToken }, { - async get() { - const { token } = this.urlParams; - const room = await findOpenRoom(token, undefined, this.userId); - if (room) { - return API.v1.success(); - } + query: isGETAgentNextToken, + response: { + 200: GETAgentNextSuccessResponse, + 400: validateBadRequestErrorResponse, + }, + }, + async function action() { + const { token } = this.urlParams; + const room = await findOpenRoom(token, undefined, this.userId); + if (room) { + return API.v1.success(); + } - let { department } = this.queryParams; - if (!department) { - const requireDepartment = await getRequiredDepartment(); - if (requireDepartment) { - department = requireDepartment._id; - } + let { department } = this.queryParams; + if (!department) { + const requireDepartment = await getRequiredDepartment(); + if (requireDepartment) { + department = requireDepartment._id; } + } - const agentData = await RoutingManager.getNextAgent(department); - if (!agentData) { - throw new Error('agent-not-found'); - } + const agentData = await RoutingManager.getNextAgent(department); + if (!agentData) { + throw new Error('agent-not-found'); + } - const agent = await findAgent(agentData.agentId); - if (!agent) { - throw new Error('invalid-agent'); - } + const agent = await findAgent(agentData.agentId); + if (!agent) { + throw new Error('invalid-agent'); + } - return API.v1.success({ agent }); - }, + return API.v1.success({ agent }); }, ); @@ -162,9 +175,11 @@ const livechatAgentsEndpoints = API.v1.post( }, ); +type LivechatAgentInfoEndpoints = ExtractRoutesFromAPI; +type LivechatAgentNextEndpoints = ExtractRoutesFromAPI; type LivechatAgentsEndpoints = ExtractRoutesFromAPI; declare module '@rocket.chat/rest-typings' { - // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface - interface Endpoints extends LivechatAgentsEndpoints {} + // eslint-disable-next-line @typescript-eslint/naming-convention + interface Endpoints extends LivechatAgentInfoEndpoints, LivechatAgentNextEndpoints, LivechatAgentsEndpoints {} } diff --git a/packages/rest-typings/src/v1/omnichannel.ts b/packages/rest-typings/src/v1/omnichannel.ts index fe5988d3583c1..2d55e97d48aab 100644 --- a/packages/rest-typings/src/v1/omnichannel.ts +++ b/packages/rest-typings/src/v1/omnichannel.ts @@ -2285,6 +2285,44 @@ const GETAgentNextTokenSchema = { export const isGETAgentNextToken = ajvQuery.compile(GETAgentNextTokenSchema); +const GETAgentInfoSuccessResponseSchema = { + type: 'object', + properties: { + agent: { + type: 'object', + }, + success: { + type: 'boolean', + enum: [true], + }, + }, + required: ['agent', 'success'], + additionalProperties: false, +}; + +export const GETAgentInfoSuccessResponse = ajv.compile<{ agent: ILivechatAgent | { hiddenInfo: true }; success: boolean }>( + GETAgentInfoSuccessResponseSchema, +); + +const GETAgentNextSuccessResponseSchema = { + type: 'object', + properties: { + agent: { + type: 'object', + }, + success: { + type: 'boolean', + enum: [true], + }, + }, + required: ['success'], + additionalProperties: false, +}; + +export const GETAgentNextSuccessResponse = ajv.compile< + { agent?: ILivechatAgent | { hiddenInfo: true }; success: boolean } | { success: boolean } +>(GETAgentNextSuccessResponseSchema); + type GETLivechatConfigParams = { token?: string; department?: string; From 731de22286d977f07fe270daffe8f73a7640d365 Mon Sep 17 00:00:00 2001 From: NAME-ASHWANIYADAV <22ashwaniyadav@gmail.com> Date: Mon, 23 Mar 2026 17:06:24 +0000 Subject: [PATCH 2/3] refactor: migrate livechat/agent.info and livechat/agent.next to OpenAPI chained pattern --- .../refactor-livechat-agent-info-next-chained-pattern.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/refactor-livechat-agent-info-next-chained-pattern.md diff --git a/.changeset/refactor-livechat-agent-info-next-chained-pattern.md b/.changeset/refactor-livechat-agent-info-next-chained-pattern.md new file mode 100644 index 0000000000000..4bbe8f5982665 --- /dev/null +++ b/.changeset/refactor-livechat-agent-info-next-chained-pattern.md @@ -0,0 +1,6 @@ +--- +'@rocket.chat/meteor': minor +'@rocket.chat/rest-typings': minor +--- + +Migrates `livechat/agent.info/:rid/:token` and `livechat/agent.next/:token` REST API endpoints from legacy `addRoute` pattern to the new chained `.get()` API pattern with typed response schemas and AJV validation. From bfe3fe9158ff16cdfe1f4e721b7b69f54139dc47 Mon Sep 17 00:00:00 2001 From: ashwani yadav <22ashwaniyadav@gmail.com> Date: Tue, 24 Mar 2026 14:24:10 +0530 Subject: [PATCH 3/3] =?UTF-8?q?refactor:=20address=20review=20=E2=80=94=20?= =?UTF-8?q?move=20response=20schemas=20inline,=20remove=20manual=20typings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/livechat/server/api/v1/agent.ts | 35 +++++++++- packages/rest-typings/src/v1/omnichannel.ts | 64 ++++--------------- 2 files changed, 44 insertions(+), 55 deletions(-) diff --git a/apps/meteor/app/livechat/server/api/v1/agent.ts b/apps/meteor/app/livechat/server/api/v1/agent.ts index 86c0608f2a6e7..d39187f052a43 100644 --- a/apps/meteor/app/livechat/server/api/v1/agent.ts +++ b/apps/meteor/app/livechat/server/api/v1/agent.ts @@ -2,8 +2,7 @@ import type { ILivechatAgent } from '@rocket.chat/core-typings'; import { ILivechatAgentStatus } from '@rocket.chat/core-typings'; import { Users } from '@rocket.chat/models'; import { - GETAgentInfoSuccessResponse, - GETAgentNextSuccessResponse, + ajv, isGETAgentNextToken, isPOSTLivechatAgentSaveInfoParams, isPOSTLivechatAgentStatusProps, @@ -23,6 +22,38 @@ import { saveAgentInfo } from '../../lib/omni-users'; import { setUserStatusLivechat, allowAgentChangeServiceStatus } from '../../lib/utils'; import { findRoom, findGuest, findAgent, findOpenRoom } from '../lib/livechat'; +const GETAgentInfoSuccessResponse = ajv.compile<{ agent: ILivechatAgent | { hiddenInfo: true }; success: boolean }>({ + type: 'object', + properties: { + agent: { + type: 'object', + }, + success: { + type: 'boolean', + enum: [true], + }, + }, + required: ['agent', 'success'], + additionalProperties: false, +}); + +const GETAgentNextSuccessResponse = ajv.compile< + { agent?: ILivechatAgent | { hiddenInfo: true }; success: boolean } | { success: boolean } +>({ + type: 'object', + properties: { + agent: { + type: 'object', + }, + success: { + type: 'boolean', + enum: [true], + }, + }, + required: ['success'], + additionalProperties: false, +}); + const livechatAgentInfoEndpoint = API.v1.get( 'livechat/agent.info/:rid/:token', { diff --git a/packages/rest-typings/src/v1/omnichannel.ts b/packages/rest-typings/src/v1/omnichannel.ts index 2d55e97d48aab..51ff9dfae9872 100644 --- a/packages/rest-typings/src/v1/omnichannel.ts +++ b/packages/rest-typings/src/v1/omnichannel.ts @@ -2285,43 +2285,6 @@ const GETAgentNextTokenSchema = { export const isGETAgentNextToken = ajvQuery.compile(GETAgentNextTokenSchema); -const GETAgentInfoSuccessResponseSchema = { - type: 'object', - properties: { - agent: { - type: 'object', - }, - success: { - type: 'boolean', - enum: [true], - }, - }, - required: ['agent', 'success'], - additionalProperties: false, -}; - -export const GETAgentInfoSuccessResponse = ajv.compile<{ agent: ILivechatAgent | { hiddenInfo: true }; success: boolean }>( - GETAgentInfoSuccessResponseSchema, -); - -const GETAgentNextSuccessResponseSchema = { - type: 'object', - properties: { - agent: { - type: 'object', - }, - success: { - type: 'boolean', - enum: [true], - }, - }, - required: ['success'], - additionalProperties: false, -}; - -export const GETAgentNextSuccessResponse = ajv.compile< - { agent?: ILivechatAgent | { hiddenInfo: true }; success: boolean } | { success: boolean } ->(GETAgentNextSuccessResponseSchema); type GETLivechatConfigParams = { token?: string; @@ -2885,14 +2848,14 @@ type POSTLivechatRoomCloseByUserParams = { generateTranscriptPdf?: boolean; forceClose?: boolean; transcriptEmail?: - | { - // Note: if sendToVisitor is false, then any previously requested transcripts (like via livechat:requestTranscript) will be also cancelled - sendToVisitor: false; - } - | { - sendToVisitor: true; - requestData: Pick, 'email' | 'subject'>; - }; + | { + // Note: if sendToVisitor is false, then any previously requested transcripts (like via livechat:requestTranscript) will be also cancelled + sendToVisitor: false; + } + | { + sendToVisitor: true; + requestData: Pick, 'email' | 'subject'>; + }; }; const POSTLivechatRoomCloseByUserParamsSchema = { @@ -4397,8 +4360,8 @@ export const isLivechatTriggerWebhookCallParams = ajv.compile { contact: ILivechatVisitor | null }; }; - '/v1/livechat/agent.info/:rid/:token': { - GET: () => { agent: ILivechatAgent | { hiddenInfo: true } }; - }; - '/v1/livechat/agent.next/:token': { - GET: (params: GETAgentNextToken) => { agent: ILivechatAgent | { hiddenInfo: true } } | void; - }; + '/v1/livechat/config': { GET: (params: GETLivechatConfigParams) => { config: { [k: string]: string | boolean } & { room?: IOmnichannelRoom; agent?: ILivechatAgent };