Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
223ee7f
wrap in transaction and save custom fields with updater
gabriellsh Feb 13, 2025
b351d4e
oops
gabriellsh Feb 13, 2025
a6fc69b
Pass session around
gabriellsh Feb 17, 2025
3f4375d
Use updater in place of Accounts setPasswordAsync
gabriellsh Feb 17, 2025
4ab2b03
pass session to avatar updates
gabriellsh Feb 17, 2025
51d5c8f
fix ts
gabriellsh Feb 18, 2025
c9de44b
fix unit
gabriellsh Feb 18, 2025
840a8ca
fix session passed to deferred operation
gabriellsh Feb 18, 2025
133152c
Move return clause in wrapInSessionTransaction
gabriellsh Feb 19, 2025
5800b2e
fix optional chaining and typo
gabriellsh Feb 19, 2025
902819f
Inline type in updateGroupDMsName
gabriellsh Feb 19, 2025
0ae5e05
accept only session in models methods options
gabriellsh Feb 19, 2025
3a462e0
Merge branch 'develop' into chore/updateUserTransaction
gabriellsh Feb 21, 2025
869f81e
helper for once session ended
gabriellsh Mar 6, 2025
3e8aafa
attach customfield side effect to event
gabriellsh Mar 6, 2025
a95661c
session in setUserAvatar
gabriellsh Mar 6, 2025
a59e1f0
session in setRealName
gabriellsh Mar 6, 2025
509a155
sideEffects after transaction in saveUserIdentity
gabriellsh Mar 6, 2025
0b860e4
session in setUsername
gabriellsh Mar 6, 2025
3c0015d
session in setEmail
gabriellsh Mar 6, 2025
ea103bd
session in setStatusText
gabriellsh Mar 6, 2025
ab64c3f
sideEffects after transaction commited in saveUser
gabriellsh Mar 6, 2025
05b1e6d
Update users model
gabriellsh Mar 6, 2025
152bfe4
Update Subscriptions model
gabriellsh Mar 6, 2025
cc44839
update BaseUploadModel
gabriellsh Mar 6, 2025
388d838
Update BaseRaw
gabriellsh Mar 6, 2025
59bf8aa
pass session to fileStore
gabriellsh Mar 6, 2025
ab6d7cb
Fix use of expired transaction
gabriellsh Mar 6, 2025
b455092
fix setUserAvatar
gabriellsh Mar 6, 2025
9d5fc45
Removed unused session from models
gabriellsh Mar 6, 2025
b420f80
Change module declaration
gabriellsh Mar 6, 2025
ef5d8c8
fix unit
gabriellsh Mar 6, 2025
a39484e
Merge branch 'develop' into chore/updateUserTransaction
gabriellsh Mar 12, 2025
cb7a812
Merge branch 'develop' into chore/updateUserTransaction
gabriellsh Mar 13, 2025
7104d0f
transaction success proxy event
gabriellsh Mar 13, 2025
8ad3063
Log errored side effects
gabriellsh Mar 13, 2025
e07b09b
wrap transaction error
gabriellsh Mar 13, 2025
979e9cf
Add flag to disable transaction
gabriellsh Mar 13, 2025
69d9a65
Merge branch 'develop' into chore/updateUserTransaction
gabriellsh Mar 13, 2025
ee6793a
Merge branch 'develop' into chore/updateUserTransaction
gabriellsh Mar 17, 2025
8238e17
Merge branch 'develop' into chore/updateUserTransaction
gabriellsh Mar 17, 2025
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: 2 additions & 3 deletions apps/meteor/app/api/server/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { Meteor } from 'meteor/meteor';
import type { RateLimiterOptionsToCheck } from 'meteor/rate-limit';
import { RateLimiter } from 'meteor/rate-limit';
import { WebApp } from 'meteor/webapp';
import semver from 'semver';
import _ from 'underscore';

import type { PermissionsPayload } from './api.helpers';
Expand Down Expand Up @@ -45,20 +44,20 @@ import type { Route } from './router';
import { Router } from './router';
import { isObject } from '../../../lib/utils/isObject';
import { getNestedProp } from '../../../server/lib/getNestedProp';
import { shouldBreakInVersion } from '../../../server/lib/shouldBreakInVersion';
import { checkCodeForUser } from '../../2fa/server/code';
import { hasPermissionAsync } from '../../authorization/server/functions/hasPermission';
import { notifyOnUserChangeAsync } from '../../lib/server/lib/notifyListener';
import { metrics } from '../../metrics/server';
import { settings } from '../../settings/server';
import { Info } from '../../utils/rocketchat.info';
import { getDefaultUserFields } from '../../utils/server/functions/getDefaultUserFields';

const logger = new Logger('API');

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

interface IAPIProperties {
useDefaultAuth: boolean;
Expand Down
4 changes: 0 additions & 4 deletions apps/meteor/app/api/server/v1/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,6 @@ API.v1.addRoute(

await saveUser(this.userId, userData);

if (this.bodyParams.data.customFields) {
await saveCustomFields(this.bodyParams.userId, this.bodyParams.data.customFields);
}

if (typeof this.bodyParams.data.active !== 'undefined') {
const {
userId,
Expand Down
36 changes: 23 additions & 13 deletions apps/meteor/app/file-upload/server/lib/FileUpload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import filesize from 'filesize';
import { Match } from 'meteor/check';
import { Meteor } from 'meteor/meteor';
import { Cookies } from 'meteor/ostrio:cookies';
import type { OptionalId } from 'mongodb';
import type { ClientSession, OptionalId } from 'mongodb';
import sharp from 'sharp';
import type { WritableStreamBuffer } from 'stream-buffers';
import streamBuffers from 'stream-buffers';
Expand Down Expand Up @@ -228,7 +228,7 @@ export const FileUpload = {

defaults,

async avatarsOnValidate(this: Store, file: IUpload) {
async avatarsOnValidate(this: Store, file: IUpload, options?: { session?: ClientSession }) {
if (settings.get('Accounts_AvatarResize') !== true) {
return;
}
Expand Down Expand Up @@ -277,6 +277,7 @@ export const FileUpload = {
...(['gif', 'svg'].includes(metadata.format || '') ? { type: 'image/png' } : {}),
},
},
options,
);
},

Expand Down Expand Up @@ -359,7 +360,7 @@ export const FileUpload = {
return store.insert(details, buffer);
},

async uploadsOnValidate(this: Store, file: IUpload) {
async uploadsOnValidate(this: Store, file: IUpload, options?: { session?: ClientSession }) {
if (!file.type || !/^image\/((x-windows-)?bmp|p?jpeg|png|gif|webp)$/.test(file.type)) {
return;
}
Expand Down Expand Up @@ -409,6 +410,7 @@ export const FileUpload = {
{
$set: { size, identify },
},
options,
);
},

Expand Down Expand Up @@ -721,13 +723,13 @@ export class FileUploadClass {
return modelsAvailable[modelName];
}

async delete(fileId: string) {
async delete(fileId: string, options?: { session?: ClientSession }) {
// TODO: Remove this method
if (this.store?.delete) {
await this.store.delete(fileId);
await this.store.delete(fileId, { session: options?.session });
}

return this.model.deleteFile(fileId);
return this.model.deleteFile(fileId, { session: options?.session });
}

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

async deleteByName(fileName: string) {
const file = await this.model.findOneByName(fileName);
async deleteByName(fileName: string, options?: { session?: ClientSession }) {
const file = await this.model.findOneByName(fileName, { session: options?.session });

if (!file) {
return;
Expand All @@ -766,8 +768,12 @@ export class FileUploadClass {
return store.delete(file._id);
}

async _doInsert(fileData: OptionalId<IUpload>, streamOrBuffer: ReadableStream | stream | Buffer): Promise<IUpload> {
const fileId = await this.store.create(fileData);
async _doInsert(
fileData: OptionalId<IUpload>,
streamOrBuffer: ReadableStream | stream | Buffer,
options?: { session?: ClientSession },
): Promise<IUpload> {
const fileId = await this.store.create(fileData, { session: options?.session });
const tmpFile = UploadFS.getTempFilePath(fileId);

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

const file = await ufsComplete(fileId, this.name);
const file = await ufsComplete(fileId, this.name, { session: options?.session });

return file;
} catch (e: any) {
throw e;
}
}

async insert(fileData: OptionalId<IUpload>, streamOrBuffer: ReadableStream | stream.Readable | Buffer) {
async insert(
fileData: OptionalId<IUpload>,
streamOrBuffer: ReadableStream | stream.Readable | Buffer,
options?: { session?: ClientSession },
) {
if (streamOrBuffer instanceof stream) {
streamOrBuffer = await streamToBuffer(streamOrBuffer);
}
Expand All @@ -803,6 +813,6 @@ export class FileUploadClass {
await filter.check(fileData, streamOrBuffer);
}

return this._doInsert(fileData, streamOrBuffer);
return this._doInsert(fileData, streamOrBuffer, { session: options?.session });
}
}
12 changes: 10 additions & 2 deletions apps/meteor/app/lib/server/functions/saveCustomFields.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import type { IUser } from '@rocket.chat/core-typings';
import type { Updater } from '@rocket.chat/models';
import type { ClientSession } from 'mongodb';

import { saveCustomFieldsWithoutValidation } from './saveCustomFieldsWithoutValidation';
import { validateCustomFields } from './validateCustomFields';
import { trim } from '../../../../lib/utils/stringUtils';
import { settings } from '../../../settings/server';

export const saveCustomFields = async function (userId: string, formData: Record<string, any>): Promise<void> {
export const saveCustomFields = async function (
userId: string,
formData: Record<string, any>,
options?: { _updater?: Updater<IUser>; session?: ClientSession },
): Promise<void> {
if (trim(settings.get('Accounts_CustomFields')).length === 0) {
return;
}

validateCustomFields(formData);
return saveCustomFieldsWithoutValidation(userId, formData);
return saveCustomFieldsWithoutValidation(userId, formData, options);
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import type { IUser } from '@rocket.chat/core-typings';
import type { Updater } from '@rocket.chat/models';
import { Subscriptions, Users } from '@rocket.chat/models';
import { Meteor } from 'meteor/meteor';
import type { ClientSession } from 'mongodb';

import { trim } from '../../../../lib/utils/stringUtils';
import { onceTransactionCommitedSuccessfully } from '../../../../server/database/utils';
import { settings } from '../../../settings/server';
import { notifyOnSubscriptionChangedByUserIdAndRoomType } from '../lib/notifyListener';

Expand All @@ -12,7 +16,14 @@ const getCustomFieldsMeta = function (customFieldsMeta: string) {
throw new Meteor.Error('error-invalid-customfield-json', 'Invalid JSON for Custom Fields');
}
};
export const saveCustomFieldsWithoutValidation = async function (userId: string, formData: Record<string, any>): Promise<void> {
export const saveCustomFieldsWithoutValidation = async function (
userId: string,
formData: Record<string, any>,
options?: {
_updater?: Updater<IUser>;
session?: ClientSession;
},
): Promise<void> {
const customFieldsSetting = settings.get<string>('Accounts_CustomFields');
if (!customFieldsSetting || trim(customFieldsSetting).length === 0) {
return;
Expand All @@ -29,7 +40,9 @@ export const saveCustomFieldsWithoutValidation = async function (userId: string,
{},
);

const updater = Users.getUpdater();
const { _updater, session } = options || {};

const updater = _updater || Users.getUpdater();

updater.set('customFields', customFields);

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

await Users.updateFromUpdater({ _id: userId }, updater);

// Update customFields of all Direct Messages' Rooms for userId
const setCustomFieldsResponse = await Subscriptions.setCustomFieldsDirectMessagesByUserId(userId, customFields);
if (setCustomFieldsResponse.modifiedCount) {
void notifyOnSubscriptionChangedByUserIdAndRoomType(userId, 'd');
if (!_updater) {
await Users.updateFromUpdater({ _id: userId }, updater, { session });
}

await onceTransactionCommitedSuccessfully(async () => {
const setCustomFieldsResponse = await Subscriptions.setCustomFieldsDirectMessagesByUserId(userId, customFields);
if (setCustomFieldsResponse.modifiedCount) {
void notifyOnSubscriptionChangedByUserIdAndRoomType(userId, 'd');
}
}, session);
};
Loading
Loading