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. diff --git a/apps/meteor/app/livechat/server/api/v1/agent.ts b/apps/meteor/app/livechat/server/api/v1/agent.ts index 3c271e832b07f..d39187f052a43 100644 --- a/apps/meteor/app/livechat/server/api/v1/agent.ts +++ b/apps/meteor/app/livechat/server/api/v1/agent.ts @@ -2,6 +2,7 @@ import type { ILivechatAgent } from '@rocket.chat/core-typings'; import { ILivechatAgentStatus } from '@rocket.chat/core-typings'; import { Users } from '@rocket.chat/models'; import { + ajv, isGETAgentNextToken, isPOSTLivechatAgentSaveInfoParams, isPOSTLivechatAgentStatusProps, @@ -21,8 +22,47 @@ 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 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', + { + response: { + 200: GETAgentInfoSuccessResponse, + 400: validateBadRequestErrorResponse, + }, + }, + async function action() { const visitor = await findGuest(this.urlParams.token); if (!visitor) { throw new Error('invalid-token'); @@ -40,39 +80,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 +206,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..51ff9dfae9872 100644 --- a/packages/rest-typings/src/v1/omnichannel.ts +++ b/packages/rest-typings/src/v1/omnichannel.ts @@ -2285,6 +2285,7 @@ const GETAgentNextTokenSchema = { export const isGETAgentNextToken = ajvQuery.compile(GETAgentNextTokenSchema); + type GETLivechatConfigParams = { token?: string; department?: string; @@ -2847,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 = { @@ -4359,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 };