refactor: Roles out of DB Watcher (#32280)

Co-authored-by: Diego Sampaio <8591547+sampaiodiego@users.noreply.github.com>
pull/32405/head
Ricardo Garim 2 years ago committed by GitHub
parent 5d1cb28ed9
commit 0154fac0de
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      apps/meteor/app/api/server/v1/roles.ts
  2. 24
      apps/meteor/app/lib/server/lib/notifyListenerOnRoleChanges.ts
  3. 8
      apps/meteor/ee/server/api/roles.ts
  4. 14
      apps/meteor/ee/server/lib/roles/insertRole.ts
  5. 3
      apps/meteor/ee/server/lib/roles/updateRole.ts
  6. 4
      apps/meteor/server/database/watchCollections.ts
  7. 11
      apps/meteor/server/lib/roles/createOrUpdateProtectedRole.ts
  8. 41
      apps/meteor/server/models/raw/Roles.ts
  9. 9
      apps/meteor/server/modules/listeners/listeners.module.ts
  10. 6
      packages/model-typings/src/models/IRolesModel.ts

@ -9,6 +9,7 @@ import { getUsersInRolePaginated } from '../../../authorization/server/functions
import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
import { hasRoleAsync, hasAnyRoleAsync } from '../../../authorization/server/functions/hasRole';
import { apiDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger';
import { notifyListenerOnRoleChanges } from '../../../lib/server/lib/notifyListenerOnRoleChanges';
import { settings } from '../../../settings/server/index';
import { API } from '../api';
import { getPaginationItems } from '../helpers/getPaginationItems';
@ -179,6 +180,8 @@ API.v1.addRoute(
await Roles.removeById(role._id);
void notifyListenerOnRoleChanges(role._id, 'removed', role);
return API.v1.success();
},
},

@ -0,0 +1,24 @@
import { api, dbWatchersDisabled } from '@rocket.chat/core-services';
import type { IRole } from '@rocket.chat/core-typings';
import { Roles } from '@rocket.chat/models';
type ClientAction = 'inserted' | 'updated' | 'removed';
export async function notifyListenerOnRoleChanges(
rid: IRole['_id'],
clientAction: ClientAction = 'updated',
existingRoleData?: IRole,
): Promise<void> {
if (!dbWatchersDisabled) {
return;
}
const role = existingRoleData || (await Roles.findOneById(rid));
if (role) {
void api.broadcast('watch.roles', {
clientAction,
role,
});
}
}

@ -130,9 +130,7 @@ API.v1.addRoute(
const role = await insertRoleAsync(roleData, options);
return API.v1.success({
role,
});
return API.v1.success({ role });
},
},
);
@ -172,9 +170,7 @@ API.v1.addRoute(
const updatedRole = await updateRole(roleId, roleData, options);
return API.v1.success({
role: updatedRole,
});
return API.v1.success({ role: updatedRole });
},
},
);

@ -2,6 +2,7 @@ import { api, MeteorError } from '@rocket.chat/core-services';
import type { IRole } from '@rocket.chat/core-typings';
import { Roles } from '@rocket.chat/models';
import { notifyListenerOnRoleChanges } from '../../../../app/lib/server/lib/notifyListenerOnRoleChanges';
import { isValidRoleScope } from '../../../../lib/roles/isValidRoleScope';
type InsertRoleOptions = {
@ -19,21 +20,16 @@ export const insertRoleAsync = async (roleData: Omit<IRole, '_id'>, options: Ins
throw new MeteorError('error-invalid-scope', 'Invalid scope');
}
const result = await Roles.createWithRandomId(name, scope, description, false, mandatory2fa);
const role = await Roles.createWithRandomId(name, scope, description, false, mandatory2fa);
const roleId = result.insertedId;
void notifyListenerOnRoleChanges(role._id, 'inserted', role);
if (options.broadcastUpdate) {
void api.broadcast('user.roleUpdate', {
type: 'changed',
_id: roleId,
_id: role._id,
});
}
const newRole = await Roles.findOneById(roleId);
if (!newRole) {
throw new MeteorError('error-role-not-found', 'Role not found');
}
return newRole;
return role;
};

@ -2,6 +2,7 @@ import { api, MeteorError } from '@rocket.chat/core-services';
import type { IRole } from '@rocket.chat/core-typings';
import { Roles } from '@rocket.chat/models';
import { notifyListenerOnRoleChanges } from '../../../../app/lib/server/lib/notifyListenerOnRoleChanges';
import { isValidRoleScope } from '../../../../lib/roles/isValidRoleScope';
type UpdateRoleOptions = {
@ -38,6 +39,8 @@ export const updateRole = async (roleId: IRole['_id'], roleData: Omit<IRole, '_i
await Roles.updateById(roleId, roleData.name, roleData.scope, roleData.description, roleData.mandatory2fa);
void notifyListenerOnRoleChanges(roleId);
if (options.broadcastUpdate) {
void api.broadcast('user.roleUpdate', {
type: 'changed',

@ -31,11 +31,9 @@ const onlyCollections = DBWATCHER_ONLY_COLLECTIONS.split(',')
export function getWatchCollections(): string[] {
const collections = [
Users.getCollectionName(),
Subscriptions.getCollectionName(),
LivechatInquiry.getCollectionName(),
LivechatDepartmentAgents.getCollectionName(),
Permissions.getCollectionName(),
Roles.getCollectionName(),
Rooms.getCollectionName(),
LoginServiceConfiguration.getCollectionName(),
InstanceStatus.getCollectionName(),
@ -45,11 +43,13 @@ export function getWatchCollections(): string[] {
PbxEvents.getCollectionName(),
Settings.getCollectionName(),
LivechatPriority.getCollectionName(),
Subscriptions.getCollectionName(),
];
// add back to the list of collections in case db watchers are enabled
if (!dbWatchersDisabled) {
collections.push(Messages.getCollectionName());
collections.push(Roles.getCollectionName());
}
if (onlyCollections.length > 0) {

@ -1,6 +1,8 @@
import type { IRole, AtLeast } from '@rocket.chat/core-typings';
import { Roles } from '@rocket.chat/models';
import { notifyListenerOnRoleChanges } from '../../../app/lib/server/lib/notifyListenerOnRoleChanges';
export const createOrUpdateProtectedRoleAsync = async (
roleId: string,
roleData: AtLeast<Omit<IRole, '_id' | 'protected'>, 'name'>,
@ -8,8 +10,9 @@ export const createOrUpdateProtectedRoleAsync = async (
const role = await Roles.findOneById<Pick<IRole, '_id' | 'name' | 'scope' | 'description' | 'mandatory2fa'>>(roleId, {
projection: { name: 1, scope: 1, description: 1, mandatory2fa: 1 },
});
if (role) {
await Roles.updateById(
const updatedRole = await Roles.updateById(
roleId,
roleData.name || role.name,
roleData.scope || role.scope,
@ -17,10 +20,12 @@ export const createOrUpdateProtectedRoleAsync = async (
roleData.mandatory2fa || role.mandatory2fa,
);
void notifyListenerOnRoleChanges(roleId, 'updated', updatedRole);
return;
}
await Roles.insertOne({
const insertedRole = await Roles.insertOne({
_id: roleId,
scope: 'Users',
description: '',
@ -28,4 +33,6 @@ export const createOrUpdateProtectedRoleAsync = async (
...roleData,
protected: true,
});
void notifyListenerOnRoleChanges(insertedRole.insertedId, 'inserted');
};

@ -1,7 +1,7 @@
import type { IRole, IRoom, IUser, RocketChatRecordDeleted } from '@rocket.chat/core-typings';
import type { IRolesModel } from '@rocket.chat/model-typings';
import { Subscriptions, Users } from '@rocket.chat/models';
import type { Collection, FindCursor, Db, Filter, FindOptions, InsertOneResult, UpdateResult, WithId, Document } from 'mongodb';
import type { Collection, FindCursor, Db, Filter, FindOptions, Document } from 'mongodb';
import { BaseRaw } from './BaseRaw';
@ -190,21 +190,31 @@ export class RolesRaw extends BaseRaw<IRole> implements IRolesModel {
return this.find(query, options || {});
}
updateById(
async updateById(
_id: IRole['_id'],
name: IRole['name'],
scope: IRole['scope'],
description: IRole['description'] = '',
mandatory2fa: IRole['mandatory2fa'] = false,
): Promise<UpdateResult> {
const queryData = {
name,
scope,
description,
mandatory2fa,
};
): Promise<IRole> {
const response = await this.findOneAndUpdate(
{ _id },
{
$set: {
name,
scope,
description,
mandatory2fa,
},
},
{ upsert: true, returnDocument: 'after' },
);
if (!response.value) {
throw new Error('Role not found');
}
return this.updateOne({ _id }, { $set: queryData }, { upsert: true });
return response.value;
}
findUsersInRole(roleId: IRole['_id'], scope?: IRoom['_id']): Promise<FindCursor<IUser>>;
@ -242,13 +252,13 @@ export class RolesRaw extends BaseRaw<IRole> implements IRolesModel {
}
}
createWithRandomId(
async createWithRandomId(
name: IRole['name'],
scope: IRole['scope'] = 'Users',
description = '',
protectedRole = true,
mandatory2fa = false,
): Promise<InsertOneResult<WithId<IRole>>> {
): Promise<IRole> {
const role = {
name,
scope,
@ -257,7 +267,12 @@ export class RolesRaw extends BaseRaw<IRole> implements IRolesModel {
mandatory2fa,
};
return this.insertOne(role);
const res = await this.insertOne(role);
return {
_id: res.insertedId,
...role,
};
}
async canAddUserToRole(uid: IUser['_id'], roleId: IRole['_id'], scope?: IRoom['_id']): Promise<boolean> {

@ -3,7 +3,7 @@ import type { ISetting as AppsSetting } from '@rocket.chat/apps-engine/definitio
import type { IServiceClass } from '@rocket.chat/core-services';
import { EnterpriseSettings } from '@rocket.chat/core-services';
import { isSettingColor, isSettingEnterprise, UserStatus } from '@rocket.chat/core-typings';
import type { IUser, IRoom, VideoConference, ISetting, IOmnichannelRoom, IMessage, IOTRMessage } from '@rocket.chat/core-typings';
import type { IUser, IRoom, IRole, VideoConference, ISetting, IOmnichannelRoom, IMessage, IOTRMessage } from '@rocket.chat/core-typings';
import { Logger } from '@rocket.chat/logger';
import { parse } from '@rocket.chat/message-parser';
@ -205,11 +205,10 @@ export class ListenersModule {
});
service.onEvent('watch.roles', ({ clientAction, role }): void => {
const payload = {
notifications.streamRoles.emitWithoutBroadcast('roles', {
type: clientAction,
...role,
};
notifications.streamRoles.emitWithoutBroadcast('roles', payload as any);
...(role as IRole),
});
});
service.onEvent('watch.inquiries', async ({ clientAction, inquiry, diff }): Promise<void> => {

@ -1,5 +1,5 @@
import type { IRole, IUser, IRoom } from '@rocket.chat/core-typings';
import type { FindCursor, FindOptions, InsertOneResult, UpdateResult, WithId } from 'mongodb';
import type { FindCursor, FindOptions } from 'mongodb';
import type { IBaseModel } from './IBaseModel';
@ -32,7 +32,7 @@ export interface IRolesModel extends IBaseModel<IRole> {
scope: IRole['scope'],
description?: IRole['description'],
mandatory2fa?: IRole['mandatory2fa'],
): Promise<UpdateResult>;
): Promise<IRole>;
findUsersInRole(roleId: IRole['_id'], scope?: IRoom['_id']): Promise<FindCursor<IUser>>;
findUsersInRole(roleId: IRole['_id'], scope: IRoom['_id'] | undefined, options: FindOptions<IUser>): Promise<FindCursor<IUser>>;
@ -58,7 +58,7 @@ export interface IRolesModel extends IBaseModel<IRole> {
description?: string,
protectedRole?: boolean,
mandatory2fa?: boolean,
): Promise<InsertOneResult<WithId<IRole>>>;
): Promise<IRole>;
canAddUserToRole(uid: IUser['_id'], roleId: IRole['_id'], scope?: IRoom['_id']): Promise<boolean>;
}

Loading…
Cancel
Save