Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/eighty-pumas-float.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': patch
---

fix(rooms): Update lastMessage to previous valid message on user deletion
46 changes: 40 additions & 6 deletions apps/meteor/app/lib/server/functions/deleteUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export async function deleteUser(userId: string, confirmRelinquish = false, dele
}

const user = await Users.findOneById(userId, {
projection: { username: 1, avatarOrigin: 1, roles: 1, federated: 1 },
projection: { username: 1, name: 1, avatarOrigin: 1, roles: 1, federated: 1 },
});

if (!user) {
Expand All @@ -61,18 +61,21 @@ export async function deleteUser(userId: string, confirmRelinquish = false, dele
}

let deletedRooms: string[] = [];
const affectedRoomIds: string[] = [];

// Users without username can't do anything, so there is nothing to remove
if (user.username != null) {
let userToReplaceWhenUnlinking: IUser | null = null;
const nameAlias = i18n.t('Removed_User');
deletedRooms = await relinquishRoomOwnerships(userId, subscribedRooms, true);
deletedRooms = await relinquishRoomOwnerships(userId, subscribedRooms);

const messageErasureType = settings.get<'Delete' | 'Unlink' | 'Keep'>('Message_ErasureType');
switch (messageErasureType) {
case 'Delete':
case 'Delete': {
const store = FileUpload.getStore('Uploads');
const cursor = Messages.findFilesByUserId(userId);

// New Rocket.Chat Logic for File Deletion
for await (const { file, files } of cursor) {
const fileIds = files?.map(({ _id }) => _id) || [];
for await (const fileId of fileIds) {
Expand All @@ -87,6 +90,20 @@ export async function deleteUser(userId: string, confirmRelinquish = false, dele
}

await Messages.removeByUserId(userId);

// Our Fix: Update lastMessage for rooms
const roomsToUpdate = await Rooms.find({ 'lastMessage.u._id': userId }, { projection: { _id: 1 } }).toArray();
for await (const room of roomsToUpdate) {
affectedRoomIds.push(room._id);
const [newLastMessage] = await Messages.find({ rid: room._id }, { sort: { ts: -1 }, limit: 1 }).toArray();
const filter = { _id: room._id, 'lastMessage.u._id': userId };
if (newLastMessage) {
await Rooms.updateOne(filter, { $set: { lastMessage: newLastMessage } });
} else {
await Rooms.updateOne(filter, { $unset: { lastMessage: 1 } });
}
}

await ReadReceipts.removeByUserId(userId);

await ModerationReports.hideMessageReportsByUserId(
Expand All @@ -97,20 +114,37 @@ export async function deleteUser(userId: string, confirmRelinquish = false, dele
);

break;
case 'Unlink':
}
case 'Unlink': {
userToReplaceWhenUnlinking = await Users.findOneById('rocket.cat');
if (!userToReplaceWhenUnlinking?._id || !userToReplaceWhenUnlinking?.username) {
break;
}
await Messages.unlinkUserId(userId, userToReplaceWhenUnlinking?._id, userToReplaceWhenUnlinking?.username, nameAlias);

// Our Fix: Update lastMessage for rooms in Unlink case too
const roomsToUpdateUnlink = await Rooms.find({ 'lastMessage.u._id': userId }, { projection: { _id: 1 } }).toArray();
for await (const room of roomsToUpdateUnlink) {
affectedRoomIds.push(room._id);
const [newLastMessage] = await Messages.find({ rid: room._id }, { sort: { ts: -1 }, limit: 1 }).toArray();
const filter = { _id: room._id, 'lastMessage.u._id': userId };
if (newLastMessage) {
await Rooms.updateOne(filter, { $set: { lastMessage: newLastMessage } });
} else {
await Rooms.updateOne(filter, { $unset: { lastMessage: 1 } });
}
}

break;
}
}

await Rooms.updateGroupDMsRemovingUsernamesByUsername(user.username, userId); // Remove direct rooms with the user
await Rooms.removeDirectRoomContainingUsername(user.username); // Remove direct rooms with the user

const rids = subscribedRooms.map((room) => room.rid);
void notifyOnRoomChangedById(rids);
const allAffectedRids = [...new Set([...rids, ...affectedRoomIds])];
void notifyOnRoomChangedById(allAffectedRids);

await Subscriptions.removeByUserId(userId);

Expand Down Expand Up @@ -185,4 +219,4 @@ export async function deleteUser(userId: string, confirmRelinquish = false, dele
await callbacks.run('afterDeleteUser', user);

return { deletedRooms };
}
}
4 changes: 4 additions & 0 deletions packages/models/src/models/Rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ export class RoomsRaw extends BaseRaw<IRoom> implements IRoomsModel {
key: { teamMain: 1 },
sparse: true,
},
{
key: { 'lastMessage.u._id': 1 },
sparse: true,
},
];
}

Expand Down
15 changes: 12 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8957,7 +8957,7 @@ __metadata:
express: "npm:^4.21.2"
hono: "npm:^4.10.7"
jest: "npm:~30.2.0"
qs: "npm:^6.14.0"
qs: "npm:^6.14.1"
supertest: "npm:~7.1.4"
ts-jest: "npm:~29.4.5"
typescript: "npm:~5.9.3"
Expand Down Expand Up @@ -9634,7 +9634,7 @@ __metadata:
proxy-from-env: "npm:^1.1.0"
proxyquire: "npm:^2.1.3"
psl: "npm:^1.10.0"
qs: "npm:^6.14.0"
qs: "npm:^6.14.1"
query-string: "npm:^7.1.3"
queue-fifo: "npm:^0.2.6"
raw-loader: "npm:~4.0.2"
Expand Down Expand Up @@ -31809,7 +31809,7 @@ __metadata:
languageName: node
linkType: hard

"qs@npm:^6.11.2, qs@npm:^6.12.3, qs@npm:^6.14.0, qs@npm:^6.9.4":
"qs@npm:^6.11.2, qs@npm:^6.12.3, qs@npm:^6.9.4":
version: 6.14.0
resolution: "qs@npm:6.14.0"
dependencies:
Expand All @@ -31818,6 +31818,15 @@ __metadata:
languageName: node
linkType: hard

"qs@npm:^6.14.1":
version: 6.14.1
resolution: "qs@npm:6.14.1"
dependencies:
side-channel: "npm:^1.1.0"
checksum: 10/34b5ab00a910df432d55180ef39c1d1375e550f098b5ec153b41787f1a6a6d7e5f9495593c3b112b77dbc6709d0ae18e55b82847a4c2bbbb0de1e8ccbb1794c5
languageName: node
linkType: hard

"qs@npm:~6.5.2":
version: 6.5.3
resolution: "qs@npm:6.5.3"
Expand Down