diff --git a/.changeset/proud-pugs-teach.md b/.changeset/proud-pugs-teach.md new file mode 100644 index 0000000000000..504d7e73369ea --- /dev/null +++ b/.changeset/proud-pugs-teach.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/rest-typings": patch +--- + +Add OpenAPI support for the Rocket.Chat Webdav 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/webdav.ts b/apps/meteor/app/api/server/v1/webdav.ts index 2bdd2ba8f2e46..43790aaea8670 100644 --- a/apps/meteor/app/api/server/v1/webdav.ts +++ b/apps/meteor/app/api/server/v1/webdav.ts @@ -1,15 +1,78 @@ import { api } from '@rocket.chat/core-services'; +import type { IWebdavAccount, IWebdavAccountIntegration } from '@rocket.chat/core-typings'; import { WebdavAccounts } from '@rocket.chat/models'; -import Ajv from 'ajv'; +import { ajv } from '@rocket.chat/rest-typings/src/v1/Ajv'; +import type { DeleteResult } from 'mongodb'; +import type { ExtractRoutesFromAPI } from '../ApiClass'; import { API } from '../api'; import { findWebdavAccountsByUserId } from '../lib/webdav'; -// TO-DO: remove this AJV instance and import one from the core-typings -const ajv = new Ajv({ coerceTypes: true }); +const webdavGetMyAccountsEndpoints = API.v1.get( + 'webdav.getMyAccounts', + { + authRequired: true, + response: { + 200: ajv.compile<{ + accounts: IWebdavAccountIntegration[]; + }>({ + type: 'object', + properties: { + accounts: { + type: 'array', + items: { + type: 'object', + properties: { + _id: { + type: 'string', + }, + serverURL: { + type: 'string', + }, + username: { + type: 'string', + }, + name: { + type: 'string', + }, + }, + required: ['_id', 'serverURL', 'username', 'name'], + additionalProperties: false, + }, + }, + success: { + type: 'boolean', + description: 'Indicates if the request was successful.', + }, + }, + required: ['success', 'accounts'], + additionalProperties: false, + }), + 401: ajv.compile({ + type: 'object', + properties: { + message: { + type: 'string', + }, + success: { + type: 'boolean', + description: 'Indicates if the request was successful.', + }, + }, + required: ['success', 'message'], + additionalProperties: false, + }), + }, + }, + async function action() { + return API.v1.success({ + accounts: await findWebdavAccountsByUserId({ uid: this.userId }), + }); + }, +); type POSTRemoveWebdavAccount = { - accountId: string; + accountId: IWebdavAccount['_id']; }; const POSTRemoveWebdavAccountSchema = { @@ -25,37 +88,96 @@ const POSTRemoveWebdavAccountSchema = { const isPOSTRemoveWebdavAccount = ajv.compile(POSTRemoveWebdavAccountSchema); -API.v1.addRoute( - 'webdav.getMyAccounts', - { authRequired: true }, - { - async get() { - return API.v1.success({ - accounts: await findWebdavAccountsByUserId({ uid: this.userId }), - }); - }, - }, -); - -API.v1.addRoute( +const webdavRemoveAccountEndpoints = API.v1.post( 'webdav.removeWebdavAccount', { authRequired: true, validateParams: isPOSTRemoveWebdavAccount, - }, - { - async post() { - const { accountId } = this.bodyParams; - - const removed = await WebdavAccounts.removeByUserAndId(accountId, this.userId); - if (removed) { - void api.broadcast('notify.webdav', this.userId, { - type: 'removed', - account: { _id: accountId }, - }); - } - - return API.v1.success({ result: removed }); + body: isPOSTRemoveWebdavAccount, + response: { + 200: ajv.compile<{ + result: DeleteResult; + }>({ + type: 'object', + properties: { + result: { + type: 'object', + properties: { + acknowledged: { + type: 'boolean', + }, + deletedCount: { + type: 'integer', + }, + }, + required: ['acknowledged', 'deletedCount'], + additionalProperties: false, + }, + success: { + type: 'boolean', + description: 'Indicates if the request was successful.', + }, + }, + required: ['result', 'success'], + additionalProperties: false, + }), + 400: ajv.compile({ + type: 'object', + properties: { + errorType: { + type: 'string', + }, + error: { + type: 'string', + }, + success: { + type: 'boolean', + description: 'Indicates if the request was successful.', + }, + }, + required: ['success', 'errorType', 'error'], + additionalProperties: false, + }), + 401: ajv.compile({ + type: 'object', + properties: { + message: { + type: 'string', + }, + success: { + type: 'boolean', + description: 'Indicates if the request was successful.', + }, + }, + required: ['success', 'message'], + additionalProperties: false, + }), }, }, + async function action() { + const { accountId } = this.bodyParams; + + const removed = await WebdavAccounts.removeByUserAndId(accountId, this.userId); + if (removed) { + void api.broadcast('notify.webdav', this.userId, { + type: 'removed', + account: { _id: accountId }, + }); + } + + return API.v1.success({ result: removed }); + }, ); + +type WebdavGetMyAccountsEndpoints = ExtractRoutesFromAPI; + +type WebdavRemoveAccountEndpoints = ExtractRoutesFromAPI; + +export type WebdavEndpoints = WebdavGetMyAccountsEndpoints | WebdavRemoveAccountEndpoints; + +declare module '@rocket.chat/rest-typings' { + // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface + interface Endpoints extends WebdavGetMyAccountsEndpoints {} + // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface + interface Endpoints extends WebdavRemoveAccountEndpoints {} +} diff --git a/packages/rest-typings/src/index.ts b/packages/rest-typings/src/index.ts index 18e3fd7f75d9b..dcdd48c12bab7 100644 --- a/packages/rest-typings/src/index.ts +++ b/packages/rest-typings/src/index.ts @@ -49,7 +49,6 @@ import type { UsersEndpoints } from './v1/users'; import type { VideoConferenceEndpoints } from './v1/videoConference'; import type { VoipEndpoints } from './v1/voip'; import type { VoipFreeSwitchEndpoints } from './v1/voip-freeswitch'; -import type { WebdavEndpoints } from './v1/webdav'; // eslint-disable-next-line @typescript-eslint/naming-convention export interface Endpoints @@ -92,7 +91,6 @@ export interface Endpoints AssetsEndpoints, EmailInboxEndpoints, MailerEndpoints, - WebdavEndpoints, OAuthAppsEndpoint, SubscriptionsEndpoints, AutoTranslateEndpoints, diff --git a/packages/rest-typings/src/v1/webdav.ts b/packages/rest-typings/src/v1/webdav.ts deleted file mode 100644 index 190d7d2f02cd1..0000000000000 --- a/packages/rest-typings/src/v1/webdav.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { IWebdavAccount, IWebdavAccountIntegration } from '@rocket.chat/core-typings'; -import type { DeleteResult } from 'mongodb'; - -export type WebdavEndpoints = { - '/v1/webdav.getMyAccounts': { - GET: () => { - accounts: IWebdavAccountIntegration[]; - }; - }; - '/v1/webdav.removeWebdavAccount': { - POST: (params: { accountId: IWebdavAccount['_id'] }) => { - result: DeleteResult; - }; - }; -};