Skip to content
Merged
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
143 changes: 94 additions & 49 deletions apps/meteor/ee/server/api/audit.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import type { IUser, IRoom } from '@rocket.chat/core-typings';
import { Rooms, AuditLog, ServerEvents } from '@rocket.chat/models';
import { isServerEventsAuditSettingsProps, ajv, ajvQuery } from '@rocket.chat/rest-typings';
import {
isServerEventsAuditSettingsProps,
ajv,
ajvQuery,
validateBadRequestErrorResponse,
validateUnauthorizedErrorResponse,
validateForbiddenErrorResponse,
validateNotFoundErrorResponse,
} from '@rocket.chat/rest-typings';
import type { PaginatedRequest, PaginatedResult } from '@rocket.chat/rest-typings';
import { convertSubObjectsIntoPaths } from '@rocket.chat/tools';

Expand Down Expand Up @@ -39,61 +47,98 @@ declare module '@rocket.chat/rest-typings' {
}
}

API.v1.addRoute(
const auditRoomMembersResponseSchema = ajv.compile<
PaginatedResult<{ members: Pick<IUser, '_id' | 'name' | 'username' | 'status' | '_updatedAt'>[] }>
>({
type: 'object',
properties: {
members: {
type: 'array',
items: {
type: 'object',
properties: {
_id: { type: 'string' },
name: { type: 'string' },
username: { type: 'string' },
nickname: { type: 'string' },
status: { type: 'string', enum: ['online', 'away', 'offline', 'busy', 'disabled'] },
avatarETag: { type: 'string' },
federated: { type: 'boolean' },
_updatedAt: { type: 'string', format: 'date-time' },
},
required: ['_id'],
additionalProperties: false,
},
},
count: { type: 'number' },
offset: { type: 'number' },
total: { type: 'number' },
success: { type: 'boolean', enum: [true] },
},
required: ['members', 'count', 'offset', 'total', 'success'],
additionalProperties: false,
});

API.v1.get(
'audit/rooms.members',
{
authRequired: true,
permissionsRequired: ['view-members-list-all-rooms'],
validateParams: isAuditRoomMembersProps,
query: isAuditRoomMembersProps,
license: ['auditing'],
},
{
async get() {
const { roomId, filter } = this.queryParams;
const { count: limit, offset: skip } = await getPaginationItems(this.queryParams);
const { sort } = await this.parseJsonQuery();

const room = await Rooms.findOneById<Pick<IRoom, '_id' | 'name' | 'fname'>>(roomId, { projection: { _id: 1, name: 1, fname: 1 } });
if (!room) {
return API.v1.notFound();
}

const { cursor, totalCount } = findUsersOfRoom({
rid: room._id,
filter,
skip,
limit,
...(sort?.username && { sort: { username: sort.username } }),
});

const [members, total] = await Promise.all([cursor.toArray(), totalCount]);

await AuditLog.insertOne({
ts: new Date(),
results: total,
u: {
_id: this.user._id,
username: this.user.username,
name: this.user.name,
...(this.user.avatarETag && { avatarETag: this.user.avatarETag }),
},
fields: {
msg: 'Room_members_list',
rids: [room._id],
type: 'room_member_list',
room: room.name || room.fname,
filters: filter,
},
});

return API.v1.success({
members,
count: members.length,
offset: skip,
total,
});
response: {
200: auditRoomMembersResponseSchema,
400: validateBadRequestErrorResponse,
401: validateUnauthorizedErrorResponse,
403: validateForbiddenErrorResponse,
404: validateNotFoundErrorResponse,
},
},
async function action() {
const { roomId, filter } = this.queryParams;
const { count: limit, offset: skip } = await getPaginationItems(this.queryParams);
const { sort } = await this.parseJsonQuery();

const room = await Rooms.findOneById<Pick<IRoom, '_id' | 'name' | 'fname'>>(roomId, { projection: { _id: 1, name: 1, fname: 1 } });
if (!room) {
return API.v1.notFound();
}

const { cursor, totalCount } = findUsersOfRoom({
rid: room._id,
filter,
skip,
limit,
...(sort?.username && { sort: { username: sort.username } }),
});

const [members, total] = await Promise.all([cursor.toArray(), totalCount]);

await AuditLog.insertOne({
ts: new Date(),
results: total,
u: {
_id: this.user._id,
username: this.user.username,
name: this.user.name,
...(this.user.avatarETag && { avatarETag: this.user.avatarETag }),
},
fields: {
msg: 'Room_members_list',
rids: [room._id],
type: 'room_member_list',
room: room.name || room.fname,
filters: filter,
},
});

return API.v1.success({
members,
count: members.length,
offset: skip,
total,
});
},
);

API.v1.get(
Expand Down
48 changes: 31 additions & 17 deletions apps/meteor/ee/server/api/chat.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import type { IMessage, IReadReceiptWithUser } from '@rocket.chat/core-typings';
import { License } from '@rocket.chat/license';
import {
ajv,
isChatGetMessageReadReceiptsProps,
validateBadRequestErrorResponse,
validateUnauthorizedErrorResponse,
} from '@rocket.chat/rest-typings';
import { Meteor } from 'meteor/meteor';

import { API } from '../../../server/api/api';
Expand All @@ -20,28 +26,36 @@ declare module '@rocket.chat/rest-typings' {
}
}

API.v1.addRoute(
const getMessageReadReceiptsResponseSchema = ajv.compile<{ receipts: IReadReceiptWithUser[] }>({
type: 'object',
properties: {
receipts: { type: 'array', items: { $ref: '#/components/schemas/IReadReceiptWithUser' } },
success: { type: 'boolean', enum: [true] },
},
required: ['receipts', 'success'],
additionalProperties: false,
});

API.v1.get(
'chat.getMessageReadReceipts',
{
authRequired: true,
// license: ['message-read-receipt']
query: isChatGetMessageReadReceiptsProps,
response: {
200: getMessageReadReceiptsResponseSchema,
400: validateBadRequestErrorResponse,
401: validateUnauthorizedErrorResponse,
},
},
{
async get() {
if (!License.hasModule('message-read-receipt')) {
throw new Meteor.Error('error-action-not-allowed', 'This is an enterprise feature');
}
async function action() {
if (!License.hasModule('message-read-receipt')) {
throw new Meteor.Error('error-action-not-allowed', 'This is an enterprise feature');
}

const { messageId } = this.queryParams;
if (!messageId) {
return API.v1.failure({
error: "The required 'messageId' param is missing.",
});
}
const { messageId } = this.queryParams;

return API.v1.success({
receipts: await getReadReceiptsFunction(messageId, this.userId),
});
},
return API.v1.success({
receipts: await getReadReceiptsFunction(messageId, this.userId),
});
},
);
119 changes: 92 additions & 27 deletions apps/meteor/ee/server/api/engagementDashboard/channels.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import type { IDirectMessageRoom, IRoom } from '@rocket.chat/core-typings';
import {
ajv,
validateBadRequestErrorResponse,
validateUnauthorizedErrorResponse,
validateForbiddenErrorResponse,
} from '@rocket.chat/rest-typings';
import { check, Match } from 'meteor/check';

import { API } from '../../../../server/api';
Expand All @@ -14,7 +20,7 @@ declare module '@rocket.chat/rest-typings' {
channels: {
room: {
_id: IRoom['_id'];
name: IRoom['name'] | IRoom['fname'];
name: IRoom['name'];
ts: IRoom['ts'];
t: IRoom['t'];
_updatedAt: IRoom['_updatedAt'];
Expand All @@ -32,40 +38,99 @@ declare module '@rocket.chat/rest-typings' {
}
}

API.v1.addRoute(
const channelsListResponseSchema = ajv.compile<{
channels: {
room: {
_id: IRoom['_id'];
name: IRoom['name'];
ts: IRoom['ts'];
t: IRoom['t'];
_updatedAt: IRoom['_updatedAt'];
usernames?: IDirectMessageRoom['usernames'];
};
messages: number;
lastWeekMessages: number;
diffFromLastWeek: number;
}[];
count: number;
offset: number;
total: number;
}>({
type: 'object',
properties: {
channels: {
type: 'array',
items: {
type: 'object',
properties: {
room: {
type: 'object',
properties: {
_id: { type: 'string' },
name: { type: 'string' },
ts: { type: 'string', format: 'date-time' },
t: { type: 'string' },
_updatedAt: { type: 'string', format: 'date-time' },
usernames: { type: 'array', items: { type: 'string' } },
},
required: ['_id'],
additionalProperties: false,
},
messages: { type: 'number' },
lastWeekMessages: { type: 'number' },
diffFromLastWeek: { type: 'number' },
},
required: ['room', 'messages', 'lastWeekMessages', 'diffFromLastWeek'],
additionalProperties: false,
},
},
count: { type: 'number' },
offset: { type: 'number' },
total: { type: 'number' },
success: { type: 'boolean', enum: [true] },
},
required: ['channels', 'count', 'offset', 'total', 'success'],
additionalProperties: false,
});

API.v1.get(
'engagement-dashboard/channels/list',
{
authRequired: true,
permissionsRequired: ['view-engagement-dashboard'],
license: ['engagement-dashboard'],
response: {
200: channelsListResponseSchema,
400: validateBadRequestErrorResponse,
401: validateUnauthorizedErrorResponse,
403: validateForbiddenErrorResponse,
},
},
{
async get() {
check(
this.queryParams,
Match.ObjectIncluding({
start: Match.Where(isDateISOString),
end: Match.Where(isDateISOString),
offset: Match.Maybe(String),
count: Match.Maybe(String),
}),
);
async function action() {
check(
this.queryParams,
Match.ObjectIncluding({
start: Match.Where(isDateISOString),
end: Match.Where(isDateISOString),
offset: Match.Maybe(String),
count: Match.Maybe(String),
}),
);

const { start, end } = this.queryParams;
const { offset, count } = await getPaginationItems(this.queryParams);
const { start, end } = this.queryParams;
const { offset, count } = await getPaginationItems(this.queryParams);

const { channels, total } = await findChannelsWithNumberOfMessages({
start: mapDateForAPI(start),
end: mapDateForAPI(end),
options: { offset, count },
});
const { channels, total } = await findChannelsWithNumberOfMessages({
start: mapDateForAPI(start),
end: mapDateForAPI(end),
options: { offset, count },
});

return API.v1.success({
channels,
total,
offset,
count: channels.length,
});
},
return API.v1.success({
channels,
total,
offset,
count: channels.length,
});
},
);
Loading
Loading