From 9a937c79b30c9a0ddabef2383bd2955bd5d61498 Mon Sep 17 00:00:00 2001 From: NAME-ASHWANIYADAV <22ashwaniyadav@gmail.com> Date: Sun, 22 Mar 2026 11:14:37 +0000 Subject: [PATCH 1/2] chore: migrate livechat/tags and livechat/tags/:tagId to OpenAPI chained pattern --- .../livechat-enterprise/server/api/tags.ts | 62 +++++++++++-------- packages/rest-typings/src/v1/omnichannel.ts | 60 +++++++++++++++--- 2 files changed, 86 insertions(+), 36 deletions(-) diff --git a/apps/meteor/ee/app/livechat-enterprise/server/api/tags.ts b/apps/meteor/ee/app/livechat-enterprise/server/api/tags.ts index e4232c9700e14..737257df19aa3 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/api/tags.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/api/tags.ts @@ -1,4 +1,7 @@ import { + isLivechatTagsListProps, + GETLivechatTagsSuccessResponse, + GETLivechatTagByIdSuccessResponse, isPOSTLivechatTagsSaveParams, POSTLivechatTagsSaveSuccessResponse, isPOSTLivechatTagsDeleteParams, @@ -6,6 +9,7 @@ import { validateBadRequestErrorResponse, validateForbiddenErrorResponse, validateUnauthorizedErrorResponse, + validateNotFoundErrorResponse, } from '@rocket.chat/rest-typings'; import { findTags, findTagById } from './lib/tags'; @@ -14,15 +18,21 @@ import type { ExtractRoutesFromAPI } from '../../../../../app/api/server/ApiClas import { getPaginationItems } from '../../../../../app/api/server/helpers/getPaginationItems'; import { LivechatEnterprise } from '../lib/LivechatEnterprise'; -API.v1.addRoute( - 'livechat/tags', - { - authRequired: true, - permissionsRequired: { GET: { permissions: ['view-l-room', 'manage-livechat-tags'], operation: 'hasAny' } }, - license: ['livechat-enterprise'], - }, - { - async get() { +const livechatTagsEndpoints = API.v1 + .get( + 'livechat/tags', + { + authRequired: true, + permissionsRequired: { GET: { permissions: ['view-l-room', 'manage-livechat-tags'], operation: 'hasAny' } }, + license: ['livechat-enterprise'], + query: isLivechatTagsListProps, + response: { + 200: GETLivechatTagsSuccessResponse, + 401: validateUnauthorizedErrorResponse, + 403: validateForbiddenErrorResponse, + }, + }, + async function action() { const { offset, count } = await getPaginationItems(this.queryParams); const { sort } = await this.parseJsonQuery(); const { text, viewAll, department } = this.queryParams; @@ -41,18 +51,21 @@ API.v1.addRoute( }), ); }, - }, -); - -API.v1.addRoute( - 'livechat/tags/:tagId', - { - authRequired: true, - permissionsRequired: { GET: { permissions: ['view-l-room', 'manage-livechat-tags'], operation: 'hasAny' } }, - license: ['livechat-enterprise'], - }, - { - async get() { + ) + .get( + 'livechat/tags/:tagId', + { + authRequired: true, + permissionsRequired: { GET: { permissions: ['view-l-room', 'manage-livechat-tags'], operation: 'hasAny' } }, + license: ['livechat-enterprise'], + response: { + 200: GETLivechatTagByIdSuccessResponse, + 401: validateUnauthorizedErrorResponse, + 403: validateForbiddenErrorResponse, + 404: validateNotFoundErrorResponse, + }, + }, + async function action() { const { tagId } = this.urlParams; const tag = await findTagById({ @@ -66,10 +79,7 @@ API.v1.addRoute( return API.v1.success(tag); }, - }, -); - -const livechatTagsEndpoints = API.v1 + ) .post( 'livechat/tags.save', { @@ -119,5 +129,5 @@ type LivechatTagsEndpoints = ExtractRoutesFromAPI; declare module '@rocket.chat/rest-typings' { // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface - interface Endpoints extends LivechatTagsEndpoints {} + interface Endpoints extends LivechatTagsEndpoints { } } diff --git a/packages/rest-typings/src/v1/omnichannel.ts b/packages/rest-typings/src/v1/omnichannel.ts index fe5988d3583c1..ad4178cb93962 100644 --- a/packages/rest-typings/src/v1/omnichannel.ts +++ b/packages/rest-typings/src/v1/omnichannel.ts @@ -649,13 +649,14 @@ const POSTLivechatTagsDeleteSuccessResponseSchema = { export const POSTLivechatTagsDeleteSuccessResponse = ajv.compile(POSTLivechatTagsDeleteSuccessResponseSchema); -type LivechatTagsListProps = PaginatedRequest<{ text: string; viewAll?: 'true' | 'false'; department?: string }, 'name'>; +type LivechatTagsListProps = PaginatedRequest<{ text?: string; viewAll?: 'true' | 'false'; department?: string }, 'name'>; const LivechatTagsListSchema = { type: 'object', properties: { text: { type: 'string', + nullable: true, }, department: { type: 'string', @@ -683,12 +684,58 @@ const LivechatTagsListSchema = { nullable: true, }, }, - required: ['text'], additionalProperties: false, }; export const isLivechatTagsListProps = ajvQuery.compile(LivechatTagsListSchema); +const GETLivechatTagsSuccessResponseSchema = { + type: 'object', + properties: { + tags: { + type: 'array', + items: { + type: 'object', + properties: { + _id: { type: 'string' }, + name: { type: 'string' }, + description: { type: 'string' }, + numDepartments: { type: 'number' }, + departments: { type: 'array', items: { type: 'string' } }, + }, + required: ['_id', 'name', 'numDepartments', 'departments'], + additionalProperties: false, + }, + }, + count: { type: 'number' }, + offset: { type: 'number' }, + total: { type: 'number' }, + success: { type: 'boolean', enum: [true] }, + }, + required: ['tags', 'count', 'offset', 'total', 'success'], + additionalProperties: false, +}; + +export const GETLivechatTagsSuccessResponse = ajv.compile<{ tags: ILivechatTag[]; count: number; offset: number; total: number }>( + GETLivechatTagsSuccessResponseSchema, +); + +const GETLivechatTagByIdSuccessResponseSchema = { + type: 'object', + properties: { + _id: { type: 'string' }, + name: { type: 'string' }, + description: { type: 'string' }, + numDepartments: { type: 'number' }, + departments: { type: 'array', items: { type: 'string' } }, + success: { type: 'boolean', enum: [true] }, + }, + required: ['_id', 'name', 'numDepartments', 'departments', 'success'], + additionalProperties: false, +}; + +export const GETLivechatTagByIdSuccessResponse = ajv.compile(GETLivechatTagByIdSuccessResponseSchema); + type LivechatDepartmentProps = PaginatedRequest<{ text?: string; onlyMyDepartments?: booleanString; @@ -4621,14 +4668,7 @@ export type OmnichannelEndpoints = { '/v1/livechat/monitors/:username': { GET: () => ILivechatMonitor; }; - '/v1/livechat/tags': { - GET: (params: LivechatTagsListProps) => PaginatedResult<{ - tags: ILivechatTag[]; - }>; - }; - '/v1/livechat/tags/:tagId': { - GET: () => ILivechatTag; - }; + '/v1/livechat/department': { GET: (params?: LivechatDepartmentProps) => PaginatedResult<{ departments: ILivechatDepartment[]; From e790d4d35bdae56e370ee4f165e5df73721781d5 Mon Sep 17 00:00:00 2001 From: NAME-ASHWANIYADAV <22ashwaniyadav@gmail.com> Date: Sun, 22 Mar 2026 11:28:13 +0000 Subject: [PATCH 2/2] chore: add changeset and fix lint --- .changeset/migrate-livechat-tags-openapi.md | 6 ++++++ apps/meteor/ee/app/livechat-enterprise/server/api/tags.ts | 2 +- yarn.lock | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 .changeset/migrate-livechat-tags-openapi.md diff --git a/.changeset/migrate-livechat-tags-openapi.md b/.changeset/migrate-livechat-tags-openapi.md new file mode 100644 index 0000000000000..8e2f5e3084250 --- /dev/null +++ b/.changeset/migrate-livechat-tags-openapi.md @@ -0,0 +1,6 @@ +--- +'@rocket.chat/meteor': minor +'@rocket.chat/rest-typings': minor +--- + +Migrates `livechat/tags` and `livechat/tags/:tagId` REST API endpoints from legacy `addRoute` pattern to the new chained `.get()` API pattern with typed response schemas and AJV query validation. diff --git a/apps/meteor/ee/app/livechat-enterprise/server/api/tags.ts b/apps/meteor/ee/app/livechat-enterprise/server/api/tags.ts index 737257df19aa3..f7e70ab2bce02 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/api/tags.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/api/tags.ts @@ -129,5 +129,5 @@ type LivechatTagsEndpoints = ExtractRoutesFromAPI; declare module '@rocket.chat/rest-typings' { // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface - interface Endpoints extends LivechatTagsEndpoints { } + interface Endpoints extends LivechatTagsEndpoints {} } diff --git a/yarn.lock b/yarn.lock index e6b5782599475..9425d8e2d401a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11366,7 +11366,7 @@ __metadata: peerDependencies: "@rocket.chat/layout": "*" "@rocket.chat/tools": 0.2.4 - "@rocket.chat/ui-contexts": 28.0.0 + "@rocket.chat/ui-contexts": 28.0.1 "@tanstack/react-query": "*" react: "*" react-hook-form: "*"