[FIX] New specific endpoint for contactChatHistoryMessages with right permissions (#23533)

Co-authored-by: Kevin Aleman <kevin.aleman@rocket.chat>
Co-authored-by: Guilherme Gazzo <guilhermegazzo@gmail.com>
pull/23675/head^2
Tiago Evangelista Pinto 4 years ago committed by GitHub
parent 34cb351a15
commit 7c8b4e5b11
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 28
      app/livechat/client/views/app/tabbar/contactChatHistoryMessages.html
  2. 24
      app/livechat/client/views/app/tabbar/contactChatHistoryMessages.js
  3. 47
      app/livechat/imports/server/rest/visitors.ts
  4. 1
      app/livechat/server/api.js
  5. 1
      app/livechat/server/api/lib/visitors.js
  6. 13
      app/models/server/raw/Messages.js
  7. 2
      client/components/Omnichannel/hooks/useAgentsList.ts
  8. 2
      client/components/Omnichannel/hooks/useDepartmentsList.ts
  9. 2
      client/views/admin/customEmoji/CustomEmoji.tsx
  10. 9
      definition/rest/helpers/PaginatedRequest.ts
  11. 4
      definition/rest/helpers/PaginatedResult.ts
  12. 6
      definition/rest/v1/emojiCustom.ts
  13. 72
      definition/rest/v1/omnichannel.ts
  14. 10
      ee/app/livechat-enterprise/server/api/departments.js
  15. 1
      server/services/authorization/canAccessRoomLivechat.ts

@ -33,17 +33,25 @@
</div>
</div>
{{else}}
<div class="flex-tab__result js-list">
<ul class="list clearfix contact-chat-history-messages-list">
{{# with messageContext}}
{{#each msg in messages}}{{> message msg=msg room=room subscription=subscription settings=settings u=u}}{{/each}}
{{/with}}
{{#if hasError}}
<div class="rocket-search-result">
<div class="contact-chat-history-search-empty">
<h2>{{_ "Not_found_or_not_allowed"}}</h2>
</div>
</div>
{{else}}
<div class="flex-tab__result js-list">
<ul class="list clearfix contact-chat-history-messages-list">
{{# with messageContext}}
{{#each msg in messages}}{{> message msg=msg room=room subscription=subscription settings=settings u=u}}{{/each}}
{{/with}}
{{#if isLoading}}
{{> loading}}
{{/if}}
</ul>
</div>
{{#if isLoading}}
{{> loading}}
{{/if}}
</ul>
</div>
{{/if}}
{{/if}}
</section>
</template>

@ -36,6 +36,12 @@ Template.contactChatHistoryMessages.helpers({
empty() {
return Template.instance().messages.get().length === 0;
},
hasError() {
return Template.instance().hasError.get();
},
error() {
return Template.instance().error.get();
},
});
Template.contactChatHistoryMessages.events({
@ -72,15 +78,23 @@ Template.contactChatHistoryMessages.onCreated(function() {
this.searchTerm = new ReactiveVar('');
this.isLoading = new ReactiveVar(true);
this.limit = new ReactiveVar(MESSAGES_LIMIT);
this.hasError = new ReactiveVar(false);
this.error = new ReactiveVar(null);
this.loadMessages = async (url) => {
this.isLoading.set(true);
const offset = this.offset.get();
const { messages, total } = await APIClient.v1.get(url);
this.messages.set(offset === 0 ? messages : this.messages.get().concat(messages));
this.hasMore.set(total > this.messages.get().length);
this.isLoading.set(false);
try {
const { messages, total } = await APIClient.v1.get(url);
this.messages.set(offset === 0 ? messages : this.messages.get().concat(messages));
this.hasMore.set(total > this.messages.get().length);
} catch (e) {
this.hasError.set(true);
this.error.set(e);
} finally {
this.isLoading.set(false);
}
};
this.autorun(() => {
@ -92,7 +106,7 @@ Template.contactChatHistoryMessages.onCreated(function() {
return this.loadMessages(`chat.search/?roomId=${ this.rid }&searchText=${ searchTerm }&count=${ limit }&offset=${ offset }&sort={"ts": 1}`);
}
this.loadMessages(`channels.messages/?roomId=${ this.rid }&count=${ limit }&offset=${ offset }&sort={"ts": 1}&query={"$or": [ {"t": {"$exists": false} }, {"t": "livechat-close"} ] }`);
this.loadMessages(`livechat/${ this.rid }/messages?count=${ limit }&offset=${ offset }&sort={"ts": 1}`);
});
this.autorun(() => {

@ -0,0 +1,47 @@
import { check } from 'meteor/check';
import { API } from '../../../../api/server';
import { LivechatRooms } from '../../../../models/server';
import { Messages } from '../../../../models/server/raw';
import { normalizeMessagesForUser } from '../../../../utils/server/lib/normalizeMessagesForUser';
import { canAccessRoom } from '../../../../authorization/server';
import { IMessage } from '../../../../../definition/IMessage';
API.v1.addRoute('livechat/:rid/messages', { authRequired: true, permissionsRequired: ['view-l-room'] }, {
async get() {
check(this.urlParams, {
rid: String,
});
const { offset, count } = this.getPaginationItems();
const { sort } = this.parseJsonQuery();
const room = LivechatRooms.findOneById(this.urlParams.rid);
if (!room) {
throw new Error('invalid-room');
}
if (!canAccessRoom(room, this.user)) {
throw new Error('not-allowed');
}
const cursor = Messages.findLivechatClosedMessages(this.urlParams.rid, {
sort: sort || { ts: -1 },
skip: offset,
limit: count,
});
const total = await cursor.count();
const messages = await cursor.toArray() as IMessage[];
return API.v1.success({
messages: normalizeMessagesForUser(messages, this.userId),
offset,
count,
total,
});
},
});

@ -11,6 +11,7 @@ import '../imports/server/rest/triggers.js';
import '../imports/server/rest/integrations.js';
import '../imports/server/rest/messages.js';
import '../imports/server/rest/visitors.js';
import '../imports/server/rest/visitors.ts';
import '../imports/server/rest/dashboards.js';
import '../imports/server/rest/queue.js';
import '../imports/server/rest/officeHour.js';

@ -72,6 +72,7 @@ export async function findChatHistory({ userId, roomId, visitorId, pagination: {
total,
};
}
export async function searchChats({ userId, roomId, visitorId, searchText, closedChatsOnly, servedChatsOnly: served, pagination: { offset, count, sort } }) {
if (!await hasPermissionAsync(userId, 'view-l-room')) {
throw new Error('error-not-authorized');

@ -184,4 +184,17 @@ export class MessagesRaw extends BaseRaw {
}
return this.col.aggregate(params).toArray();
}
findLivechatClosedMessages(rid, options) {
return this.find(
{
rid,
$or: [
{ t: { $exists: false } },
{ t: 'livechat-close' },
],
},
options,
);
}
}

@ -37,7 +37,7 @@ export const useAgentsList = (
...(options.text && { text: options.text }),
offset: start,
count: end + start,
sort: JSON.stringify({ name: 1 }),
sort: `{ name: 1 }`,
});
const items = agents.map((agent: any) => {

@ -40,7 +40,7 @@ export const useDepartmentsList = (
text: options.filter,
offset: start,
count: end + start,
sort: JSON.stringify({ name: 1 }),
sort: `{ name: 1 }`,
});
const items = departments

@ -42,7 +42,7 @@ const CustomEmoji: FC<CustomEmojiProps> = function CustomEmoji({ onClick, reload
useMemo(
() => ({
query: JSON.stringify({ name: { $regex: text || '', $options: 'i' } }),
sort: JSON.stringify({ [sortBy]: sortDirection === 'asc' ? 1 : -1 }),
sort: `{ ${sortBy}: ${sortDirection === 'asc' ? 1 : -1} }`,
count: itemsPerPage,
offset: current,
}),

@ -1,4 +1,5 @@
export type PaginatedRequest = {
count: number;
offset: number;
};
export type PaginatedRequest<T = {}, S extends string = string> = {
count?: number;
offset?: number;
sort?: `{ ${S}: ${1 | -1} }` | string;
} & T;

@ -1,5 +1,5 @@
export type PaginatedResult = {
export type PaginatedResult<T = {}> = {
count: number;
offset: number;
total: number;
};
} & T;

@ -4,11 +4,7 @@ import { PaginatedResult } from '../helpers/PaginatedResult';
export type EmojiCustomEndpoints = {
'emoji-custom.all': {
GET: (
params: { query: string } & PaginatedRequest & {
sort: string; // {name: 'asc' | 'desc';}>;
},
) => {
GET: (params: PaginatedRequest<{ query: string }, 'name'>) => {
emojis: ICustomEmojiDescriptor[];
} & PaginatedResult;
};

@ -1,8 +1,11 @@
import { ILivechatDepartment } from '../../ILivechatDepartment';
import { ILivechatMonitor } from '../../ILivechatMonitor';
import { ILivechatTag } from '../../ILivechatTag';
import { IMessage } from '../../IMessage';
import { IOmnichannelRoom, IRoom } from '../../IRoom';
import { ISetting } from '../../ISetting';
import { PaginatedRequest } from '../helpers/PaginatedRequest';
import { PaginatedResult } from '../helpers/PaginatedResult';
export type OmnichannelEndpoints = {
'livechat/appearance': {
@ -23,55 +26,55 @@ export type OmnichannelEndpoints = {
POST: (params: { roomId: IRoom['_id'] }) => void;
};
'livechat/monitors.list': {
GET: (params: { text: string; offset: number; count: number }) => {
GET: (params: PaginatedRequest<{ text: string }>) => PaginatedResult<{
monitors: ILivechatMonitor[];
total: number;
};
}>;
};
'livechat/tags.list': {
GET: (params: { text: string; offset: number; count: number }) => {
GET: (params: PaginatedRequest<{ text: string }, 'name'>) => PaginatedResult<{
tags: ILivechatTag[];
total: number;
};
}>;
};
'livechat/department': {
GET: (params: {
text: string;
offset?: number;
count?: number;
sort?: string;
onlyMyDepartments?: boolean;
}) => {
GET: (
params: PaginatedRequest<{
text: string;
onlyMyDepartments?: boolean;
}>,
) => PaginatedResult<{
departments: ILivechatDepartment[];
total: number;
};
}>;
};
'livechat/department/:_id': {
GET: () => {
department: ILivechatDepartment;
};
};
'livechat/departments.by-unit/:id': {
GET: (params: { text: string; offset: number; count: number }) => {
'livechat/departments.available-by-unit/:id': {
GET: (params: PaginatedRequest<{ text: string }>) => PaginatedResult<{
departments: ILivechatDepartment[];
total: number;
};
}>;
};
'livechat/departments.available-by-unit/:id': {
GET: (params: { text: string; offset: number; count: number }) => {
'livechat/departments.by-unit/': {
GET: (params: PaginatedRequest<{ text: string }>) => PaginatedResult<{
departments: ILivechatDepartment[];
total: number;
};
}>;
};
'livechat/departments.by-unit/:id': {
GET: (params: PaginatedRequest<{ text: string }>) => PaginatedResult<{
departments: ILivechatDepartment[];
}>;
};
'livechat/custom-fields': {
GET: () => {
GET: (params: PaginatedRequest<{ text: string }>) => PaginatedResult<{
customFields: [
{
_id: string;
label: string;
},
];
};
}>;
};
'livechat/rooms': {
GET: (params: {
@ -86,15 +89,17 @@ export type OmnichannelEndpoints = {
current: number;
itemsPerPage: number;
tags: string[];
}) => {
}) => PaginatedResult<{
rooms: IOmnichannelRoom[];
count: number;
offset: number;
total: number;
};
}>;
};
'livechat/:rid/messages': {
GET: (params: PaginatedRequest<{ query: string }>) => PaginatedResult<{
messages: IMessage[];
}>;
};
'livechat/users/agent': {
GET: (params: { text?: string; offset?: number; count?: number; sort?: string }) => {
GET: (params: PaginatedRequest<{ text?: string }>) => PaginatedResult<{
users: {
_id: string;
emails: {
@ -109,9 +114,6 @@ export type OmnichannelEndpoints = {
maxNumberSimultaneousChat: number;
};
}[];
count: number;
offset: number;
total: number;
};
}>;
};
};

@ -344,15 +344,15 @@ API.v1.addRoute('livechat/departments.available-by-unit/:unitId', { authRequired
},
});
API.v1.addRoute('livechat/departments.by-unit/:unitId', { authRequired: true }, {
get() {
API.v1.addRoute('livechat/departments.by-unit/:id', { authRequired: true }, {
async get() {
check(this.urlParams, {
unitId: String,
id: String,
});
const { offset, count } = this.getPaginationItems();
const { unitId } = this.urlParams;
const { id } = this.urlParams;
const { departments, total } = Promise.await(findAllDepartmentsByUnit(unitId, offset, count));
const { departments, total } = await findAllDepartmentsByUnit(id, offset, count);
return API.v1.success({
departments,

@ -9,6 +9,7 @@ export const canAccessRoomLivechat: RoomAccessValidator = async (room, user, ext
// room can be sent as `null` but in that case a `rid` is also sent on extraData
// this is the case for file uploads
const livechatRoom = room || (extraData?.rid && await Rooms.findOneById(extraData?.rid));
if (livechatRoom?.t !== 'l') {
return false;
}

Loading…
Cancel
Save