Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 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/migrate-push-endpoints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@rocket.chat/meteor": minor
"@rocket.chat/rest-typings": minor
---

Migrate push REST API endpoints to chained API pattern with AJV request/response schema validation and OpenAPI documentation support.
79 changes: 53 additions & 26 deletions apps/meteor/app/api/server/v1/push.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import type { IPushToken, IPushTokenTypes } from '@rocket.chat/core-typings';
import { Messages, PushToken, Users, Rooms, Settings } from '@rocket.chat/models';
import {
ajv,
isPushGetProps,
validateNotFoundErrorResponse,
validateBadRequestErrorResponse,
validateUnauthorizedErrorResponse,
validateForbiddenErrorResponse,
} from '@rocket.chat/rest-typings';
import type { JSONSchemaType } from 'ajv';
import { Accounts } from 'meteor/accounts-base';
import { Match, check } from 'meteor/check';
import { Meteor } from 'meteor/meteor';

import { executePushTest } from '../../../../server/lib/pushConfig';
Expand Down Expand Up @@ -220,27 +220,43 @@ const pushTokenEndpoints = API.v1

return API.v1.success();
},
);

API.v1.addRoute(
'push.get',
{ authRequired: true },
{
async get() {
const params = this.queryParams;
check(
params,
Match.ObjectIncluding({
id: String,
)
.get(
'push.get',
{
authRequired: true,
query: isPushGetProps,
response: {
200: ajv.compile<{ data: { message: object; notification: object }; success: true }>({
type: 'object',
properties: {
data: {
type: 'object',
properties: {
message: { type: 'object', additionalProperties: true },
notification: { type: 'object', additionalProperties: true },
},
required: ['message', 'notification'],
additionalProperties: false,
},
success: { type: 'boolean', enum: [true] },
},
required: ['data', 'success'],
additionalProperties: false,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We avoid using additionalProperties: true because it weakens validation. Instead, we use the $ref tags from Typia or explicitly define the properties if they are simple. In this specific case, I think you should use the Typia reference props

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the push.test response schema — the type annotation is now SuccessResult<{ tokensCount: number }>['body'] (consistent with the push.token pattern), and the schema is tightened to additionalProperties: false throughout. For push.get, since the response wraps IMessage and IPushNotificationConfig — both complex core types , I've used $ref: '#/components/schemas/IMessage' and $ref: '#/components/schemas/IPushNotificationConfig' as Typia reference properties rather than inlining the full object shapes. does that work?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about this issue? why still unsolve

}),
);
400: validateBadRequestErrorResponse,
401: validateUnauthorizedErrorResponse,
},
},
async function action() {
const { id } = this.queryParams;

const receiver = await Users.findOneById(this.userId);
if (!receiver) {
throw new Error('error-user-not-found');
}

const message = await Messages.findOneById(params.id);
const message = await Messages.findOneById(id);
if (!message) {
throw new Error('error-message-not-found');
}
Expand All @@ -258,23 +274,34 @@ API.v1.addRoute(

return API.v1.success({ data });
},
},
);

API.v1.addRoute(
'push.info',
{ authRequired: true },
{
async get() {
)
.get(
'push.info',
{
authRequired: true,
response: {
200: ajv.compile<{ pushGatewayEnabled: boolean; defaultPushGateway: boolean; success: true }>({
type: 'object',
properties: {
pushGatewayEnabled: { type: 'boolean' },
defaultPushGateway: { type: 'boolean' },
success: { type: 'boolean', enum: [true] },
},
required: ['pushGatewayEnabled', 'defaultPushGateway', 'success'],
additionalProperties: false,
}),
401: validateUnauthorizedErrorResponse,
},
},
async function action() {
const defaultGateway = (await Settings.findOneById('Push_gateway', { projection: { packageValue: 1 } }))?.packageValue;
const defaultPushGateway = settings.get('Push_gateway') === defaultGateway;
return API.v1.success({
pushGatewayEnabled: settings.get('Push_enable'),
pushGatewayEnabled: settings.get<boolean>('Push_enable'),
defaultPushGateway,
});
},
},
);
);

const pushTestEndpoints = API.v1.post(
'push.test',
Expand Down
4 changes: 2 additions & 2 deletions packages/rest-typings/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import type { MiscEndpoints } from './v1/misc';
import type { ModerationEndpoints } from './v1/moderation';
import type { OmnichannelEndpoints } from './v1/omnichannel';
import type { PresenceEndpoints } from './v1/presence';
import type { PushEndpoints } from './v1/push';
import type { RolesEndpoints } from './v1/roles';
import type { RoomsEndpoints } from './v1/rooms';
import type { ServerEventsEndpoints } from './v1/server-events';
Expand Down Expand Up @@ -62,7 +61,6 @@ export interface Endpoints
ImEndpoints,
LDAPEndpoints,
RoomsEndpoints,
PushEndpoints,
RolesEndpoints,
TeamsEndpoints,
SettingsEndpoints,
Expand Down Expand Up @@ -257,5 +255,7 @@ export * from './v1/cloud';
export * from './v1/banners';
export * from './default';

export * from './v1/push';

// Export the ajv instance for use in other packages
export * from './v1/Ajv';
19 changes: 1 addition & 18 deletions packages/rest-typings/src/v1/push.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { IMessage, IPushNotificationConfig, IPushTokenTypes } from '@rocket.chat/core-typings';
import type { IPushTokenTypes } from '@rocket.chat/core-typings';

import { ajv } from './Ajv';

Expand Down Expand Up @@ -48,20 +48,3 @@ const PushGetPropsSchema = {
};

export const isPushGetProps = ajv.compile<PushGetProps>(PushGetPropsSchema);

export type PushEndpoints = {
'/v1/push.get': {
GET: (params: PushGetProps) => {
data: {
message: IMessage;
notification: IPushNotificationConfig;
};
};
};
'/v1/push.info': {
GET: () => {
pushGatewayEnabled: boolean;
defaultPushGateway: boolean;
};
};
};