diff --git a/.changeset/eighty-ways-care.md b/.changeset/eighty-ways-care.md new file mode 100644 index 0000000000000..e058243a499f8 --- /dev/null +++ b/.changeset/eighty-ways-care.md @@ -0,0 +1,7 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/core-typings": patch +"@rocket.chat/rest-typings": patch +--- + +Add OpenAPI support for the Rocket.Chat integrations.get API endpoints by migrating to a modern chained route definition syntax and utilizing shared AJV schemas for validation to enhance API documentation and ensure type safety through response validation. diff --git a/apps/meteor/app/api/server/v1/integrations.ts b/apps/meteor/app/api/server/v1/integrations.ts index 5fb1781aa1411..e436c36f95998 100644 --- a/apps/meteor/app/api/server/v1/integrations.ts +++ b/apps/meteor/app/api/server/v1/integrations.ts @@ -1,10 +1,18 @@ -import type { IIntegration, INewIncomingIntegration, INewOutgoingIntegration } from '@rocket.chat/core-typings'; +import type { + IIntegration, + INewIncomingIntegration, + INewOutgoingIntegration, + IIncomingIntegration, + IOutgoingIntegration, +} from '@rocket.chat/core-typings'; import { Integrations, IntegrationHistory } from '@rocket.chat/models'; import { + ajv, + validateUnauthorizedErrorResponse, + validateBadRequestErrorResponse, isIntegrationsCreateProps, isIntegrationsHistoryProps, isIntegrationsRemoveProps, - isIntegrationsGetProps, isIntegrationsUpdateProps, isIntegrationsListProps, } from '@rocket.chat/rest-typings'; @@ -22,10 +30,70 @@ import { updateIncomingIntegration } from '../../../integrations/server/methods/ import { addOutgoingIntegration } from '../../../integrations/server/methods/outgoing/addOutgoingIntegration'; import { deleteOutgoingIntegration } from '../../../integrations/server/methods/outgoing/deleteOutgoingIntegration'; import { updateOutgoingIntegration } from '../../../integrations/server/methods/outgoing/updateOutgoingIntegration'; +import type { ExtractRoutesFromAPI } from '../ApiClass'; import { API } from '../api'; import { getPaginationItems } from '../helpers/getPaginationItems'; import { findOneIntegration } from '../lib/integrations'; +type IntegrationsGetProps = { integrationId: string; createdBy?: string }; + +const integrationsGetSchema = { + type: 'object', + properties: { + integrationId: { + type: 'string', + nullable: false, + }, + createdBy: { + type: 'string', + nullable: true, + }, + }, + required: ['integrationId'], +}; + +const isIntegrationsGetProps = ajv.compile(integrationsGetSchema); + +const integrationsEndpoints = API.v1.get( + 'integrations.get', + { + authRequired: true, + query: isIntegrationsGetProps, + response: { + 400: validateBadRequestErrorResponse, + 401: validateUnauthorizedErrorResponse, + 200: ajv.compile<{ integration: IIncomingIntegration | IOutgoingIntegration }>({ + type: 'object', + properties: { + integration: { + anyOf: [{ $ref: '#/components/schemas/IIncomingIntegration' }, { $ref: '#/components/schemas/IOutgoingIntegration' }], + }, + success: { type: 'boolean', enum: [true] }, + }, + required: ['integration', 'success'], + }), + }, + }, + + async function action() { + const { integrationId, createdBy } = this.queryParams; + if (!integrationId) { + return API.v1.failure('The query parameter "integrationId" is required.'); + } + + const integration = await findOneIntegration({ + userId: this.userId, + integrationId, + createdBy, + }); + + // TODO : remove this line if the database doesnt return null value for script. since script should never be null + if (!integration.script) integration.script = ''; + + return API.v1.success({ integration }); + }, +); + API.v1.addRoute( 'integrations.create', { authRequired: true, validateParams: isIntegrationsCreateProps }, @@ -208,27 +276,6 @@ API.v1.addRoute( }, ); -API.v1.addRoute( - 'integrations.get', - { authRequired: true, validateParams: isIntegrationsGetProps }, - { - async get() { - const { integrationId, createdBy } = this.queryParams; - if (!integrationId) { - return API.v1.failure('The query parameter "integrationId" is required.'); - } - - return API.v1.success({ - integration: await findOneIntegration({ - userId: this.userId, - integrationId, - createdBy, - }), - }); - }, - }, -); - API.v1.addRoute( 'integrations.update', { authRequired: true, validateParams: isIntegrationsUpdateProps }, @@ -272,3 +319,10 @@ API.v1.addRoute( }, }, ); + +export type IntegrationsEndpoints = ExtractRoutesFromAPI; + +declare module '@rocket.chat/rest-typings' { + // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface + interface Endpoints extends IntegrationsEndpoints {} +} diff --git a/packages/core-typings/src/Ajv.ts b/packages/core-typings/src/Ajv.ts index 8d828e041b1bd..55246541116d4 100644 --- a/packages/core-typings/src/Ajv.ts +++ b/packages/core-typings/src/Ajv.ts @@ -1,10 +1,14 @@ import typia from 'typia'; import type { ICustomSound } from './ICustomSound'; +import type { IIncomingIntegration, IOutgoingIntegration } from './IIntegration'; import type { IInvite } from './IInvite'; import type { IMessage } from './IMessage'; import type { IOAuthApps } from './IOAuthApps'; import type { IPermission } from './IPermission'; import type { ISubscription } from './ISubscription'; -export const schemas = typia.json.schemas<[ISubscription | IInvite | ICustomSound | IMessage | IOAuthApps | IPermission], '3.0'>(); +export const schemas = typia.json.schemas< + [ISubscription | IInvite | ICustomSound | IMessage | IOAuthApps | IPermission | IIncomingIntegration | IOutgoingIntegration], + '3.0' +>(); diff --git a/packages/core-typings/src/IIntegration.ts b/packages/core-typings/src/IIntegration.ts index 22fa520818757..853b6dc4d4f30 100644 --- a/packages/core-typings/src/IIntegration.ts +++ b/packages/core-typings/src/IIntegration.ts @@ -13,7 +13,7 @@ export interface IIncomingIntegration extends IRocketChatRecord { token: string; scriptEnabled: boolean; - script: string; + script?: string; scriptCompiled?: string; scriptError?: Pick; @@ -53,7 +53,7 @@ export interface IOutgoingIntegration extends IRocketChatRecord { token: string; scriptEnabled: boolean; - script: string; + script?: string; scriptCompiled?: string; scriptError?: Pick; runOnEdits?: boolean; diff --git a/packages/rest-typings/src/v1/integrations/IntegrationsGetProps.ts b/packages/rest-typings/src/v1/integrations/IntegrationsGetProps.ts deleted file mode 100644 index 2ab243db58203..0000000000000 --- a/packages/rest-typings/src/v1/integrations/IntegrationsGetProps.ts +++ /dev/null @@ -1,22 +0,0 @@ -import Ajv from 'ajv'; - -const ajv = new Ajv(); - -export type IntegrationsGetProps = { integrationId: string; createdBy?: string }; - -const integrationsGetSchema = { - type: 'object', - properties: { - integrationId: { - type: 'string', - nullable: false, - }, - createdBy: { - type: 'string', - nullable: true, - }, - }, - required: ['integrationId'], -}; - -export const isIntegrationsGetProps = ajv.compile(integrationsGetSchema); diff --git a/packages/rest-typings/src/v1/integrations/index.ts b/packages/rest-typings/src/v1/integrations/index.ts index a3ab979138548..78e61cf388884 100644 --- a/packages/rest-typings/src/v1/integrations/index.ts +++ b/packages/rest-typings/src/v1/integrations/index.ts @@ -3,7 +3,6 @@ export * from './integrations'; export * from './IntegrationsCreateProps'; export * from './IntegrationsHistoryProps'; export * from './IntegrationsRemoveProps'; -export * from './IntegrationsGetProps'; export * from './IntegrationsUpdateProps'; export * from './IntegrationsListProps'; export * from './hooks/IntegrationHooksAddProps'; diff --git a/packages/rest-typings/src/v1/integrations/integrations.ts b/packages/rest-typings/src/v1/integrations/integrations.ts index c2833f20bdbe0..db087f4fe990c 100644 --- a/packages/rest-typings/src/v1/integrations/integrations.ts +++ b/packages/rest-typings/src/v1/integrations/integrations.ts @@ -1,7 +1,6 @@ import type { IIntegration, IIntegrationHistory } from '@rocket.chat/core-typings'; import type { IntegrationsCreateProps } from './IntegrationsCreateProps'; -import type { IntegrationsGetProps } from './IntegrationsGetProps'; import type { IntegrationsHistoryProps } from './IntegrationsHistoryProps'; import type { IntegrationsListProps } from './IntegrationsListProps'; import type { IntegrationsRemoveProps } from './IntegrationsRemoveProps'; @@ -33,10 +32,6 @@ export type IntegrationsEndpoints = { }; }; - '/v1/integrations.get': { - GET: (params: IntegrationsGetProps) => { integration: IIntegration }; - }; - '/v1/integrations.update': { PUT: (params: IntegrationsUpdateProps) => { integration: IIntegration | null }; };