diff --git a/.changeset/migrate-push-endpoints.md b/.changeset/migrate-push-endpoints.md new file mode 100644 index 0000000000000..53be2c5331604 --- /dev/null +++ b/.changeset/migrate-push-endpoints.md @@ -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. diff --git a/apps/meteor/app/api/server/v1/push.ts b/apps/meteor/app/api/server/v1/push.ts index 2c72d37d7e5b3..2e53b949300df 100644 --- a/apps/meteor/app/api/server/v1/push.ts +++ b/apps/meteor/app/api/server/v1/push.ts @@ -1,8 +1,9 @@ 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, @@ -10,7 +11,6 @@ import { } 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'; @@ -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['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'); } @@ -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['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('Push_enable'), defaultPushGateway, }); }, - }, -); + ); const pushTestEndpoints = API.v1.post( 'push.test', @@ -289,7 +316,7 @@ const pushTestEndpoints = API.v1.post( response: { 400: validateBadRequestErrorResponse, 401: validateUnauthorizedErrorResponse, - 200: ajv.compile<{ tokensCount: number }>({ + 200: ajv.compile['body']>({ type: 'object', properties: { tokensCount: { type: 'integer' }, diff --git a/packages/apps-engine/src/server/managers/AppListenerManager.ts b/packages/apps-engine/src/server/managers/AppListenerManager.ts index 327bc949ba0ac..6dd22f056e6c4 100644 --- a/packages/apps-engine/src/server/managers/AppListenerManager.ts +++ b/packages/apps-engine/src/server/managers/AppListenerManager.ts @@ -357,16 +357,13 @@ 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: @@ -374,8 +371,7 @@ export class AppListenerManager { 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: @@ -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: @@ -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: diff --git a/packages/core-typings/src/Ajv.ts b/packages/core-typings/src/Ajv.ts index e33f94e99e2ae..d612146a71bd6 100644 --- a/packages/core-typings/src/Ajv.ts +++ b/packages/core-typings/src/Ajv.ts @@ -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'; @@ -49,6 +50,7 @@ export const schemas = typia.json.schemas< | IModerationAudit | IModerationReport | IBanner + | IPushNotificationConfig ), CallHistoryItem, ICustomUserStatus, diff --git a/packages/rest-typings/src/index.ts b/packages/rest-typings/src/index.ts index deb8359b1f896..7bfb76e9d734e 100644 --- a/packages/rest-typings/src/index.ts +++ b/packages/rest-typings/src/index.ts @@ -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'; @@ -62,7 +61,6 @@ export interface Endpoints ImEndpoints, LDAPEndpoints, RoomsEndpoints, - PushEndpoints, RolesEndpoints, TeamsEndpoints, SettingsEndpoints, @@ -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'; diff --git a/packages/rest-typings/src/v1/push.ts b/packages/rest-typings/src/v1/push.ts index f09cfc892a611..0b3f83a235713 100644 --- a/packages/rest-typings/src/v1/push.ts +++ b/packages/rest-typings/src/v1/push.ts @@ -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'; @@ -48,20 +48,3 @@ const PushGetPropsSchema = { }; export const isPushGetProps = ajv.compile(PushGetPropsSchema); - -export type PushEndpoints = { - '/v1/push.get': { - GET: (params: PushGetProps) => { - data: { - message: IMessage; - notification: IPushNotificationConfig; - }; - }; - }; - '/v1/push.info': { - GET: () => { - pushGatewayEnabled: boolean; - defaultPushGateway: boolean; - }; - }; -};