Skip to content

Commit 8a3c50a

Browse files
Merge branch 'develop' into chore/room-manager
2 parents 7c0d877 + d4226a5 commit 8a3c50a

File tree

11 files changed

+221
-72
lines changed

11 files changed

+221
-72
lines changed

.changeset/big-bees-crash.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@rocket.chat/meteor': minor
3+
---
4+
5+
Adds theme palette to Application error page

.changeset/proud-pugs-teach.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@rocket.chat/meteor": patch
3+
"@rocket.chat/rest-typings": patch
4+
---
5+
6+
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.

apps/meteor/app/api/server/ApiClass.spec.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,23 @@ it('Should return the expected type', () => {
3939
>;
4040
true as test;
4141
});
42+
43+
describe('ExtractRoutesFromAPI', () => {
44+
it('Should extract correct function signature when query is not present', () => {
45+
type APIWithNeverQuery = APIClass<
46+
'/v1',
47+
{
48+
method: 'GET';
49+
path: '/v1/endpoint.test';
50+
response: {
51+
200: ValidateFunction<unknown>;
52+
};
53+
authRequired: true;
54+
}
55+
>;
56+
type ExpectedFunctionSignature = Expect<
57+
ShallowEqual<ExtractRoutesFromAPI<APIWithNeverQuery>['/v1/endpoint.test']['GET'], () => unknown>
58+
>;
59+
true as ExpectedFunctionSignature;
60+
});
61+
});

apps/meteor/app/api/server/ApiClass.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@ type ConvertToRoute<TRoute extends MinimalRoute> = {
7373
[K in TRoute['path']]: {
7474
[K2 in Extract<TRoute, { path: K }>['method']]: K2 extends 'GET'
7575
? (
76-
params: ExtractValidation<Extract<TRoute, { path: K; method: K2 }>['query']>,
76+
...args: [ExtractValidation<Extract<TRoute, { path: K; method: K2 }>['query']>] extends [never]
77+
? [params?: never]
78+
: [params: ExtractValidation<Extract<TRoute, { path: K; method: K2 }>['query']>]
7779
) => ExtractValidation<Extract<TRoute, { path: K; method: K2 }>['response'][200]>
7880
: K2 extends 'POST'
7981
? (
Lines changed: 153 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,78 @@
11
import { api } from '@rocket.chat/core-services';
2+
import type { IWebdavAccount, IWebdavAccountIntegration } from '@rocket.chat/core-typings';
23
import { WebdavAccounts } from '@rocket.chat/models';
3-
import Ajv from 'ajv';
4+
import { ajv } from '@rocket.chat/rest-typings/src/v1/Ajv';
5+
import type { DeleteResult } from 'mongodb';
46

7+
import type { ExtractRoutesFromAPI } from '../ApiClass';
58
import { API } from '../api';
69
import { findWebdavAccountsByUserId } from '../lib/webdav';
710

8-
// TO-DO: remove this AJV instance and import one from the core-typings
9-
const ajv = new Ajv({ coerceTypes: true });
11+
const webdavGetMyAccountsEndpoints = API.v1.get(
12+
'webdav.getMyAccounts',
13+
{
14+
authRequired: true,
15+
response: {
16+
200: ajv.compile<{
17+
accounts: IWebdavAccountIntegration[];
18+
}>({
19+
type: 'object',
20+
properties: {
21+
accounts: {
22+
type: 'array',
23+
items: {
24+
type: 'object',
25+
properties: {
26+
_id: {
27+
type: 'string',
28+
},
29+
serverURL: {
30+
type: 'string',
31+
},
32+
username: {
33+
type: 'string',
34+
},
35+
name: {
36+
type: 'string',
37+
},
38+
},
39+
required: ['_id', 'serverURL', 'username', 'name'],
40+
additionalProperties: false,
41+
},
42+
},
43+
success: {
44+
type: 'boolean',
45+
description: 'Indicates if the request was successful.',
46+
},
47+
},
48+
required: ['success', 'accounts'],
49+
additionalProperties: false,
50+
}),
51+
401: ajv.compile({
52+
type: 'object',
53+
properties: {
54+
message: {
55+
type: 'string',
56+
},
57+
success: {
58+
type: 'boolean',
59+
description: 'Indicates if the request was successful.',
60+
},
61+
},
62+
required: ['success', 'message'],
63+
additionalProperties: false,
64+
}),
65+
},
66+
},
67+
async function action() {
68+
return API.v1.success({
69+
accounts: await findWebdavAccountsByUserId({ uid: this.userId }),
70+
});
71+
},
72+
);
1073

1174
type POSTRemoveWebdavAccount = {
12-
accountId: string;
75+
accountId: IWebdavAccount['_id'];
1376
};
1477

1578
const POSTRemoveWebdavAccountSchema = {
@@ -25,37 +88,96 @@ const POSTRemoveWebdavAccountSchema = {
2588

2689
const isPOSTRemoveWebdavAccount = ajv.compile<POSTRemoveWebdavAccount>(POSTRemoveWebdavAccountSchema);
2790

28-
API.v1.addRoute(
29-
'webdav.getMyAccounts',
30-
{ authRequired: true },
31-
{
32-
async get() {
33-
return API.v1.success({
34-
accounts: await findWebdavAccountsByUserId({ uid: this.userId }),
35-
});
36-
},
37-
},
38-
);
39-
40-
API.v1.addRoute(
91+
const webdavRemoveAccountEndpoints = API.v1.post(
4192
'webdav.removeWebdavAccount',
4293
{
4394
authRequired: true,
4495
validateParams: isPOSTRemoveWebdavAccount,
45-
},
46-
{
47-
async post() {
48-
const { accountId } = this.bodyParams;
49-
50-
const removed = await WebdavAccounts.removeByUserAndId(accountId, this.userId);
51-
if (removed) {
52-
void api.broadcast('notify.webdav', this.userId, {
53-
type: 'removed',
54-
account: { _id: accountId },
55-
});
56-
}
57-
58-
return API.v1.success({ result: removed });
96+
body: isPOSTRemoveWebdavAccount,
97+
response: {
98+
200: ajv.compile<{
99+
result: DeleteResult;
100+
}>({
101+
type: 'object',
102+
properties: {
103+
result: {
104+
type: 'object',
105+
properties: {
106+
acknowledged: {
107+
type: 'boolean',
108+
},
109+
deletedCount: {
110+
type: 'integer',
111+
},
112+
},
113+
required: ['acknowledged', 'deletedCount'],
114+
additionalProperties: false,
115+
},
116+
success: {
117+
type: 'boolean',
118+
description: 'Indicates if the request was successful.',
119+
},
120+
},
121+
required: ['result', 'success'],
122+
additionalProperties: false,
123+
}),
124+
400: ajv.compile({
125+
type: 'object',
126+
properties: {
127+
errorType: {
128+
type: 'string',
129+
},
130+
error: {
131+
type: 'string',
132+
},
133+
success: {
134+
type: 'boolean',
135+
description: 'Indicates if the request was successful.',
136+
},
137+
},
138+
required: ['success', 'errorType', 'error'],
139+
additionalProperties: false,
140+
}),
141+
401: ajv.compile({
142+
type: 'object',
143+
properties: {
144+
message: {
145+
type: 'string',
146+
},
147+
success: {
148+
type: 'boolean',
149+
description: 'Indicates if the request was successful.',
150+
},
151+
},
152+
required: ['success', 'message'],
153+
additionalProperties: false,
154+
}),
59155
},
60156
},
157+
async function action() {
158+
const { accountId } = this.bodyParams;
159+
160+
const removed = await WebdavAccounts.removeByUserAndId(accountId, this.userId);
161+
if (removed) {
162+
void api.broadcast('notify.webdav', this.userId, {
163+
type: 'removed',
164+
account: { _id: accountId },
165+
});
166+
}
167+
168+
return API.v1.success({ result: removed });
169+
},
61170
);
171+
172+
type WebdavGetMyAccountsEndpoints = ExtractRoutesFromAPI<typeof webdavGetMyAccountsEndpoints>;
173+
174+
type WebdavRemoveAccountEndpoints = ExtractRoutesFromAPI<typeof webdavRemoveAccountEndpoints>;
175+
176+
export type WebdavEndpoints = WebdavGetMyAccountsEndpoints | WebdavRemoveAccountEndpoints;
177+
178+
declare module '@rocket.chat/rest-typings' {
179+
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface
180+
interface Endpoints extends WebdavGetMyAccountsEndpoints {}
181+
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface
182+
interface Endpoints extends WebdavRemoveAccountEndpoints {}
183+
}

apps/meteor/client/views/root/AppErrorPage.tsx

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { Box, States, StatesAction, StatesActions, StatesIcon, StatesSubtitle, StatesTitle } from '@rocket.chat/fuselage';
1+
import { Box, PaletteStyleTag, States, StatesAction, StatesActions, StatesIcon, StatesSubtitle, StatesTitle } from '@rocket.chat/fuselage';
2+
import { useThemeMode } from '@rocket.chat/ui-theming';
23
import type { ErrorInfo, ReactElement } from 'react';
34

45
type AppErrorPageProps = {
@@ -8,30 +9,35 @@ type AppErrorPageProps = {
89
};
910

1011
const AppErrorPage = (_props: AppErrorPageProps): ReactElement => {
12+
const [, , theme] = useThemeMode();
13+
1114
return (
12-
<Box display='flex' justifyContent='center' height='full' backgroundColor='surface'>
13-
<States>
14-
<StatesIcon name='error-circle' />
15-
<StatesTitle>Application Error</StatesTitle>
16-
<StatesSubtitle>The application GUI just crashed.</StatesSubtitle>
15+
<>
16+
<PaletteStyleTag theme={theme} tagId='app-error-palette' />
17+
<Box display='flex' justifyContent='center' height='full' backgroundColor='surface'>
18+
<States>
19+
<StatesIcon name='error-circle' />
20+
<StatesTitle>Application Error</StatesTitle>
21+
<StatesSubtitle>The application GUI just crashed.</StatesSubtitle>
1722

18-
<StatesActions>
19-
<StatesAction
20-
onClick={() => {
21-
const result = indexedDB.deleteDatabase('MeteorDynamicImportCache');
22-
result.onsuccess = () => {
23-
window.location.reload();
24-
};
25-
result.onerror = () => {
26-
window.location.reload();
27-
};
28-
}}
29-
>
30-
Reload Application
31-
</StatesAction>
32-
</StatesActions>
33-
</States>
34-
</Box>
23+
<StatesActions>
24+
<StatesAction
25+
onClick={() => {
26+
const result = indexedDB.deleteDatabase('MeteorDynamicImportCache');
27+
result.onsuccess = () => {
28+
window.location.reload();
29+
};
30+
result.onerror = () => {
31+
window.location.reload();
32+
};
33+
}}
34+
>
35+
Reload Application
36+
</StatesAction>
37+
</StatesActions>
38+
</States>
39+
</Box>
40+
</>
3541
);
3642
};
3743

ee/apps/queue-worker/Dockerfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ COPY ./packages/tsconfig packages/tsconfig
7171
COPY ./packages/ui-kit/package.json packages/ui-kit/package.json
7272
COPY ./packages/ui-kit/dist packages/ui-kit/dist
7373

74+
COPY ./packages/i18n/package.json packages/i18n/package.json
75+
COPY ./packages/i18n/dist packages/i18n/dist
76+
7477
COPY ./ee/apps/${SERVICE}/dist .
7578

7679
COPY ./package.json .

ee/packages/omnichannel-services/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"@rocket.chat/core-services": "workspace:^",
1616
"@rocket.chat/core-typings": "workspace:^",
1717
"@rocket.chat/emitter": "~0.31.25",
18+
"@rocket.chat/i18n": "workspace:^",
1819
"@rocket.chat/logger": "workspace:^",
1920
"@rocket.chat/model-typings": "workspace:^",
2021
"@rocket.chat/models": "workspace:^",

packages/rest-typings/src/index.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ import type { UsersEndpoints } from './v1/users';
4949
import type { VideoConferenceEndpoints } from './v1/videoConference';
5050
import type { VoipEndpoints } from './v1/voip';
5151
import type { VoipFreeSwitchEndpoints } from './v1/voip-freeswitch';
52-
import type { WebdavEndpoints } from './v1/webdav';
5352

5453
// eslint-disable-next-line @typescript-eslint/naming-convention
5554
export interface Endpoints
@@ -92,7 +91,6 @@ export interface Endpoints
9291
AssetsEndpoints,
9392
EmailInboxEndpoints,
9493
MailerEndpoints,
95-
WebdavEndpoints,
9694
OAuthAppsEndpoint,
9795
SubscriptionsEndpoints,
9896
AutoTranslateEndpoints,

packages/rest-typings/src/v1/webdav.ts

Lines changed: 0 additions & 15 deletions
This file was deleted.

0 commit comments

Comments
 (0)