[Gekidou MM-39729] Websocket Events - Groups (#5930)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
This commit is contained in:
Jason Frerich
2022-03-01 10:13:45 -06:00
committed by GitHub
parent b87cf8358b
commit efd2fd0c02
49 changed files with 26 additions and 1598 deletions

View File

@@ -9,10 +9,6 @@ import type ChannelMembershipModel from '@typings/database/models/servers/channe
import type CustomEmojiModel from '@typings/database/models/servers/custom_emoji';
import type DraftModel from '@typings/database/models/servers/draft';
import type FileModel from '@typings/database/models/servers/file';
import type GroupModel from '@typings/database/models/servers/group';
import type GroupMembershipModel from '@typings/database/models/servers/group_membership';
import type GroupsChannelModel from '@typings/database/models/servers/groups_channel';
import type GroupsTeamModel from '@typings/database/models/servers/groups_team';
import type MyChannelModel from '@typings/database/models/servers/my_channel';
import type MyChannelSettingsModel from '@typings/database/models/servers/my_channel_settings';
import type MyTeamModel from '@typings/database/models/servers/my_team';
@@ -83,26 +79,10 @@ export const isRecordCustomEmojiEqualToRaw = (record: CustomEmojiModel, raw: Cus
return raw.id === record.id;
};
export const isRecordGroupMembershipEqualToRaw = (record: GroupMembershipModel, raw: GroupMembership) => {
return raw.user_id === record.userId && raw.group_id === record.groupId;
};
export const isRecordChannelMembershipEqualToRaw = (record: ChannelMembershipModel, raw: Pick<ChannelMembership, 'user_id' | 'channel_id'>) => {
return raw.user_id === record.userId && raw.channel_id === record.channelId;
};
export const isRecordGroupEqualToRaw = (record: GroupModel, raw: Group) => {
return raw.id === record.id;
};
export const isRecordGroupsTeamEqualToRaw = (record: GroupsTeamModel, raw: GroupTeam) => {
return raw.team_id === record.teamId && raw.group_id === record.groupId;
};
export const isRecordGroupsChannelEqualToRaw = (record: GroupsChannelModel, raw: GroupChannel) => {
return raw.channel_id === record.channelId && raw.group_id === record.groupId;
};
export const isRecordTeamEqualToRaw = (record: TeamModel, raw: Team) => {
return raw.id === record.id;
};

View File

@@ -1,159 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import DatabaseManager from '@database/manager';
import {
isRecordGroupEqualToRaw,
isRecordGroupMembershipEqualToRaw,
isRecordGroupsChannelEqualToRaw,
isRecordGroupsTeamEqualToRaw,
} from '@database/operator/server_data_operator/comparators';
import {
transformGroupMembershipRecord,
transformGroupRecord,
transformGroupsChannelRecord,
transformGroupsTeamRecord,
} from '@database/operator/server_data_operator/transformers/group';
import ServerDataOperator from '..';
describe('*** Operator: Group Handlers tests ***', () => {
let operator: ServerDataOperator;
beforeAll(async () => {
await DatabaseManager.init(['baseHandler.test.com']);
operator = DatabaseManager.serverDatabases['baseHandler.test.com'].operator;
});
it('=> HandleGroup: should write to the GROUP table', async () => {
expect.assertions(2);
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
const groups: Group[] = [
{
id: 'id_groupdfjdlfkjdkfdsf',
name: 'mobile_team',
display_name: 'mobile team',
description: '',
remote_id: '',
create_at: 0,
update_at: 0,
delete_at: 0,
has_syncables: true,
type: '',
member_count: 1,
allow_reference: true,
},
];
await operator.handleGroup({
groups,
prepareRecordsOnly: false,
});
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
expect(spyOnHandleRecords).toHaveBeenCalledWith({
fieldName: 'id',
createOrUpdateRawValues: groups,
tableName: 'Group',
prepareRecordsOnly: false,
findMatchingRecordBy: isRecordGroupEqualToRaw,
transformer: transformGroupRecord,
});
});
it('=> HandleGroupsTeam: should write to the GROUPS_TEAM table', async () => {
expect.assertions(2);
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
const groupsTeams = [
{
team_id: 'team_899',
team_display_name: '',
team_type: '',
group_id: 'group_id89',
auto_add: true,
create_at: 0,
delete_at: 0,
update_at: 0,
},
];
await operator.handleGroupsTeam({
groupsTeams,
prepareRecordsOnly: false,
});
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
expect(spyOnHandleRecords).toHaveBeenCalledWith({
fieldName: 'group_id',
createOrUpdateRawValues: groupsTeams,
tableName: 'GroupsTeam',
prepareRecordsOnly: false,
findMatchingRecordBy: isRecordGroupsTeamEqualToRaw,
transformer: transformGroupsTeamRecord,
});
});
it('=> HandleGroupsChannel: should write to the GROUPS_CHANNEL table', async () => {
expect.assertions(2);
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
const groupsChannels = [
{
auto_add: true,
channel_display_name: '',
channel_id: 'channelid',
channel_type: '',
create_at: 0,
delete_at: 0,
group_id: 'groupId',
team_display_name: '',
team_id: '',
team_type: '',
update_at: 0,
},
];
await operator.handleGroupsChannel({
groupsChannels,
prepareRecordsOnly: false,
});
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
expect(spyOnHandleRecords).toHaveBeenCalledWith({
fieldName: 'group_id',
createOrUpdateRawValues: groupsChannels,
tableName: 'GroupsChannel',
prepareRecordsOnly: false,
findMatchingRecordBy: isRecordGroupsChannelEqualToRaw,
transformer: transformGroupsChannelRecord,
});
});
it('=> HandleGroupMembership: should write to the GROUP_MEMBERSHIP table', async () => {
expect.assertions(2);
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
const groupMemberships = [
{
user_id: 'u4cprpki7ri81mbx8efixcsb8jo',
group_id: 'g4cprpki7ri81mbx8efixcsb8jo',
},
];
await operator.handleGroupMembership({
groupMemberships,
prepareRecordsOnly: false,
});
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
expect(spyOnHandleRecords).toHaveBeenCalledWith({
fieldName: 'group_id',
createOrUpdateRawValues: groupMemberships,
tableName: 'GroupMembership',
prepareRecordsOnly: false,
findMatchingRecordBy: isRecordGroupMembershipEqualToRaw,
transformer: transformGroupMembershipRecord,
});
});
});

View File

@@ -1,152 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {MM_TABLES} from '@constants/database';
import DataOperatorException from '@database/exceptions/data_operator_exception';
import {
isRecordGroupEqualToRaw,
isRecordGroupMembershipEqualToRaw,
isRecordGroupsChannelEqualToRaw,
isRecordGroupsTeamEqualToRaw,
} from '@database/operator/server_data_operator/comparators';
import {
transformGroupMembershipRecord,
transformGroupRecord,
transformGroupsChannelRecord,
transformGroupsTeamRecord,
} from '@database/operator/server_data_operator/transformers/group';
import {getUniqueRawsBy} from '@database/operator/utils/general';
import type {HandleGroupArgs, HandleGroupMembershipArgs, HandleGroupsChannelArgs, HandleGroupsTeamArgs} from '@typings/database/database';
import type GroupModel from '@typings/database/models/servers/group';
import type GroupMembershipModel from '@typings/database/models/servers/group_membership';
import type GroupsChannelModel from '@typings/database/models/servers/groups_channel';
import type GroupsTeamModel from '@typings/database/models/servers/groups_team';
const {
GROUP,
GROUPS_CHANNEL,
GROUPS_TEAM,
GROUP_MEMBERSHIP,
} = MM_TABLES.SERVER;
export interface GroupHandlerMix {
handleGroupMembership: ({groupMemberships, prepareRecordsOnly}: HandleGroupMembershipArgs) => Promise<GroupMembershipModel[]>;
handleGroup: ({groups, prepareRecordsOnly}: HandleGroupArgs) => Promise<GroupModel[]>;
handleGroupsTeam: ({groupsTeams, prepareRecordsOnly}: HandleGroupsTeamArgs) => Promise<GroupsTeamModel[]>;
handleGroupsChannel: ({groupsChannels, prepareRecordsOnly}: HandleGroupsChannelArgs) => Promise<GroupsChannelModel[]>;
}
const GroupHandler = (superclass: any) => class extends superclass {
/**
* handleGroupMembership: Handler responsible for the Create/Update operations occurring on the GROUP_MEMBERSHIP table from the 'Server' schema
* @param {HandleGroupMembershipArgs} groupMembershipsArgs
* @param {RawGroupMembership[]} groupMembershipsArgs.groupMemberships
* @param {boolean} groupMembershipsArgs.prepareRecordsOnly
* @throws DataOperatorException
* @returns {Promise<GroupMembershipModel[]>}
*/
handleGroupMembership = ({groupMemberships, prepareRecordsOnly = true}: HandleGroupMembershipArgs): Promise<GroupMembershipModel[]> => {
if (!groupMemberships.length) {
throw new DataOperatorException(
'An empty "groupMemberships" array has been passed to the handleGroupMembership method',
);
}
const createOrUpdateRawValues = getUniqueRawsBy({raws: groupMemberships, key: 'group_id'});
return this.handleRecords({
fieldName: 'group_id',
findMatchingRecordBy: isRecordGroupMembershipEqualToRaw,
transformer: transformGroupMembershipRecord,
prepareRecordsOnly,
createOrUpdateRawValues,
tableName: GROUP_MEMBERSHIP,
});
};
/**
* handleGroup: Handler responsible for the Create/Update operations occurring on the GROUP table from the 'Server' schema
* @param {HandleGroupArgs} groupsArgs
* @param {RawGroup[]} groupsArgs.groups
* @param {boolean} groupsArgs.prepareRecordsOnly
* @throws DataOperatorException
* @returns {Promise<GroupModel[]>}
*/
handleGroup = ({groups, prepareRecordsOnly = true}: HandleGroupArgs): Promise<GroupModel[]> => {
if (!groups.length) {
throw new DataOperatorException(
'An empty "groups" array has been passed to the handleGroup method',
);
}
const createOrUpdateRawValues = getUniqueRawsBy({raws: groups, key: 'id'});
return this.handleRecords({
fieldName: 'id',
findMatchingRecordBy: isRecordGroupEqualToRaw,
transformer: transformGroupRecord,
prepareRecordsOnly,
createOrUpdateRawValues,
tableName: GROUP,
});
};
/**
* handleGroupsTeam: Handler responsible for the Create/Update operations occurring on the GROUPS_TEAM table from the 'Server' schema
* @param {HandleGroupsTeamArgs} groupsTeamsArgs
* @param {GroupsTeam[]} groupsTeamsArgs.groupsTeams
* @param {boolean} groupsTeamsArgs.prepareRecordsOnly
* @throws DataOperatorException
* @returns {Promise<GroupsTeamModel[]>}
*/
handleGroupsTeam = ({groupsTeams, prepareRecordsOnly = true}: HandleGroupsTeamArgs): Promise<GroupsTeamModel[]> => {
if (!groupsTeams.length) {
throw new DataOperatorException(
'An empty "groups" array has been passed to the handleGroupsTeam method',
);
}
const createOrUpdateRawValues = groupsTeams.filter((gt, index, self) => (
index === self.findIndex((item) => item.team_id === gt.team_id && item.group_id === gt.group_id)));
return this.handleRecords({
fieldName: 'group_id',
findMatchingRecordBy: isRecordGroupsTeamEqualToRaw,
transformer: transformGroupsTeamRecord,
prepareRecordsOnly,
createOrUpdateRawValues,
tableName: GROUPS_TEAM,
});
};
/**
* handleGroupsChannel: Handler responsible for the Create/Update operations occurring on the GROUPS_CHANNEL table from the 'Server' schema
* @param {HandleGroupsChannelArgs} groupsChannelsArgs
* @param {GroupsChannel[]} groupsChannelsArgs.groupsChannels
* @param {boolean} groupsChannelsArgs.prepareRecordsOnly
* @throws DataOperatorException
* @returns {Promise<GroupsChannelModel[]>}
*/
handleGroupsChannel = ({groupsChannels, prepareRecordsOnly = true}: HandleGroupsChannelArgs): Promise<GroupsChannelModel[]> => {
if (!groupsChannels.length) {
throw new DataOperatorException(
'An empty "groups" array has been passed to the handleGroupsTeam method',
);
}
const createOrUpdateRawValues = groupsChannels.filter((gc, index, self) => (
index === self.findIndex((item) => item.channel_id === gc.channel_id && item.group_id === gc.group_id)));
return this.handleRecords({
fieldName: 'group_id',
findMatchingRecordBy: isRecordGroupsChannelEqualToRaw,
transformer: transformGroupsChannelRecord,
prepareRecordsOnly,
createOrUpdateRawValues,
tableName: GROUPS_CHANNEL,
});
};
};
export default GroupHandler;

View File

@@ -4,7 +4,6 @@
import ServerDataOperatorBase from '@database/operator/server_data_operator/handlers';
import CategoryHandler, {CategoryHandlerMix} from '@database/operator/server_data_operator/handlers/category';
import ChannelHandler, {ChannelHandlerMix} from '@database/operator/server_data_operator/handlers/channel';
import GroupHandler, {GroupHandlerMix} from '@database/operator/server_data_operator/handlers/group';
import PostHandler, {PostHandlerMix} from '@database/operator/server_data_operator/handlers/post';
import PostsInChannelHandler, {PostsInChannelHandlerMix} from '@database/operator/server_data_operator/handlers/posts_in_channel';
import PostsInThreadHandler, {PostsInThreadHandlerMix} from '@database/operator/server_data_operator/handlers/posts_in_thread';
@@ -15,12 +14,11 @@ import mix from '@utils/mix';
import type {Database} from '@nozbe/watermelondb';
interface ServerDataOperator extends ServerDataOperatorBase, PostHandlerMix, PostsInChannelHandlerMix,
PostsInThreadHandlerMix, UserHandlerMix, GroupHandlerMix, ChannelHandlerMix, CategoryHandlerMix, TeamHandlerMix {}
PostsInThreadHandlerMix, UserHandlerMix, ChannelHandlerMix, CategoryHandlerMix, TeamHandlerMix {}
class ServerDataOperator extends mix(ServerDataOperatorBase).with(
CategoryHandler,
ChannelHandler,
GroupHandler,
PostHandler,
PostsInChannelHandler,
PostsInThreadHandler,

View File

@@ -1,126 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {
transformGroupMembershipRecord,
transformGroupRecord,
transformGroupsChannelRecord,
transformGroupsTeamRecord,
} from '@database/operator/server_data_operator/transformers/group';
import {createTestConnection} from '@database/operator/utils/create_test_connection';
import {OperationType} from '@typings/database/enums';
describe('*** GROUP Prepare Records Test ***', () => {
it('=> transformGroupRecord: should return an array of type Group', async () => {
expect.assertions(3);
const database = await createTestConnection({databaseName: 'group_prepare_records', setActive: true});
expect(database).toBeTruthy();
const preparedRecords = await transformGroupRecord({
action: OperationType.CREATE,
database: database!,
value: {
record: undefined,
raw: {
id: 'id_groupdfjdlfkjdkfdsf',
name: 'mobile_team',
display_name: 'mobile team',
description: '',
type: '',
remote_id: '',
create_at: 0,
update_at: 0,
delete_at: 0,
has_syncables: true,
member_count: 0,
allow_reference: false,
},
},
});
expect(preparedRecords).toBeTruthy();
expect(preparedRecords!.collection.modelClass.name).toBe('GroupModel');
});
it('=> transformGroupsTeamRecord: should return an array of type GroupsTeam', async () => {
expect.assertions(3);
const database = await createTestConnection({databaseName: 'group_prepare_records', setActive: true});
expect(database).toBeTruthy();
const preparedRecords = await transformGroupsTeamRecord({
action: OperationType.CREATE,
database: database!,
value: {
record: undefined,
raw: {
team_id: 'team_89',
team_display_name: '',
team_type: '',
group_id: 'group_id89',
auto_add: true,
create_at: 0,
delete_at: 0,
update_at: 0,
},
},
});
expect(preparedRecords).toBeTruthy();
expect(preparedRecords!.collection.modelClass.name).toBe('GroupsTeamModel');
});
it('=> transformGroupsChannelRecord: should return an array of type GroupsChannel', async () => {
expect.assertions(3);
const database = await createTestConnection({databaseName: 'group_prepare_records', setActive: true});
expect(database).toBeTruthy();
const preparedRecords = await transformGroupsChannelRecord({
action: OperationType.CREATE,
database: database!,
value: {
record: undefined,
raw: {
auto_add: true,
channel_display_name: '',
channel_id: 'channelid',
channel_type: '',
create_at: 0,
delete_at: 0,
group_id: 'groupId',
team_display_name: '',
team_id: '',
team_type: '',
update_at: 0,
},
},
});
expect(preparedRecords).toBeTruthy();
expect(preparedRecords!.collection.modelClass.name).toBe('GroupsChannelModel');
});
it('=> transformGroupMembershipRecord: should return an array of type GroupMembership', async () => {
expect.assertions(3);
const database = await createTestConnection({databaseName: 'group_prepare_records', setActive: true});
expect(database).toBeTruthy();
const preparedRecords = await transformGroupMembershipRecord({
action: OperationType.CREATE,
database: database!,
value: {
record: undefined,
raw: {
user_id: 'u4cprpki7ri81mbx8efixcsb8jo',
group_id: 'g4cprpki7ri81mbx8efixcsb8jo',
},
},
});
expect(preparedRecords).toBeTruthy();
expect(preparedRecords!.collection.modelClass.name).toBe('GroupMembershipModel');
});
});

View File

@@ -1,131 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {MM_TABLES} from '@constants/database';
import {prepareBaseRecord} from '@database/operator/server_data_operator/transformers/index';
import {OperationType} from '@typings/database/enums';
import type {TransformerArgs} from '@typings/database/database';
import type GroupModel from '@typings/database/models/servers/group';
import type GroupMembershipModel from '@typings/database/models/servers/group_membership';
import type GroupsChannelModel from '@typings/database/models/servers/groups_channel';
import type GroupsTeamModel from '@typings/database/models/servers/groups_team';
const {
GROUP,
GROUPS_CHANNEL,
GROUPS_TEAM,
GROUP_MEMBERSHIP,
} = MM_TABLES.SERVER;
/**
* transformGroupMembershipRecord: Prepares a record of the SERVER database 'GroupMembership' table for update or create actions.
* @param {TransformerArgs} operator
* @param {Database} operator.database
* @param {RecordPair} operator.value
* @returns {Promise<GroupMembershipModel>}
*/
export const transformGroupMembershipRecord = ({action, database, value}: TransformerArgs): Promise<GroupMembershipModel> => {
const raw = value.raw as GroupMembership;
const record = value.record as GroupMembershipModel;
const isCreateAction = action === OperationType.CREATE;
// If isCreateAction is true, we will use the id (API response) from the RAW, else we shall use the existing record id from the database
const fieldsMapper = (groupMember: GroupMembershipModel) => {
groupMember._raw.id = isCreateAction ? (raw?.id ?? groupMember.id) : record.id;
groupMember.groupId = raw.group_id;
groupMember.userId = raw.user_id;
};
return prepareBaseRecord({
action,
database,
tableName: GROUP_MEMBERSHIP,
value,
fieldsMapper,
}) as Promise<GroupMembershipModel>;
};
/**
* transformGroupRecord: Prepares a record of the SERVER database 'Group' table for update or create actions.
* @param {DataFactory} operator
* @param {Database} operator.database
* @param {RecordPair} operator.value
* @returns {Promise<GroupModel>}
*/
export const transformGroupRecord = ({action, database, value}: TransformerArgs): Promise<GroupModel> => {
const raw = value.raw as Group;
const record = value.record as GroupModel;
const isCreateAction = action === OperationType.CREATE;
// If isCreateAction is true, we will use the id (API response) from the RAW, else we shall use the existing record id from the database
const fieldsMapper = (group: GroupModel) => {
group._raw.id = isCreateAction ? (raw?.id ?? group.id) : record.id;
group.allowReference = raw.allow_reference;
group.deleteAt = raw.delete_at;
group.name = raw.name;
group.displayName = raw.display_name;
};
return prepareBaseRecord({
action,
database,
tableName: GROUP,
value,
fieldsMapper,
}) as Promise<GroupModel>;
};
/**
* transformGroupsTeamRecord: Prepares a record of the SERVER database 'GroupsTeam' table for update or create actions.
* @param {DataFactory} operator
* @param {Database} operator.database
* @param {RecordPair} operator.value
* @returns {Promise<GroupsTeamModel>}
*/
export const transformGroupsTeamRecord = ({action, database, value}: TransformerArgs): Promise<GroupsTeamModel> => {
const raw = value.raw as GroupTeam;
const record = value.record as GroupsTeamModel;
const isCreateAction = action === OperationType.CREATE;
const fieldsMapper = (groupsTeam: GroupsTeamModel) => {
groupsTeam._raw.id = isCreateAction ? groupsTeam.id : record.id;
groupsTeam.teamId = raw.team_id;
groupsTeam.groupId = raw.group_id;
};
return prepareBaseRecord({
action,
database,
tableName: GROUPS_TEAM,
value,
fieldsMapper,
}) as Promise<GroupsTeamModel>;
};
/**
* transformGroupsChannelRecord: Prepares a record of the SERVER database 'GroupsChannel' table for update or create actions.
* @param {DataFactory} operator
* @param {Database} operator.database
* @param {RecordPair} operator.value
* @returns {Promise<GroupsChannelModel>}
*/
export const transformGroupsChannelRecord = ({action, database, value}: TransformerArgs): Promise<GroupsChannelModel> => {
const raw = value.raw as GroupChannelRelation;
const record = value.record as GroupsChannelModel;
const isCreateAction = action === OperationType.CREATE;
const fieldsMapper = (groupsChannel: GroupsChannelModel) => {
groupsChannel._raw.id = isCreateAction ? groupsChannel.id : record.id;
groupsChannel.channelId = raw.channel_id;
groupsChannel.groupId = raw.group_id;
};
return prepareBaseRecord({
action,
database,
tableName: GROUPS_CHANNEL,
value,
fieldsMapper,
}) as Promise<GroupsChannelModel>;
};