From e1bb39d7103042dc5aae1ff8def19bb2c98d5421 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Mon, 16 Dec 2024 23:19:20 -0300 Subject: [PATCH] chore: use updater in Livechat Contacts' `updateContactChannel` (#33980) --- apps/meteor/app/livechat/server/lib/Helper.ts | 2 +- .../server/api/lib/contacts.ts | 2 +- .../ee/server/patches/verifyContactChannel.ts | 18 ++---- .../server/lib/verifyContactChannel.spec.ts | 55 +++++++++++-------- apps/meteor/server/models/raw/BaseRaw.ts | 4 +- .../server/models/raw/LivechatContacts.ts | 45 +++++++++------ .../src/models/ILivechatContactsModel.ts | 10 +++- 7 files changed, 77 insertions(+), 59 deletions(-) diff --git a/apps/meteor/app/livechat/server/lib/Helper.ts b/apps/meteor/app/livechat/server/lib/Helper.ts index 4f2887ab369..7eaf800a05d 100644 --- a/apps/meteor/app/livechat/server/lib/Helper.ts +++ b/apps/meteor/app/livechat/server/lib/Helper.ts @@ -97,7 +97,7 @@ export const createLivechatRoom = async ( const source = extraRoomInfo.source || roomInfo.source; if (settings.get('Livechat_Require_Contact_Verification') === 'always') { - await LivechatContacts.updateContactChannel({ visitorId: _id, source }, { verified: false }); + await LivechatContacts.setChannelVerifiedStatus({ visitorId: _id, source }, false); } const contactId = await migrateVisitorIfMissingContact(_id, source); diff --git a/apps/meteor/ee/app/livechat-enterprise/server/api/lib/contacts.ts b/apps/meteor/ee/app/livechat-enterprise/server/api/lib/contacts.ts index cb8fd7e526e..587a1704eee 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/api/lib/contacts.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/api/lib/contacts.ts @@ -6,7 +6,7 @@ import { Livechat } from '../../../../../../app/livechat/server/lib/LivechatType import { i18n } from '../../../../../../server/lib/i18n'; export async function changeContactBlockStatus({ block, visitor }: { visitor: ILivechatContactVisitorAssociation; block: boolean }) { - const result = await LivechatContacts.updateContactChannel(visitor, { blocked: block }); + const result = await LivechatContacts.setChannelBlockStatus(visitor, block); if (!result.modifiedCount) { throw new Error('error-contact-not-found'); diff --git a/apps/meteor/ee/server/patches/verifyContactChannel.ts b/apps/meteor/ee/server/patches/verifyContactChannel.ts index f26419d57e1..8e7a2c05cf8 100644 --- a/apps/meteor/ee/server/patches/verifyContactChannel.ts +++ b/apps/meteor/ee/server/patches/verifyContactChannel.ts @@ -29,20 +29,10 @@ async function _verifyContactChannel( session.startTransaction(); logger.debug({ msg: 'Start verifying contact channel', contactId, visitorId, roomId }); - await LivechatContacts.updateContactChannel( - { - visitorId, - source: room.source, - }, - { - verified: true, - verifiedAt: new Date(), - field, - value: value.toLowerCase(), - }, - {}, - { session }, - ); + const updater = LivechatContacts.getUpdater(); + LivechatContacts.setVerifiedUpdateQuery(true, updater); + LivechatContacts.setFieldAndValueUpdateQuery(field, value.toLowerCase(), updater); + await LivechatContacts.updateFromUpdaterByAssociation({ visitorId, source: room.source }, updater, { session }); await LivechatRooms.update({ _id: roomId }, { $set: { verified: true } }, { session }); logger.debug({ msg: 'Merging contacts', contactId, visitorId, roomId }); diff --git a/apps/meteor/ee/tests/unit/apps/livechat-enterprise/server/lib/verifyContactChannel.spec.ts b/apps/meteor/ee/tests/unit/apps/livechat-enterprise/server/lib/verifyContactChannel.spec.ts index 7e57f5d1a22..cdf310b5852 100644 --- a/apps/meteor/ee/tests/unit/apps/livechat-enterprise/server/lib/verifyContactChannel.spec.ts +++ b/apps/meteor/ee/tests/unit/apps/livechat-enterprise/server/lib/verifyContactChannel.spec.ts @@ -4,7 +4,10 @@ import sinon from 'sinon'; const modelsMock = { LivechatContacts: { - updateContactChannel: sinon.stub(), + getUpdater: sinon.stub(), + setVerifiedUpdateQuery: sinon.stub(), + setFieldAndValueUpdateQuery: sinon.stub(), + updateFromUpdaterByAssociation: sinon.stub(), }, LivechatRooms: { update: sinon.stub(), @@ -44,7 +47,10 @@ const { runVerifyContactChannel } = proxyquire.noCallThru().load('../../../../.. describe('verifyContactChannel', () => { beforeEach(() => { - modelsMock.LivechatContacts.updateContactChannel.reset(); + modelsMock.LivechatContacts.getUpdater.reset(); + modelsMock.LivechatContacts.setVerifiedUpdateQuery.reset(); + modelsMock.LivechatContacts.setFieldAndValueUpdateQuery.reset(); + modelsMock.LivechatContacts.updateFromUpdaterByAssociation.reset(); modelsMock.LivechatRooms.update.reset(); modelsMock.LivechatInquiry.findOneByRoomId.reset(); modelsMock.LivechatRooms.findOneById.reset(); @@ -55,6 +61,8 @@ describe('verifyContactChannel', () => { mergeContactsStub.reset(); queueManager.processNewInquiry.reset(); queueManager.verifyInquiry.reset(); + + modelsMock.LivechatContacts.getUpdater.returns({}); }); afterEach(() => { @@ -68,24 +76,23 @@ describe('verifyContactChannel', () => { await runVerifyContactChannel(() => undefined, { contactId: 'contactId', field: 'field', - value: 'value', + value: 'Value', visitorId: 'visitorId', roomId: 'roomId', }); + expect(modelsMock.LivechatContacts.getUpdater.calledOnce).to.be.true; + expect(modelsMock.LivechatContacts.setVerifiedUpdateQuery.calledOnceWith(true, {})).to.be.true; + expect(modelsMock.LivechatContacts.setFieldAndValueUpdateQuery.calledOnceWith('field', 'value', {})).to.be.true; expect( - modelsMock.LivechatContacts.updateContactChannel.calledOnceWith( + modelsMock.LivechatContacts.updateFromUpdaterByAssociation.calledOnceWith( sinon.match({ visitorId: 'visitorId', source: sinon.match({ type: 'sms', }), }), - sinon.match({ - verified: true, - field: 'field', - value: 'value', - }), + {}, ), ).to.be.true; expect(modelsMock.LivechatRooms.update.calledOnceWith({ _id: 'roomId' }, { $set: { verified: true } })).to.be.true; @@ -116,21 +123,21 @@ describe('verifyContactChannel', () => { roomId: 'roomId', }); + expect(modelsMock.LivechatContacts.getUpdater.calledOnce).to.be.true; + expect(modelsMock.LivechatContacts.setVerifiedUpdateQuery.calledOnceWith(true, {})).to.be.true; + expect(modelsMock.LivechatContacts.setFieldAndValueUpdateQuery.calledOnceWith('field', 'value', {})).to.be.true; expect( - modelsMock.LivechatContacts.updateContactChannel.calledOnceWith( + modelsMock.LivechatContacts.updateFromUpdaterByAssociation.calledOnceWith( sinon.match({ visitorId: 'visitorId', source: sinon.match({ type: 'sms', }), }), - sinon.match({ - verified: true, - field: 'field', - value: 'value', - }), + {}, ), ).to.be.true; + expect(modelsMock.LivechatRooms.update.calledOnceWith({ _id: 'roomId' }, { $set: { verified: true } })).to.be.true; expect( mergeContactsStub.calledOnceWith( @@ -160,7 +167,11 @@ describe('verifyContactChannel', () => { }), ).to.be.rejectedWith('error-invalid-room'); - expect(modelsMock.LivechatContacts.updateContactChannel.notCalled).to.be.true; + expect(modelsMock.LivechatContacts.getUpdater.notCalled).to.be.true; + expect(modelsMock.LivechatContacts.setVerifiedUpdateQuery.notCalled).to.be.true; + expect(modelsMock.LivechatContacts.setFieldAndValueUpdateQuery.notCalled).to.be.true; + expect(modelsMock.LivechatContacts.updateFromUpdaterByAssociation.notCalled).to.be.true; + expect(modelsMock.LivechatRooms.update.notCalled).to.be.true; expect(mergeContactsStub.notCalled).to.be.true; expect(queueManager.verifyInquiry.notCalled).to.be.true; @@ -180,21 +191,21 @@ describe('verifyContactChannel', () => { }), ).to.be.rejectedWith('error-invalid-inquiry'); + expect(modelsMock.LivechatContacts.getUpdater.calledOnce).to.be.true; + expect(modelsMock.LivechatContacts.setVerifiedUpdateQuery.calledOnceWith(true, {})).to.be.true; + expect(modelsMock.LivechatContacts.setFieldAndValueUpdateQuery.calledOnceWith('field', 'value', {})).to.be.true; expect( - modelsMock.LivechatContacts.updateContactChannel.calledOnceWith( + modelsMock.LivechatContacts.updateFromUpdaterByAssociation.calledOnceWith( sinon.match({ visitorId: 'visitorId', source: sinon.match({ type: 'sms', }), }), - sinon.match({ - verified: true, - field: 'field', - value: 'value', - }), + {}, ), ).to.be.true; + expect(modelsMock.LivechatRooms.update.calledOnceWith({ _id: 'roomId' }, { $set: { verified: true } })).to.be.true; expect( mergeContactsStub.calledOnceWith( diff --git a/apps/meteor/server/models/raw/BaseRaw.ts b/apps/meteor/server/models/raw/BaseRaw.ts index e1caf93cf3a..022576e8f49 100644 --- a/apps/meteor/server/models/raw/BaseRaw.ts +++ b/apps/meteor/server/models/raw/BaseRaw.ts @@ -124,9 +124,9 @@ export abstract class BaseRaw< return new UpdaterImpl(); } - public updateFromUpdater(query: Filter, updater: Updater): Promise { + public updateFromUpdater(query: Filter, updater: Updater, options: UpdateOptions = {}): Promise { const updateFilter = updater.getUpdateFilter(); - return this.updateOne(query, updateFilter).catch((e) => { + return this.updateOne(query, updateFilter, options).catch((e) => { console.warn(e, updateFilter); return Promise.reject(e); }); diff --git a/apps/meteor/server/models/raw/LivechatContacts.ts b/apps/meteor/server/models/raw/LivechatContacts.ts index 4e80f23956e..572530252d7 100644 --- a/apps/meteor/server/models/raw/LivechatContacts.ts +++ b/apps/meteor/server/models/raw/LivechatContacts.ts @@ -6,7 +6,7 @@ import type { ILivechatVisitor, RocketChatRecordDeleted, } from '@rocket.chat/core-typings'; -import type { FindPaginated, ILivechatContactsModel, InsertionModel } from '@rocket.chat/model-typings'; +import type { FindPaginated, ILivechatContactsModel, InsertionModel, Updater } from '@rocket.chat/model-typings'; import { escapeRegExp } from '@rocket.chat/string-helpers'; import type { Document, @@ -198,24 +198,37 @@ export class LivechatContactsRaw extends BaseRaw implements IL return Boolean(await this.findOne(this.makeQueryForVisitor(visitor, { blocked: true }), { projection: { _id: 1 } })); } - async updateContactChannel( + setChannelBlockStatus(visitor: ILivechatContactVisitorAssociation, blocked: boolean): Promise { + return this.updateOne(this.makeQueryForVisitor(visitor), { $set: { 'channels.$.blocked': blocked } }); + } + + setChannelVerifiedStatus(visitor: ILivechatContactVisitorAssociation, verified: boolean): Promise { + return this.updateOne(this.makeQueryForVisitor(visitor), { + $set: { + 'channels.$.verified': verified, + ...(verified && { 'channels.$.verifiedAt': new Date() }), + }, + }); + } + + setVerifiedUpdateQuery(verified: boolean, contactUpdater: Updater): Updater { + if (verified) { + contactUpdater.set('channels.$.verifiedAt', new Date()); + } + return contactUpdater.set('channels.$.verified', verified); + } + + setFieldAndValueUpdateQuery(field: string, value: string, contactUpdater: Updater): Updater { + contactUpdater.set('channels.$.field', field); + return contactUpdater.set('channels.$.value', value); + } + + updateFromUpdaterByAssociation( visitor: ILivechatContactVisitorAssociation, - data: Partial, - contactData?: Partial>, + contactUpdater: Updater, options: UpdateOptions = {}, ): Promise { - return this.updateOne( - this.makeQueryForVisitor(visitor), - { - $set: { - ...contactData, - ...(Object.fromEntries( - Object.keys(data).map((key) => [`channels.$.${key}`, data[key as keyof ILivechatContactChannel]]), - ) as UpdateFilter['$set']), - }, - }, - options, - ); + return this.updateFromUpdater(this.makeQueryForVisitor(visitor), contactUpdater, options); } async findSimilarVerifiedContacts( diff --git a/packages/model-typings/src/models/ILivechatContactsModel.ts b/packages/model-typings/src/models/ILivechatContactsModel.ts index 5cf68b15449..744236c5fa7 100644 --- a/packages/model-typings/src/models/ILivechatContactsModel.ts +++ b/packages/model-typings/src/models/ILivechatContactsModel.ts @@ -7,6 +7,7 @@ import type { } from '@rocket.chat/core-typings'; import type { Document, FindCursor, FindOneAndUpdateOptions, FindOptions, UpdateFilter, UpdateOptions, UpdateResult } from 'mongodb'; +import type { Updater } from '../updater'; import type { FindPaginated, IBaseModel, InsertionModel } from './IBaseModel'; export interface ILivechatContactsModel extends IBaseModel { @@ -31,10 +32,9 @@ export interface ILivechatContactsModel extends IBaseModel { options?: FindOptions, ): Promise; isChannelBlocked(visitor: ILivechatContactVisitorAssociation): Promise; - updateContactChannel( + updateFromUpdaterByAssociation( visitor: ILivechatContactVisitorAssociation, - data: Partial, - contactData?: Partial>, + contactUpdater: Updater, options?: UpdateOptions, ): Promise; findSimilarVerifiedContacts( @@ -44,4 +44,8 @@ export interface ILivechatContactsModel extends IBaseModel { ): Promise; findAllByVisitorId(visitorId: string): FindCursor; addEmail(contactId: string, email: string): Promise; + setChannelBlockStatus(visitor: ILivechatContactVisitorAssociation, blocked: boolean): Promise; + setChannelVerifiedStatus(visitor: ILivechatContactVisitorAssociation, verified: boolean): Promise; + setVerifiedUpdateQuery(verified: boolean, contactUpdater: Updater): Updater; + setFieldAndValueUpdateQuery(field: string, value: string, contactUpdater: Updater): Updater; }