diff --git a/app/actions/local/channel.ts b/app/actions/local/channel.ts index c775de554..70304c902 100644 --- a/app/actions/local/channel.ts +++ b/app/actions/local/channel.ts @@ -1,25 +1,34 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {Model} from '@nozbe/watermelondb'; +import {Model, Q} from '@nozbe/watermelondb'; import {DeviceEventEmitter} from 'react-native'; -import {Navigation as NavigationConstants, Screens} from '@constants'; +import {General, Navigation as NavigationConstants, Preferences, Screens} from '@constants'; +import {MM_TABLES} from '@constants/database'; import DatabaseManager from '@database/manager'; +import {getTeammateNameDisplaySetting} from '@helpers/api/preference'; import {prepareDeleteChannel, queryAllMyChannelIds, queryChannelsById, queryMyChannel} from '@queries/servers/channel'; +import {queryPreferencesByCategoryAndName} from '@queries/servers/preference'; import {prepareCommonSystemValues, PrepareCommonSystemValuesArgs, queryCommonSystemValues, queryCurrentTeamId, setCurrentChannelId} from '@queries/servers/system'; import {addChannelToTeamHistory, addTeamToTeamHistory, removeChannelFromTeamHistory} from '@queries/servers/team'; +import {queryCurrentUser} from '@queries/servers/user'; import {dismissAllModalsAndPopToRoot, dismissAllModalsAndPopToScreen} from '@screens/navigation'; import {isTablet} from '@utils/helpers'; +import {displayGroupMessageName, displayUsername} from '@utils/user'; import type ChannelModel from '@typings/database/models/servers/channel'; +import type UserModel from '@typings/database/models/servers/user'; -export const switchToChannel = async (serverUrl: string, channelId: string, teamId?: string) => { +const {SERVER: {CHANNEL_MEMBERSHIP, USER}} = MM_TABLES; + +export const switchToChannel = async (serverUrl: string, channelId: string, teamId?: string, prepareRecordsOnly = false) => { const database = DatabaseManager.serverDatabases[serverUrl]?.database; if (!database) { return {error: `${serverUrl} database not found`}; } + const models: Model[] = []; try { const dt = Date.now(); const isTabletDevice = await isTablet(); @@ -29,7 +38,6 @@ export const switchToChannel = async (serverUrl: string, channelId: string, team if (member) { const channel: ChannelModel = await member.channel.fetch(); const {operator} = DatabaseManager.serverDatabases[serverUrl]; - const models = []; const commonValues: PrepareCommonSystemValuesArgs = {currentChannelId: channelId}; if (isTabletDevice) { // On tablet, the channel is being rendered, by setting the channel to empty first we speed up @@ -58,7 +66,7 @@ export const switchToChannel = async (serverUrl: string, channelId: string, team models.push(viewedAt); } - if (models.length) { + if (models.length && !prepareRecordsOnly) { await operator.batchRecords(models); } @@ -75,21 +83,22 @@ export const switchToChannel = async (serverUrl: string, channelId: string, team return {error}; } - return {error: undefined}; + return {models}; }; -export const localRemoveUserFromChannel = async (serverUrl: string, channelId: string) => { +export const removeCurrentUserFromChannel = async (serverUrl: string, channelId: string, prepareRecordsOnly = false) => { const serverDatabase = DatabaseManager.serverDatabases[serverUrl]; if (!serverDatabase) { - return; + return {error: `${serverUrl} database not found`}; } const {operator, database} = serverDatabase; + const models: Model[] = []; const myChannel = await queryMyChannel(database, channelId); if (myChannel) { const channel = await myChannel.channel.fetch() as ChannelModel; - const models = await prepareDeleteChannel(channel); + models.push(...await prepareDeleteChannel(channel)); let teamId = channel.teamId; if (teamId) { teamId = await queryCurrentTeamId(database); @@ -98,7 +107,7 @@ export const localRemoveUserFromChannel = async (serverUrl: string, channelId: s if (system) { models.push(...system); } - if (models.length) { + if (models.length && !prepareRecordsOnly) { try { await operator.batchRecords(models); } catch { @@ -107,9 +116,10 @@ export const localRemoveUserFromChannel = async (serverUrl: string, channelId: s } } } + return {models}; }; -export const localSetChannelDeleteAt = async (serverUrl: string, channelId: string, deleteAt: number) => { +export const setChannelDeleteAt = async (serverUrl: string, channelId: string, deleteAt: number) => { const serverDatabase = DatabaseManager.serverDatabases[serverUrl]; if (!serverDatabase) { return; @@ -196,3 +206,43 @@ export const resetMessageCount = async (serverUrl: string, channelId: string) => return {error}; } }; + +export async function updateChannelsDisplayName(serverUrl: string, channels: ChannelModel[], user: UserProfile, prepareRecordsOnly = false) { + const database = DatabaseManager.serverDatabases[serverUrl]; + if (!database) { + return {}; + } + const currentUser = await queryCurrentUser(database.database); + if (!currentUser) { + return {}; + } + + const {config, license} = await queryCommonSystemValues(database.database); + const preferences = await queryPreferencesByCategoryAndName(database.database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT); + const displaySettings = getTeammateNameDisplaySetting(preferences, config, license); + const models: Model[] = []; + channels?.forEach(async (channel) => { + let newDisplayName = ''; + if (channel.type === General.DM_CHANNEL) { + newDisplayName = displayUsername(user, currentUser.locale, displaySettings); + } else { + const dbProfiles = await database.database.get(USER).query(Q.on(CHANNEL_MEMBERSHIP, Q.where('channel_id', channel.id))).fetch(); + const newProfiles: Array = dbProfiles.filter((u) => u.id !== user.id); + newProfiles.push(user); + newDisplayName = displayGroupMessageName(newProfiles, currentUser.locale, displaySettings, currentUser.id); + } + + if (channel.displayName !== newDisplayName) { + channel.prepareUpdate((c) => { + c.displayName = newDisplayName; + }); + models.push(channel); + } + }); + + if (models.length && !prepareRecordsOnly) { + database.operator.batchRecords(models); + } + + return {models}; +} diff --git a/app/actions/remote/post.ts b/app/actions/remote/post.ts index 4c7139d81..b421cb408 100644 --- a/app/actions/remote/post.ts +++ b/app/actions/remote/post.ts @@ -42,7 +42,7 @@ export const fetchPostsForCurrentChannel = async (serverUrl: string) => { return fetchPostsForChannel(serverUrl, currentChannelId); }; -export const fetchPostsForChannel = async (serverUrl: string, channelId: string) => { +export const fetchPostsForChannel = async (serverUrl: string, channelId: string, fetchOnly = false) => { const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; if (!operator) { return {error: `${serverUrl} database not found`}; @@ -65,42 +65,42 @@ export const fetchPostsForChannel = async (serverUrl: string, channelId: string) // Here we should emit an event that fetching posts failed. } + let authors: UserProfile[] = []; if (data.posts?.length && data.order?.length) { - const models: Model[] = []; try { - const {authors} = await fetchPostAuthors(serverUrl, data.posts, true); - if (authors?.length) { - const users = await operator.handleUsers({ - users: authors, - prepareRecordsOnly: true, - }); - if (users.length) { - models.push(...users); - } - } + const {authors: fetchedAuthors} = await fetchPostAuthors(serverUrl, data.posts, true); + authors = fetchedAuthors || []; } catch (error) { // eslint-disable-next-line no-console console.log('FETCH AUTHORS ERROR', error); } - const postModels = await operator.handlePosts({ - actionType, - order: data.order, - posts: data.posts, - previousPostId: data.previousPostId, - prepareRecordsOnly: true, - }); + if (!fetchOnly) { + const models = []; + const postModels = await operator.handlePosts({ + actionType, + order: data.order, + posts: data.posts, + previousPostId: data.previousPostId, + prepareRecordsOnly: true, + }); + if (postModels) { + models.push(...postModels); + } + if (authors.length) { + const userModels = await operator.handleUsers({users: authors, prepareRecordsOnly: true}); + if (userModels.length) { + models.push(...userModels); + } + } - if (postModels.length) { - models.push(...postModels); - } - - if (models.length) { - await operator.batchRecords(models); + if (models.length) { + operator.batchRecords(models); + } } } - return {posts: data.posts}; + return {posts: data.posts, order: data.order, authors, actionType, previousPostId: data.previousPostId}; }; export const fetchPostsForUnreadChannels = async (serverUrl: string, channels: Channel[], memberships: ChannelMembership[], excludeChannelId?: string) => { diff --git a/app/actions/remote/user.ts b/app/actions/remote/user.ts index b71fa15fd..c98cbe1bf 100644 --- a/app/actions/remote/user.ts +++ b/app/actions/remote/user.ts @@ -360,7 +360,7 @@ export const updateAllUsersSinceLastDisconnect = async (serverUrl: string) => { return {users: userUpdates}; }; -export const updateUsersNoLongerVisible = async (serverUrl: string): Promise<{error?: unknown}> => { +export const updateUsersNoLongerVisible = async (serverUrl: string, prepareRecordsOnly = false): Promise<{error?: unknown; models?: Model[]}> => { let client: Client; try { client = NetworkManager.getClient(serverUrl); @@ -373,12 +373,12 @@ export const updateUsersNoLongerVisible = async (serverUrl: string): Promise<{er return {error: `${serverUrl} database not found`}; } + const models: Model[] = []; try { const knownUsers = new Set(await client.getKnownUsers()); const currentUserId = await queryCurrentUserId(serverDatabase.database); knownUsers.add(currentUserId); - const models: Model[] = []; const allUsers = await queryAllUsers(serverDatabase.database); for (const user of allUsers) { if (!knownUsers.has(user.id)) { @@ -386,7 +386,7 @@ export const updateUsersNoLongerVisible = async (serverUrl: string): Promise<{er models.push(user); } } - if (models.length) { + if (models.length && !prepareRecordsOnly) { serverDatabase.operator.batchRecords(models); } } catch (error) { @@ -394,7 +394,7 @@ export const updateUsersNoLongerVisible = async (serverUrl: string): Promise<{er return {error}; } - return {}; + return {models}; }; export const setStatus = async (serverUrl: string, status: UserStatus) => { diff --git a/app/actions/websocket/channel.ts b/app/actions/websocket/channel.ts index 8c73ca523..958d56e73 100644 --- a/app/actions/websocket/channel.ts +++ b/app/actions/websocket/channel.ts @@ -1,22 +1,89 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {Model} from '@nozbe/watermelondb'; import {DeviceEventEmitter} from 'react-native'; -import {localRemoveUserFromChannel, localSetChannelDeleteAt, switchToChannel} from '@actions/local/channel'; -import {updateUsersNoLongerVisible} from '@actions/remote/user'; +import {removeCurrentUserFromChannel, setChannelDeleteAt, switchToChannel} from '@actions/local/channel'; +import {fetchMyChannel} from '@actions/remote/channel'; +import {fetchPostsForChannel} from '@actions/remote/post'; +import {fetchUsersByIds, updateUsersNoLongerVisible} from '@actions/remote/user'; import Events from '@constants/events'; import DatabaseManager from '@database/manager'; import {queryActiveServer} from '@queries/app/servers'; -import {deleteChannelMembership, queryCurrentChannel} from '@queries/servers/channel'; -import {queryConfig, setCurrentChannelId} from '@queries/servers/system'; +import {deleteChannelMembership, prepareMyChannelsForTeam, queryChannelsById, queryCurrentChannel} from '@queries/servers/channel'; +import {prepareCommonSystemValues, queryConfig, setCurrentChannelId} from '@queries/servers/system'; import {queryLastChannelFromTeam} from '@queries/servers/team'; -import {queryCurrentUser} from '@queries/servers/user'; +import {queryCurrentUser, queryUserById} from '@queries/servers/user'; import {dismissAllModals, popToRoot} from '@screens/navigation'; import {isTablet} from '@utils/helpers'; -import {isGuest} from '@utils/user'; -export async function handleUserRemovedEvent(serverUrl: string, msg: any) { +export async function handleUserAddedToChannelEvent(serverUrl: string, msg: any) { + const database = DatabaseManager.serverDatabases[serverUrl]; + if (!database) { + return; + } + const currentUser = await queryCurrentUser(database.database); + const {team_id: teamId, channel_id: channelId, user_id: userId} = msg.data; + + const models: Model[] = []; + + try { + const addedUser = queryUserById(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 prepareModels = await Promise.all(prepare); + const flattenedModels = prepareModels.flat(); + if (flattenedModels?.length > 0) { + models.push(...flattenedModels); + } + } + } + + const {posts, order, authors, actionType, previousPostId} = await fetchPostsForChannel(serverUrl, channelId, true); + if (posts?.length && order && actionType) { + models.push(...await database.operator.handlePosts({ + actionType, + order, + posts, + previousPostId, + prepareRecordsOnly: true, + })); + } + + if (authors?.length) { + models.push(...await database.operator.handleUsers({users: authors, prepareRecordsOnly: true})); + } + + database.operator.batchRecords(models); + } else { + const channels = await queryChannelsById(database.database, [channelId]); + if (channels?.[0]) { + models.push(...await database.operator.handleChannelMembership({ + channelMemberships: [{channel_id: channelId, user_id: userId}], + prepareRecordsOnly: true, + })); + } + } + } catch { + // Do nothing + } + + database.operator.batchRecords(models); +} + +export async function handleUserRemovedFromChannelEvent(serverUrl: string, msg: any) { const database = DatabaseManager.serverDatabases[serverUrl]; if (!database) { return; @@ -28,14 +95,25 @@ export async function handleUserRemovedEvent(serverUrl: string, msg: any) { return; } - if (user.id === msg.data.user_id) { - localRemoveUserFromChannel(serverUrl, msg.data.channel_id); + const userId = msg.data.user_id; + const channelId = msg.data.channel_id; - if (isGuest(user.roles)) { - updateUsersNoLongerVisible(serverUrl); + const models: Model[] = []; + + if (user.isGuest) { + const {models: updateVisibleModels} = await updateUsersNoLongerVisible(serverUrl, true); + if (updateVisibleModels) { + models.push(...updateVisibleModels); + } + } + + if (user.id === userId) { + const {models: removeUserModels} = await removeCurrentUserFromChannel(serverUrl, channelId, true); + if (removeUserModels) { + models.push(...removeUserModels); } - if (channel && channel.id === msg.data.channel_id) { + if (channel && channel.id === channelId) { const currentServer = await queryActiveServer(DatabaseManager.appDatabase!.database); if (currentServer?.url === serverUrl) { @@ -46,16 +124,27 @@ export async function handleUserRemovedEvent(serverUrl: string, msg: any) { if (await isTablet()) { const channelToJumpTo = await queryLastChannelFromTeam(database.database, channel?.teamId); if (channelToJumpTo) { - switchToChannel(serverUrl, channelToJumpTo); - } // TODO else jump to "join a channel" screen + const {models: switchChannelModels} = await switchToChannel(serverUrl, channelToJumpTo, '', true); + if (switchChannelModels) { + models.push(...switchChannelModels); + } + } // TODO else jump to "join a channel" screen https://mattermost.atlassian.net/browse/MM-41051 } else { - setCurrentChannelId(database.operator, ''); + const currentChannelModels = await prepareCommonSystemValues(database.operator, {currentChannelId: ''}); + if (currentChannelModels?.length) { + models.push(...currentChannelModels); + } } } } } else { - deleteChannelMembership(database.operator, msg.data.user_id, msg.data.channel_id); + const {models: deleteMemberModels} = await deleteChannelMembership(database.operator, userId, channelId, true); + if (deleteMemberModels) { + models.push(...deleteMemberModels); + } } + + database.operator.batchRecords(models); } export async function handleChannelDeletedEvent(serverUrl: string, msg: any) { @@ -72,14 +161,14 @@ export async function handleChannelDeletedEvent(serverUrl: string, msg: any) { const config = await queryConfig(database.database); - await localSetChannelDeleteAt(serverUrl, msg.data.channel_id, msg.data.delete_at); + await setChannelDeleteAt(serverUrl, msg.data.channel_id, msg.data.delete_at); - if (isGuest(user.roles)) { + if (user.isGuest) { updateUsersNoLongerVisible(serverUrl); } if (config?.ExperimentalViewArchivedChannels !== 'true') { - localRemoveUserFromChannel(serverUrl, msg.data.channel_id); + removeCurrentUserFromChannel(serverUrl, msg.data.channel_id); if (currentChannel && currentChannel.id === msg.data.channel_id) { const currentServer = await queryActiveServer(DatabaseManager.appDatabase!.database); diff --git a/app/actions/websocket/index.ts b/app/actions/websocket/index.ts index ce1451d06..dd74c5208 100644 --- a/app/actions/websocket/index.ts +++ b/app/actions/websocket/index.ts @@ -19,9 +19,10 @@ import {prepareMyChannelsForTeam} from '@queries/servers/channel'; import {queryCommonSystemValues, queryConfig, queryWebSocketLastDisconnected} from '@queries/servers/system'; import {queryCurrentUser} from '@queries/servers/user'; -import {handleChannelDeletedEvent, handleUserRemovedEvent} from './channel'; +import {handleChannelDeletedEvent, handleUserAddedToChannelEvent, handleUserRemovedFromChannelEvent} from './channel'; import {handlePreferenceChangedEvent, handlePreferencesChangedEvent, handlePreferencesDeletedEvent} from './preferences'; import {handleLeaveTeamEvent} from './teams'; +import {handleUserUpdatedEvent} from './users'; import type {Model} from '@nozbe/watermelondb'; @@ -122,7 +123,7 @@ async function doReconnect(serverUrl: string) { const viewArchivedChannels = config?.ExperimentalViewArchivedChannels === 'true'; if (!stillMemberOfCurrentChannel) { - handleUserRemovedEvent(serverUrl, {data: {user_id: currentUserId, channel_id: currentChannelId}}); + handleUserRemovedFromChannelEvent(serverUrl, {data: {user_id: currentUserId, channel_id: currentChannelId}}); } else if (!channelStillExist || (!viewArchivedChannels && channelStillExist.delete_at !== 0) ) { @@ -177,16 +178,14 @@ export async function handleEvent(serverUrl: string, msg: any) { // return dispatch(handleTeamAddedEvent(msg)); case WebsocketEvents.USER_ADDED: + handleUserAddedToChannelEvent(serverUrl, msg); break; - - // return dispatch(handleUserAddedEvent(msg)); case WebsocketEvents.USER_REMOVED: - handleUserRemovedEvent(serverUrl, msg); + handleUserRemovedFromChannelEvent(serverUrl, msg); break; case WebsocketEvents.USER_UPDATED: + handleUserUpdatedEvent(serverUrl, msg); break; - - // return dispatch(handleUserUpdatedEvent(msg)); case WebsocketEvents.ROLE_ADDED: break; diff --git a/app/actions/websocket/teams.ts b/app/actions/websocket/teams.ts index 19b34052f..c6956e034 100644 --- a/app/actions/websocket/teams.ts +++ b/app/actions/websocket/teams.ts @@ -13,7 +13,6 @@ import {queryCurrentTeamId} from '@queries/servers/system'; import {queryLastTeam} from '@queries/servers/team'; import {queryCurrentUser} from '@queries/servers/user'; import {dismissAllModals, popToRoot} from '@screens/navigation'; -import {isGuest} from '@utils/user'; export async function handleLeaveTeamEvent(serverUrl: string, msg: any) { const database = DatabaseManager.serverDatabases[serverUrl]; @@ -31,7 +30,7 @@ export async function handleLeaveTeamEvent(serverUrl: string, msg: any) { await removeUserFromTeam(serverUrl, msg.data.team_id); fetchAllTeams(serverUrl); - if (isGuest(user.roles)) { + if (user.isGuest) { updateUsersNoLongerVisible(serverUrl); } diff --git a/app/actions/websocket/users.ts b/app/actions/websocket/users.ts new file mode 100644 index 000000000..015f54174 --- /dev/null +++ b/app/actions/websocket/users.ts @@ -0,0 +1,62 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {Q} from '@nozbe/watermelondb'; + +import {updateChannelsDisplayName} from '@actions/local/channel'; +import {fetchMe} from '@actions/remote/user'; +import {General} from '@constants'; +import {MM_TABLES} from '@constants/database'; +import DatabaseManager from '@database/manager'; +import {queryCurrentUser} from '@queries/servers/user'; + +import type ChannelModel from '@typings/database/models/servers/channel'; + +const {SERVER: {CHANNEL, CHANNEL_MEMBERSHIP}} = MM_TABLES; + +export async function handleUserUpdatedEvent(serverUrl: string, msg: any) { + const database = DatabaseManager.serverDatabases[serverUrl]; + if (!database) { + return; + } + const currentUser = await queryCurrentUser(database.database); + if (!currentUser) { + return; + } + + const user: UserProfile = msg.data.user; + + if (user.id === currentUser.id) { + if (user.update_at > (currentUser?.updateAt || 0)) { + // Need to request me to make sure we don't override with sanitized fields from the + // websocket event + // TODO Potential improvement https://mattermost.atlassian.net/browse/MM-40582 + fetchMe(serverUrl, false); + + // Update GMs display name if locale has changed + if (user.locale !== currentUser.locale) { + const channels = await database.database.get(CHANNEL).query( + Q.where('type', Q.eq(General.GM_CHANNEL))).fetch(); + const {models} = await updateChannelsDisplayName(serverUrl, channels, user, true); + if (models?.length) { + database.operator.batchRecords(models); + } + } + } + } else { + database.operator.handleUsers({users: [user], prepareRecordsOnly: false}); + + const channels = await database.database.get(CHANNEL).query( + Q.where('type', Q.oneOf([General.DM_CHANNEL, General.GM_CHANNEL])), + Q.on(CHANNEL_MEMBERSHIP, Q.where('user_id', user.id))).fetch(); + if (!channels?.length) { + return; + } + + const {models} = await updateChannelsDisplayName(serverUrl, channels, user, true); + + if (models?.length) { + database.operator.batchRecords(models); + } + } +} diff --git a/app/database/operator/server_data_operator/comparators/index.ts b/app/database/operator/server_data_operator/comparators/index.ts index 42f13a5a7..999d2b96c 100644 --- a/app/database/operator/server_data_operator/comparators/index.ts +++ b/app/database/operator/server_data_operator/comparators/index.ts @@ -77,7 +77,7 @@ export const isRecordGroupMembershipEqualToRaw = (record: GroupMembershipModel, return raw.user_id === record.userId && raw.group_id === record.groupId; }; -export const isRecordChannelMembershipEqualToRaw = (record: ChannelMembershipModel, raw: ChannelMembership) => { +export const isRecordChannelMembershipEqualToRaw = (record: ChannelMembershipModel, raw: Pick) => { return raw.user_id === record.userId && raw.channel_id === record.channelId; }; diff --git a/app/database/operator/server_data_operator/handlers/post.ts b/app/database/operator/server_data_operator/handlers/post.ts index c02bd08d7..79f22e44a 100644 --- a/app/database/operator/server_data_operator/handlers/post.ts +++ b/app/database/operator/server_data_operator/handlers/post.ts @@ -71,6 +71,7 @@ const PostHandler = (superclass: any) => class extends superclass { * @param {string[]} handlePosts.orders * @param {RawPost[]} handlePosts.values * @param {string | undefined} handlePosts.previousPostId + * @param {boolean | undefined} handlePosts.prepareRecordsOnly * @returns {Promise} */ handlePosts = async ({actionType, order, posts, previousPostId = '', prepareRecordsOnly = false}: HandlePostsArgs): Promise => { diff --git a/app/queries/servers/channel.ts b/app/queries/servers/channel.ts index 747bf9cec..05a4f1ca9 100644 --- a/app/queries/servers/channel.ts +++ b/app/queries/servers/channel.ts @@ -214,14 +214,25 @@ export const queryCurrentChannel = async (database: Database) => { return undefined; }; -export const deleteChannelMembership = async (operator: ServerDataOperator, userId: string, channelId: string) => { +export const deleteChannelMembership = async (operator: ServerDataOperator, userId: string, channelId: string, prepareRecordsOnly = false) => { try { const channelMembership = await operator.database.get(CHANNEL_MEMBERSHIP).query(Q.where('user_id', Q.eq(userId)), Q.where('channel_id', Q.eq(channelId))).fetch(); const models: Model[] = []; for (const membership of channelMembership) { models.push(membership.prepareDestroyPermanently()); } - await operator.batchRecords(models); + if (models.length && !prepareRecordsOnly) { + await operator.batchRecords(models); + } + return {models}; + } catch (error) { + return {error}; + } +}; + +export const addChannelMembership = async (operator: ServerDataOperator, userId: string, channelId: string) => { + try { + await operator.handleChannelMembership({channelMemberships: [{channel_id: channelId, user_id: userId}], prepareRecordsOnly: false}); return {}; } catch (error) { return {error}; diff --git a/app/utils/user/index.ts b/app/utils/user/index.ts index f556ddf44..f417dba05 100644 --- a/app/utils/user/index.ts +++ b/app/utils/user/index.ts @@ -34,7 +34,7 @@ export function displayUsername(user?: UserProfile | UserModel, locale?: string, return name; } -export function displayGroupMessageName(users: UserProfile[], locale?: string, teammateDisplayNameSetting?: string, excludeUserId?: string) { +export function displayGroupMessageName(users: Array, locale?: string, teammateDisplayNameSetting?: string, excludeUserId?: string) { const names: string[] = []; const sortUsernames = (a: string, b: string) => { return a.localeCompare(b, locale || DEFAULT_LOCALE, {numeric: true}); diff --git a/types/api/channels.d.ts b/types/api/channels.d.ts index bf7043dca..f684f50e2 100644 --- a/types/api/channels.d.ts +++ b/types/api/channels.d.ts @@ -5,6 +5,7 @@ type ChannelStats = { channel_id: string; guest_count: number; member_count: number; + guest_count: number; pinnedpost_count: number; }; type ChannelNotifyProps = { diff --git a/types/database/database.d.ts b/types/database/database.d.ts index b7511902e..65a4ef08f 100644 --- a/types/database/database.d.ts +++ b/types/database/database.d.ts @@ -223,7 +223,7 @@ export type HandleGroupArgs = PrepareOnly & { }; export type HandleChannelMembershipArgs = PrepareOnly & { - channelMemberships: ChannelMember[]; + channelMemberships: Array>; }; export type HandleGroupMembershipArgs = PrepareOnly & { diff --git a/types/database/raw_values.d.ts b/types/database/raw_values.d.ts index a39db483a..c39c74cd4 100644 --- a/types/database/raw_values.d.ts +++ b/types/database/raw_values.d.ts @@ -122,3 +122,4 @@ type RawValue = | TeamSearchHistory | TermsOfService | UserProfile + | Pick