Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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/bright-gorillas-repeat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@rocket.chat/rest-typings': minor
'@rocket.chat/meteor': minor
---

Migration groups.invites added ajv types , schema for request and response.
149 changes: 108 additions & 41 deletions apps/meteor/app/api/server/v1/groups.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import { Team, isMeteorError } from '@rocket.chat/core-services';
import type { IIntegration, IUser, IRoom, RoomType, UserStatus } from '@rocket.chat/core-typings';
import { Integrations, Messages, Rooms, Subscriptions, Uploads, Users } from '@rocket.chat/models';
import { isGroupsOnlineProps, isGroupsMessagesProps, isGroupsFilesProps } from '@rocket.chat/rest-typings';
import {
isGroupsOnlineProps,
isGroupsMessagesProps,
isGroupsFilesProps,
ajv,
validateBadRequestErrorResponse,
validateUnauthorizedErrorResponse,
validateForbiddenErrorResponse,
withGroupBaseProperties,
} from '@rocket.chat/rest-typings';
import { isTruthy } from '@rocket.chat/tools';
import { check, Match } from 'meteor/check';
import { Meteor } from 'meteor/meteor';
Expand Down Expand Up @@ -31,12 +40,103 @@ import { executeGetRoomRoles } from '../../../lib/server/methods/getRoomRoles';
import { leaveRoomMethod } from '../../../lib/server/methods/leaveRoom';
import { executeUnarchiveRoom } from '../../../lib/server/methods/unarchiveRoom';
import { normalizeMessagesForUser } from '../../../utils/server/lib/normalizeMessagesForUser';
import type { ExtractRoutesFromAPI } from '../ApiClass';
import { API } from '../api';
import { addUserToFileObj } from '../helpers/addUserToFileObj';
import { composeRoomWithLastMessage } from '../helpers/composeRoomWithLastMessage';
import { getPaginationItems } from '../helpers/getPaginationItems';
import { getUserFromParams, getUserListFromParams } from '../helpers/getUserFromParams';

type GroupsInvitesProps = {
roomId?: string;
roomName?: string;
userId?: string;
userIds?: string[];
usernames?: string[];
};

const isGroupsInvitePropSchema = withGroupBaseProperties({
userId: {
type: 'string',
},
userIds: {
type: 'array',
items: {
type: 'string',
},
},
usernames: {
type: 'array',
items: {
type: 'string',
},
},
});

const isGroupsInviteProps = ajv.compile<GroupsInvitesProps>(isGroupsInvitePropSchema);

const isGroupsInviteResponse = ajv.compile({
type: 'object',
properties: {
group: {
$ref: '#/components/schemas/IRoom',
},
success: {
type: 'boolean',
enum: [true],
},
},
required: ['group', 'success'],
additionalProperties: false,
});

const groupsEndPoints = API.v1.post(
'groups.invite',
{
authRequired: true,
body: isGroupsInviteProps,
response: {
200: isGroupsInviteResponse,
400: validateBadRequestErrorResponse,
401: validateUnauthorizedErrorResponse,
403: validateForbiddenErrorResponse,
},
},
async function action() {
const roomId = 'roomId' in this.bodyParams ? this.bodyParams.roomId : '';
const roomName = 'roomName' in this.bodyParams ? this.bodyParams.roomName : '';
const idOrName = roomId || roomName;

if (!idOrName?.trim()) {
throw new Meteor.Error('error-room-param-not-provided', 'The parameter "roomId" or "roomName" is required');
}

const { _id: rid, t: type } = (await Rooms.findOneByIdOrName(idOrName)) || {};

if (!rid || type !== 'p') {
throw new Meteor.Error('error-room-not-found', 'The required "roomId" or "roomName" param provided does not match any group');
}

const users = await getUserListFromParams(this.bodyParams);

if (!users.length) {
throw new Meteor.Error('error-empty-invite-list', 'Cannot invite if no valid users are provided');
}

await addUsersToRoomMethod(this.userId, { rid, users: users.map((u) => u.username).filter(isTruthy) }, this.user);

const room = await Rooms.findOneById(rid, { projection: API.v1.defaultFieldsToExclude });

if (!room) {
throw new Meteor.Error('error-room-not-found', 'The required "roomId" or "roomName" param provided does not match any group');
}

return API.v1.success({
group: await composeRoomWithLastMessage(room, this.userId),
});
},
);

async function getRoomFromParams(params: { roomId?: string } | { roomName?: string }): Promise<IRoom> {
if (
(!('roomId' in params) && !('roomName' in params)) ||
Expand Down Expand Up @@ -570,46 +670,6 @@ API.v1.addRoute(
},
);

API.v1.addRoute(
'groups.invite',
{ authRequired: true },
{
async post() {
const roomId = 'roomId' in this.bodyParams ? this.bodyParams.roomId : '';
const roomName = 'roomName' in this.bodyParams ? this.bodyParams.roomName : '';
const idOrName = roomId || roomName;

if (!idOrName?.trim()) {
throw new Meteor.Error('error-room-param-not-provided', 'The parameter "roomId" or "roomName" is required');
}

const { _id: rid, t: type } = (await Rooms.findOneByIdOrName(idOrName)) || {};

if (!rid || type !== 'p') {
throw new Meteor.Error('error-room-not-found', 'The required "roomId" or "roomName" param provided does not match any group');
}

const users = await getUserListFromParams(this.bodyParams);

if (!users.length) {
throw new Meteor.Error('error-empty-invite-list', 'Cannot invite if no valid users are provided');
}

await addUsersToRoomMethod(this.userId, { rid, users: users.map((u) => u.username).filter(isTruthy) }, this.user);

const room = await Rooms.findOneById(rid, { projection: API.v1.defaultFieldsToExclude });

if (!room) {
throw new Meteor.Error('error-room-not-found', 'The required "roomId" or "roomName" param provided does not match any group');
}

return API.v1.success({
group: await composeRoomWithLastMessage(room, this.userId),
});
},
},
);

API.v1.addRoute(
'groups.kick',
{ authRequired: true },
Expand Down Expand Up @@ -1300,3 +1360,10 @@ API.v1.addRoute(
},
},
);

export type GroupsHistoryEndpoints = ExtractRoutesFromAPI<typeof groupsEndPoints>;

declare module '@rocket.chat/rest-typings' {
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface
interface Endpoints extends GroupsHistoryEndpoints {}
}
5 changes: 0 additions & 5 deletions packages/rest-typings/src/v1/groups/GroupsInviteProps.ts

This file was deleted.

8 changes: 1 addition & 7 deletions packages/rest-typings/src/v1/groups/groups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import type { GroupsFilesProps } from './GroupsFilesProps';
import type { GroupsGetIntegrationsProps } from './GroupsGetIntegrationsProps';
import type { GroupsHistoryProps } from './GroupsHistoryProps';
import type { GroupsInfoProps } from './GroupsInfoProps';
import type { GroupsInviteProps } from './GroupsInviteProps';
import type { GroupsKickProps } from './GroupsKickProps';
import type { GroupsLeaveProps } from './GroupsLeaveProps';
import type { GroupsListProps } from './GroupsListProps';
Expand Down Expand Up @@ -125,7 +124,7 @@ export type GroupsEndpoints = {
'/v1/groups.addAll': {
POST: (params: GroupsAddAllProps) => {
group: IRoom;
};
}
};
'/v1/groups.getIntegrations': {
GET: (params: GroupsGetIntegrationsProps) => {
Expand All @@ -140,11 +139,6 @@ export type GroupsEndpoints = {
group: IRoom;
};
};
'/v1/groups.invite': {
POST: (params: GroupsInviteProps) => {
group: IRoom;
};
};
'/v1/groups.list': {
GET: (params: GroupsListProps) => {
count: number;
Expand Down
1 change: 0 additions & 1 deletion packages/rest-typings/src/v1/groups/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ export * from './GroupsAddModeratorProps';
export * from './GroupsAddOwnerProps';
export * from './GroupsGetIntegrationsProps';
export * from './GroupsInfoProps';
export * from './GroupsInviteProps';
export * from './GroupsListProps';
export * from './GroupsOnlineProps';
export * from './GroupsOpenProps';
Expand Down