forked from Ivasoft/mattermost-mobile
[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:
@@ -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};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>;
|
||||
};
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
};
|
||||
|
||||
7
types/api/groups.d.ts
vendored
7
types/api/groups.d.ts
vendored
@@ -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 = {
|
||||
|
||||
5
types/database/database.d.ts
vendored
5
types/database/database.d.ts
vendored
@@ -227,6 +227,11 @@ export type HandleGroupMembershipForMemberArgs = PrepareOnly & {
|
||||
groups?: Group[];
|
||||
}
|
||||
|
||||
export type HandleGroupTeamsForTeamArgs = PrepareOnly & {
|
||||
teamId: string;
|
||||
groups?: Group[];
|
||||
}
|
||||
|
||||
export type HandleCategoryChannelArgs = PrepareOnly & {
|
||||
categoryChannels?: CategoryChannel[];
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user