Skip to content

Commit cc3eb9d

Browse files
committed
chore: migrate chat.delete and chat.react to OpenAPI
1 parent 0d00b05 commit cc3eb9d

3 files changed

Lines changed: 160 additions & 147 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@rocket.chat/meteor": minor
3+
"@rocket.chat/rest-typings": minor
4+
---
5+
6+
Add OpenAPI support for the chat.delete and chat.react API endpoints by migrating to a modern chained route definition syntax and utilizing AJV schemas for body and response validation.

apps/meteor/app/api/server/v1/chat.ts

Lines changed: 153 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
isChatGetURLPreviewProps,
99
isChatUpdateProps,
1010
isChatGetThreadsListProps,
11-
isChatDeleteProps,
1211
isChatSyncMessagesProps,
1312
isChatGetMessageProps,
1413
isChatPostMessageProps,
@@ -17,7 +16,6 @@ import {
1716
isChatIgnoreUserProps,
1817
isChatGetPinnedMessagesProps,
1918
isChatGetMentionedMessagesProps,
20-
isChatReactProps,
2119
isChatGetDeletedMessagesProps,
2220
isChatSyncThreadsListProps,
2321
isChatGetThreadMessagesProps,
@@ -127,46 +125,6 @@ const isChatFollowMessageLocalProps = ajv.compile<ChatFollowMessageLocal>(ChatFo
127125

128126
const isChatUnfollowMessageLocalProps = ajv.compile<ChatUnfollowMessageLocal>(ChatUnfollowMessageLocalSchema);
129127

130-
API.v1.addRoute(
131-
'chat.delete',
132-
{ authRequired: true, validateParams: isChatDeleteProps },
133-
{
134-
async post() {
135-
const msg = await Messages.findOneById(this.bodyParams.msgId, { projection: { u: 1, rid: 1 } });
136-
137-
if (!msg) {
138-
return API.v1.failure(`No message found with the id of "${this.bodyParams.msgId}".`);
139-
}
140-
141-
if (this.bodyParams.roomId !== msg.rid) {
142-
return API.v1.failure('The room id provided does not match where the message is from.');
143-
}
144-
145-
if (
146-
this.bodyParams.asUser &&
147-
msg.u._id !== this.userId &&
148-
!(await hasPermissionAsync(this.userId, 'force-delete-message', msg.rid))
149-
) {
150-
return API.v1.failure('Unauthorized. You must have the permission "force-delete-message" to delete other\'s message as them.');
151-
}
152-
153-
const userId = this.bodyParams.asUser ? msg.u._id : this.userId;
154-
const user = await Users.findOneById(userId, { projection: { _id: 1 } });
155-
156-
if (!user) {
157-
return API.v1.failure('User not found');
158-
}
159-
160-
await deleteMessageValidatingPermission(msg, user._id);
161-
162-
return API.v1.success({
163-
_id: msg._id,
164-
ts: Date.now().toString(),
165-
message: msg,
166-
});
167-
},
168-
},
169-
);
170128

171129
API.v1.addRoute(
172130
'chat.syncMessages',
@@ -275,6 +233,56 @@ const isChatPinMessageProps = ajv.compile<ChatPinMessage>(ChatPinMessageSchema);
275233

276234
const isChatUnpinMessageProps = ajv.compile<ChatUnpinMessage>(ChatUnpinMessageSchema);
277235

236+
type ChatDeleteLocal = {
237+
msgId: string;
238+
roomId: string;
239+
asUser?: boolean;
240+
};
241+
242+
const ChatDeleteLocalSchema = {
243+
type: 'object',
244+
properties: {
245+
msgId: { type: 'string' },
246+
roomId: { type: 'string' },
247+
asUser: { type: 'boolean', nullable: true },
248+
},
249+
required: ['msgId', 'roomId'],
250+
additionalProperties: false,
251+
};
252+
253+
const isChatDeleteLocalProps = ajv.compile<ChatDeleteLocal>(ChatDeleteLocalSchema);
254+
255+
type ChatReactLocal =
256+
| { emoji: string; messageId: string; shouldReact?: boolean }
257+
| { reaction: string; messageId: string; shouldReact?: boolean };
258+
259+
const ChatReactLocalSchema = {
260+
oneOf: [
261+
{
262+
type: 'object',
263+
properties: {
264+
emoji: { type: 'string' },
265+
messageId: { type: 'string', minLength: 1 },
266+
shouldReact: { type: 'boolean', nullable: true },
267+
},
268+
required: ['emoji', 'messageId'],
269+
additionalProperties: false,
270+
},
271+
{
272+
type: 'object',
273+
properties: {
274+
reaction: { type: 'string' },
275+
messageId: { type: 'string', minLength: 1 },
276+
shouldReact: { type: 'boolean', nullable: true },
277+
},
278+
required: ['reaction', 'messageId'],
279+
additionalProperties: false,
280+
},
281+
],
282+
};
283+
284+
const isChatReactLocalProps = ajv.compile<ChatReactLocal>(ChatReactLocalSchema);
285+
278286
const chatEndpoints = API.v1
279287
.post(
280288
'chat.pinMessage',
@@ -419,6 +427,109 @@ const chatEndpoints = API.v1
419427
});
420428
},
421429
)
430+
.post(
431+
'chat.delete',
432+
{
433+
authRequired: true,
434+
body: isChatDeleteLocalProps,
435+
response: {
436+
400: validateBadRequestErrorResponse,
437+
401: validateUnauthorizedErrorResponse,
438+
200: ajv.compile<{ _id: string; ts: string; message: Pick<IMessage, '_id' | 'rid' | 'u'> }>({
439+
type: 'object',
440+
properties: {
441+
_id: { type: 'string' },
442+
ts: { type: 'string' },
443+
message: {
444+
type: 'object',
445+
properties: {
446+
_id: { type: 'string' },
447+
rid: { type: 'string' },
448+
u: { type: 'object' },
449+
},
450+
required: ['_id', 'rid', 'u'],
451+
},
452+
success: { type: 'boolean', enum: [true] },
453+
},
454+
required: ['_id', 'ts', 'message', 'success'],
455+
additionalProperties: false,
456+
}),
457+
},
458+
},
459+
async function action() {
460+
const msg = await Messages.findOneById(this.bodyParams.msgId, { projection: { u: 1, rid: 1 } });
461+
462+
if (!msg) {
463+
return API.v1.failure(`No message found with the id of "${this.bodyParams.msgId}".`);
464+
}
465+
466+
if (this.bodyParams.roomId !== msg.rid) {
467+
return API.v1.failure('The room id provided does not match where the message is from.');
468+
}
469+
470+
if (
471+
this.bodyParams.asUser &&
472+
msg.u._id !== this.userId &&
473+
!(await hasPermissionAsync(this.userId, 'force-delete-message', msg.rid))
474+
) {
475+
return API.v1.failure('Unauthorized. You must have the permission "force-delete-message" to delete other\'s message as them.');
476+
}
477+
478+
const userId = this.bodyParams.asUser ? msg.u._id : this.userId;
479+
const user = await Users.findOneById(userId, { projection: { _id: 1 } });
480+
481+
if (!user) {
482+
return API.v1.failure('User not found');
483+
}
484+
485+
await deleteMessageValidatingPermission(msg, user._id);
486+
487+
return API.v1.success({
488+
_id: msg._id,
489+
ts: Date.now().toString(),
490+
message: msg,
491+
});
492+
},
493+
)
494+
.post(
495+
'chat.react',
496+
{
497+
authRequired: true,
498+
body: isChatReactLocalProps,
499+
response: {
500+
400: validateBadRequestErrorResponse,
501+
401: validateUnauthorizedErrorResponse,
502+
200: ajv.compile<void>({
503+
type: 'object',
504+
properties: {
505+
success: {
506+
type: 'boolean',
507+
enum: [true],
508+
},
509+
},
510+
required: ['success'],
511+
additionalProperties: false,
512+
}),
513+
},
514+
},
515+
async function action() {
516+
const msg = await Messages.findOneById(this.bodyParams.messageId);
517+
518+
if (!msg) {
519+
throw new Meteor.Error('error-message-not-found', 'The provided "messageId" does not match any existing message.');
520+
}
521+
522+
const emoji = 'emoji' in this.bodyParams ? this.bodyParams.emoji : (this.bodyParams as { reaction: string }).reaction;
523+
524+
if (!emoji) {
525+
throw new Meteor.Error('error-emoji-param-not-provided', 'The required "emoji" param is missing.');
526+
}
527+
528+
await executeSetReaction(this.userId, emoji, msg, this.bodyParams.shouldReact);
529+
530+
return API.v1.success();
531+
},
532+
)
422533
.post(
423534
'chat.starMessage',
424535
{
@@ -653,29 +764,7 @@ API.v1.addRoute(
653764
},
654765
);
655766

656-
API.v1.addRoute(
657-
'chat.react',
658-
{ authRequired: true, validateParams: isChatReactProps },
659-
{
660-
async post() {
661-
const msg = await Messages.findOneById(this.bodyParams.messageId);
662-
663-
if (!msg) {
664-
throw new Meteor.Error('error-message-not-found', 'The provided "messageId" does not match any existing message.');
665-
}
666-
667-
const emoji = 'emoji' in this.bodyParams ? this.bodyParams.emoji : (this.bodyParams as { reaction: string }).reaction;
668767

669-
if (!emoji) {
670-
throw new Meteor.Error('error-emoji-param-not-provided', 'The required "emoji" param is missing.');
671-
}
672-
673-
await executeSetReaction(this.userId, emoji, msg, this.bodyParams.shouldReact);
674-
675-
return API.v1.success();
676-
},
677-
},
678-
);
679768

680769
API.v1.addRoute(
681770
'chat.reportMessage',

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

Lines changed: 1 addition & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -218,79 +218,6 @@ const ChatSyncThreadsListSchema = {
218218

219219
export const isChatSyncThreadsListProps = ajv.compile<ChatSyncThreadsList>(ChatSyncThreadsListSchema);
220220

221-
type ChatDelete = {
222-
msgId: IMessage['_id'];
223-
roomId: IRoom['_id'];
224-
asUser?: boolean;
225-
};
226-
227-
const ChatDeleteSchema = {
228-
type: 'object',
229-
properties: {
230-
msgId: {
231-
type: 'string',
232-
},
233-
roomId: {
234-
type: 'string',
235-
},
236-
asUser: {
237-
type: 'boolean',
238-
nullable: true,
239-
},
240-
},
241-
required: ['msgId', 'roomId'],
242-
additionalProperties: false,
243-
};
244-
245-
export const isChatDeleteProps = ajv.compile<ChatDelete>(ChatDeleteSchema);
246-
247-
type ChatReact =
248-
| { emoji: string; messageId: IMessage['_id']; shouldReact?: boolean }
249-
| { reaction: string; messageId: IMessage['_id']; shouldReact?: boolean };
250-
251-
const ChatReactSchema = {
252-
oneOf: [
253-
{
254-
type: 'object',
255-
properties: {
256-
emoji: {
257-
type: 'string',
258-
},
259-
messageId: {
260-
type: 'string',
261-
minLength: 1,
262-
},
263-
shouldReact: {
264-
type: 'boolean',
265-
nullable: true,
266-
},
267-
},
268-
required: ['emoji', 'messageId'],
269-
additionalProperties: false,
270-
},
271-
{
272-
type: 'object',
273-
properties: {
274-
reaction: {
275-
type: 'string',
276-
},
277-
messageId: {
278-
type: 'string',
279-
minLength: 1,
280-
},
281-
shouldReact: {
282-
type: 'boolean',
283-
nullable: true,
284-
},
285-
},
286-
required: ['reaction', 'messageId'],
287-
additionalProperties: false,
288-
},
289-
],
290-
};
291-
292-
export const isChatReactProps = ajv.compile<ChatReact>(ChatReactSchema);
293-
294221
/**
295222
* The param `ignore` cannot be boolean, since this is a GET method. Use strings 'true' or 'false' instead.
296223
* @param {string} ignore
@@ -920,16 +847,7 @@ export type ChatEndpoints = {
920847
};
921848
};
922849
};
923-
'/v1/chat.delete': {
924-
POST: (params: ChatDelete) => {
925-
_id: string;
926-
ts: string;
927-
message: Pick<IMessage, '_id' | 'rid' | 'u'>;
928-
};
929-
};
930-
'/v1/chat.react': {
931-
POST: (params: ChatReact) => void;
932-
};
850+
933851
'/v1/chat.ignoreUser': {
934852
GET: (params: ChatIgnoreUser) => void;
935853
};

0 commit comments

Comments
 (0)