Skip to content
Draft
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
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.
153 changes: 112 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,107 @@ 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[];
username?:string;
usernames?: string[];
};

const isGroupsInvitePropSchema = withGroupBaseProperties({
userId: {
type: 'string',
},
userIds: {
type: 'array',
items: {
type: 'string',
},
},
username: {
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 +674,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 +1364,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