Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
7 changes: 7 additions & 0 deletions .changeset/migrate-push-endpoints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@rocket.chat/meteor": minor
"@rocket.chat/core-typings": 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.
83 changes: 55 additions & 28 deletions apps/meteor/app/api/server/v1/push.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { Push } from '@rocket.chat/core-services';
import type { IPushToken, IPushTokenTypes } from '@rocket.chat/core-typings';
import type { IMessage, IPushNotificationConfig, 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<SuccessResult<{ data: { message: IMessage; notification: IPushNotificationConfig } }>['body']>({
type: 'object',
properties: {
data: {
type: 'object',
properties: {
message: { $ref: '#/components/schemas/IMessage' },
notification: { $ref: '#/components/schemas/IPushNotificationConfig' },
},
required: ['message', 'notification'],
additionalProperties: false,
},
success: { type: 'boolean', enum: [true] },
},
required: ['data', 'success'],
additionalProperties: false,
}),
);
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<SuccessResult<{ pushGatewayEnabled: boolean; defaultPushGateway: boolean }>['body']>({
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 All @@ -289,7 +316,7 @@ const pushTestEndpoints = API.v1.post(
response: {
400: validateBadRequestErrorResponse,
401: validateUnauthorizedErrorResponse,
200: ajv.compile<{ tokensCount: number }>({
200: ajv.compile<SuccessResult<{ tokensCount: number }>['body']>({
type: 'object',
properties: {
tokensCount: { type: 'integer' },
Expand Down
24 changes: 8 additions & 16 deletions packages/apps-engine/src/server/managers/AppListenerManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,25 +357,21 @@ export class AppListenerManager {
case AppInterface.IPreMessageSentModify:
return this.executePreMessageSentModify(data as IMessage);
case AppInterface.IPostMessageSent:
this.executePostMessageSent(data as IMessage);
return;
return this.executePostMessageSent(data as IMessage);
case AppInterface.IPostSystemMessageSent:
this.executePostSystemMessageSent(data as IMessage);
return;
return this.executePostSystemMessageSent(data as IMessage);
case AppInterface.IPreMessageDeletePrevent:
return this.executePreMessageDeletePrevent(data as IMessage);
case AppInterface.IPostMessageDeleted:
this.executePostMessageDelete(data as IMessageDeleteContext);
return;
return this.executePostMessageDelete(data as IMessageDeleteContext);
case AppInterface.IPreMessageUpdatedPrevent:
return this.executePreMessageUpdatedPrevent(data as IMessage);
case AppInterface.IPreMessageUpdatedExtend:
return this.executePreMessageUpdatedExtend(data as IMessage);
case AppInterface.IPreMessageUpdatedModify:
return this.executePreMessageUpdatedModify(data as IMessage);
case AppInterface.IPostMessageUpdated:
this.executePostMessageUpdated(data as IMessage);
return;
return this.executePostMessageUpdated(data as IMessage);
case AppInterface.IPostMessageReacted:
return this.executePostMessageReacted(data as IMessageReactionContext);
case AppInterface.IPostMessageFollowed:
Expand All @@ -394,13 +390,11 @@ export class AppListenerManager {
case AppInterface.IPreRoomCreateModify:
return this.executePreRoomCreateModify(data as IRoom);
case AppInterface.IPostRoomCreate:
this.executePostRoomCreate(data as IRoom);
return;
return this.executePostRoomCreate(data as IRoom);
case AppInterface.IPreRoomDeletePrevent:
return this.executePreRoomDeletePrevent(data as IRoom);
case AppInterface.IPostRoomDeleted:
this.executePostRoomDeleted(data as IRoom);
return;
return this.executePostRoomDeleted(data as IRoom);
case AppInterface.IPreRoomUserJoined:
return this.executePreRoomUserJoined(data as IRoomUserJoinedContext);
case AppInterface.IPostRoomUserJoined:
Expand All @@ -411,11 +405,9 @@ export class AppListenerManager {
return this.executePostRoomUserLeave(data as IRoomUserLeaveContext);
// External Components
case AppInterface.IPostExternalComponentOpened:
this.executePostExternalComponentOpened(data as IExternalComponent);
return;
return this.executePostExternalComponentOpened(data as IExternalComponent);
case AppInterface.IPostExternalComponentClosed:
this.executePostExternalComponentClosed(data as IExternalComponent);
return;
return this.executePostExternalComponentClosed(data as IExternalComponent);
case AppInterface.IUIKitInteractionHandler:
return this.executeUIKitInteraction(data as UIKitIncomingInteraction);
case AppInterface.IUIKitLivechatInteractionHandler:
Expand Down
2 changes: 2 additions & 0 deletions packages/core-typings/src/Ajv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type { IMessage } from './IMessage';
import type { IModerationAudit, IModerationReport } from './IModerationReport';
import type { IOAuthApps } from './IOAuthApps';
import type { IPermission } from './IPermission';
import type { IPushNotificationConfig } from './IPushNotificationConfig';
import type { IRole } from './IRole';
import type { IRoom, IDirectoryChannelResult } from './IRoom';
import type { ISubscription } from './ISubscription';
Expand Down Expand Up @@ -49,6 +50,7 @@ export const schemas = typia.json.schemas<
| IModerationAudit
| IModerationReport
| IBanner
| IPushNotificationConfig
),
CallHistoryItem,
ICustomUserStatus,
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;
};
};
};