Skip to content

Commit 79a6c1f

Browse files
authored
chore: Use transaction for saveUser function (#35203)
1 parent aa7b6a4 commit 79a6c1f

File tree

29 files changed

+568
-283
lines changed

29 files changed

+568
-283
lines changed

apps/meteor/app/api/server/api.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import { Meteor } from 'meteor/meteor';
1515
import type { RateLimiterOptionsToCheck } from 'meteor/rate-limit';
1616
import { RateLimiter } from 'meteor/rate-limit';
1717
import { WebApp } from 'meteor/webapp';
18-
import semver from 'semver';
1918
import _ from 'underscore';
2019

2120
import type { PermissionsPayload } from './api.helpers';
@@ -45,20 +44,20 @@ import type { Route } from './router';
4544
import { Router } from './router';
4645
import { isObject } from '../../../lib/utils/isObject';
4746
import { getNestedProp } from '../../../server/lib/getNestedProp';
47+
import { shouldBreakInVersion } from '../../../server/lib/shouldBreakInVersion';
4848
import { checkCodeForUser } from '../../2fa/server/code';
4949
import { hasPermissionAsync } from '../../authorization/server/functions/hasPermission';
5050
import { notifyOnUserChangeAsync } from '../../lib/server/lib/notifyListener';
5151
import { metrics } from '../../metrics/server';
5252
import { settings } from '../../settings/server';
53-
import { Info } from '../../utils/rocketchat.info';
5453
import { getDefaultUserFields } from '../../utils/server/functions/getDefaultUserFields';
5554

5655
const logger = new Logger('API');
5756

5857
// We have some breaking changes planned to the API.
5958
// To avoid conflicts or missing something during the period we are adopting a 'feature flag approach'
6059
// TODO: MAJOR check if this is still needed
61-
const applyBreakingChanges = semver.gte(Info.version, '8.0.0');
60+
const applyBreakingChanges = shouldBreakInVersion('8.0.0');
6261

6362
interface IAPIProperties {
6463
useDefaultAuth: boolean;

apps/meteor/app/api/server/v1/users.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,6 @@ API.v1.addRoute(
117117

118118
await saveUser(this.userId, userData);
119119

120-
if (this.bodyParams.data.customFields) {
121-
await saveCustomFields(this.bodyParams.userId, this.bodyParams.data.customFields);
122-
}
123-
124120
if (typeof this.bodyParams.data.active !== 'undefined') {
125121
const {
126122
userId,

apps/meteor/app/file-upload/server/lib/FileUpload.ts

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import filesize from 'filesize';
1717
import { Match } from 'meteor/check';
1818
import { Meteor } from 'meteor/meteor';
1919
import { Cookies } from 'meteor/ostrio:cookies';
20-
import type { OptionalId } from 'mongodb';
20+
import type { ClientSession, OptionalId } from 'mongodb';
2121
import sharp from 'sharp';
2222
import type { WritableStreamBuffer } from 'stream-buffers';
2323
import streamBuffers from 'stream-buffers';
@@ -228,7 +228,7 @@ export const FileUpload = {
228228

229229
defaults,
230230

231-
async avatarsOnValidate(this: Store, file: IUpload) {
231+
async avatarsOnValidate(this: Store, file: IUpload, options?: { session?: ClientSession }) {
232232
if (settings.get('Accounts_AvatarResize') !== true) {
233233
return;
234234
}
@@ -277,6 +277,7 @@ export const FileUpload = {
277277
...(['gif', 'svg'].includes(metadata.format || '') ? { type: 'image/png' } : {}),
278278
},
279279
},
280+
options,
280281
);
281282
},
282283

@@ -359,7 +360,7 @@ export const FileUpload = {
359360
return store.insert(details, buffer);
360361
},
361362

362-
async uploadsOnValidate(this: Store, file: IUpload) {
363+
async uploadsOnValidate(this: Store, file: IUpload, options?: { session?: ClientSession }) {
363364
if (!file.type || !/^image\/((x-windows-)?bmp|p?jpeg|png|gif|webp)$/.test(file.type)) {
364365
return;
365366
}
@@ -409,6 +410,7 @@ export const FileUpload = {
409410
{
410411
$set: { size, identify },
411412
},
413+
options,
412414
);
413415
},
414416

@@ -721,13 +723,13 @@ export class FileUploadClass {
721723
return modelsAvailable[modelName];
722724
}
723725

724-
async delete(fileId: string) {
726+
async delete(fileId: string, options?: { session?: ClientSession }) {
725727
// TODO: Remove this method
726728
if (this.store?.delete) {
727-
await this.store.delete(fileId);
729+
await this.store.delete(fileId, { session: options?.session });
728730
}
729731

730-
return this.model.deleteFile(fileId);
732+
return this.model.deleteFile(fileId, { session: options?.session });
731733
}
732734

733735
async deleteById(fileId: string) {
@@ -742,8 +744,8 @@ export class FileUploadClass {
742744
return store.delete(file._id);
743745
}
744746

745-
async deleteByName(fileName: string) {
746-
const file = await this.model.findOneByName(fileName);
747+
async deleteByName(fileName: string, options?: { session?: ClientSession }) {
748+
const file = await this.model.findOneByName(fileName, { session: options?.session });
747749

748750
if (!file) {
749751
return;
@@ -766,8 +768,12 @@ export class FileUploadClass {
766768
return store.delete(file._id);
767769
}
768770

769-
async _doInsert(fileData: OptionalId<IUpload>, streamOrBuffer: ReadableStream | stream | Buffer): Promise<IUpload> {
770-
const fileId = await this.store.create(fileData);
771+
async _doInsert(
772+
fileData: OptionalId<IUpload>,
773+
streamOrBuffer: ReadableStream | stream | Buffer,
774+
options?: { session?: ClientSession },
775+
): Promise<IUpload> {
776+
const fileId = await this.store.create(fileData, { session: options?.session });
771777
const tmpFile = UploadFS.getTempFilePath(fileId);
772778

773779
try {
@@ -779,15 +785,19 @@ export class FileUploadClass {
779785
throw new Error('Invalid file type');
780786
}
781787

782-
const file = await ufsComplete(fileId, this.name);
788+
const file = await ufsComplete(fileId, this.name, { session: options?.session });
783789

784790
return file;
785791
} catch (e: any) {
786792
throw e;
787793
}
788794
}
789795

790-
async insert(fileData: OptionalId<IUpload>, streamOrBuffer: ReadableStream | stream.Readable | Buffer) {
796+
async insert(
797+
fileData: OptionalId<IUpload>,
798+
streamOrBuffer: ReadableStream | stream.Readable | Buffer,
799+
options?: { session?: ClientSession },
800+
) {
791801
if (streamOrBuffer instanceof stream) {
792802
streamOrBuffer = await streamToBuffer(streamOrBuffer);
793803
}
@@ -803,6 +813,6 @@ export class FileUploadClass {
803813
await filter.check(fileData, streamOrBuffer);
804814
}
805815

806-
return this._doInsert(fileData, streamOrBuffer);
816+
return this._doInsert(fileData, streamOrBuffer, { session: options?.session });
807817
}
808818
}
Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
1+
import type { IUser } from '@rocket.chat/core-typings';
2+
import type { Updater } from '@rocket.chat/models';
3+
import type { ClientSession } from 'mongodb';
4+
15
import { saveCustomFieldsWithoutValidation } from './saveCustomFieldsWithoutValidation';
26
import { validateCustomFields } from './validateCustomFields';
37
import { trim } from '../../../../lib/utils/stringUtils';
48
import { settings } from '../../../settings/server';
59

6-
export const saveCustomFields = async function (userId: string, formData: Record<string, any>): Promise<void> {
10+
export const saveCustomFields = async function (
11+
userId: string,
12+
formData: Record<string, any>,
13+
options?: { _updater?: Updater<IUser>; session?: ClientSession },
14+
): Promise<void> {
715
if (trim(settings.get('Accounts_CustomFields')).length === 0) {
816
return;
917
}
1018

1119
validateCustomFields(formData);
12-
return saveCustomFieldsWithoutValidation(userId, formData);
20+
return saveCustomFieldsWithoutValidation(userId, formData, options);
1321
};

apps/meteor/app/lib/server/functions/saveCustomFieldsWithoutValidation.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
import type { IUser } from '@rocket.chat/core-typings';
2+
import type { Updater } from '@rocket.chat/models';
13
import { Subscriptions, Users } from '@rocket.chat/models';
24
import { Meteor } from 'meteor/meteor';
5+
import type { ClientSession } from 'mongodb';
36

47
import { trim } from '../../../../lib/utils/stringUtils';
8+
import { onceTransactionCommitedSuccessfully } from '../../../../server/database/utils';
59
import { settings } from '../../../settings/server';
610
import { notifyOnSubscriptionChangedByUserIdAndRoomType } from '../lib/notifyListener';
711

@@ -12,7 +16,14 @@ const getCustomFieldsMeta = function (customFieldsMeta: string) {
1216
throw new Meteor.Error('error-invalid-customfield-json', 'Invalid JSON for Custom Fields');
1317
}
1418
};
15-
export const saveCustomFieldsWithoutValidation = async function (userId: string, formData: Record<string, any>): Promise<void> {
19+
export const saveCustomFieldsWithoutValidation = async function (
20+
userId: string,
21+
formData: Record<string, any>,
22+
options?: {
23+
_updater?: Updater<IUser>;
24+
session?: ClientSession;
25+
},
26+
): Promise<void> {
1627
const customFieldsSetting = settings.get<string>('Accounts_CustomFields');
1728
if (!customFieldsSetting || trim(customFieldsSetting).length === 0) {
1829
return;
@@ -29,7 +40,9 @@ export const saveCustomFieldsWithoutValidation = async function (userId: string,
2940
{},
3041
);
3142

32-
const updater = Users.getUpdater();
43+
const { _updater, session } = options || {};
44+
45+
const updater = _updater || Users.getUpdater();
3346

3447
updater.set('customFields', customFields);
3548

@@ -48,11 +61,14 @@ export const saveCustomFieldsWithoutValidation = async function (userId: string,
4861
}
4962
});
5063

51-
await Users.updateFromUpdater({ _id: userId }, updater);
52-
53-
// Update customFields of all Direct Messages' Rooms for userId
54-
const setCustomFieldsResponse = await Subscriptions.setCustomFieldsDirectMessagesByUserId(userId, customFields);
55-
if (setCustomFieldsResponse.modifiedCount) {
56-
void notifyOnSubscriptionChangedByUserIdAndRoomType(userId, 'd');
64+
if (!_updater) {
65+
await Users.updateFromUpdater({ _id: userId }, updater, { session });
5766
}
67+
68+
await onceTransactionCommitedSuccessfully(async () => {
69+
const setCustomFieldsResponse = await Subscriptions.setCustomFieldsDirectMessagesByUserId(userId, customFields);
70+
if (setCustomFieldsResponse.modifiedCount) {
71+
void notifyOnSubscriptionChangedByUserIdAndRoomType(userId, 'd');
72+
}
73+
}, session);
5874
};

0 commit comments

Comments
 (0)