[Gekidou MM-39728] - Websocket Events - Channels (#6038)

* wip checkin

* wip checkin because my system is messed up again

* correct the add user and remove user from channel functions. THe user and channel ids sometimes come from the broudcast field, not the data object

* remove old comments

* remove old comments

* remove old comments

* add back in unarchive handler. handleChannelUpdateEvent will also update the deleteAt value for a channel, but does not get called when unarchive from the channel view, only through the system console.

* nit remove empty line

* wip

* wip

* remove handleChannelConvertedEvent

* remove handleChannelDirectAddedEvent

* wip

* wip

* wip

* Fix websockets events

* Address feedback

* Ensure categories and some refactoring

* Self review fixes

* Fix category creation bug

* Address feedback and minor improvements

* Address feedback

* Fix lint

Co-authored-by: Daniel Espino García <larkox@gmail.com>
This commit is contained in:
Jason Frerich 2022-04-04 13:25:42 -05:00 committed by GitHub
parent b13a4abc27
commit 137ba9d191
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 558 additions and 299 deletions

View file

@ -5,14 +5,17 @@ import {Model} from '@nozbe/watermelondb';
import {DeviceEventEmitter} from 'react-native';
import {General, Navigation as NavigationConstants, Preferences, Screens} from '@constants';
import {CHANNELS_CATEGORY, DMS_CATEGORY} from '@constants/categories';
import DatabaseManager from '@database/manager';
import {getTeammateNameDisplaySetting} from '@helpers/api/preference';
import {prepareDeleteChannel, prepareMyChannelsForTeam, queryAllMyChannel, getMyChannel, getChannelById, queryUsersOnChannel} from '@queries/servers/channel';
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
import {prepareCommonSystemValues, PrepareCommonSystemValuesArgs, getCommonSystemValues, getCurrentTeamId, setCurrentChannelId} from '@queries/servers/system';
import {addChannelToTeamHistory, addTeamToTeamHistory, getTeamById, removeChannelFromTeamHistory} from '@queries/servers/team';
import {prepareCommonSystemValues, PrepareCommonSystemValuesArgs, getCommonSystemValues, getCurrentTeamId, setCurrentChannelId, getCurrentUserId} from '@queries/servers/system';
import {addChannelToTeamHistory, addTeamToTeamHistory, getTeamById, queryMyTeams, removeChannelFromTeamHistory} from '@queries/servers/team';
import {getCurrentUser} from '@queries/servers/user';
import {dismissAllModalsAndPopToRoot, dismissAllModalsAndPopToScreen} from '@screens/navigation';
import {makeCategoryChannelId, makeCategoryId} from '@utils/categories';
import {isDMorGM} from '@utils/channel';
import {isTablet} from '@utils/helpers';
import {displayGroupMessageName, displayUsername, getUserIdFromChannelName} from '@utils/user';
@ -253,20 +256,22 @@ export const storeMyChannelsForTeam = async (serverUrl: string, teamId: string,
if (!operator) {
return {error: `${serverUrl} database not found`};
}
const modelPromises: Array<Promise<Model[]>> = [];
const prepare = await prepareMyChannelsForTeam(operator, teamId, channels, memberships);
if (prepare) {
modelPromises.push(...prepare);
}
const modelPromises: Array<Promise<Model[]>> = [
...await prepareMyChannelsForTeam(operator, teamId, channels, memberships),
];
const models = await Promise.all(modelPromises);
if (!models.length) {
return {models: []};
}
const flattenedModels = models.flat() as Model[];
if (prepareRecordsOnly) {
return {models: flattenedModels};
}
if (flattenedModels?.length > 0) {
if (flattenedModels.length) {
try {
await operator.batchRecords(flattenedModels);
} catch (error) {
@ -279,6 +284,42 @@ export const storeMyChannelsForTeam = async (serverUrl: string, teamId: string,
return {models: flattenedModels};
};
export const updateMyChannelFromWebsocket = async (serverUrl: string, channelMember: ChannelMembership, prepareRecordsOnly = false) => {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
const member = await getMyChannel(operator.database, channelMember.channel_id);
if (member) {
member.prepareUpdate((m) => {
m.roles = channelMember.roles;
});
if (!prepareRecordsOnly) {
operator.batchRecords([member]);
}
}
return {model: member};
};
export const updateChannelInfoFromChannel = async (serverUrl: string, channel: Channel, prepareRecordsOnly = false) => {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
const newInfo = (await operator.handleChannelInfo({channelInfos: [{
header: channel.header,
purpose: channel.purpose,
id: channel.id,
}],
prepareRecordsOnly: true}))[0];
if (!prepareRecordsOnly) {
operator.batchRecords([newInfo]);
}
return {model: newInfo};
};
export const updateLastPostAt = async (serverUrl: string, channelId: string, lastPostAt: number, prepareRecordsOnly = false) => {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
@ -355,3 +396,51 @@ export async function updateChannelsDisplayName(serverUrl: string, channels: Cha
return {models};
}
export async function addChannelToDefaultCategory(serverUrl: string, channel: Channel | ChannelModel, prepareRecordsOnly = false) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
const {database} = operator;
const teamId = 'teamId' in channel ? channel.teamId : channel.team_id;
const userId = await getCurrentUserId(database);
if (!userId) {
return {error: 'no current user id'};
}
if (!isDMorGM(channel)) {
const models = await operator.handleCategoryChannels({categoryChannels: [{
category_id: makeCategoryId(CHANNELS_CATEGORY, userId, teamId),
channel_id: channel.id,
sort_order: 0,
id: makeCategoryChannelId(teamId, channel.id),
}],
prepareRecordsOnly});
return {models};
}
const allTeams = await queryMyTeams(database).fetch();
const models = (
await Promise.all(
allTeams.map(
(t) => operator.handleCategoryChannels({categoryChannels: [{
category_id: makeCategoryId(DMS_CATEGORY, userId, t.id),
channel_id: channel.id,
sort_order: 0,
id: makeCategoryChannelId(t.id, channel.id),
}],
prepareRecordsOnly: true}),
),
)
).flat();
if (models.length && !prepareRecordsOnly) {
operator.batchRecords(models);
}
return {models};
}

View file

@ -6,7 +6,7 @@ import {Model} from '@nozbe/watermelondb';
import {IntlShape} from 'react-intl';
import {storeCategories} from '@actions/local/category';
import {storeMyChannelsForTeam, switchToChannel} from '@actions/local/channel';
import {addChannelToDefaultCategory, storeMyChannelsForTeam, switchToChannel} from '@actions/local/channel';
import {General, Preferences} from '@constants';
import DatabaseManager from '@database/manager';
import {privateChannelJoinPrompt} from '@helpers/api/channel';
@ -17,7 +17,7 @@ import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
import {getCommonSystemValues, getCurrentTeamId, getCurrentUserId} from '@queries/servers/system';
import {prepareMyTeams, getNthLastChannelFromTeam, getMyTeamById, getTeamById, getTeamByName} from '@queries/servers/team';
import {getCurrentUser} from '@queries/servers/user';
import {generateChannelNameFromDisplayName, getDirectChannelName} from '@utils/channel';
import {generateChannelNameFromDisplayName, getDirectChannelName, isDMorGM} from '@utils/channel';
import {PERMALINK_GENERIC_TEAM_NAME_REDIRECT} from '@utils/url';
import {displayGroupMessageName, displayUsername} from '@utils/user';
@ -29,7 +29,6 @@ import {fetchProfilesPerChannels, fetchUsersByIds} from './user';
import type {Client} from '@client/rest';
import type ChannelModel from '@typings/database/models/servers/channel';
import type ChannelInfoModel from '@typings/database/models/servers/channel_info';
import type MyChannelModel from '@typings/database/models/servers/my_channel';
import type MyTeamModel from '@typings/database/models/servers/my_team';
import type TeamModel from '@typings/database/models/servers/team';
@ -140,22 +139,16 @@ export const createChannel = async (serverUrl: string, displayName: string, purp
const models: Model[] = [];
const channelModels = await prepareMyChannelsForTeam(operator, channelData.team_id, [channelData], [member]);
if (channelModels?.length) {
if (channelModels.length) {
const resolvedModels = await Promise.all(channelModels);
models.push(...resolvedModels.flat());
}
const categoriesModels = await operator.handleCategoryChannels({categoryChannels: [{
category_id: `channels_${currentUserId}_${currentTeamId}`,
channel_id: channelData.id,
sort_order: 0,
id: `${currentTeamId}_${channelData.id}`,
}],
prepareRecordsOnly: true});
if (categoriesModels?.length) {
models.push(...categoriesModels);
const categoriesModels = await addChannelToDefaultCategory(serverUrl, channelData, true);
if (categoriesModels.models?.length) {
models.push(...categoriesModels.models);
}
if (models.length) {
await operator.batchRecords(models.flat());
await operator.batchRecords(models);
}
fetchChannelStats(serverUrl, channelData.id, false);
return {channel: channelData};
@ -270,14 +263,11 @@ export const fetchChannelStats = async (serverUrl: string, channelId: string, fe
if (!fetchOnly) {
const channel = await getChannelById(operator.database, channelId);
if (channel) {
const channelInfo = await channel.info.fetch() as ChannelInfoModel;
const channelInfos: ChannelInfo[] = [{
const channelInfos: Array<Partial<ChannelInfo>> = [{
guest_count: stats.guest_count,
header: channelInfo.header,
id: channelId,
member_count: stats.member_count,
pinned_post_count: stats.pinnedpost_count,
purpose: channelInfo.purpose,
}];
await operator.handleChannelInfo({channelInfos, prepareRecordsOnly: false});
}
@ -313,7 +303,7 @@ export const fetchMyChannelsForTeam = async (serverUrl: string, teamId: string,
let channels = allChannels;
let memberships = channelMemberships;
if (excludeDirect) {
channels = channels.filter((c) => c.type !== General.GM_CHANNEL && c.type !== General.DM_CHANNEL);
channels = channels.filter((c) => !isDMorGM(c));
}
const channelIds = new Set<string>(channels.map((c) => c.id));
@ -342,6 +332,11 @@ export const fetchMyChannelsForTeam = async (serverUrl: string, teamId: string,
};
export const fetchMyChannel = async (serverUrl: string, teamId: string, channelId: string, fetchOnly = false): Promise<MyChannelsRequest> => {
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
if (!database) {
return {error: `${serverUrl} database not found`};
}
let client: Client;
try {
client = NetworkManager.getClient(serverUrl);
@ -356,7 +351,7 @@ export const fetchMyChannel = async (serverUrl: string, teamId: string, channelI
]);
if (!fetchOnly) {
storeMyChannelsForTeam(serverUrl, channel.team_id || teamId, [channel], [member]);
await storeMyChannelsForTeam(serverUrl, channel.team_id || teamId, [channel], [member]);
}
return {
@ -437,8 +432,7 @@ export const joinChannel = async (serverUrl: string, userId: string, teamId: str
channel = await client.getChannel(channelId);
} else if (channelName) {
channel = await client.getChannelByName(teamId, channelName, true);
const directTypes: string[] = [General.GM_CHANNEL, General.DM_CHANNEL];
if (directTypes.includes(channel.type)) {
if (isDMorGM(channel)) {
member = await client.getChannelMember(channel.id, userId);
} else {
member = await client.addToChannel(userId, channel.id);
@ -453,14 +447,14 @@ export const joinChannel = async (serverUrl: string, userId: string, teamId: str
if (channel && member && !fetchOnly) {
fetchRolesIfNeeded(serverUrl, member.roles.split(' '));
const modelPromises: Array<Promise<Model[]>> = [];
const prepare = await prepareMyChannelsForTeam(operator, teamId, [channel], [member]);
if (prepare) {
modelPromises.push(...prepare);
}
const modelPromises: Array<Promise<Model[]>> = await prepareMyChannelsForTeam(operator, teamId, [channel], [member]);
if (modelPromises.length) {
const models = await Promise.all(modelPromises);
const flattenedModels = models.flat() as Model[];
const flattenedModels: Model[] = models.flat();
const categoriesModels = await addChannelToDefaultCategory(serverUrl, channel, true);
if (categoriesModels.models?.length) {
flattenedModels.push(...categoriesModels.models);
}
if (flattenedModels?.length > 0) {
try {
await operator.batchRecords(flattenedModels);
@ -593,10 +587,7 @@ export const switchToChannelByName = async (serverUrl: string, channelName: stri
const modelPromises: Array<Promise<Model[]>> = [];
const {operator} = DatabaseManager.serverDatabases[serverUrl];
if (!(team instanceof Model)) {
const prepT = prepareMyTeams(operator, [team], [(myTeam as TeamMembership)]);
if (prepT) {
modelPromises.push(...prepT);
}
modelPromises.push(...prepareMyTeams(operator, [team], [(myTeam as TeamMembership)]));
} else if (!(myTeam instanceof Model)) {
const mt: MyTeam[] = [{
id: myTeam.team_id,
@ -609,10 +600,7 @@ export const switchToChannelByName = async (serverUrl: string, channelName: stri
}
if (!(myChannel instanceof Model)) {
const prepCh = await prepareMyChannelsForTeam(operator, team.id, [channel], [myChannel]);
if (prepCh) {
modelPromises.push(...prepCh);
}
modelPromises.push(...await prepareMyChannelsForTeam(operator, team.id, [channel], [myChannel]));
}
let teamId;
@ -654,6 +642,7 @@ export const createDirectChannel = async (serverUrl: string, userId: string, dis
if (!operator) {
return {error: `${serverUrl} database not found`};
}
const {database} = operator;
let client: Client;
try {
@ -663,7 +652,7 @@ export const createDirectChannel = async (serverUrl: string, userId: string, dis
}
try {
const currentUser = await getCurrentUser(operator.database);
const currentUser = await getCurrentUser(database);
if (!currentUser) {
return {error: 'Cannot get the current user'};
}
@ -672,8 +661,8 @@ export const createDirectChannel = async (serverUrl: string, userId: string, dis
if (displayName) {
created.display_name = displayName;
} else {
const preferences = await queryPreferencesByCategoryAndName(operator.database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT).fetch();
const system = await getCommonSystemValues(operator.database);
const preferences = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT).fetch();
const system = await getCommonSystemValues(database);
const teammateDisplayNameSetting = getTeammateNameDisplaySetting(preferences || [], system.config, system.license);
const {directChannels} = await fetchMissingSidebarInfo(serverUrl, [created], currentUser.locale, teammateDisplayNameSetting, currentUser.id, true);
created.display_name = directChannels?.[0].display_name || created.display_name;
@ -694,12 +683,16 @@ export const createDirectChannel = async (serverUrl: string, userId: string, dis
const models = [];
const channelPromises = await prepareMyChannelsForTeam(operator, '', [created], [member, {...member, user_id: userId}]);
if (channelPromises) {
if (channelPromises.length) {
const channelModels = await Promise.all(channelPromises);
const flattenedChannelModels = channelModels.flat();
if (flattenedChannelModels.length) {
models.push(...flattenedChannelModels);
}
const categoryModels = await addChannelToDefaultCategory(serverUrl, created, true);
if (categoryModels.models?.length) {
models.push(...categoryModels.models);
}
}
if (models.length) {
@ -824,11 +817,15 @@ export const createGroupChannel = async (serverUrl: string, userIds: string[]) =
if (directChannels?.length) {
const channelPromises = await prepareMyChannelsForTeam(operator, '', directChannels, members);
if (channelPromises) {
if (channelPromises.length) {
const channelModels = await Promise.all(channelPromises);
const flattenedChannelModels = channelModels.flat();
if (flattenedChannelModels.length) {
operator.batchRecords(flattenedChannelModels);
const models: Model[] = channelModels.flat();
const categoryModels = await addChannelToDefaultCategory(serverUrl, created, true);
if (categoryModels.models?.length) {
models.push(...categoryModels.models);
}
if (models.length) {
operator.batchRecords(models);
}
}
}
@ -935,15 +932,20 @@ export async function getOrCreateDirectChannel(serverUrl: string, otherUserId: s
const member = await client.getMyChannelMember(newChannel.id);
const modelPromises: Array<Promise<Model[]>> = [];
const prepare = await prepareMyChannelsForTeam(operator, '', [newChannel], [member]);
if (prepare?.length) {
modelPromises.push(...prepare);
const models = await Promise.all(modelPromises);
const flattenedModels = models.flat() as Model[];
if (flattenedModels?.length > 0) {
const modelPromises: Array<Promise<Model[]>> = [...await prepareMyChannelsForTeam(operator, '', [newChannel], [member])];
if (modelPromises.length) {
const channelModels = await Promise.all(modelPromises);
const models: Model[] = channelModels.flat();
const categoryModels = await addChannelToDefaultCategory(serverUrl, newChannel, true);
if (categoryModels.models?.length) {
models.push(...categoryModels.models);
}
if (models.length) {
operator.batchRecords(models);
}
if (models.length > 0) {
try {
await operator.batchRecords(flattenedModels);
await operator.batchRecords(models);
} catch {
// eslint-disable-next-line no-console
console.log('FAILED TO BATCH CHANNELS');

View file

@ -7,7 +7,7 @@ import {MyPreferencesRequest, fetchMyPreferences} from '@actions/remote/preferen
import {fetchConfigAndLicense} from '@actions/remote/systems';
import {fetchAllTeams, fetchMyTeams, fetchTeamsChannelsAndUnreadPosts, MyTeamsRequest} from '@actions/remote/team';
import {fetchMe, MyUserRequest, updateAllUsersSince} from '@actions/remote/user';
import {General, Preferences} from '@constants';
import {Preferences} from '@constants';
import DatabaseManager from '@database/manager';
import {getPreferenceValue, getTeammateNameDisplaySetting} from '@helpers/api/preference';
import {selectDefaultTeam} from '@helpers/api/team';
@ -17,6 +17,7 @@ import {queryAllServers} from '@queries/app/servers';
import {queryAllChannelsForTeam} from '@queries/servers/channel';
import {getConfig} from '@queries/servers/system';
import {deleteMyTeams, getAvailableTeamIds, queryMyTeams, queryMyTeamsByIds, queryTeamsById} from '@queries/servers/team';
import {isDMorGM} from '@utils/channel';
import type ClientError from '@client/rest/error';
@ -192,7 +193,7 @@ export const deferredAppEntryActions = async (
// defer sidebar DM & GM profiles
if (chData?.channels?.length && chData.memberships?.length) {
const directChannels = chData.channels.filter((c) => c.type === General.DM_CHANNEL || c.type === General.GM_CHANNEL);
const directChannels = chData.channels.filter(isDMorGM);
const channelsToFetchProfiles = new Set<Channel>(directChannels);
if (channelsToFetchProfiles.size) {
const teammateDisplayNameSetting = getTeammateNameDisplaySetting(preferences || [], config, license);

View file

@ -685,8 +685,8 @@ export async function fetchMissingChannelsFromPosts(serverUrl: string, posts: Po
const channelMemberships = await Promise.all(userPromises);
if (!fetchOnly && channels.length && channelMemberships.length) {
const modelPromises = prepareMissingChannelsForAllTeams(operator, channels, channelMemberships) as Array<Promise<Model[]>>;
if (modelPromises && modelPromises.length) {
const modelPromises = prepareMissingChannelsForAllTeams(operator, channels, channelMemberships);
if (modelPromises.length) {
const channelModelsArray = await Promise.all(modelPromises);
if (channelModelsArray.length) {
const models = channelModelsArray.flatMap((mdls) => {
@ -928,7 +928,7 @@ export async function fetchSavedPosts(serverUrl: string, teamId?: string, channe
const promises: Array<Promise<Model[]>> = [];
const {authors} = await fetchPostAuthors(serverUrl, postsArray, true);
const {channels, channelMemberships} = await fetchMissingChannelsFromPosts(serverUrl, postsArray, true) as {channels: Channel[]; channelMemberships: ChannelMembership[]};
const {channels, channelMemberships} = await fetchMissingChannelsFromPosts(serverUrl, postsArray, true);
if (authors?.length) {
promises.push(
@ -940,8 +940,8 @@ export async function fetchSavedPosts(serverUrl: string, teamId?: string, channe
}
if (channels?.length && channelMemberships?.length) {
const channelPromises = prepareMissingChannelsForAllTeams(operator, channels, channelMemberships) as Array<Promise<Model[]>>;
if (channelPromises && channelPromises.length) {
const channelPromises = prepareMissingChannelsForAllTeams(operator, channels, channelMemberships);
if (channelPromises.length) {
promises.push(...channelPromises);
}
}

View file

@ -1,17 +1,18 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {General, Preferences} from '@constants';
import {Preferences} from '@constants';
import DatabaseManager from '@database/manager';
import {getPreferenceValue, getTeammateNameDisplaySetting} from '@helpers/api/preference';
import {selectDefaultTeam} from '@helpers/api/team';
import NetworkManager from '@init/network_manager';
import {prepareCategories, prepareCategoryChannels} from '@queries/servers/categories';
import {prepareMyChannelsForTeam} from '@queries/servers/channel';
import {prepareMyPreferences, queryPreferencesByCategoryAndName} from '@queries/servers/preference';
import {prepareCommonSystemValues, getCommonSystemValues} from '@queries/servers/system';
import {prepareMyTeams} from '@queries/servers/team';
import {getCurrentUser} from '@queries/servers/user';
import {selectDefaultChannelForTeam} from '@utils/channel';
import {isDMorGM, selectDefaultChannelForTeam} from '@utils/channel';
import {fetchMissingSidebarInfo, fetchMyChannelsForTeam, MyChannelsRequest} from './channel';
import {fetchPostsForChannel} from './post';
@ -23,11 +24,13 @@ import {fetchMyTeams, MyTeamsRequest} from './team';
import type {Model} from '@nozbe/watermelondb';
export const retryInitialTeamAndChannel = async (serverUrl: string) => {
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
if (!database) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
const {database} = operator;
try {
NetworkManager.getClient(serverUrl);
} catch (error) {
@ -95,42 +98,26 @@ export const retryInitialTeamAndChannel = async (serverUrl: string) => {
return {error: true};
}
const modelPromises: Array<Promise<Model[]>> = [];
const {operator} = DatabaseManager.serverDatabases[serverUrl];
const models: Model[] = (await Promise.all([
prepareMyPreferences(operator, prefData.preferences!),
...prepareMyTeams(operator, teamData.teams!, teamData.memberships!),
...await prepareMyChannelsForTeam(operator, initialTeam.id, chData!.channels!, chData!.memberships!),
prepareCategories(operator, chData!.categories!),
prepareCategoryChannels(operator, chData!.categories!),
prepareCommonSystemValues(
operator,
{
config: clData.config!,
license: clData.license!,
currentTeamId: initialTeam?.id,
currentChannelId: initialChannel?.id,
},
),
])).flat();
const prefModel = prepareMyPreferences(operator, prefData.preferences!);
if (prefModel) {
modelPromises.push(prefModel);
}
await operator.batchRecords(models);
const teamModels = prepareMyTeams(operator, teamData.teams!, teamData.memberships!);
if (teamModels) {
modelPromises.push(...teamModels);
}
const channelModels = await prepareMyChannelsForTeam(operator, initialTeam!.id, chData!.channels!, chData!.memberships!);
if (channelModels) {
modelPromises.push(...channelModels);
}
const systemModels = prepareCommonSystemValues(
operator,
{
config: clData.config!,
license: clData.license!,
currentTeamId: initialTeam?.id,
currentChannelId: initialChannel?.id,
},
);
if (systemModels) {
modelPromises.push(systemModels);
}
const models = await Promise.all(modelPromises);
await operator.batchRecords(models.flat());
const directChannels = chData!.channels!.filter((c) => c.type === General.DM_CHANNEL || c.type === General.GM_CHANNEL);
const directChannels = chData!.channels!.filter(isDMorGM);
const channelsToFetchProfiles = new Set<Channel>(directChannels);
if (channelsToFetchProfiles.size) {
const teammateDisplayNameSetting = getTeammateNameDisplaySetting(prefData.preferences || [], clData.config, clData.license);
@ -146,11 +133,13 @@ export const retryInitialTeamAndChannel = async (serverUrl: string) => {
};
export const retryInitialChannel = async (serverUrl: string, teamId: string) => {
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
if (!database) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
const {database} = operator;
try {
NetworkManager.getClient(serverUrl);
} catch (error) {
@ -198,29 +187,16 @@ export const retryInitialChannel = async (serverUrl: string, teamId: string) =>
return {error: true};
}
const modelPromises: Array<Promise<Model[]>> = [];
const {operator} = DatabaseManager.serverDatabases[serverUrl];
const models: Model[] = (await Promise.all([
...await prepareMyChannelsForTeam(operator, teamId, chData!.channels!, chData!.memberships!),
prepareCategories(operator, chData!.categories!),
prepareCategoryChannels(operator, chData!.categories!),
prepareCommonSystemValues(operator, {currentChannelId: initialChannel?.id}),
])).flat();
const channelModels = await prepareMyChannelsForTeam(operator, teamId, chData!.channels!, chData!.memberships!);
if (channelModels) {
modelPromises.push(...channelModels);
}
await operator.batchRecords(models);
const systemModels = prepareCommonSystemValues(
operator,
{
currentChannelId: initialChannel?.id,
},
);
if (systemModels) {
modelPromises.push(systemModels);
}
const models = await Promise.all(modelPromises);
await operator.batchRecords(models.flat());
const directChannels = chData!.channels!.filter((c) => c.type === General.DM_CHANNEL || c.type === General.GM_CHANNEL);
const directChannels = chData!.channels!.filter(isDMorGM);
const channelsToFetchProfiles = new Set<Channel>(directChannels);
if (channelsToFetchProfiles.size) {
const teammateDisplayNameSetting = getTeammateNameDisplaySetting(preferences || [], config, license);

View file

@ -67,7 +67,7 @@ export async function fetchRecentMentions(serverUrl: string): Promise<PostSearch
if (postsArray.length) {
const {authors} = await fetchPostAuthors(serverUrl, postsArray, true);
const {channels, channelMemberships} = await fetchMissingChannelsFromPosts(serverUrl, postsArray, true) as {channels: Channel[]; channelMemberships: ChannelMembership[]};
const {channels, channelMemberships} = await fetchMissingChannelsFromPosts(serverUrl, postsArray, true);
if (authors?.length) {
promises.push(
@ -79,8 +79,8 @@ export async function fetchRecentMentions(serverUrl: string): Promise<PostSearch
}
if (channels?.length && channelMemberships?.length) {
const channelPromises = prepareMissingChannelsForAllTeams(operator, channels, channelMemberships) as Array<Promise<Model[]>>;
if (channelPromises && channelPromises.length) {
const channelPromises = prepareMissingChannelsForAllTeams(operator, channels, channelMemberships);
if (channelPromises.length) {
promises.push(...channelPromises);
}
}

View file

@ -8,6 +8,7 @@ import {removeUserFromTeam as localRemoveUserFromTeam} from '@actions/local/team
import {Events} from '@constants';
import DatabaseManager from '@database/manager';
import NetworkManager from '@init/network_manager';
import {prepareCategories, prepareCategoryChannels} from '@queries/servers/categories';
import {prepareMyChannelsForTeam, getDefaultChannelForTeam} from '@queries/servers/channel';
import {prepareCommonSystemValues, getCurrentTeamId, getWebSocketLastDisconnected} from '@queries/servers/system';
import {addTeamToTeamHistory, prepareDeleteTeam, prepareMyTeams, getNthLastChannelFromTeam, queryTeamsById, syncTeamTable} from '@queries/servers/team';
@ -39,7 +40,7 @@ export const addUserToTeam = async (serverUrl: string, teamId: string, userId: s
if (!fetchOnly) {
fetchRolesIfNeeded(serverUrl, member.roles.split(' '));
const {channels, memberships: channelMembers} = await fetchMyChannelsForTeam(serverUrl, teamId, false, 0, true);
const {channels, memberships: channelMembers, categories} = await fetchMyChannelsForTeam(serverUrl, teamId, false, 0, true);
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (operator) {
const myTeams: MyTeam[] = [{
@ -47,17 +48,16 @@ export const addUserToTeam = async (serverUrl: string, teamId: string, userId: s
roles: member.roles,
}];
const models = await Promise.all([
const models: Model[] = (await Promise.all([
operator.handleMyTeam({myTeams, prepareRecordsOnly: true}),
operator.handleTeamMemberships({teamMemberships: [member], prepareRecordsOnly: true}),
prepareMyChannelsForTeam(operator, teamId, channels || [], channelMembers || []),
]);
...await prepareMyChannelsForTeam(operator, teamId, channels || [], channelMembers || []),
prepareCategories(operator, categories || []),
prepareCategoryChannels(operator, categories || []),
])).flat();
if (models.length) {
const flattenedModels = models.flat() as Model[];
if (flattenedModels?.length > 0) {
await operator.batchRecords(flattenedModels);
}
await operator.batchRecords(models);
}
if (await isTablet()) {
@ -92,15 +92,11 @@ export const fetchMyTeams = async (serverUrl: string, fetchOnly = false): Promis
if (!fetchOnly) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
const modelPromises: Array<Promise<Model[]>> = [];
if (operator) {
const removeTeamIds = memberships.filter((m) => m.delete_at > 0).map((m) => m.team_id);
const remainingTeams = teams.filter((t) => !removeTeamIds.includes(t.id));
const prepare = prepareMyTeams(operator, remainingTeams, memberships);
if (prepare) {
modelPromises.push(...prepare);
}
const modelPromises = prepareMyTeams(operator, remainingTeams, memberships);
if (removeTeamIds.length) {
if (removeTeamIds?.length) {
// Immediately delete myTeams so that the UI renders only teams the user is a member of.
@ -113,8 +109,8 @@ export const fetchMyTeams = async (serverUrl: string, fetchOnly = false): Promis
if (modelPromises.length) {
const models = await Promise.all(modelPromises);
const flattenedModels = models.flat() as Model[];
if (flattenedModels?.length > 0) {
const flattenedModels = models.flat();
if (flattenedModels.length > 0) {
await operator.batchRecords(flattenedModels);
}
}
@ -143,15 +139,11 @@ export const fetchMyTeam = async (serverUrl: string, teamId: string, fetchOnly =
]);
if (!fetchOnly) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
const modelPromises: Array<Promise<Model[]>> = [];
if (operator) {
const prepare = prepareMyTeams(operator, [team], [membership]);
if (prepare) {
modelPromises.push(...prepare);
}
const modelPromises = prepareMyTeams(operator, [team], [membership]);
if (modelPromises.length) {
const models = await Promise.all(modelPromises);
const flattenedModels = models.flat() as Model[];
const flattenedModels = models.flat();
if (flattenedModels?.length > 0) {
await operator.batchRecords(flattenedModels);
}

View file

@ -4,9 +4,18 @@
import {Model} from '@nozbe/watermelondb';
import {DeviceEventEmitter} from 'react-native';
import {removeCurrentUserFromChannel, setChannelDeleteAt, switchToChannel} from '@actions/local/channel';
import {fetchMyChannel} from '@actions/remote/channel';
import {
addChannelToDefaultCategory,
markChannelAsViewed,
removeCurrentUserFromChannel,
setChannelDeleteAt,
storeMyChannelsForTeam,
switchToChannel,
updateChannelInfoFromChannel,
updateMyChannelFromWebsocket} from '@actions/local/channel';
import {fetchMissingSidebarInfo, fetchMyChannel, fetchChannelStats, fetchChannelById} from '@actions/remote/channel';
import {fetchPostsForChannel} from '@actions/remote/post';
import {fetchRolesIfNeeded} from '@actions/remote/role';
import {fetchUsersByIds, updateUsersNoLongerVisible} from '@actions/remote/user';
import Events from '@constants/events';
import DatabaseManager from '@database/manager';
@ -18,42 +27,201 @@ import {getCurrentUser, getUserById} from '@queries/servers/user';
import {dismissAllModals, popToRoot} from '@screens/navigation';
import {isTablet} from '@utils/helpers';
export async function handleUserAddedToChannelEvent(serverUrl: string, msg: any) {
const database = DatabaseManager.serverDatabases[serverUrl];
if (!database) {
// Received when current user created a channel in a different client
export async function handleChannelCreatedEvent(serverUrl: string, msg: any) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return;
}
const currentUser = await getCurrentUser(database.database);
const {team_id: teamId, user_id: userId} = msg.data;
const {channel_id: channelId} = msg.broadcast;
const {team_id: teamId, channel_id: channelId} = msg.data;
try {
const models: Model[] = [];
const {channels, memberships} = await fetchMyChannel(serverUrl, teamId, channelId, true);
if (channels && memberships) {
const prepare = await prepareMyChannelsForTeam(operator, teamId, channels, memberships);
if (prepare.length) {
const prepareModels = await Promise.all(prepare);
const flattenedModels = prepareModels.flat();
if (flattenedModels?.length > 0) {
models.push(...flattenedModels);
}
const categoryModels = await addChannelToDefaultCategory(serverUrl, channels[0], true);
if (categoryModels.models?.length) {
models.push(...categoryModels.models);
}
}
}
operator.batchRecords(models);
} catch {
// do nothing
}
}
export async function handleChannelUnarchiveEvent(serverUrl: string, msg: any) {
try {
await setChannelDeleteAt(serverUrl, msg.data.channel_id, 0);
} catch {
// do nothing
}
}
export async function handleChannelConvertedEvent(serverUrl: string, msg: any) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return;
}
try {
const channelId = msg.data.channel_id;
const {channel} = await fetchChannelById(serverUrl, channelId);
if (channel) {
operator.handleChannel({channels: [channel], prepareRecordsOnly: false});
}
} catch {
// do nothing
}
}
export async function handleChannelUpdatedEvent(serverUrl: string, msg: any) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return;
}
const updatedChannel = JSON.parse(msg.data.channel);
try {
const models: Model[] = await operator.handleChannel({channels: [updatedChannel], prepareRecordsOnly: true});
const infoModel = await updateChannelInfoFromChannel(serverUrl, updatedChannel, true);
if (infoModel.model) {
models.push(infoModel.model);
}
operator.batchRecords(models);
} catch {
// Do nothing
}
}
export async function handleChannelViewedEvent(serverUrl: string, msg: any) {
try {
const {channel_id: channelId} = msg.data;
await markChannelAsViewed(serverUrl, channelId, false);
} catch {
// do nothing
}
}
// This event is triggered by changes in the notify props or in the roles.
export async function handleChannelMemberUpdatedEvent(serverUrl: string, msg: any) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return;
}
try {
const models: Model[] = [];
const updatedChannelMember: ChannelMembership = JSON.parse(msg.data.channelMember);
updatedChannelMember.id = updatedChannelMember.channel_id;
const myMemberModel = await updateMyChannelFromWebsocket(serverUrl, updatedChannelMember, true);
if (myMemberModel.model) {
models.push(myMemberModel.model);
}
models.push(...await operator.handleMyChannelSettings({
settings: [updatedChannelMember],
prepareRecordsOnly: true,
}));
const rolesRequest = await fetchRolesIfNeeded(serverUrl, updatedChannelMember.roles.split(','), true);
if (rolesRequest.roles?.length) {
models.push(...await operator.handleRole({roles: rolesRequest.roles, prepareRecordsOnly: true}));
}
operator.batchRecords(models);
} catch {
// do nothing
}
}
export async function handleDirectAddedEvent(serverUrl: string, msg: any) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return;
}
const {database} = operator;
try {
const {channel_id: channelId} = msg.broadcast;
const {channels, memberships} = await fetchMyChannel(serverUrl, '', channelId, true);
if (!channels || !memberships) {
return;
}
const user = await getCurrentUser(database);
if (!user) {
return;
}
const {directChannels} = await fetchMissingSidebarInfo(serverUrl, channels, user.locale, '', user.id, true);
if (!directChannels?.[0]) {
return;
}
const models: Model[] = [];
const channelModels = await storeMyChannelsForTeam(serverUrl, '', directChannels, memberships);
if (channelModels.models?.length) {
models.push(...channelModels.models);
}
const categoryModels = await addChannelToDefaultCategory(serverUrl, channels[0], false);
if (categoryModels.models?.length) {
models.push(...categoryModels.models);
}
if (models.length) {
operator.batchRecords(models);
}
} catch {
// do nothing
}
}
export async function handleUserAddedToChannelEvent(serverUrl: string, msg: any) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return;
}
const {database} = operator;
const currentUser = await getCurrentUser(database);
const userId = msg.data.user_id || msg.broadcast.userId;
const channelId = msg.data.channel_id || msg.broadcast.channel_id;
const {team_id: teamId} = msg.data;
const models: Model[] = [];
try {
const addedUser = getUserById(database.database, userId);
if (!addedUser) {
// TODO Potential improvement https://mattermost.atlassian.net/browse/MM-40581
const {users} = await fetchUsersByIds(serverUrl, [userId], true);
if (users) {
models.push(...await database.operator.handleUsers({users, prepareRecordsOnly: true}));
}
}
if (userId === currentUser?.id) {
const {channels, memberships} = await fetchMyChannel(serverUrl, teamId, channelId, true);
if (channels && memberships) {
const prepare = await prepareMyChannelsForTeam(database.operator, teamId, channels, memberships);
if (prepare) {
const prepare = await prepareMyChannelsForTeam(operator, teamId, channels, memberships);
if (prepare.length) {
const prepareModels = await Promise.all(prepare);
const flattenedModels = prepareModels.flat();
if (flattenedModels?.length > 0) {
models.push(...flattenedModels);
await operator.batchRecords(flattenedModels);
}
}
const categoriesModels = await addChannelToDefaultCategory(serverUrl, channels[0], true);
if (categoriesModels.models?.length) {
models.push(...categoriesModels.models);
}
}
const {posts, order, authors, actionType, previousPostId} = await fetchPostsForChannel(serverUrl, channelId, true);
if (posts?.length && order && actionType) {
models.push(...await database.operator.handlePosts({
models.push(...await operator.handlePosts({
actionType,
order,
posts,
@ -63,32 +231,43 @@ export async function handleUserAddedToChannelEvent(serverUrl: string, msg: any)
}
if (authors?.length) {
models.push(...await database.operator.handleUsers({users: authors, prepareRecordsOnly: true}));
models.push(...await operator.handleUsers({users: authors, prepareRecordsOnly: true}));
}
} else {
const channel = await getChannelById(database.database, channelId);
const addedUser = getUserById(database, userId);
if (!addedUser) {
// TODO Potential improvement https://mattermost.atlassian.net/browse/MM-40581
const {users} = await fetchUsersByIds(serverUrl, [userId], true);
if (users) {
models.push(...await operator.handleUsers({users, prepareRecordsOnly: true}));
}
}
const channel = await getChannelById(database, channelId);
if (channel) {
models.push(...await database.operator.handleChannelMembership({
models.push(...await operator.handleChannelMembership({
channelMemberships: [{channel_id: channelId, user_id: userId}],
prepareRecordsOnly: true,
}));
}
}
await operator.batchRecords(models);
await fetchChannelStats(serverUrl, channelId, false);
} catch {
// Do nothing
}
database.operator.batchRecords(models);
}
export async function handleUserRemovedFromChannelEvent(serverUrl: string, msg: any) {
const database = DatabaseManager.serverDatabases[serverUrl];
if (!database) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return;
}
const channel = await getCurrentChannel(database.database);
const user = await getCurrentUser(database.database);
const {database} = operator;
const channel = await getCurrentChannel(database);
const user = await getCurrentUser(database);
if (!user) {
return;
}
@ -121,7 +300,7 @@ export async function handleUserRemovedFromChannelEvent(serverUrl: string, msg:
await popToRoot();
if (await isTablet()) {
const channelToJumpTo = await getNthLastChannelFromTeam(database.database, channel?.teamId);
const channelToJumpTo = await getNthLastChannelFromTeam(database, channel?.teamId);
if (channelToJumpTo) {
const {models: switchChannelModels} = await switchToChannel(serverUrl, channelToJumpTo, '', true);
if (switchChannelModels) {
@ -129,7 +308,7 @@ export async function handleUserRemovedFromChannelEvent(serverUrl: string, msg:
}
} // TODO else jump to "join a channel" screen https://mattermost.atlassian.net/browse/MM-41051
} else {
const currentChannelModels = await prepareCommonSystemValues(database.operator, {currentChannelId: ''});
const currentChannelModels = await prepareCommonSystemValues(operator, {currentChannelId: ''});
if (currentChannelModels?.length) {
models.push(...currentChannelModels);
}
@ -137,30 +316,33 @@ export async function handleUserRemovedFromChannelEvent(serverUrl: string, msg:
}
}
} else {
const {models: deleteMemberModels} = await deleteChannelMembership(database.operator, userId, channelId, true);
const {models: deleteMemberModels} = await deleteChannelMembership(operator, userId, channelId, true);
if (deleteMemberModels) {
models.push(...deleteMemberModels);
}
}
database.operator.batchRecords(models);
await fetchChannelStats(serverUrl, channelId, false);
operator.batchRecords(models);
}
export async function handleChannelDeletedEvent(serverUrl: string, msg: WebSocketMessage) {
const database = DatabaseManager.serverDatabases[serverUrl];
if (!database) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return;
}
const currentChannel = await getCurrentChannel(database.database);
const user = await getCurrentUser(database.database);
const {database} = operator;
const currentChannel = await getCurrentChannel(database);
const user = await getCurrentUser(database);
if (!user) {
return;
}
const {channel_id: channelId, delete_at: deleteAt} = msg.data;
const config = await getConfig(database.database);
const config = await getConfig(database);
await setChannelDeleteAt(serverUrl, channelId, deleteAt);
@ -180,12 +362,12 @@ export async function handleChannelDeletedEvent(serverUrl: string, msg: WebSocke
await popToRoot();
if (await isTablet()) {
const channelToJumpTo = await getNthLastChannelFromTeam(database.database, currentChannel?.teamId);
const channelToJumpTo = await getNthLastChannelFromTeam(database, currentChannel?.teamId);
if (channelToJumpTo) {
switchToChannel(serverUrl, channelToJumpTo);
} // TODO else jump to "join a channel" screen
} else {
setCurrentChannelId(database.operator, '');
setCurrentChannelId(operator, '');
}
}
}

View file

@ -8,17 +8,26 @@ import {fetchRoles} from '@actions/remote/role';
import {fetchConfigAndLicense} from '@actions/remote/systems';
import {fetchAllTeams, fetchTeamsChannelsAndUnreadPosts} from '@actions/remote/team';
import {fetchStatusByIds, updateAllUsersSince} from '@actions/remote/user';
import {General, WebsocketEvents} from '@constants';
import {WebsocketEvents} from '@constants';
import {SYSTEM_IDENTIFIERS} from '@constants/database';
import DatabaseManager from '@database/manager';
import {getTeammateNameDisplaySetting} from '@helpers/api/preference';
import {queryChannelsById, getDefaultChannelForTeam} from '@queries/servers/channel';
import {prepareModels} from '@queries/servers/entry';
import {getCommonSystemValues, getConfig, getCurrentChannelId, getWebSocketLastDisconnected, resetWebSocketLastDisconnected, setCurrentTeamAndChannelId} from '@queries/servers/system';
import {isDMorGM} from '@utils/channel';
import {isTablet} from '@utils/helpers';
import {handleCategoryCreatedEvent, handleCategoryDeletedEvent, handleCategoryOrderUpdatedEvent, handleCategoryUpdatedEvent} from './category';
import {handleChannelDeletedEvent, handleUserAddedToChannelEvent, handleUserRemovedFromChannelEvent} from './channel';
import {handleChannelConvertedEvent, handleChannelCreatedEvent,
handleChannelDeletedEvent,
handleChannelMemberUpdatedEvent,
handleChannelUnarchiveEvent,
handleChannelUpdatedEvent,
handleChannelViewedEvent,
handleDirectAddedEvent,
handleUserAddedToChannelEvent,
handleUserRemovedFromChannelEvent} from './channel';
import {handleNewPostEvent, handlePostDeleted, handlePostEdited, handlePostUnread} from './posts';
import {handlePreferenceChangedEvent, handlePreferencesChangedEvent, handlePreferencesDeletedEvent} from './preferences';
import {handleAddCustomEmoji, handleReactionRemovedFromPostEvent, handleReactionAddedToPostEvent} from './reactions';
@ -106,7 +115,7 @@ async function doReconnect(serverUrl: string) {
const teammateDisplayNameSetting = getTeammateNameDisplaySetting(prefData.preferences || [], config, license);
let directChannels: Channel[];
[chData.channels, directChannels] = chData.channels.reduce(([others, direct], c: Channel) => {
if (c.type === General.DM_CHANNEL || c.type === General.GM_CHANNEL) {
if (isDMorGM(c)) {
direct.push(c);
} else {
others.push(c);
@ -248,40 +257,40 @@ export async function handleEvent(serverUrl: string, msg: WebSocketMessage) {
break;
case WebsocketEvents.CHANNEL_CREATED:
handleChannelCreatedEvent(serverUrl, msg);
break;
// return dispatch(handleChannelCreatedEvent(msg));
case WebsocketEvents.CHANNEL_DELETED:
handleChannelDeletedEvent(serverUrl, msg);
break;
case WebsocketEvents.CHANNEL_UNARCHIVED:
handleChannelUnarchiveEvent(serverUrl, msg);
break;
// return dispatch(handleChannelUnarchiveEvent(msg));
case WebsocketEvents.CHANNEL_UPDATED:
handleChannelUpdatedEvent(serverUrl, msg);
break;
// return dispatch(handleChannelUpdatedEvent(msg));
case WebsocketEvents.CHANNEL_CONVERTED:
handleChannelConvertedEvent(serverUrl, msg);
break;
// return dispatch(handleChannelConvertedEvent(msg));
case WebsocketEvents.CHANNEL_VIEWED:
handleChannelViewedEvent(serverUrl, msg);
break;
// return dispatch(handleChannelViewedEvent(msg));
case WebsocketEvents.CHANNEL_MEMBER_UPDATED:
handleChannelMemberUpdatedEvent(serverUrl, msg);
break;
// return dispatch(handleChannelMemberUpdatedEvent(msg));
case WebsocketEvents.CHANNEL_SCHEME_UPDATED:
// Do nothing, handled by CHANNEL_UPDATED due to changes in the channel scheme.
break;
// return dispatch(handleChannelSchemeUpdatedEvent(msg));
case WebsocketEvents.DIRECT_ADDED:
handleDirectAddedEvent(serverUrl, msg);
break;
// return dispatch(handleDirectAddedEvent(msg));
case WebsocketEvents.PREFERENCE_CHANGED:
handlePreferenceChangedEvent(serverUrl, msg);
break;

View file

@ -80,7 +80,11 @@ export async function handleTeamMemberRoleUpdatedEvent(serverUrl: string, msg: W
}
try {
const member = JSON.parse(msg.data.member);
const member: TeamMembership = JSON.parse(msg.data.member);
if (member.delete_at > 0) {
return;
}
const currentUserId = await getCurrentUserId(operator.database);
if (currentUserId !== member.user_id) {

View file

@ -112,10 +112,7 @@ export async function handleUserAddedToTeamEvent(serverUrl: string, msg: WebSock
}
if (teams && teamMemberships) {
const preparedTeamModels = prepareMyTeams(database.operator, teams, teamMemberships);
if (preparedTeamModels) {
modelPromises.push(...preparedTeamModels);
}
modelPromises.push(...prepareMyTeams(database.operator, teams, teamMemberships));
}
if (modelPromises.length) {

View file

@ -8,8 +8,8 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context';
import ChannelIcon from '@components/channel_icon';
import {BotTag, GuestTag} from '@components/tag';
import TouchableWithFeedback from '@components/touchable_with_feedback';
import {General} from '@constants';
import {useTheme} from '@context/theme';
import {isDMorGM} from '@utils/channel';
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
import type ChannelModel from '@typings/database/models/servers/channel';
@ -59,7 +59,7 @@ const ChannelMentionItem = ({
const theme = useTheme();
const completeMention = () => {
if (channel.type === General.DM_CHANNEL || channel.type === General.GM_CHANNEL) {
if (isDMorGM(channel)) {
onPress('@' + displayName?.replace(/ /g, ''));
} else {
onPress(channel.name);
@ -78,7 +78,7 @@ const ChannelMentionItem = ({
const isArchived = ('delete_at' in channel ? channel.delete_at : channel.deleteAt) > 0;
if (channel.type === General.DM_CHANNEL || channel.type === General.GM_CHANNEL) {
if (isDMorGM(channel)) {
if (!displayName) {
return null;
}

View file

@ -4,6 +4,7 @@
import React, {useCallback, useMemo} from 'react';
import {FlatList} from 'react-native';
import {DMS_CATEGORY} from '@constants/categories';
import ChannelModel from '@typings/database/models/servers/channel';
import ChannelListItem from './channel';
@ -29,7 +30,7 @@ const CategoryBody = ({currentChannelId, sortedChannels, category, hiddenChannel
filteredChannels = sortedChannels.filter((item) => item && !hiddenChannelIds.includes(item.id));
}
if (category.type === 'direct_messages' && limit > 0) {
if (category.type === DMS_CATEGORY && limit > 0) {
return filteredChannels.slice(0, limit - 1);
}
return filteredChannels;

View file

@ -8,6 +8,7 @@ import {combineLatest, of as of$} from 'rxjs';
import {map, switchMap, concatAll} from 'rxjs/operators';
import {General, Preferences} from '@constants';
import {DMS_CATEGORY} from '@constants/categories';
import {queryChannelsByNames, queryMyChannelSettingsByIds} from '@queries/servers/channel';
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
import {WithDatabaseArgs} from '@typings/database/database';
@ -114,7 +115,7 @@ const enhance = withObservables(['category'], ({category, locale, database, curr
observe().pipe(switchMap(mapPrefName));
let limit = of$(Preferences.CHANNEL_SIDEBAR_LIMIT_DMS_DEFAULT);
if (category.type === 'direct_messages') {
if (category.type === DMS_CATEGORY) {
limit = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_SIDEBAR_SETTINGS, Preferences.CHANNEL_SIDEBAR_LIMIT_DMS).observe().pipe(
switchMap((val) => {
return val[0] ? of$(parseInt(val[0].value, 10)) : of$(Preferences.CHANNEL_SIDEBAR_LIMIT_DMS_DEFAULT);

View file

@ -57,6 +57,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
lineHeight: 12,
marginBottom: 8,
marginTop: 2,
height: 13,
},
titleContainer: {
alignItems: Platform.select({android: 'flex-start', ios: 'center'}),

View file

@ -0,0 +1,10 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
export const CHANNELS_CATEGORY = 'channels';
export const DMS_CATEGORY = 'direct_messages';
export default {
CHANNELS_CATEGORY,
DMS_CATEGORY,
};

View file

@ -3,6 +3,7 @@
import ActionType from './action_type';
import Apps from './apps';
import Categories from './categories';
import Channel from './channel';
import Config from './config';
import {CustomStatusDuration} from './custom_status';
@ -31,6 +32,7 @@ import WebsocketEvents from './websocket';
export {
ActionType,
Apps,
Categories,
Config,
CustomStatusDuration,
Channel,

View file

@ -113,7 +113,7 @@ const ChannelHandler = (superclass: any) => class extends superclass {
}
const createOrUpdateRawValues = getUniqueRawsBy({
raws: channelInfos,
raws: channelInfos as ChannelInfo[],
key: 'id',
});

View file

@ -109,17 +109,17 @@ export const transformMyChannelSettingsRecord = ({action, database, value}: Tran
* @returns {Promise<ChannelInfoModel>}
*/
export const transformChannelInfoRecord = ({action, database, value}: TransformerArgs): Promise<ChannelInfoModel> => {
const raw = value.raw as ChannelInfo;
const raw = value.raw as Partial<ChannelInfo>;
const record = value.record as ChannelInfoModel;
const isCreateAction = action === OperationType.CREATE;
const fieldsMapper = (channelInfo: ChannelInfoModel) => {
channelInfo._raw.id = isCreateAction ? (raw.id || channelInfo.id) : record.id;
channelInfo.guestCount = raw.guest_count;
channelInfo.header = raw.header;
channelInfo.memberCount = raw.member_count;
channelInfo.pinnedPostCount = raw.pinned_post_count;
channelInfo.purpose = raw.purpose;
channelInfo.guestCount = raw.guest_count ?? channelInfo.guestCount ?? 0;
channelInfo.header = raw.header ?? channelInfo.header ?? '';
channelInfo.memberCount = raw.member_count ?? channelInfo.memberCount ?? 0;
channelInfo.pinnedPostCount = raw.pinned_post_count ?? channelInfo.pinnedPostCount ?? 0;
channelInfo.purpose = raw.purpose ?? channelInfo.purpose ?? '';
};
return prepareBaseRecord({

View file

@ -4,9 +4,11 @@
import {Database, Model, Q, Query} from '@nozbe/watermelondb';
import {MM_TABLES} from '@constants/database';
import {makeCategoryChannelId} from '@utils/categories';
import type ServerDataOperator from '@database/operator/server_data_operator';
import type CategoryModel from '@typings/database/models/servers/category';
import type CategoryChannelModel from '@typings/database/models/servers/category_channel';
const {SERVER: {CATEGORY}} = MM_TABLES;
@ -31,17 +33,17 @@ export const prepareCategories = (operator: ServerDataOperator, categories: Cate
return operator.handleCategories({categories, prepareRecordsOnly: true});
};
export const prepareCategoryChannels = (
export async function prepareCategoryChannels(
operator: ServerDataOperator,
categories: CategoryWithChannels[],
) => {
): Promise<CategoryChannelModel[]> {
try {
const categoryChannels: CategoryChannel[] = [];
categories.forEach((category) => {
category.channel_ids.forEach((channelId, index) => {
categoryChannels.push({
id: `${category.team_id}_${channelId}`,
id: makeCategoryChannelId(category.team_id, channelId),
category_id: category.id,
channel_id: channelId,
sort_order: index,
@ -53,21 +55,21 @@ export const prepareCategoryChannels = (
return operator.handleCategoryChannels({categoryChannels, prepareRecordsOnly: true});
}
return undefined;
return [];
} catch (e) {
return undefined;
return [];
}
};
}
export const prepareDeleteCategory = async (category: CategoryModel): Promise<Model[]> => {
const preparedModels: Model[] = [category.prepareDestroyPermanently()];
const associatedChildren: Array<Query<Model>|undefined> = [
const associatedChildren: Array<Query<Model>> = [
category.categoryChannels,
];
await Promise.all(associatedChildren.map(async (children) => {
const models = await children?.fetch();
models?.forEach((model) => preparedModels.push(model.prepareDestroyPermanently()));
const models = await children.fetch();
models.forEach((model) => preparedModels.push(model.prepareDestroyPermanently()));
}));
return preparedModels;

View file

@ -22,7 +22,7 @@ import type UserModel from '@typings/database/models/servers/user';
const {SERVER: {CHANNEL, MY_CHANNEL, CHANNEL_MEMBERSHIP, MY_CHANNEL_SETTINGS, CHANNEL_INFO, USER}} = MM_TABLES;
export function prepareMissingChannelsForAllTeams(operator: ServerDataOperator, channels: Channel[], channelMembers: ChannelMembership[]): Array<Promise<Model[]>> | undefined {
export function prepareMissingChannelsForAllTeams(operator: ServerDataOperator, channels: Channel[], channelMembers: ChannelMembership[]): Array<Promise<Model[]>> {
const channelInfos: ChannelInfo[] = [];
const memberships = channelMembers.map((cm) => ({...cm, id: cm.channel_id}));
@ -38,19 +38,19 @@ export function prepareMissingChannelsForAllTeams(operator: ServerDataOperator,
}
try {
const channelRecords: Promise<Model[]> = operator.handleChannel({channels, prepareRecordsOnly: true});
const channelInfoRecords: Promise<Model[]> = operator.handleChannelInfo({channelInfos, prepareRecordsOnly: true});
const membershipRecords: Promise<Model[]> = operator.handleChannelMembership({channelMemberships: memberships, prepareRecordsOnly: true});
const myChannelRecords: Promise<Model[]> = operator.handleMyChannel({channels, myChannels: memberships, prepareRecordsOnly: true});
const myChannelSettingsRecords: Promise<Model[]> = operator.handleMyChannelSettings({settings: memberships, prepareRecordsOnly: true});
const channelRecords = operator.handleChannel({channels, prepareRecordsOnly: true});
const channelInfoRecords = operator.handleChannelInfo({channelInfos, prepareRecordsOnly: true});
const membershipRecords = operator.handleChannelMembership({channelMemberships: memberships, prepareRecordsOnly: true});
const myChannelRecords = operator.handleMyChannel({channels, myChannels: memberships, prepareRecordsOnly: true});
const myChannelSettingsRecords = operator.handleMyChannelSettings({settings: memberships, prepareRecordsOnly: true});
return [channelRecords, channelInfoRecords, membershipRecords, myChannelRecords, myChannelSettingsRecords];
} catch {
return undefined;
return [];
}
}
export const prepareMyChannelsForTeam = async (operator: ServerDataOperator, teamId: string, channels: Channel[], channelMembers: ChannelMembership[]) => {
export const prepareMyChannelsForTeam = async (operator: ServerDataOperator, teamId: string, channels: Channel[], channelMembers: ChannelMembership[]): Promise<Array<Promise<Model[]>>> => {
const allChannelsForTeam = await queryAllChannelsForTeam(operator.database, teamId).fetch();
const channelInfos: ChannelInfo[] = [];
const memberships = channelMembers.map((cm) => ({...cm, id: cm.channel_id}));
@ -92,7 +92,7 @@ export const prepareMyChannelsForTeam = async (operator: ServerDataOperator, tea
return [channelRecords, channelInfoRecords, membershipRecords, myChannelRecords, myChannelSettingsRecords];
} catch {
return undefined;
return [];
}
};
@ -292,7 +292,7 @@ export const queryUserChannelsByTypes = (database: Database, userId: string, cha
};
export const queryTeamDefaultChannel = (database: Database, teamId: string) => {
return database.get<ChannelModel>(MM_TABLES.SERVER.CHANNEL).query(
return database.get<ChannelModel>(CHANNEL).query(
Q.where('team_id', teamId),
Q.where('name', General.DEFAULT_CHANNEL),
);

View file

@ -43,44 +43,25 @@ export const prepareModels = async ({operator, initialTeamId, removeTeams, remov
});
}
if (teamData?.teams?.length) {
const teamModels = prepareMyTeams(operator, teamData.teams, teamData.memberships || []);
if (teamModels) {
modelPromises.push(...teamModels);
}
if (teamData?.teams?.length && teamData.memberships?.length) {
modelPromises.push(...prepareMyTeams(operator, teamData.teams, teamData.memberships));
}
if (chData?.categories?.length) {
const categoryModels = prepareCategories(operator, chData.categories);
if (categoryModels) {
modelPromises.push(categoryModels);
}
const categoryChannelModels = prepareCategoryChannels(operator, chData.categories);
if (categoryChannelModels) {
modelPromises.push(categoryChannelModels);
}
modelPromises.push(prepareCategories(operator, chData.categories));
modelPromises.push(prepareCategoryChannels(operator, chData.categories));
}
if (initialTeamId && chData?.channels?.length) {
const channelModels = await prepareMyChannelsForTeam(operator, initialTeamId, chData.channels, chData.memberships || []);
if (channelModels) {
modelPromises.push(...channelModels);
}
if (initialTeamId && chData?.channels?.length && chData.memberships?.length) {
modelPromises.push(...await prepareMyChannelsForTeam(operator, initialTeamId, chData.channels, chData.memberships));
}
if (prefData?.preferences?.length) {
const prefModel = prepareMyPreferences(operator, prefData.preferences, true);
if (prefModel) {
modelPromises.push(prefModel);
}
modelPromises.push(prepareMyPreferences(operator, prefData.preferences, true));
}
if (meData?.user) {
const userModels = prepareUsers(operator, [meData.user]);
if (userModels) {
modelPromises.push(userModels);
}
modelPromises.push(prepareUsers(operator, [meData.user]));
}
return modelPromises;

View file

@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Database, Model, Q, Query, Relation} from '@nozbe/watermelondb';
import {Database, Model, Q, Query} from '@nozbe/watermelondb';
import {of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
@ -15,16 +15,12 @@ const {SERVER: {POST, POSTS_IN_CHANNEL, POSTS_IN_THREAD}} = MM_TABLES;
export const prepareDeletePost = async (post: PostModel): Promise<Model[]> => {
const preparedModels: Model[] = [post.prepareDestroyPermanently()];
const relations: Array<Relation<Model> | Query<Model>> = [post.drafts, post.postsInThread];
const relations: Array<Query<Model>> = [post.drafts, post.postsInThread];
for await (const relation of relations) {
try {
const model = await relation?.fetch();
const model = await relation.fetch();
if (model) {
if (Array.isArray(model)) {
model.forEach((m) => preparedModels.push(m.prepareDestroyPermanently()));
} else {
preparedModels.push(model.prepareDestroyPermanently());
}
model.forEach((m) => preparedModels.push(m.prepareDestroyPermanently()));
}
} catch {
// Record not found, do nothing

View file

@ -14,7 +14,7 @@ import type PreferenceModel from '@typings/database/models/servers/preference';
const {SERVER: {PREFERENCE}} = MM_TABLES;
export const prepareMyPreferences = (operator: ServerDataOperator, preferences: PreferenceType[], sync = false) => {
export async function prepareMyPreferences(operator: ServerDataOperator, preferences: PreferenceType[], sync = false): Promise<PreferenceModel[]> {
try {
return operator.handlePreferences({
prepareRecordsOnly: true,
@ -22,9 +22,9 @@ export const prepareMyPreferences = (operator: ServerDataOperator, preferences:
sync,
});
} catch {
return undefined;
return [];
}
};
}
export const queryPreferencesByCategoryAndName = (database: Database, category: string, name?: string, value?: string) => {
const clauses = [Q.where('category', category)];

View file

@ -6,6 +6,7 @@ import {of as of$, combineLatest} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import {Database as DatabaseConstants, General, Permissions} from '@constants';
import {isDMorGM} from '@utils/channel';
import {hasPermission} from '@utils/role';
import type ChannelModel from '@typings/database/models/servers/channel';
@ -71,8 +72,7 @@ export function observePermissionForPost(post: PostModel, user: UserModel, permi
export function observeCanManageChannelMembers(post: PostModel, user: UserModel) {
return post.channel.observe().pipe((switchMap((c) => {
const directTypes: ChannelType[] = [General.DM_CHANNEL, General.GM_CHANNEL];
if (!c || c.deleteAt !== 0 || directTypes.includes(c.type) || c.name === General.DEFAULT_CHANNEL) {
if (!c || c.deleteAt !== 0 || isDMorGM(c) || c.name === General.DEFAULT_CHANNEL) {
return of$(false);
}

View file

@ -262,8 +262,8 @@ export const patchTeamHistory = (operator: ServerDataOperator, value: string[],
prepareRecordsOnly});
};
export const prepareCommonSystemValues = (
operator: ServerDataOperator, values: PrepareCommonSystemValuesArgs) => {
export async function prepareCommonSystemValues(
operator: ServerDataOperator, values: PrepareCommonSystemValuesArgs): Promise<SystemModel[]> {
try {
const {config, currentChannelId, currentTeamId, currentUserId, license} = values;
const systems: IdValue[] = [];
@ -307,9 +307,9 @@ export const prepareCommonSystemValues = (
prepareRecordsOnly: true,
});
} catch {
return undefined;
return [];
}
};
}
export const setCurrentChannelId = async (operator: ServerDataOperator, channelId: string) => {
try {

View file

@ -17,8 +17,6 @@ import {patchTeamHistory, getConfig, getTeamHistory, observeCurrentTeamId} from
import {getCurrentUser} from './user';
import type ServerDataOperator from '@database/operator/server_data_operator';
import type CategoryModel from '@typings/database/models/servers/category';
import type ChannelModel from '@typings/database/models/servers/channel';
import type MyTeamModel from '@typings/database/models/servers/my_team';
import type TeamModel from '@typings/database/models/servers/team';
import type TeamChannelHistoryModel from '@typings/database/models/servers/team_channel_history';
@ -183,7 +181,7 @@ export const getDefaultTeamId = async (database: Database) => {
return defaultTeam?.id;
};
export const prepareMyTeams = (operator: ServerDataOperator, teams: Team[], memberships: TeamMembership[]) => {
export function prepareMyTeams(operator: ServerDataOperator, teams: Team[], memberships: TeamMembership[]): Array<Promise<Model[]>> {
try {
const teamRecords = operator.handleTeam({prepareRecordsOnly: true, teams});
const teamMemberships = memberships.filter((m) => teams.find((t) => t.id === m.team_id) && m.delete_at === 0);
@ -198,9 +196,9 @@ export const prepareMyTeams = (operator: ServerDataOperator, teams: Team[], memb
return [teamRecords, teamMembershipRecords, myTeamRecords];
} catch {
return undefined;
return [];
}
};
}
export const deleteMyTeams = async (operator: ServerDataOperator, myTeams: MyTeamModel[]) => {
try {
@ -247,8 +245,8 @@ export const prepareDeleteTeam = async (team: TeamModel): Promise<Model[]> => {
}
}));
const categories = await team.categories?.fetch() as CategoryModel[] | undefined;
if (categories?.length) {
const categories = await team.categories?.fetch();
if (categories.length) {
for await (const category of categories) {
try {
const preparedCategory = await prepareDeleteCategory(category);
@ -259,8 +257,8 @@ export const prepareDeleteTeam = async (team: TeamModel): Promise<Model[]> => {
}
}
const channels = await team.channels?.fetch() as ChannelModel[] | undefined;
if (channels?.length) {
const channels = await team.channels?.fetch();
if (channels.length) {
for await (const channel of channels) {
try {
const preparedChannel = await prepareDeleteChannel(channel);

View file

@ -58,17 +58,17 @@ export const queryUsersByUsername = (database: Database, usernames: string[]) =>
return database.get<UserModel>(USER).query(Q.where('username', Q.oneOf(usernames)));
};
export const prepareUsers = (operator: ServerDataOperator, users: UserProfile[]) => {
export async function prepareUsers(operator: ServerDataOperator, users: UserProfile[]): Promise<UserModel[]> {
try {
if (users.length) {
return operator.handleUsers({users, prepareRecordsOnly: true});
}
return undefined;
return [];
} catch {
return undefined;
return [];
}
};
}
export const observeTeammateNameDisplay = (database: Database) => {
const config = observeConfig(database);

10
app/utils/categories.ts Normal file
View file

@ -0,0 +1,10 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
export function makeCategoryId(name: string, userId: string, teamId: string) {
return `${name}_${userId}_${teamId}`;
}
export function makeCategoryChannelId(teamId: string, channelId: string) {
return `${teamId}_${channelId}`;
}

View file

@ -23,6 +23,11 @@ export function getDirectChannelName(id: string, otherId: string): string {
return handle;
}
export function isDMorGM(channel: Channel | ChannelModel): boolean {
const directTypes: string[] = [General.GM_CHANNEL, General.DM_CHANNEL];
return directTypes.includes(channel.type);
}
export function selectDefaultChannelForTeam<T extends Channel|ChannelModel>(channels: T[], memberships: ChannelMembership[], teamId: string, roles?: Role[], locale = DEFAULT_LOCALE) {
let channel: T|undefined;
let canIJoinPublicChannelsInTeam = false;

View file

@ -204,7 +204,7 @@ export type HandleMyChannelArgs = PrepareOnly & {
};
export type HandleChannelInfoArgs = PrepareOnly &{
channelInfos: ChannelInfo[];
channelInfos: Array<Partial<ChannelInfo>>;
};
export type HandleMyChannelSettingsArgs = PrepareOnly & {