From bfffa0e10ae33a7d1b608cdbe410f9a2a16461ed Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Mon, 13 Jun 2022 18:32:47 -0400 Subject: [PATCH] [Gekidou] db manager database getters (#6377) --- app/actions/app/global.ts | 51 +- app/actions/local/category.ts | 181 ++++--- app/actions/local/channel.ts | 447 +++++++++--------- app/actions/local/draft.ts | 204 ++++---- app/actions/local/file.ts | 20 +- app/actions/local/notification.ts | 9 +- app/actions/local/post.ts | 222 ++++----- app/actions/local/reactions.ts | 20 +- app/actions/local/team.ts | 43 +- app/actions/local/thread.ts | 248 +++++----- app/actions/local/timezone.ts | 29 -- app/actions/local/user.ts | 152 +++--- app/actions/remote/session.ts | 2 +- app/actions/remote/user.ts | 5 +- .../custom_status/custom_status_expiry.tsx | 4 +- app/database/manager/__mocks__/index.ts | 18 + app/database/manager/index.ts | 32 ++ app/utils/timezone.ts | 12 + app/utils/user/index.ts | 15 + 19 files changed, 846 insertions(+), 868 deletions(-) delete mode 100644 app/actions/local/timezone.ts create mode 100644 app/utils/timezone.ts diff --git a/app/actions/app/global.ts b/app/actions/app/global.ts index 9fcdaaa4f5..09706cfc22 100644 --- a/app/actions/app/global.ts +++ b/app/actions/app/global.ts @@ -5,40 +5,37 @@ import {GLOBAL_IDENTIFIERS} from '@constants/database'; import DatabaseManager from '@database/manager'; export const storeDeviceToken = async (token: string, prepareRecordsOnly = false) => { - const operator = DatabaseManager.appDatabase?.operator; - - if (!operator) { - return {error: 'No App database found'}; + try { + const {operator} = DatabaseManager.getAppDatabaseAndOperator(); + return operator.handleGlobal({ + globals: [{id: GLOBAL_IDENTIFIERS.DEVICE_TOKEN, value: token}], + prepareRecordsOnly, + }); + } catch (error) { + return {error}; } - - return operator.handleGlobal({ - globals: [{id: GLOBAL_IDENTIFIERS.DEVICE_TOKEN, value: token}], - prepareRecordsOnly, - }); }; export const storeMultiServerTutorial = async (prepareRecordsOnly = false) => { - const operator = DatabaseManager.appDatabase?.operator; - - if (!operator) { - return {error: 'No App database found'}; + try { + const {operator} = DatabaseManager.getAppDatabaseAndOperator(); + return operator.handleGlobal({ + globals: [{id: GLOBAL_IDENTIFIERS.MULTI_SERVER_TUTORIAL, value: 'true'}], + prepareRecordsOnly, + }); + } catch (error) { + return {error}; } - - return operator.handleGlobal({ - globals: [{id: GLOBAL_IDENTIFIERS.MULTI_SERVER_TUTORIAL, value: 'true'}], - prepareRecordsOnly, - }); }; export const storeProfileLongPressTutorial = async (prepareRecordsOnly = false) => { - const operator = DatabaseManager.appDatabase?.operator; - - if (!operator) { - return {error: 'No App database found'}; + try { + const {operator} = DatabaseManager.getAppDatabaseAndOperator(); + return operator.handleGlobal({ + globals: [{id: GLOBAL_IDENTIFIERS.PROFILE_LONG_PRESS_TUTORIAL, value: 'true'}], + prepareRecordsOnly, + }); + } catch (error) { + return {error}; } - - return operator.handleGlobal({ - globals: [{id: GLOBAL_IDENTIFIERS.PROFILE_LONG_PRESS_TUTORIAL, value: 'true'}], - prepareRecordsOnly, - }); }; diff --git a/app/actions/local/category.ts b/app/actions/local/category.ts index 210e4611ba..0079a175ae 100644 --- a/app/actions/local/category.ts +++ b/app/actions/local/category.ts @@ -14,12 +14,8 @@ import {pluckUnique} from '@utils/helpers'; import type ChannelModel from '@typings/database/models/servers/channel'; export const deleteCategory = async (serverUrl: string, categoryId: string) => { - const database = DatabaseManager.serverDatabases[serverUrl]?.database; - if (!database) { - return {error: `${serverUrl} database not found`}; - } - try { + const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); const category = await getCategoryById(database, categoryId); if (category) { @@ -37,67 +33,59 @@ export const deleteCategory = async (serverUrl: string, categoryId: string) => { }; export async function storeCategories(serverUrl: string, categories: CategoryWithChannels[], prune = false, prepareRecordsOnly = false) { - const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; + try { + const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); - if (!operator) { - return {error: `${serverUrl} database not found`}; - } - const modelPromises: Array> = []; - const preparedCategories = prepareCategories(operator, categories); - if (preparedCategories) { - modelPromises.push(preparedCategories); - } - - const preparedCategoryChannels = prepareCategoryChannels(operator, categories); - if (preparedCategoryChannels) { - modelPromises.push(preparedCategoryChannels); - } - - const models = await Promise.all(modelPromises); - const flattenedModels = models.flat(); - - if (prune && categories.length) { - const {database} = operator; - const remoteCategoryIds = new Set(categories.map((cat) => cat.id)); - - // If the passed categories have more than one team, we want to update across teams - const teamIds = pluckUnique('team_id')(categories) as string[]; - const localCategories = await queryCategoriesByTeamIds(database, teamIds).fetch(); - - localCategories. - filter((category) => category.type === 'custom'). - forEach((localCategory) => { - if (!remoteCategoryIds.has(localCategory.id)) { - localCategory.prepareDestroyPermanently(); - flattenedModels.push(localCategory); - } - }); - } - - if (prepareRecordsOnly) { - return {models: flattenedModels}; - } - - if (flattenedModels?.length > 0) { - try { - await operator.batchRecords(flattenedModels); - } catch (error) { - // eslint-disable-next-line no-console - console.log('FAILED TO BATCH CATEGORIES', error); - return {error}; + const modelPromises: Array> = []; + const preparedCategories = prepareCategories(operator, categories); + if (preparedCategories) { + modelPromises.push(preparedCategories); } - } - return {models: flattenedModels}; + const preparedCategoryChannels = prepareCategoryChannels(operator, categories); + if (preparedCategoryChannels) { + modelPromises.push(preparedCategoryChannels); + } + + const models = await Promise.all(modelPromises); + const flattenedModels = models.flat(); + + if (prune && categories.length) { + const remoteCategoryIds = new Set(categories.map((cat) => cat.id)); + + // If the passed categories have more than one team, we want to update across teams + const teamIds = pluckUnique('team_id')(categories) as string[]; + const localCategories = await queryCategoriesByTeamIds(database, teamIds).fetch(); + + localCategories. + filter((category) => category.type === 'custom'). + forEach((localCategory) => { + if (!remoteCategoryIds.has(localCategory.id)) { + localCategory.prepareDestroyPermanently(); + flattenedModels.push(localCategory); + } + }); + } + + if (prepareRecordsOnly) { + return {models: flattenedModels}; + } + + if (flattenedModels?.length > 0) { + await operator.batchRecords(flattenedModels); + } + + return {models: flattenedModels}; + } catch (error) { + // eslint-disable-next-line no-console + console.log('FAILED TO STORE CATEGORIES', error); + return {error}; + } } export const toggleCollapseCategory = async (serverUrl: string, categoryId: string) => { - const database = DatabaseManager.serverDatabases[serverUrl].database; - if (!database) { - return {error: `${serverUrl} database not found`}; - } - try { + const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); const category = await getCategoryById(database, categoryId); if (category) { @@ -117,47 +105,46 @@ export const toggleCollapseCategory = async (serverUrl: string, categoryId: stri }; 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`}; - } + try { + const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + const teamId = 'teamId' in channel ? channel.teamId : channel.team_id; + const userId = await getCurrentUserId(database); - 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'}; - } - - const models: Model[] = []; - const categoriesWithChannels: CategoryWithChannels[] = []; - - if (isDMorGM(channel)) { - const allTeamIds = await queryMyTeams(database).fetchIds(); - const categories = await queryCategoriesByTeamIds(database, allTeamIds).fetch(); - const channelCategories = categories.filter((c) => c.type === DMS_CATEGORY); - for await (const cc of channelCategories) { - const cwc = await cc.toCategoryWithChannels(); - cwc.channel_ids.unshift(channel.id); - categoriesWithChannels.push(cwc); - } - } else { - const categories = await queryCategoriesByTeamIds(database, [teamId]).fetch(); - const channelCategory = categories.find((c) => c.type === CHANNELS_CATEGORY); - if (channelCategory) { - const cwc = await channelCategory.toCategoryWithChannels(); - cwc.channel_ids.unshift(channel.id); - categoriesWithChannels.push(cwc); + if (!userId) { + return {error: 'no current user id'}; } - const ccModels = await prepareCategoryChannels(operator, categoriesWithChannels); - models.push(...ccModels); - } + const models: Model[] = []; + const categoriesWithChannels: CategoryWithChannels[] = []; - if (models.length && !prepareRecordsOnly) { - await operator.batchRecords(models); - } + if (isDMorGM(channel)) { + const allTeamIds = await queryMyTeams(database).fetchIds(); + const categories = await queryCategoriesByTeamIds(database, allTeamIds).fetch(); + const channelCategories = categories.filter((c) => c.type === DMS_CATEGORY); + for await (const cc of channelCategories) { + const cwc = await cc.toCategoryWithChannels(); + cwc.channel_ids.unshift(channel.id); + categoriesWithChannels.push(cwc); + } + } else { + const categories = await queryCategoriesByTeamIds(database, [teamId]).fetch(); + const channelCategory = categories.find((c) => c.type === CHANNELS_CATEGORY); + if (channelCategory) { + const cwc = await channelCategory.toCategoryWithChannels(); + cwc.channel_ids.unshift(channel.id); + categoriesWithChannels.push(cwc); + } - return {models}; + const ccModels = await prepareCategoryChannels(operator, categoriesWithChannels); + models.push(...ccModels); + } + + if (models.length && !prepareRecordsOnly) { + await operator.batchRecords(models); + } + + return {models}; + } catch (error) { + return {error}; + } } diff --git a/app/actions/local/channel.ts b/app/actions/local/channel.ts index e908c558f1..57c5b20ddf 100644 --- a/app/actions/local/channel.ts +++ b/app/actions/local/channel.ts @@ -29,14 +29,9 @@ import type ChannelModel from '@typings/database/models/servers/channel'; import type UserModel from '@typings/database/models/servers/user'; export async function switchToChannel(serverUrl: string, channelId: string, teamId?: string, skipLastUnread = false, prepareRecordsOnly = false) { - const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; - if (!operator) { - return {error: `${serverUrl} database not found`}; - } - - const {database} = operator; - let models: Model[] = []; try { + const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + let models: Model[] = []; const dt = Date.now(); const isTabletDevice = await isTablet(); const system = await getCommonSystemValues(database); @@ -113,103 +108,89 @@ export async function switchToChannel(serverUrl: string, channelId: string, team console.log('channel switch to', channel?.displayName, channelId, (Date.now() - dt), 'ms'); //eslint-disable-line } } + + return {models}; } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed to switch to channelId', channelId, 'teamId', teamId, 'error', error); return {error}; } - - return {models}; } export async function removeCurrentUserFromChannel(serverUrl: string, channelId: string, prepareRecordsOnly = false) { - const serverDatabase = DatabaseManager.serverDatabases[serverUrl]; - if (!serverDatabase) { - return {error: `${serverUrl} database not found`}; - } + try { + const {operator, database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); - const {operator, database} = serverDatabase; + const models: Model[] = []; + const myChannel = await getMyChannel(database, channelId); + if (myChannel) { + const channel = await myChannel.channel.fetch(); + if (!channel) { + throw new Error('myChannel present but no channel on the database'); + } + models.push(...await prepareDeleteChannel(channel)); + let teamId = channel.teamId; + if (teamId) { + teamId = await getCurrentTeamId(database); + } - const models: Model[] = []; - const myChannel = await getMyChannel(database, channelId); - if (myChannel) { - const channel = await myChannel.channel.fetch(); - if (!channel) { - return {error: 'myChannel present but no channel on the database'}; - } - models.push(...await prepareDeleteChannel(channel)); - let teamId = channel.teamId; - if (teamId) { - teamId = await getCurrentTeamId(database); - } + // We update the history ASAP to avoid clashes with channel switch. + await removeChannelFromTeamHistory(operator, teamId, channel.id, false); - // We update the history ASAP to avoid clashes with channel switch. - await removeChannelFromTeamHistory(operator, teamId, channel.id, false); - - if (models.length && !prepareRecordsOnly) { - try { + if (models.length && !prepareRecordsOnly) { await operator.batchRecords(models); - } catch { - // eslint-disable-next-line no-console - console.log('FAILED TO BATCH CHANGES FOR REMOVE USER FROM CHANNEL'); } } + return {models}; + } catch (error) { + // eslint-disable-next-line no-console + console.log('failed to removeCurrentUserFromChannel', error); + return {error}; } - return {models}; } export async function setChannelDeleteAt(serverUrl: string, channelId: string, deleteAt: number) { - const serverDatabase = DatabaseManager.serverDatabases[serverUrl]; - if (!serverDatabase) { - return; - } - - const {operator, database} = serverDatabase; - - const channel = await getChannelById(database, channelId); - if (!channel) { - return; - } - - const model = channel.prepareUpdate((c) => { - c.deleteAt = deleteAt; - }); - try { + const {operator, database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + const channel = await getChannelById(database, channelId); + if (!channel) { + return; + } + + const model = channel.prepareUpdate((c) => { + c.deleteAt = deleteAt; + }); await operator.batchRecords([model]); - } catch { + } catch (error) { // eslint-disable-next-line no-console - console.log('FAILED TO BATCH CHANGES FOR CHANNEL DELETE AT'); + console.log('FAILED TO BATCH CHANGES FOR CHANNEL DELETE AT', error); } } export async function selectAllMyChannelIds(serverUrl: string) { - const database = DatabaseManager.serverDatabases[serverUrl]?.database; - if (!database) { + try { + const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + return queryAllMyChannel(database).fetchIds(); + } catch { return []; } - - return queryAllMyChannel(database).fetchIds(); } export async function markChannelAsViewed(serverUrl: string, channelId: string, prepareRecordsOnly = false) { - const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; - if (!operator) { - return {error: `${serverUrl} database not found`}; - } - - const member = await getMyChannel(operator.database, channelId); - if (!member) { - return {error: 'not a member'}; - } - - member.prepareUpdate((m) => { - m.isUnread = false; - m.mentionsCount = 0; - m.manuallyUnread = false; - m.viewedAt = member.lastViewedAt; - m.lastViewedAt = Date.now(); - }); - try { + const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + const member = await getMyChannel(database, channelId); + if (!member) { + return {error: 'not a member'}; + } + + member.prepareUpdate((m) => { + m.isUnread = false; + m.mentionsCount = 0; + m.manuallyUnread = false; + m.viewedAt = member.lastViewedAt; + m.lastViewedAt = Date.now(); + }); PushNotifications.cancelChannelNotifications(channelId); if (!prepareRecordsOnly) { await operator.batchRecords([member]); @@ -217,53 +198,47 @@ export async function markChannelAsViewed(serverUrl: string, channelId: string, return {member}; } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed markChannelAsViewed', error); return {error}; } } export async function markChannelAsUnread(serverUrl: string, channelId: string, messageCount: number, mentionsCount: number, lastViewed: number, prepareRecordsOnly = false) { - const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; - if (!operator) { - return {error: `${serverUrl} database not found`}; - } - - const member = await getMyChannel(operator.database, channelId); - if (!member) { - return {error: 'not a member'}; - } - - member.prepareUpdate((m) => { - m.viewedAt = lastViewed - 1; - m.lastViewedAt = lastViewed - 1; - m.messageCount = messageCount; - m.mentionsCount = mentionsCount; - m.manuallyUnread = true; - m.isUnread = true; - }); - try { + const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + const member = await getMyChannel(database, channelId); + if (!member) { + return {error: 'not a member'}; + } + + member.prepareUpdate((m) => { + m.viewedAt = lastViewed - 1; + m.lastViewedAt = lastViewed - 1; + m.messageCount = messageCount; + m.mentionsCount = mentionsCount; + m.manuallyUnread = true; + m.isUnread = true; + }); if (!prepareRecordsOnly) { await operator.batchRecords([member]); } return {member}; } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed markChannelAsUnread', error); return {error}; } } export async function resetMessageCount(serverUrl: string, channelId: string) { - const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; - if (!operator) { - return {error: `${serverUrl} database not found`}; - } - - const member = await getMyChannel(operator.database, channelId); - if (!member) { - return {error: 'not a member'}; - } - try { + const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + const member = await getMyChannel(database, channelId); + if (!member) { + return {error: 'not a member'}; + } member.prepareUpdate((m) => { m.messageCount = 0; }); @@ -271,185 +246,185 @@ export async function resetMessageCount(serverUrl: string, channelId: string) { return member; } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed resetMessageCount', error); return {error}; } } export async function storeMyChannelsForTeam(serverUrl: string, teamId: string, channels: Channel[], memberships: ChannelMembership[], prepareRecordsOnly = false, isCRTEnabled?: boolean) { - const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; - if (!operator) { - return {error: `${serverUrl} database not found`}; - } - const modelPromises: Array> = [ - ...await prepareMyChannelsForTeam(operator, teamId, channels, memberships, isCRTEnabled), - ]; + try { + const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + const modelPromises: Array> = [ + ...await prepareMyChannelsForTeam(operator, teamId, channels, memberships, isCRTEnabled), + ]; - const models = await Promise.all(modelPromises); - if (!models.length) { - return {models: []}; - } - - const flattenedModels = models.flat(); - - if (prepareRecordsOnly) { - return {models: flattenedModels}; - } - - if (flattenedModels.length) { - try { - await operator.batchRecords(flattenedModels); - } catch (error) { - // eslint-disable-next-line no-console - console.log('FAILED TO BATCH CHANNELS'); - return {error}; + const models = await Promise.all(modelPromises); + if (!models.length) { + return {models: []}; } - } - return {models: flattenedModels}; + const flattenedModels = models.flat(); + + if (prepareRecordsOnly) { + return {models: flattenedModels}; + } + + if (flattenedModels.length) { + await operator.batchRecords(flattenedModels); + } + + return {models: flattenedModels}; + } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed storeMyChannelsForTeam', error); + return {error}; + } } export async function updateMyChannelFromWebsocket(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]); + try { + const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + const member = await getMyChannel(database, channelMember.channel_id); + if (member) { + member.prepareUpdate((m) => { + m.roles = channelMember.roles; + }); + if (!prepareRecordsOnly) { + operator.batchRecords([member]); + } } + return {model: member}; + } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed updateMyChannelFromWebsocket', error); + return {error}; } - return {model: member}; } export async function updateChannelInfoFromChannel(serverUrl: string, channel: Channel, prepareRecordsOnly = false) { - const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; - if (!operator) { - return {error: `${serverUrl} database not found`}; + try { + const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + const newInfo = await operator.handleChannelInfo({channelInfos: [{ + header: channel.header, + purpose: channel.purpose, + id: channel.id, + }], + prepareRecordsOnly: true}); + if (!prepareRecordsOnly) { + operator.batchRecords(newInfo); + } + return {model: newInfo}; + } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed updateChannelInfoFromChannel', error); + return {error}; } - - const newInfo = await operator.handleChannelInfo({channelInfos: [{ - header: channel.header, - purpose: channel.purpose, - id: channel.id, - }], - prepareRecordsOnly: true}); - if (!prepareRecordsOnly) { - operator.batchRecords(newInfo); - } - return {model: newInfo}; } export async function updateLastPostAt(serverUrl: string, channelId: string, lastPostAt: number, prepareRecordsOnly = false) { - const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; - if (!operator) { - return {error: `${serverUrl} database not found`}; - } + try { + const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + const member = await getMyChannel(database, channelId); + if (!member) { + return {error: 'not a member'}; + } - const member = await getMyChannel(operator.database, channelId); - if (!member) { - return {error: 'not a member'}; - } + if (lastPostAt > member.lastPostAt) { + member.prepareUpdate((m) => { + m.lastPostAt = lastPostAt; + }); - if (lastPostAt > member.lastPostAt) { - member.prepareUpdate((m) => { - m.lastPostAt = lastPostAt; - }); - - try { if (!prepareRecordsOnly) { await operator.batchRecords([member]); } - } catch (error) { - return {error}; + + return {member}; } - return {member}; + return {member: undefined}; + } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed updateLastPostAt', error); + return {error}; } - - return {member: undefined}; } type User = UserProfile | UserModel; export async function updateChannelsDisplayName(serverUrl: string, channels: ChannelModel[], users: User[], prepareRecordsOnly = false) { - const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; - if (!operator) { - return {}; - } + try { + const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + const currentUser = await getCurrentUser(database); + if (!currentUser) { + return {}; + } - const {database} = operator; - const currentUser = await getCurrentUser(database); - if (!currentUser) { - return {}; - } + const {config, license} = await getCommonSystemValues(database); + const preferences = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT).fetch(); + const displaySettings = getTeammateNameDisplaySetting(preferences, config, license); + const models: Model[] = []; + for await (const channel of channels) { + let newDisplayName = ''; + if (channel.type === General.DM_CHANNEL) { + const otherUserId = getUserIdFromChannelName(currentUser.id, channel.name); + const user = users.find((u) => u.id === otherUserId); + newDisplayName = displayUsername(user, currentUser.locale, displaySettings, false); + } else { + const dbProfiles = await queryUsersOnChannel(database, channel.id).fetch(); + const profileIds = new Set(dbProfiles.map((p) => p.id)); + const gmUsers = users.filter((u) => profileIds.has(u.id)); + if (gmUsers.length) { + const uIds = new Set(gmUsers.map((u) => u.id)); + const newProfiles: Array = dbProfiles.filter((u) => !uIds.has(u.id)); + newProfiles.push(...gmUsers); + newDisplayName = displayGroupMessageName(newProfiles, currentUser.locale, displaySettings, currentUser.id); + } + } - const {config, license} = await getCommonSystemValues(database); - const preferences = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT).fetch(); - const displaySettings = getTeammateNameDisplaySetting(preferences, config, license); - const models: Model[] = []; - for await (const channel of channels) { - let newDisplayName = ''; - if (channel.type === General.DM_CHANNEL) { - const otherUserId = getUserIdFromChannelName(currentUser.id, channel.name); - const user = users.find((u) => u.id === otherUserId); - newDisplayName = displayUsername(user, currentUser.locale, displaySettings, false); - } else { - const dbProfiles = await queryUsersOnChannel(database, channel.id).fetch(); - const profileIds = new Set(dbProfiles.map((p) => p.id)); - const gmUsers = users.filter((u) => profileIds.has(u.id)); - if (gmUsers.length) { - const uIds = new Set(gmUsers.map((u) => u.id)); - const newProfiles: Array = dbProfiles.filter((u) => !uIds.has(u.id)); - newProfiles.push(...gmUsers); - newDisplayName = displayGroupMessageName(newProfiles, currentUser.locale, displaySettings, currentUser.id); + if (newDisplayName && channel.displayName !== newDisplayName) { + channel.prepareUpdate((c) => { + c.displayName = extractChannelDisplayName({ + type: c.type, + display_name: newDisplayName, + fake: true, + }, c); + }); + models.push(channel); } } - if (newDisplayName && channel.displayName !== newDisplayName) { - channel.prepareUpdate((c) => { - c.displayName = extractChannelDisplayName({ - type: c.type, - display_name: newDisplayName, - fake: true, - }, c); - }); - models.push(channel); + if (models.length && !prepareRecordsOnly) { + await operator.batchRecords(models); } - } - if (models.length && !prepareRecordsOnly) { - await operator.batchRecords(models); + return {models}; + } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed updateChannelsDisplayName', error); + return {error}; } - - return {models}; } export async function showUnreadChannelsOnly(serverUrl: string, onlyUnreads: boolean) { - const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; - if (!operator) { - return {error: `${serverUrl} database not found`}; + try { + const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + return operator.handleSystem({ + systems: [{ + id: SYSTEM_IDENTIFIERS.ONLY_UNREADS, + value: JSON.stringify(onlyUnreads), + }], + prepareRecordsOnly: false, + }); + } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed showUnreadChannelsOnly', error); + return {error}; } - - return operator.handleSystem({ - systems: [{ - id: SYSTEM_IDENTIFIERS.ONLY_UNREADS, - value: JSON.stringify(onlyUnreads), - }], - prepareRecordsOnly: false, - }); } export const updateDmGmDisplayName = async (serverUrl: string) => { - const database = DatabaseManager.serverDatabases[serverUrl]?.database; - if (!database) { - return {error: `${serverUrl} database not found`}; - } - try { + const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); const currentUserId = await getCurrentUserId(database); if (!currentUserId) { return {error: 'The current user id could not be retrieved from the database'}; @@ -471,6 +446,8 @@ export const updateDmGmDisplayName = async (serverUrl: string) => { return {channels}; } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed updateDmGmDisplayName', error); return {error}; } }; diff --git a/app/actions/local/draft.ts b/app/actions/local/draft.ts index b21a026d2b..1f033624fb 100644 --- a/app/actions/local/draft.ts +++ b/app/actions/local/draft.ts @@ -5,163 +5,157 @@ import DatabaseManager from '@database/manager'; import {getDraft} from '@queries/servers/drafts'; export async function updateDraftFile(serverUrl: string, channelId: string, rootId: string, file: FileInfo, prepareRecordsOnly = false) { - const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; - if (!operator) { - return {error: `${serverUrl} database not found`}; - } - - const draft = await getDraft(operator.database, channelId, rootId); - if (!draft) { - return {error: 'no draft'}; - } - - const i = draft.files.findIndex((v) => v.clientId === file.clientId); - if (i === -1) { - return {error: 'file not found'}; - } - - // We create a new list to make sure we re-render the draft input. - const newFiles = [...draft.files]; - newFiles[i] = file; - draft.prepareUpdate((d) => { - d.files = newFiles; - }); - try { + const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + const draft = await getDraft(database, channelId, rootId); + if (!draft) { + return {error: 'no draft'}; + } + + const i = draft.files.findIndex((v) => v.clientId === file.clientId); + if (i === -1) { + return {error: 'file not found'}; + } + + // We create a new list to make sure we re-render the draft input. + const newFiles = [...draft.files]; + newFiles[i] = file; + draft.prepareUpdate((d) => { + d.files = newFiles; + }); + if (!prepareRecordsOnly) { await operator.batchRecords([draft]); } return {draft}; } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed updateDraftFile', error); return {error}; } } export async function removeDraftFile(serverUrl: string, channelId: string, rootId: string, clientId: string, prepareRecordsOnly = false) { - const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; - if (!operator) { - return {error: `${serverUrl} database not found`}; - } - - const draft = await getDraft(operator.database, channelId, rootId); - if (!draft) { - return {error: 'no draft'}; - } - - const i = draft.files.findIndex((v) => v.clientId === clientId); - if (i === -1) { - return {error: 'file not found'}; - } - - if (draft.files.length === 1 && !draft.message) { - draft.prepareDestroyPermanently(); - } else { - draft.prepareUpdate((d) => { - d.files = draft.files.filter((v, index) => index !== i); - }); - } - try { + const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + const draft = await getDraft(database, channelId, rootId); + if (!draft) { + return {error: 'no draft'}; + } + + const i = draft.files.findIndex((v) => v.clientId === clientId); + if (i === -1) { + return {error: 'file not found'}; + } + + if (draft.files.length === 1 && !draft.message) { + draft.prepareDestroyPermanently(); + } else { + draft.prepareUpdate((d) => { + d.files = draft.files.filter((v, index) => index !== i); + }); + } + if (!prepareRecordsOnly) { await operator.batchRecords([draft]); } return {draft}; } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed removeDraftFile', error); return {error}; } } export async function updateDraftMessage(serverUrl: string, channelId: string, rootId: string, message: string, prepareRecordsOnly = false) { - const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; - if (!operator) { - return {error: `${serverUrl} database not found`}; - } + try { + const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + const draft = await getDraft(database, channelId, rootId); + if (!draft) { + if (!message) { + return {}; + } - const draft = await getDraft(operator.database, channelId, rootId); - if (!draft) { - if (!message) { - return {}; + const newDraft: Draft = { + channel_id: channelId, + root_id: rootId, + message, + }; + + return operator.handleDraft({drafts: [newDraft], prepareRecordsOnly}); } - const newDraft: Draft = { - channel_id: channelId, - root_id: rootId, - message, - }; + if (draft.message === message) { + return {draft}; + } - return operator.handleDraft({drafts: [newDraft], prepareRecordsOnly}); - } + if (draft.files.length === 0 && !message) { + draft.prepareDestroyPermanently(); + } else { + draft.prepareUpdate((d) => { + d.message = message; + }); + } - if (draft.message === message) { - return {draft}; - } - - if (draft.files.length === 0 && !message) { - draft.prepareDestroyPermanently(); - } else { - draft.prepareUpdate((d) => { - d.message = message; - }); - } - - try { if (!prepareRecordsOnly) { await operator.batchRecords([draft]); } return {draft}; } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed updateDraftMessage', error); return {error}; } } export async function addFilesToDraft(serverUrl: string, channelId: string, rootId: string, files: FileInfo[], prepareRecordsOnly = false) { - const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; - if (!operator) { - return {error: `${serverUrl} database not found`}; - } - - const draft = await getDraft(operator.database, channelId, rootId); - if (!draft) { - const newDraft: Draft = { - channel_id: channelId, - root_id: rootId, - files, - message: '', - }; - - return operator.handleDraft({drafts: [newDraft], prepareRecordsOnly}); - } - - draft.prepareUpdate((d) => { - d.files = [...draft.files, ...files]; - }); - try { + const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + const draft = await getDraft(database, channelId, rootId); + if (!draft) { + const newDraft: Draft = { + channel_id: channelId, + root_id: rootId, + files, + message: '', + }; + + return operator.handleDraft({drafts: [newDraft], prepareRecordsOnly}); + } + + draft.prepareUpdate((d) => { + d.files = [...draft.files, ...files]; + }); + if (!prepareRecordsOnly) { await operator.batchRecords([draft]); } return {draft}; } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed addFilesToDraft', error); return {error}; } } export const removeDraft = async (serverUrl: string, channelId: string, rootId = '') => { - const database = DatabaseManager.serverDatabases[serverUrl]?.database; - if (!database) { - return {error: `${serverUrl} database not found`}; - } + try { + const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + const draft = await getDraft(database, channelId, rootId); + if (draft) { + await database.write(async () => { + await draft.destroyPermanently(); + }); + } - const draft = await getDraft(database, channelId, rootId); - if (draft) { - await database.write(async () => { - await draft.destroyPermanently(); - }); + return {draft}; + } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed removeDraft', error); + return {error}; } - - return {draft}; }; diff --git a/app/actions/local/file.ts b/app/actions/local/file.ts index 215518cd4e..84dc67607d 100644 --- a/app/actions/local/file.ts +++ b/app/actions/local/file.ts @@ -5,21 +5,19 @@ import DatabaseManager from '@database/manager'; import {getFileById} from '@queries/servers/file'; export const updateLocalFile = async (serverUrl: string, file: FileInfo) => { - const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; - if (!operator) { - return {error: `${serverUrl} database not found`}; + try { + const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + return operator.handleFiles({files: [file], prepareRecordsOnly: false}); + } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed updateLocalFile', error); + return {error}; } - - return operator.handleFiles({files: [file], prepareRecordsOnly: false}); }; export const updateLocalFilePath = async (serverUrl: string, fileId: string, localPath: string) => { - const database = DatabaseManager.serverDatabases[serverUrl]?.database; - if (!database) { - return {error: `${serverUrl} database not found`}; - } - try { + const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); const file = await getFileById(database, fileId); if (file) { await database.write(async () => { @@ -31,6 +29,8 @@ export const updateLocalFilePath = async (serverUrl: string, fileId: string, loc return {error: undefined}; } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed updateLocalFilePath', error); return {error}; } }; diff --git a/app/actions/local/notification.ts b/app/actions/local/notification.ts index 1e83be557f..445ff91561 100644 --- a/app/actions/local/notification.ts +++ b/app/actions/local/notification.ts @@ -5,14 +5,9 @@ import DatabaseManager from '@database/manager'; import {getPostById, queryPostsInChannel, queryPostsInThread} from '@queries/servers/post'; export const updatePostSinceCache = async (serverUrl: string, notification: NotificationWithData) => { - const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; - if (!operator) { - return {error: `${serverUrl} database not found`}; - } - try { + const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); if (notification.payload?.channel_id) { - const {database} = operator; const chunks = await queryPostsInChannel(database, notification.payload.channel_id).fetch(); if (chunks.length) { const recent = chunks[0]; @@ -28,6 +23,8 @@ export const updatePostSinceCache = async (serverUrl: string, notification: Noti } return {}; } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed updatePostSinceCache', error); return {error}; } }; diff --git a/app/actions/local/post.ts b/app/actions/local/post.ts index b503cdcdfc..993bffbc54 100644 --- a/app/actions/local/post.ts +++ b/app/actions/local/post.ts @@ -12,17 +12,65 @@ import type PostModel from '@typings/database/models/servers/post'; import type UserModel from '@typings/database/models/servers/user'; export const sendAddToChannelEphemeralPost = async (serverUrl: string, user: UserModel, addedUsernames: string[], messages: string[], channeId: string, postRootId = '') => { - const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; - if (!operator) { - return {error: `${serverUrl} database not found`}; - } + try { + const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + const timestamp = Date.now(); + const posts = addedUsernames.map((addedUsername, index) => { + const message = messages[index]; + return { + id: generateId(), + user_id: user.id, + channel_id: channeId, + message, + type: Post.POST_TYPES.EPHEMERAL_ADD_TO_CHANNEL as PostType, + create_at: timestamp, + edit_at: 0, + update_at: timestamp, + delete_at: 0, + is_pinned: false, + original_id: '', + hashtags: '', + pending_post_id: '', + reply_count: 0, + metadata: {}, + root_id: postRootId, + props: { + username: user.username, + addedUsername, + }, + } as Post; + }); - const timestamp = Date.now(); - const posts = addedUsernames.map((addedUsername, index) => { - const message = messages[index]; - return { + await operator.handlePosts({ + actionType: ActionType.POSTS.RECEIVED_NEW, + order: posts.map((p) => p.id), + posts, + }); + + return {posts}; + } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed sendAddToChannelEphemeralPost', error); + return {error}; + } +}; + +export const sendEphemeralPost = async (serverUrl: string, message: string, channeId: string, rootId = '', userId?: string) => { + try { + const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + if (!channeId) { + throw new Error('channel Id not defined'); + } + + let authorId = userId; + if (!authorId) { + authorId = await getCurrentUserId(database); + } + + const timestamp = Date.now(); + const post = { id: generateId(), - user_id: user.id, + user_id: authorId, channel_id: channeId, message, type: Post.POST_TYPES.EPHEMERAL_ADD_TO_CHANNEL as PostType, @@ -36,122 +84,82 @@ export const sendAddToChannelEphemeralPost = async (serverUrl: string, user: Use pending_post_id: '', reply_count: 0, metadata: {}, - root_id: postRootId, - props: { - username: user.username, - addedUsername, - }, + participants: null, + root_id: rootId, + props: {}, } as Post; - }); - await operator.handlePosts({ - actionType: ActionType.POSTS.RECEIVED_NEW, - order: posts.map((p) => p.id), - posts, - }); + await operator.handlePosts({ + actionType: ActionType.POSTS.RECEIVED_NEW, + order: [post.id], + posts: [post], + }); - return {posts}; -}; - -export const sendEphemeralPost = async (serverUrl: string, message: string, channeId: string, rootId = '', userId?: string) => { - const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; - if (!operator) { - return {error: `${serverUrl} database not found`}; + return {post}; + } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed sendEphemeralPost', error); + return {error}; } - - if (!channeId) { - return {error: 'channel Id not defined'}; - } - - let authorId = userId; - if (!authorId) { - authorId = await getCurrentUserId(operator.database); - } - - const timestamp = Date.now(); - const post = { - id: generateId(), - user_id: authorId, - channel_id: channeId, - message, - type: Post.POST_TYPES.EPHEMERAL_ADD_TO_CHANNEL as PostType, - create_at: timestamp, - edit_at: 0, - update_at: timestamp, - delete_at: 0, - is_pinned: false, - original_id: '', - hashtags: '', - pending_post_id: '', - reply_count: 0, - metadata: {}, - participants: null, - root_id: rootId, - props: {}, - } as Post; - - await operator.handlePosts({ - actionType: ActionType.POSTS.RECEIVED_NEW, - order: [post.id], - posts: [post], - }); - - return {post}; }; export async function removePost(serverUrl: string, post: PostModel | Post) { - const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; - if (!operator) { - return {error: `${serverUrl} database not found`}; - } + try { + const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + if (post.type === Post.POST_TYPES.COMBINED_USER_ACTIVITY && post.props?.system_post_ids) { + const systemPostIds = getPostIdsForCombinedUserActivityPost(post.id); + const removeModels = []; + for await (const id of systemPostIds) { + const postModel = await getPostById(database, id); + if (postModel) { + const preparedPost = await prepareDeletePost(postModel); + removeModels.push(...preparedPost); + } + } - if (post.type === Post.POST_TYPES.COMBINED_USER_ACTIVITY && post.props?.system_post_ids) { - const systemPostIds = getPostIdsForCombinedUserActivityPost(post.id); - const removeModels = []; - for await (const id of systemPostIds) { - const postModel = await getPostById(operator.database, id); + if (removeModels.length) { + await operator.batchRecords(removeModels); + } + } else { + const postModel = await getPostById(database, post.id); if (postModel) { const preparedPost = await prepareDeletePost(postModel); - removeModels.push(...preparedPost); + if (preparedPost.length) { + await operator.batchRecords(preparedPost); + } } } - if (removeModels.length) { - await operator.batchRecords(removeModels); - } - } else { - const postModel = await getPostById(operator.database, post.id); - if (postModel) { - const preparedPost = await prepareDeletePost(postModel); - if (preparedPost.length) { - await operator.batchRecords(preparedPost); - } - } + return {post}; + } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed removePost', error); + return {error}; } - - return {post}; } export async function markPostAsDeleted(serverUrl: string, post: Post, prepareRecordsOnly = false) { - const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; - if (!operator) { - return {error: `${serverUrl} database not found`}; - } + try { + const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + const dbPost = await getPostById(database, post.id); + if (!dbPost) { + throw new Error('Post not found'); + } - const dbPost = await getPostById(operator.database, post.id); - if (!dbPost) { - return {error: 'Post not found'}; - } + const model = dbPost.prepareUpdate((p) => { + p.deleteAt = Date.now(); + p.message = ''; + p.metadata = null; + p.props = undefined; + }); - const model = dbPost.prepareUpdate((p) => { - p.deleteAt = Date.now(); - p.message = ''; - p.metadata = null; - p.props = undefined; - }); - - if (!prepareRecordsOnly) { - await operator.batchRecords([dbPost]); + if (!prepareRecordsOnly) { + await operator.batchRecords([dbPost]); + } + return {model}; + } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed markPostAsDeleted', error); + return {error}; } - return {model}; } diff --git a/app/actions/local/reactions.ts b/app/actions/local/reactions.ts index fe42ab1605..1f78f7b693 100644 --- a/app/actions/local/reactions.ts +++ b/app/actions/local/reactions.ts @@ -9,19 +9,13 @@ import {getEmojiFirstAlias} from '@utils/emoji/helpers'; const MAXIMUM_RECENT_EMOJI = 27; export const addRecentReaction = async (serverUrl: string, emojiNames: string[], prepareRecordsOnly = false) => { - const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; - if (!operator) { - return {error: `${serverUrl} database not found`}; - } - const {database} = operator; - - if (!emojiNames.length) { - return []; - } - - let recent = await getRecentReactions(database); - try { + if (!emojiNames.length) { + return []; + } + + const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + let recent = await getRecentReactions(database); const recentEmojis = new Set(recent); const aliases = emojiNames.map((e) => getEmojiFirstAlias(e)); for (const alias of aliases) { @@ -43,6 +37,8 @@ export const addRecentReaction = async (serverUrl: string, emojiNames: string[], prepareRecordsOnly, }); } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed addRecentReaction', error); return {error}; } }; diff --git a/app/actions/local/team.ts b/app/actions/local/team.ts index c93868fed9..7f8e077c8a 100644 --- a/app/actions/local/team.ts +++ b/app/actions/local/team.ts @@ -5,31 +5,28 @@ import DatabaseManager from '@database/manager'; import {prepareDeleteTeam, getMyTeamById, removeTeamFromTeamHistory} from '@queries/servers/team'; export async function removeUserFromTeam(serverUrl: string, teamId: string) { - const serverDatabase = DatabaseManager.serverDatabases[serverUrl]; - if (!serverDatabase) { - return; - } - - const {operator, database} = serverDatabase; - - const myTeam = await getMyTeamById(database, teamId); - if (myTeam) { - const team = await myTeam.team.fetch(); - if (!team) { - return; - } - const models = await prepareDeleteTeam(team); - const system = await removeTeamFromTeamHistory(operator, team.id, true); - if (system) { - models.push(...system); - } - if (models.length) { - try { + try { + const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + const myTeam = await getMyTeamById(database, teamId); + if (myTeam) { + const team = await myTeam.team.fetch(); + if (!team) { + throw new Error('Team not found'); + } + const models = await prepareDeleteTeam(team); + const system = await removeTeamFromTeamHistory(operator, team.id, true); + if (system) { + models.push(...system); + } + if (models.length) { await operator.batchRecords(models); - } catch { - // eslint-disable-next-line no-console - console.log('FAILED TO BATCH CHANGES FOR REMOVE USER FROM TEAM'); } } + + return {error: undefined}; + } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed removeUserFromTeam', error); + return {error}; } } diff --git a/app/actions/local/thread.ts b/app/actions/local/thread.ts index a9cf1feb96..64e263346a 100644 --- a/app/actions/local/thread.ts +++ b/app/actions/local/thread.ts @@ -23,24 +23,19 @@ import {changeOpacity, setThemeDefaults, updateThemeIfNeeded} from '@utils/theme import type Model from '@nozbe/watermelondb/Model'; export const switchToGlobalThreads = async (serverUrl: string, teamId?: string, prepareRecordsOnly = false) => { - const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; - if (!operator) { - return {error: `${serverUrl} database not found`}; - } - - const {database} = operator; - const models: Model[] = []; - - let teamIdToUse = teamId; - if (!teamId) { - teamIdToUse = await getCurrentTeamId(database); - } - - if (!teamIdToUse) { - return {error: 'no team to switch to'}; - } - try { + const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + const models: Model[] = []; + + let teamIdToUse = teamId; + if (!teamId) { + teamIdToUse = await getCurrentTeamId(database); + } + + if (!teamIdToUse) { + throw new Error('no team to switch to'); + } + await setCurrentTeamAndChannelId(operator, teamIdToUse, ''); const history = await addChannelToTeamHistory(operator, teamIdToUse, Screens.GLOBAL_THREADS, true); models.push(...history); @@ -55,34 +50,30 @@ export const switchToGlobalThreads = async (serverUrl: string, teamId?: string, } else { goToScreen(Screens.GLOBAL_THREADS, '', {}, {topBar: {visible: false}}); } + + return {models}; } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed switchToGlobalThreads', error); return {error}; } - - return {models}; }; export const switchToThread = async (serverUrl: string, rootId: string, isFromNotification = false) => { - const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; - if (!operator) { - return {error: `${serverUrl} database not found`}; - } - - const {database} = operator; - try { + const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); const user = await getCurrentUser(database); if (!user) { - return {error: 'User not found'}; + throw new Error('User not found'); } const post = await getPostById(database, rootId); if (!post) { - return {error: 'Post not found'}; + throw new Error('Post not found'); } const channel = await getChannelById(database, post.channelId); if (!channel) { - return {error: 'Channel not found'}; + throw new Error('Channel not found'); } const system = await getCommonSystemValues(database); @@ -180,6 +171,8 @@ export const switchToThread = async (serverUrl: string, rootId: string, isFromNo return {}; } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed switchToThread', error); return {error}; } }; @@ -188,105 +181,104 @@ export const switchToThread = async (serverUrl: string, rootId: string, isFromNo // 1. If a reply, then update the reply_count, add user as the participant // 2. Else add the post as a thread export async function createThreadFromNewPost(serverUrl: string, post: Post, prepareRecordsOnly = false) { - const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; - if (!operator) { - return {error: `${serverUrl} database not found`}; - } - - const models: Model[] = []; - if (post.root_id) { + try { + const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + const models: Model[] = []; + if (post.root_id) { // Update the thread data: `reply_count` - const {model: threadModel} = await updateThread(serverUrl, post.root_id, {reply_count: post.reply_count}, true); - if (threadModel) { - models.push(threadModel); + const {model: threadModel} = await updateThread(serverUrl, post.root_id, {reply_count: post.reply_count}, true); + if (threadModel) { + models.push(threadModel); + } + + // Add user as a participant to the thread + const threadParticipantModels = await operator.handleThreadParticipants({ + threadsParticipants: [{ + thread_id: post.root_id, + participants: [{ + thread_id: post.root_id, + id: post.user_id, + }], + }], + prepareRecordsOnly: true, + skipSync: true, + }); + models.push(...threadParticipantModels); + } else { // If the post is a root post, then we need to add it to the thread table + const threadModels = await prepareThreadsFromReceivedPosts(operator, [post]); + models.push(...threadModels); } - // Add user as a participant to the thread - const threadParticipantModels = await operator.handleThreadParticipants({ - threadsParticipants: [{ - thread_id: post.root_id, - participants: [{ - thread_id: post.root_id, - id: post.user_id, - }], - }], - prepareRecordsOnly: true, - skipSync: true, - }); - models.push(...threadParticipantModels); - } else { // If the post is a root post, then we need to add it to the thread table - const threadModels = await prepareThreadsFromReceivedPosts(operator, [post]); - models.push(...threadModels); - } + if (!prepareRecordsOnly) { + await operator.batchRecords(models); + } - if (!prepareRecordsOnly) { - await operator.batchRecords(models); + return {models}; + } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed createThreadFromNewPost', error); + return {error}; } - - return {models}; } // On receiving threads, Along with the "threads" & "thread participants", extract and save "posts" & "users" export async function processReceivedThreads(serverUrl: string, threads: Thread[], teamId: string, loadedInGlobalThreads = false, prepareRecordsOnly = false) { - const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; - if (!operator) { - return {error: `${serverUrl} database not found`}; - } + try { + const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + const currentUserId = await getCurrentUserId(database); - const {database} = operator; - const currentUserId = await getCurrentUserId(database); + const posts: Post[] = []; + const users: UserProfile[] = []; - const posts: Post[] = []; - const users: UserProfile[] = []; + // Extract posts & users from the received threads + for (let i = 0; i < threads.length; i++) { + const {participants, post} = threads[i]; + posts.push(post); + participants.forEach((participant) => { + if (currentUserId !== participant.id) { + users.push(participant); + } + }); + } - // Extract posts & users from the received threads - for (let i = 0; i < threads.length; i++) { - const {participants, post} = threads[i]; - posts.push(post); - participants.forEach((participant) => { - if (currentUserId !== participant.id) { - users.push(participant); - } - }); - } - - const postModels = await operator.handlePosts({ - actionType: ActionType.POSTS.RECEIVED_IN_CHANNEL, - order: [], - posts, - prepareRecordsOnly: true, - }); - - const threadModels = await operator.handleThreads({ - threads, - teamId, - prepareRecordsOnly: true, - loadedInGlobalThreads, - }); - - const models = [...postModels, ...threadModels]; - - if (users.length) { - const userModels = await operator.handleUsers({ - users, + const postModels = await operator.handlePosts({ + actionType: ActionType.POSTS.RECEIVED_IN_CHANNEL, + order: [], + posts, prepareRecordsOnly: true, }); - models.push(...userModels); - } - if (!prepareRecordsOnly) { - await operator.batchRecords(models); + const threadModels = await operator.handleThreads({ + threads, + teamId, + prepareRecordsOnly: true, + loadedInGlobalThreads, + }); + + const models = [...postModels, ...threadModels]; + + if (users.length) { + const userModels = await operator.handleUsers({ + users, + prepareRecordsOnly: true, + }); + models.push(...userModels); + } + + if (!prepareRecordsOnly) { + await operator.batchRecords(models); + } + return {models}; + } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed processReceivedThreads', error); + return {error}; } - return {models}; } export async function markTeamThreadsAsRead(serverUrl: string, teamId: string, prepareRecordsOnly = false) { - const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; - if (!operator) { - return {error: `${serverUrl} database not found`}; - } try { - const {database} = operator; + const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); const threads = await queryThreadsInTeam(database, teamId, true, true, true).fetch(); const models = threads.map((thread) => thread.prepareUpdate((record) => { record.unreadMentions = 0; @@ -298,35 +290,35 @@ export async function markTeamThreadsAsRead(serverUrl: string, teamId: string, p } return {models}; } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed markTeamThreadsAsRead', error); return {error}; } } export async function updateThread(serverUrl: string, threadId: string, updatedThread: Partial, prepareRecordsOnly = false) { - const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; - if (!operator) { - return {error: `${serverUrl} database not found`}; - } - try { - const {database} = operator; + const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); const thread = await getThreadById(database, threadId); - if (thread) { - const model = thread.prepareUpdate((record) => { - record.isFollowing = updatedThread.is_following ?? record.isFollowing; - record.replyCount = updatedThread.reply_count ?? record.replyCount; - - record.lastViewedAt = updatedThread.last_viewed_at ?? record.lastViewedAt; - record.unreadMentions = updatedThread.unread_mentions ?? record.unreadMentions; - record.unreadReplies = updatedThread.unread_replies ?? record.unreadReplies; - }); - if (!prepareRecordsOnly) { - await operator.batchRecords([model]); - } - return {model}; + if (!thread) { + throw new Error('Thread not found'); } - return {error: 'Thread not found'}; + + const model = thread.prepareUpdate((record) => { + record.isFollowing = updatedThread.is_following ?? record.isFollowing; + record.replyCount = updatedThread.reply_count ?? record.replyCount; + + record.lastViewedAt = updatedThread.last_viewed_at ?? record.lastViewedAt; + record.unreadMentions = updatedThread.unread_mentions ?? record.unreadMentions; + record.unreadReplies = updatedThread.unread_replies ?? record.unreadReplies; + }); + if (!prepareRecordsOnly) { + await operator.batchRecords([model]); + } + return {model}; } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed markTeamThreadsAsRead', error); return {error}; } } diff --git a/app/actions/local/timezone.ts b/app/actions/local/timezone.ts deleted file mode 100644 index cfda4071ee..0000000000 --- a/app/actions/local/timezone.ts +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import {getTimeZone} from 'react-native-localize'; - -import type UserModel from '@typings/database/models/servers/user'; - -export const isTimezoneEnabled = (config: Partial) => { - return config?.ExperimentalTimezone === 'true'; -}; - -export function getDeviceTimezone() { - return getTimeZone(); -} - -export const getUserTimezone = (currentUser: UserModel) => { - if (currentUser?.timezone) { - return { - ...currentUser?.timezone, - useAutomaticTimezone: currentUser?.timezone?.useAutomaticTimezone === 'true', - }; - } - - return { - useAutomaticTimezone: true, - automaticTimezone: '', - manualTimezone: '', - }; -}; diff --git a/app/actions/local/user.ts b/app/actions/local/user.ts index 738aa1d23a..90ffcb9379 100644 --- a/app/actions/local/user.ts +++ b/app/actions/local/user.ts @@ -13,107 +13,95 @@ import type Model from '@nozbe/watermelondb/Model'; import type UserModel from '@typings/database/models/servers/user'; export async function setCurrentUserStatusOffline(serverUrl: string) { - const serverDatabase = DatabaseManager.serverDatabases[serverUrl]; - if (!serverDatabase) { - return {error: `No database present for ${serverUrl}`}; - } - - const {database, operator} = serverDatabase; - - const user = await getCurrentUser(database); - if (!user) { - return {error: `No current user for ${serverUrl}`}; - } - - user.prepareStatus(General.OFFLINE); - try { - await operator.batchRecords([user]); - } catch { - // eslint-disable-next-line no-console - console.log('FAILED TO BATCH CHANGES FOR SET CURRENT USER STATUS OFFLINE'); - } + const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + const user = await getCurrentUser(database); + if (!user) { + throw new Error(`No current user for ${serverUrl}`); + } - return null; + user.prepareStatus(General.OFFLINE); + await operator.batchRecords([user]); + return null; + } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed setCurrentUserStatusOffline', error); + return {error}; + } } export async function updateLocalCustomStatus(serverUrl: string, user: UserModel, customStatus?: UserCustomStatus) { - const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; - if (!operator) { - return {error: `${serverUrl} database not found`}; - } + try { + const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + const models: Model[] = []; + const currentProps = {...user.props, customStatus: customStatus || {}}; + const userModel = user.prepareUpdate((u: UserModel) => { + u.props = currentProps; + }); - const models: Model[] = []; - const currentProps = {...user.props, customStatus: customStatus || {}}; - const userModel = user.prepareUpdate((u: UserModel) => { - u.props = currentProps; - }); + models.push(userModel); + if (customStatus) { + const recent = await updateRecentCustomStatuses(serverUrl, customStatus, true); + if (Array.isArray(recent)) { + models.push(...recent); + } - models.push(userModel); - if (customStatus) { - const recent = await updateRecentCustomStatuses(serverUrl, customStatus, true); - if (Array.isArray(recent)) { - models.push(...recent); - } - - if (customStatus.emoji) { - const recentEmojis = await addRecentReaction(serverUrl, [customStatus.emoji], true); - if (Array.isArray(recentEmojis)) { - models.push(...recentEmojis); + if (customStatus.emoji) { + const recentEmojis = await addRecentReaction(serverUrl, [customStatus.emoji], true); + if (Array.isArray(recentEmojis)) { + models.push(...recentEmojis); + } } } - } - try { await operator.batchRecords(models); - } catch { - // eslint-disable-next-line no-console - console.log('FAILED TO BATCH CHANGES FOR UPDATING CUSTOM STATUS'); - } - return {}; + return {}; + } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed updateLocalCustomStatus', error); + return {error}; + } } export const updateRecentCustomStatuses = async (serverUrl: string, customStatus: UserCustomStatus, prepareRecordsOnly = false, remove = false) => { - const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; - if (!operator) { - return {error: `${serverUrl} database not found`}; - } - - const recentStatuses = await getRecentCustomStatuses(operator.database); - const index = recentStatuses.findIndex((cs) => ( - cs.emoji === customStatus.emoji && + try { + const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + const recentStatuses = await getRecentCustomStatuses(database); + const index = recentStatuses.findIndex((cs) => ( + cs.emoji === customStatus.emoji && cs.text === customStatus.text && cs.duration === customStatus.duration - )); + )); - if (index !== -1) { - recentStatuses.splice(index, 1); + if (index !== -1) { + recentStatuses.splice(index, 1); + } + + if (!remove) { + recentStatuses.unshift(customStatus); + } + + return operator.handleSystem({ + systems: [{ + id: SYSTEM_IDENTIFIERS.RECENT_CUSTOM_STATUS, + value: JSON.stringify(recentStatuses), + }], + prepareRecordsOnly, + }); + } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed updateRecentCustomStatuses', error); + return {error}; } - - if (!remove) { - recentStatuses.unshift(customStatus); - } - - return operator.handleSystem({ - systems: [{ - id: SYSTEM_IDENTIFIERS.RECENT_CUSTOM_STATUS, - value: JSON.stringify(recentStatuses), - }], - prepareRecordsOnly, - }); }; export const updateLocalUser = async ( serverUrl: string, userDetails: Partial & { status?: string}, ) => { - const database = DatabaseManager.serverDatabases[serverUrl]?.database; - if (!database) { - return {error: `${serverUrl} database not found`}; - } - try { + const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); const user = await getCurrentUser(database); if (user) { await database.write(async () => { @@ -135,21 +123,17 @@ export const updateLocalUser = async ( }); }); } + return {user}; } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed updateLocalUser', error); return {error}; } - - return {}; }; export const storeProfile = async (serverUrl: string, profile: UserProfile) => { - const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; - if (!operator) { - return {error: `${serverUrl} database not found`}; - } - try { - const {database} = operator; + const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); const user = await getUserById(database, profile.id); if (user) { return {user}; @@ -162,6 +146,8 @@ export const storeProfile = async (serverUrl: string, profile: UserProfile) => { return {user: records[0]}; } catch (error) { + // eslint-disable-next-line no-console + console.log('Failed storeProfile', error); return {error}; } }; diff --git a/app/actions/remote/session.ts b/app/actions/remote/session.ts index 2aff726157..c7a2689a5d 100644 --- a/app/actions/remote/session.ts +++ b/app/actions/remote/session.ts @@ -3,7 +3,6 @@ import {DeviceEventEmitter} from 'react-native'; -import {getDeviceTimezone, isTimezoneEnabled} from '@actions/local/timezone'; import {Database, Events} from '@constants'; import {SYSTEM_IDENTIFIERS} from '@constants/database'; import DatabaseManager from '@database/manager'; @@ -14,6 +13,7 @@ import {getDeviceToken} from '@queries/app/global'; import {getCurrentUserId, getCommonSystemValues} from '@queries/servers/system'; import EphemeralStore from '@store/ephemeral_store'; import {getCSRFFromCookie} from '@utils/security'; +import {getDeviceTimezone, isTimezoneEnabled} from '@utils/timezone'; import {loginEntry} from './entry'; import {fetchDataRetentionPolicy} from './systems'; diff --git a/app/actions/remote/user.ts b/app/actions/remote/user.ts index 18cc2aed58..837e2383d1 100644 --- a/app/actions/remote/user.ts +++ b/app/actions/remote/user.ts @@ -7,7 +7,6 @@ import {Model} from '@nozbe/watermelondb'; import {chunk} from 'lodash'; import {updateChannelsDisplayName} from '@actions/local/channel'; -import {getUserTimezone} from '@actions/local/timezone'; import {updateRecentCustomStatuses, updateLocalUser} from '@actions/local/user'; import {fetchRolesIfNeeded} from '@actions/remote/role'; import {General} from '@constants'; @@ -17,7 +16,7 @@ import NetworkManager from '@managers/network_manager'; import {getMembersCountByChannelsId, queryChannelsByTypes} from '@queries/servers/channel'; import {getCurrentTeamId, getCurrentUserId} from '@queries/servers/system'; import {getCurrentUser, getUserById, prepareUsers, queryAllUsers, queryUsersById, queryUsersByUsername} from '@queries/servers/user'; -import {removeUserFromList} from '@utils/user'; +import {getUserTimezoneProps, removeUserFromList} from '@utils/user'; import {forceLogoutIfNecessary} from './session'; @@ -804,7 +803,7 @@ export const autoUpdateTimezone = async (serverUrl: string, {deviceTimezone, use return null; } - const currentTimezone = getUserTimezone(currentUser); + const currentTimezone = getUserTimezoneProps(currentUser); const newTimezoneExists = currentTimezone.automaticTimezone !== deviceTimezone; if (currentTimezone.useAutomaticTimezone && newTimezoneExists) { diff --git a/app/components/custom_status/custom_status_expiry.tsx b/app/components/custom_status/custom_status_expiry.tsx index c3e484db81..7277fd189b 100644 --- a/app/components/custom_status/custom_status_expiry.tsx +++ b/app/components/custom_status/custom_status_expiry.tsx @@ -9,7 +9,6 @@ import {Text, TextStyle} from 'react-native'; import {of as of$} from 'rxjs'; import {switchMap} from 'rxjs/operators'; -import {getUserTimezone} from '@actions/local/timezone'; import FormattedDate from '@components/formatted_date'; import FormattedText from '@components/formatted_text'; import FormattedTime from '@components/formatted_time'; @@ -19,6 +18,7 @@ import {queryPreferencesByCategoryAndName} from '@queries/servers/preference'; import {observeCurrentUser} from '@queries/servers/user'; import {getCurrentMomentForTimezone} from '@utils/helpers'; import {makeStyleSheetFromTheme} from '@utils/theme'; +import {getUserTimezoneProps} from '@utils/user'; import type {WithDatabaseArgs} from '@typings/database/database'; import type UserModel from '@typings/database/models/servers/user'; @@ -46,7 +46,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { }); const CustomStatusExpiry = ({currentUser, isMilitaryTime, showPrefix, showTimeCompulsory, showToday, testID = '', textStyles = {}, theme, time, withinBrackets}: Props) => { - const userTimezone = getUserTimezone(currentUser); + const userTimezone = getUserTimezoneProps(currentUser); const timezone = userTimezone.useAutomaticTimezone ? userTimezone.automaticTimezone : userTimezone.manualTimezone; const styles = getStyleSheet(theme); const currentMomentTime = getCurrentMomentForTimezone(timezone); diff --git a/app/database/manager/__mocks__/index.ts b/app/database/manager/__mocks__/index.ts index fdba301a71..2e84d37a74 100644 --- a/app/database/manager/__mocks__/index.ts +++ b/app/database/manager/__mocks__/index.ts @@ -229,6 +229,24 @@ class DatabaseManager { return undefined; }; + public getAppDatabaseAndOperator = () => { + const app = this.appDatabase; + if (!app) { + throw new Error('App database not found'); + } + + return app; + }; + + public getServerDatabaseAndOperator = (serverUrl: string) => { + const server = this.serverDatabases[serverUrl]; + if (!server) { + throw new Error(`${serverUrl} database not found`); + } + + return server; + }; + public getActiveServerDatabase = async (): Promise => { const database = this.appDatabase?.database; if (database) { diff --git a/app/database/manager/index.ts b/app/database/manager/index.ts index f3027ae0d0..e30b1a2d8d 100644 --- a/app/database/manager/index.ts +++ b/app/database/manager/index.ts @@ -307,6 +307,38 @@ class DatabaseManager { return undefined; }; + /** + * getAppDatabaseAndOperator: Helper function that returns App the database and operator. + * use within a try/catch block + * @returns AppDatabase + * @throws Error + */ + public getAppDatabaseAndOperator = () => { + const app = this.appDatabase; + if (!app) { + throw new Error('App database not found'); + } + + return app; + }; + + /** + * getServerDatabaseAndOperator: Helper function that returns the database and operator + * for a specific server. + * use within a try/catch block + * @param serverUrl the url of the server + * @returns ServerDatabase + * @throws Error + */ + public getServerDatabaseAndOperator = (serverUrl: string) => { + const server = this.serverDatabases[serverUrl]; + if (!server) { + throw new Error(`${serverUrl} database not found`); + } + + return server; + }; + /** * setActiveServerDatabase: Set the new active server database. * This method should be called when switching to another server. diff --git a/app/utils/timezone.ts b/app/utils/timezone.ts new file mode 100644 index 0000000000..ca3e0f536f --- /dev/null +++ b/app/utils/timezone.ts @@ -0,0 +1,12 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {getTimeZone} from 'react-native-localize'; + +export const isTimezoneEnabled = (config: Partial) => { + return config?.ExperimentalTimezone === 'true'; +}; + +export function getDeviceTimezone() { + return getTimeZone(); +} diff --git a/app/utils/user/index.ts b/app/utils/user/index.ts index 039db27f89..0f72379273 100644 --- a/app/utils/user/index.ts +++ b/app/utils/user/index.ts @@ -101,6 +101,21 @@ export const getUsersByUsername = (users: UserModel[]) => { return usersByUsername; }; +export const getUserTimezoneProps = (currentUser: UserModel) => { + if (currentUser?.timezone) { + return { + ...currentUser?.timezone, + useAutomaticTimezone: currentUser?.timezone?.useAutomaticTimezone === 'true', + }; + } + + return { + useAutomaticTimezone: true, + automaticTimezone: '', + manualTimezone: '', + }; +}; + export const getUserTimezone = (user: UserModel) => { return getTimezone(user.timezone); };