Fixes a race condition and deleting only teams that you no longer belong to (#6028)

* Fixes a race condition and deleting only teams that you no longer belong to

* common action teamsToRemove
This commit is contained in:
Elias Nahum
2022-03-07 19:20:47 -03:00
committed by GitHub
parent 3829e43731
commit 49506c1b6c
6 changed files with 43 additions and 41 deletions

View File

@@ -8,12 +8,11 @@ import DatabaseManager from '@database/manager';
import {queryChannelsById, queryDefaultChannelForTeam} from '@queries/servers/channel';
import {prepareModels} from '@queries/servers/entry';
import {prepareCommonSystemValues, queryCommonSystemValues, queryCurrentChannelId, queryCurrentTeamId, queryWebSocketLastDisconnected, setCurrentTeamAndChannelId} from '@queries/servers/system';
import {deleteMyTeams, queryTeamsById} from '@queries/servers/team';
import {queryCurrentUser} from '@queries/servers/user';
import {deleteV1Data} from '@utils/file';
import {isTablet} from '@utils/helpers';
import {AppEntryData, AppEntryError, deferredAppEntryActions, fetchAppEntryData, syncOtherServers} from './common';
import {AppEntryData, AppEntryError, deferredAppEntryActions, fetchAppEntryData, syncOtherServers, teamsToRemove} from './common';
export const appEntry = async (serverUrl: string, since = 0) => {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
@@ -62,12 +61,7 @@ export const appEntry = async (serverUrl: string, since = 0) => {
}
}
let removeTeams;
if (removeTeamIds?.length) {
// Immediately delete myTeams so that the UI renders only teams the user is a member of.
removeTeams = await queryTeamsById(database, removeTeamIds);
await deleteMyTeams(operator, removeTeams!);
}
const removeTeams = await teamsToRemove(serverUrl, removeTeamIds);
let removeChannels;
if (removeChannelIds?.length) {

View File

@@ -5,7 +5,7 @@ import {fetchChannelStats, fetchMissingSidebarInfo, fetchMyChannelsForTeam, mark
import {fetchPostsForChannel, fetchPostsForUnreadChannels} from '@actions/remote/post';
import {MyPreferencesRequest, fetchMyPreferences} from '@actions/remote/preference';
import {fetchConfigAndLicense} from '@actions/remote/systems';
import {fetchMyTeams, fetchTeamsChannelsAndUnreadPosts, MyTeamsRequest} from '@actions/remote/team';
import {fetchAllTeams, fetchMyTeams, fetchTeamsChannelsAndUnreadPosts, MyTeamsRequest} from '@actions/remote/team';
import {fetchMe, MyUserRequest, updateAllUsersSince} from '@actions/remote/user';
import {General, Preferences} from '@constants';
import DatabaseManager from '@database/manager';
@@ -16,7 +16,7 @@ import NetworkManager from '@init/network_manager';
import {queryAllServers} from '@queries/app/servers';
import {queryAllChannelsForTeam} from '@queries/servers/channel';
import {queryConfig} from '@queries/servers/system';
import {queryAvailableTeamIds, queryMyTeams} from '@queries/servers/team';
import {deleteMyTeams, queryAvailableTeamIds, queryMyTeams, queryMyTeamsById, queryTeamsById} from '@queries/servers/team';
import type ClientError from '@client/rest/error';
@@ -34,6 +34,27 @@ export type AppEntryError = {
error?: Error | ClientError | string;
}
export const teamsToRemove = async (serverUrl: string, removeTeamIds?: string[]) => {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return undefined;
}
const {database} = operator;
if (removeTeamIds?.length) {
// Immediately delete myTeams so that the UI renders only teams the user is a member of.
const removeMyTeams = await queryMyTeamsById(database, removeTeamIds);
if (removeMyTeams?.length) {
await deleteMyTeams(operator, removeMyTeams);
const ids = removeMyTeams.map((m) => m.id);
const removeTeams = await queryTeamsById(database, ids);
return removeTeams;
}
}
return undefined;
};
export const fetchAppEntryData = async (serverUrl: string, since: number, initialTeamId: string): Promise<AppEntryData | AppEntryError> => {
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
if (!database) {
@@ -187,6 +208,7 @@ export const deferredAppEntryActions = async (
await fetchTeamsChannelsAndUnreadPosts(serverUrl, since, teamData.teams, teamData.memberships, initialTeamId);
}
fetchAllTeams(serverUrl);
updateAllUsersSince(serverUrl, since);
};

View File

@@ -11,13 +11,13 @@ import DatabaseManager from '@database/manager';
import {queryChannelsById, queryDefaultChannelForTeam, queryMyChannel} from '@queries/servers/channel';
import {prepareModels} from '@queries/servers/entry';
import {queryCommonSystemValues, queryCurrentTeamId, queryWebSocketLastDisconnected, setCurrentTeamAndChannelId} from '@queries/servers/system';
import {deleteMyTeams, queryMyTeamById, queryTeamsById} from '@queries/servers/team';
import {queryMyTeamById} from '@queries/servers/team';
import {queryCurrentUser} from '@queries/servers/user';
import EphemeralStore from '@store/ephemeral_store';
import {isTablet} from '@utils/helpers';
import {emitNotificationError} from '@utils/notification';
import {AppEntryData, AppEntryError, deferredAppEntryActions, fetchAppEntryData, syncOtherServers} from './common';
import {AppEntryData, AppEntryError, deferredAppEntryActions, fetchAppEntryData, syncOtherServers, teamsToRemove} from './common';
export const pushNotificationEntry = async (serverUrl: string, notification: NotificationWithData) => {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
@@ -117,12 +117,7 @@ export const pushNotificationEntry = async (serverUrl: string, notification: Not
emitNotificationError('Channel');
}
let removeTeams;
if (removeTeamIds?.length) {
// Immediately delete myTeams so that the UI renders only teams the user is a member of.
removeTeams = await queryTeamsById(operator.database, removeTeamIds);
await deleteMyTeams(operator, removeTeams!);
}
const removeTeams = await teamsToRemove(serverUrl, removeTeamIds);
let removeChannels;
if (removeChannelIds?.length) {

View File

@@ -2,7 +2,7 @@
// See LICENSE.txt for license information.
import {fetchMissingSidebarInfo, switchToChannelById} from '@actions/remote/channel';
import {AppEntryData, AppEntryError, fetchAppEntryData} from '@actions/remote/entry/common';
import {AppEntryData, AppEntryError, fetchAppEntryData, teamsToRemove} from '@actions/remote/entry/common';
import {fetchPostsForUnreadChannels, fetchPostsSince} from '@actions/remote/post';
import {fetchRoles} from '@actions/remote/role';
import {fetchConfigAndLicense} from '@actions/remote/systems';
@@ -15,7 +15,6 @@ import {getTeammateNameDisplaySetting} from '@helpers/api/preference';
import {queryChannelsById, queryDefaultChannelForTeam} from '@queries/servers/channel';
import {prepareModels} from '@queries/servers/entry';
import {queryCommonSystemValues, queryConfig, queryCurrentChannelId, queryWebSocketLastDisconnected, resetWebSocketLastDisconnected, setCurrentTeamAndChannelId} from '@queries/servers/system';
import {deleteMyTeams, queryTeamsById} from '@queries/servers/team';
import {isTablet} from '@utils/helpers';
import {handleCategoryCreatedEvent, handleCategoryDeletedEvent, handleCategoryOrderUpdatedEvent, handleCategoryUpdatedEvent} from './category';
@@ -137,12 +136,7 @@ async function doReconnect(serverUrl: string) {
}
}
let removeTeams;
if (removeTeamIds?.length) {
// Immediately delete myTeams so that the UI renders only teams the user is a member of.
removeTeams = await queryTeamsById(database, removeTeamIds);
await deleteMyTeams(operator, removeTeams!);
}
const removeTeams = await teamsToRemove(serverUrl, removeTeamIds);
let removeChannels;
if (removeChannelIds?.length) {

View File

@@ -4,9 +4,7 @@
import React, {useEffect} from 'react';
import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
import {fetchAllTeams} from '@actions/remote/team';
import {TEAM_SIDEBAR_WIDTH} from '@constants/view';
import {useServerUrl} from '@context/server';
import {useTheme} from '@context/theme';
import {makeStyleSheetFromTheme} from '@utils/theme';
@@ -47,7 +45,6 @@ export default function TeamSidebar({canCreateTeams, iconPad, otherTeams, teamsC
const width = useSharedValue(initialWidth);
const marginTop = useSharedValue(iconPad ? 44 : 0);
const theme = useTheme();
const serverUrl = useServerUrl();
const styles = getStyleSheet(theme);
const transform = useAnimatedStyle(() => {
@@ -64,10 +61,6 @@ export default function TeamSidebar({canCreateTeams, iconPad, otherTeams, teamsC
marginTop.value = iconPad ? 44 : 0;
}, [iconPad]);
useEffect(() => {
fetchAllTeams(serverUrl);
}, [serverUrl]);
useEffect(() => {
width.value = teamsCount > 1 ? TEAM_SIDEBAR_WIDTH : 0;
}, [teamsCount]);

View File

@@ -188,16 +188,11 @@ export const prepareMyTeams = (operator: ServerDataOperator, teams: Team[], memb
}
};
export const deleteMyTeams = async (operator: ServerDataOperator, teams: TeamModel[]) => {
export const deleteMyTeams = async (operator: ServerDataOperator, myTeams: MyTeamModel[]) => {
try {
const preparedModels: Model[] = [];
for await (const team of teams) {
try {
const myTeam = await team.myTeam.fetch() as MyTeamModel;
preparedModels.push(myTeam.prepareDestroyPermanently());
} catch {
// Record not found, do nothing
}
for (const myTeam of myTeams) {
preparedModels.push(myTeam.prepareDestroyPermanently());
}
if (preparedModels.length) {
@@ -296,6 +291,15 @@ export const queryTeamsById = async (database: Database, teamIds: string[]): Pro
}
};
export const queryMyTeamsById = async (database: Database, teamIds: string[]): Promise<MyTeamModel[]|undefined> => {
try {
const teams = (await database.get<MyTeamModel>(MY_TEAM).query(Q.where('id', Q.oneOf(teamIds))).fetch());
return teams;
} catch {
return undefined;
}
};
export const queryTeamByName = async (database: Database, teamName: string): Promise<TeamModel|undefined> => {
try {
const team = (await database.get(TEAM).query(Q.where('name', teamName)).fetch()) as TeamModel[];