forked from Ivasoft/mattermost-mobile
[Gekidou] Refactor storage layer (#5471)
* Refactored storage layer - in progress * Refactored DatabaseManager & Operators * Renamed isRecordAppEqualToRaw to isRecordInfoEqualToRaw * Review feedback * Update app/database/models/app/info.ts Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com> * Update app/database/models/server/my_team.ts Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com> Co-authored-by: Avinash Lingaloo <> Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {
|
||||
isRecordChannelEqualToRaw,
|
||||
isRecordChannelInfoEqualToRaw,
|
||||
isRecordMyChannelEqualToRaw,
|
||||
isRecordMyChannelSettingsEqualToRaw,
|
||||
} from '@database/operator/server_data_operator/comparators';
|
||||
import {
|
||||
transformChannelInfoRecord,
|
||||
transformChannelRecord,
|
||||
transformMyChannelRecord,
|
||||
transformMyChannelSettingsRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/channel';
|
||||
|
||||
import ServerDataOperator from '..';
|
||||
|
||||
import type {RawChannel} from '@typings/database/database';
|
||||
|
||||
describe('*** Operator: Channel Handlers tests ***', () => {
|
||||
let operator: ServerDataOperator;
|
||||
beforeAll(async () => {
|
||||
await DatabaseManager.init(['baseHandler.test.com']);
|
||||
operator = DatabaseManager.serverDatabases['baseHandler.test.com'].operator;
|
||||
});
|
||||
|
||||
it('=> HandleChannel: should write to the CHANNEL table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const channels: RawChannel[] = [
|
||||
{
|
||||
id: 'kjlw9j1ttnxwig7tnqgebg7dtipno',
|
||||
create_at: 1600185541285,
|
||||
update_at: 1604401077256,
|
||||
delete_at: 0,
|
||||
team_id: '',
|
||||
type: 'D',
|
||||
display_name: '',
|
||||
name: 'gh781zkzkhh357b4bejephjz5u8daw__9ciscaqbrpd6d8s68k76xb9bte',
|
||||
header: '(https://mattermost',
|
||||
purpose: '',
|
||||
last_post_at: 1617311494451,
|
||||
total_msg_count: 585,
|
||||
extra_update_at: 0,
|
||||
creator_id: '',
|
||||
group_constrained: null,
|
||||
shared: false,
|
||||
props: null,
|
||||
scheme_id: null,
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleChannel({
|
||||
channels,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'id',
|
||||
createOrUpdateRawValues: channels,
|
||||
tableName: 'Channel',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordChannelEqualToRaw,
|
||||
transformer: transformChannelRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleMyChannelSettings: should write to the MY_CHANNEL_SETTINGS table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const settings = [
|
||||
{
|
||||
channel_id: 'c',
|
||||
notify_props: {
|
||||
desktop: 'all',
|
||||
desktop_sound: true,
|
||||
email: true,
|
||||
first_name: true,
|
||||
mention_keys: '',
|
||||
push: 'mention',
|
||||
channel: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleMyChannelSettings({
|
||||
settings,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'channel_id',
|
||||
createOrUpdateRawValues: settings,
|
||||
tableName: 'MyChannelSettings',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordMyChannelSettingsEqualToRaw,
|
||||
transformer: transformMyChannelSettingsRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleChannelInfo: should write to the CHANNEL_INFO table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator as any, 'handleRecords');
|
||||
const channelInfos = [
|
||||
{
|
||||
channel_id: 'c',
|
||||
guest_count: 10,
|
||||
header: 'channel info header',
|
||||
member_count: 10,
|
||||
pinned_post_count: 3,
|
||||
purpose: 'sample channel ',
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleChannelInfo({
|
||||
channelInfos,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'channel_id',
|
||||
createOrUpdateRawValues: channelInfos,
|
||||
tableName: 'ChannelInfo',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordChannelInfoEqualToRaw,
|
||||
transformer: transformChannelInfoRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleMyChannel: should write to the MY_CHANNEL table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const myChannels = [
|
||||
{
|
||||
channel_id: 'c',
|
||||
last_post_at: 1617311494451,
|
||||
last_viewed_at: 1617311494451,
|
||||
mentions_count: 3,
|
||||
message_count: 10,
|
||||
roles: 'guest',
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleMyChannel({
|
||||
myChannels,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'channel_id',
|
||||
createOrUpdateRawValues: myChannels,
|
||||
tableName: 'MyChannel',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordMyChannelEqualToRaw,
|
||||
transformer: transformMyChannelRecord,
|
||||
});
|
||||
});
|
||||
});
|
||||
176
app/database/operator/server_data_operator/handlers/channel.ts
Normal file
176
app/database/operator/server_data_operator/handlers/channel.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
// 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 {
|
||||
isRecordChannelEqualToRaw,
|
||||
isRecordChannelInfoEqualToRaw,
|
||||
isRecordMyChannelEqualToRaw,
|
||||
isRecordMyChannelSettingsEqualToRaw,
|
||||
} from '@database/operator/server_data_operator/comparators';
|
||||
import {
|
||||
transformChannelInfoRecord,
|
||||
transformChannelRecord,
|
||||
transformMyChannelRecord,
|
||||
transformMyChannelSettingsRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/channel';
|
||||
import {getUniqueRawsBy} from '@database/operator/utils/general';
|
||||
import Channel from '@typings/database/models/servers/channel';
|
||||
import ChannelInfo from '@typings/database/models/servers/channel_info';
|
||||
import {
|
||||
HandleChannelArgs,
|
||||
HandleChannelInfoArgs,
|
||||
HandleMyChannelArgs,
|
||||
HandleMyChannelSettingsArgs,
|
||||
} from '@typings/database/database';
|
||||
import MyChannel from '@typings/database/models/servers/my_channel';
|
||||
import MyChannelSettings from '@typings/database/models/servers/my_channel_settings';
|
||||
|
||||
const {
|
||||
CHANNEL,
|
||||
CHANNEL_INFO,
|
||||
MY_CHANNEL,
|
||||
MY_CHANNEL_SETTINGS,
|
||||
} = MM_TABLES.SERVER;
|
||||
|
||||
export interface ChannelHandlerMix {
|
||||
handleChannel: ({channels, prepareRecordsOnly}: HandleChannelArgs) => Channel[] | boolean;
|
||||
handleMyChannelSettings: ({settings, prepareRecordsOnly}: HandleMyChannelSettingsArgs) => MyChannelSettings[] | boolean;
|
||||
handleChannelInfo: ({channelInfos, prepareRecordsOnly}: HandleChannelInfoArgs) => ChannelInfo[] | boolean;
|
||||
handleMyChannel: ({myChannels, prepareRecordsOnly}: HandleMyChannelArgs) => MyChannel[] | boolean;
|
||||
}
|
||||
|
||||
const ChannelHandler = (superclass: any) => class extends superclass {
|
||||
/**
|
||||
* handleChannel: Handler responsible for the Create/Update operations occurring on the CHANNEL table from the 'Server' schema
|
||||
* @param {HandleChannelArgs} channelsArgs
|
||||
* @param {RawChannel[]} channelsArgs.channels
|
||||
* @param {boolean} channelsArgs.prepareRecordsOnly
|
||||
* @throws DataOperatorException
|
||||
* @returns {Channel[]}
|
||||
*/
|
||||
handleChannel = async ({channels, prepareRecordsOnly = true}: HandleChannelArgs) => {
|
||||
let records: Channel[] = [];
|
||||
|
||||
if (!channels.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "channels" array has been passed to the handleChannel method',
|
||||
);
|
||||
}
|
||||
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: channels, key: 'id'});
|
||||
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'id',
|
||||
findMatchingRecordBy: isRecordChannelEqualToRaw,
|
||||
transformer: transformChannelRecord,
|
||||
prepareRecordsOnly,
|
||||
createOrUpdateRawValues,
|
||||
tableName: CHANNEL,
|
||||
});
|
||||
|
||||
return records;
|
||||
};
|
||||
|
||||
/**
|
||||
* handleMyChannelSettings: Handler responsible for the Create/Update operations occurring on the MY_CHANNEL_SETTINGS table from the 'Server' schema
|
||||
* @param {HandleMyChannelSettingsArgs} settingsArgs
|
||||
* @param {RawMyChannelSettings[]} settingsArgs.settings
|
||||
* @param {boolean} settingsArgs.prepareRecordsOnly
|
||||
* @throws DataOperatorException
|
||||
* @returns {MyChannelSettings[]}
|
||||
*/
|
||||
handleMyChannelSettings = async ({settings, prepareRecordsOnly = true}: HandleMyChannelSettingsArgs) => {
|
||||
let records: MyChannelSettings[] = [];
|
||||
|
||||
if (!settings.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "settings" array has been passed to the handleMyChannelSettings method',
|
||||
);
|
||||
}
|
||||
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: settings, key: 'channel_id'});
|
||||
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'channel_id',
|
||||
findMatchingRecordBy: isRecordMyChannelSettingsEqualToRaw,
|
||||
transformer: transformMyChannelSettingsRecord,
|
||||
prepareRecordsOnly,
|
||||
createOrUpdateRawValues,
|
||||
tableName: MY_CHANNEL_SETTINGS,
|
||||
});
|
||||
|
||||
return records;
|
||||
};
|
||||
|
||||
/**
|
||||
* handleChannelInfo: Handler responsible for the Create/Update operations occurring on the CHANNEL_INFO table from the 'Server' schema
|
||||
* @param {HandleChannelInfoArgs} channelInfosArgs
|
||||
* @param {RawChannelInfo[]} channelInfosArgs.channelInfos
|
||||
* @param {boolean} channelInfosArgs.prepareRecordsOnly
|
||||
* @throws DataOperatorException
|
||||
* @returns {ChannelInfo[]}
|
||||
*/
|
||||
handleChannelInfo = async ({channelInfos, prepareRecordsOnly = true}: HandleChannelInfoArgs) => {
|
||||
let records: ChannelInfo[] = [];
|
||||
|
||||
if (!channelInfos.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "channelInfos" array has been passed to the handleMyChannelSettings method',
|
||||
);
|
||||
}
|
||||
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({
|
||||
raws: channelInfos,
|
||||
key: 'channel_id',
|
||||
});
|
||||
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'channel_id',
|
||||
findMatchingRecordBy: isRecordChannelInfoEqualToRaw,
|
||||
transformer: transformChannelInfoRecord,
|
||||
prepareRecordsOnly,
|
||||
createOrUpdateRawValues,
|
||||
tableName: CHANNEL_INFO,
|
||||
});
|
||||
|
||||
return records;
|
||||
};
|
||||
|
||||
/**
|
||||
* handleMyChannel: Handler responsible for the Create/Update operations occurring on the MY_CHANNEL table from the 'Server' schema
|
||||
* @param {HandleMyChannelArgs} myChannelsArgs
|
||||
* @param {RawMyChannel[]} myChannelsArgs.myChannels
|
||||
* @param {boolean} myChannelsArgs.prepareRecordsOnly
|
||||
* @throws DataOperatorException
|
||||
* @returns {MyChannel[]}
|
||||
*/
|
||||
handleMyChannel = async ({myChannels, prepareRecordsOnly = true}: HandleMyChannelArgs) => {
|
||||
let records: MyChannel[] = [];
|
||||
|
||||
if (!myChannels.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "myChannels" array has been passed to the handleMyChannel method',
|
||||
);
|
||||
}
|
||||
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({
|
||||
raws: myChannels,
|
||||
key: 'channel_id',
|
||||
});
|
||||
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'channel_id',
|
||||
findMatchingRecordBy: isRecordMyChannelEqualToRaw,
|
||||
transformer: transformMyChannelRecord,
|
||||
prepareRecordsOnly,
|
||||
createOrUpdateRawValues,
|
||||
tableName: MY_CHANNEL,
|
||||
});
|
||||
|
||||
return records;
|
||||
};
|
||||
};
|
||||
|
||||
export default ChannelHandler;
|
||||
@@ -0,0 +1,159 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {
|
||||
isRecordGroupEqualToRaw,
|
||||
isRecordGroupMembershipEqualToRaw,
|
||||
isRecordGroupsInChannelEqualToRaw,
|
||||
isRecordGroupsInTeamEqualToRaw,
|
||||
} from '@database/operator/server_data_operator/comparators';
|
||||
import {
|
||||
transformGroupMembershipRecord,
|
||||
transformGroupRecord,
|
||||
transformGroupsInChannelRecord,
|
||||
transformGroupsInTeamRecord,
|
||||
} 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 = [
|
||||
{
|
||||
id: 'id_groupdfjdlfkjdkfdsf',
|
||||
name: 'mobile_team',
|
||||
display_name: 'mobile team',
|
||||
description: '',
|
||||
source: '',
|
||||
remote_id: '',
|
||||
create_at: 0,
|
||||
update_at: 0,
|
||||
delete_at: 0,
|
||||
has_syncables: true,
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleGroup({
|
||||
groups,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'name',
|
||||
createOrUpdateRawValues: groups,
|
||||
tableName: 'Group',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordGroupEqualToRaw,
|
||||
transformer: transformGroupRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleGroupsInTeam: should write to the GROUPS_IN_TEAM table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const groupsInTeams = [
|
||||
{
|
||||
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.handleGroupsInTeam({
|
||||
groupsInTeams,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'group_id',
|
||||
createOrUpdateRawValues: groupsInTeams,
|
||||
tableName: 'GroupsInTeam',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordGroupsInTeamEqualToRaw,
|
||||
transformer: transformGroupsInTeamRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleGroupsInChannel: should write to the GROUPS_IN_CHANNEL table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const groupsInChannels = [
|
||||
{
|
||||
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,
|
||||
member_count: 0,
|
||||
timezone_count: 0,
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleGroupsInChannel({
|
||||
groupsInChannels,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'group_id',
|
||||
createOrUpdateRawValues: groupsInChannels,
|
||||
tableName: 'GroupsInChannel',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordGroupsInChannelEqualToRaw,
|
||||
transformer: transformGroupsInChannelRecord,
|
||||
});
|
||||
});
|
||||
|
||||
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: 'user_id',
|
||||
createOrUpdateRawValues: groupMemberships,
|
||||
tableName: 'GroupMembership',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordGroupMembershipEqualToRaw,
|
||||
transformer: transformGroupMembershipRecord,
|
||||
});
|
||||
});
|
||||
});
|
||||
170
app/database/operator/server_data_operator/handlers/group.ts
Normal file
170
app/database/operator/server_data_operator/handlers/group.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
// 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,
|
||||
isRecordGroupsInChannelEqualToRaw,
|
||||
isRecordGroupsInTeamEqualToRaw,
|
||||
} from '@database/operator/server_data_operator/comparators';
|
||||
import {
|
||||
transformGroupMembershipRecord,
|
||||
transformGroupRecord,
|
||||
transformGroupsInChannelRecord,
|
||||
transformGroupsInTeamRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/group';
|
||||
import {getUniqueRawsBy} from '@database/operator/utils/general';
|
||||
import {
|
||||
HandleGroupArgs,
|
||||
HandleGroupMembershipArgs,
|
||||
HandleGroupsInChannelArgs,
|
||||
HandleGroupsInTeamArgs,
|
||||
} from '@typings/database/database';
|
||||
import Group from '@typings/database/models/servers/group';
|
||||
import GroupMembership from '@typings/database/models/servers/group_membership';
|
||||
import GroupsInChannel from '@typings/database/models/servers/groups_in_channel';
|
||||
import GroupsInTeam from '@typings/database/models/servers/groups_in_team';
|
||||
|
||||
const {
|
||||
GROUP,
|
||||
GROUPS_IN_CHANNEL,
|
||||
GROUPS_IN_TEAM,
|
||||
GROUP_MEMBERSHIP,
|
||||
} = MM_TABLES.SERVER;
|
||||
|
||||
export interface GroupHandlerMix {
|
||||
handleGroupMembership : ({groupMemberships, prepareRecordsOnly}: HandleGroupMembershipArgs) => GroupMembership[] | boolean,
|
||||
handleGroup : ({groups, prepareRecordsOnly}: HandleGroupArgs) => Group[] | boolean,
|
||||
handleGroupsInTeam : ({groupsInTeams, prepareRecordsOnly} : HandleGroupsInTeamArgs) => GroupsInTeam[] | boolean,
|
||||
handleGroupsInChannel : ({groupsInChannels, prepareRecordsOnly}: HandleGroupsInChannelArgs) => GroupsInChannel[] | boolean
|
||||
}
|
||||
|
||||
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 {GroupMembership[]}
|
||||
*/
|
||||
handleGroupMembership = async ({groupMemberships, prepareRecordsOnly = true}: HandleGroupMembershipArgs) => {
|
||||
let records: GroupMembership[] = [];
|
||||
|
||||
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'});
|
||||
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'user_id',
|
||||
findMatchingRecordBy: isRecordGroupMembershipEqualToRaw,
|
||||
transformer: transformGroupMembershipRecord,
|
||||
prepareRecordsOnly,
|
||||
createOrUpdateRawValues,
|
||||
tableName: GROUP_MEMBERSHIP,
|
||||
});
|
||||
|
||||
return records;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 {Group[]}
|
||||
*/
|
||||
handleGroup = async ({groups, prepareRecordsOnly = true}: HandleGroupArgs) => {
|
||||
let records: Group[] = [];
|
||||
|
||||
if (!groups.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "groups" array has been passed to the handleGroup method',
|
||||
);
|
||||
}
|
||||
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: groups, key: 'name'});
|
||||
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'name',
|
||||
findMatchingRecordBy: isRecordGroupEqualToRaw,
|
||||
transformer: transformGroupRecord,
|
||||
prepareRecordsOnly,
|
||||
createOrUpdateRawValues,
|
||||
tableName: GROUP,
|
||||
});
|
||||
|
||||
return records;
|
||||
};
|
||||
|
||||
/**
|
||||
* handleGroupsInTeam: Handler responsible for the Create/Update operations occurring on the GROUPS_IN_TEAM table from the 'Server' schema
|
||||
* @param {HandleGroupsInTeamArgs} groupsInTeamsArgs
|
||||
* @param {RawGroupsInTeam[]} groupsInTeamsArgs.groupsInTeams
|
||||
* @param {boolean} groupsInTeamsArgs.prepareRecordsOnly
|
||||
* @throws DataOperatorException
|
||||
* @returns {GroupsInTeam[]}
|
||||
*/
|
||||
handleGroupsInTeam = async ({groupsInTeams, prepareRecordsOnly = true} : HandleGroupsInTeamArgs) => {
|
||||
let records: GroupsInTeam[] = [];
|
||||
|
||||
if (!groupsInTeams.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "groups" array has been passed to the handleGroupsInTeam method',
|
||||
);
|
||||
}
|
||||
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: groupsInTeams, key: 'group_id'});
|
||||
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'group_id',
|
||||
findMatchingRecordBy: isRecordGroupsInTeamEqualToRaw,
|
||||
transformer: transformGroupsInTeamRecord,
|
||||
prepareRecordsOnly,
|
||||
createOrUpdateRawValues,
|
||||
tableName: GROUPS_IN_TEAM,
|
||||
});
|
||||
|
||||
return records;
|
||||
};
|
||||
|
||||
/**
|
||||
* handleGroupsInChannel: Handler responsible for the Create/Update operations occurring on the GROUPS_IN_CHANNEL table from the 'Server' schema
|
||||
* @param {HandleGroupsInChannelArgs} groupsInChannelsArgs
|
||||
* @param {RawGroupsInChannel[]} groupsInChannelsArgs.groupsInChannels
|
||||
* @param {boolean} groupsInChannelsArgs.prepareRecordsOnly
|
||||
* @throws DataOperatorException
|
||||
* @returns {GroupsInChannel[]}
|
||||
*/
|
||||
handleGroupsInChannel = async ({groupsInChannels, prepareRecordsOnly = true}: HandleGroupsInChannelArgs) => {
|
||||
let records: GroupsInChannel[] = [];
|
||||
|
||||
if (!groupsInChannels.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "groups" array has been passed to the handleGroupsInTeam method',
|
||||
);
|
||||
}
|
||||
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: groupsInChannels, key: 'channel_id'});
|
||||
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'group_id',
|
||||
findMatchingRecordBy: isRecordGroupsInChannelEqualToRaw,
|
||||
transformer: transformGroupsInChannelRecord,
|
||||
prepareRecordsOnly,
|
||||
createOrUpdateRawValues,
|
||||
tableName: GROUPS_IN_CHANNEL,
|
||||
});
|
||||
|
||||
return records;
|
||||
};
|
||||
};
|
||||
|
||||
export default GroupHandler;
|
||||
@@ -0,0 +1,158 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import DataOperatorException from '@database/exceptions/data_operator_exception';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {
|
||||
isRecordCustomEmojiEqualToRaw,
|
||||
isRecordRoleEqualToRaw,
|
||||
isRecordSystemEqualToRaw,
|
||||
isRecordTermsOfServiceEqualToRaw,
|
||||
} from '@database/operator/server_data_operator/comparators';
|
||||
import {
|
||||
transformCustomEmojiRecord,
|
||||
transformRoleRecord,
|
||||
transformSystemRecord,
|
||||
transformTermsOfServiceRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/general';
|
||||
import {RawRole, RawTermsOfService} from '@typings/database/database';
|
||||
|
||||
import ServerDataOperator from '..';
|
||||
|
||||
describe('*** DataOperator: Base Handlers tests ***', () => {
|
||||
let operator: ServerDataOperator;
|
||||
beforeAll(async () => {
|
||||
await DatabaseManager.init(['baseHandler.test.com']);
|
||||
operator = DatabaseManager.serverDatabases['baseHandler.test.com'].operator;
|
||||
});
|
||||
|
||||
it('=> HandleRole: should write to the ROLE table', async () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
|
||||
const roles: RawRole[] = [
|
||||
{
|
||||
id: 'custom-role-id-1',
|
||||
name: 'custom-role-1',
|
||||
permissions: ['custom-permission-1'],
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleRole({
|
||||
roles,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'id',
|
||||
transformer: transformRoleRecord,
|
||||
findMatchingRecordBy: isRecordRoleEqualToRaw,
|
||||
createOrUpdateRawValues: roles,
|
||||
tableName: 'Role',
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleCustomEmojis: should write to the CUSTOM_EMOJI table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const emojis = [
|
||||
{
|
||||
id: 'i',
|
||||
create_at: 1580913641769,
|
||||
update_at: 1580913641769,
|
||||
delete_at: 0,
|
||||
creator_id: '4cprpki7ri81mbx8efixcsb8jo',
|
||||
name: 'boomI',
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleCustomEmojis({
|
||||
emojis,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'id',
|
||||
createOrUpdateRawValues: emojis,
|
||||
tableName: 'CustomEmoji',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordCustomEmojiEqualToRaw,
|
||||
transformer: transformCustomEmojiRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleSystem: should write to the SYSTEM table', async () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
|
||||
const systems = [{name: 'system-1', value: 'system-1'}];
|
||||
|
||||
await operator.handleSystem({
|
||||
systems,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
findMatchingRecordBy: isRecordSystemEqualToRaw,
|
||||
fieldName: 'name',
|
||||
transformer: transformSystemRecord,
|
||||
createOrUpdateRawValues: systems,
|
||||
tableName: 'System',
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleTermsOfService: should write to the TERMS_OF_SERVICE table', async () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
|
||||
const termOfService: RawTermsOfService[] = [
|
||||
{
|
||||
id: 'tos-1',
|
||||
accepted_at: 1,
|
||||
create_at: 1613667352029,
|
||||
user_id: 'user1613667352029',
|
||||
text: '',
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleTermOfService({
|
||||
termOfService,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
findMatchingRecordBy: isRecordTermsOfServiceEqualToRaw,
|
||||
fieldName: 'id',
|
||||
transformer: transformTermsOfServiceRecord,
|
||||
createOrUpdateRawValues: termOfService,
|
||||
tableName: 'TermsOfService',
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> No table name: should not call execute if tableName is invalid', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
const appDatabase = DatabaseManager.appDatabase?.database;
|
||||
const appOperator = DatabaseManager.appDatabase?.operator;
|
||||
expect(appDatabase).toBeTruthy();
|
||||
expect(appOperator).toBeTruthy();
|
||||
|
||||
await expect(
|
||||
operator?.handleRecords({
|
||||
fieldName: 'invalidField',
|
||||
tableName: 'INVALID_TABLE_NAME',
|
||||
|
||||
// @ts-expect-error: Type does not match RawValue
|
||||
createOrUpdateRawValues: [{id: 'tos-1', accepted_at: 1}],
|
||||
}),
|
||||
).rejects.toThrow(DataOperatorException);
|
||||
});
|
||||
});
|
||||
122
app/database/operator/server_data_operator/handlers/index.ts
Normal file
122
app/database/operator/server_data_operator/handlers/index.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import BaseDataOperator from '@database/operator/base_data_operator';
|
||||
import DataOperatorException from '@database/exceptions/data_operator_exception';
|
||||
import {
|
||||
isRecordCustomEmojiEqualToRaw,
|
||||
isRecordRoleEqualToRaw,
|
||||
isRecordSystemEqualToRaw,
|
||||
isRecordTermsOfServiceEqualToRaw,
|
||||
} from '@database/operator/server_data_operator/comparators';
|
||||
import {
|
||||
transformCustomEmojiRecord,
|
||||
transformRoleRecord,
|
||||
transformSystemRecord,
|
||||
transformTermsOfServiceRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/general';
|
||||
import {getUniqueRawsBy} from '@database/operator/utils/general';
|
||||
import {HandleCustomEmojiArgs, HandleRoleArgs, HandleSystemArgs, HandleTOSArgs, OperationArgs} from '@typings/database/database';
|
||||
|
||||
const {SERVER: {CUSTOM_EMOJI, ROLE, SYSTEM, TERMS_OF_SERVICE}} = MM_TABLES;
|
||||
|
||||
export default class ServerDataOperatorBase extends BaseDataOperator {
|
||||
handleRole = async ({roles, prepareRecordsOnly = true}: HandleRoleArgs) => {
|
||||
if (!roles.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "values" array has been passed to the handleRole',
|
||||
);
|
||||
}
|
||||
|
||||
const records = await this.handleRecords({
|
||||
fieldName: 'id',
|
||||
findMatchingRecordBy: isRecordRoleEqualToRaw,
|
||||
transformer: transformRoleRecord,
|
||||
prepareRecordsOnly,
|
||||
createOrUpdateRawValues: getUniqueRawsBy({raws: roles, key: 'id'}),
|
||||
tableName: ROLE,
|
||||
});
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
handleCustomEmojis = async ({emojis, prepareRecordsOnly = true}: HandleCustomEmojiArgs) => {
|
||||
if (!emojis.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "values" array has been passed to the handleCustomEmojis',
|
||||
);
|
||||
}
|
||||
|
||||
const records = await this.handleRecords({
|
||||
fieldName: 'id',
|
||||
findMatchingRecordBy: isRecordCustomEmojiEqualToRaw,
|
||||
transformer: transformCustomEmojiRecord,
|
||||
prepareRecordsOnly,
|
||||
createOrUpdateRawValues: getUniqueRawsBy({raws: emojis, key: 'id'}),
|
||||
tableName: CUSTOM_EMOJI,
|
||||
});
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
handleSystem = async ({systems, prepareRecordsOnly = true}: HandleSystemArgs) => {
|
||||
if (!systems.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "values" array has been passed to the handleSystem',
|
||||
);
|
||||
}
|
||||
|
||||
const records = await this.handleRecords({
|
||||
fieldName: 'name',
|
||||
findMatchingRecordBy: isRecordSystemEqualToRaw,
|
||||
transformer: transformSystemRecord,
|
||||
prepareRecordsOnly,
|
||||
createOrUpdateRawValues: getUniqueRawsBy({raws: systems, key: 'name'}),
|
||||
tableName: SYSTEM,
|
||||
});
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
handleTermOfService = async ({termOfService, prepareRecordsOnly = true}: HandleTOSArgs) => {
|
||||
if (!termOfService.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "values" array has been passed to the handleTermOfService',
|
||||
);
|
||||
}
|
||||
|
||||
const records = await this.handleRecords({
|
||||
fieldName: 'id',
|
||||
findMatchingRecordBy: isRecordTermsOfServiceEqualToRaw,
|
||||
transformer: transformTermsOfServiceRecord,
|
||||
prepareRecordsOnly,
|
||||
createOrUpdateRawValues: getUniqueRawsBy({raws: termOfService, key: 'id'}),
|
||||
tableName: TERMS_OF_SERVICE,
|
||||
});
|
||||
|
||||
return records;
|
||||
}
|
||||
|
||||
/**
|
||||
* execute: Handles the Create/Update operations on an table.
|
||||
* @param {OperationArgs} execute
|
||||
* @param {string} execute.tableName
|
||||
* @param {RecordValue[]} execute.createRaws
|
||||
* @param {RecordValue[]} execute.updateRaws
|
||||
* @param {(TransformerArgs) => Promise<Model>} execute.recordOperator
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
execute = async ({createRaws, transformer, tableName, updateRaws}: OperationArgs) => {
|
||||
const models = await this.prepareRecords({
|
||||
tableName,
|
||||
createRaws,
|
||||
updateRaws,
|
||||
transformer,
|
||||
});
|
||||
|
||||
if (models?.length > 0) {
|
||||
await this.batchRecords(models);
|
||||
}
|
||||
};
|
||||
}
|
||||
342
app/database/operator/server_data_operator/handlers/post.test.ts
Normal file
342
app/database/operator/server_data_operator/handlers/post.test.ts
Normal file
@@ -0,0 +1,342 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {isRecordDraftEqualToRaw} from '@database/operator/server_data_operator/comparators';
|
||||
import {transformDraftRecord} from '@database/operator/server_data_operator/transformers/post';
|
||||
|
||||
import ServerDataOperator from '..';
|
||||
|
||||
describe('*** Operator: Post Handlers tests ***', () => {
|
||||
let operator: ServerDataOperator;
|
||||
|
||||
beforeAll(async () => {
|
||||
await DatabaseManager.init(['baseHandler.test.com']);
|
||||
operator = DatabaseManager.serverDatabases['baseHandler.test.com'].operator;
|
||||
});
|
||||
|
||||
it('=> HandleDraft: should write to the the Draft table', async () => {
|
||||
expect.assertions(1);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const drafts = [
|
||||
{
|
||||
channel_id: '4r9jmr7eqt8dxq3f9woypzurrychannelid',
|
||||
files: [
|
||||
{
|
||||
id: '322dxx',
|
||||
user_id: 'user_id',
|
||||
post_id: 'post_id',
|
||||
create_at: 123,
|
||||
update_at: 456,
|
||||
delete_at: 789,
|
||||
name: 'an_image',
|
||||
extension: 'jpg',
|
||||
size: 10,
|
||||
mime_type: 'image',
|
||||
width: 10,
|
||||
height: 10,
|
||||
has_preview_image: false,
|
||||
clientId: 'clientId',
|
||||
},
|
||||
],
|
||||
message: 'test draft message for post',
|
||||
root_id: '',
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleDraft({drafts, prepareRecordsOnly: false});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
findMatchingRecordBy: isRecordDraftEqualToRaw,
|
||||
fieldName: 'channel_id',
|
||||
transformer: transformDraftRecord,
|
||||
createOrUpdateRawValues: drafts,
|
||||
tableName: 'Draft',
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandlePosts: should write to the Post and its sub-child tables', async () => {
|
||||
expect.assertions(12);
|
||||
|
||||
const posts = [
|
||||
{
|
||||
id: '8swgtrrdiff89jnsiwiip3y1eoe',
|
||||
create_at: 1596032651747,
|
||||
update_at: 1596032651747,
|
||||
edit_at: 0,
|
||||
delete_at: 0,
|
||||
is_pinned: false,
|
||||
user_id: 'q3mzxua9zjfczqakxdkowc6u6yy',
|
||||
channel_id: 'xxoq1p6bqg7dkxb3kj1mcjoungw',
|
||||
root_id: '',
|
||||
parent_id: 'ps81iqbddesfby8jayz7owg4yypoo',
|
||||
original_id: '',
|
||||
message: "I'll second these kudos! Thanks m!",
|
||||
type: '',
|
||||
props: {},
|
||||
hashtags: '',
|
||||
pending_post_id: '',
|
||||
reply_count: 4,
|
||||
last_reply_at: 0,
|
||||
participants: null,
|
||||
metadata: {
|
||||
images: {
|
||||
'https://community-release.mattermost.com/api/v4/image?url=https%3A%2F%2Favatars1.githubusercontent.com%2Fu%2F6913320%3Fs%3D400%26v%3D4': {
|
||||
width: 400,
|
||||
height: 400,
|
||||
format: 'png',
|
||||
frame_count: 0,
|
||||
},
|
||||
},
|
||||
reactions: [
|
||||
{
|
||||
user_id: 'njic1w1k5inefp848jwk6oukio',
|
||||
post_id: 'a7ebyw883trm884p1qcgt8yw4a',
|
||||
emoji_name: 'clap',
|
||||
create_at: 1608252965442,
|
||||
update_at: 1608252965442,
|
||||
delete_at: 0,
|
||||
},
|
||||
],
|
||||
embeds: [
|
||||
{
|
||||
type: 'opengraph',
|
||||
url: 'https://github.com/mickmister/mattermost-plugin-default-theme',
|
||||
data: {
|
||||
type: 'object',
|
||||
url: 'https://github.com/mickmister/mattermost-plugin-default-theme',
|
||||
title: 'mickmister/mattermost-plugin-default-theme',
|
||||
description: 'Contribute to mickmister/mattermost-plugin-default-theme development by creating an account on GitHub.',
|
||||
determiner: '',
|
||||
site_name: 'GitHub',
|
||||
locale: '',
|
||||
locales_alternate: null,
|
||||
images: [
|
||||
{
|
||||
url: '',
|
||||
secure_url: 'https://community-release.mattermost.com/api/v4/image?url=https%3A%2F%2Favatars1.githubusercontent.com%2Fu%2F6913320%3Fs%3D400%26v%3D4',
|
||||
type: '',
|
||||
width: 0,
|
||||
height: 0,
|
||||
},
|
||||
],
|
||||
audios: null,
|
||||
videos: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
emojis: [
|
||||
{
|
||||
id: 'dgwyadacdbbwjc8t357h6hwsrh',
|
||||
create_at: 1502389307432,
|
||||
update_at: 1502389307432,
|
||||
delete_at: 0,
|
||||
creator_id: 'x6sdh1ok1tyd9f4dgq4ybw839a',
|
||||
name: 'thanks',
|
||||
},
|
||||
],
|
||||
files: [
|
||||
{
|
||||
id: 'f1oxe5rtepfs7n3zifb4sso7po',
|
||||
user_id: '89ertha8xpfsumpucqppy5knao',
|
||||
post_id: 'a7ebyw883trm884p1qcgt8yw4a',
|
||||
create_at: 1608270920357,
|
||||
update_at: 1608270920357,
|
||||
delete_at: 0,
|
||||
name: '4qtwrg.jpg',
|
||||
extension: 'jpg',
|
||||
size: 89208,
|
||||
mime_type: 'image/jpeg',
|
||||
width: 500,
|
||||
height: 656,
|
||||
has_preview_image: true,
|
||||
mini_preview:
|
||||
'/9j/2wCEAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRQBAwQEBQQFCQUFCRQNCw0UFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFP/AABEIABAAEAMBIgACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/AN/T/iZp+pX15FpUmnwLbXtpJpyy2sQLw8CcBXA+bksCDnHGOaf4W+P3xIshbQ6loB8RrbK11f3FpbBFW3ZwiFGHB2kr25BIOeCPPbX4S3407T7rTdDfxFNIpDyRaw9lsB4OECHGR15yO4GK6fRPhR4sGmSnxAs8NgchNOjvDPsjz8qSHA37cDk5JPPFdlOpTdPlcVt/Ku1lrvr17b67EPnjrH8/626H/9k=',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '8fcnk3p1jt8mmkaprgajoxz115a',
|
||||
create_at: 1596104683748,
|
||||
update_at: 1596104683748,
|
||||
edit_at: 0,
|
||||
delete_at: 0,
|
||||
is_pinned: false,
|
||||
user_id: 'hy5sq51sebfh58ktrce5ijtcwyy',
|
||||
channel_id: 'xxoq1p6bqg7dkxb3kj1mcjoungw',
|
||||
root_id: '8swgtrrdiff89jnsiwiip3y1eoe',
|
||||
parent_id: '',
|
||||
original_id: '',
|
||||
message: 'a added to the channel by j.',
|
||||
type: 'system_add_to_channel',
|
||||
props: {
|
||||
addedUserId: 'z89qsntet7bimd3xddfu7u9ncdaxc',
|
||||
addedUsername: 'a',
|
||||
userId: 'hy5sdfdfq51sebfh58ktrce5ijtcwy',
|
||||
username: 'j',
|
||||
},
|
||||
hashtags: '',
|
||||
pending_post_id: '',
|
||||
reply_count: 0,
|
||||
last_reply_at: 0,
|
||||
participants: null,
|
||||
metadata: {},
|
||||
},
|
||||
{
|
||||
id: '3y3w3a6gkbg73bnj3xund9o5ic',
|
||||
create_at: 1596277483749,
|
||||
update_at: 1596277483749,
|
||||
edit_at: 0,
|
||||
delete_at: 0,
|
||||
is_pinned: false,
|
||||
user_id: '44ud4m9tqwby3mphzzdwm7h31sr',
|
||||
channel_id: 'xxoq1p6bqg7dkxb3kj1mcjoungw',
|
||||
root_id: '8swgtrrdiff89jnsiwiip3y1eoe',
|
||||
parent_id: 'ps81iqbwesfby8jayz7owg4yypo',
|
||||
original_id: '',
|
||||
message: 'Great work M!',
|
||||
type: '',
|
||||
props: {},
|
||||
hashtags: '',
|
||||
pending_post_id: '',
|
||||
reply_count: 4,
|
||||
last_reply_at: 0,
|
||||
participants: null,
|
||||
metadata: {},
|
||||
},
|
||||
];
|
||||
|
||||
const spyOnHandleFiles = jest.spyOn(operator, 'handleFiles');
|
||||
const spyOnHandlePostMetadata = jest.spyOn(operator, 'handlePostMetadata');
|
||||
const spyOnHandleReactions = jest.spyOn(operator, 'handleReactions');
|
||||
const spyOnHandleCustomEmojis = jest.spyOn(operator, 'handleCustomEmojis');
|
||||
const spyOnHandlePostsInThread = jest.spyOn(operator, 'handlePostsInThread');
|
||||
const spyOnHandlePostsInChannel = jest.spyOn(operator, 'handlePostsInChannel');
|
||||
|
||||
// handlePosts will in turn call handlePostsInThread
|
||||
await operator.handlePosts({
|
||||
orders: [
|
||||
'8swgtrrdiff89jnsiwiip3y1eoe',
|
||||
'8fcnk3p1jt8mmkaprgajoxz115a',
|
||||
'3y3w3a6gkbg73bnj3xund9o5ic',
|
||||
],
|
||||
values: posts,
|
||||
previousPostId: '',
|
||||
});
|
||||
|
||||
expect(spyOnHandleReactions).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleReactions).toHaveBeenCalledWith({
|
||||
reactions: [
|
||||
{
|
||||
user_id: 'njic1w1k5inefp848jwk6oukio',
|
||||
post_id: 'a7ebyw883trm884p1qcgt8yw4a',
|
||||
emoji_name: 'clap',
|
||||
create_at: 1608252965442,
|
||||
update_at: 1608252965442,
|
||||
delete_at: 0,
|
||||
},
|
||||
],
|
||||
prepareRecordsOnly: true,
|
||||
});
|
||||
|
||||
expect(spyOnHandleFiles).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleFiles).toHaveBeenCalledWith({
|
||||
files: [
|
||||
{
|
||||
id: 'f1oxe5rtepfs7n3zifb4sso7po',
|
||||
user_id: '89ertha8xpfsumpucqppy5knao',
|
||||
post_id: 'a7ebyw883trm884p1qcgt8yw4a',
|
||||
create_at: 1608270920357,
|
||||
update_at: 1608270920357,
|
||||
delete_at: 0,
|
||||
name: '4qtwrg.jpg',
|
||||
extension: 'jpg',
|
||||
size: 89208,
|
||||
mime_type: 'image/jpeg',
|
||||
width: 500,
|
||||
height: 656,
|
||||
has_preview_image: true,
|
||||
mini_preview:
|
||||
'/9j/2wCEAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRQBAwQEBQQFCQUFCRQNCw0UFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFP/AABEIABAAEAMBIgACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/AN/T/iZp+pX15FpUmnwLbXtpJpyy2sQLw8CcBXA+bksCDnHGOaf4W+P3xIshbQ6loB8RrbK11f3FpbBFW3ZwiFGHB2kr25BIOeCPPbX4S3407T7rTdDfxFNIpDyRaw9lsB4OECHGR15yO4GK6fRPhR4sGmSnxAs8NgchNOjvDPsjz8qSHA37cDk5JPPFdlOpTdPlcVt/Ku1lrvr17b67EPnjrH8/626H/9k=',
|
||||
},
|
||||
],
|
||||
prepareRecordsOnly: true,
|
||||
});
|
||||
|
||||
expect(spyOnHandlePostMetadata).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandlePostMetadata).toHaveBeenCalledWith({
|
||||
embeds: [
|
||||
{
|
||||
embed: [
|
||||
{
|
||||
type: 'opengraph',
|
||||
url: 'https://github.com/mickmister/mattermost-plugin-default-theme',
|
||||
data: {
|
||||
type: 'object',
|
||||
url: 'https://github.com/mickmister/mattermost-plugin-default-theme',
|
||||
title: 'mickmister/mattermost-plugin-default-theme',
|
||||
description: 'Contribute to mickmister/mattermost-plugin-default-theme development by creating an account on GitHub.',
|
||||
determiner: '',
|
||||
site_name: 'GitHub',
|
||||
locale: '',
|
||||
locales_alternate: null,
|
||||
images: [
|
||||
{
|
||||
url: '',
|
||||
secure_url: 'https://community-release.mattermost.com/api/v4/image?url=https%3A%2F%2Favatars1.githubusercontent.com%2Fu%2F6913320%3Fs%3D400%26v%3D4',
|
||||
type: '',
|
||||
width: 0,
|
||||
height: 0,
|
||||
},
|
||||
],
|
||||
audios: null,
|
||||
videos: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
postId: '8swgtrrdiff89jnsiwiip3y1eoe',
|
||||
},
|
||||
],
|
||||
images: [
|
||||
{
|
||||
images: {
|
||||
'https://community-release.mattermost.com/api/v4/image?url=https%3A%2F%2Favatars1.githubusercontent.com%2Fu%2F6913320%3Fs%3D400%26v%3D4': {
|
||||
width: 400,
|
||||
height: 400,
|
||||
format: 'png',
|
||||
frame_count: 0,
|
||||
},
|
||||
},
|
||||
postId: '8swgtrrdiff89jnsiwiip3y1eoe',
|
||||
},
|
||||
],
|
||||
prepareRecordsOnly: true,
|
||||
});
|
||||
|
||||
expect(spyOnHandleCustomEmojis).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleCustomEmojis).toHaveBeenCalledWith({
|
||||
prepareRecordsOnly: false,
|
||||
emojis: [
|
||||
{
|
||||
id: 'dgwyadacdbbwjc8t357h6hwsrh',
|
||||
create_at: 1502389307432,
|
||||
update_at: 1502389307432,
|
||||
delete_at: 0,
|
||||
creator_id: 'x6sdh1ok1tyd9f4dgq4ybw839a',
|
||||
name: 'thanks',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(spyOnHandlePostsInThread).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandlePostsInThread).toHaveBeenCalledWith([
|
||||
{earliest: 1596032651747, post_id: '8swgtrrdiff89jnsiwiip3y1eoe'},
|
||||
]);
|
||||
|
||||
expect(spyOnHandlePostsInChannel).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandlePostsInChannel).toHaveBeenCalledWith(posts.slice(0, 3));
|
||||
});
|
||||
});
|
||||
478
app/database/operator/server_data_operator/handlers/post.ts
Normal file
478
app/database/operator/server_data_operator/handlers/post.ts
Normal file
@@ -0,0 +1,478 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Q} from '@nozbe/watermelondb';
|
||||
import Model from '@nozbe/watermelondb/Model';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import DataOperatorException from '@database/exceptions/data_operator_exception';
|
||||
import {isRecordDraftEqualToRaw, isRecordPostEqualToRaw} from '@database/operator/server_data_operator/comparators';
|
||||
import {
|
||||
transformDraftRecord,
|
||||
transformFileRecord,
|
||||
transformPostInThreadRecord,
|
||||
transformPostMetadataRecord,
|
||||
transformPostRecord,
|
||||
transformPostsInChannelRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/post';
|
||||
import {getRawRecordPairs, getUniqueRawsBy, retrieveRecords} from '@database/operator/utils/general';
|
||||
import {createPostsChain, sanitizePosts} from '@database/operator/utils/post';
|
||||
import {
|
||||
HandleDraftArgs,
|
||||
HandleFilesArgs,
|
||||
HandlePostMetadataArgs,
|
||||
HandlePostsArgs,
|
||||
PostImage,
|
||||
RawCustomEmoji,
|
||||
RawEmbed,
|
||||
RawFile,
|
||||
RawPost,
|
||||
RawPostMetadata,
|
||||
RawPostsInThread,
|
||||
RawReaction, RecordPair,
|
||||
} from '@typings/database/database';
|
||||
import Draft from '@typings/database/models/servers/draft';
|
||||
import File from '@typings/database/models/servers/file';
|
||||
import Post from '@typings/database/models/servers/post';
|
||||
import PostMetadata from '@typings/database/models/servers/post_metadata';
|
||||
import PostsInChannel from '@typings/database/models/servers/posts_in_channel';
|
||||
import PostsInThread from '@typings/database/models/servers/posts_in_thread';
|
||||
import Reaction from '@typings/database/models/servers/reaction';
|
||||
|
||||
const {
|
||||
DRAFT,
|
||||
FILE,
|
||||
POST,
|
||||
POSTS_IN_CHANNEL,
|
||||
POSTS_IN_THREAD,
|
||||
POST_METADATA,
|
||||
} = MM_TABLES.SERVER;
|
||||
|
||||
export interface PostHandlerMix {
|
||||
handleDraft: ({drafts, prepareRecordsOnly}: HandleDraftArgs) => Draft[] | boolean
|
||||
handleFiles: ({files, prepareRecordsOnly}: HandleFilesArgs) => Promise<File[] | any[]>;
|
||||
handlePostMetadata: ({embeds, images, prepareRecordsOnly}: HandlePostMetadataArgs) => Promise<any[] | PostMetadata[]>;
|
||||
handlePosts: ({orders, values, previousPostId}: HandlePostsArgs) => Promise<void>;
|
||||
handlePostsInChannel: (posts: RawPost[]) => Promise<void>;
|
||||
handlePostsInThread: (rootPosts: RawPostsInThread[]) => Promise<void>;
|
||||
}
|
||||
|
||||
const PostHandler = (superclass: any) => class extends superclass {
|
||||
/**
|
||||
* handleDraft: Handler responsible for the Create/Update operations occurring the Draft table from the 'Server' schema
|
||||
* @param {HandleDraftArgs} draftsArgs
|
||||
* @param {RawDraft[]} draftsArgs.drafts
|
||||
* @param {boolean} draftsArgs.prepareRecordsOnly
|
||||
* @throws DataOperatorException
|
||||
* @returns {Draft[]}
|
||||
*/
|
||||
handleDraft = async ({drafts, prepareRecordsOnly = true}: HandleDraftArgs) => {
|
||||
let records: Draft[] = [];
|
||||
|
||||
if (!drafts.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "drafts" array has been passed to the handleDraft method',
|
||||
);
|
||||
}
|
||||
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: drafts, key: 'channel_id'});
|
||||
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'channel_id',
|
||||
findMatchingRecordBy: isRecordDraftEqualToRaw,
|
||||
transformer: transformDraftRecord,
|
||||
prepareRecordsOnly,
|
||||
createOrUpdateRawValues,
|
||||
tableName: DRAFT,
|
||||
});
|
||||
|
||||
return records;
|
||||
};
|
||||
|
||||
/**
|
||||
* handlePosts: Handler responsible for the Create/Update operations occurring on the Post table from the 'Server' schema
|
||||
* @param {HandlePostsArgs} handlePosts
|
||||
* @param {string[]} handlePosts.orders
|
||||
* @param {RawPost[]} handlePosts.values
|
||||
* @param {string | undefined} handlePosts.previousPostId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
handlePosts = async ({orders, values, previousPostId}: HandlePostsArgs) => {
|
||||
const tableName = POST;
|
||||
|
||||
// We rely on the order array; if it is empty, we stop processing
|
||||
if (!orders.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "order" array has been passed to the handlePosts method',
|
||||
);
|
||||
}
|
||||
|
||||
const rawValues = getUniqueRawsBy({
|
||||
raws: values,
|
||||
key: 'id',
|
||||
}) as RawPost[];
|
||||
|
||||
// By sanitizing the values, we are separating 'posts' that needs updating ( i.e. un-ordered posts ) from those that need to be created in our database
|
||||
const {postsOrdered, postsUnordered} = sanitizePosts({
|
||||
posts: rawValues,
|
||||
orders,
|
||||
});
|
||||
|
||||
// Here we verify in our database that the postsOrdered truly need 'CREATION'
|
||||
const futureEntries = await this.processRecords({
|
||||
createOrUpdateRawValues: postsOrdered,
|
||||
tableName,
|
||||
findMatchingRecordBy: isRecordPostEqualToRaw,
|
||||
fieldName: 'id',
|
||||
});
|
||||
|
||||
if (futureEntries.createRaws?.length) {
|
||||
let batch: Model[] = [];
|
||||
let files: RawFile[] = [];
|
||||
const postsInThread = [];
|
||||
let reactions: RawReaction[] = [];
|
||||
let emojis: RawCustomEmoji[] = [];
|
||||
const images: { images: Dictionary<PostImage>; postId: string }[] = [];
|
||||
const embeds: { embed: RawEmbed[]; postId: string }[] = [];
|
||||
|
||||
// We create the 'chain of posts' by linking each posts' previousId to the post before it in the order array
|
||||
const linkedRawPosts: RecordPair[] = createPostsChain({
|
||||
orders,
|
||||
previousPostId: previousPostId || '',
|
||||
rawPosts: postsOrdered,
|
||||
});
|
||||
|
||||
// Prepares records for batch processing onto the 'Post' table for the server schema
|
||||
const posts = (await this.prepareRecords({
|
||||
createRaws: linkedRawPosts,
|
||||
transformer: transformPostRecord,
|
||||
tableName,
|
||||
})) as Post[];
|
||||
|
||||
// Appends the processed records into the final batch array
|
||||
batch = batch.concat(posts);
|
||||
|
||||
// Starts extracting information from each post to build up for related tables' data
|
||||
for (const post of postsOrdered) {
|
||||
// PostInThread handler: checks for id === root_id , if so, then call PostsInThread operator
|
||||
if (!post.root_id) {
|
||||
postsInThread.push({
|
||||
earliest: post.create_at,
|
||||
post_id: post.id,
|
||||
});
|
||||
}
|
||||
|
||||
if (post?.metadata && Object.keys(post?.metadata).length > 0) {
|
||||
const metadata = post.metadata;
|
||||
|
||||
// Extracts reaction from post's metadata
|
||||
reactions = reactions.concat(metadata?.reactions ?? []);
|
||||
|
||||
// Extracts emojis from post's metadata
|
||||
emojis = emojis.concat(metadata?.emojis ?? []);
|
||||
|
||||
// Extracts files from post's metadata
|
||||
files = files.concat(metadata?.files ?? []);
|
||||
|
||||
// Extracts images and embeds from post's metadata
|
||||
if (metadata?.images) {
|
||||
images.push({images: metadata.images, postId: post.id});
|
||||
}
|
||||
|
||||
if (metadata?.embeds) {
|
||||
embeds.push({embed: metadata.embeds, postId: post.id});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (reactions.length) {
|
||||
// calls handler for Reactions
|
||||
const postReactions = (await this.handleReactions({reactions, prepareRecordsOnly: true})) as Reaction[];
|
||||
batch = batch.concat(postReactions);
|
||||
}
|
||||
|
||||
if (files.length) {
|
||||
// calls handler for Files
|
||||
const postFiles = await this.handleFiles({files, prepareRecordsOnly: true});
|
||||
batch = batch.concat(postFiles);
|
||||
}
|
||||
|
||||
if (images.length || embeds.length) {
|
||||
// calls handler for postMetadata ( embeds and images )
|
||||
const postMetadata = await this.handlePostMetadata({
|
||||
images,
|
||||
embeds,
|
||||
prepareRecordsOnly: true,
|
||||
});
|
||||
|
||||
batch = batch.concat(postMetadata);
|
||||
}
|
||||
|
||||
if (batch.length) {
|
||||
await this.batchRecords(batch);
|
||||
}
|
||||
|
||||
// LAST: calls handler for CustomEmojis, PostsInThread, PostsInChannel
|
||||
if (emojis.length) {
|
||||
await this.handleCustomEmojis({
|
||||
emojis,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
}
|
||||
|
||||
if (postsInThread.length) {
|
||||
await this.handlePostsInThread(postsInThread);
|
||||
}
|
||||
|
||||
if (postsOrdered.length) {
|
||||
await this.handlePostsInChannel(postsOrdered);
|
||||
}
|
||||
}
|
||||
|
||||
if (postsUnordered.length) {
|
||||
// Truly update those posts that have a different update_at value
|
||||
await this.handleRecords({
|
||||
findMatchingRecordBy: isRecordPostEqualToRaw,
|
||||
fieldName: 'id',
|
||||
trasformer: transformPostRecord,
|
||||
createOrUpdateRawValues: postsUnordered,
|
||||
tableName: POST,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* handleFiles: Handler responsible for the Create/Update operations occurring on the File table from the 'Server' schema
|
||||
* @param {HandleFilesArgs} handleFiles
|
||||
* @param {RawFile[]} handleFiles.files
|
||||
* @param {boolean} handleFiles.prepareRecordsOnly
|
||||
* @returns {Promise<File[] | any[]>}
|
||||
*/
|
||||
handleFiles = async ({files, prepareRecordsOnly}: HandleFilesArgs) => {
|
||||
if (!files.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const postFiles = await this.prepareRecords({
|
||||
createRaws: getRawRecordPairs(files),
|
||||
transformer: transformFileRecord,
|
||||
tableName: FILE,
|
||||
});
|
||||
|
||||
if (prepareRecordsOnly) {
|
||||
return postFiles;
|
||||
}
|
||||
|
||||
if (postFiles?.length) {
|
||||
await this.batchRecords(postFiles);
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
/**
|
||||
* handlePostMetadata: Handler responsible for the Create/Update operations occurring on the PostMetadata table from the 'Server' schema
|
||||
* @param {HandlePostMetadataArgs} handlePostMetadata
|
||||
* @param {{embed: RawEmbed[], postId: string}[] | undefined} handlePostMetadata.embeds
|
||||
* @param {{images: Dictionary<PostImage>, postId: string}[] | undefined} handlePostMetadata.images
|
||||
* @param {boolean} handlePostMetadata.prepareRecordsOnly
|
||||
* @returns {Promise<any[] | PostMetadata[]>}
|
||||
*/
|
||||
handlePostMetadata = async ({embeds, images, prepareRecordsOnly}: HandlePostMetadataArgs) => {
|
||||
const metadata: RawPostMetadata[] = [];
|
||||
|
||||
if (images?.length) {
|
||||
images.forEach((image) => {
|
||||
const imageEntry = Object.entries(image.images);
|
||||
metadata.push({
|
||||
data: {...imageEntry?.[0]?.[1], url: imageEntry?.[0]?.[0]},
|
||||
type: 'images',
|
||||
postId: image.postId,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (embeds?.length) {
|
||||
embeds.forEach((postEmbed) => {
|
||||
postEmbed.embed.forEach((embed: RawEmbed) => {
|
||||
metadata.push({
|
||||
data: {...embed.data},
|
||||
type: embed.type,
|
||||
postId: postEmbed.postId,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (!metadata.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const postMetas = await this.prepareRecords({
|
||||
createRaws: getRawRecordPairs(metadata),
|
||||
transformer: transformPostMetadataRecord,
|
||||
tableName: POST_METADATA,
|
||||
});
|
||||
|
||||
if (prepareRecordsOnly) {
|
||||
return postMetas;
|
||||
}
|
||||
|
||||
if (postMetas?.length) {
|
||||
await this.batchRecords(postMetas);
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
/**
|
||||
* handlePostsInThread: Handler responsible for the Create/Update operations occurring on the PostsInThread table from the 'Server' schema
|
||||
* @param {RawPostsInThread[]} rootPosts
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
handlePostsInThread = async (rootPosts: RawPostsInThread[]) => {
|
||||
if (!rootPosts.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const postIds = rootPosts.map((postThread) => postThread.post_id);
|
||||
const rawPostsInThreads: RawPostsInThread[] = [];
|
||||
|
||||
// Retrieves all threads whereby their root_id can be one of the element in the postIds array
|
||||
const threads = (await this.database.collections.
|
||||
get(POST).
|
||||
query(Q.where('root_id', Q.oneOf(postIds))).
|
||||
fetch()) as Post[];
|
||||
|
||||
// The aim here is to find the last reply in that thread; hence the latest create_at value
|
||||
rootPosts.forEach((rootPost) => {
|
||||
const maxCreateAt: number = threads.reduce((max: number, thread: Post) => {
|
||||
return thread.createAt > max ? thread.createAt : maxCreateAt;
|
||||
}, 0);
|
||||
|
||||
// Collects all 'raw' postInThreads objects that will be sent to the operatePostsInThread function
|
||||
rawPostsInThreads.push({...rootPost, latest: maxCreateAt});
|
||||
});
|
||||
|
||||
if (rawPostsInThreads.length) {
|
||||
const postInThreadRecords = (await this.prepareRecords({
|
||||
createRaws: getRawRecordPairs(rawPostsInThreads),
|
||||
transformer: transformPostInThreadRecord,
|
||||
tableName: POSTS_IN_THREAD,
|
||||
})) as PostsInThread[];
|
||||
|
||||
if (postInThreadRecords?.length) {
|
||||
await this.batchRecords(postInThreadRecords);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* handlePostsInChannel: Handler responsible for the Create/Update operations occurring on the PostsInChannel table from the 'Server' schema
|
||||
* @param {RawPost[]} posts
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
handlePostsInChannel = async (posts: RawPost[]) => {
|
||||
// At this point, the parameter 'posts' is already a chain of posts. Now, we have to figure out how to plug it
|
||||
// into existing chains in the PostsInChannel table
|
||||
|
||||
if (!posts.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Sort a clone of 'posts' array by create_at
|
||||
const sortedPosts = [...posts].sort((a, b) => {
|
||||
return a.create_at - b.create_at;
|
||||
});
|
||||
|
||||
// The first element (beginning of chain)
|
||||
const tipOfChain: RawPost = sortedPosts[0];
|
||||
|
||||
// Channel Id for this chain of posts
|
||||
const channelId = tipOfChain.channel_id;
|
||||
|
||||
// Find smallest 'create_at' value in chain
|
||||
const earliest = tipOfChain.create_at;
|
||||
|
||||
// Find highest 'create_at' value in chain; -1 means we are dealing with one item in the posts array
|
||||
const latest = sortedPosts[sortedPosts.length - 1].create_at;
|
||||
|
||||
// Find the records in the PostsInChannel table that have a matching channel_id
|
||||
// const chunks = (await database.collections.get(POSTS_IN_CHANNEL).query(Q.where('channel_id', channelId)).fetch()) as PostsInChannel[];
|
||||
const chunks = (await retrieveRecords({
|
||||
database: this.database,
|
||||
tableName: POSTS_IN_CHANNEL,
|
||||
condition: Q.where('channel_id', channelId),
|
||||
})) as PostsInChannel[];
|
||||
|
||||
const createPostsInChannelRecord = async () => {
|
||||
await this.execute({
|
||||
createRaws: [{record: undefined, raw: {channel_id: channelId, earliest, latest}}],
|
||||
tableName: POSTS_IN_CHANNEL,
|
||||
transformer: transformPostsInChannelRecord,
|
||||
});
|
||||
};
|
||||
|
||||
// chunk length 0; then it's a new chunk to be added to the PostsInChannel table
|
||||
if (chunks.length === 0) {
|
||||
await createPostsInChannelRecord();
|
||||
return [];
|
||||
}
|
||||
|
||||
// Sort chunks (in-place) by earliest field ( oldest to newest )
|
||||
chunks.sort((a, b) => {
|
||||
return a.earliest - b.earliest;
|
||||
});
|
||||
|
||||
let found = false;
|
||||
let targetChunk: PostsInChannel;
|
||||
|
||||
for (const chunk of chunks) {
|
||||
// find if we should plug the chain before
|
||||
if (earliest < chunk.earliest) {
|
||||
found = true;
|
||||
targetChunk = chunk;
|
||||
}
|
||||
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
// We have a potential chunk to plug nearby
|
||||
const potentialPosts = (await retrieveRecords({
|
||||
database: this.database,
|
||||
tableName: POST,
|
||||
condition: Q.where('create_at', earliest),
|
||||
})) as Post[];
|
||||
|
||||
if (potentialPosts?.length > 0) {
|
||||
const targetPost = potentialPosts[0];
|
||||
|
||||
// now we decide if we need to operate on the targetChunk or just create a new chunk
|
||||
const isChainable = tipOfChain.prev_post_id === targetPost.previousPostId;
|
||||
|
||||
if (isChainable) {
|
||||
// Update this chunk's data in PostsInChannel table. earliest comes from tipOfChain while latest comes from chunk
|
||||
await this.database.action(async () => {
|
||||
await targetChunk.update((postInChannel) => {
|
||||
postInChannel.earliest = earliest;
|
||||
});
|
||||
});
|
||||
} else {
|
||||
await createPostsInChannelRecord();
|
||||
return [];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await createPostsInChannelRecord();
|
||||
return [];
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
};
|
||||
|
||||
export default PostHandler;
|
||||
231
app/database/operator/server_data_operator/handlers/team.test.ts
Normal file
231
app/database/operator/server_data_operator/handlers/team.test.ts
Normal file
@@ -0,0 +1,231 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {
|
||||
isRecordMyTeamEqualToRaw,
|
||||
isRecordSlashCommandEqualToRaw,
|
||||
isRecordTeamChannelHistoryEqualToRaw,
|
||||
isRecordTeamEqualToRaw,
|
||||
isRecordTeamMembershipEqualToRaw,
|
||||
isRecordTeamSearchHistoryEqualToRaw,
|
||||
} from '@database/operator/server_data_operator/comparators';
|
||||
import {
|
||||
transformMyTeamRecord,
|
||||
transformSlashCommandRecord,
|
||||
transformTeamChannelHistoryRecord,
|
||||
transformTeamMembershipRecord,
|
||||
transformTeamRecord,
|
||||
transformTeamSearchHistoryRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/team';
|
||||
|
||||
import ServerDataOperator from '..';
|
||||
|
||||
describe('*** Operator: Team Handlers tests ***', () => {
|
||||
let operator: ServerDataOperator;
|
||||
beforeAll(async () => {
|
||||
await DatabaseManager.init(['baseHandler.test.com']);
|
||||
operator = DatabaseManager.serverDatabases['baseHandler.test.com'].operator;
|
||||
});
|
||||
|
||||
it('=> HandleTeam: should write to the TEAM table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const teams = [
|
||||
{
|
||||
id: 'rcgiyftm7jyrxnmdfdfa1osd8zswby',
|
||||
create_at: 1445538153952,
|
||||
update_at: 1588876392150,
|
||||
delete_at: 0,
|
||||
display_name: 'Contributors',
|
||||
name: 'core',
|
||||
description: '',
|
||||
email: '',
|
||||
type: 'O',
|
||||
company_name: '',
|
||||
allowed_domains: '',
|
||||
invite_id: 'codoy5s743rq5mk18i7u5dfdfksz7e',
|
||||
allow_open_invite: true,
|
||||
last_team_icon_update: 1525181587639,
|
||||
scheme_id: 'hbwgrncq1pfcdkpotzidfdmarn95o',
|
||||
group_constrained: null,
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleTeam({
|
||||
teams,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'id',
|
||||
createOrUpdateRawValues: teams,
|
||||
tableName: 'Team',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordTeamEqualToRaw,
|
||||
transformer: transformTeamRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleTeamMemberships: should write to the TEAM_MEMBERSHIP table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const teamMemberships = [
|
||||
{
|
||||
team_id: 'a',
|
||||
user_id: 'ab',
|
||||
roles: '3ngdqe1e7tfcbmam4qgnxp91bw',
|
||||
delete_at: 0,
|
||||
scheme_guest: false,
|
||||
scheme_user: true,
|
||||
scheme_admin: false,
|
||||
explicit_roles: '',
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleTeamMemberships({
|
||||
teamMemberships,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'user_id',
|
||||
createOrUpdateRawValues: teamMemberships,
|
||||
tableName: 'TeamMembership',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordTeamMembershipEqualToRaw,
|
||||
transformer: transformTeamMembershipRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleMyTeam: should write to the MY_TEAM table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const myTeams = [
|
||||
{
|
||||
team_id: 'teamA',
|
||||
roles: 'roleA, roleB, roleC',
|
||||
is_unread: true,
|
||||
mentions_count: 3,
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleMyTeam({
|
||||
myTeams,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'team_id',
|
||||
createOrUpdateRawValues: myTeams,
|
||||
tableName: 'MyTeam',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordMyTeamEqualToRaw,
|
||||
transformer: transformMyTeamRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleTeamChannelHistory: should write to the TEAM_CHANNEL_HISTORY table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const teamChannelHistories = [
|
||||
{
|
||||
team_id: 'a',
|
||||
channel_ids: ['ca', 'cb'],
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleTeamChannelHistory({
|
||||
teamChannelHistories,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'team_id',
|
||||
createOrUpdateRawValues: teamChannelHistories,
|
||||
tableName: 'TeamChannelHistory',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordTeamChannelHistoryEqualToRaw,
|
||||
transformer: transformTeamChannelHistoryRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleTeamSearchHistory: should write to the TEAM_SEARCH_HISTORY table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const teamSearchHistories = [
|
||||
{
|
||||
team_id: 'a',
|
||||
term: 'termA',
|
||||
display_term: 'termA',
|
||||
created_at: 1445538153952,
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleTeamSearchHistory({
|
||||
teamSearchHistories,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'team_id',
|
||||
createOrUpdateRawValues: teamSearchHistories,
|
||||
tableName: 'TeamSearchHistory',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordTeamSearchHistoryEqualToRaw,
|
||||
transformer: transformTeamSearchHistoryRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleSlashCommand: should write to the SLASH_COMMAND table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const slashCommands = [
|
||||
{
|
||||
id: 'command_1',
|
||||
auto_complete: true,
|
||||
auto_complete_desc: 'mock_command',
|
||||
auto_complete_hint: 'hint',
|
||||
create_at: 1445538153952,
|
||||
creator_id: 'creator_id',
|
||||
delete_at: 1445538153952,
|
||||
description: 'description',
|
||||
display_name: 'display_name',
|
||||
icon_url: 'display_name',
|
||||
method: 'get',
|
||||
team_id: 'teamA',
|
||||
token: 'token',
|
||||
trigger: 'trigger',
|
||||
update_at: 1445538153953,
|
||||
url: 'url',
|
||||
username: 'userA',
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleSlashCommand({
|
||||
slashCommands,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'id',
|
||||
createOrUpdateRawValues: slashCommands,
|
||||
tableName: 'SlashCommand',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordSlashCommandEqualToRaw,
|
||||
transformer: transformSlashCommandRecord,
|
||||
});
|
||||
});
|
||||
});
|
||||
243
app/database/operator/server_data_operator/handlers/team.ts
Normal file
243
app/database/operator/server_data_operator/handlers/team.ts
Normal file
@@ -0,0 +1,243 @@
|
||||
// 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 {
|
||||
isRecordMyTeamEqualToRaw,
|
||||
isRecordSlashCommandEqualToRaw,
|
||||
isRecordTeamChannelHistoryEqualToRaw,
|
||||
isRecordTeamEqualToRaw,
|
||||
isRecordTeamMembershipEqualToRaw,
|
||||
isRecordTeamSearchHistoryEqualToRaw,
|
||||
} from '@database/operator/server_data_operator/comparators';
|
||||
import {
|
||||
transformMyTeamRecord,
|
||||
transformSlashCommandRecord,
|
||||
transformTeamChannelHistoryRecord,
|
||||
transformTeamMembershipRecord,
|
||||
transformTeamRecord,
|
||||
transformTeamSearchHistoryRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/team';
|
||||
import {getUniqueRawsBy} from '@database/operator/utils/general';
|
||||
import {
|
||||
HandleMyTeamArgs,
|
||||
HandleSlashCommandArgs,
|
||||
HandleTeamArgs,
|
||||
HandleTeamChannelHistoryArgs,
|
||||
HandleTeamMembershipArgs,
|
||||
HandleTeamSearchHistoryArgs,
|
||||
} from '@typings/database/database';
|
||||
import MyTeam from '@typings/database/models/servers/my_team';
|
||||
import SlashCommand from '@typings/database/models/servers/slash_command';
|
||||
import Team from '@typings/database/models/servers/team';
|
||||
import TeamChannelHistory from '@typings/database/models/servers/team_channel_history';
|
||||
import TeamMembership from '@typings/database/models/servers/team_membership';
|
||||
import TeamSearchHistory from '@typings/database/models/servers/team_search_history';
|
||||
|
||||
const {
|
||||
MY_TEAM,
|
||||
SLASH_COMMAND,
|
||||
TEAM,
|
||||
TEAM_CHANNEL_HISTORY,
|
||||
TEAM_MEMBERSHIP,
|
||||
TEAM_SEARCH_HISTORY,
|
||||
} = MM_TABLES.SERVER;
|
||||
|
||||
export interface TeamHandlerMix {
|
||||
handleTeamMemberships: ({teamMemberships, prepareRecordsOnly}: HandleTeamMembershipArgs) => TeamMembership[];
|
||||
handleTeam: ({teams, prepareRecordsOnly}: HandleTeamArgs) => Team[];
|
||||
handleTeamChannelHistory: ({teamChannelHistories, prepareRecordsOnly}: HandleTeamChannelHistoryArgs) => TeamChannelHistory[];
|
||||
handleSlashCommand: ({slashCommands, prepareRecordsOnly}: HandleSlashCommandArgs) => SlashCommand[];
|
||||
handleMyTeam: ({myTeams, prepareRecordsOnly}: HandleMyTeamArgs) => MyTeam[];
|
||||
}
|
||||
|
||||
const TeamHandler = (superclass: any) => class extends superclass {
|
||||
/**
|
||||
* handleTeamMemberships: Handler responsible for the Create/Update operations occurring on the TEAM_MEMBERSHIP table from the 'Server' schema
|
||||
* @param {HandleTeamMembershipArgs} teamMembershipsArgs
|
||||
* @param {RawTeamMembership[]} teamMembershipsArgs.teamMemberships
|
||||
* @param {boolean} teamMembershipsArgs.prepareRecordsOnly
|
||||
* @throws DataOperatorException
|
||||
* @returns {TeamMembership[]}
|
||||
*/
|
||||
handleTeamMemberships = async ({teamMemberships, prepareRecordsOnly = true}: HandleTeamMembershipArgs) => {
|
||||
let records: TeamMembership[] = [];
|
||||
|
||||
if (!teamMemberships.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "teamMemberships" array has been passed to the handleTeamMemberships method',
|
||||
);
|
||||
}
|
||||
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: teamMemberships, key: 'team_id'});
|
||||
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'user_id',
|
||||
findMatchingRecordBy: isRecordTeamMembershipEqualToRaw,
|
||||
transformer: transformTeamMembershipRecord,
|
||||
createOrUpdateRawValues,
|
||||
tableName: TEAM_MEMBERSHIP,
|
||||
prepareRecordsOnly,
|
||||
});
|
||||
|
||||
return records;
|
||||
};
|
||||
|
||||
/**
|
||||
* handleTeam: Handler responsible for the Create/Update operations occurring on the TEAM table from the 'Server' schema
|
||||
* @param {HandleTeamArgs} teamsArgs
|
||||
* @param {RawTeam[]} teamsArgs.teams
|
||||
* @param {boolean} teamsArgs.prepareRecordsOnly
|
||||
* @throws DataOperatorException
|
||||
* @returns {Team[]}
|
||||
*/
|
||||
handleTeam = async ({teams, prepareRecordsOnly = true}: HandleTeamArgs) => {
|
||||
let records: Team[] = [];
|
||||
|
||||
if (!teams.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "teams" array has been passed to the handleTeam method',
|
||||
);
|
||||
}
|
||||
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: teams, key: 'id'});
|
||||
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'id',
|
||||
findMatchingRecordBy: isRecordTeamEqualToRaw,
|
||||
transformer: transformTeamRecord,
|
||||
prepareRecordsOnly,
|
||||
createOrUpdateRawValues,
|
||||
tableName: TEAM,
|
||||
});
|
||||
|
||||
return records;
|
||||
};
|
||||
|
||||
/**
|
||||
* handleTeamChannelHistory: Handler responsible for the Create/Update operations occurring on the TEAM_CHANNEL_HISTORY table from the 'Server' schema
|
||||
* @param {HandleTeamChannelHistoryArgs} teamChannelHistoriesArgs
|
||||
* @param {RawTeamChannelHistory[]} teamChannelHistoriesArgs.teamChannelHistories
|
||||
* @param {boolean} teamChannelHistoriesArgs.prepareRecordsOnly
|
||||
* @throws DataOperatorException
|
||||
* @returns {TeamChannelHistory[]}
|
||||
*/
|
||||
handleTeamChannelHistory = async ({teamChannelHistories, prepareRecordsOnly = true}: HandleTeamChannelHistoryArgs) => {
|
||||
let records: TeamChannelHistory[] = [];
|
||||
|
||||
if (!teamChannelHistories.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "teamChannelHistories" array has been passed to the handleTeamChannelHistory method',
|
||||
);
|
||||
}
|
||||
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: teamChannelHistories, key: 'team_id'});
|
||||
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'team_id',
|
||||
findMatchingRecordBy: isRecordTeamChannelHistoryEqualToRaw,
|
||||
transformer: transformTeamChannelHistoryRecord,
|
||||
prepareRecordsOnly,
|
||||
createOrUpdateRawValues,
|
||||
tableName: TEAM_CHANNEL_HISTORY,
|
||||
});
|
||||
|
||||
return records;
|
||||
};
|
||||
|
||||
/**
|
||||
* handleTeamSearchHistory: Handler responsible for the Create/Update operations occurring on the TEAM_SEARCH_HISTORY table from the 'Server' schema
|
||||
* @param {HandleTeamSearchHistoryArgs} teamSearchHistoriesArgs
|
||||
* @param {RawTeamSearchHistory[]} teamSearchHistoriesArgs.teamSearchHistories
|
||||
* @param {boolean} teamSearchHistoriesArgs.prepareRecordsOnly
|
||||
* @throws DataOperatorException
|
||||
* @returns {TeamSearchHistory[]}
|
||||
*/
|
||||
handleTeamSearchHistory = async ({teamSearchHistories, prepareRecordsOnly = true}: HandleTeamSearchHistoryArgs) => {
|
||||
let records: TeamSearchHistory[] = [];
|
||||
|
||||
if (!teamSearchHistories.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "teamSearchHistories" array has been passed to the handleTeamSearchHistory method',
|
||||
);
|
||||
}
|
||||
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: teamSearchHistories, key: 'term'});
|
||||
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'team_id',
|
||||
findMatchingRecordBy: isRecordTeamSearchHistoryEqualToRaw,
|
||||
transformer: transformTeamSearchHistoryRecord,
|
||||
prepareRecordsOnly,
|
||||
createOrUpdateRawValues,
|
||||
tableName: TEAM_SEARCH_HISTORY,
|
||||
});
|
||||
|
||||
return records;
|
||||
};
|
||||
|
||||
/**
|
||||
* handleSlashCommand: Handler responsible for the Create/Update operations occurring on the SLASH_COMMAND table from the 'Server' schema
|
||||
* @param {HandleSlashCommandArgs} slashCommandsArgs
|
||||
* @param {RawSlashCommand[]} slashCommandsArgs.slashCommands
|
||||
* @param {boolean} slashCommandsArgs.prepareRecordsOnly
|
||||
* @throws DataOperatorException
|
||||
* @returns {SlashCommand[]}
|
||||
*/
|
||||
handleSlashCommand = async ({slashCommands, prepareRecordsOnly = true}: HandleSlashCommandArgs) => {
|
||||
let records: SlashCommand[] = [];
|
||||
|
||||
if (!slashCommands.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "slashCommands" array has been passed to the handleSlashCommand method',
|
||||
);
|
||||
}
|
||||
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: slashCommands, key: 'id'});
|
||||
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'id',
|
||||
findMatchingRecordBy: isRecordSlashCommandEqualToRaw,
|
||||
transformer: transformSlashCommandRecord,
|
||||
prepareRecordsOnly,
|
||||
createOrUpdateRawValues,
|
||||
tableName: SLASH_COMMAND,
|
||||
});
|
||||
|
||||
return records;
|
||||
};
|
||||
|
||||
/**
|
||||
* handleMyTeam: Handler responsible for the Create/Update operations occurring on the MY_TEAM table from the 'Server' schema
|
||||
* @param {HandleMyTeamArgs} myTeamsArgs
|
||||
* @param {RawMyTeam[]} myTeamsArgs.myTeams
|
||||
* @param {boolean} myTeamsArgs.prepareRecordsOnly
|
||||
* @throws DataOperatorException
|
||||
* @returns {MyTeam[]}
|
||||
*/
|
||||
handleMyTeam = async ({myTeams, prepareRecordsOnly = true}: HandleMyTeamArgs) => {
|
||||
let records: MyTeam[] = [];
|
||||
|
||||
if (!myTeams.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "myTeams" array has been passed to the handleSlashCommand method',
|
||||
);
|
||||
}
|
||||
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: myTeams, key: 'team_id'});
|
||||
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'team_id',
|
||||
findMatchingRecordBy: isRecordMyTeamEqualToRaw,
|
||||
transformer: transformMyTeamRecord,
|
||||
prepareRecordsOnly,
|
||||
createOrUpdateRawValues,
|
||||
tableName: MY_TEAM,
|
||||
});
|
||||
|
||||
return records;
|
||||
};
|
||||
};
|
||||
|
||||
export default TeamHandler;
|
||||
222
app/database/operator/server_data_operator/handlers/user.test.ts
Normal file
222
app/database/operator/server_data_operator/handlers/user.test.ts
Normal file
@@ -0,0 +1,222 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import DatabaseManager from '@database/manager';
|
||||
import ServerDataOperator from '@database/operator/server_data_operator';
|
||||
import {
|
||||
isRecordChannelMembershipEqualToRaw,
|
||||
isRecordPreferenceEqualToRaw,
|
||||
isRecordUserEqualToRaw,
|
||||
} from '@database/operator/server_data_operator/comparators';
|
||||
import {
|
||||
transformChannelMembershipRecord,
|
||||
transformPreferenceRecord,
|
||||
transformUserRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/user';
|
||||
|
||||
describe('*** Operator: User Handlers tests ***', () => {
|
||||
let operator: ServerDataOperator;
|
||||
|
||||
beforeAll(async () => {
|
||||
await DatabaseManager.init(['baseHandler.test.com']);
|
||||
operator = DatabaseManager.serverDatabases['baseHandler.test.com'].operator;
|
||||
});
|
||||
|
||||
it('=> HandleReactions: should write to both Reactions and CustomEmoji tables', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnPrepareRecords = jest.spyOn(operator, 'prepareRecords');
|
||||
const spyOnBatchOperation = jest.spyOn(operator, 'batchRecords');
|
||||
|
||||
await operator.handleReactions({
|
||||
reactions: [
|
||||
{
|
||||
create_at: 1608263728086,
|
||||
delete_at: 0,
|
||||
emoji_name: 'p4p1',
|
||||
post_id: '4r9jmr7eqt8dxq3f9woypzurry',
|
||||
update_at: 1608263728077,
|
||||
user_id: 'ooumoqgq3bfiijzwbn8badznwc',
|
||||
},
|
||||
],
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
// Called twice: Once for Reaction record and once for CustomEmoji record
|
||||
expect(spyOnPrepareRecords).toHaveBeenCalledTimes(2);
|
||||
|
||||
// Only one batch operation for both tables
|
||||
expect(spyOnBatchOperation).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('=> HandleUsers: should write to the User table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const users = [
|
||||
{
|
||||
id: '9ciscaqbrpd6d8s68k76xb9bte',
|
||||
create_at: 1599457495881,
|
||||
update_at: 1607683720173,
|
||||
delete_at: 0,
|
||||
username: 'a.l',
|
||||
auth_service: 'saml',
|
||||
email: 'a.l@mattermost.com',
|
||||
email_verified: true,
|
||||
is_bot: false,
|
||||
nickname: '',
|
||||
first_name: 'A',
|
||||
last_name: 'L',
|
||||
position: 'Mobile Engineer',
|
||||
roles: 'system_user',
|
||||
props: {},
|
||||
notify_props: {
|
||||
desktop: 'all',
|
||||
desktop_sound: true,
|
||||
email: true,
|
||||
first_name: true,
|
||||
mention_keys: '',
|
||||
push: 'mention',
|
||||
channel: true,
|
||||
auto_responder_active: false,
|
||||
auto_responder_message: 'Hello, I am out of office and unable to respond to messages.',
|
||||
comments: 'never',
|
||||
desktop_notification_sound: 'Hello',
|
||||
push_status: 'online',
|
||||
},
|
||||
last_password_update: 1604323112537,
|
||||
last_picture_update: 1604686302260,
|
||||
locale: 'en',
|
||||
timezone: {
|
||||
automaticTimezone: 'Indian/Mauritius',
|
||||
manualTimezone: '',
|
||||
useAutomaticTimezone: '',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
|
||||
await operator.handleUsers({users, prepareRecordsOnly: false});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'id',
|
||||
createOrUpdateRawValues: users,
|
||||
tableName: 'User',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordUserEqualToRaw,
|
||||
transformer: transformUserRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandlePreferences: should write to the PREFERENCE table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const preferences = [
|
||||
{
|
||||
user_id: '9ciscaqbrpd6d8s68k76xb9bte',
|
||||
category: 'group_channel_show',
|
||||
name: 'qj91hepgjfn6xr4acm5xzd8zoc',
|
||||
value: 'true',
|
||||
},
|
||||
{
|
||||
user_id: '9ciscaqbrpd6d8s68k76xb9bte',
|
||||
category: 'notifications',
|
||||
name: 'email_interval',
|
||||
value: '30',
|
||||
},
|
||||
{
|
||||
user_id: '9ciscaqbrpd6d8s68k76xb9bte',
|
||||
category: 'theme',
|
||||
name: '',
|
||||
value:
|
||||
'{"awayIndicator":"#c1b966","buttonBg":"#4cbba4","buttonColor":"#ffffff","centerChannelBg":"#2f3e4e","centerChannelColor":"#dddddd","codeTheme":"solarized-dark","dndIndicator":"#e81023","errorTextColor":"#ff6461","image":"/static/files/0b8d56c39baf992e5e4c58d74fde0fd6.png","linkColor":"#a4ffeb","mentionBg":"#b74a4a","mentionColor":"#ffffff","mentionHighlightBg":"#984063","mentionHighlightLink":"#a4ffeb","newMessageSeparator":"#5de5da","onlineIndicator":"#65dcc8","sidebarBg":"#1b2c3e","sidebarHeaderBg":"#1b2c3e","sidebarHeaderTextColor":"#ffffff","sidebarText":"#ffffff","sidebarTextActiveBorder":"#66b9a7","sidebarTextActiveColor":"#ffffff","sidebarTextHoverBg":"#4a5664","sidebarUnreadText":"#ffffff","type":"Mattermost Dark"}',
|
||||
},
|
||||
{
|
||||
user_id: '9ciscaqbrpd6d8s68k76xb9bte',
|
||||
category: 'tutorial_step',
|
||||
name: '9ciscaqbrpd6d8s68k76xb9bte',
|
||||
value: '2',
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handlePreferences({
|
||||
preferences,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'user_id',
|
||||
createOrUpdateRawValues: preferences,
|
||||
tableName: 'Preference',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordPreferenceEqualToRaw,
|
||||
transformer: transformPreferenceRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> HandleChannelMembership: should write to the CHANNEL_MEMBERSHIP table', async () => {
|
||||
expect.assertions(2);
|
||||
const channelMemberships = [
|
||||
{
|
||||
channel_id: '17bfnb1uwb8epewp4q3x3rx9go',
|
||||
user_id: '9ciscaqbrpd6d8s68k76xb9bte',
|
||||
roles: 'wqyby5r5pinxxdqhoaomtacdhc',
|
||||
last_viewed_at: 1613667352029,
|
||||
msg_count: 3864,
|
||||
mention_count: 0,
|
||||
notify_props: {
|
||||
desktop: 'default',
|
||||
email: 'default',
|
||||
ignore_channel_mentions: 'default',
|
||||
mark_unread: 'mention',
|
||||
push: 'default',
|
||||
},
|
||||
last_update_at: 1613667352029,
|
||||
scheme_guest: false,
|
||||
scheme_user: true,
|
||||
scheme_admin: false,
|
||||
explicit_roles: '',
|
||||
},
|
||||
{
|
||||
channel_id: '1yw6gxfr4bn1jbyp9nr7d53yew',
|
||||
user_id: '9ciscaqbrpd6d8s68k76xb9bte',
|
||||
roles: 'channel_user',
|
||||
last_viewed_at: 1615300540549,
|
||||
msg_count: 16,
|
||||
mention_count: 0,
|
||||
notify_props: {
|
||||
desktop: 'default',
|
||||
email: 'default',
|
||||
ignore_channel_mentions: 'default',
|
||||
mark_unread: 'all',
|
||||
push: 'default',
|
||||
},
|
||||
last_update_at: 1615300540549,
|
||||
scheme_guest: false,
|
||||
scheme_user: true,
|
||||
scheme_admin: false,
|
||||
explicit_roles: '',
|
||||
},
|
||||
];
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
|
||||
await operator.handleChannelMembership({
|
||||
channelMemberships,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'user_id',
|
||||
createOrUpdateRawValues: channelMemberships,
|
||||
tableName: 'ChannelMembership',
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordChannelMembershipEqualToRaw,
|
||||
transformer: transformChannelMembershipRecord,
|
||||
});
|
||||
});
|
||||
});
|
||||
205
app/database/operator/server_data_operator/handlers/user.ts
Normal file
205
app/database/operator/server_data_operator/handlers/user.ts
Normal file
@@ -0,0 +1,205 @@
|
||||
// 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 {
|
||||
isRecordChannelMembershipEqualToRaw,
|
||||
isRecordPreferenceEqualToRaw,
|
||||
isRecordUserEqualToRaw,
|
||||
} from '@database/operator/server_data_operator/comparators';
|
||||
import {transformCustomEmojiRecord} from '@database/operator/server_data_operator/transformers/general';
|
||||
import {
|
||||
transformChannelMembershipRecord,
|
||||
transformPreferenceRecord,
|
||||
transformReactionRecord,
|
||||
transformUserRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/user';
|
||||
import {getRawRecordPairs, getUniqueRawsBy} from '@database/operator/utils/general';
|
||||
import {sanitizeReactions} from '@database/operator/utils/reaction';
|
||||
import ChannelMembership from '@typings/database/models/servers/channel_membership';
|
||||
import CustomEmoji from '@typings/database/models/servers/custom_emoji';
|
||||
import {
|
||||
HandleChannelMembershipArgs,
|
||||
HandlePreferencesArgs,
|
||||
HandleReactionsArgs,
|
||||
HandleUsersArgs,
|
||||
RawReaction,
|
||||
} from '@typings/database/database';
|
||||
import Preference from '@typings/database/models/servers/preference';
|
||||
import Reaction from '@typings/database/models/servers/reaction';
|
||||
import User from '@typings/database/models/servers/user';
|
||||
|
||||
const {
|
||||
CHANNEL_MEMBERSHIP,
|
||||
CUSTOM_EMOJI,
|
||||
PREFERENCE,
|
||||
REACTION,
|
||||
USER,
|
||||
} = MM_TABLES.SERVER;
|
||||
|
||||
export interface UserHandlerMix {
|
||||
handleChannelMembership : ({channelMemberships, prepareRecordsOnly}: HandleChannelMembershipArgs) => Promise<ChannelMembership[]>;
|
||||
handlePreferences : ({preferences, prepareRecordsOnly}: HandlePreferencesArgs) => Promise<Preference[]>;
|
||||
handleReactions : ({reactions, prepareRecordsOnly}: HandleReactionsArgs) => Promise<(Reaction | CustomEmoji)[]>;
|
||||
handleUsers : ({users, prepareRecordsOnly}: HandleUsersArgs) => Promise<User[]>;
|
||||
}
|
||||
|
||||
const UserHandler = (superclass: any) => class extends superclass {
|
||||
/**
|
||||
* handleChannelMembership: Handler responsible for the Create/Update operations occurring on the CHANNEL_MEMBERSHIP table from the 'Server' schema
|
||||
* @param {HandleChannelMembershipArgs} channelMembershipsArgs
|
||||
* @param {RawChannelMembership[]} channelMembershipsArgs.channelMemberships
|
||||
* @param {boolean} channelMembershipsArgs.prepareRecordsOnly
|
||||
* @throws DataOperatorException
|
||||
* @returns {Promise<ChannelMembership[]>}
|
||||
*/
|
||||
handleChannelMembership = async ({channelMemberships, prepareRecordsOnly = true}: HandleChannelMembershipArgs) => {
|
||||
let records: ChannelMembership[] = [];
|
||||
|
||||
if (!channelMemberships.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "channelMemberships" array has been passed to the handleChannelMembership method',
|
||||
);
|
||||
}
|
||||
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: channelMemberships, key: 'channel_id'});
|
||||
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'user_id',
|
||||
findMatchingRecordBy: isRecordChannelMembershipEqualToRaw,
|
||||
transformer: transformChannelMembershipRecord,
|
||||
prepareRecordsOnly,
|
||||
createOrUpdateRawValues,
|
||||
tableName: CHANNEL_MEMBERSHIP,
|
||||
});
|
||||
|
||||
return records;
|
||||
};
|
||||
|
||||
/**
|
||||
* handlePreferences: Handler responsible for the Create/Update operations occurring on the PREFERENCE table from the 'Server' schema
|
||||
* @param {HandlePreferencesArgs} preferencesArgs
|
||||
* @param {RawPreference[]} preferencesArgs.preferences
|
||||
* @param {boolean} preferencesArgs.prepareRecordsOnly
|
||||
* @throws DataOperatorException
|
||||
* @returns {Promise<Preference[]>}
|
||||
*/
|
||||
handlePreferences = async ({preferences, prepareRecordsOnly = true}: HandlePreferencesArgs) => {
|
||||
let records: Preference[] = [];
|
||||
|
||||
if (!preferences.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "preferences" array has been passed to the handlePreferences method',
|
||||
);
|
||||
}
|
||||
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: preferences, key: 'name'});
|
||||
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'user_id',
|
||||
findMatchingRecordBy: isRecordPreferenceEqualToRaw,
|
||||
transformer: transformPreferenceRecord,
|
||||
prepareRecordsOnly,
|
||||
createOrUpdateRawValues,
|
||||
tableName: PREFERENCE,
|
||||
});
|
||||
|
||||
return records;
|
||||
};
|
||||
|
||||
/**
|
||||
* handleReactions: Handler responsible for the Create/Update operations occurring on the Reaction table from the 'Server' schema
|
||||
* @param {HandleReactionsArgs} handleReactions
|
||||
* @param {RawReaction[]} handleReactions.reactions
|
||||
* @param {boolean} handleReactions.prepareRecordsOnly
|
||||
* @throws DataOperatorException
|
||||
* @returns {Promise<(Reaction| CustomEmoji)[]>}
|
||||
*/
|
||||
handleReactions = async ({reactions, prepareRecordsOnly}: HandleReactionsArgs) => {
|
||||
let batchRecords: (Reaction| CustomEmoji)[] = [];
|
||||
|
||||
if (!reactions.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "reactions" array has been passed to the handleReactions method',
|
||||
);
|
||||
}
|
||||
|
||||
const rawValues = getUniqueRawsBy({raws: reactions, key: 'emoji_name'}) as RawReaction[];
|
||||
|
||||
const {
|
||||
createEmojis,
|
||||
createReactions,
|
||||
deleteReactions,
|
||||
} = await sanitizeReactions({
|
||||
database: this.database,
|
||||
post_id: reactions[0].post_id,
|
||||
rawReactions: rawValues,
|
||||
});
|
||||
|
||||
if (createReactions.length) {
|
||||
// Prepares record for model Reactions
|
||||
const reactionsRecords = (await this.prepareRecords({
|
||||
createRaws: createReactions,
|
||||
transformer: transformReactionRecord,
|
||||
tableName: REACTION,
|
||||
})) as Reaction[];
|
||||
batchRecords = batchRecords.concat(reactionsRecords);
|
||||
}
|
||||
|
||||
if (createEmojis.length) {
|
||||
// Prepares records for model CustomEmoji
|
||||
const emojiRecords = (await this.prepareRecords({
|
||||
createRaws: getRawRecordPairs(createEmojis),
|
||||
transformer: transformCustomEmojiRecord,
|
||||
tableName: CUSTOM_EMOJI,
|
||||
})) as CustomEmoji[];
|
||||
batchRecords = batchRecords.concat(emojiRecords);
|
||||
}
|
||||
|
||||
batchRecords = batchRecords.concat(deleteReactions);
|
||||
|
||||
if (prepareRecordsOnly) {
|
||||
return batchRecords;
|
||||
}
|
||||
|
||||
if (batchRecords?.length) {
|
||||
await this.batchRecords(batchRecords);
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
/**
|
||||
* handleUsers: Handler responsible for the Create/Update operations occurring on the User table from the 'Server' schema
|
||||
* @param {HandleUsersArgs} usersArgs
|
||||
* @param {RawUser[]} usersArgs.users
|
||||
* @param {boolean} usersArgs.prepareRecordsOnly
|
||||
* @throws DataOperatorException
|
||||
* @returns {Promise<User[]>}
|
||||
*/
|
||||
handleUsers = async ({users, prepareRecordsOnly = true}: HandleUsersArgs) => {
|
||||
let records: User[] = [];
|
||||
|
||||
if (!users.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "users" array has been passed to the handleUsers method',
|
||||
);
|
||||
}
|
||||
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: users, key: 'id'});
|
||||
|
||||
records = await this.handleRecords({
|
||||
fieldName: 'id',
|
||||
findMatchingRecordBy: isRecordUserEqualToRaw,
|
||||
transformer: transformUserRecord,
|
||||
createOrUpdateRawValues,
|
||||
tableName: USER,
|
||||
prepareRecordsOnly,
|
||||
});
|
||||
|
||||
return records;
|
||||
};
|
||||
};
|
||||
|
||||
export default UserHandler;
|
||||
Reference in New Issue
Block a user