diff --git a/app/actions/local/channel.ts b/app/actions/local/channel.ts index 5c2de684b..03ab9403f 100644 --- a/app/actions/local/channel.ts +++ b/app/actions/local/channel.ts @@ -15,7 +15,7 @@ import { getMyChannel, getChannelById, queryUsersOnChannel, queryUserChannelsByTypes, } from '@queries/servers/channel'; import {queryPreferencesByCategoryAndName} from '@queries/servers/preference'; -import {prepareCommonSystemValues, PrepareCommonSystemValuesArgs, getCommonSystemValues, getCurrentTeamId, setCurrentChannelId, getCurrentUserId} from '@queries/servers/system'; +import {prepareCommonSystemValues, PrepareCommonSystemValuesArgs, getCommonSystemValues, getCurrentTeamId, setCurrentChannelId, getCurrentUserId, getConfig, getLicense} from '@queries/servers/system'; import {addChannelToTeamHistory, addTeamToTeamHistory, getTeamById, removeChannelFromTeamHistory} from '@queries/servers/team'; import {getCurrentUser, queryUsersById} from '@queries/servers/user'; import {dismissAllModalsAndPopToRoot, dismissAllModalsAndPopToScreen} from '@screens/navigation'; @@ -365,9 +365,10 @@ export async function updateChannelsDisplayName(serverUrl: string, channels: Cha return {}; } - const {config, license} = await getCommonSystemValues(database); + const license = await getLicense(database); + const config = await getConfig(database); const preferences = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT).fetch(); - const displaySettings = getTeammateNameDisplaySetting(preferences, config, license); + const displaySettings = getTeammateNameDisplaySetting(preferences, config.LockTeammateNameDisplay, config.TeammateNameDisplay, license); const models: Model[] = []; for await (const channel of channels) { let newDisplayName = ''; diff --git a/app/actions/local/systems.ts b/app/actions/local/systems.ts index 3a192d990..ea2f15f2b 100644 --- a/app/actions/local/systems.ts +++ b/app/actions/local/systems.ts @@ -6,7 +6,7 @@ import deepEqual from 'deep-equal'; import {SYSTEM_IDENTIFIERS} from '@constants/database'; import DatabaseManager from '@database/manager'; import {getServerCredentials} from '@init/credentials'; -import {getCommonSystemValues} from '@queries/servers/system'; +import {getConfig, getLicense} from '@queries/servers/system'; import {logError} from '@utils/log'; export async function storeConfigAndLicense(serverUrl: string, config: ClientConfig, license: ClientLicense) { @@ -14,17 +14,11 @@ export async function storeConfigAndLicense(serverUrl: string, config: ClientCon // If we have credentials for this server then update the values in the database const credentials = await getServerCredentials(serverUrl); if (credentials) { - const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); - const current = await getCommonSystemValues(operator.database); + const {operator, database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + const currentLicense = await getLicense(database); const systems: IdValue[] = []; - if (!deepEqual(config, current.config)) { - systems.push({ - id: SYSTEM_IDENTIFIERS.CONFIG, - value: JSON.stringify(config), - }); - } - if (!deepEqual(license, current.license)) { + if (!deepEqual(license, currentLicense)) { systems.push({ id: SYSTEM_IDENTIFIERS.LICENSE, value: JSON.stringify(license), @@ -34,8 +28,43 @@ export async function storeConfigAndLicense(serverUrl: string, config: ClientCon if (systems.length) { await operator.handleSystem({systems, prepareRecordsOnly: false}); } + + await storeConfig(serverUrl, config); } } catch (error) { logError('An error occurred while saving config & license', error); } } + +export async function storeConfig(serverUrl: string, config: ClientConfig | undefined, prepareRecordsOnly = false) { + if (!config) { + return []; + } + const {operator, database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + const currentConfig = await getConfig(database); + const configsToUpdate: IdValue[] = []; + const configsToDelete: IdValue[] = []; + + let k: keyof ClientConfig; + for (k in config) { + if (currentConfig?.[k] !== config[k]) { + configsToUpdate.push({ + id: k, + value: config[k], + }); + } + } + for (k in currentConfig) { + if (config[k] === undefined) { + configsToDelete.push({ + id: k, + value: currentConfig[k], + }); + } + } + + if (configsToDelete.length || configsToUpdate.length) { + return operator.handleConfigs({configs: configsToUpdate, configsToDelete, prepareRecordsOnly}); + } + return []; +} diff --git a/app/actions/local/thread.ts b/app/actions/local/thread.ts index a06549f9f..8f25bb9ae 100644 --- a/app/actions/local/thread.ts +++ b/app/actions/local/thread.ts @@ -8,7 +8,7 @@ import DatabaseManager from '@database/manager'; import {getTranslations, t} from '@i18n'; import {getChannelById} from '@queries/servers/channel'; import {getPostById} from '@queries/servers/post'; -import {getCommonSystemValues, getCurrentTeamId, getCurrentUserId, prepareCommonSystemValues, PrepareCommonSystemValuesArgs, setCurrentTeamAndChannelId} from '@queries/servers/system'; +import {getCurrentTeamId, getCurrentUserId, prepareCommonSystemValues, PrepareCommonSystemValuesArgs, setCurrentTeamAndChannelId} from '@queries/servers/system'; import {addChannelToTeamHistory, addTeamToTeamHistory} from '@queries/servers/team'; import {getIsCRTEnabled, getThreadById, prepareThreadsFromReceivedPosts, queryThreadsInTeam} from '@queries/servers/thread'; import {getCurrentUser} from '@queries/servers/user'; @@ -74,12 +74,12 @@ export const switchToThread = async (serverUrl: string, rootId: string, isFromNo throw new Error('Channel not found'); } - const system = await getCommonSystemValues(database); + const currentTeamId = await getCurrentTeamId(database); const isTabletDevice = await isTablet(); - const teamId = channel.teamId || system.currentTeamId; + const teamId = channel.teamId || currentTeamId; let switchingTeams = false; - if (system.currentTeamId !== teamId) { + if (currentTeamId !== teamId) { const modelPromises: Array> = []; switchingTeams = true; modelPromises.push(addTeamToTeamHistory(operator, teamId, true)); diff --git a/app/actions/remote/channel.ts b/app/actions/remote/channel.ts index f034b621e..73e65ac22 100644 --- a/app/actions/remote/channel.ts +++ b/app/actions/remote/channel.ts @@ -523,7 +523,7 @@ export async function fetchDirectChannelsInfo(serverUrl: string, directChannels: const preferences = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS).fetch(); const config = await getConfig(database); const license = await getLicense(database); - const teammateDisplayNameSetting = getTeammateNameDisplaySetting(preferences, config, license); + const teammateDisplayNameSetting = getTeammateNameDisplaySetting(preferences, config?.LockTeammateNameDisplay, config?.TeammateNameDisplay, license); const currentUser = await getCurrentUser(database); const channels = directChannels.map((d) => d.toApi()); return fetchMissingDirectChannelsInfo(serverUrl, channels, currentUser?.locale, teammateDisplayNameSetting, currentUser?.id); @@ -743,8 +743,9 @@ export async function createDirectChannel(serverUrl: string, userId: string, dis created.display_name = displayName; } else { const preferences = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT).fetch(); - const system = await getCommonSystemValues(database); - const teammateDisplayNameSetting = getTeammateNameDisplaySetting(preferences || [], system.config, system.license); + const license = await getLicense(database); + const config = await getConfig(database); + const teammateDisplayNameSetting = getTeammateNameDisplaySetting(preferences || [], config.LockTeammateNameDisplay, config.TeammateNameDisplay, license); const {directChannels, users} = await fetchMissingDirectChannelsInfo(serverUrl, [created], currentUser.locale, teammateDisplayNameSetting, currentUser.id, true); created.display_name = directChannels?.[0].display_name || created.display_name; if (users?.length) { @@ -860,19 +861,16 @@ export async function fetchArchivedChannels(serverUrl: string, teamId: string, p } export async function createGroupChannel(serverUrl: string, userIds: string[]) { - const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; - if (!operator) { - return {error: `${serverUrl} database not found`}; - } - let client: Client; try { client = NetworkManager.getClient(serverUrl); } catch (error) { return {error}; } + try { - const currentUser = await getCurrentUser(operator.database); + const {operator, database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); + const currentUser = await getCurrentUser(database); if (!currentUser) { return {error: 'Cannot get the current user'}; } @@ -886,9 +884,10 @@ export async function createGroupChannel(serverUrl: string, userIds: string[]) { return {data: created}; } - const preferences = await queryPreferencesByCategoryAndName(operator.database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT).fetch(); - const system = await getCommonSystemValues(operator.database); - const teammateDisplayNameSetting = getTeammateNameDisplaySetting(preferences || [], system.config, system.license); + const preferences = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT).fetch(); + const license = await getLicense(database); + const config = await getConfig(database); + const teammateDisplayNameSetting = getTeammateNameDisplaySetting(preferences || [], config.LockTeammateNameDisplay, config.TeammateNameDisplay, license); const {directChannels, users} = await fetchMissingDirectChannelsInfo(serverUrl, [created], currentUser.locale, teammateDisplayNameSetting, currentUser.id, true); const member = { diff --git a/app/actions/remote/entry/app.ts b/app/actions/remote/entry/app.ts index 7040e45c0..2c8cdbe61 100644 --- a/app/actions/remote/entry/app.ts +++ b/app/actions/remote/entry/app.ts @@ -4,7 +4,7 @@ import {switchToChannelById} from '@actions/remote/channel'; import {fetchConfigAndLicense} from '@actions/remote/systems'; import DatabaseManager from '@database/manager'; -import {prepareCommonSystemValues, getCommonSystemValues, getCurrentTeamId, getWebSocketLastDisconnected, setCurrentTeamAndChannelId, getCurrentChannelId} from '@queries/servers/system'; +import {prepareCommonSystemValues, getCurrentTeamId, getWebSocketLastDisconnected, setCurrentTeamAndChannelId, getCurrentChannelId, getConfig, getLicense} from '@queries/servers/system'; import {getCurrentUser} from '@queries/servers/user'; import {deleteV1Data} from '@utils/file'; import {isTablet} from '@utils/helpers'; @@ -65,7 +65,8 @@ export async function appEntry(serverUrl: string, since = 0, isUpgrade = false) logInfo('ENTRY MODELS BATCHING TOOK', `${Date.now() - dt}ms`); const {id: currentUserId, locale: currentUserLocale} = meData?.user || (await getCurrentUser(database))!; - const {config, license} = await getCommonSystemValues(database); + const config = await getConfig(database); + const license = await getLicense(database); await deferredAppEntryActions(serverUrl, lastDisconnectedAt, currentUserId, currentUserLocale, prefData.preferences, config, license, teamData, chData, initialTeamId, switchToChannel ? initialChannelId : undefined); if (!since) { diff --git a/app/actions/remote/entry/common.ts b/app/actions/remote/entry/common.ts index e4f787ed6..dcf0de25c 100644 --- a/app/actions/remote/entry/common.ts +++ b/app/actions/remote/entry/common.ts @@ -145,7 +145,7 @@ export const fetchAppEntryData = async (serverUrl: string, sinceArg: number, ini const confReq = await fetchConfigAndLicense(serverUrl); const prefData = await fetchMyPreferences(serverUrl, fetchOnly); - const isCRTEnabled = Boolean(prefData.preferences && processIsCRTEnabled(prefData.preferences, confReq.config)); + const isCRTEnabled = Boolean(prefData.preferences && processIsCRTEnabled(prefData.preferences, confReq.config?.CollapsedThreads, confReq.config?.FeatureFlagCollapsedThreads)); if (prefData.preferences) { const crtToggled = await getHasCRTChanged(database, prefData.preferences); if (crtToggled) { @@ -306,7 +306,7 @@ export async function entryInitialChannelId(database: Database, requestedChannel export async function restDeferredAppEntryActions( serverUrl: string, since: number, currentUserId: string, currentUserLocale: string, preferences: PreferenceType[] | undefined, - config: ClientConfig, license: ClientLicense, teamData: MyTeamsRequest, chData: MyChannelsRequest | undefined, + config: ClientConfig, license: ClientLicense | undefined, teamData: MyTeamsRequest, chData: MyChannelsRequest | undefined, initialTeamId?: string, initialChannelId?: string) { // defer sidebar DM & GM profiles let channelsToFetchProfiles: Set|undefined; @@ -325,7 +325,7 @@ export async function restDeferredAppEntryActions( fetchTeamsChannelsAndUnreadPosts(serverUrl, since, teamData.teams, teamData.memberships, initialTeamId); } - if (preferences && processIsCRTEnabled(preferences, config)) { + if (preferences && processIsCRTEnabled(preferences, config.CollapsedThreads, config.FeatureFlagCollapsedThreads)) { if (initialTeamId) { await fetchNewThreads(serverUrl, initialTeamId, false); } @@ -348,7 +348,7 @@ export async function restDeferredAppEntryActions( setTimeout(async () => { if (channelsToFetchProfiles?.size) { - const teammateDisplayNameSetting = getTeammateNameDisplaySetting(preferences || [], config, license); + const teammateDisplayNameSetting = getTeammateNameDisplaySetting(preferences || [], config.LockTeammateNameDisplay, config.TeammateNameDisplay, license); fetchMissingDirectChannelsInfo(serverUrl, Array.from(channelsToFetchProfiles), currentUserLocale, teammateDisplayNameSetting, currentUserId); } }, FETCH_MISSING_DM_TIMEOUT); @@ -454,7 +454,7 @@ const restSyncAllChannelMembers = async (serverUrl: string) => { for (const myTeam of myTeams) { fetchMyChannelsForTeam(serverUrl, myTeam.id, false, 0, false, excludeDirect); excludeDirect = true; - if (preferences && processIsCRTEnabled(preferences, config)) { + if (preferences && processIsCRTEnabled(preferences, config.CollapsedThreads, config.FeatureFlagCollapsedThreads)) { fetchNewThreads(serverUrl, myTeam.id, false); } } diff --git a/app/actions/remote/entry/gql_common.ts b/app/actions/remote/entry/gql_common.ts index 3e0683531..3b70dc711 100644 --- a/app/actions/remote/entry/gql_common.ts +++ b/app/actions/remote/entry/gql_common.ts @@ -52,7 +52,7 @@ export async function deferredAppEntryGraphQLActions( } }, FETCH_UNREADS_TIMEOUT); - if (preferences && processIsCRTEnabled(preferences, config)) { + if (preferences && processIsCRTEnabled(preferences, config.CollapsedThreads, config.FeatureFlagCollapsedThreads)) { if (initialTeamId) { await fetchNewThreads(serverUrl, initialTeamId, false); } @@ -256,7 +256,7 @@ export const entry = async (serverUrl: string, teamId?: string, channelId?: stri export async function deferredAppEntryActions( serverUrl: string, since: number, currentUserId: string, currentUserLocale: string, preferences: PreferenceType[] | undefined, - config: ClientConfig, license: ClientLicense, teamData: MyTeamsRequest, chData: MyChannelsRequest | undefined, + config: ClientConfig, license: ClientLicense | undefined, teamData: MyTeamsRequest, chData: MyChannelsRequest | undefined, initialTeamId?: string, initialChannelId?: string) { let result; if (config?.FeatureFlagGraphQL === 'true') { diff --git a/app/actions/remote/entry/notification.ts b/app/actions/remote/entry/notification.ts index 5365afa8a..25eb4bb73 100644 --- a/app/actions/remote/entry/notification.ts +++ b/app/actions/remote/entry/notification.ts @@ -8,7 +8,7 @@ import {getDefaultThemeByAppearance} from '@context/theme'; import DatabaseManager from '@database/manager'; import {getMyChannel} from '@queries/servers/channel'; import {queryPreferencesByCategoryAndName} from '@queries/servers/preference'; -import {getCommonSystemValues, getCurrentTeamId, getWebSocketLastDisconnected, setCurrentTeamAndChannelId} from '@queries/servers/system'; +import {getConfig, getCurrentTeamId, getLicense, getWebSocketLastDisconnected, setCurrentTeamAndChannelId} from '@queries/servers/system'; import {getMyTeamById} from '@queries/servers/team'; import {getIsCRTEnabled} from '@queries/servers/thread'; import {getCurrentUser} from '@queries/servers/user'; @@ -136,7 +136,8 @@ export async function pushNotificationEntry(serverUrl: string, notification: Not await operator.batchRecords(models); const {id: currentUserId, locale: currentUserLocale} = (await getCurrentUser(operator.database))!; - const {config, license} = await getCommonSystemValues(operator.database); + const config = await getConfig(database); + const license = await getLicense(database); await deferredAppEntryActions(serverUrl, lastDisconnectedAt, currentUserId, currentUserLocale, prefData.preferences, config, license, teamData, chData, selectedTeamId, selectedChannelId); diff --git a/app/actions/remote/notifications.ts b/app/actions/remote/notifications.ts index f62f3758f..018c838ea 100644 --- a/app/actions/remote/notifications.ts +++ b/app/actions/remote/notifications.ts @@ -11,7 +11,7 @@ import {fetchMyTeam} from '@actions/remote/team'; import {fetchAndSwitchToThread} from '@actions/remote/thread'; import DatabaseManager from '@database/manager'; import {getMyChannel, getChannelById} from '@queries/servers/channel'; -import {getCommonSystemValues, getWebSocketLastDisconnected} from '@queries/servers/system'; +import {getCurrentTeamId, getWebSocketLastDisconnected} from '@queries/servers/system'; import {getMyTeamById} from '@queries/servers/team'; import {getIsCRTEnabled} from '@queries/servers/thread'; import {emitNotificationError} from '@utils/notification'; @@ -30,14 +30,14 @@ const fetchNotificationData = async (serverUrl: string, notification: Notificati } const {database} = operator; - const system = await getCommonSystemValues(database); + const currentTeamId = await getCurrentTeamId(database); let teamId = notification.payload?.team_id; let isDirectChannel = false; if (!teamId) { // If the notification payload does not have a teamId we assume is a DM/GM isDirectChannel = true; - teamId = system.currentTeamId; + teamId = currentTeamId; } // To make the switch faster we determine if we already have the team & channel @@ -129,13 +129,13 @@ export const openNotification = async (serverUrl: string, notification: Notifica const isCRTEnabled = await getIsCRTEnabled(database); const isThreadNotification = isCRTEnabled && Boolean(rootId); - const system = await getCommonSystemValues(database); + const currentTeamId = await getCurrentTeamId(database); const currentServerUrl = await DatabaseManager.getActiveServerUrl(); let teamId = notification.payload?.team_id; if (!teamId) { // If the notification payload does not have a teamId we assume is a DM/GM - teamId = system.currentTeamId; + teamId = currentTeamId; } if (currentServerUrl !== serverUrl) { diff --git a/app/actions/remote/retry.ts b/app/actions/remote/retry.ts index 783a5e4ce..497f691f0 100644 --- a/app/actions/remote/retry.ts +++ b/app/actions/remote/retry.ts @@ -1,6 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {storeConfig} from '@actions/local/systems'; import {Preferences} from '@constants'; import DatabaseManager from '@database/manager'; import {getPreferenceValue, getTeammateNameDisplaySetting} from '@helpers/api/preference'; @@ -9,7 +10,7 @@ import NetworkManager from '@managers/network_manager'; import {prepareCategories, prepareCategoryChannels} from '@queries/servers/categories'; import {prepareMyChannelsForTeam} from '@queries/servers/channel'; import {prepareMyPreferences, queryPreferencesByCategoryAndName} from '@queries/servers/preference'; -import {prepareCommonSystemValues, getCommonSystemValues} from '@queries/servers/system'; +import {prepareCommonSystemValues, getConfig, getLicense} from '@queries/servers/system'; import {prepareMyTeams} from '@queries/servers/team'; import {getCurrentUser} from '@queries/servers/user'; import {isDMorGM, selectDefaultChannelForTeam} from '@utils/channel'; @@ -101,14 +102,15 @@ export async function retryInitialTeamAndChannel(serverUrl: string) { const models: Model[] = (await Promise.all([ prepareMyPreferences(operator, prefData.preferences!), + storeConfig(serverUrl, clData.config, true), ...prepareMyTeams(operator, teamData.teams!, teamData.memberships!), ...await prepareMyChannelsForTeam(operator, initialTeam.id, chData!.channels!, chData!.memberships!), prepareCategories(operator, chData!.categories!), prepareCategoryChannels(operator, chData!.categories!), + prepareCommonSystemValues( operator, { - config: clData.config!, license: clData.license!, currentTeamId: initialTeam?.id, currentChannelId: initialChannel?.id, @@ -121,7 +123,7 @@ export async function retryInitialTeamAndChannel(serverUrl: string) { const directChannels = chData!.channels!.filter(isDMorGM); const channelsToFetchProfiles = new Set(directChannels); if (channelsToFetchProfiles.size) { - const teammateDisplayNameSetting = getTeammateNameDisplaySetting(prefData.preferences || [], clData.config, clData.license); + const teammateDisplayNameSetting = getTeammateNameDisplaySetting(prefData.preferences || [], clData.config?.LockTeammateNameDisplay, clData.config?.TeammateNameDisplay, clData.license); fetchMissingDirectChannelsInfo(serverUrl, Array.from(channelsToFetchProfiles), user.locale, teammateDisplayNameSetting, user.id); } @@ -163,7 +165,8 @@ export async function retryInitialChannel(serverUrl: string, teamId: string) { user_id: p.userId, value: p.value, })); - const {config, license} = await getCommonSystemValues(database); + const license = await getLicense(database); + const config = await getConfig(database); // fetch channels / channel membership for initial team const chData = await fetchMyChannelsForTeam(serverUrl, teamId, false, 0, true); @@ -200,7 +203,7 @@ export async function retryInitialChannel(serverUrl: string, teamId: string) { const directChannels = chData!.channels!.filter(isDMorGM); const channelsToFetchProfiles = new Set(directChannels); if (channelsToFetchProfiles.size) { - const teammateDisplayNameSetting = getTeammateNameDisplaySetting(preferences || [], config, license); + const teammateDisplayNameSetting = getTeammateNameDisplaySetting(preferences || [], config.LockTeammateNameDisplay, config.TeammateNameDisplay, license); fetchMissingDirectChannelsInfo(serverUrl, Array.from(channelsToFetchProfiles), user.locale, teammateDisplayNameSetting, user.id); } diff --git a/app/actions/remote/session.ts b/app/actions/remote/session.ts index b451a5503..d88728e79 100644 --- a/app/actions/remote/session.ts +++ b/app/actions/remote/session.ts @@ -13,7 +13,7 @@ import NetworkManager from '@managers/network_manager'; import WebsocketManager from '@managers/websocket_manager'; import {getDeviceToken} from '@queries/app/global'; import {queryServerName} from '@queries/app/servers'; -import {getCurrentUserId, getCommonSystemValues, getExpiredSession} from '@queries/servers/system'; +import {getCurrentUserId, getExpiredSession, getConfig, getLicense} from '@queries/servers/system'; import {getCurrentUser} from '@queries/servers/user'; import EphemeralStore from '@store/ephemeral_store'; import {logWarning, logError} from '@utils/log'; @@ -35,9 +35,10 @@ export const completeLogin = async (serverUrl: string) => { } const {database} = operator; - const {config, license}: { config: Partial; license: Partial } = await getCommonSystemValues(database); + const license = await getLicense(database); + const config = await getConfig(database); - if (!Object.keys(config)?.length || !Object.keys(license)?.length) { + if (!Object.keys(config)?.length || !license || !Object.keys(license)?.length) { return null; } diff --git a/app/actions/remote/thread.ts b/app/actions/remote/thread.ts index 0a1c57b67..6461ab6bf 100644 --- a/app/actions/remote/thread.ts +++ b/app/actions/remote/thread.ts @@ -9,7 +9,7 @@ import PushNotifications from '@init/push_notifications'; import AppsManager from '@managers/apps_manager'; import NetworkManager from '@managers/network_manager'; import {getPostById} from '@queries/servers/post'; -import {getCommonSystemValues, getCurrentChannelId, getCurrentTeamId} from '@queries/servers/system'; +import {getConfigValue, getCurrentChannelId, getCurrentTeamId} from '@queries/servers/system'; import {getIsCRTEnabled, getNewestThreadInTeam, getThreadById} from '@queries/servers/thread'; import {getCurrentUser} from '@queries/servers/user'; @@ -104,9 +104,8 @@ export const fetchThreads = async ( } try { - const {config} = await getCommonSystemValues(database); - - const data = await client.getThreads('me', teamId, before, after, perPage, deleted, unread, since, false, config.Version); + const version = await getConfigValue(database, 'Version'); + const data = await client.getThreads('me', teamId, before, after, perPage, deleted, unread, since, false, version); const {threads} = data; @@ -323,7 +322,7 @@ async function fetchBatchThreads( return {error: 'currentUser not found'}; } - const {config} = await getCommonSystemValues(operator.database); + const version = await getConfigValue(operator.database, 'Version'); const data: Thread[] = []; const fetchThreadsFunc = async (opts: FetchThreadsOptions) => { @@ -331,7 +330,7 @@ async function fetchBatchThreads( const {before, after, perPage = General.CRT_CHUNK_SIZE, deleted, unread, since} = opts; page += 1; - const {threads} = await client.getThreads(currentUser.id, teamId, before, after, perPage, deleted, unread, since, false, config.Version); + const {threads} = await client.getThreads(currentUser.id, teamId, before, after, perPage, deleted, unread, since, false, version); if (threads.length) { // Mark all fetched threads as following for (const thread of threads) { diff --git a/app/actions/websocket/group.ts b/app/actions/websocket/group.ts index 252227370..5b96117d5 100644 --- a/app/actions/websocket/group.ts +++ b/app/actions/websocket/group.ts @@ -2,9 +2,9 @@ // See LICENSE.txt for license information. import {fetchGroupsForChannel, fetchGroupsForMember, fetchGroupsForTeam} from '@actions/remote/groups'; -import {deleteGroupChannelById, deleteGroupMembershipById, deleteGroupTeamById} from '@app/queries/servers/group'; -import {generateGroupAssociationId} from '@app/utils/groups'; import DatabaseManager from '@database/manager'; +import {deleteGroupChannelById, deleteGroupMembershipById, deleteGroupTeamById} from '@queries/servers/group'; +import {generateGroupAssociationId} from '@utils/groups'; import {logError} from '@utils/log'; type WebsocketGroupMessage = WebSocketMessage<{ diff --git a/app/actions/websocket/index.ts b/app/actions/websocket/index.ts index 385d1f9c1..dc1062567 100644 --- a/app/actions/websocket/index.ts +++ b/app/actions/websocket/index.ts @@ -31,9 +31,9 @@ import AppsManager from '@managers/apps_manager'; import {getActiveServerUrl, queryActiveServer} from '@queries/app/servers'; import {getCurrentChannel} from '@queries/servers/channel'; import { - getCommonSystemValues, getConfig, getCurrentUserId, + getLicense, getWebSocketLastDisconnected, resetWebSocketLastDisconnected, setCurrentTeamAndChannelId, @@ -178,7 +178,8 @@ async function doReconnect(serverUrl: string) { logInfo('WEBSOCKET RECONNECT MODELS BATCHING TOOK', `${Date.now() - dt}ms`); const {id: currentUserId, locale: currentUserLocale} = (await getCurrentUser(database))!; - const {config, license} = await getCommonSystemValues(database); + const license = await getLicense(database); + const config = await getConfig(database); if (isSupportedServerCalls(config?.Version)) { loadConfigAndCalls(serverUrl, currentUserId); diff --git a/app/actions/websocket/system.ts b/app/actions/websocket/system.ts index 565c8b45c..ed3bb8cb2 100644 --- a/app/actions/websocket/system.ts +++ b/app/actions/websocket/system.ts @@ -2,6 +2,7 @@ // See LICENSE.txt for license information. import {updateDmGmDisplayName} from '@actions/local/channel'; +import {storeConfig} from '@actions/local/systems'; import {SYSTEM_IDENTIFIERS} from '@constants/database'; import DatabaseManager from '@database/manager'; import {getConfig, getLicense} from '@queries/servers/system'; @@ -35,10 +36,8 @@ export async function handleConfigChangedEvent(serverUrl: string, msg: WebSocket try { const config = msg.data.config; - const systems: IdValue[] = [{id: SYSTEM_IDENTIFIERS.CONFIG, value: JSON.stringify(config)}]; - const prevConfig = await getConfig(operator.database); - await operator.handleSystem({systems, prepareRecordsOnly: false}); + await storeConfig(serverUrl, config); if (config?.LockTeammateNameDisplay && (prevConfig?.LockTeammateNameDisplay !== config.LockTeammateNameDisplay)) { updateDmGmDisplayName(serverUrl); } diff --git a/app/actions/websocket/users.ts b/app/actions/websocket/users.ts index 291462093..1cf23d41e 100644 --- a/app/actions/websocket/users.ts +++ b/app/actions/websocket/users.ts @@ -12,7 +12,7 @@ import {getTeammateNameDisplaySetting} from '@helpers/api/preference'; import WebsocketManager from '@managers/websocket_manager'; import {queryChannelsByTypes, queryUserChannelsByTypes} from '@queries/servers/channel'; import {queryPreferencesByCategoryAndName} from '@queries/servers/preference'; -import {getCommonSystemValues} from '@queries/servers/system'; +import {getConfig, getLicense} from '@queries/servers/system'; import {getCurrentUser} from '@queries/servers/user'; import {displayUsername} from '@utils/user'; @@ -84,13 +84,14 @@ export async function handleUserTypingEvent(serverUrl: string, msg: WebSocketMes return; } - const {config, license} = await getCommonSystemValues(database); + const license = await getLicense(database); + const config = await getConfig(database); const {users, existingUsers} = await fetchUsersByIds(serverUrl, [msg.data.user_id]); const user = users?.[0] || existingUsers?.[0]; const namePreference = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT).fetch(); - const teammateDisplayNameSetting = getTeammateNameDisplaySetting(namePreference, config, license); + const teammateDisplayNameSetting = getTeammateNameDisplaySetting(namePreference, config.LockTeammateNameDisplay, config.TeammateNameDisplay, license); const currentUser = await getCurrentUser(database); const username = displayUsername(user, currentUser?.locale, teammateDisplayNameSetting); const data = { diff --git a/app/client/websocket/index.ts b/app/client/websocket/index.ts index 5274d68c8..b5fb3c560 100644 --- a/app/client/websocket/index.ts +++ b/app/client/websocket/index.ts @@ -6,7 +6,7 @@ import {Platform} from 'react-native'; import {WebsocketEvents} from '@constants'; import DatabaseManager from '@database/manager'; -import {getCommonSystemValues} from '@queries/servers/system'; +import {getConfig} from '@queries/servers/system'; import {logError, logInfo, logWarning} from '@utils/log'; const MAX_WEBSOCKET_FAILS = 7; @@ -75,8 +75,8 @@ export default class WebSocketClient { return; } - const system = await getCommonSystemValues(database); - const connectionUrl = (system.config.WebsocketURL || this.serverUrl) + '/api/v4/websocket'; + const config = await getConfig(database); + const connectionUrl = (config.WebsocketURL || this.serverUrl) + '/api/v4/websocket'; if (this.connectingCallback) { this.connectingCallback(); @@ -97,7 +97,7 @@ export default class WebSocketClient { this.url = connectionUrl; - const reliableWebSockets = system.config.EnableReliableWebSockets === 'true'; + const reliableWebSockets = config.EnableReliableWebSockets === 'true'; if (reliableWebSockets) { // Add connection id, and last_sequence_number to the query param. // We cannot also send it as part of the auth_challenge, because the session cookie is already sent with the request. diff --git a/app/components/channel_item/custom_status/index.ts b/app/components/channel_item/custom_status/index.ts index 797d8def5..13a19c450 100644 --- a/app/components/channel_item/custom_status/index.ts +++ b/app/components/channel_item/custom_status/index.ts @@ -4,9 +4,9 @@ import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider'; import withObservables from '@nozbe/with-observables'; import {of as of$} from 'rxjs'; -import {map, switchMap} from 'rxjs/operators'; +import {switchMap} from 'rxjs/operators'; -import {observeConfig} from '@queries/servers/system'; +import {observeConfigBooleanValue} from '@queries/servers/system'; import {observeUser} from '@queries/servers/user'; import {getUserCustomStatus, isCustomStatusExpired} from '@utils/user'; @@ -21,9 +21,8 @@ type HeaderInputProps = { const enhanced = withObservables( ['userId'], ({userId, database}: WithDatabaseArgs & HeaderInputProps) => { - const config = observeConfig(database); const user = observeUser(database, userId); - const isCustomStatusEnabled = config.pipe(map((cfg) => cfg?.EnableCustomUserStatuses === 'true')); + const isCustomStatusEnabled = observeConfigBooleanValue(database, 'EnableCustomUserStatuses'); const customStatus = user.pipe(switchMap((u) => (u?.isBot ? of$(undefined) : of$(getUserCustomStatus(u))))); const customStatusExpired = user.pipe(switchMap((u) => (u?.isBot ? of$(false) : of$(isCustomStatusExpired(u))))); diff --git a/app/components/files/index.ts b/app/components/files/index.ts index 4f0b3da92..7a49560ca 100644 --- a/app/components/files/index.ts +++ b/app/components/files/index.ts @@ -6,7 +6,7 @@ import withObservables from '@nozbe/with-observables'; import {combineLatest, of as of$, from as from$} from 'rxjs'; import {map, switchMap} from 'rxjs/operators'; -import {observeConfig, observeLicense} from '@queries/servers/system'; +import {observeConfigBooleanValue, observeLicense} from '@queries/servers/system'; import {fileExists} from '@utils/file'; import Files from './files'; @@ -36,14 +36,8 @@ const filesLocalPathValidation = async (files: FileModel[], authorId: string) => }; const enhance = withObservables(['post'], ({database, post}: EnhanceProps) => { - const config = observeConfig(database); - const enableMobileFileDownload = config.pipe( - switchMap((cfg) => of$(cfg?.EnableMobileFileDownload !== 'false')), - ); - - const publicLinkEnabled = config.pipe( - switchMap((cfg) => of$(cfg?.EnablePublicLink !== 'false')), - ); + const enableMobileFileDownload = observeConfigBooleanValue(database, 'EnableMobileFileDownload'); + const publicLinkEnabled = observeConfigBooleanValue(database, 'EnablePublicLink'); const complianceDisabled = observeLicense(database).pipe( switchMap((lcs) => of$(lcs?.IsLicensed === 'false' || lcs?.Compliance === 'false')), diff --git a/app/components/markdown/index.ts b/app/components/markdown/index.ts index 5bad76742..cf0209521 100644 --- a/app/components/markdown/index.ts +++ b/app/components/markdown/index.ts @@ -4,19 +4,16 @@ import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider'; import withObservables from '@nozbe/with-observables'; import React from 'react'; -import {of as of$} from 'rxjs'; -import {switchMap} from 'rxjs/operators'; -import {observeConfig} from '@queries/servers/system'; +import {observeConfigBooleanValue} from '@queries/servers/system'; import Markdown from './markdown'; import type {WithDatabaseArgs} from '@typings/database/database'; const enhanced = withObservables([], ({database}: WithDatabaseArgs) => { - const config = observeConfig(database); - const enableLatex = config.pipe(switchMap((c) => of$(c?.EnableLatex === 'true'))); - const enableInlineLatex = config.pipe(switchMap((c) => of$(c?.EnableInlineLatex === 'true'))); + const enableLatex = observeConfigBooleanValue(database, 'EnableLatex'); + const enableInlineLatex = observeConfigBooleanValue(database, 'EnableInlineLatex'); return { enableLatex, diff --git a/app/components/markdown/markdown_link/index.ts b/app/components/markdown/markdown_link/index.ts index c18ff2661..3c5747856 100644 --- a/app/components/markdown/markdown_link/index.ts +++ b/app/components/markdown/markdown_link/index.ts @@ -3,23 +3,16 @@ import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider'; import withObservables from '@nozbe/with-observables'; -import {of as of$} from 'rxjs'; -import {switchMap} from 'rxjs/operators'; -import {observeConfig} from '@queries/servers/system'; +import {observeConfigValue} from '@queries/servers/system'; import MarkdownLink from './markdown_link'; import type {WithDatabaseArgs} from '@typings/database/database'; const enhance = withObservables([], ({database}: WithDatabaseArgs) => { - const config = observeConfig(database); - const experimentalNormalizeMarkdownLinks = config.pipe( - switchMap((cfg) => of$(cfg?.ExperimentalNormalizeMarkdownLinks)), - ); - const siteURL = config.pipe( - switchMap((cfg) => of$(cfg?.SiteURL)), - ); + const experimentalNormalizeMarkdownLinks = observeConfigValue(database, 'ExperimentalNormalizeMarkdownLinks'); + const siteURL = observeConfigValue(database, 'SiteURL'); return { experimentalNormalizeMarkdownLinks, diff --git a/app/components/post_draft/draft_handler/draft_handler.tsx b/app/components/post_draft/draft_handler/draft_handler.tsx index 342ae2132..1ac994c29 100644 --- a/app/components/post_draft/draft_handler/draft_handler.tsx +++ b/app/components/post_draft/draft_handler/draft_handler.tsx @@ -17,8 +17,8 @@ type Props = { cursorPosition: number; rootId?: string; files?: FileInfo[]; - maxFileSize: number; maxFileCount: number; + maxFileSize: number; canUploadFiles: boolean; updateCursorPosition: React.Dispatch>; updatePostInputTop: (top: number) => void; @@ -41,8 +41,8 @@ export default function DraftHandler(props: Props) { cursorPosition, rootId = '', files, - maxFileSize, maxFileCount, + maxFileSize, canUploadFiles, updateCursorPosition, updatePostInputTop, @@ -106,7 +106,7 @@ export default function DraftHandler(props: Props) { } newUploadError(null); - }, [intl, newUploadError, maxFileCount, maxFileSize, serverUrl, files?.length, channelId, rootId]); + }, [intl, newUploadError, maxFileSize, serverUrl, files?.length, channelId, rootId]); // This effect mainly handles keeping clean the uploadErrorHandlers, and // reinstantiate them on component mount and file retry. diff --git a/app/components/post_draft/draft_handler/index.ts b/app/components/post_draft/draft_handler/index.ts index 1dc3bdc8a..0f465286e 100644 --- a/app/components/post_draft/draft_handler/index.ts +++ b/app/components/post_draft/draft_handler/index.ts @@ -4,42 +4,23 @@ import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider'; import withObservables from '@nozbe/with-observables'; import React from 'react'; -import {combineLatest, of as of$} from 'rxjs'; -import {switchMap} from 'rxjs/operators'; import {DEFAULT_SERVER_MAX_FILE_SIZE} from '@constants/post_draft'; -import {observeConfig, observeLicense} from '@queries/servers/system'; -import {isMinimumServerVersion} from '@utils/helpers'; +import {observeCanUploadFiles, observeConfigIntValue, observeMaxFileCount} from '@queries/servers/system'; import DraftHandler from './draft_handler'; import type {WithDatabaseArgs} from '@typings/database/database'; const enhanced = withObservables([], ({database}: WithDatabaseArgs) => { - const config = observeConfig(database); - - const license = observeLicense(database); - - const canUploadFiles = combineLatest([config, license]).pipe( - switchMap(([c, l]) => of$( - c?.EnableFileAttachments === 'true' || - (l?.IsLicensed !== 'true' && l?.Compliance !== 'true' && c?.EnableMobileFileUpload === 'true'), - ), - ), - ); - - const maxFileSize = config.pipe( - switchMap((cfg) => of$(parseInt(cfg?.MaxFileSize || '0', 10) || DEFAULT_SERVER_MAX_FILE_SIZE)), - ); - - const maxFileCount = config.pipe( - switchMap((cfg) => of$(isMinimumServerVersion(cfg?.Version || '', 6, 0) ? 10 : 5)), - ); + const canUploadFiles = observeCanUploadFiles(database); + const maxFileSize = observeConfigIntValue(database, 'MaxFileSize', DEFAULT_SERVER_MAX_FILE_SIZE); + const maxFileCount = observeMaxFileCount(database); return { maxFileSize, - maxFileCount, canUploadFiles, + maxFileCount, }; }); diff --git a/app/components/post_draft/post_input/index.ts b/app/components/post_draft/post_input/index.ts index 8485f4809..e78482e6f 100644 --- a/app/components/post_draft/post_input/index.ts +++ b/app/components/post_draft/post_input/index.ts @@ -8,7 +8,7 @@ import {of as of$} from 'rxjs'; import {switchMap, distinctUntilChanged} from 'rxjs/operators'; import {observeChannel} from '@queries/servers/channel'; -import {observeConfig} from '@queries/servers/system'; +import {observeConfigBooleanValue, observeConfigIntValue} from '@queries/servers/system'; import PostInput from './post_input'; @@ -20,18 +20,9 @@ type OwnProps = { } const enhanced = withObservables(['channelId'], ({database, channelId}: WithDatabaseArgs & OwnProps) => { - const config = observeConfig(database); - const timeBetweenUserTypingUpdatesMilliseconds = config.pipe( - switchMap((cfg) => of$(parseInt(cfg?.TimeBetweenUserTypingUpdatesMilliseconds || '0', 10))), - ); - - const enableUserTypingMessage = config.pipe( - switchMap((cfg) => of$(cfg?.EnableUserTypingMessages === 'true')), - ); - - const maxNotificationsPerChannel = config.pipe( - switchMap((cfg) => of$(parseInt(cfg?.MaxNotificationsPerChannel || '0', 10))), - ); + const timeBetweenUserTypingUpdatesMilliseconds = observeConfigIntValue(database, 'TimeBetweenUserTypingUpdatesMilliseconds'); + const enableUserTypingMessage = observeConfigBooleanValue(database, 'EnableUserTypingMessages'); + const maxNotificationsPerChannel = observeConfigIntValue(database, 'MaxNotificationsPerChannel'); const channel = observeChannel(database, channelId); diff --git a/app/components/post_draft/quick_actions/index.ts b/app/components/post_draft/quick_actions/index.ts index e23f63773..b5c7111f0 100644 --- a/app/components/post_draft/quick_actions/index.ts +++ b/app/components/post_draft/quick_actions/index.ts @@ -4,31 +4,16 @@ import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider'; import withObservables from '@nozbe/with-observables'; import React from 'react'; -import {combineLatest, of as of$} from 'rxjs'; -import {switchMap} from 'rxjs/operators'; -import {observeConfig, observeLicense} from '@queries/servers/system'; -import {isMinimumServerVersion} from '@utils/helpers'; +import {observeCanUploadFiles, observeMaxFileCount} from '@queries/servers/system'; import QuickActions from './quick_actions'; import type {WithDatabaseArgs} from '@typings/database/database'; const enhanced = withObservables([], ({database}: WithDatabaseArgs) => { - const config = observeConfig(database); - const license = observeLicense(database); - - const canUploadFiles = combineLatest([config, license]).pipe( - switchMap(([c, l]) => of$( - c?.EnableFileAttachments === 'true' || - (l?.IsLicensed !== 'true' && l?.Compliance !== 'true' && c?.EnableMobileFileUpload === 'true'), - ), - ), - ); - - const maxFileCount = config.pipe( - switchMap((cfg) => of$(isMinimumServerVersion(cfg?.Version || '', 6, 0) ? 10 : 5)), - ); + const canUploadFiles = observeCanUploadFiles(database); + const maxFileCount = observeMaxFileCount(database); return { canUploadFiles, diff --git a/app/components/post_draft/send_handler/index.ts b/app/components/post_draft/send_handler/index.ts index bbb23e536..590ac7f7d 100644 --- a/app/components/post_draft/send_handler/index.ts +++ b/app/components/post_draft/send_handler/index.ts @@ -11,7 +11,7 @@ import {MAX_MESSAGE_LENGTH_FALLBACK} from '@constants/post_draft'; import {observeChannel, observeCurrentChannel} from '@queries/servers/channel'; import {queryAllCustomEmojis} from '@queries/servers/custom_emoji'; import {observePermissionForChannel} from '@queries/servers/role'; -import {observeConfig, observeCurrentUserId} from '@queries/servers/system'; +import {observeConfigBooleanValue, observeConfigIntValue, observeCurrentUserId} from '@queries/servers/system'; import {observeUser} from '@queries/servers/user'; import SendHandler from './send_handler'; @@ -42,16 +42,9 @@ const enhanced = withObservables([], (ownProps: WithDatabaseArgs & OwnProps) => switchMap((u) => of$(u?.status === General.OUT_OF_OFFICE)), ); - const config = observeConfig(database); - const enableConfirmNotificationsToChannel = config.pipe( - switchMap((cfg) => of$(Boolean(cfg?.EnableConfirmNotificationsToChannel === 'true'))), - ); - const isTimezoneEnabled = config.pipe( - switchMap((cfg) => of$(Boolean(cfg?.ExperimentalTimezone === 'true'))), - ); - const maxMessageLength = config.pipe( - switchMap((cfg) => of$(parseInt(cfg?.MaxPostSize || '0', 10) || MAX_MESSAGE_LENGTH_FALLBACK)), - ); + const enableConfirmNotificationsToChannel = observeConfigBooleanValue(database, 'EnableConfirmNotificationsToChannel'); + const isTimezoneEnabled = observeConfigBooleanValue(database, 'ExperimentalTimezone'); + const maxMessageLength = observeConfigIntValue(database, 'MaxPostSize', MAX_MESSAGE_LENGTH_FALLBACK); const useChannelMentions = combineLatest([channel, currentUser]).pipe( switchMap(([c, u]) => { diff --git a/app/components/post_list/post/body/content/opengraph/index.ts b/app/components/post_list/post/body/content/opengraph/index.ts index 061899af5..d11cafa46 100644 --- a/app/components/post_list/post/body/content/opengraph/index.ts +++ b/app/components/post_list/post/body/content/opengraph/index.ts @@ -8,7 +8,7 @@ import {of as of$, combineLatest} from 'rxjs'; import {Preferences} from '@constants'; import {getPreferenceAsBool} from '@helpers/api/preference'; import {queryPreferencesByCategoryAndName} from '@queries/servers/preference'; -import {observeConfig} from '@queries/servers/system'; +import {observeConfigBooleanValue} from '@queries/servers/system'; import Opengraph from './opengraph'; @@ -21,12 +21,12 @@ const enhance = withObservables( return {showLinkPreviews: of$(false)}; } - const config = observeConfig(database); + const linkPreviewsConfig = observeConfigBooleanValue(database, 'EnableLinkPreviews'); const linkPreviewPreference = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.LINK_PREVIEW_DISPLAY). observeWithColumns(['value']); - const showLinkPreviews = combineLatest([config, linkPreviewPreference], (cfg, pref) => { + const showLinkPreviews = combineLatest([linkPreviewsConfig, linkPreviewPreference], (cfg, pref) => { const previewsEnabled = getPreferenceAsBool(pref, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.LINK_PREVIEW_DISPLAY, true); - return of$(previewsEnabled && cfg?.EnableLinkPreviews === 'true'); + return of$(previewsEnabled && cfg); }); return {showLinkPreviews}; diff --git a/app/components/remove_markdown/index.tsx b/app/components/remove_markdown/index.tsx index 9b8bf9530..b677fafcd 100644 --- a/app/components/remove_markdown/index.tsx +++ b/app/components/remove_markdown/index.tsx @@ -14,7 +14,7 @@ type Props = { enableEmoji?: boolean; enableHardBreak?: boolean; enableSoftBreak?: boolean; - textStyle: StyleProp; + textStyle?: StyleProp; value: string; }; diff --git a/app/components/system_header/index.tsx b/app/components/system_header/index.tsx index e9c98fb76..a33209e05 100644 --- a/app/components/system_header/index.tsx +++ b/app/components/system_header/index.tsx @@ -12,7 +12,7 @@ import FormattedTime from '@components/formatted_time'; import {Preferences} from '@constants'; import {getPreferenceAsBool} from '@helpers/api/preference'; import {queryPreferencesByCategoryAndName} from '@queries/servers/preference'; -import {observeConfig} from '@queries/servers/system'; +import {observeConfigBooleanValue} from '@queries/servers/system'; import {observeCurrentUser} from '@queries/servers/user'; import {makeStyleSheetFromTheme} from '@utils/theme'; import {typography} from '@utils/typography'; @@ -80,10 +80,9 @@ const SystemHeader = ({isMilitaryTime, isTimezoneEnabled, createAt, theme, user} }; const enhanced = withObservables([], ({database}: WithDatabaseArgs) => { - const config = observeConfig(database); const preferences = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time'). observeWithColumns(['value']); - const isTimezoneEnabled = config.pipe(map((cfg) => cfg?.ExperimentalTimezone === 'true')); + const isTimezoneEnabled = observeConfigBooleanValue(database, 'ExperimentalTimezone'); const isMilitaryTime = preferences.pipe( map((prefs) => getPreferenceAsBool(prefs, Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time', false)), ); diff --git a/app/components/toast/index.tsx b/app/components/toast/index.tsx index b9ac62d25..a05968b4f 100644 --- a/app/components/toast/index.tsx +++ b/app/components/toast/index.tsx @@ -5,9 +5,9 @@ import React, {useMemo} from 'react'; import {StyleProp, Text, TextStyle, useWindowDimensions, View, ViewStyle} from 'react-native'; import Animated, {AnimatedStyleProp} from 'react-native-reanimated'; -import {useIsTablet} from '@app/hooks/device'; import CompassIcon from '@components/compass_icon'; import {useTheme} from '@context/theme'; +import {useIsTablet} from '@hooks/device'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import {typography} from '@utils/typography'; diff --git a/app/components/user_item/index.ts b/app/components/user_item/index.ts index 0b3d5285f..fa9f73386 100644 --- a/app/components/user_item/index.ts +++ b/app/components/user_item/index.ts @@ -3,23 +3,16 @@ import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider'; import withObservables from '@nozbe/with-observables'; -import {of as of$} from 'rxjs'; -import {switchMap} from 'rxjs/operators'; -import {observeConfig, observeCurrentUserId} from '@queries/servers/system'; +import {observeConfigBooleanValue, observeCurrentUserId} from '@queries/servers/system'; import UserItem from './user_item'; import type {WithDatabaseArgs} from '@typings/database/database'; const enhanced = withObservables([], ({database}: WithDatabaseArgs) => { - const config = observeConfig(database); - const isCustomStatusEnabled = config.pipe( - switchMap((cfg) => of$(cfg?.EnableCustomUserStatuses === 'true')), - ); - const showFullName = config.pipe( - switchMap((cfg) => of$(cfg?.ShowFullName === 'true')), - ); + const isCustomStatusEnabled = observeConfigBooleanValue(database, 'EnableCustomUserStatuses'); + const showFullName = observeConfigBooleanValue(database, 'ShowFullName'); const currentUserId = observeCurrentUserId(database); return { isCustomStatusEnabled, diff --git a/app/constants/database.ts b/app/constants/database.ts index 4ac2f8695..1d3a72369 100644 --- a/app/constants/database.ts +++ b/app/constants/database.ts @@ -15,6 +15,7 @@ export const MM_TABLES = { CHANNEL: 'Channel', CHANNEL_INFO: 'ChannelInfo', CHANNEL_MEMBERSHIP: 'ChannelMembership', + CONFIG: 'Config', CUSTOM_EMOJI: 'CustomEmoji', DRAFT: 'Draft', FILE: 'File', diff --git a/app/database/manager/__mocks__/index.ts b/app/database/manager/__mocks__/index.ts index 03085aa68..3d1baf3ec 100644 --- a/app/database/manager/__mocks__/index.ts +++ b/app/database/manager/__mocks__/index.ts @@ -11,7 +11,7 @@ import {DatabaseType, MIGRATION_EVENTS, MM_TABLES} from '@constants/database'; import AppDatabaseMigrations from '@database/migration/app'; import ServerDatabaseMigrations from '@database/migration/server'; import {InfoModel, GlobalModel, ServersModel} from '@database/models/app'; -import {CategoryModel, CategoryChannelModel, ChannelModel, ChannelInfoModel, ChannelMembershipModel, CustomEmojiModel, DraftModel, FileModel, +import {CategoryModel, CategoryChannelModel, ChannelModel, ChannelInfoModel, ChannelMembershipModel, ConfigModel, CustomEmojiModel, DraftModel, FileModel, GroupModel, GroupChannelModel, GroupTeamModel, GroupMembershipModel, MyChannelModel, MyChannelSettingsModel, MyTeamModel, PostModel, PostsInChannelModel, PostsInThreadModel, PreferenceModel, ReactionModel, RoleModel, SystemModel, TeamModel, TeamChannelHistoryModel, TeamMembershipModel, TeamSearchHistoryModel, @@ -47,7 +47,7 @@ class DatabaseManager { constructor() { this.appModels = [InfoModel, GlobalModel, ServersModel]; this.serverModels = [ - CategoryModel, CategoryChannelModel, ChannelModel, ChannelInfoModel, ChannelMembershipModel, CustomEmojiModel, DraftModel, FileModel, + CategoryModel, CategoryChannelModel, ChannelModel, ChannelInfoModel, ChannelMembershipModel, ConfigModel, CustomEmojiModel, DraftModel, FileModel, GroupModel, GroupChannelModel, GroupTeamModel, GroupMembershipModel, MyChannelModel, MyChannelSettingsModel, MyTeamModel, PostModel, PostsInChannelModel, PostsInThreadModel, PreferenceModel, ReactionModel, RoleModel, SystemModel, TeamModel, TeamChannelHistoryModel, TeamMembershipModel, TeamSearchHistoryModel, diff --git a/app/database/manager/index.ts b/app/database/manager/index.ts index 7599a09f9..7ca411162 100644 --- a/app/database/manager/index.ts +++ b/app/database/manager/index.ts @@ -8,7 +8,7 @@ import {DeviceEventEmitter, Platform} from 'react-native'; import DeviceInfo from 'react-native-device-info'; import FileSystem from 'react-native-fs'; -import {DatabaseType, MIGRATION_EVENTS, MM_TABLES} from '@constants/database'; +import {DatabaseType, MIGRATION_EVENTS, MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database'; import AppDatabaseMigrations from '@database/migration/app'; import ServerDatabaseMigrations from '@database/migration/server'; import {InfoModel, GlobalModel, ServersModel} from '@database/models/app'; @@ -16,13 +16,14 @@ import {CategoryModel, CategoryChannelModel, ChannelModel, ChannelInfoModel, Cha GroupModel, GroupChannelModel, GroupTeamModel, GroupMembershipModel, MyChannelModel, MyChannelSettingsModel, MyTeamModel, PostModel, PostsInChannelModel, PostsInThreadModel, PreferenceModel, ReactionModel, RoleModel, SystemModel, TeamModel, TeamChannelHistoryModel, TeamMembershipModel, TeamSearchHistoryModel, - ThreadModel, ThreadParticipantModel, ThreadInTeamModel, UserModel, + ThreadModel, ThreadParticipantModel, ThreadInTeamModel, UserModel, ConfigModel, } from '@database/models/server'; import AppDataOperator from '@database/operator/app_data_operator'; import ServerDataOperator from '@database/operator/server_data_operator'; import {schema as appSchema} from '@database/schema/app'; import {serverSchema} from '@database/schema/server'; import {queryActiveServer, queryServer, queryServerByIdentifier} from '@queries/app/servers'; +import {querySystemValue} from '@queries/servers/system'; import {deleteLegacyFileCache} from '@utils/file'; import {emptyFunction} from '@utils/general'; import {logDebug, logError} from '@utils/log'; @@ -45,7 +46,7 @@ class DatabaseManager { constructor() { this.appModels = [InfoModel, GlobalModel, ServersModel]; this.serverModels = [ - CategoryModel, CategoryChannelModel, ChannelModel, ChannelInfoModel, ChannelMembershipModel, CustomEmojiModel, DraftModel, FileModel, + CategoryModel, CategoryChannelModel, ChannelModel, ChannelInfoModel, ChannelMembershipModel, ConfigModel, CustomEmojiModel, DraftModel, FileModel, GroupModel, GroupChannelModel, GroupTeamModel, GroupMembershipModel, MyChannelModel, MyChannelSettingsModel, MyTeamModel, PostModel, PostsInChannelModel, PostsInThreadModel, PreferenceModel, ReactionModel, RoleModel, SystemModel, TeamModel, TeamChannelHistoryModel, TeamMembershipModel, TeamSearchHistoryModel, @@ -178,13 +179,38 @@ class DatabaseManager { * @returns {Promise} */ private initServerDatabase = async (serverUrl: string): Promise => { - await this.createServerDatabase({ + const serverDatabase = await this.createServerDatabase({ config: { dbName: serverUrl, dbType: DatabaseType.SERVER, serverUrl, }, }); + + // Migration for config + if (serverDatabase) { + const {database, operator} = serverDatabase; + const oldConfigList = await querySystemValue(database, SYSTEM_IDENTIFIERS.CONFIG).fetch(); + if (oldConfigList.length) { + const oldConfigModel = oldConfigList[0]; + const oldConfig = oldConfigModel.value as ClientConfig; + + const configs = []; + let k: keyof ClientConfig; + for (k in oldConfig) { + // Check to silence eslint (guard-for-in) + if (Object.prototype.hasOwnProperty.call(oldConfig, k)) { + configs.push({ + id: k, + value: oldConfig[k], + }); + } + } + const models = await operator.handleConfigs({configs, configsToDelete: [], prepareRecordsOnly: true}); + + operator.batchRecords([...models, oldConfigModel.prepareDestroyPermanently()]); + } + } }; /** diff --git a/app/database/migration/server/index.ts b/app/database/migration/server/index.ts index 51ebc7666..3ff742d00 100644 --- a/app/database/migration/server/index.ts +++ b/app/database/migration/server/index.ts @@ -4,9 +4,10 @@ // NOTE : To implement migration, please follow this document // https://nozbe.github.io/WatermelonDB/Advanced/Migrations.html -import {schemaMigrations, addColumns} from '@nozbe/watermelondb/Schema/migrations'; +import {schemaMigrations, addColumns, createTable} from '@nozbe/watermelondb/Schema/migrations'; import {MM_TABLES} from '@constants/database'; +import {tableSchemaSpec as configSpec} from '@database/schema/server/table_schemas/config'; const {SERVER: { GROUP, @@ -16,6 +17,12 @@ const {SERVER: { }} = MM_TABLES; export default schemaMigrations({migrations: [ + { + toVersion: 5, + steps: [ + createTable(configSpec), + ], + }, { toVersion: 4, steps: [ diff --git a/app/database/models/server/config.ts b/app/database/models/server/config.ts new file mode 100644 index 000000000..dfa919659 --- /dev/null +++ b/app/database/models/server/config.ts @@ -0,0 +1,23 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {Model} from '@nozbe/watermelondb'; +import {field} from '@nozbe/watermelondb/decorators'; + +import {MM_TABLES} from '@constants/database'; + +import type ConfigModelInterface from '@typings/database/models/servers/config'; + +const {CONFIG} = MM_TABLES.SERVER; + +/** + * The Config model is another set of key-value pair combination but this one + * will hold the server configuration. + */ +export default class ConfigModel extends Model implements ConfigModelInterface { + /** table (name) : Config */ + static table = CONFIG; + + /** value : The value for that config/information and whose key will be the id column */ + @field('value') value!: string; +} diff --git a/app/database/models/server/index.ts b/app/database/models/server/index.ts index a940587af..d371e48fb 100644 --- a/app/database/models/server/index.ts +++ b/app/database/models/server/index.ts @@ -6,6 +6,7 @@ export {default as CategoryChannelModel} from './category_channel'; export {default as ChannelInfoModel} from './channel_info'; export {default as ChannelMembershipModel} from './channel_membership'; export {default as ChannelModel} from './channel'; +export {default as ConfigModel} from './config'; export {default as CustomEmojiModel} from './custom_emoji'; export {default as DraftModel} from './draft'; export {default as FileModel} from './file'; diff --git a/app/database/operator/server_data_operator/handlers/index.test.ts b/app/database/operator/server_data_operator/handlers/index.test.ts index 3b1fccc9a..d7330dc87 100644 --- a/app/database/operator/server_data_operator/handlers/index.test.ts +++ b/app/database/operator/server_data_operator/handlers/index.test.ts @@ -3,6 +3,7 @@ import DatabaseManager from '@database/manager'; import { + transformConfigRecord, transformCustomEmojiRecord, transformRoleRecord, transformSystemRecord, @@ -95,6 +96,30 @@ describe('*** DataOperator: Base Handlers tests ***', () => { }); }); + it('=> HandleConfig: should write to the CONFIG table', async () => { + expect.assertions(1); + + const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords'); + + const configs = [{id: 'config-1', value: 'config-1'}]; + const configsToDelete = [{id: 'toDelete', value: 'toDelete'}]; + + await operator.handleConfigs({ + configs, + configsToDelete, + prepareRecordsOnly: false, + }); + + expect(spyOnHandleRecords).toHaveBeenCalledWith({ + fieldName: 'id', + transformer: transformConfigRecord, + createOrUpdateRawValues: configs, + tableName: 'Config', + prepareRecordsOnly: false, + deleteRawValues: configsToDelete, + }); + }); + it('=> No table name: should not call execute if tableName is invalid', async () => { expect.assertions(3); diff --git a/app/database/operator/server_data_operator/handlers/index.ts b/app/database/operator/server_data_operator/handlers/index.ts index 9fc5e5b3d..b3a5a9356 100644 --- a/app/database/operator/server_data_operator/handlers/index.ts +++ b/app/database/operator/server_data_operator/handlers/index.ts @@ -4,6 +4,7 @@ import {MM_TABLES} from '@constants/database'; import BaseDataOperator from '@database/operator/base_data_operator'; import { + transformConfigRecord, transformCustomEmojiRecord, transformRoleRecord, transformSystemRecord, @@ -12,12 +13,12 @@ import {getUniqueRawsBy} from '@database/operator/utils/general'; import {logWarning} from '@utils/log'; import type {Model} from '@nozbe/watermelondb'; -import type {HandleCustomEmojiArgs, HandleRoleArgs, HandleSystemArgs, OperationArgs} from '@typings/database/database'; +import type {HandleConfigArgs, HandleCustomEmojiArgs, HandleRoleArgs, HandleSystemArgs, OperationArgs} from '@typings/database/database'; import type CustomEmojiModel from '@typings/database/models/servers/custom_emoji'; import type RoleModel from '@typings/database/models/servers/role'; import type SystemModel from '@typings/database/models/servers/system'; -const {SERVER: {CUSTOM_EMOJI, ROLE, SYSTEM}} = MM_TABLES; +const {SERVER: {CONFIG, CUSTOM_EMOJI, ROLE, SYSTEM}} = MM_TABLES; export default class ServerDataOperatorBase extends BaseDataOperator { handleRole = async ({roles, prepareRecordsOnly = true}: HandleRoleArgs) => { @@ -71,6 +72,24 @@ export default class ServerDataOperatorBase extends BaseDataOperator { }) as Promise; }; + handleConfigs = async ({configs, configsToDelete, prepareRecordsOnly = true}: HandleConfigArgs) => { + if (!configs?.length && !configsToDelete?.length) { + logWarning( + 'An empty or undefined "configs" and "configsToDelete" arrays has been passed to the handleConfigs', + ); + return []; + } + + return this.handleRecords({ + fieldName: 'id', + transformer: transformConfigRecord, + prepareRecordsOnly, + createOrUpdateRawValues: getUniqueRawsBy({raws: configs, key: 'id'}), + tableName: CONFIG, + deleteRawValues: configsToDelete, + }); + }; + /** * execute: Handles the Create/Update operations on an table. * @param {OperationArgs} execute diff --git a/app/database/operator/server_data_operator/transformers/general.ts b/app/database/operator/server_data_operator/transformers/general.ts index 1cefd4f1d..d15778076 100644 --- a/app/database/operator/server_data_operator/transformers/general.ts +++ b/app/database/operator/server_data_operator/transformers/general.ts @@ -5,6 +5,7 @@ import {MM_TABLES, OperationType} from '@constants/database'; import {prepareBaseRecord} from '@database/operator/server_data_operator/transformers/index'; import type {TransformerArgs} from '@typings/database/database'; +import type ConfigModel from '@typings/database/models/servers/config'; import type CustomEmojiModel from '@typings/database/models/servers/custom_emoji'; import type RoleModel from '@typings/database/models/servers/role'; import type SystemModel from '@typings/database/models/servers/system'; @@ -13,6 +14,7 @@ const { CUSTOM_EMOJI, ROLE, SYSTEM, + CONFIG, } = MM_TABLES.SERVER; /** @@ -94,3 +96,28 @@ export const transformSystemRecord = ({action, database, value}: TransformerArgs fieldsMapper, }) as Promise; }; + +/** + * transformConfigRecord: Prepares a record of the SERVER database 'Config' table for update or create actions. + * @param {TransformerArgs} operator + * @param {Database} operator.database + * @param {RecordPair} operator.value + * @returns {Promise} + */ +export const transformConfigRecord = ({action, database, value}: TransformerArgs): Promise => { + const raw = value.raw as IdValue; + + // If isCreateAction is true, we will use the id (API response) from the RAW, else we shall use the existing record id from the database + const fieldsMapper = (config: ConfigModel) => { + config._raw.id = raw?.id; + config.value = raw?.value as string; + }; + + return prepareBaseRecord({ + action, + database, + tableName: CONFIG, + value, + fieldsMapper, + }) as Promise; +}; diff --git a/app/database/schema/server/index.ts b/app/database/schema/server/index.ts index f4466e0f4..b6d0d06aa 100644 --- a/app/database/schema/server/index.ts +++ b/app/database/schema/server/index.ts @@ -9,6 +9,7 @@ import { ChannelInfoSchema, ChannelMembershipSchema, ChannelSchema, + ConfigSchema, CustomEmojiSchema, DraftSchema, FileSchema, @@ -37,13 +38,14 @@ import { } from './table_schemas'; export const serverSchema: AppSchema = appSchema({ - version: 4, + version: 5, tables: [ CategorySchema, CategoryChannelSchema, ChannelInfoSchema, ChannelMembershipSchema, ChannelSchema, + ConfigSchema, CustomEmojiSchema, DraftSchema, FileSchema, diff --git a/app/database/schema/server/table_schemas/config.ts b/app/database/schema/server/table_schemas/config.ts new file mode 100644 index 000000000..1ddc50153 --- /dev/null +++ b/app/database/schema/server/table_schemas/config.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {tableSchema} from '@nozbe/watermelondb'; + +import {MM_TABLES} from '@constants/database'; + +import type {TableSchemaSpec} from '@nozbe/watermelondb/Schema'; + +const {CONFIG} = MM_TABLES.SERVER; + +export const tableSchemaSpec: TableSchemaSpec = { + name: CONFIG, + columns: [ + {name: 'value', type: 'string'}, + ], +}; + +export default tableSchema(tableSchemaSpec); diff --git a/app/database/schema/server/table_schemas/index.ts b/app/database/schema/server/table_schemas/index.ts index fac977d29..a388e5151 100644 --- a/app/database/schema/server/table_schemas/index.ts +++ b/app/database/schema/server/table_schemas/index.ts @@ -31,3 +31,4 @@ export {default as ThreadSchema} from './thread'; export {default as ThreadParticipantSchema} from './thread_participant'; export {default as ThreadInTeamSchema} from './thread_in_team'; export {default as UserSchema} from './user'; +export {default as ConfigSchema} from './config'; diff --git a/app/database/schema/server/test.ts b/app/database/schema/server/test.ts index 34ee07d84..efa7bb8f9 100644 --- a/app/database/schema/server/test.ts +++ b/app/database/schema/server/test.ts @@ -13,6 +13,7 @@ const { CHANNEL, CHANNEL_INFO, CHANNEL_MEMBERSHIP, + CONFIG, CUSTOM_EMOJI, DRAFT, FILE, @@ -43,7 +44,8 @@ const { describe('*** Test schema for SERVER database ***', () => { it('=> The SERVER SCHEMA should strictly match', () => { expect(serverSchema).toEqual({ - version: 4, + version: 5, + unsafeSql: undefined, tables: { [CATEGORY]: { name: CATEGORY, @@ -145,6 +147,16 @@ describe('*** Test schema for SERVER database ***', () => { {name: 'scheme_admin', type: 'boolean'}, ], }, + [CONFIG]: { + name: CONFIG, + unsafeSql: undefined, + columns: { + value: {name: 'value', type: 'string'}, + }, + columnArray: [ + {name: 'value', type: 'string'}, + ], + }, [CUSTOM_EMOJI]: { name: CUSTOM_EMOJI, unsafeSql: undefined, diff --git a/app/helpers/api/preference.ts b/app/helpers/api/preference.ts index 59188d583..f55478f6e 100644 --- a/app/helpers/api/preference.ts +++ b/app/helpers/api/preference.ts @@ -29,13 +29,13 @@ export function getPreferenceAsInt(preferences: PreferenceType[] | PreferenceMod return defaultValue; } -export function getTeammateNameDisplaySetting(preferences: PreferenceType[] | PreferenceModel[], config?: ClientConfig, license?: ClientLicense) { - const useAdminTeammateNameDisplaySetting = license?.LockTeammateNameDisplay === 'true' && config?.LockTeammateNameDisplay === 'true'; +export function getTeammateNameDisplaySetting(preferences: PreferenceType[] | PreferenceModel[], lockTeammateNameDisplay?: string, teammateNameDisplay?: string, license?: ClientLicense) { + const useAdminTeammateNameDisplaySetting = license?.LockTeammateNameDisplay === 'true' && lockTeammateNameDisplay === 'true'; const preference = getPreferenceValue(preferences, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT, '') as string; if (preference && !useAdminTeammateNameDisplaySetting) { return preference; - } else if (config?.TeammateNameDisplay) { - return config.TeammateNameDisplay; + } else if (teammateNameDisplay) { + return teammateNameDisplay; } return General.TEAMMATE_NAME_DISPLAY.SHOW_USERNAME; diff --git a/app/managers/session_manager.ts b/app/managers/session_manager.ts index b48e21f19..63445e3d6 100644 --- a/app/managers/session_manager.ts +++ b/app/managers/session_manager.ts @@ -6,9 +6,9 @@ import {AppState, AppStateStatus, DeviceEventEmitter, Platform} from 'react-nati import FastImage from 'react-native-fast-image'; import {cancelSessionNotification, logout, scheduleSessionNotification} from '@actions/remote/session'; -import {resetMomentLocale} from '@app/i18n'; import {Events, Launch} from '@constants'; import DatabaseManager from '@database/manager'; +import {resetMomentLocale} from '@i18n'; import {getAllServerCredentials, removeServerCredentials} from '@init/credentials'; import {relaunchApp} from '@init/launch'; import PushNotifications from '@init/push_notifications'; diff --git a/app/products/calls/actions/calls.ts b/app/products/calls/actions/calls.ts index bc1a2dd10..fb3dfc336 100644 --- a/app/products/calls/actions/calls.ts +++ b/app/products/calls/actions/calls.ts @@ -25,7 +25,7 @@ import {getTeammateNameDisplaySetting} from '@helpers/api/preference'; import NetworkManager from '@managers/network_manager'; import {getChannelById} from '@queries/servers/channel'; import {queryPreferencesByCategoryAndName} from '@queries/servers/preference'; -import {getCommonSystemValues} from '@queries/servers/system'; +import {getConfig, getLicense} from '@queries/servers/system'; import {getCurrentUser, getUserById} from '@queries/servers/user'; import {displayUsername, getUserIdFromChannelName, isSystemAdmin} from '@utils/user'; @@ -346,9 +346,10 @@ export const getEndCallMessage = async (serverUrl: string, channelId: string, cu if (channel.type === General.DM_CHANNEL) { const otherID = getUserIdFromChannelName(currentUserId, channel.name); const otherUser = await getUserById(database, otherID); - const {config, license} = await getCommonSystemValues(database); + const license = await getLicense(database); + const config = await getConfig(database); const preferences = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT).fetch(); - const displaySetting = getTeammateNameDisplaySetting(preferences, config, license); + const displaySetting = getTeammateNameDisplaySetting(preferences, config.LockTeammateNameDisplay, config.TeammateNameDisplay, license); msg = intl.formatMessage({ id: 'mobile.calls_end_msg_dm', defaultMessage: 'Are you sure you want to end the call with {displayName}?', diff --git a/app/products/calls/connection/websocket_client.ts b/app/products/calls/connection/websocket_client.ts index 64dc5baa3..e72fc847c 100644 --- a/app/products/calls/connection/websocket_client.ts +++ b/app/products/calls/connection/websocket_client.ts @@ -7,8 +7,8 @@ import {encode} from '@msgpack/msgpack/dist'; import Calls from '@constants/calls'; import DatabaseManager from '@database/manager'; -import {getCommonSystemValues} from '@queries/servers/system'; -import {logDebug, logError} from '@utils/log'; +import {getConfigValue} from '@queries/servers/system'; +import {logError, logDebug} from '@utils/log'; const wsMinReconnectRetryTimeMs = 1000; // 1 second const wsReconnectionTimeout = 30000; // 30 seconds @@ -42,8 +42,8 @@ export class WebSocketClient extends EventEmitter { return; } - const system = await getCommonSystemValues(database); - const connectionUrl = (system.config.WebsocketURL || this.serverUrl) + this.wsPath; + const websocketURL = await getConfigValue(database, 'WebsocketURL'); + const connectionUrl = (websocketURL || this.serverUrl) + this.wsPath; this.ws = new WebSocket(`${connectionUrl}?connection_id=${this.connID}&sequence_number=${this.serverSeqNo}`, [], {headers: {authorization: `Bearer ${this.authToken}`}}); diff --git a/app/queries/servers/system.ts b/app/queries/servers/system.ts index a6216b027..562ed9ed1 100644 --- a/app/queries/servers/system.ts +++ b/app/queries/servers/system.ts @@ -2,18 +2,19 @@ // See LICENSE.txt for license information. import {Database, Q} from '@nozbe/watermelondb'; -import {of as of$, Observable} from 'rxjs'; -import {switchMap} from 'rxjs/operators'; +import {of as of$, Observable, combineLatest} from 'rxjs'; +import {switchMap, distinctUntilChanged} from 'rxjs/operators'; import {Config, Preferences} from '@constants'; import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database'; import {PUSH_PROXY_STATUS_UNKNOWN} from '@constants/push_proxy'; +import {isMinimumServerVersion} from '@utils/helpers'; import type ServerDataOperator from '@database/operator/server_data_operator'; +import type ConfigModel from '@typings/database/models/servers/config'; import type SystemModel from '@typings/database/models/servers/system'; export type PrepareCommonSystemValuesArgs = { - config?: ClientConfig; lastUnreadChannelId?: string; currentChannelId?: string; currentTeamId?: string; @@ -22,7 +23,7 @@ export type PrepareCommonSystemValuesArgs = { teamHistory?: string; } -const {SERVER: {SYSTEM}} = MM_TABLES; +const {SERVER: {SYSTEM, CONFIG}} = MM_TABLES; export const getCurrentChannelId = async (serverDatabase: Database): Promise => { try { @@ -94,7 +95,6 @@ export const observePushVerificationStatus = (database: Database): Observable { const systemRecords = (await serverDatabase.collections.get(SYSTEM).query().fetch()); - let config: ClientConfig = {} as ClientConfig; let license: ClientLicense = {} as ClientLicense; let currentChannelId = ''; let currentTeamId = ''; @@ -102,9 +102,6 @@ export const getCommonSystemValues = async (serverDatabase: Database) => { let lastUnreadChannelId = ''; systemRecords.forEach((systemRecord) => { switch (systemRecord.id) { - case SYSTEM_IDENTIFIERS.CONFIG: - config = systemRecord.value; - break; case SYSTEM_IDENTIFIERS.CURRENT_CHANNEL_ID: currentChannelId = systemRecord.value; break; @@ -128,51 +125,75 @@ export const getCommonSystemValues = async (serverDatabase: Database) => { currentTeamId, currentUserId, lastUnreadChannelId, - config, license, }; }; -export const getConfig = async (serverDatabase: Database) => { - try { - const config = await serverDatabase.get(SYSTEM).find(SYSTEM_IDENTIFIERS.CONFIG); - return (config?.value || {}) as ClientConfig; - } catch { - return undefined; - } +const fromModelToClientConfig = (list: ConfigModel[]) => { + const config: {[key: string]: any} = {}; + list.forEach((v) => { + config[v.id] = v.value; + }); + return config as ClientConfig; +}; + +export const getConfig = async (database: Database) => { + const configList = await database.get(CONFIG).query().fetch(); + return fromModelToClientConfig(configList); +}; + +export const queryConfigValue = (database: Database, key: keyof ClientConfig) => { + return database.get(CONFIG).query(Q.where('id', Q.eq(key))); +}; + +export const getConfigValue = async (database: Database, key: keyof ClientConfig) => { + const list = await queryConfigValue(database, key).fetch(); + return list.length ? list[0].value : undefined; }; export const observeConfig = (database: Database): Observable => { - return querySystemValue(database, SYSTEM_IDENTIFIERS.CONFIG).observe().pipe( - switchMap((result) => (result.length ? result[0].observe() : of$({value: undefined}))), - switchMap((model) => of$(model.value)), + return database.get(CONFIG).query().observeWithColumns(['value']).pipe( + switchMap((result) => of$(fromModelToClientConfig(result))), ); }; export const observeConfigValue = (database: Database, key: keyof ClientConfig) => { - return observeConfig(database).pipe( - switchMap((cfg) => of$(cfg?.[key])), + return queryConfigValue(database, key).observeWithColumns(['value']).pipe( + switchMap((result) => of$(result.length ? result[0].value : undefined)), + ); +}; + +export const observeMaxFileCount = (database: Database) => { + return observeConfigValue(database, 'Version').pipe( + switchMap((v) => of$(isMinimumServerVersion(v || '', 6, 0) ? 10 : 5)), + ); +}; + +export const observeIsCustomStatusExpirySupported = (database: Database) => { + return observeConfigValue(database, 'Version').pipe( + switchMap((v) => of$(isMinimumServerVersion(v || '', 5, 37))), ); }; export const observeConfigBooleanValue = (database: Database, key: keyof ClientConfig) => { - return observeConfig(database).pipe( - switchMap((cfg) => of$(cfg?.[key] === 'true')), + return observeConfigValue(database, key).pipe( + switchMap((v) => of$(v === 'true')), + distinctUntilChanged(), ); }; export const observeConfigIntValue = (database: Database, key: keyof ClientConfig, defaultValue = 0) => { - return observeConfig(database).pipe( - switchMap((cfg) => of$((parseInt(cfg?.[key] || '0', 10) || defaultValue))), + return observeConfigValue(database, key).pipe( + switchMap((v) => of$((parseInt(v || '0', 10) || defaultValue))), ); }; export const observeIsPostPriorityEnabled = (database: Database) => { - const config = observeConfig(database); - return config.pipe( - switchMap( - (cfg) => of$(cfg?.FeatureFlagPostPriority === Config.TRUE && cfg?.PostPriority === Config.TRUE), - ), + const featureFlag = observeConfigValue(database, 'FeatureFlagPostPriority'); + const cfg = observeConfigValue(database, 'PostPriority'); + return combineLatest([featureFlag, cfg]).pipe( + switchMap(([ff, c]) => of$(ff === Config.TRUE && c === Config.TRUE)), + distinctUntilChanged(), ); }; @@ -297,14 +318,8 @@ export const patchTeamHistory = (operator: ServerDataOperator, value: string[], export async function prepareCommonSystemValues( operator: ServerDataOperator, values: PrepareCommonSystemValuesArgs): Promise { try { - const {config, lastUnreadChannelId, currentChannelId, currentTeamId, currentUserId, license} = values; + const {lastUnreadChannelId, currentChannelId, currentTeamId, currentUserId, license} = values; const systems: IdValue[] = []; - if (config !== undefined) { - systems.push({ - id: SYSTEM_IDENTIFIERS.CONFIG, - value: JSON.stringify(config), - }); - } if (license !== undefined) { systems.push({ @@ -431,3 +446,17 @@ export const getExpiredSession = async (database: Database) => { return undefined; } }; + +export const observeCanUploadFiles = (database: Database) => { + const enableFileAttachments = observeConfigBooleanValue(database, 'EnableFileAttachments'); + const enableMobileFileUpload = observeConfigBooleanValue(database, 'EnableMobileFileUpload'); + const license = observeLicense(database); + + return combineLatest([enableFileAttachments, enableMobileFileUpload, license]).pipe( + switchMap(([efa, emfu, l]) => of$( + efa || + (l?.IsLicensed !== 'true' && l?.Compliance !== 'true' && emfu), + ), + ), + ); +}; diff --git a/app/queries/servers/thread.ts b/app/queries/servers/thread.ts index 2df721da3..3aae03cfd 100644 --- a/app/queries/servers/thread.ts +++ b/app/queries/servers/thread.ts @@ -10,7 +10,7 @@ import {MM_TABLES} from '@constants/database'; import {processIsCRTEnabled} from '@utils/thread'; import {queryPreferencesByCategoryAndName} from './preference'; -import {getConfig, observeConfig} from './system'; +import {getConfig, observeConfigValue} from './system'; import type ServerDataOperator from '@database/operator/server_data_operator'; import type Model from '@nozbe/watermelondb/Model'; @@ -22,7 +22,7 @@ const {SERVER: {CHANNEL, POST, THREAD, THREADS_IN_TEAM, THREAD_PARTICIPANT, USER export const getIsCRTEnabled = async (database: Database): Promise => { const config = await getConfig(database); const preferences = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS).fetch(); - return processIsCRTEnabled(preferences, config); + return processIsCRTEnabled(preferences, config?.CollapsedThreads, config?.FeatureFlagCollapsedThreads); }; export const getThreadById = async (database: Database, threadId: string) => { @@ -35,11 +35,12 @@ export const getThreadById = async (database: Database, threadId: string) => { }; export const observeIsCRTEnabled = (database: Database) => { - const config = observeConfig(database); + const cfgValue = observeConfigValue(database, 'CollapsedThreads'); + const featureFlag = observeConfigValue(database, 'FeatureFlagCollapsedThreads'); const preferences = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS).observeWithColumns(['value']); - return combineLatest([config, preferences]).pipe( + return combineLatest([cfgValue, featureFlag, preferences]).pipe( map( - ([cfg, prefs]) => processIsCRTEnabled(prefs, cfg), + ([cfgV, ff, prefs]) => processIsCRTEnabled(prefs, cfgV, ff), ), distinctUntilChanged(), ); diff --git a/app/queries/servers/user.ts b/app/queries/servers/user.ts index 784f516d3..3279ec117 100644 --- a/app/queries/servers/user.ts +++ b/app/queries/servers/user.ts @@ -12,7 +12,7 @@ import {observeMyChannel} from '@queries/servers/channel'; import {isChannelAdmin} from '@utils/user'; import {queryPreferencesByCategoryAndName} from './preference'; -import {observeConfig, observeCurrentUserId, observeLicense, getCurrentUserId, getConfig, getLicense} from './system'; +import {observeCurrentUserId, observeLicense, getCurrentUserId, getConfig, getLicense, observeConfigValue} from './system'; import type ServerDataOperator from '@database/operator/server_data_operator'; import type ChannelMembershipModel from '@typings/database/models/servers/channel_membership'; @@ -67,13 +67,14 @@ export async function prepareUsers(operator: ServerDataOperator, users: UserProf } export const observeTeammateNameDisplay = (database: Database) => { - const config = observeConfig(database); + const lockTeammateNameDisplay = observeConfigValue(database, 'LockTeammateNameDisplay'); + const teammateNameDisplay = observeConfigValue(database, 'TeammateNameDisplay'); const license = observeLicense(database); const preferences = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS). observeWithColumns(['value']); - return combineLatest([config, license, preferences]).pipe( + return combineLatest([lockTeammateNameDisplay, teammateNameDisplay, license, preferences]).pipe( switchMap( - ([cfg, lcs, prefs]) => of$(getTeammateNameDisplaySetting(prefs, cfg, lcs)), + ([ltnd, tnd, lcs, prefs]) => of$(getTeammateNameDisplaySetting(prefs, ltnd, tnd, lcs)), ), ); }; @@ -82,7 +83,7 @@ export async function getTeammateNameDisplay(database: Database) { const config = await getConfig(database); const license = await getLicense(database); const preferences = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS).fetch(); - return getTeammateNameDisplaySetting(preferences, config, license); + return getTeammateNameDisplaySetting(preferences, config?.LockTeammateNameDisplay, config?.TeammateNameDisplay, license); } export const queryUsersLike = (database: Database, likeUsername: string) => { diff --git a/app/screens/browse_channels/index.ts b/app/screens/browse_channels/index.ts index 4d85c3628..e0b0ab9d6 100644 --- a/app/screens/browse_channels/index.ts +++ b/app/screens/browse_channels/index.ts @@ -9,7 +9,7 @@ import {switchMap} from 'rxjs/operators'; import {Permissions} from '@constants'; import {queryAllMyChannel} from '@queries/servers/channel'; import {queryRolesByNames} from '@queries/servers/role'; -import {observeConfig, observeCurrentTeamId, observeCurrentUserId} from '@queries/servers/system'; +import {observeConfigBooleanValue, observeCurrentTeamId, observeCurrentUserId} from '@queries/servers/system'; import {observeUser} from '@queries/servers/user'; import {hasPermission} from '@utils/role'; @@ -18,15 +18,8 @@ import SearchHandler from './search_handler'; import type {WithDatabaseArgs} from '@typings/database/database'; const enhanced = withObservables([], ({database}: WithDatabaseArgs) => { - const config = observeConfig(database); - - const sharedChannelsEnabled = config.pipe( - switchMap((v) => of$(v?.ExperimentalSharedChannels === 'true')), - ); - - const canShowArchivedChannels = config.pipe( - switchMap((v) => of$(v?.ExperimentalViewArchivedChannels === 'true')), - ); + const sharedChannelsEnabled = observeConfigBooleanValue(database, 'ExperimentalSharedChannels'); + const canShowArchivedChannels = observeConfigBooleanValue(database, 'ExperimentalViewArchivedChannels'); const currentTeamId = observeCurrentTeamId(database); const currentUserId = observeCurrentUserId(database); diff --git a/app/screens/channel/channel_post_list/intro/public_or_private_channel/index.ts b/app/screens/channel/channel_post_list/intro/public_or_private_channel/index.ts index e3a18ee76..79715c3e1 100644 --- a/app/screens/channel/channel_post_list/intro/public_or_private_channel/index.ts +++ b/app/screens/channel/channel_post_list/intro/public_or_private_channel/index.ts @@ -6,11 +6,7 @@ import withObservables from '@nozbe/with-observables'; import {combineLatest, of as of$} from 'rxjs'; import {map} from 'rxjs/operators'; -import {Preferences} from '@constants'; -import {getTeammateNameDisplaySetting} from '@helpers/api/preference'; -import {queryPreferencesByCategoryAndName} from '@queries/servers/preference'; -import {observeConfig, observeLicense} from '@queries/servers/system'; -import {observeCurrentUser, observeUser} from '@queries/servers/user'; +import {observeCurrentUser, observeTeammateNameDisplay, observeUser} from '@queries/servers/user'; import {displayUsername} from '@utils/user'; import PublicOrPrivateChannel from './public_or_private_channel'; @@ -21,16 +17,10 @@ import type ChannelModel from '@typings/database/models/servers/channel'; const enhanced = withObservables([], ({channel, database}: {channel: ChannelModel} & WithDatabaseArgs) => { let creator; if (channel.creatorId) { - const config = observeConfig(database); - const license = observeLicense(database); - const preferences = queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS). - observeWithColumns(['value']); const me = observeCurrentUser(database); const profile = observeUser(database, channel.creatorId); - const teammateNameDisplay = combineLatest([preferences, config, license]).pipe( - map(([prefs, cfg, lcs]) => getTeammateNameDisplaySetting(prefs, cfg, lcs)), - ); + const teammateNameDisplay = observeTeammateNameDisplay(database); creator = combineLatest([profile, teammateNameDisplay, me]).pipe( map(([user, displaySetting, currentUser]) => (user ? displayUsername(user, currentUser?.locale, displaySetting, true) : '')), diff --git a/app/screens/create_direct_message/index.ts b/app/screens/create_direct_message/index.ts index 4e8c607d2..8403c4454 100644 --- a/app/screens/create_direct_message/index.ts +++ b/app/screens/create_direct_message/index.ts @@ -8,7 +8,7 @@ import {switchMap} from 'rxjs/operators'; import {General} from '@constants'; import {observeProfileLongPresTutorial} from '@queries/app/global'; -import {observeConfig, observeCurrentTeamId, observeCurrentUserId} from '@queries/servers/system'; +import {observeConfigValue, observeCurrentTeamId, observeCurrentUserId} from '@queries/servers/system'; import {observeTeammateNameDisplay} from '@queries/servers/user'; import CreateDirectMessage from './create_direct_message'; @@ -16,8 +16,8 @@ import CreateDirectMessage from './create_direct_message'; import type {WithDatabaseArgs} from '@typings/database/database'; const enhanced = withObservables([], ({database}: WithDatabaseArgs) => { - const restrictDirectMessage = observeConfig(database).pipe( - switchMap((cfg) => of$(cfg?.RestrictDirectMessage !== General.RESTRICT_DIRECT_MESSAGE_ANY)), + const restrictDirectMessage = observeConfigValue(database, 'RestrictDirectMessage').pipe( + switchMap((v) => of$(v !== General.RESTRICT_DIRECT_MESSAGE_ANY)), ); return { diff --git a/app/screens/custom_status/components/custom_status_suggestion.tsx b/app/screens/custom_status/components/custom_status_suggestion/custom_status_suggestion.tsx similarity index 100% rename from app/screens/custom_status/components/custom_status_suggestion.tsx rename to app/screens/custom_status/components/custom_status_suggestion/custom_status_suggestion.tsx diff --git a/app/screens/custom_status/components/custom_status_suggestion/index.ts b/app/screens/custom_status/components/custom_status_suggestion/index.ts new file mode 100644 index 000000000..911e4d312 --- /dev/null +++ b/app/screens/custom_status/components/custom_status_suggestion/index.ts @@ -0,0 +1,19 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider'; +import withObservables from '@nozbe/with-observables'; + +import {observeIsCustomStatusExpirySupported} from '@queries/servers/system'; + +import CustomStatusSuggestion from './custom_status_suggestion'; + +import type {WithDatabaseArgs} from '@typings/database/database'; + +const enhanced = withObservables([], ({database}: WithDatabaseArgs) => { + return { + isExpirySupported: observeIsCustomStatusExpirySupported(database), + }; +}); + +export default withDatabase(enhanced(CustomStatusSuggestion)); diff --git a/app/screens/custom_status/components/custom_status_suggestions.tsx b/app/screens/custom_status/components/custom_status_suggestions.tsx index 58513b4b3..6eb6a962f 100644 --- a/app/screens/custom_status/components/custom_status_suggestions.tsx +++ b/app/screens/custom_status/components/custom_status_suggestions.tsx @@ -13,7 +13,6 @@ import CustomStatusSuggestion from './custom_status_suggestion'; type Props = { intl: IntlShape; - isExpirySupported: boolean; onHandleCustomStatusSuggestionClick: (status: UserCustomStatus) => void; recentCustomStatuses: UserCustomStatus[]; theme: Theme; @@ -57,7 +56,6 @@ const defaultCustomStatusSuggestions: DefaultUserCustomStatus[] = [ const CustomStatusSuggestions = ({ intl, - isExpirySupported, onHandleCustomStatusSuggestionClick, recentCustomStatuses, theme, @@ -81,7 +79,6 @@ const CustomStatusSuggestions = ({ theme={theme} separator={index !== arr.length - 1} duration={status.duration} - isExpirySupported={isExpirySupported} /> )); diff --git a/app/screens/custom_status/components/recent_custom_statuses.tsx b/app/screens/custom_status/components/recent_custom_statuses.tsx index ef028e784..3a19f5498 100644 --- a/app/screens/custom_status/components/recent_custom_statuses.tsx +++ b/app/screens/custom_status/components/recent_custom_statuses.tsx @@ -10,7 +10,6 @@ import CustomStatusSuggestion from '@screens/custom_status/components/custom_sta import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; type Props = { - isExpirySupported: boolean; onHandleClear: (status: UserCustomStatus) => void; onHandleSuggestionClick: (status: UserCustomStatus) => void; recentCustomStatuses: UserCustomStatus[]; @@ -38,7 +37,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { }; }); -const RecentCustomStatuses = ({isExpirySupported, onHandleClear, onHandleSuggestionClick, recentCustomStatuses, theme}: Props) => { +const RecentCustomStatuses = ({onHandleClear, onHandleSuggestionClick, recentCustomStatuses, theme}: Props) => { const style = getStyleSheet(theme); if (recentCustomStatuses.length === 0) { @@ -66,7 +65,6 @@ const RecentCustomStatuses = ({isExpirySupported, onHandleClear, onHandleSuggest separator={index !== recentCustomStatuses.length - 1} duration={status.duration} expires_at={status.expires_at} - isExpirySupported={isExpirySupported} /> ))} diff --git a/app/screens/custom_status/index.tsx b/app/screens/custom_status/index.tsx index 5aec63c5b..e923a0526 100644 --- a/app/screens/custom_status/index.tsx +++ b/app/screens/custom_status/index.tsx @@ -9,8 +9,6 @@ import {injectIntl, IntlShape} from 'react-intl'; import {BackHandler, DeviceEventEmitter, Keyboard, KeyboardAvoidingView, Platform, ScrollView, View} from 'react-native'; import {EventSubscription, Navigation, NavigationButtonPressedEvent, NavigationComponent, NavigationComponentProps} from 'react-native-navigation'; import {Edge, SafeAreaView} from 'react-native-safe-area-context'; -import {of as of$} from 'rxjs'; -import {switchMap} from 'rxjs/operators'; import {updateLocalCustomStatus} from '@actions/local/user'; import {removeRecentCustomStatus, updateCustomStatus, unsetCustomStatus} from '@actions/remote/user'; @@ -20,11 +18,11 @@ import {Events, Screens} from '@constants'; import {CustomStatusDurationEnum, SET_CUSTOM_STATUS_FAILURE} from '@constants/custom_status'; import {withServerUrl} from '@context/server'; import {withTheme} from '@context/theme'; -import {observeConfig, observeRecentCustomStatus} from '@queries/servers/system'; +import {observeIsCustomStatusExpirySupported, observeRecentCustomStatus} from '@queries/servers/system'; import {observeCurrentUser} from '@queries/servers/user'; import {dismissModal, goToScreen, showModal} from '@screens/navigation'; import NavigationStore from '@store/navigation_store'; -import {getCurrentMomentForTimezone, getRoundedTime, isCustomStatusExpirySupported} from '@utils/helpers'; +import {getCurrentMomentForTimezone, getRoundedTime} from '@utils/helpers'; import {logDebug} from '@utils/log'; import {mergeNavigationOptions} from '@utils/navigation'; import {preventDoubleTap} from '@utils/tap'; @@ -376,7 +374,6 @@ class CustomStatusModal extends NavigationComponent { {recentCustomStatuses.length > 0 && ( { } { const augmentCSM = injectIntl(withTheme(withServerUrl(CustomStatusModal))); const enhancedCSM = withObservables([], ({database}: WithDatabaseArgs) => { - const config = observeConfig(database); return { currentUser: observeCurrentUser(database), - customStatusExpirySupported: config.pipe( - switchMap((cfg) => of$(isCustomStatusExpirySupported(cfg?.Version || ''))), - ), recentCustomStatuses: observeRecentCustomStatus(database), + customStatusExpirySupported: observeIsCustomStatusExpirySupported(database), }; }); diff --git a/app/screens/edit_profile/index.ts b/app/screens/edit_profile/index.ts index f16f08a79..bbc7c338e 100644 --- a/app/screens/edit_profile/index.ts +++ b/app/screens/edit_profile/index.ts @@ -3,10 +3,10 @@ import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider'; import withObservables from '@nozbe/with-observables'; -import {of as of$} from 'rxjs'; +import {of as of$, combineLatest} from 'rxjs'; import {switchMap} from 'rxjs/operators'; -import {observeConfig} from '@queries/servers/system'; +import {observeConfigBooleanValue} from '@queries/servers/system'; import {observeCurrentUser} from '@queries/servers/user'; import EditProfile from './edit_profile'; @@ -14,35 +14,31 @@ import EditProfile from './edit_profile'; import type {WithDatabaseArgs} from '@typings/database/database'; const enhanced = withObservables([], ({database}: WithDatabaseArgs) => { - const config = observeConfig(database); + const ldapFirstNameAttributeSet = observeConfigBooleanValue(database, 'LdapFirstNameAttributeSet'); + const ldapLastNameAttributeSet = observeConfigBooleanValue(database, 'LdapLastNameAttributeSet'); + const ldapNicknameAttributeSet = observeConfigBooleanValue(database, 'LdapNicknameAttributeSet'); + const ldapPositionAttributeSet = observeConfigBooleanValue(database, 'LdapPositionAttributeSet'); + + const samlFirstNameAttributeSet = observeConfigBooleanValue(database, 'SamlFirstNameAttributeSet'); + const samlLastNameAttributeSet = observeConfigBooleanValue(database, 'SamlLastNameAttributeSet'); + const samlNicknameAttributeSet = observeConfigBooleanValue(database, 'SamlNicknameAttributeSet'); + const samlPositionAttributeSet = observeConfigBooleanValue(database, 'SamlPositionAttributeSet'); return { currentUser: observeCurrentUser(database), - lockedFirstName: config.pipe( - switchMap( - (cfg) => of$(cfg?.LdapFirstNameAttributeSet === 'true' || cfg?.SamlFirstNameAttributeSet === 'true'), - ), + lockedFirstName: combineLatest([ldapFirstNameAttributeSet, samlFirstNameAttributeSet]).pipe( + switchMap(([ldap, saml]) => of$(ldap || saml)), ), - lockedLastName: config.pipe( - switchMap( - (cfg) => of$(cfg?.LdapLastNameAttributeSet === 'true' || cfg?.SamlLastNameAttributeSet === 'true'), - ), + lockedLastName: combineLatest([ldapLastNameAttributeSet, samlLastNameAttributeSet]).pipe( + switchMap(([ldap, saml]) => of$(ldap || saml)), ), - lockedNickname: config.pipe( - switchMap( - (cfg) => of$(cfg?.LdapNicknameAttributeSet === 'true' || cfg?.SamlNicknameAttributeSet === 'true'), - ), + lockedNickname: combineLatest([ldapNicknameAttributeSet, samlNicknameAttributeSet]).pipe( + switchMap(([ldap, saml]) => of$(ldap || saml)), ), - lockedPosition: config.pipe( - switchMap( - (cfg) => of$(cfg?.LdapPositionAttributeSet === 'true' || cfg?.SamlPositionAttributeSet === 'true'), - ), - ), - lockedPicture: config.pipe( - switchMap( - (cfg) => of$(cfg?.LdapPictureAttributeSet === 'true'), - ), + lockedPosition: combineLatest([ldapPositionAttributeSet, samlPositionAttributeSet]).pipe( + switchMap(([ldap, saml]) => of$(ldap || saml)), ), + lockedPicture: observeConfigBooleanValue(database, 'LdapPictureAttributeSet'), }; }); diff --git a/app/screens/find_channels/filtered_list/index.ts b/app/screens/find_channels/filtered_list/index.ts index 0007efa2d..c12c9d15c 100644 --- a/app/screens/find_channels/filtered_list/index.ts +++ b/app/screens/find_channels/filtered_list/index.ts @@ -8,7 +8,7 @@ import {combineLatestWith, switchMap} from 'rxjs/operators'; import {General} from '@constants'; import {observeArchiveChannelsByTerm, observeDirectChannelsByTerm, observeJoinedChannelsByTerm, observeNotDirectChannelsByTerm} from '@queries/servers/channel'; -import {observeConfig, observeCurrentTeamId} from '@queries/servers/system'; +import {observeConfigValue, observeCurrentTeamId} from '@queries/servers/system'; import {queryJoinedTeams} from '@queries/servers/team'; import {observeTeammateNameDisplay} from '@queries/servers/user'; import {retrieveChannels} from '@screens/find_channels/utils'; @@ -50,8 +50,8 @@ const enhanced = withObservables(['term'], ({database, term}: EnhanceProps) => { const usersMatchStart = observeNotDirectChannelsByTerm(database, term, MAX_RESULTS, true); const usersMatch = observeNotDirectChannelsByTerm(database, term, MAX_RESULTS); - const restrictDirectMessage = observeConfig(database).pipe( - switchMap((cfg) => of$(cfg?.RestrictDirectMessage !== General.RESTRICT_DIRECT_MESSAGE_ANY)), + const restrictDirectMessage = observeConfigValue(database, 'RestrictDirectMessage').pipe( + switchMap((v) => of$(v !== General.RESTRICT_DIRECT_MESSAGE_ANY)), ); const teammateDisplayNameSetting = observeTeammateNameDisplay(database); diff --git a/app/screens/gallery/footer/index.ts b/app/screens/gallery/footer/index.ts index a030a9905..4b64fdab2 100644 --- a/app/screens/gallery/footer/index.ts +++ b/app/screens/gallery/footer/index.ts @@ -9,7 +9,7 @@ import {switchMap} from 'rxjs/operators'; import {General} from '@constants'; import {observeChannel} from '@queries/servers/channel'; import {observePost} from '@queries/servers/post'; -import {observeConfig, observeCurrentChannelId, observeCurrentUserId, observeLicense} from '@queries/servers/system'; +import {observeConfigBooleanValue, observeCurrentChannelId, observeCurrentUserId, observeLicense} from '@queries/servers/system'; import {observeTeammateNameDisplay, observeUser} from '@queries/servers/user'; import Footer from './footer'; @@ -27,7 +27,6 @@ const enhanced = withObservables(['item'], ({database, item}: FooterProps) => { const currentChannelId = observeCurrentChannelId(database); const currentUserId = observeCurrentUserId(database); - const config = observeConfig(database); const license = observeLicense(database); const teammateNameDisplay = observeTeammateNameDisplay(database); @@ -47,10 +46,10 @@ const enhanced = withObservables(['item'], ({database, item}: FooterProps) => { return p?.channel.observe() || observeChannel(database, cId); }), ); - const enablePostUsernameOverride = config.pipe(switchMap((c) => of$(c?.EnablePostUsernameOverride === 'true'))); - const enablePostIconOverride = config.pipe(switchMap((c) => of$(c?.EnablePostIconOverride === 'true'))); - const enablePublicLink = config.pipe(switchMap((c) => of$(c?.EnablePublicLink === 'true'))); - const enableMobileFileDownload = config.pipe(switchMap((c) => of$(c?.EnableMobileFileDownload !== 'false'))); + const enablePostUsernameOverride = observeConfigBooleanValue(database, 'EnablePostUsernameOverride'); + const enablePostIconOverride = observeConfigBooleanValue(database, 'EnablePostIconOverride'); + const enablePublicLink = observeConfigBooleanValue(database, 'EnablePublicLink'); + const enableMobileFileDownload = observeConfigBooleanValue(database, 'EnableMobileFileDownload'); const complianceDisabled = license.pipe(switchMap((l) => of$(l?.IsLicensed === 'false' || l?.Compliance === 'false'))); const canDownloadFiles = combineLatest([enableMobileFileDownload, complianceDisabled]).pipe( switchMap(([download, compliance]) => of$(compliance || download)), diff --git a/app/screens/home/account/account.tsx b/app/screens/home/account/account.tsx index 61a3d65fb..e18841311 100644 --- a/app/screens/home/account/account.tsx +++ b/app/screens/home/account/account.tsx @@ -20,7 +20,6 @@ import type UserModel from '@typings/database/models/servers/user'; type AccountScreenProps = { currentUser: UserModel; - customStatusExpirySupported: boolean; enableCustomUserStatuses: boolean; showFullName: boolean; }; @@ -57,7 +56,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => { }; }); -const AccountScreen = ({currentUser, enableCustomUserStatuses, customStatusExpirySupported, showFullName}: AccountScreenProps) => { +const AccountScreen = ({currentUser, enableCustomUserStatuses, showFullName}: AccountScreenProps) => { const theme = useTheme(); const [start, setStart] = useState(false); const route = useRoute(); @@ -119,7 +118,6 @@ const AccountScreen = ({currentUser, enableCustomUserStatuses, customStatusExpir /> { + return { + isCustomStatusExpirySupported: observeIsCustomStatusExpirySupported(database), + }; +}); + +export default withDatabase(enhanced(CustomLabel)); diff --git a/app/screens/home/account/components/options/custom_status/index.tsx b/app/screens/home/account/components/options/custom_status/index.tsx index ae775ac80..8d3cc9a68 100644 --- a/app/screens/home/account/components/options/custom_status/index.tsx +++ b/app/screens/home/account/components/options/custom_status/index.tsx @@ -38,12 +38,11 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { }); type CustomStatusProps = { - isCustomStatusExpirySupported: boolean; isTablet: boolean; currentUser: UserModel; } -const CustomStatus = ({isCustomStatusExpirySupported, isTablet, currentUser}: CustomStatusProps) => { +const CustomStatus = ({isTablet, currentUser}: CustomStatusProps) => { const theme = useTheme(); const intl = useIntl(); const serverUrl = useServerUrl(); @@ -96,7 +95,6 @@ const CustomStatus = ({isCustomStatusExpirySupported, isTablet, currentUser}: Cu /> { }; }); -const AccountOptions = ({user, enableCustomUserStatuses, isCustomStatusExpirySupported, isTablet, theme}: AccountScreenProps) => { +const AccountOptions = ({user, enableCustomUserStatuses, isTablet, theme}: AccountScreenProps) => { const styles = getStyleSheet(theme); return ( @@ -59,7 +58,6 @@ const AccountOptions = ({user, enableCustomUserStatuses, isCustomStatusExpirySup /> {enableCustomUserStatuses && } diff --git a/app/screens/home/account/index.ts b/app/screens/home/account/index.ts index bcd2b8b97..fe44aeb0f 100644 --- a/app/screens/home/account/index.ts +++ b/app/screens/home/account/index.ts @@ -4,35 +4,28 @@ import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider'; import withObservables from '@nozbe/with-observables'; import {of as of$} from 'rxjs'; -import {switchMap} from 'rxjs/operators'; +import {combineLatestWith, switchMap} from 'rxjs/operators'; -import {observeConfig} from '@queries/servers/system'; +import {observeConfigBooleanValue, observeConfigValue} from '@queries/servers/system'; import {observeCurrentUser} from '@queries/servers/user'; -import {isCustomStatusExpirySupported, isMinimumServerVersion} from '@utils/helpers'; +import {isMinimumServerVersion} from '@utils/helpers'; import AccountScreen from './account'; import type {WithDatabaseArgs} from '@typings/database/database'; const enhanced = withObservables([], ({database}: WithDatabaseArgs) => { - const config = observeConfig(database); - const showFullName = config.pipe((switchMap((cfg) => of$(cfg?.ShowFullName === 'true')))); - const enableCustomUserStatuses = config.pipe((switchMap((cfg) => { - return of$(cfg?.EnableCustomUserStatuses === 'true' && isMinimumServerVersion(cfg?.Version || '', 5, 36)); - }))); - const version = config.pipe( - switchMap((cfg) => of$(cfg?.Version || '')), - ); - const customStatusExpirySupported = config.pipe( - switchMap((cfg) => of$(isCustomStatusExpirySupported(cfg?.Version || ''))), + const showFullName = observeConfigBooleanValue(database, 'ShowFullName'); + const version = observeConfigValue(database, 'Version'); + const enableCustomUserStatuses = observeConfigBooleanValue(database, 'EnableCustomUserStatuses').pipe( + combineLatestWith(version), + switchMap(([cfg, v]) => of$(cfg && isMinimumServerVersion(v || '', 5, 36))), ); return { currentUser: observeCurrentUser(database), enableCustomUserStatuses, - customStatusExpirySupported, showFullName, - version, }; }); diff --git a/app/screens/home/channel_list/categories_list/categories/body/index.ts b/app/screens/home/channel_list/categories_list/categories/body/index.ts index 0c966852a..4ec32b9a5 100644 --- a/app/screens/home/channel_list/categories_list/categories/body/index.ts +++ b/app/screens/home/channel_list/categories_list/categories/body/index.ts @@ -7,9 +7,9 @@ import withObservables from '@nozbe/with-observables'; import {combineLatest, of as of$} from 'rxjs'; import {map, switchMap, combineLatestWith} from 'rxjs/operators'; -import {MyChannelModel} from '@app/database/models/server'; import {General, Preferences} from '@constants'; import {DMS_CATEGORY} from '@constants/categories'; +import {MyChannelModel} from '@database/models/server'; import {getPreferenceAsBool} from '@helpers/api/preference'; import {observeChannelsByCategoryChannelSortOrder, observeChannelsByLastPostAtInCategory} from '@queries/servers/categories'; import {observeNotifyPropsByChannels, queryChannelsByNames, queryEmptyDirectAndGroupChannels} from '@queries/servers/channel'; diff --git a/app/screens/home/channel_list/categories_list/threads_button/index.ts b/app/screens/home/channel_list/categories_list/threads_button/index.ts index 657eb1f10..87c0c696d 100644 --- a/app/screens/home/channel_list/categories_list/threads_button/index.ts +++ b/app/screens/home/channel_list/categories_list/threads_button/index.ts @@ -6,10 +6,10 @@ import withObservables from '@nozbe/with-observables'; import {of as of$} from 'rxjs'; import {switchMap} from 'rxjs/operators'; -import Preferences from '@app/constants/preferences'; -import {PreferenceModel} from '@app/database/models/server'; -import {queryPreferencesByCategoryAndName} from '@app/queries/servers/preference'; +import Preferences from '@constants/preferences'; +import {PreferenceModel} from '@database/models/server'; import {getPreferenceAsBool} from '@helpers/api/preference'; +import {queryPreferencesByCategoryAndName} from '@queries/servers/preference'; import {observeCurrentChannelId, observeCurrentTeamId, observeOnlyUnreads} from '@queries/servers/system'; import {observeUnreadsAndMentionsInTeam} from '@queries/servers/thread'; diff --git a/app/screens/home/search/results/file_results.tsx b/app/screens/home/search/results/file_results.tsx index ada3f5f98..fdf6950fb 100644 --- a/app/screens/home/search/results/file_results.tsx +++ b/app/screens/home/search/results/file_results.tsx @@ -5,10 +5,10 @@ import React, {useCallback, useMemo, useState} from 'react'; import {FlatList, ListRenderItemInfo, StyleProp, ViewStyle} from 'react-native'; import {useSafeAreaInsets} from 'react-native-safe-area-context'; -import {useIsTablet} from '@app/hooks/device'; -import {useImageAttachments} from '@app/hooks/files'; import NoResultsWithTerm from '@components/no_results_with_term'; import {useTheme} from '@context/theme'; +import {useIsTablet} from '@hooks/device'; +import {useImageAttachments} from '@hooks/files'; import {GalleryAction} from '@typings/screens/gallery'; import { getChannelNamesWithID, diff --git a/app/screens/home/search/results/results.tsx b/app/screens/home/search/results/results.tsx index 5971ec699..b3f42023d 100644 --- a/app/screens/home/search/results/results.tsx +++ b/app/screens/home/search/results/results.tsx @@ -5,7 +5,7 @@ import React, {useMemo} from 'react'; import {ScaledSize, StyleSheet, useWindowDimensions, View} from 'react-native'; import Animated, {useAnimatedStyle, withTiming} from 'react-native-reanimated'; -import Loading from '@app/components/loading'; +import Loading from '@components/loading'; import {useTheme} from '@context/theme'; import {TabTypes, TabType} from '@utils/search'; diff --git a/app/screens/home/search/search.tsx b/app/screens/home/search/search.tsx index 512cbbf83..93b982a22 100644 --- a/app/screens/home/search/search.tsx +++ b/app/screens/home/search/search.tsx @@ -11,7 +11,6 @@ import {Edge, SafeAreaView, useSafeAreaInsets} from 'react-native-safe-area-cont import {getPosts} from '@actions/local/post'; import {addSearchToTeamSearchHistory} from '@actions/local/team'; import {searchPosts, searchFiles} from '@actions/remote/search'; -import useDidUpdate from '@app/hooks/did_update'; import Autocomplete from '@components/autocomplete'; import FreezeScreen from '@components/freeze_screen'; import Loading from '@components/loading'; @@ -22,6 +21,7 @@ import {BOTTOM_TAB_HEIGHT} from '@constants/view'; import {useServerUrl} from '@context/server'; import {useTheme} from '@context/theme'; import {useKeyboardHeight} from '@hooks/device'; +import useDidUpdate from '@hooks/did_update'; import {useCollapsibleHeader} from '@hooks/header'; import {FileFilter, FileFilters, filterFileExtensions} from '@utils/file'; import {TabTypes, TabType} from '@utils/search'; diff --git a/app/screens/in_app_notification/icon.tsx b/app/screens/in_app_notification/icon.tsx index f20893dd2..17c039149 100644 --- a/app/screens/in_app_notification/icon.tsx +++ b/app/screens/in_app_notification/icon.tsx @@ -5,12 +5,10 @@ import withObservables from '@nozbe/with-observables'; import React from 'react'; import {StyleSheet, View} from 'react-native'; import FastImage, {Source} from 'react-native-fast-image'; -import {of as of$} from 'rxjs'; -import {switchMap} from 'rxjs/operators'; import CompassIcon from '@components/compass_icon'; import NetworkManager from '@managers/network_manager'; -import {observeConfig} from '@queries/servers/system'; +import {observeConfigBooleanValue} from '@queries/servers/system'; import {observeUser} from '@queries/servers/user'; import type {Client} from '@client/rest'; @@ -89,14 +87,11 @@ const NotificationIcon = ({author, enablePostIconOverride, fromWebhook, override }; const enhanced = withObservables([], ({database, senderId}: WithDatabaseArgs & {senderId: string}) => { - const config = observeConfig(database); const author = observeUser(database, senderId); return { author, - enablePostIconOverride: config.pipe( - switchMap((cfg) => of$(cfg?.EnablePostIconOverride === 'true')), - ), + enablePostIconOverride: observeConfigBooleanValue(database, 'EnablePostIconOverride'), }; }); diff --git a/app/screens/post_options/index.ts b/app/screens/post_options/index.ts index 57c2be72a..cf33174ca 100644 --- a/app/screens/post_options/index.ts +++ b/app/screens/post_options/index.ts @@ -10,7 +10,7 @@ import {General, Permissions, Post, Screens} from '@constants'; import {MAX_ALLOWED_REACTIONS} from '@constants/emoji'; import {observePost, observePostSaved} from '@queries/servers/post'; import {observePermissionForChannel, observePermissionForPost} from '@queries/servers/role'; -import {observeConfig, observeLicense} from '@queries/servers/system'; +import {observeConfigBooleanValue, observeConfigIntValue, observeConfigValue, observeLicense} from '@queries/servers/system'; import {observeIsCRTEnabled, observeThreadById} from '@queries/servers/thread'; import {observeCurrentUser} from '@queries/servers/user'; import {isMinimumServerVersion} from '@utils/helpers'; @@ -73,11 +73,10 @@ const enhanced = withObservables([], ({combinedPost, post, showAddReaction, loca const channel = post.channel.observe(); const channelIsArchived = channel.pipe(switchMap((ch: ChannelModel) => of$(ch.deleteAt !== 0))); const currentUser = observeCurrentUser(database); - const config = observeConfig(database); const isLicensed = observeLicense(database).pipe(switchMap((lcs) => of$(lcs?.IsLicensed === 'true'))); - const allowEditPost = config.pipe(switchMap((cfg) => of$(cfg?.AllowEditPost))); - const serverVersion = config.pipe(switchMap((cfg) => of$(cfg?.Version || ''))); - const postEditTimeLimit = config.pipe(switchMap((cfg) => of$(parseInt(cfg?.PostEditTimeLimit || '-1', 10)))); + const allowEditPost = observeConfigValue(database, 'AllowEditPost'); + const serverVersion = observeConfigValue(database, 'Version'); + const postEditTimeLimit = observeConfigIntValue(database, 'PostEditTimeLimit', -1); const canPostPermission = combineLatest([channel, currentUser]).pipe(switchMap(([c, u]) => observePermissionForChannel(database, c, u, Permissions.CREATE_POST, false))); const hasAddReactionPermission = currentUser.pipe(switchMap((u) => observePermissionForPost(database, post, u, Permissions.ADD_REACTION, true))); @@ -86,7 +85,7 @@ const enhanced = withObservables([], ({combinedPost, post, showAddReaction, loca return observePermissionForPost(database, post, u, isOwner ? Permissions.DELETE_POST : Permissions.DELETE_OTHERS_POSTS, false); })); - const experimentalTownSquareIsReadOnly = config.pipe(switchMap((value) => of$(value?.ExperimentalTownSquareIsReadOnly === 'true'))); + const experimentalTownSquareIsReadOnly = observeConfigBooleanValue(database, 'ExperimentalTownSquareIsReadOnly'); const channelIsReadOnly = combineLatest([currentUser, channel, experimentalTownSquareIsReadOnly]).pipe(switchMap(([u, c, readOnly]) => { return of$(c?.name === General.DEFAULT_CHANNEL && (u && !isSystemAdmin(u.roles)) && readOnly); })); @@ -98,7 +97,7 @@ const enhanced = withObservables([], ({combinedPost, post, showAddReaction, loca const canEditUntil = combineLatest([isLicensed, allowEditPost, postEditTimeLimit, serverVersion, channelIsArchived, channelIsReadOnly]).pipe( switchMap(([ls, alw, limit, semVer, isArchived, isReadOnly]) => { - if (!isArchived && !isReadOnly && ls && ((alw === Permissions.ALLOW_EDIT_POST_TIME_LIMIT && !isMinimumServerVersion(semVer, 6)) || (limit !== -1))) { + if (!isArchived && !isReadOnly && ls && ((alw === Permissions.ALLOW_EDIT_POST_TIME_LIMIT && !isMinimumServerVersion(semVer || '', 6)) || (limit !== -1))) { return of$(post.createAt + (limit * (1000))); } return of$(-1); diff --git a/app/utils/files.tsx b/app/utils/files.tsx index 8c625ee6d..d8375da91 100644 --- a/app/utils/files.tsx +++ b/app/utils/files.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {ChannelModel} from '@app/database/models/server'; +import {ChannelModel} from '@database/models/server'; import {fileToGalleryItem} from '@utils/gallery'; export const getNumberFileMenuOptions = (canDownloadFiles: boolean, publicLinkEnabled: boolean) => { diff --git a/app/utils/helpers.ts b/app/utils/helpers.ts index 7f55e26ec..a3019c199 100644 --- a/app/utils/helpers.ts +++ b/app/utils/helpers.ts @@ -111,10 +111,6 @@ export function getUtcOffsetForTimeZone(timezone: string) { return moment.tz(timezone).utcOffset(); } -export function isCustomStatusExpirySupported(version: string) { - return isMinimumServerVersion(version, 5, 37); -} - export function toTitleCase(str: string) { function doTitleCase(txt: string) { return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); diff --git a/app/utils/thread/index.ts b/app/utils/thread/index.ts index 2f2c86687..a279fb236 100644 --- a/app/utils/thread/index.ts +++ b/app/utils/thread/index.ts @@ -6,21 +6,20 @@ import {getPreferenceValue} from '@helpers/api/preference'; import type PreferenceModel from '@typings/database/models/servers/preference'; -export function processIsCRTEnabled(preferences: PreferenceModel[]|PreferenceType[], config?: ClientConfig): boolean { +export function processIsCRTEnabled(preferences: PreferenceModel[]|PreferenceType[], configValue?: string, featureFlag?: string): boolean { let preferenceDefault = Preferences.COLLAPSED_REPLY_THREADS_OFF; - const configValue = config?.CollapsedThreads; if (configValue === Config.DEFAULT_ON) { preferenceDefault = Preferences.COLLAPSED_REPLY_THREADS_ON; } const preference = getPreferenceValue(preferences, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.COLLAPSED_REPLY_THREADS, preferenceDefault); const isAllowed = ( - config?.FeatureFlagCollapsedThreads === Config.TRUE && - config?.CollapsedThreads !== Config.DISABLED + featureFlag === Config.TRUE && + configValue !== Config.DISABLED ); return isAllowed && ( preference === Preferences.COLLAPSED_REPLY_THREADS_ON || - config?.CollapsedThreads === Config.ALWAYS_ON + configValue === Config.ALWAYS_ON ); } diff --git a/ios/Gekidou/Sources/Gekidou/Storage/Database+System.swift b/ios/Gekidou/Sources/Gekidou/Storage/Database+System.swift index cc1f331c9..b5796c751 100644 --- a/ios/Gekidou/Sources/Gekidou/Storage/Database+System.swift +++ b/ios/Gekidou/Sources/Gekidou/Storage/Database+System.swift @@ -10,19 +10,18 @@ import Foundation import SQLite extension Database { - public func getConfig(_ serverUrl: String) -> [String: Any]? { + public func getConfig(_ serverUrl: String, _ key: String) -> String? { do { let db = try getDatabaseForServer(serverUrl) let id = Expression("id") let value = Expression("value") - let query = systemTable.select(value).filter(id == "config") - var json: [String: Any]? = nil + let query = configTable.select(value).filter(id == key) if let result = try db.pluck(query) { let val = try result.get(value) - json = try? JSONSerialization.jsonObject(with: val.data(using: .utf8)!, options: []) as? [String: Any] + return val } - return json + return nil } catch { return nil } diff --git a/ios/Gekidou/Sources/Gekidou/Storage/Database.swift b/ios/Gekidou/Sources/Gekidou/Storage/Database.swift index 4955a759d..e649d022b 100644 --- a/ios/Gekidou/Sources/Gekidou/Storage/Database.swift +++ b/ios/Gekidou/Sources/Gekidou/Storage/Database.swift @@ -60,6 +60,7 @@ public class Database: NSObject { internal var userTable = Table("User") internal var threadTable = Table("Thread") internal var threadParticipantTable = Table("ThreadParticipant") + internal var configTable = Table("Config") @objc public static let `default` = Database() diff --git a/ios/MattermostShare/Services/ServerService.swift b/ios/MattermostShare/Services/ServerService.swift index 965286476..8c59b2cf0 100644 --- a/ios/MattermostShare/Services/ServerService.swift +++ b/ios/MattermostShare/Services/ServerService.swift @@ -31,23 +31,25 @@ class ServerService: ObservableObject { private func updateServerSettings(_ server: ServerModel?) -> ServerModel? { if var s = server { - if let config = Gekidou.Database.default.getConfig(s.url) { + let fileSize = Gekidou.Database.default.getConfig(s.url, "MaxFileSize") + let postSize = Gekidou.Database.default.getConfig(s.url, "MaxPostSize") + let mobileFileUpload = Gekidou.Database.default.getConfig(s.url, "EnableMobileFileUpload") + let hasChannels = Gekidou.Database.default.serverHasChannels(s.url) var maxFileSize: Int64? = nil - if let fileSize = config["MaxFileSize"] as? String { - maxFileSize = Int64(fileSize) + if let value = fileSize { + maxFileSize = Int64(value) } var maxPostSize: Int64? = nil - if let length = config["MaxPostSize"] as? String { - maxPostSize = Int64(length) + if let value = postSize { + maxPostSize = Int64(value) } - let uploadsEnabled = config["EnableMobileFileUpload"] as? String == "true" + let uploadsEnabled = mobileFileUpload == "true" s.updateSettings(hasChannels, maxPostSize, maxFileSize, uploadsEnabled) return s - } } return nil } diff --git a/test/test_helper.ts b/test/test_helper.ts index 2e1dff952..f31928fb4 100644 --- a/test/test_helper.ts +++ b/test/test_helper.ts @@ -98,7 +98,6 @@ class TestHelper { }); const systems = await prepareCommonSystemValues(operator, { - config: {} as ClientConfig, license: {} as ClientLicense, currentChannelId: '', currentTeamId: this.basicTeam!.id, diff --git a/types/database/database.ts b/types/database/database.ts index b2ea93aea..24deead0f 100644 --- a/types/database/database.ts +++ b/types/database/database.ts @@ -194,6 +194,11 @@ export type HandleSystemArgs = PrepareOnly & { systems?: IdValue[]; } +export type HandleConfigArgs = PrepareOnly & { + configs: IdValue[]; + configsToDelete: IdValue[]; +} + export type HandleMyChannelArgs = PrepareOnly & { channels?: Channel[]; myChannels?: ChannelMembership[]; diff --git a/types/database/models/servers/config.ts b/types/database/models/servers/config.ts new file mode 100644 index 000000000..64d70529c --- /dev/null +++ b/types/database/models/servers/config.ts @@ -0,0 +1,18 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import type {Model} from '@nozbe/watermelondb'; + +/** + * The Config model is another set of key-value pair combination but this one + * will hold the server configuration. + */ +declare class ConfigModel extends Model { + /** table (name) : Config */ + static table: string; + + /** value : The value for that config/information and whose key will be the id column */ + value: string; +} + +export default ConfigModel;