Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
6 changes: 6 additions & 0 deletions .changeset/proud-pugs-teach.md
Original file line number Diff line number Diff line change
@@ -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.
184 changes: 153 additions & 31 deletions apps/meteor/app/api/server/v1/webdav.ts
Original file line number Diff line number Diff line change
@@ -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 = {
Expand All @@ -25,37 +88,96 @@ const POSTRemoveWebdavAccountSchema = {

const isPOSTRemoveWebdavAccount = ajv.compile<POSTRemoveWebdavAccount>(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<typeof webdavGetMyAccountsEndpoints>;

type WebdavRemoveAccountEndpoints = ExtractRoutesFromAPI<typeof webdavRemoveAccountEndpoints>;

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 {}
}
2 changes: 0 additions & 2 deletions packages/rest-typings/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -92,7 +91,6 @@ export interface Endpoints
AssetsEndpoints,
EmailInboxEndpoints,
MailerEndpoints,
WebdavEndpoints,
OAuthAppsEndpoint,
SubscriptionsEndpoints,
AutoTranslateEndpoints,
Expand Down
Loading