From 137ba9d19109e44194490ca6648f1b616d25a69f Mon Sep 17 00:00:00 2001 From: Jason Frerich Date: Mon, 4 Apr 2022 13:25:42 -0500 Subject: [PATCH] [Gekidou MM-39728] - Websocket Events - Channels (#6038) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- app/actions/local/channel.ts | 105 ++++++- app/actions/remote/channel.ts | 106 +++---- app/actions/remote/entry/common.ts | 5 +- app/actions/remote/post.ts | 10 +- app/actions/remote/retry.ts | 98 +++---- app/actions/remote/search.ts | 6 +- app/actions/remote/team.ts | 36 +-- app/actions/websocket/channel.ts | 264 +++++++++++++++--- app/actions/websocket/index.ts | 31 +- app/actions/websocket/roles.ts | 6 +- app/actions/websocket/teams.ts | 5 +- .../channel_mention_item.tsx | 6 +- .../categories/body/category_body.tsx | 3 +- .../channel_list/categories/body/index.ts | 3 +- app/components/navigation_header/header.tsx | 1 + app/constants/categories.ts | 10 + app/constants/index.ts | 2 + .../server_data_operator/handlers/channel.ts | 2 +- .../transformers/channel.ts | 12 +- app/queries/servers/categories.ts | 20 +- app/queries/servers/channel.ts | 20 +- app/queries/servers/entry.ts | 35 +-- app/queries/servers/post.ts | 12 +- app/queries/servers/preference.ts | 6 +- app/queries/servers/role.ts | 4 +- app/queries/servers/system.ts | 8 +- app/queries/servers/team.ts | 16 +- app/queries/servers/user.ts | 8 +- app/utils/categories.ts | 10 + app/utils/channel/index.ts | 5 + types/database/database.d.ts | 2 +- 31 files changed, 558 insertions(+), 299 deletions(-) create mode 100644 app/constants/categories.ts create mode 100644 app/utils/categories.ts diff --git a/app/actions/local/channel.ts b/app/actions/local/channel.ts index acb7a4096..e026c3d62 100644 --- a/app/actions/local/channel.ts +++ b/app/actions/local/channel.ts @@ -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> = []; - const prepare = await prepareMyChannelsForTeam(operator, teamId, channels, memberships); - if (prepare) { - modelPromises.push(...prepare); - } + const modelPromises: Array> = [ + ...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}; +} diff --git a/app/actions/remote/channel.ts b/app/actions/remote/channel.ts index 357e47a04..027432047 100644 --- a/app/actions/remote/channel.ts +++ b/app/actions/remote/channel.ts @@ -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> = [{ 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(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 => { + 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> = []; - const prepare = await prepareMyChannelsForTeam(operator, teamId, [channel], [member]); - if (prepare) { - modelPromises.push(...prepare); - } + const modelPromises: Array> = 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> = []; 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> = []; - 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> = [...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'); diff --git a/app/actions/remote/entry/common.ts b/app/actions/remote/entry/common.ts index 3fab20a2f..466422217 100644 --- a/app/actions/remote/entry/common.ts +++ b/app/actions/remote/entry/common.ts @@ -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(directChannels); if (channelsToFetchProfiles.size) { const teammateDisplayNameSetting = getTeammateNameDisplaySetting(preferences || [], config, license); diff --git a/app/actions/remote/post.ts b/app/actions/remote/post.ts index c09c018ad..ad702dcaa 100644 --- a/app/actions/remote/post.ts +++ b/app/actions/remote/post.ts @@ -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>; - 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> = []; 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>; - if (channelPromises && channelPromises.length) { + const channelPromises = prepareMissingChannelsForAllTeams(operator, channels, channelMemberships); + if (channelPromises.length) { promises.push(...channelPromises); } } diff --git a/app/actions/remote/retry.ts b/app/actions/remote/retry.ts index 7a4ee0bd6..b5d65e5d4 100644 --- a/app/actions/remote/retry.ts +++ b/app/actions/remote/retry.ts @@ -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> = []; - 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(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> = []; - 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(directChannels); if (channelsToFetchProfiles.size) { const teammateDisplayNameSetting = getTeammateNameDisplaySetting(preferences || [], config, license); diff --git a/app/actions/remote/search.ts b/app/actions/remote/search.ts index 1c5cc96b6..f43968fac 100644 --- a/app/actions/remote/search.ts +++ b/app/actions/remote/search.ts @@ -67,7 +67,7 @@ export async function fetchRecentMentions(serverUrl: string): Promise>; - if (channelPromises && channelPromises.length) { + const channelPromises = prepareMissingChannelsForAllTeams(operator, channels, channelMemberships); + if (channelPromises.length) { promises.push(...channelPromises); } } diff --git a/app/actions/remote/team.ts b/app/actions/remote/team.ts index 4e017563a..b7dbff798 100644 --- a/app/actions/remote/team.ts +++ b/app/actions/remote/team.ts @@ -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> = []; + 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> = []; 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); } diff --git a/app/actions/websocket/channel.ts b/app/actions/websocket/channel.ts index 7b9729440..14cb245d4 100644 --- a/app/actions/websocket/channel.ts +++ b/app/actions/websocket/channel.ts @@ -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, ''); } } } diff --git a/app/actions/websocket/index.ts b/app/actions/websocket/index.ts index 8ddf9d4a5..f28e1ab8b 100644 --- a/app/actions/websocket/index.ts +++ b/app/actions/websocket/index.ts @@ -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; diff --git a/app/actions/websocket/roles.ts b/app/actions/websocket/roles.ts index 3a20a726d..40a1f84e2 100644 --- a/app/actions/websocket/roles.ts +++ b/app/actions/websocket/roles.ts @@ -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) { diff --git a/app/actions/websocket/teams.ts b/app/actions/websocket/teams.ts index c338ad0de..7c5cd2b5d 100644 --- a/app/actions/websocket/teams.ts +++ b/app/actions/websocket/teams.ts @@ -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) { diff --git a/app/components/autocomplete/channel_mention_item/channel_mention_item.tsx b/app/components/autocomplete/channel_mention_item/channel_mention_item.tsx index d08e33917..1684d8ea5 100644 --- a/app/components/autocomplete/channel_mention_item/channel_mention_item.tsx +++ b/app/components/autocomplete/channel_mention_item/channel_mention_item.tsx @@ -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; } diff --git a/app/components/channel_list/categories/body/category_body.tsx b/app/components/channel_list/categories/body/category_body.tsx index ac7fb2f73..e3c42e7e1 100644 --- a/app/components/channel_list/categories/body/category_body.tsx +++ b/app/components/channel_list/categories/body/category_body.tsx @@ -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; diff --git a/app/components/channel_list/categories/body/index.ts b/app/components/channel_list/categories/body/index.ts index dc94303f3..155954a37 100644 --- a/app/components/channel_list/categories/body/index.ts +++ b/app/components/channel_list/categories/body/index.ts @@ -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); diff --git a/app/components/navigation_header/header.tsx b/app/components/navigation_header/header.tsx index 8b18a34d8..3bbfadb9a 100644 --- a/app/components/navigation_header/header.tsx +++ b/app/components/navigation_header/header.tsx @@ -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'}), diff --git a/app/constants/categories.ts b/app/constants/categories.ts new file mode 100644 index 000000000..c0126c7e1 --- /dev/null +++ b/app/constants/categories.ts @@ -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, +}; diff --git a/app/constants/index.ts b/app/constants/index.ts index 0cb2fede4..1f0699148 100644 --- a/app/constants/index.ts +++ b/app/constants/index.ts @@ -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, diff --git a/app/database/operator/server_data_operator/handlers/channel.ts b/app/database/operator/server_data_operator/handlers/channel.ts index 7c005f9fa..a2a278a38 100644 --- a/app/database/operator/server_data_operator/handlers/channel.ts +++ b/app/database/operator/server_data_operator/handlers/channel.ts @@ -113,7 +113,7 @@ const ChannelHandler = (superclass: any) => class extends superclass { } const createOrUpdateRawValues = getUniqueRawsBy({ - raws: channelInfos, + raws: channelInfos as ChannelInfo[], key: 'id', }); diff --git a/app/database/operator/server_data_operator/transformers/channel.ts b/app/database/operator/server_data_operator/transformers/channel.ts index 576a45fad..2a357bf6a 100644 --- a/app/database/operator/server_data_operator/transformers/channel.ts +++ b/app/database/operator/server_data_operator/transformers/channel.ts @@ -109,17 +109,17 @@ export const transformMyChannelSettingsRecord = ({action, database, value}: Tran * @returns {Promise} */ export const transformChannelInfoRecord = ({action, database, value}: TransformerArgs): Promise => { - const raw = value.raw as ChannelInfo; + const raw = value.raw as Partial; 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({ diff --git a/app/queries/servers/categories.ts b/app/queries/servers/categories.ts index 6c7031e8b..6c7a37998 100644 --- a/app/queries/servers/categories.ts +++ b/app/queries/servers/categories.ts @@ -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 { 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 => { const preparedModels: Model[] = [category.prepareDestroyPermanently()]; - const associatedChildren: Array|undefined> = [ + const associatedChildren: Array> = [ 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; diff --git a/app/queries/servers/channel.ts b/app/queries/servers/channel.ts index c975ac4d7..c0b103534 100644 --- a/app/queries/servers/channel.ts +++ b/app/queries/servers/channel.ts @@ -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> | undefined { +export function prepareMissingChannelsForAllTeams(operator: ServerDataOperator, channels: Channel[], channelMembers: ChannelMembership[]): Array> { 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 = operator.handleChannel({channels, prepareRecordsOnly: true}); - const channelInfoRecords: Promise = operator.handleChannelInfo({channelInfos, prepareRecordsOnly: true}); - const membershipRecords: Promise = operator.handleChannelMembership({channelMemberships: memberships, prepareRecordsOnly: true}); - const myChannelRecords: Promise = operator.handleMyChannel({channels, myChannels: memberships, prepareRecordsOnly: true}); - const myChannelSettingsRecords: Promise = 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>> => { 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(MM_TABLES.SERVER.CHANNEL).query( + return database.get(CHANNEL).query( Q.where('team_id', teamId), Q.where('name', General.DEFAULT_CHANNEL), ); diff --git a/app/queries/servers/entry.ts b/app/queries/servers/entry.ts index f44ce3271..0daeed9da 100644 --- a/app/queries/servers/entry.ts +++ b/app/queries/servers/entry.ts @@ -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; diff --git a/app/queries/servers/post.ts b/app/queries/servers/post.ts index 40ddceb7f..5dc0e0c4e 100644 --- a/app/queries/servers/post.ts +++ b/app/queries/servers/post.ts @@ -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 => { const preparedModels: Model[] = [post.prepareDestroyPermanently()]; - const relations: Array | Query> = [post.drafts, post.postsInThread]; + const relations: Array> = [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 diff --git a/app/queries/servers/preference.ts b/app/queries/servers/preference.ts index 93aa39f41..0112ef6fb 100644 --- a/app/queries/servers/preference.ts +++ b/app/queries/servers/preference.ts @@ -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 { 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)]; diff --git a/app/queries/servers/role.ts b/app/queries/servers/role.ts index c47aa4900..dbc90a15e 100644 --- a/app/queries/servers/role.ts +++ b/app/queries/servers/role.ts @@ -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); } diff --git a/app/queries/servers/system.ts b/app/queries/servers/system.ts index 38813b06a..4441a9538 100644 --- a/app/queries/servers/system.ts +++ b/app/queries/servers/system.ts @@ -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 { 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 { diff --git a/app/queries/servers/team.ts b/app/queries/servers/team.ts index 33cc804f4..8e694a066 100644 --- a/app/queries/servers/team.ts +++ b/app/queries/servers/team.ts @@ -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> { 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 => { } })); - 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 => { } } - 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); diff --git a/app/queries/servers/user.ts b/app/queries/servers/user.ts index 8bf22553d..884e74123 100644 --- a/app/queries/servers/user.ts +++ b/app/queries/servers/user.ts @@ -58,17 +58,17 @@ export const queryUsersByUsername = (database: Database, usernames: string[]) => return database.get(USER).query(Q.where('username', Q.oneOf(usernames))); }; -export const prepareUsers = (operator: ServerDataOperator, users: UserProfile[]) => { +export async function prepareUsers(operator: ServerDataOperator, users: UserProfile[]): Promise { 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); diff --git a/app/utils/categories.ts b/app/utils/categories.ts new file mode 100644 index 000000000..128c5a3c1 --- /dev/null +++ b/app/utils/categories.ts @@ -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}`; +} diff --git a/app/utils/channel/index.ts b/app/utils/channel/index.ts index 5430ee874..b19809eda 100644 --- a/app/utils/channel/index.ts +++ b/app/utils/channel/index.ts @@ -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(channels: T[], memberships: ChannelMembership[], teamId: string, roles?: Role[], locale = DEFAULT_LOCALE) { let channel: T|undefined; let canIJoinPublicChannelsInTeam = false; diff --git a/types/database/database.d.ts b/types/database/database.d.ts index 3cb8e4034..cdb110da6 100644 --- a/types/database/database.d.ts +++ b/types/database/database.d.ts @@ -204,7 +204,7 @@ export type HandleMyChannelArgs = PrepareOnly & { }; export type HandleChannelInfoArgs = PrepareOnly &{ - channelInfos: ChannelInfo[]; + channelInfos: Array>; }; export type HandleMyChannelSettingsArgs = PrepareOnly & {