[Gekidou] Saves groups + groupTeams for constrained teams (#6357)

* Rebases and addresses PR feedback

* Update method docs, parallel promises

* Cleans up method docs
This commit is contained in:
Shaz MJ
2022-07-15 17:02:29 +10:00
committed by GitHub
parent de2c240bc7
commit f12be07df7
7 changed files with 145 additions and 22 deletions

View File

@@ -4,6 +4,7 @@
import {Client} from '@client/rest';
import DatabaseManager from '@database/manager';
import NetworkManager from '@managers/network_manager';
import {getTeamById} from '@queries/servers/team';
import {forceLogoutIfNecessary} from './session';
@@ -57,12 +58,21 @@ export const fetchGroupsForChannel = async (serverUrl: string, channelId: string
export const fetchGroupsForTeam = async (serverUrl: string, teamId: string, fetchOnly = false) => {
try {
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const client: Client = NetworkManager.getClient(serverUrl);
const response = await client.getAllGroupsAssociatedToTeam(teamId);
return operator.handleGroups({groups: response.groups, prepareRecordsOnly: fetchOnly});
const [groups, groupTeams] = await Promise.all([
operator.handleGroups({groups: response.groups, prepareRecordsOnly: true}),
operator.handleGroupTeamsForTeam({groups: response.groups, teamId, prepareRecordsOnly: true}),
]);
if (!fetchOnly) {
await operator.batchRecords([...groups, ...groupTeams]);
}
return {groups, groupTeams};
} catch (error) {
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
return {error};
}
};
@@ -74,8 +84,10 @@ export const fetchGroupsForMember = async (serverUrl: string, userId: string, fe
const client: Client = NetworkManager.getClient(serverUrl);
const response = await client.getAllGroupsAssociatedToMembership(userId);
const groups = await operator.handleGroups({groups: response, prepareRecordsOnly: true});
const groupMemberships = await operator.handleGroupMembershipsForMember({groups: response, userId, prepareRecordsOnly: true});
const [groups, groupMemberships] = await Promise.all([
operator.handleGroups({groups: response, prepareRecordsOnly: true}),
operator.handleGroupMembershipsForMember({groups: response, userId, prepareRecordsOnly: true}),
]);
if (!fetchOnly) {
await operator.batchRecords([...groups, ...groupMemberships]);
@@ -115,3 +127,17 @@ export const fetchFilteredChannelGroups = async (serverUrl: string, searchTerm:
}
};
export const fetchGroupsForTeamIfConstrained = async (serverUrl: string, teamId: string, fetchOnly = false) => {
try {
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const team = await getTeamById(database, teamId);
if (team?.isGroupConstrained) {
return fetchGroupsForTeam(serverUrl, teamId, fetchOnly);
}
return {};
} catch (error) {
return {error};
}
};

View File

@@ -16,6 +16,7 @@ import EphemeralStore from '@store/ephemeral_store';
import {isTablet} from '@utils/helpers';
import {fetchMyChannelsForTeam, switchToChannelById} from './channel';
import {fetchGroupsForTeamIfConstrained} from './groups';
import {fetchPostsForChannel, fetchPostsForUnreadChannels} from './post';
import {fetchRolesIfNeeded} from './role';
import {forceLogoutIfNecessary} from './session';
@@ -292,4 +293,7 @@ export async function handleTeamChange(serverUrl: string, teamId: string) {
await operator.batchRecords(models);
}
DeviceEventEmitter.emit(Events.TEAM_SWITCH, false);
// Fetch Groups + GroupTeams
fetchGroupsForTeamIfConstrained(serverUrl, teamId);
}

View File

@@ -2,29 +2,31 @@
// See LICENSE.txt for license information.
import {MM_TABLES} from '@constants/database';
import {transformGroupMembershipRecord, transformGroupRecord} from '@database/operator/server_data_operator/transformers/group';
import {transformGroupMembershipRecord, transformGroupRecord, transformGroupTeamRecord} from '@database/operator/server_data_operator/transformers/group';
import {getUniqueRawsBy} from '@database/operator/utils/general';
import {queryGroupMembershipForMember} from '@queries/servers/group';
import {queryGroupMembershipForMember, queryGroupTeamForTeam} from '@queries/servers/group';
import {generateGroupAssociationId} from '@utils/groups';
import {logWarning} from '@utils/log';
import type {HandleGroupArgs, HandleGroupMembershipForMemberArgs} from '@typings/database/database';
import type {HandleGroupArgs, HandleGroupMembershipForMemberArgs, HandleGroupTeamsForTeamArgs} from '@typings/database/database';
import type GroupModel from '@typings/database/models/servers/group';
import type GroupMembershipModel from '@typings/database/models/servers/group_membership';
import type GroupTeamModel from '@typings/database/models/servers/group_team';
const {GROUP, GROUP_MEMBERSHIP} = MM_TABLES.SERVER;
const {GROUP, GROUP_MEMBERSHIP, GROUP_TEAM} = MM_TABLES.SERVER;
export interface GroupHandlerMix {
handleGroups: ({groups, prepareRecordsOnly}: HandleGroupArgs) => Promise<GroupModel[]>;
handleGroupMembershipsForMember: ({userId, groups, prepareRecordsOnly}: HandleGroupMembershipForMemberArgs) => Promise<GroupMembershipModel[]>;
handleGroupTeamsForTeam: ({teamId, groups, prepareRecordsOnly}: HandleGroupTeamsForTeamArgs) => Promise<GroupTeamModel[]>;
}
const GroupHandler = (superclass: any) => class extends superclass implements GroupHandlerMix {
/**
* handleGroups: Handler responsible for the Create/Update operations occurring on the Group table from the 'Server' schema
* @param {HandleGroupArgs} groupsArgs
* @param {Group[]} groupsArgs.groups
* @param {boolean} groupsArgs.prepareRecordsOnly
*
* @param {HandleGroupArgs}
* @throws DataOperatorException
* @returns {Promise<GroupModel[]>}
*/
handleGroups = async ({groups, prepareRecordsOnly = true}: HandleGroupArgs): Promise<GroupModel[]> => {
@@ -48,10 +50,8 @@ const GroupHandler = (superclass: any) => class extends superclass implements Gr
/**
* handleGroupMembershipsForMember: Handler responsible for the Create/Update operations occurring on the GroupMembership table from the 'Server' schema
* @param {string} userId
* @param {HandleGroupMembershipForMemberArgs} groupMembershipsArgs
* @param {GroupMembership[]} groupMembershipsArgs.groupMemberships
* @param {boolean} groupMembershipsArgs.prepareRecordsOnly
*
* @param {HandleGroupMembershipForMemberArgs}
* @throws DataOperatorException
* @returns {Promise<GroupMembershipModel[]>}
*/
@@ -73,7 +73,7 @@ const GroupHandler = (superclass: any) => class extends superclass implements Gr
const groupsSet: {[key: string]: GroupMembership} = {};
for (const g of groups) {
groupsSet[`${g.id}`] = {id: generateGroupAssociationId(g.id, userId), user_id: userId, group_id: g.id};
groupsSet[g.id] = {id: generateGroupAssociationId(g.id, userId), user_id: userId, group_id: g.id};
}
for (const gm of existingGroupMemberships) {
@@ -105,6 +105,64 @@ const GroupHandler = (superclass: any) => class extends superclass implements Gr
return records;
};
/**
* handleGroupTeamsForTeam: Handler responsible for the Create/Update operations occurring on the GroupTeam table from the 'Server' schema
*
* @param {HandleGroupTeamsForTeamArgs}
* @throws DataOperatorException
* @returns {Promise<GroupTeamModel[]>}
*/
handleGroupTeamsForTeam = async ({teamId, groups, prepareRecordsOnly = true}: HandleGroupTeamsForTeamArgs): Promise<GroupTeamModel[]> => {
// Get existing group teams
const existingGroupTeams = await queryGroupTeamForTeam(this.database, teamId).fetch();
let records: GroupTeamModel[] = [];
let rawValues: GroupTeam[] = [];
// Nothing to add or remove
if (!groups?.length && !existingGroupTeams.length) {
return records;
} else if (!groups?.length && existingGroupTeams.length) { // No groups - remove all existing ones
records = existingGroupTeams.map((gt) => gt.prepareDestroyPermanently());
} else if (groups?.length && !existingGroupTeams.length) { // No existing groups - add all new ones
rawValues = groups.map((g) => ({id: generateGroupAssociationId(g.id, teamId), team_id: teamId, group_id: g.id}));
} else if (groups?.length && existingGroupTeams.length) { // If both, we only want to save new ones and delete one's no longer in groups
const groupsSet: {[key: string]: GroupTeam} = {};
for (const g of groups) {
groupsSet[g.id] = {id: generateGroupAssociationId(g.id, teamId), team_id: teamId, group_id: g.id};
}
for (const gt of existingGroupTeams) {
// Check if existingGroups overlaps with groups
if (groupsSet[gt.groupId]) {
// If there is an existing group already, we don't need to add it
delete groupsSet[gt.groupId];
} else {
// No group? Remove existing one
records.push(gt.prepareDestroyPermanently());
}
}
rawValues.push(...Object.values(groupsSet));
}
records.push(...(await this.handleRecords({
fieldName: 'id',
transformer: transformGroupTeamRecord,
rawValues,
tableName: GROUP_TEAM,
prepareRecordsOnly: true,
})));
// Batch update if there are records
if (records.length && !prepareRecordsOnly) {
await this.batchRecords(records);
}
return records;
};
};
export default GroupHandler;

View File

@@ -10,10 +10,12 @@ import {generateGroupAssociationId} from '@utils/groups';
import type {TransformerArgs} from '@typings/database/database';
import type GroupModel from '@typings/database/models/servers/group';
import type GroupMembershipModel from '@typings/database/models/servers/group_membership';
import type GroupTeamModel from '@typings/database/models/servers/group_team';
const {
GROUP,
GROUP_MEMBERSHIP,
GROUP_TEAM,
} = MM_TABLES.SERVER;
/**
@@ -72,3 +74,29 @@ export const transformGroupMembershipRecord = ({action, database, value}: Transf
fieldsMapper,
}) as Promise<GroupMembershipModel>;
};
/**
* transformGroupTeamRecord: Prepares a record of the SERVER database 'GroupTeam' table for update or create actions.
* @param {TransformerArgs} operator
* @param {Database} operator.database
* @param {RecordPair} operator.value
* @returns {Promise<GroupTeamModel>}
*/
export const transformGroupTeamRecord = ({action, database, value}: TransformerArgs): Promise<GroupTeamModel> => {
const raw = value.raw as GroupTeam;
// id of group comes from server response
const fieldsMapper = (model: GroupTeamModel) => {
model._raw.id = raw.id || generateGroupAssociationId(raw.group_id, raw.team_id);
model.groupId = raw.group_id;
model.teamId = raw.team_id;
};
return prepareBaseRecord({
action,
database,
tableName: GROUP_TEAM,
value,
fieldsMapper,
}) as Promise<GroupTeamModel>;
};

View File

@@ -7,6 +7,7 @@ import {MM_TABLES} from '@constants/database';
import type GroupModel from '@typings/database/models/servers/group';
import type GroupMembershipModel from '@typings/database/models/servers/group_membership';
import type GroupTeamModel from '@typings/database/models/servers/group_team';
const {SERVER: {GROUP, GROUP_CHANNEL, GROUP_MEMBERSHIP, GROUP_TEAM}} = MM_TABLES;
@@ -41,3 +42,9 @@ export const queryGroupMembershipForMember = (database: Database, userId: string
Q.where('user_id', userId),
);
};
export const queryGroupTeamForTeam = (database: Database, teamId: string) => {
return database.collections.get<GroupTeamModel>(GROUP_TEAM).query(
Q.where('team_id', teamId),
);
};

View File

@@ -16,14 +16,9 @@ type Group = {
};
type GroupTeam = {
id?: string;
team_id: string;
team_display_name: string;
team_type: string;
group_id: string;
auto_add: boolean;
create_at: number;
delete_at: number;
update_at: number;
}
type GroupChannel = {

View File

@@ -227,6 +227,11 @@ export type HandleGroupMembershipForMemberArgs = PrepareOnly & {
groups?: Group[];
}
export type HandleGroupTeamsForTeamArgs = PrepareOnly & {
teamId: string;
groups?: Group[];
}
export type HandleCategoryChannelArgs = PrepareOnly & {
categoryChannels?: CategoryChannel[];
};