diff --git a/apps/meteor/app/livechat/server/api/v1/visitor.ts b/apps/meteor/app/livechat/server/api/v1/visitor.ts index 9226926f94a9..052186212846 100644 --- a/apps/meteor/app/livechat/server/api/v1/visitor.ts +++ b/apps/meteor/app/livechat/server/api/v1/visitor.ts @@ -1,9 +1,9 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; import type { ILivechatVisitorDTO, IRoom } from '@rocket.chat/core-typings'; -import { LivechatVisitors as VisitorsRaw } from '@rocket.chat/models'; +import { LivechatVisitors as VisitorsRaw, LivechatCustomField } from '@rocket.chat/models'; -import { LivechatRooms, LivechatCustomField } from '../../../../models/server'; +import { LivechatRooms } from '../../../../models/server'; import { API } from '../../../../api/server'; import { findGuest, normalizeHttpHeaderData } from '../lib/livechat'; import { Livechat } from '../../lib/Livechat'; @@ -40,17 +40,15 @@ API.v1.addRoute('livechat/visitor', { const visitorId = await Livechat.registerGuest(guest as any); // TODO: Rewrite Livechat to TS let visitor = await VisitorsRaw.findOneById(visitorId, {}); - // If it's updating an existing visitor, it must also update the roomInfo - const cursor = LivechatRooms.findOpenByVisitorToken(visitor?.token); - cursor.forEach((room: IRoom) => { - if (visitor) { - Livechat.saveRoomInfo(room, visitor); - } - }); + if (visitor) { + // If it's updating an existing visitor, it must also update the roomInfo + const rooms = LivechatRooms.findOpenByVisitorToken(visitor?.token).fetch(); + await Promise.all(rooms.map((room: IRoom) => Livechat.saveRoomInfo(room, visitor))); + } if (customFields && customFields instanceof Array) { customFields.forEach((field) => { - const customField = LivechatCustomField.findOneById(field.key); + const customField = Promise.await(LivechatCustomField.findOneById(field.key)); if (!customField) { return; } diff --git a/apps/meteor/app/livechat/server/lib/Contacts.ts b/apps/meteor/app/livechat/server/lib/Contacts.ts index 45fbf828fdb2..9a1297ee0ee9 100644 --- a/apps/meteor/app/livechat/server/lib/Contacts.ts +++ b/apps/meteor/app/livechat/server/lib/Contacts.ts @@ -2,10 +2,10 @@ import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import s from 'underscore.string'; import { MatchKeysAndValues, OnlyFieldsOfType } from 'mongodb'; -import { LivechatVisitors, Users, LivechatRooms } from '@rocket.chat/models'; +import { LivechatVisitors, Users, LivechatRooms, LivechatCustomField } from '@rocket.chat/models'; import { ILivechatCustomField, ILivechatVisitor, IOmnichannelRoom } from '@rocket.chat/core-typings'; -import { LivechatCustomField, Rooms, LivechatInquiry, Subscriptions } from '../../../models/server'; +import { Rooms, LivechatInquiry, Subscriptions } from '../../../models/server'; type RegisterContactProps = { _id?: string; @@ -71,9 +71,9 @@ export const Contacts = { } } - const allowedCF: ILivechatCustomField['_id'][] = LivechatCustomField.find({ scope: 'visitor' }, { fields: { _id: 1 } }).map( - ({ _id }: ILivechatCustomField) => _id, - ); + const allowedCF = await LivechatCustomField.findByScope>('visitor', { projection: { _id: 1 } }) + .map(({ _id }) => _id) + .toArray(); const livechatData = Object.keys(customFields) .filter((key) => allowedCF.includes(key) && customFields[key] !== '' && customFields[key] !== undefined) diff --git a/apps/meteor/app/livechat/server/lib/Livechat.js b/apps/meteor/app/livechat/server/lib/Livechat.js index 79c0e5c86bbd..9f8863596a90 100644 --- a/apps/meteor/app/livechat/server/lib/Livechat.js +++ b/apps/meteor/app/livechat/server/lib/Livechat.js @@ -9,7 +9,7 @@ import _ from 'underscore'; import s from 'underscore.string'; import moment from 'moment-timezone'; import UAParser from 'ua-parser-js'; -import { Users as UsersRaw, LivechatVisitors } from '@rocket.chat/models'; +import { Users as UsersRaw, LivechatVisitors, LivechatCustomField } from '@rocket.chat/models'; import { QueueManager } from './QueueManager'; import { RoutingManager } from './RoutingManager'; @@ -26,7 +26,6 @@ import { Rooms, LivechatDepartmentAgents, LivechatDepartment, - LivechatCustomField, LivechatInquiry, } from '../../../models/server'; import { Logger } from '../../../logger/server'; @@ -393,10 +392,10 @@ export const Livechat = { } const customFields = {}; - const fields = LivechatCustomField.find({ scope: 'visitor' }); if (!userId || hasPermission(userId, 'edit-livechat-room-customfields')) { - fields.forEach((field) => { + const fields = LivechatCustomField.findByScope('visitor'); + for await (const field of fields) { if (!livechatData.hasOwnProperty(field._id)) { return; } @@ -408,7 +407,7 @@ export const Livechat = { } } customFields[field._id] = value; - }); + } updateData.livechatData = customFields; } const ret = await LivechatVisitors.saveGuestById(_id, updateData); @@ -513,7 +512,7 @@ export const Livechat = { check(overwrite, Boolean); Livechat.logger.debug(`Setting custom fields data for visitor with token ${token}`); - const customField = LivechatCustomField.findOneById(key); + const customField = await LivechatCustomField.findOneById(key); if (!customField) { throw new Meteor.Error('invalid-custom-field'); } @@ -579,14 +578,14 @@ export const Livechat = { return rcSettings; }, - saveRoomInfo(roomData, guestData, userId) { + async saveRoomInfo(roomData, guestData, userId) { Livechat.logger.debug(`Saving room information on room ${roomData._id}`); const { livechatData = {} } = roomData; const customFields = {}; if (!userId || hasPermission(userId, 'edit-livechat-room-customfields')) { - const fields = LivechatCustomField.find({ scope: 'room' }); - fields.forEach((field) => { + const fields = LivechatCustomField.findByScope('room'); + for await (const field of fields) { if (!livechatData.hasOwnProperty(field._id)) { return; } @@ -598,7 +597,7 @@ export const Livechat = { } } customFields[field._id] = value; - }); + } roomData.livechatData = customFields; } diff --git a/apps/meteor/app/livechat/server/methods/getCustomFields.js b/apps/meteor/app/livechat/server/methods/getCustomFields.js index f18f94b6df2d..c880d1cf5094 100644 --- a/apps/meteor/app/livechat/server/methods/getCustomFields.js +++ b/apps/meteor/app/livechat/server/methods/getCustomFields.js @@ -1,9 +1,8 @@ import { Meteor } from 'meteor/meteor'; - -import { LivechatCustomField } from '../../../models/server'; +import { LivechatCustomField } from '@rocket.chat/models'; Meteor.methods({ - 'livechat:getCustomFields'() { - return LivechatCustomField.find().fetch(); + async 'livechat:getCustomFields'() { + return LivechatCustomField.find().toArray(); }, }); diff --git a/apps/meteor/app/livechat/server/methods/registerGuest.js b/apps/meteor/app/livechat/server/methods/registerGuest.js index b6c7b7f14f32..d242c365ef76 100644 --- a/apps/meteor/app/livechat/server/methods/registerGuest.js +++ b/apps/meteor/app/livechat/server/methods/registerGuest.js @@ -27,10 +27,8 @@ Meteor.methods({ }); // If it's updating an existing visitor, it must also update the roomInfo - const cursor = LivechatRooms.findOpenByVisitorToken(token); - cursor.forEach((room) => { - Livechat.saveRoomInfo(room, visitor); - }); + const rooms = LivechatRooms.findOpenByVisitorToken(token).fetch(); + await Promise.all(rooms.map((room) => Livechat.saveRoomInfo(room, visitor))); if (customFields && customFields instanceof Array) { // TODO: refactor to use normal await diff --git a/apps/meteor/app/livechat/server/methods/removeCustomField.js b/apps/meteor/app/livechat/server/methods/removeCustomField.js index d0364716194e..3eaca777d867 100644 --- a/apps/meteor/app/livechat/server/methods/removeCustomField.js +++ b/apps/meteor/app/livechat/server/methods/removeCustomField.js @@ -1,11 +1,11 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; +import { LivechatCustomField } from '@rocket.chat/models'; -import { hasPermission } from '../../../authorization'; -import { LivechatCustomField } from '../../../models/server'; +import { hasPermission } from '../../../authorization/server'; Meteor.methods({ - 'livechat:removeCustomField'(_id) { + async 'livechat:removeCustomField'(_id) { if (!Meteor.userId() || !hasPermission(Meteor.userId(), 'view-livechat-manager')) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'livechat:removeCustomField', @@ -14,8 +14,7 @@ Meteor.methods({ check(_id, String); - const customField = LivechatCustomField.findOneById(_id, { fields: { _id: 1 } }); - + const customField = await LivechatCustomField.findOneById(_id, { projection: { _id: 1 } }); if (!customField) { throw new Meteor.Error('error-invalid-custom-field', 'Custom field not found', { method: 'livechat:removeCustomField', diff --git a/apps/meteor/app/livechat/server/methods/saveCustomField.js b/apps/meteor/app/livechat/server/methods/saveCustomField.js index f50ac18231cb..e22c1957c396 100644 --- a/apps/meteor/app/livechat/server/methods/saveCustomField.js +++ b/apps/meteor/app/livechat/server/methods/saveCustomField.js @@ -1,11 +1,11 @@ import { Meteor } from 'meteor/meteor'; import { Match, check } from 'meteor/check'; +import { LivechatCustomField } from '@rocket.chat/models'; -import { hasPermission } from '../../../authorization'; -import { LivechatCustomField } from '../../../models/server'; +import { hasPermission } from '../../../authorization/server'; Meteor.methods({ - 'livechat:saveCustomField'(_id, customFieldData) { + async 'livechat:saveCustomField'(_id, customFieldData) { if (!Meteor.userId() || !hasPermission(Meteor.userId(), 'view-livechat-manager')) { throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'livechat:saveCustomField', @@ -36,7 +36,7 @@ Meteor.methods({ } if (_id) { - const customField = LivechatCustomField.findOneById(_id); + const customField = await LivechatCustomField.findOneById(_id); if (!customField) { throw new Meteor.Error('error-invalid-custom-field', 'Custom Field Not found', { method: 'livechat:saveCustomField', @@ -45,7 +45,7 @@ Meteor.methods({ } if (!_id) { - const customField = LivechatCustomField.findOneById(customFieldData.field); + const customField = await LivechatCustomField.findOneById(customFieldData.field); if (customField) { throw new Meteor.Error('error-custom-field-name-already-exists', 'Custom field name already exists', { method: 'livechat:saveCustomField', diff --git a/apps/meteor/app/livechat/server/methods/saveInfo.js b/apps/meteor/app/livechat/server/methods/saveInfo.js index c5b4a164d6ca..5607b2966fb8 100644 --- a/apps/meteor/app/livechat/server/methods/saveInfo.js +++ b/apps/meteor/app/livechat/server/methods/saveInfo.js @@ -49,7 +49,7 @@ Meteor.methods({ delete guestData.phone; } - const ret = (await Livechat.saveGuest(guestData, userId)) && Livechat.saveRoomInfo(roomData, guestData, userId); + const ret = (await Livechat.saveGuest(guestData, userId)) && (await Livechat.saveRoomInfo(roomData, guestData, userId)); const user = Meteor.users.findOne({ _id: userId }, { fields: { _id: 1, username: 1 } }); diff --git a/apps/meteor/app/livechat/server/methods/setCustomField.js b/apps/meteor/app/livechat/server/methods/setCustomField.js index cd8576bcd0aa..e9764ed827a0 100644 --- a/apps/meteor/app/livechat/server/methods/setCustomField.js +++ b/apps/meteor/app/livechat/server/methods/setCustomField.js @@ -1,11 +1,11 @@ import { Meteor } from 'meteor/meteor'; -import { LivechatVisitors } from '@rocket.chat/models'; +import { LivechatVisitors, LivechatCustomField } from '@rocket.chat/models'; -import { LivechatRooms, LivechatCustomField } from '../../../models/server'; +import { LivechatRooms } from '../../../models/server'; Meteor.methods({ async 'livechat:setCustomField'(token, key, value, overwrite = true) { - const customField = LivechatCustomField.findOneById(key); + const customField = await LivechatCustomField.findOneById(key); if (customField) { if (customField.scope === 'room') { return LivechatRooms.updateDataByToken(token, key, value, overwrite); diff --git a/apps/meteor/app/models/server/index.ts b/apps/meteor/app/models/server/index.ts index c378f5ad6714..7ebc4b43d30b 100644 --- a/apps/meteor/app/models/server/index.ts +++ b/apps/meteor/app/models/server/index.ts @@ -6,7 +6,6 @@ import Settings from './models/Settings'; import Subscriptions from './models/Subscriptions'; import Users from './models/Users'; import Imports from './models/Imports'; -import LivechatCustomField from './models/LivechatCustomField'; import LivechatDepartment from './models/LivechatDepartment'; import LivechatDepartmentAgents from './models/LivechatDepartmentAgents'; import LivechatRooms from './models/LivechatRooms'; @@ -29,7 +28,6 @@ export { Subscriptions, Users, Imports, - LivechatCustomField, LivechatDepartment, LivechatDepartmentAgents, LivechatRooms, diff --git a/apps/meteor/app/models/server/models/LivechatCustomField.js b/apps/meteor/app/models/server/models/LivechatCustomField.js deleted file mode 100644 index 3875fc2b959d..000000000000 --- a/apps/meteor/app/models/server/models/LivechatCustomField.js +++ /dev/null @@ -1,49 +0,0 @@ -import _ from 'underscore'; - -import { Base } from './_Base'; - -/** - * Livechat Custom Fields model - */ -export class LivechatCustomField extends Base { - constructor() { - super('livechat_custom_field'); - - this.tryEnsureIndex({ scope: 1 }); - } - - // FIND - findOneById(_id, options) { - const query = { _id }; - - return this.findOne(query, options); - } - - createOrUpdateCustomField(_id, field, label, scope, visibility, extraData) { - const record = { - label, - scope, - visibility, - }; - - _.extend(record, extraData); - - if (_id) { - this.update({ _id }, { $set: record }); - } else { - record._id = field; - _id = this.insert(record); - } - - return record; - } - - // REMOVE - removeById(_id) { - const query = { _id }; - - return this.remove(query); - } -} - -export default new LivechatCustomField(); diff --git a/apps/meteor/ee/app/livechat-enterprise/server/hooks/onLoadConfigApi.js b/apps/meteor/ee/app/livechat-enterprise/server/hooks/onLoadConfigApi.js index 8845d2c40f9b..b6d6e23ad6bf 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/hooks/onLoadConfigApi.js +++ b/apps/meteor/ee/app/livechat-enterprise/server/hooks/onLoadConfigApi.js @@ -3,11 +3,12 @@ import { getLivechatQueueInfo, getLivechatCustomFields } from '../lib/Helper'; callbacks.add( 'livechat.onLoadConfigApi', + // TODO callbacks cannot be async async (options = {}) => { const { room } = options; const queueInfo = await getLivechatQueueInfo(room); - const customFields = getLivechatCustomFields(); + const customFields = await getLivechatCustomFields(); return { ...(queueInfo && { queueInfo }), diff --git a/apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.js b/apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.js index b0cde9eb53f4..dc955411c4b1 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.js +++ b/apps/meteor/ee/app/livechat-enterprise/server/lib/Helper.js @@ -1,9 +1,14 @@ import { Meteor } from 'meteor/meteor'; import moment from 'moment'; -import { Rooms as RoomRaw, LivechatRooms as LivechatRoomsRaw, LivechatDepartment as LivechatDepartmentRaw } from '@rocket.chat/models'; +import { + Rooms as RoomRaw, + LivechatRooms as LivechatRoomsRaw, + LivechatDepartment as LivechatDepartmentRaw, + LivechatCustomField, +} from '@rocket.chat/models'; import { memoizeDebounce } from './debounceByParams'; -import { Users, LivechatInquiry, LivechatRooms, Messages, LivechatCustomField } from '../../../../../app/models/server'; +import { Users, LivechatInquiry, LivechatRooms, Messages } from '../../../../../app/models/server'; import { settings } from '../../../../../app/settings/server'; import { RoutingManager } from '../../../../../app/livechat/server/lib/RoutingManager'; import { dispatchAgentDelegated } from '../../../../../app/livechat/server/lib/Helper'; @@ -248,12 +253,12 @@ export const updatePriorityInquiries = (priority) => { }); }; -export const getLivechatCustomFields = () => { - const customFields = LivechatCustomField.find({ +export const getLivechatCustomFields = async () => { + const customFields = await LivechatCustomField.find({ visibility: 'visible', scope: 'visitor', public: true, - }).fetch(); + }).toArray(); return customFields.map(({ _id, label, regexp, required = false, type, defaultValue = null, options }) => ({ _id, label, diff --git a/apps/meteor/server/models/raw/LivechatCustomField.ts b/apps/meteor/server/models/raw/LivechatCustomField.ts index b56c5c7cffe5..a0402efca92f 100644 --- a/apps/meteor/server/models/raw/LivechatCustomField.ts +++ b/apps/meteor/server/models/raw/LivechatCustomField.ts @@ -1,6 +1,6 @@ import type { ILivechatCustomField, RocketChatRecordDeleted } from '@rocket.chat/core-typings'; import type { ILivechatCustomFieldModel } from '@rocket.chat/model-typings'; -import type { Db, Collection } from 'mongodb'; +import type { Db, Collection, IndexDescription, FindOptions, FindCursor } from 'mongodb'; import { BaseRaw } from './BaseRaw'; @@ -8,4 +8,37 @@ export class LivechatCustomFieldRaw extends BaseRaw implem constructor(db: Db, trash?: Collection>) { super(db, 'livechat_custom_field', trash); } + + protected modelIndexes(): IndexDescription[] { + return [{ key: { scope: 1 } }]; + } + + findByScope(scope: ILivechatCustomField['scope'], options?: FindOptions): FindCursor { + return this.find({ scope }, options || {}); + } + + async createOrUpdateCustomField( + _id: string, + field: string, + label: ILivechatCustomField['label'], + scope: ILivechatCustomField['scope'], + visibility: ILivechatCustomField['visibility'], + extraData: any, + ) { + const record = { + label, + scope, + visibility, + ...extraData, + }; + + if (_id) { + await this.updateOne({ _id }, { $set: record }); + } else { + record._id = field; + await this.insertOne(record); + } + + return record; + } } diff --git a/packages/model-typings/src/models/ILivechatCustomFieldModel.ts b/packages/model-typings/src/models/ILivechatCustomFieldModel.ts index 20f1177c460c..68ead81af0bf 100644 --- a/packages/model-typings/src/models/ILivechatCustomFieldModel.ts +++ b/packages/model-typings/src/models/ILivechatCustomFieldModel.ts @@ -1,8 +1,10 @@ import type { ILivechatCustomField } from '@rocket.chat/core-typings'; +import type { FindOptions, FindCursor } from 'mongodb'; import type { IBaseModel } from './IBaseModel'; // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface ILivechatCustomFieldModel extends IBaseModel { - // + findByScope(scope: ILivechatCustomField['scope'], options?: FindOptions): FindCursor; + findByScope(scope: ILivechatCustomField['scope'], options?: FindOptions): FindCursor; }