From cb673755339c2fb9210cb70317d5e2bf788e811c Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Fri, 19 Jan 2024 12:20:23 -0300 Subject: [PATCH 01/16] chore: dont ignoreUndefined --- apps/meteor/packages/rocketchat-mongo-config/server/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/packages/rocketchat-mongo-config/server/index.js b/apps/meteor/packages/rocketchat-mongo-config/server/index.js index 63343ac640143..1c72f87dfc41e 100644 --- a/apps/meteor/packages/rocketchat-mongo-config/server/index.js +++ b/apps/meteor/packages/rocketchat-mongo-config/server/index.js @@ -19,7 +19,7 @@ tls.DEFAULT_ECDH_CURVE = 'auto'; const mongoConnectionOptions = { // add retryWrites=false if not present in MONGO_URL ...(!process.env.MONGO_URL.includes('retryWrites') && { retryWrites: false }), - // ignoreUndefined: false, // TODO evaluate adding this config + ignoreUndefined: false, // TODO ideally we should call isTracingEnabled(), but since this is a Meteor package we can't :/ monitorCommands: ['yes', 'true'].includes(String(process.env.TRACING_ENABLED).toLowerCase()), From 08645fdd350af745446deb05f0892efbe721ea6e Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Mon, 20 Jan 2025 16:07:22 -0300 Subject: [PATCH 02/16] fix places saving `null` --- apps/meteor/app/api/server/v1/chat.ts | 2 +- apps/meteor/app/api/server/v1/teams.ts | 6 +++--- .../app/lib/server/functions/createDirectRoom.ts | 2 +- apps/meteor/app/lib/server/functions/createRoom.ts | 4 ++-- apps/meteor/app/livechat/server/api/v1/contact.ts | 5 ++++- .../livechat/server/lib/contacts/createContact.ts | 12 ++++++------ .../livechat/server/lib/contacts/updateContact.ts | 10 +++++----- 7 files changed, 22 insertions(+), 19 deletions(-) diff --git a/apps/meteor/app/api/server/v1/chat.ts b/apps/meteor/app/api/server/v1/chat.ts index 7569f321fa203..39b1109a948f6 100644 --- a/apps/meteor/app/api/server/v1/chat.ts +++ b/apps/meteor/app/api/server/v1/chat.ts @@ -375,7 +375,7 @@ API.v1.addRoute( _id: msg._id, msg: msgFromBody, rid: msg.rid, - customFields: this.bodyParams.customFields as Record | undefined, + ...(this.bodyParams.customFields && { customFields: this.bodyParams.customFields }), }, this.bodyParams.previewUrls, ), diff --git a/apps/meteor/app/api/server/v1/teams.ts b/apps/meteor/app/api/server/v1/teams.ts index 4b54cbffb7226..84d1ba5db264c 100644 --- a/apps/meteor/app/api/server/v1/teams.ts +++ b/apps/meteor/app/api/server/v1/teams.ts @@ -441,9 +441,9 @@ API.v1.addRoute( const canSeeAllMembers = await hasPermissionAsync(this.userId, 'view-all-teams', team.roomId); const query = { - username: username ? new RegExp(escapeRegExp(username), 'i') : undefined, - name: name ? new RegExp(escapeRegExp(name), 'i') : undefined, - status: status ? { $in: status as UserStatus[] } : undefined, + ...(username && { username: new RegExp(escapeRegExp(username), 'i') }), + ...(name && { name: new RegExp(escapeRegExp(name), 'i') }), + ...(status && { status: { $in: status as UserStatus[] } }), }; const { records, total } = await Team.members(this.userId, team._id, canSeeAllMembers, { offset, count }, query); diff --git a/apps/meteor/app/lib/server/functions/createDirectRoom.ts b/apps/meteor/app/lib/server/functions/createDirectRoom.ts index f77ee1f55901b..ad74bd400f5d6 100644 --- a/apps/meteor/app/lib/server/functions/createDirectRoom.ts +++ b/apps/meteor/app/lib/server/functions/createDirectRoom.ts @@ -122,7 +122,7 @@ export async function createDirectRoom( ); if (typeof result === 'object') { - Object.assign(roomInfo, result); + Object.assign(roomInfo, Object.fromEntries(Object.entries(result).filter(([_, v]) => v != null))); } delete tmpRoom._USERNAMES; diff --git a/apps/meteor/app/lib/server/functions/createRoom.ts b/apps/meteor/app/lib/server/functions/createRoom.ts index 914b3d9a5d937..a0c6cd810a23e 100644 --- a/apps/meteor/app/lib/server/functions/createRoom.ts +++ b/apps/meteor/app/lib/server/functions/createRoom.ts @@ -203,7 +203,7 @@ export const createRoom = async ( }, ts: now, ro: readOnly === true, - sidepanel, + ...(sidepanel && { sidepanel }), }; if (teamId) { @@ -236,7 +236,7 @@ export const createRoom = async ( ); if (eventResult && typeof eventResult === 'object' && delete eventResult._USERNAMES) { - Object.assign(roomProps, eventResult); + Object.assign(roomProps, Object.fromEntries(Object.entries(eventResult).filter(([_, v]) => v != null))); } const shouldBeHandledByFederation = roomProps.federated === true || owner.username.includes(':'); diff --git a/apps/meteor/app/livechat/server/api/v1/contact.ts b/apps/meteor/app/livechat/server/api/v1/contact.ts index 03cc5ddeaabd0..300cd1bc1e7c0 100644 --- a/apps/meteor/app/livechat/server/api/v1/contact.ts +++ b/apps/meteor/app/livechat/server/api/v1/contact.ts @@ -19,6 +19,7 @@ import { getContactChannelsGrouped } from '../../lib/contacts/getContactChannels import { getContactHistory } from '../../lib/contacts/getContactHistory'; import { getContacts } from '../../lib/contacts/getContacts'; import { registerContact } from '../../lib/contacts/registerContact'; +import type { UpdateContactParams } from '../../lib/contacts/updateContact'; import { updateContact } from '../../lib/contacts/updateContact'; API.v1.addRoute( @@ -121,7 +122,9 @@ API.v1.addRoute( { authRequired: true, permissionsRequired: ['update-livechat-contact'], validateParams: isPOSTUpdateOmnichannelContactsProps }, { async post() { - const contact = await updateContact({ ...this.bodyParams }); + const contact = await updateContact( + Object.fromEntries(Object.entries(this.bodyParams).filter(([_, v]) => v != null)) as unknown as UpdateContactParams, + ); return API.v1.success({ contact }); }, diff --git a/apps/meteor/app/livechat/server/lib/contacts/createContact.ts b/apps/meteor/app/livechat/server/lib/contacts/createContact.ts index 98cf238d9b5e3..44efef5b2e220 100644 --- a/apps/meteor/app/livechat/server/lib/contacts/createContact.ts +++ b/apps/meteor/app/livechat/server/lib/contacts/createContact.ts @@ -40,12 +40,12 @@ export async function createContact({ return LivechatContacts.insertContact({ name, - emails: emails?.map((address) => ({ address })), - phones: phones?.map((phoneNumber) => ({ phoneNumber })), - contactManager, - channels, - customFields, - lastChat, + ...(emails && { emails: emails?.map((address) => ({ address })) }), + ...(phones && { phones: phones?.map((phoneNumber) => ({ phoneNumber })) }), + ...(contactManager && { contactManager }), + ...(channels && { channels }), + ...(customFields && { customFields }), + ...(lastChat && { lastChat }), unknown, ...(importIds?.length && { importIds }), }); diff --git a/apps/meteor/app/livechat/server/lib/contacts/updateContact.ts b/apps/meteor/app/livechat/server/lib/contacts/updateContact.ts index 6c344274386e6..cabb0359796a3 100644 --- a/apps/meteor/app/livechat/server/lib/contacts/updateContact.ts +++ b/apps/meteor/app/livechat/server/lib/contacts/updateContact.ts @@ -73,11 +73,11 @@ export async function updateContact(params: UpdateContactParams): Promise ({ address })), - phones: phones?.map((phoneNumber) => ({ phoneNumber })), - contactManager, - channels, - customFields: customFieldsToUpdate, + ...(emails && { emails: emails?.map((address) => ({ address })) }), + ...(phones && { phones: phones?.map((phoneNumber) => ({ phoneNumber })) }), + ...(contactManager && { contactManager }), + ...(channels && { channels }), + ...(customFieldsToUpdate && { customFields: customFieldsToUpdate }), ...(wipeConflicts && { conflictingFields: [] }), }); From 44ce2c139e972b2f913e40de8ec982ad20b51730 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Mon, 20 Jan 2025 18:50:29 -0300 Subject: [PATCH 03/16] fix more places saving `null` --- apps/meteor/app/livechat/server/lib/Helper.ts | 1 - apps/meteor/ee/server/api/audit.ts | 2 +- apps/meteor/ee/server/models/raw/CannedResponse.ts | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/meteor/app/livechat/server/lib/Helper.ts b/apps/meteor/app/livechat/server/lib/Helper.ts index b5d1b4569a430..a9ec0ee1941bd 100644 --- a/apps/meteor/app/livechat/server/lib/Helper.ts +++ b/apps/meteor/app/livechat/server/lib/Helper.ts @@ -139,7 +139,6 @@ export const prepareLivechatRoom = async ( alias: 'unknown', }, queuedAt: newRoomAt, - livechatData: undefined, priorityWeight: LivechatPriorityWeight.NOT_SPECIFIED, estimatedWaitingTimeQueue: DEFAULT_SLA_CONFIG.ESTIMATED_WAITING_TIME_QUEUE, ...extraRoomInfo, diff --git a/apps/meteor/ee/server/api/audit.ts b/apps/meteor/ee/server/api/audit.ts index 2b96927e69960..61a0c618b410d 100644 --- a/apps/meteor/ee/server/api/audit.ts +++ b/apps/meteor/ee/server/api/audit.ts @@ -78,7 +78,7 @@ API.v1.addRoute( _id: this.user._id, username: this.user.username, name: this.user.name, - avatarETag: this.user.avatarETag, + ...(this.user.avatarETag && { avatarETag: this.user.avatarETag }), }, fields: { msg: 'Room_members_list', diff --git a/apps/meteor/ee/server/models/raw/CannedResponse.ts b/apps/meteor/ee/server/models/raw/CannedResponse.ts index fdfb4a02d97b5..2bae8abd5dd70 100644 --- a/apps/meteor/ee/server/models/raw/CannedResponse.ts +++ b/apps/meteor/ee/server/models/raw/CannedResponse.ts @@ -113,6 +113,6 @@ export class CannedResponseRaw extends BaseRaw imple }, }; - return this.updateMany({}, update); + return this.updateMany({ tags: tagId }, update); } } From c7f0d6c2a7d691ea51054f1f579314c204d6dd72 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 21 Jan 2025 11:12:44 -0300 Subject: [PATCH 04/16] cleanup undefined from msg attachments --- apps/meteor/app/apps/server/converters/messages.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/meteor/app/apps/server/converters/messages.js b/apps/meteor/app/apps/server/converters/messages.js index 8cc6f4ea270f2..e2479bb5000a0 100644 --- a/apps/meteor/app/apps/server/converters/messages.js +++ b/apps/meteor/app/apps/server/converters/messages.js @@ -236,8 +236,8 @@ export class AppMessagesConverter { } return attachments.map((attachment) => - Object.assign( - { + Object.fromEntries( + Object.entries({ collapsed: attachment.collapsed, color: attachment.color, text: attachment.text, @@ -266,8 +266,8 @@ export class AppMessagesConverter { actions: attachment.actions, type: attachment.type, description: attachment.description, - }, - attachment._unmappedProperties_, + ...attachment._unmappedProperties_, + }).filter(([, v]) => v != null), ), ); } From 7cfbfbc507e9b0e80ef893aabbe61406fc2cca11 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 21 Jan 2025 14:21:58 -0300 Subject: [PATCH 05/16] cleanup undefined from processWebhookMessage --- apps/meteor/app/lib/server/functions/processWebhookMessage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/app/lib/server/functions/processWebhookMessage.ts b/apps/meteor/app/lib/server/functions/processWebhookMessage.ts index ae304b2af01d3..95e59d030fd42 100644 --- a/apps/meteor/app/lib/server/functions/processWebhookMessage.ts +++ b/apps/meteor/app/lib/server/functions/processWebhookMessage.ts @@ -135,7 +135,7 @@ export const processWebhookMessage = async function ( await validateRoomMessagePermissionsAsync(room, { uid: user._id, ...user }); - const messageReturn = await sendMessage(user, message, room); + const messageReturn = await sendMessage(user, Object.fromEntries(Object.entries(message).filter(([_, v]) => v != null)), room); sentData.push({ channel, message: messageReturn }); } From 46cdec89635bfe5a172c324e6b8af2c829a80846 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 21 Jan 2025 17:42:22 -0300 Subject: [PATCH 06/16] try inquiry --- apps/meteor/app/livechat/server/lib/Helper.ts | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/apps/meteor/app/livechat/server/lib/Helper.ts b/apps/meteor/app/livechat/server/lib/Helper.ts index a9ec0ee1941bd..d5c70edf14528 100644 --- a/apps/meteor/app/livechat/server/lib/Helper.ts +++ b/apps/meteor/app/livechat/server/lib/Helper.ts @@ -147,7 +147,7 @@ export const prepareLivechatRoom = async ( export const createLivechatRoom = async (room: InsertionModel, session: ClientSession) => { const result = await LivechatRooms.findOneAndUpdate( - room, + Object.fromEntries(Object.entries(room).filter(([_, v]) => v != null)), { $set: {}, }, @@ -211,26 +211,28 @@ export const createLivechatInquiry = async ({ }); const result = await LivechatInquiry.findOneAndUpdate( - { - rid, - name, - ts, - department, - message: message ?? '', - status: initialStatus || LivechatInquiryStatus.READY, - v: { - _id, - username, - token, - status, - ...(activity?.length && { activity }), - }, - t: 'l', - priorityWeight: LivechatPriorityWeight.NOT_SPECIFIED, - estimatedWaitingTimeQueue: DEFAULT_SLA_CONFIG.ESTIMATED_WAITING_TIME_QUEUE, + Object.fromEntries( + Object.entries({ + rid, + name, + ts, + department, + message: message ?? '', + status: initialStatus || LivechatInquiryStatus.READY, + v: { + _id, + username, + token, + status, + ...(activity?.length && { activity }), + }, + t: 'l', + priorityWeight: LivechatPriorityWeight.NOT_SPECIFIED, + estimatedWaitingTimeQueue: DEFAULT_SLA_CONFIG.ESTIMATED_WAITING_TIME_QUEUE, - ...extraInquiryInfo, - }, + ...extraInquiryInfo, + }).filter(([_, v]) => v != null), + ), { $set: { _id: new ObjectId().toHexString(), From 2dbef776ba80d9cfcb490844e6ab697b8ddc3933 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 22 Jan 2025 10:57:41 -0300 Subject: [PATCH 07/16] fix apps --- .../app/apps/server/converters/users.js | 34 ++++++++++--------- .../ee/server/apps/communication/rest.ts | 7 +++- packages/apps-engine/src/server/AppManager.ts | 8 ++--- 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/apps/meteor/app/apps/server/converters/users.js b/apps/meteor/app/apps/server/converters/users.js index e89bf71a04281..6a5d3979d5fb5 100644 --- a/apps/meteor/app/apps/server/converters/users.js +++ b/apps/meteor/app/apps/server/converters/users.js @@ -56,22 +56,24 @@ export class AppUsersConverter { return undefined; } - return { - _id: user.id, - username: user.username, - emails: user.emails, - type: user.type, - active: user.isEnabled, - name: user.name, - roles: user.roles, - status: user.status, - statusConnection: user.statusConnection, - utcOffset: user.utfOffset, - createdAt: user.createdAt, - _updatedAt: user.updatedAt, - lastLogin: user.lastLoginAt, - appId: user.appId, - }; + return Object.fromEntries( + Object.entries({ + _id: user.id, + username: user.username, + emails: user.emails, + type: user.type, + active: user.isEnabled, + name: user.name, + roles: user.roles, + status: user.status, + statusConnection: user.statusConnection, + utcOffset: user.utfOffset, + createdAt: user.createdAt, + _updatedAt: user.updatedAt, + lastLogin: user.lastLoginAt, + appId: user.appId, + }).filter(([, v]) => v != null), + ); } _convertUserTypeToEnum(type) { diff --git a/apps/meteor/ee/server/apps/communication/rest.ts b/apps/meteor/ee/server/apps/communication/rest.ts index 32282c78d15cc..53719aee9a87a 100644 --- a/apps/meteor/ee/server/apps/communication/rest.ts +++ b/apps/meteor/ee/server/apps/communication/rest.ts @@ -407,7 +407,12 @@ export class AppsRestApi { ?.get('users') ?.convertToApp(await Meteor.userAsync()); - const aff = await manager.add(buff, { marketplaceInfo, permissionsGranted, enable: false, user }); + const aff = await manager.add(buff, { + ...(marketplaceInfo && { marketplaceInfo }), + permissionsGranted, + enable: false, + user, + }); const info: IAppInfo & { status?: AppStatus } = aff.getAppInfo(); if (aff.hasStorageError()) { diff --git a/packages/apps-engine/src/server/AppManager.ts b/packages/apps-engine/src/server/AppManager.ts index bbc31eef5e630..813a1765148bb 100644 --- a/packages/apps-engine/src/server/AppManager.ts +++ b/packages/apps-engine/src/server/AppManager.ts @@ -607,7 +607,7 @@ export class AppManager { } descriptor.signature = await this.getSignatureManager().signApp(descriptor); - const created = await this.appMetadataStorage.create(descriptor); + const created = await this.appMetadataStorage.create(Object.fromEntries(Object.entries(descriptor).filter(([_, v]) => v != null)) as unknown as any); if (!created) { aff.setStorageError('Failed to create the App, the storage did not return it.'); @@ -717,9 +717,9 @@ export class AppManager { languageContent: result.languageContent, settings: old.settings, implemented: result.implemented.getValues(), - marketplaceInfo: old.marketplaceInfo, - sourcePath: old.sourcePath, - permissionsGranted, + ...(old.marketplaceInfo && { marketplaceInfo: old.marketplaceInfo }), + ...(old.sourcePath && { sourcePath: old.sourcePath }), + ...(permissionsGranted && { permissionsGranted }), }; try { From 60d5387281c07e6d5fe7765bc99a0695bb62b805 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 22 Jan 2025 12:35:08 -0300 Subject: [PATCH 08/16] cleanup from app converter --- .../app/apps/server/converters/rooms.js | 195 +++++++++++------- .../lib/server/functions/createDirectRoom.ts | 2 +- .../app/lib/server/functions/createRoom.ts | 2 +- 3 files changed, 120 insertions(+), 79 deletions(-) diff --git a/apps/meteor/app/apps/server/converters/rooms.js b/apps/meteor/app/apps/server/converters/rooms.js index b2bbcda49610d..e2ab9ff202277 100644 --- a/apps/meteor/app/apps/server/converters/rooms.js +++ b/apps/meteor/app/apps/server/converters/rooms.js @@ -20,73 +20,120 @@ export class AppRoomsConverter { return this.convertRoom(room); } - async convertAppRoom(room, isPartial = false) { - if (!room) { - return undefined; + async __getCreator(user) { + if (!user) { + return; } - let u; - if (room.creator) { - const creator = await Users.findOneById(room.creator.id); - u = { - _id: creator._id, - username: creator.username, - name: creator.name, - }; + const creator = await Users.findOneById(user, { projection: { _id: 1, username: 1, name: 1 } }); + if (!creator) { + return; } - let v; - if (room.visitor) { - const visitor = await LivechatVisitors.findOneEnabledById(room.visitor.id); + return { + _id: creator._id, + username: creator.username, + name: creator.name, + }; + } - const { lastMessageTs, phone } = room.visitorChannelInfo; + async __getVisitor({ visitor: roomVisitor, visitorChannelInfo }) { + if (!roomVisitor) { + return; + } - v = { - _id: visitor._id, - username: visitor.username, - token: visitor.token, - status: visitor.status || 'online', - ...(lastMessageTs && { lastMessageTs }), - ...(phone && { phone }), - }; + const visitor = await LivechatVisitors.findOneEnabledById(roomVisitor.id); + if (!visitor) { + return; } - let departmentId; - if (room.department) { - const department = await LivechatDepartment.findOneById(room.department.id, { projection: { _id: 1 } }); - departmentId = department._id; + const { lastMessageTs, phone } = visitorChannelInfo; + + return { + _id: visitor._id, + username: visitor.username, + token: visitor.token, + status: visitor.status || 'online', + ...(lastMessageTs && { lastMessageTs }), + ...(phone && { phone }), + }; + } + + async __getUserIdAndUsername(uid) { + if (!uid) { + return; + } + + const user = await Users.findOneById(uid, { projection: { _id: 1, username: 1 } }); + if (!user) { + return; + } + + return { + _id: user._id, + username: user.username, + }; + } + + async __getRoomCloser(room, v) { + if (!room.closedBy) { + return; } - let servedBy; - if (room.servedBy) { - const user = await Users.findOneById(room.servedBy.id); - servedBy = { + if (room.closer === 'user') { + const user = await Users.findOneById(room.closedBy.id, { projection: { _id: 1, username: 1 } }); + if (!user) { + return; + } + + return { _id: user._id, username: user.username, }; } - let closedBy; - if (room.closedBy) { - if (room.closer === 'user') { - const user = await Users.findOneById(room.closedBy.id); - closedBy = { - _id: user._id, - username: user.username, - }; - } else if (room.closer === 'visitor') { - closedBy = { - _id: v._id, - username: v.username, - }; - } + if (room.closer === 'visitor' && v) { + return { + _id: v._id, + username: v.username, + }; } + } - let contactId; - if (room.contact?._id) { - const contact = await LivechatContacts.findOneById(room.contact._id, { projection: { _id: 1 } }); - contactId = contact._id; + // TODO do we really need this? + async __getContactId({ contact }) { + if (!contact?._id) { + return; } + const contactFromDb = await LivechatContacts.findOneById(contact._id, { projection: { _id: 1 } }); + return contactFromDb?._id; + } + + // TODO do we really need this? + async __getDepartment({ department }) { + if (!department) { + return; + } + const dept = await LivechatDepartment.findOneById(department.id, { projection: { _id: 1 } }); + return dept?._id; + } + + async convertAppRoom(room, isPartial = false) { + if (!room) { + return undefined; + } + + const u = this.__getCreator(room.creator?.id); + + const v = this.__getVisitor(room); + + const departmentId = await this.__getDepartment(room); + + const servedBy = await this.__getUserIdAndUsername(room.servedBy); + + const closedBy = await this.__getRoomCloser(room, v); + + const contactId = await this.__getContactId(room); let _default; if (typeof room.isDefault !== 'undefined') { @@ -110,30 +157,30 @@ export class AppRoomsConverter { const newRoom = { ...(room.id && { _id: room.id }), - fname: room.displayName, - name: room.slugifiedName, t: room.type, - u, - v, - ro, - sysMes, - msgs, - departmentId, - servedBy, - closedBy, - members: room.members, - uids: room.userIds, - default: _default, - waitingResponse: typeof room.isWaitingResponse === 'undefined' ? undefined : !!room.isWaitingResponse, - open: typeof room.isOpen === 'undefined' ? undefined : !!room.isOpen, ts: room.createdAt, _updatedAt: room.updatedAt, - closedAt: room.closedAt, - lm: room.lastModifiedAt, - customFields: room.customFields, - livechatData: room.livechatData, - prid: typeof room.parentRoom === 'undefined' ? undefined : room.parentRoom.id, - contactId, + ...(room.displayName && { fname: room.displayName }), + ...(room.type !== 'd' && { name: room.slugifiedName }), + ...(room.members && { members: room.members }), + ...(_default && { default: _default }), + ...(ro && { ro }), + ...(sysMes && { sysMes }), + ...(msgs && { msgs }), + ...(u && { u }), + ...(v && { v }), + ...(departmentId && { departmentId }), + ...(servedBy && { servedBy }), + ...(closedBy && { closedBy }), + ...(room.userIds && { uids: room.userIds }), + ...(typeof room.isWaitingResponse !== 'undefined' && { waitingResponse: !!room.isWaitingResponse }), + ...(typeof room.isOpen !== 'undefined' && { open: !!room.isOpen }), + ...(room.closedAt && { closedAt: room.closedAt }), + ...(room.lastModifiedAt && { lm: room.lastModifiedAt }), + ...(room.customFields && { customFields: room.customFields }), + ...(room.livechatData && { livechatData: room.livechatData }), + ...(typeof room.parentRoom !== 'undefined' && { prid: room.parentRoom.id }), + ...(contactId && { contactId }), ...(room._USERNAMES && { _USERNAMES: room._USERNAMES }), ...(room.source && { source: { @@ -142,13 +189,7 @@ export class AppRoomsConverter { }), }; - if (isPartial) { - Object.entries(newRoom).forEach(([key, value]) => { - if (typeof value === 'undefined') { - delete newRoom[key]; - } - }); - } else { + if (!isPartial) { Object.assign(newRoom, room._unmappedProperties_); } diff --git a/apps/meteor/app/lib/server/functions/createDirectRoom.ts b/apps/meteor/app/lib/server/functions/createDirectRoom.ts index ad74bd400f5d6..f77ee1f55901b 100644 --- a/apps/meteor/app/lib/server/functions/createDirectRoom.ts +++ b/apps/meteor/app/lib/server/functions/createDirectRoom.ts @@ -122,7 +122,7 @@ export async function createDirectRoom( ); if (typeof result === 'object') { - Object.assign(roomInfo, Object.fromEntries(Object.entries(result).filter(([_, v]) => v != null))); + Object.assign(roomInfo, result); } delete tmpRoom._USERNAMES; diff --git a/apps/meteor/app/lib/server/functions/createRoom.ts b/apps/meteor/app/lib/server/functions/createRoom.ts index a0c6cd810a23e..5df34629292a0 100644 --- a/apps/meteor/app/lib/server/functions/createRoom.ts +++ b/apps/meteor/app/lib/server/functions/createRoom.ts @@ -236,7 +236,7 @@ export const createRoom = async ( ); if (eventResult && typeof eventResult === 'object' && delete eventResult._USERNAMES) { - Object.assign(roomProps, Object.fromEntries(Object.entries(eventResult).filter(([_, v]) => v != null))); + Object.assign(roomProps, eventResult); } const shouldBeHandledByFederation = roomProps.federated === true || owner.username.includes(':'); From cac788170c2843965e8d2d525edc908c4312359b Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 22 Jan 2025 13:09:39 -0300 Subject: [PATCH 09/16] create helper function removeEmpty --- .../app/apps/server/converters/messages.js | 65 +++++++------- .../app/apps/server/converters/users.js | 35 ++++---- .../server/functions/processWebhookMessage.ts | 3 +- .../app/livechat/server/api/v1/contact.ts | 5 +- apps/meteor/app/livechat/server/lib/Helper.ts | 43 +++++---- packages/apps-engine/package.json | 1 + packages/apps-engine/src/server/AppManager.ts | 4 +- packages/tools/src/index.ts | 1 + packages/tools/src/removeEmpty.spec.ts | 87 +++++++++++++++++++ packages/tools/src/removeEmpty.ts | 7 ++ yarn.lock | 1 + 11 files changed, 174 insertions(+), 78 deletions(-) create mode 100644 packages/tools/src/removeEmpty.spec.ts create mode 100644 packages/tools/src/removeEmpty.ts diff --git a/apps/meteor/app/apps/server/converters/messages.js b/apps/meteor/app/apps/server/converters/messages.js index e2479bb5000a0..a824df3228396 100644 --- a/apps/meteor/app/apps/server/converters/messages.js +++ b/apps/meteor/app/apps/server/converters/messages.js @@ -1,6 +1,7 @@ import { isMessageFromVisitor } from '@rocket.chat/core-typings'; import { Messages, Rooms, Users } from '@rocket.chat/models'; import { Random } from '@rocket.chat/random'; +import { removeEmpty } from '@rocket.chat/tools'; import { cachedFunction } from './cachedFunction'; import { transformMappedData } from './transformMappedData'; @@ -236,39 +237,37 @@ export class AppMessagesConverter { } return attachments.map((attachment) => - Object.fromEntries( - Object.entries({ - collapsed: attachment.collapsed, - color: attachment.color, - text: attachment.text, - ts: attachment.timestamp ? attachment.timestamp.toJSON() : attachment.timestamp, - message_link: attachment.timestampLink, - thumb_url: attachment.thumbnailUrl, - author_name: attachment.author ? attachment.author.name : undefined, - author_link: attachment.author ? attachment.author.link : undefined, - author_icon: attachment.author ? attachment.author.icon : undefined, - title: attachment.title ? attachment.title.value : undefined, - title_link: attachment.title ? attachment.title.link : undefined, - title_link_download: attachment.title ? attachment.title.displayDownloadLink : undefined, - image_dimensions: attachment.imageDimensions, - image_preview: attachment.imagePreview, - image_url: attachment.imageUrl, - image_type: attachment.imageType, - image_size: attachment.imageSize, - audio_url: attachment.audioUrl, - audio_type: attachment.audioType, - audio_size: attachment.audioSize, - video_url: attachment.videoUrl, - video_type: attachment.videoType, - video_size: attachment.videoSize, - fields: attachment.fields, - button_alignment: attachment.actionButtonsAlignment, - actions: attachment.actions, - type: attachment.type, - description: attachment.description, - ...attachment._unmappedProperties_, - }).filter(([, v]) => v != null), - ), + removeEmpty({ + collapsed: attachment.collapsed, + color: attachment.color, + text: attachment.text, + ts: attachment.timestamp ? attachment.timestamp.toJSON() : attachment.timestamp, + message_link: attachment.timestampLink, + thumb_url: attachment.thumbnailUrl, + author_name: attachment.author ? attachment.author.name : undefined, + author_link: attachment.author ? attachment.author.link : undefined, + author_icon: attachment.author ? attachment.author.icon : undefined, + title: attachment.title ? attachment.title.value : undefined, + title_link: attachment.title ? attachment.title.link : undefined, + title_link_download: attachment.title ? attachment.title.displayDownloadLink : undefined, + image_dimensions: attachment.imageDimensions, + image_preview: attachment.imagePreview, + image_url: attachment.imageUrl, + image_type: attachment.imageType, + image_size: attachment.imageSize, + audio_url: attachment.audioUrl, + audio_type: attachment.audioType, + audio_size: attachment.audioSize, + video_url: attachment.videoUrl, + video_type: attachment.videoType, + video_size: attachment.videoSize, + fields: attachment.fields, + button_alignment: attachment.actionButtonsAlignment, + actions: attachment.actions, + type: attachment.type, + description: attachment.description, + ...attachment._unmappedProperties_, + }), ); } diff --git a/apps/meteor/app/apps/server/converters/users.js b/apps/meteor/app/apps/server/converters/users.js index 6a5d3979d5fb5..b8f4d5c9043b7 100644 --- a/apps/meteor/app/apps/server/converters/users.js +++ b/apps/meteor/app/apps/server/converters/users.js @@ -1,5 +1,6 @@ import { UserStatusConnection, UserType } from '@rocket.chat/apps-engine/definition/users'; import { Users } from '@rocket.chat/models'; +import { removeEmpty } from '@rocket.chat/tools'; export class AppUsersConverter { constructor(orch) { @@ -56,24 +57,22 @@ export class AppUsersConverter { return undefined; } - return Object.fromEntries( - Object.entries({ - _id: user.id, - username: user.username, - emails: user.emails, - type: user.type, - active: user.isEnabled, - name: user.name, - roles: user.roles, - status: user.status, - statusConnection: user.statusConnection, - utcOffset: user.utfOffset, - createdAt: user.createdAt, - _updatedAt: user.updatedAt, - lastLogin: user.lastLoginAt, - appId: user.appId, - }).filter(([, v]) => v != null), - ); + return removeEmpty({ + _id: user.id, + username: user.username, + emails: user.emails, + type: user.type, + active: user.isEnabled, + name: user.name, + roles: user.roles, + status: user.status, + statusConnection: user.statusConnection, + utcOffset: user.utfOffset, + createdAt: user.createdAt, + _updatedAt: user.updatedAt, + lastLogin: user.lastLoginAt, + appId: user.appId, + }); } _convertUserTypeToEnum(type) { diff --git a/apps/meteor/app/lib/server/functions/processWebhookMessage.ts b/apps/meteor/app/lib/server/functions/processWebhookMessage.ts index 95e59d030fd42..372df0d8ed77c 100644 --- a/apps/meteor/app/lib/server/functions/processWebhookMessage.ts +++ b/apps/meteor/app/lib/server/functions/processWebhookMessage.ts @@ -1,4 +1,5 @@ import type { IMessage, IUser, RequiredField, MessageAttachment } from '@rocket.chat/core-typings'; +import { removeEmpty } from '@rocket.chat/tools'; import { Meteor } from 'meteor/meteor'; import _ from 'underscore'; @@ -135,7 +136,7 @@ export const processWebhookMessage = async function ( await validateRoomMessagePermissionsAsync(room, { uid: user._id, ...user }); - const messageReturn = await sendMessage(user, Object.fromEntries(Object.entries(message).filter(([_, v]) => v != null)), room); + const messageReturn = await sendMessage(user, removeEmpty(message), room); sentData.push({ channel, message: messageReturn }); } diff --git a/apps/meteor/app/livechat/server/api/v1/contact.ts b/apps/meteor/app/livechat/server/api/v1/contact.ts index 300cd1bc1e7c0..47579e67e2cd9 100644 --- a/apps/meteor/app/livechat/server/api/v1/contact.ts +++ b/apps/meteor/app/livechat/server/api/v1/contact.ts @@ -9,6 +9,7 @@ import { isGETOmnichannelContactsCheckExistenceProps, } from '@rocket.chat/rest-typings'; import { escapeRegExp } from '@rocket.chat/string-helpers'; +import { removeEmpty } from '@rocket.chat/tools'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -122,9 +123,7 @@ API.v1.addRoute( { authRequired: true, permissionsRequired: ['update-livechat-contact'], validateParams: isPOSTUpdateOmnichannelContactsProps }, { async post() { - const contact = await updateContact( - Object.fromEntries(Object.entries(this.bodyParams).filter(([_, v]) => v != null)) as unknown as UpdateContactParams, - ); + const contact = await updateContact(removeEmpty(this.bodyParams) as unknown as UpdateContactParams); return API.v1.success({ contact }); }, diff --git a/apps/meteor/app/livechat/server/lib/Helper.ts b/apps/meteor/app/livechat/server/lib/Helper.ts index d5c70edf14528..7b26f7e18654a 100644 --- a/apps/meteor/app/livechat/server/lib/Helper.ts +++ b/apps/meteor/app/livechat/server/lib/Helper.ts @@ -31,6 +31,7 @@ import { Users, LivechatContacts, } from '@rocket.chat/models'; +import { removeEmpty } from '@rocket.chat/tools'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import type { ClientSession } from 'mongodb'; @@ -147,7 +148,7 @@ export const prepareLivechatRoom = async ( export const createLivechatRoom = async (room: InsertionModel, session: ClientSession) => { const result = await LivechatRooms.findOneAndUpdate( - Object.fromEntries(Object.entries(room).filter(([_, v]) => v != null)), + removeEmpty(room), { $set: {}, }, @@ -211,28 +212,26 @@ export const createLivechatInquiry = async ({ }); const result = await LivechatInquiry.findOneAndUpdate( - Object.fromEntries( - Object.entries({ - rid, - name, - ts, - department, - message: message ?? '', - status: initialStatus || LivechatInquiryStatus.READY, - v: { - _id, - username, - token, - status, - ...(activity?.length && { activity }), - }, - t: 'l', - priorityWeight: LivechatPriorityWeight.NOT_SPECIFIED, - estimatedWaitingTimeQueue: DEFAULT_SLA_CONFIG.ESTIMATED_WAITING_TIME_QUEUE, + removeEmpty({ + rid, + name, + ts, + department, + message: message ?? '', + status: initialStatus || LivechatInquiryStatus.READY, + v: { + _id, + username, + token, + status, + ...(activity?.length && { activity }), + }, + t: 'l', + priorityWeight: LivechatPriorityWeight.NOT_SPECIFIED, + estimatedWaitingTimeQueue: DEFAULT_SLA_CONFIG.ESTIMATED_WAITING_TIME_QUEUE, - ...extraInquiryInfo, - }).filter(([_, v]) => v != null), - ), + ...extraInquiryInfo, + }), { $set: { _id: new ObjectId().toHexString(), diff --git a/packages/apps-engine/package.json b/packages/apps-engine/package.json index d8dfa07394c59..6bcc996a20b18 100644 --- a/packages/apps-engine/package.json +++ b/packages/apps-engine/package.json @@ -93,6 +93,7 @@ }, "dependencies": { "@msgpack/msgpack": "3.0.0-beta2", + "@rocket.chat/tools": "workspace:^", "adm-zip": "^0.5.16", "debug": "^4.3.7", "esbuild": "^0.25.0", diff --git a/packages/apps-engine/src/server/AppManager.ts b/packages/apps-engine/src/server/AppManager.ts index 813a1765148bb..cb398d506085a 100644 --- a/packages/apps-engine/src/server/AppManager.ts +++ b/packages/apps-engine/src/server/AppManager.ts @@ -1,5 +1,7 @@ import { Buffer } from 'buffer'; +import { removeEmpty } from '@rocket.chat/tools'; + import type { IGetAppsFilter } from './IGetAppsFilter'; import { ProxiedApp } from './ProxiedApp'; import type { PersistenceBridge, UserBridge } from './bridges'; @@ -607,7 +609,7 @@ export class AppManager { } descriptor.signature = await this.getSignatureManager().signApp(descriptor); - const created = await this.appMetadataStorage.create(Object.fromEntries(Object.entries(descriptor).filter(([_, v]) => v != null)) as unknown as any); + const created = await this.appMetadataStorage.create(removeEmpty(descriptor)); if (!created) { aff.setStorageError('Failed to create the App, the storage did not return it.'); diff --git a/packages/tools/src/index.ts b/packages/tools/src/index.ts index 410bd711d24a4..76dfbee33ddb0 100644 --- a/packages/tools/src/index.ts +++ b/packages/tools/src/index.ts @@ -8,3 +8,4 @@ export * from './timezone'; export * from './wrapExceptions'; export * from './getLoginExpiration'; export * from './converter'; +export * from './removeEmpty'; diff --git a/packages/tools/src/removeEmpty.spec.ts b/packages/tools/src/removeEmpty.spec.ts new file mode 100644 index 0000000000000..62c3c15593f1e --- /dev/null +++ b/packages/tools/src/removeEmpty.spec.ts @@ -0,0 +1,87 @@ +import { removeEmpty } from './removeEmpty'; + +describe('removeEmpty', () => { + it('should remove null props', () => { + const obj = { a: 1, b: null }; + + expect(removeEmpty(obj)).toEqual({ a: 1 }); + }); + + it('should remove undefined props', () => { + const obj = { a: 1, b: undefined }; + + expect(removeEmpty(obj)).toEqual({ a: 1 }); + }); + + it('should not remove empty strings', () => { + const obj = { a: 1, b: '' }; + + expect(removeEmpty(obj)).toEqual({ a: 1, b: '' }); + }); + + it('should not remove empty arrays', () => { + const obj = { a: 1, b: [] }; + + expect(removeEmpty(obj)).toEqual({ a: 1, b: [] }); + }); + + it('should not remove empty objects', () => { + const obj = { a: 1, b: {} }; + + expect(removeEmpty(obj)).toEqual({ a: 1, b: {} }); + }); + + it('should not remove 0', () => { + const obj = { a: 1, b: 0 }; + + expect(removeEmpty(obj)).toEqual({ a: 1, b: 0 }); + }); + + it('should not remove false', () => { + const obj = { a: 1, b: false }; + + expect(removeEmpty(obj)).toEqual({ a: 1, b: false }); + }); + + it('should not remove NaN', () => { + const obj = { a: 1, b: NaN }; + + expect(removeEmpty(obj)).toEqual({ a: 1, b: NaN }); + }); + + it('should not remove Infinity', () => { + const obj = { a: 1, b: Infinity }; + + expect(removeEmpty(obj)).toEqual({ a: 1, b: Infinity }); + }); + + it('should not remove functions', () => { + const fn = () => { + // noop + }; + + const obj = { + a: 1, + fn, + }; + + expect(removeEmpty(obj)).toEqual({ + a: 1, + fn, + }); + }); + + it('should not remove symbols', () => { + const b = Symbol('test'); + + const obj = { a: 1, b }; + + expect(removeEmpty(obj)).toEqual({ a: 1, b }); + }); + + it('should not remove objects with non-empty props', () => { + const obj = { a: 1, b: { c: 2 } }; + + expect(removeEmpty(obj)).toEqual({ a: 1, b: { c: 2 } }); + }); +}); diff --git a/packages/tools/src/removeEmpty.ts b/packages/tools/src/removeEmpty.ts new file mode 100644 index 0000000000000..2e8bfc3197362 --- /dev/null +++ b/packages/tools/src/removeEmpty.ts @@ -0,0 +1,7 @@ +type NonEmpty = { + [K in keyof T]: Exclude; +}; + +export function removeEmpty>(obj: T): NonEmpty { + return Object.fromEntries(Object.entries(obj).filter(([_, v]) => v != null)) as NonEmpty; +} diff --git a/yarn.lock b/yarn.lock index 5c89d178dca06..7a12e6c363557 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7815,6 +7815,7 @@ __metadata: dependencies: "@msgpack/msgpack": "npm:3.0.0-beta2" "@rocket.chat/eslint-config": "workspace:~" + "@rocket.chat/tools": "workspace:^" "@rocket.chat/ui-kit": "workspace:~" "@types/adm-zip": "npm:^0.5.6" "@types/debug": "npm:^4.1.12" From 9a0dc1b3f471a15ed7168db4a62cbcd7f269192a Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 22 Jan 2025 13:09:51 -0300 Subject: [PATCH 10/16] add await to room converter --- apps/meteor/app/apps/server/converters/rooms.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/meteor/app/apps/server/converters/rooms.js b/apps/meteor/app/apps/server/converters/rooms.js index e2ab9ff202277..87a288c5b8b46 100644 --- a/apps/meteor/app/apps/server/converters/rooms.js +++ b/apps/meteor/app/apps/server/converters/rooms.js @@ -123,9 +123,9 @@ export class AppRoomsConverter { return undefined; } - const u = this.__getCreator(room.creator?.id); + const u = await this.__getCreator(room.creator?.id); - const v = this.__getVisitor(room); + const v = await this.__getVisitor(room); const departmentId = await this.__getDepartment(room); From b771217a4542be6e67504504818440f21272ddfe Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 22 Jan 2025 13:45:46 -0300 Subject: [PATCH 11/16] more empty props on room converter --- apps/meteor/app/apps/server/converters/rooms.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/apps/meteor/app/apps/server/converters/rooms.js b/apps/meteor/app/apps/server/converters/rooms.js index 87a288c5b8b46..62a0179c24a85 100644 --- a/apps/meteor/app/apps/server/converters/rooms.js +++ b/apps/meteor/app/apps/server/converters/rooms.js @@ -150,15 +150,11 @@ export class AppRoomsConverter { sysMes = room.displaySystemMessages; } - let msgs; - if (typeof room.messageCount !== 'undefined') { - msgs = room.messageCount; - } - const newRoom = { ...(room.id && { _id: room.id }), t: room.type, ts: room.createdAt, + msgs: room.messageCount || 0, _updatedAt: room.updatedAt, ...(room.displayName && { fname: room.displayName }), ...(room.type !== 'd' && { name: room.slugifiedName }), @@ -166,7 +162,6 @@ export class AppRoomsConverter { ...(_default && { default: _default }), ...(ro && { ro }), ...(sysMes && { sysMes }), - ...(msgs && { msgs }), ...(u && { u }), ...(v && { v }), ...(departmentId && { departmentId }), From 58bdbc85a113e6e193579bb361180f080bdfac71 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Wed, 22 Jan 2025 15:04:29 -0300 Subject: [PATCH 12/16] remove type casting --- apps/meteor/app/livechat/server/api/v1/contact.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/meteor/app/livechat/server/api/v1/contact.ts b/apps/meteor/app/livechat/server/api/v1/contact.ts index 47579e67e2cd9..702662c917f25 100644 --- a/apps/meteor/app/livechat/server/api/v1/contact.ts +++ b/apps/meteor/app/livechat/server/api/v1/contact.ts @@ -20,7 +20,6 @@ import { getContactChannelsGrouped } from '../../lib/contacts/getContactChannels import { getContactHistory } from '../../lib/contacts/getContactHistory'; import { getContacts } from '../../lib/contacts/getContacts'; import { registerContact } from '../../lib/contacts/registerContact'; -import type { UpdateContactParams } from '../../lib/contacts/updateContact'; import { updateContact } from '../../lib/contacts/updateContact'; API.v1.addRoute( @@ -123,7 +122,7 @@ API.v1.addRoute( { authRequired: true, permissionsRequired: ['update-livechat-contact'], validateParams: isPOSTUpdateOmnichannelContactsProps }, { async post() { - const contact = await updateContact(removeEmpty(this.bodyParams) as unknown as UpdateContactParams); + const contact = await updateContact(removeEmpty(this.bodyParams)); return API.v1.success({ contact }); }, From bc79b103ea0316c3ed5cfd7673dafc20ef02570a Mon Sep 17 00:00:00 2001 From: Pierre Date: Tue, 28 Jan 2025 13:17:54 -0300 Subject: [PATCH 13/16] additional safeguards --- .../server/methods/setUserPublicAndPrivateKeys.ts | 6 ++++++ .../app/federation/server/endpoints/dispatch.js | 14 +++++++++----- .../app/federation/server/functions/addUser.js | 3 ++- .../server/classes/converters/RoomConverter.ts | 3 ++- .../server/classes/converters/UserConverter.ts | 2 +- apps/meteor/app/lib/server/functions/createRoom.ts | 3 ++- .../app/livechat/server/lib/LivechatTyped.ts | 3 ++- packages/models/src/models/Users.ts | 2 +- 8 files changed, 25 insertions(+), 11 deletions(-) diff --git a/apps/meteor/app/e2e/server/methods/setUserPublicAndPrivateKeys.ts b/apps/meteor/app/e2e/server/methods/setUserPublicAndPrivateKeys.ts index 2477d1547ca25..b852e408eea03 100644 --- a/apps/meteor/app/e2e/server/methods/setUserPublicAndPrivateKeys.ts +++ b/apps/meteor/app/e2e/server/methods/setUserPublicAndPrivateKeys.ts @@ -25,6 +25,12 @@ Meteor.methods({ }); } + if (!keyPair.public_key || !keyPair.private_key) { + throw new Meteor.Error('error-invalid-keys', 'Invalid keys', { + method: 'e2e.setUserPublicAndPrivateKeys', + }); + } + if (!keyPair.force) { const keys = await Users.fetchKeysByUserId(userId); diff --git a/apps/meteor/app/federation/server/endpoints/dispatch.js b/apps/meteor/app/federation/server/endpoints/dispatch.js index d80f74bf18d35..65a675722bba2 100644 --- a/apps/meteor/app/federation/server/endpoints/dispatch.js +++ b/apps/meteor/app/federation/server/endpoints/dispatch.js @@ -1,6 +1,7 @@ import { api } from '@rocket.chat/core-services'; import { eventTypes } from '@rocket.chat/core-typings'; import { FederationServers, FederationRoomEvents, Rooms, Messages, Subscriptions, Users, ReadReceipts } from '@rocket.chat/models'; +import { removeEmpty } from '@rocket.chat/tools'; import EJSON from 'ejson'; import { API } from '../../../api/server'; @@ -120,8 +121,8 @@ const eventHandlers = { if (persistedUser) { // Update the federation, if its not already set (if it's set, this is likely an event being reprocessed) - if (!persistedUser.federation) { - await Users.updateOne({ _id: persistedUser._id }, { $set: { federation: user.federation } }); + if (!persistedUser.federation && user.federation) { + await Users.updateOne({ _id: persistedUser._id }, { $set: { federation: removeEmpty(user.federation) } }); federationAltered = true; } } else { @@ -139,8 +140,11 @@ const eventHandlers = { try { if (persistedSubscription) { // Update the federation, if its not already set (if it's set, this is likely an event being reprocessed - if (!persistedSubscription.federation) { - await Subscriptions.updateOne({ _id: persistedSubscription._id }, { $set: { federation: subscription.federation } }); + if (!persistedSubscription.federation && subscription.federation) { + await Subscriptions.updateOne( + { _id: persistedSubscription._id }, + { $set: { federation: removeEmpty(subscription.federation) } }, + ); federationAltered = true; } } else { @@ -148,7 +152,7 @@ const eventHandlers = { const denormalizedSubscription = normalizers.denormalizeSubscription(subscription); // Create the subscription - const { insertedId } = await Subscriptions.insertOne(denormalizedSubscription); + const { insertedId } = await Subscriptions.insertOne(removeEmpty(denormalizedSubscription)); if (insertedId) { void notifyOnSubscriptionChangedById(insertedId); } diff --git a/apps/meteor/app/federation/server/functions/addUser.js b/apps/meteor/app/federation/server/functions/addUser.js index 8420a47944af7..0cddb226d263a 100644 --- a/apps/meteor/app/federation/server/functions/addUser.js +++ b/apps/meteor/app/federation/server/functions/addUser.js @@ -1,4 +1,5 @@ import { FederationServers, Users } from '@rocket.chat/models'; +import { removeEmpty } from '@rocket.chat/tools'; import { Meteor } from 'meteor/meteor'; import { getUserByUsername } from '../handler'; @@ -19,7 +20,7 @@ export async function addUser(query) { try { // Create the local user - userId = await Users.create(user); + userId = await Users.create(removeEmpty(user)); // Refresh the servers list await FederationServers.refreshServers(); diff --git a/apps/meteor/app/importer/server/classes/converters/RoomConverter.ts b/apps/meteor/app/importer/server/classes/converters/RoomConverter.ts index e9a14fed6a038..ba7d01b0009ee 100644 --- a/apps/meteor/app/importer/server/classes/converters/RoomConverter.ts +++ b/apps/meteor/app/importer/server/classes/converters/RoomConverter.ts @@ -1,5 +1,6 @@ import type { IImportChannel, IImportChannelRecord, IRoom } from '@rocket.chat/core-typings'; import { Subscriptions, Rooms, Users } from '@rocket.chat/models'; +import { removeEmpty } from '@rocket.chat/tools'; import limax from 'limax'; import { RecordConverter } from './RecordConverter'; @@ -150,7 +151,7 @@ export class RoomConverter extends RecordConverter { const roomUpdate: { $set?: Record; $addToSet?: Record } = {}; if (Object.keys(set).length > 0) { - roomUpdate.$set = set; + roomUpdate.$set = removeEmpty(set); } if (roomData.importIds.length) { diff --git a/apps/meteor/app/importer/server/classes/converters/UserConverter.ts b/apps/meteor/app/importer/server/classes/converters/UserConverter.ts index 88ea742de7bc7..03f853832ef51 100644 --- a/apps/meteor/app/importer/server/classes/converters/UserConverter.ts +++ b/apps/meteor/app/importer/server/classes/converters/UserConverter.ts @@ -201,7 +201,7 @@ export class UserConverter extends RecordConverter, currentPath: string): void => { for (const key in source) { - if (!source.hasOwnProperty(key)) { + if (!source.hasOwnProperty(key) || source[key] === undefined) { continue; } diff --git a/apps/meteor/app/lib/server/functions/createRoom.ts b/apps/meteor/app/lib/server/functions/createRoom.ts index 5df34629292a0..67ac77f90b6ae 100644 --- a/apps/meteor/app/lib/server/functions/createRoom.ts +++ b/apps/meteor/app/lib/server/functions/createRoom.ts @@ -5,6 +5,7 @@ import { Federation, FederationEE, License, Message, Team } from '@rocket.chat/c import type { ICreateRoomParams, ISubscriptionExtraData } from '@rocket.chat/core-services'; import type { ICreatedRoom, IUser, IRoom, RoomType } from '@rocket.chat/core-typings'; import { Rooms, Subscriptions, Users } from '@rocket.chat/models'; +import { removeEmpty } from '@rocket.chat/tools'; import { Meteor } from 'meteor/meteor'; import { createDirectRoom } from './createDirectRoom'; @@ -250,7 +251,7 @@ export const createRoom = async ( await callbacks.run('beforeCreateChannel', owner, roomProps); } - const room = await Rooms.createWithFullRoomData(roomProps); + const room = await Rooms.createWithFullRoomData(removeEmpty(roomProps)); void notifyOnRoomChanged(room, 'inserted'); diff --git a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts index 67230078847f6..38d5375b0380d 100644 --- a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts +++ b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts @@ -26,6 +26,7 @@ import { Rooms, LivechatCustomField, } from '@rocket.chat/models'; +import { removeEmpty } from '@rocket.chat/tools'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import type { Filter } from 'mongodb'; @@ -203,7 +204,7 @@ class LivechatClass { throw new Meteor.Error('error-user-is-not-agent', 'User is not a livechat agent'); } - await Users.setLivechatData(_id, agentData); + await Users.setLivechatData(_id, removeEmpty(agentData)); const currentDepartmentsForAgent = await LivechatDepartmentAgents.findByAgentId(_id).toArray(); diff --git a/packages/models/src/models/Users.ts b/packages/models/src/models/Users.ts index 4408d494c2404..0c86e727a187f 100644 --- a/packages/models/src/models/Users.ts +++ b/packages/models/src/models/Users.ts @@ -3059,7 +3059,7 @@ export class UsersRaw extends BaseRaw> implements IU const settingsObject = Object.assign( {}, ...Object.keys(preferences).map((key) => ({ - [`settings.preferences.${key}`]: preferences[key], + ...(preferences[key] !== undefined && { [`settings.preferences.${key}`]: preferences[key] }), })), ); From fc689d5c81be096f0f5e4283884328d88e495c71 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Tue, 28 Jan 2025 17:01:33 -0300 Subject: [PATCH 14/16] revert removeEmpty on createRoom --- apps/meteor/app/lib/server/functions/createRoom.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/meteor/app/lib/server/functions/createRoom.ts b/apps/meteor/app/lib/server/functions/createRoom.ts index 67ac77f90b6ae..22a5d7c69dc7b 100644 --- a/apps/meteor/app/lib/server/functions/createRoom.ts +++ b/apps/meteor/app/lib/server/functions/createRoom.ts @@ -1,11 +1,9 @@ -/* eslint-disable complexity */ import { AppEvents, Apps } from '@rocket.chat/apps'; import { AppsEngineException } from '@rocket.chat/apps-engine/definition/exceptions'; import { Federation, FederationEE, License, Message, Team } from '@rocket.chat/core-services'; import type { ICreateRoomParams, ISubscriptionExtraData } from '@rocket.chat/core-services'; import type { ICreatedRoom, IUser, IRoom, RoomType } from '@rocket.chat/core-typings'; import { Rooms, Subscriptions, Users } from '@rocket.chat/models'; -import { removeEmpty } from '@rocket.chat/tools'; import { Meteor } from 'meteor/meteor'; import { createDirectRoom } from './createDirectRoom'; @@ -251,7 +249,7 @@ export const createRoom = async ( await callbacks.run('beforeCreateChannel', owner, roomProps); } - const room = await Rooms.createWithFullRoomData(removeEmpty(roomProps)); + const room = await Rooms.createWithFullRoomData(roomProps); void notifyOnRoomChanged(room, 'inserted'); From 43d206839fb4f2621787c2861de56d1b1e1d780e Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Thu, 20 Feb 2025 14:24:26 -0300 Subject: [PATCH 15/16] direct check --- .../app/apps/server/converters/rooms.js | 21 +++---------------- 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/apps/meteor/app/apps/server/converters/rooms.js b/apps/meteor/app/apps/server/converters/rooms.js index 62a0179c24a85..5fc1c5ab57bf8 100644 --- a/apps/meteor/app/apps/server/converters/rooms.js +++ b/apps/meteor/app/apps/server/converters/rooms.js @@ -135,21 +135,6 @@ export class AppRoomsConverter { const contactId = await this.__getContactId(room); - let _default; - if (typeof room.isDefault !== 'undefined') { - _default = room.isDefault; - } - - let ro; - if (typeof room.isReadOnly !== 'undefined') { - ro = room.isReadOnly; - } - - let sysMes; - if (typeof room.displaySystemMessages !== 'undefined') { - sysMes = room.displaySystemMessages; - } - const newRoom = { ...(room.id && { _id: room.id }), t: room.type, @@ -159,9 +144,9 @@ export class AppRoomsConverter { ...(room.displayName && { fname: room.displayName }), ...(room.type !== 'd' && { name: room.slugifiedName }), ...(room.members && { members: room.members }), - ...(_default && { default: _default }), - ...(ro && { ro }), - ...(sysMes && { sysMes }), + ...(typeof room.isDefault !== 'undefined' && { default: room.isDefault }), + ...(typeof room.isReadOnly !== 'undefined' && { ro: room.isReadOnly }), + ...(typeof room.displaySystemMessages !== 'undefined' && { sysMes: room.displaySystemMessages }), ...(u && { u }), ...(v && { v }), ...(departmentId && { departmentId }), From da28e8f26248a66df36e37994ffa161a681f1388 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Thu, 20 Feb 2025 15:30:44 -0300 Subject: [PATCH 16/16] test: rooms should always have msgs count --- apps/meteor/tests/unit/app/apps/server/rooms.tests.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/tests/unit/app/apps/server/rooms.tests.ts b/apps/meteor/tests/unit/app/apps/server/rooms.tests.ts index 718d79baef361..0e7508556cf74 100644 --- a/apps/meteor/tests/unit/app/apps/server/rooms.tests.ts +++ b/apps/meteor/tests/unit/app/apps/server/rooms.tests.ts @@ -112,9 +112,9 @@ describe('The AppMessagesConverter instance', () => { expect(rocketchatRoom).to.have.property('_id', appRoom.id); expect(rocketchatRoom).to.have.property('name', appRoom.slugifiedName); expect(rocketchatRoom).to.have.property('sysMes', appRoom.displaySystemMessages); + expect(rocketchatRoom).to.have.property('msgs', 0); expect(rocketchatRoom).to.have.property('_updatedAt', appRoom.updatedAt); - expect(rocketchatRoom).to.not.have.property('msgs'); expect(rocketchatRoom).to.not.have.property('ro'); expect(rocketchatRoom).to.not.have.property('default'); });