diff --git a/app/livechat/client/views/app/tabbar/contactChatHistoryMessages.html b/app/livechat/client/views/app/tabbar/contactChatHistoryMessages.html
index 1b5e7ec2b10..7b83a9db8de 100644
--- a/app/livechat/client/views/app/tabbar/contactChatHistoryMessages.html
+++ b/app/livechat/client/views/app/tabbar/contactChatHistoryMessages.html
@@ -33,17 +33,25 @@
{{else}}
-
+ {{/if}}
{{/if}}
diff --git a/app/livechat/client/views/app/tabbar/contactChatHistoryMessages.js b/app/livechat/client/views/app/tabbar/contactChatHistoryMessages.js
index c73136c07a8..cb3171f67c6 100644
--- a/app/livechat/client/views/app/tabbar/contactChatHistoryMessages.js
+++ b/app/livechat/client/views/app/tabbar/contactChatHistoryMessages.js
@@ -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(() => {
diff --git a/app/livechat/imports/server/rest/visitors.ts b/app/livechat/imports/server/rest/visitors.ts
new file mode 100644
index 00000000000..e75d4a955a2
--- /dev/null
+++ b/app/livechat/imports/server/rest/visitors.ts
@@ -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,
+ });
+ },
+});
diff --git a/app/livechat/server/api.js b/app/livechat/server/api.js
index 6a13dddc86b..7aa0ee39c4a 100644
--- a/app/livechat/server/api.js
+++ b/app/livechat/server/api.js
@@ -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';
diff --git a/app/livechat/server/api/lib/visitors.js b/app/livechat/server/api/lib/visitors.js
index aec58e18db4..d03566d6da9 100644
--- a/app/livechat/server/api/lib/visitors.js
+++ b/app/livechat/server/api/lib/visitors.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');
diff --git a/app/models/server/raw/Messages.js b/app/models/server/raw/Messages.js
index 06addaca1cd..f3704d6a53b 100644
--- a/app/models/server/raw/Messages.js
+++ b/app/models/server/raw/Messages.js
@@ -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,
+ );
+ }
}
diff --git a/client/components/Omnichannel/hooks/useAgentsList.ts b/client/components/Omnichannel/hooks/useAgentsList.ts
index 4b3b894ebc5..5c01de2c896 100644
--- a/client/components/Omnichannel/hooks/useAgentsList.ts
+++ b/client/components/Omnichannel/hooks/useAgentsList.ts
@@ -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) => {
diff --git a/client/components/Omnichannel/hooks/useDepartmentsList.ts b/client/components/Omnichannel/hooks/useDepartmentsList.ts
index 5447c37d187..9b6b8e46a25 100644
--- a/client/components/Omnichannel/hooks/useDepartmentsList.ts
+++ b/client/components/Omnichannel/hooks/useDepartmentsList.ts
@@ -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
diff --git a/client/views/admin/customEmoji/CustomEmoji.tsx b/client/views/admin/customEmoji/CustomEmoji.tsx
index 28db8f4b4ce..c8b40a477d5 100644
--- a/client/views/admin/customEmoji/CustomEmoji.tsx
+++ b/client/views/admin/customEmoji/CustomEmoji.tsx
@@ -42,7 +42,7 @@ const CustomEmoji: FC = 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,
}),
diff --git a/definition/rest/helpers/PaginatedRequest.ts b/definition/rest/helpers/PaginatedRequest.ts
index 44962dea206..edaa045a559 100644
--- a/definition/rest/helpers/PaginatedRequest.ts
+++ b/definition/rest/helpers/PaginatedRequest.ts
@@ -1,4 +1,5 @@
-export type PaginatedRequest = {
- count: number;
- offset: number;
-};
+export type PaginatedRequest = {
+ count?: number;
+ offset?: number;
+ sort?: `{ ${S}: ${1 | -1} }` | string;
+} & T;
diff --git a/definition/rest/helpers/PaginatedResult.ts b/definition/rest/helpers/PaginatedResult.ts
index e78980c0d1e..ea153093cee 100644
--- a/definition/rest/helpers/PaginatedResult.ts
+++ b/definition/rest/helpers/PaginatedResult.ts
@@ -1,5 +1,5 @@
-export type PaginatedResult = {
+export type PaginatedResult = {
count: number;
offset: number;
total: number;
-};
+} & T;
diff --git a/definition/rest/v1/emojiCustom.ts b/definition/rest/v1/emojiCustom.ts
index 89963edc429..8ef956e5acd 100644
--- a/definition/rest/v1/emojiCustom.ts
+++ b/definition/rest/v1/emojiCustom.ts
@@ -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;
};
diff --git a/definition/rest/v1/omnichannel.ts b/definition/rest/v1/omnichannel.ts
index a2d43915ab0..1624a4be26a 100644
--- a/definition/rest/v1/omnichannel.ts
+++ b/definition/rest/v1/omnichannel.ts
@@ -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;
- };
+ }>;
};
};
diff --git a/ee/app/livechat-enterprise/server/api/departments.js b/ee/app/livechat-enterprise/server/api/departments.js
index 49c9e840d3e..07c7c5a6c68 100644
--- a/ee/app/livechat-enterprise/server/api/departments.js
+++ b/ee/app/livechat-enterprise/server/api/departments.js
@@ -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,
diff --git a/server/services/authorization/canAccessRoomLivechat.ts b/server/services/authorization/canAccessRoomLivechat.ts
index a0dd92acbb1..0418d105624 100644
--- a/server/services/authorization/canAccessRoomLivechat.ts
+++ b/server/services/authorization/canAccessRoomLivechat.ts
@@ -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;
}