Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/eighty-ways-care.md
Original file line number Diff line number Diff line change
@@ -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.
100 changes: 77 additions & 23 deletions apps/meteor/app/api/server/v1/integrations.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<IntegrationsGetProps>(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 },
Expand Down Expand Up @@ -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 },
Expand Down Expand Up @@ -272,3 +319,10 @@ API.v1.addRoute(
},
},
);

export type IntegrationsEndpoints = ExtractRoutesFromAPI<typeof integrationsEndpoints>;

declare module '@rocket.chat/rest-typings' {
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface
interface Endpoints extends IntegrationsEndpoints {}
}
6 changes: 5 additions & 1 deletion packages/core-typings/src/Ajv.ts
Original file line number Diff line number Diff line change
@@ -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'
>();
4 changes: 2 additions & 2 deletions packages/core-typings/src/IIntegration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export interface IIncomingIntegration extends IRocketChatRecord {

token: string;
scriptEnabled: boolean;
script: string;
script?: string;
scriptCompiled?: string;
scriptError?: Pick<Error, 'name' | 'message' | 'stack'>;

Expand Down Expand Up @@ -53,7 +53,7 @@ export interface IOutgoingIntegration extends IRocketChatRecord {
token: string;

scriptEnabled: boolean;
script: string;
script?: string;
scriptCompiled?: string;
scriptError?: Pick<Error, 'name' | 'message' | 'stack'>;
runOnEdits?: boolean;
Expand Down

This file was deleted.

1 change: 0 additions & 1 deletion packages/rest-typings/src/v1/integrations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
5 changes: 0 additions & 5 deletions packages/rest-typings/src/v1/integrations/integrations.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -33,10 +32,6 @@ export type IntegrationsEndpoints = {
};
};

'/v1/integrations.get': {
GET: (params: IntegrationsGetProps) => { integration: IIntegration };
};

'/v1/integrations.update': {
PUT: (params: IntegrationsUpdateProps) => { integration: IIntegration | null };
};
Expand Down
Loading