Move config to its own database table (#6744)

* Move config to its own database table

* Address feedback

* Fix test

* Revert minimum version related changes
This commit is contained in:
Daniel Espino García 2022-11-11 18:20:42 +01:00 committed by GitHub
parent 887565423c
commit 1aa4188f8e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
85 changed files with 562 additions and 403 deletions

View file

@ -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 = '';

View file

@ -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 [];
}

View file

@ -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<Promise<Model[]>> = [];
switchingTeams = true;
modelPromises.push(addTeamToTeamHistory(operator, teamId, true));

View file

@ -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 = {

View file

@ -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) {

View file

@ -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<Channel>|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);
}
}

View file

@ -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') {

View file

@ -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);

View file

@ -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) {

View file

@ -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<Channel>(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<Channel>(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);
}

View file

@ -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<ClientConfig>; license: Partial<ClientLicense> } = 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;
}

View file

@ -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) {

View file

@ -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<{

View file

@ -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);

View file

@ -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);
}

View file

@ -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 = {

View file

@ -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.

View file

@ -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)))));

View file

@ -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')),

View file

@ -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,

View file

@ -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,

View file

@ -17,8 +17,8 @@ type Props = {
cursorPosition: number;
rootId?: string;
files?: FileInfo[];
maxFileSize: number;
maxFileCount: number;
maxFileSize: number;
canUploadFiles: boolean;
updateCursorPosition: React.Dispatch<React.SetStateAction<number>>;
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.

View file

@ -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,
};
});

View file

@ -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);

View file

@ -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,

View file

@ -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]) => {

View file

@ -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};

View file

@ -14,7 +14,7 @@ type Props = {
enableEmoji?: boolean;
enableHardBreak?: boolean;
enableSoftBreak?: boolean;
textStyle: StyleProp<TextStyle>;
textStyle?: StyleProp<TextStyle>;
value: string;
};

View file

@ -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)),
);

View file

@ -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';

View file

@ -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,

View file

@ -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',

View file

@ -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,

View file

@ -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<void>}
*/
private initServerDatabase = async (serverUrl: string): Promise<void> => {
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()]);
}
}
};
/**

View file

@ -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: [

View file

@ -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;
}

View file

@ -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';

View file

@ -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);

View file

@ -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<SystemModel[]>;
};
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

View file

@ -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<SystemModel>;
};
/**
* 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<ConfigModel>}
*/
export const transformConfigRecord = ({action, database, value}: TransformerArgs): Promise<ConfigModel> => {
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<ConfigModel>;
};

View file

@ -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,

View file

@ -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);

View file

@ -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';

View file

@ -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,

View file

@ -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;

View file

@ -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';

View file

@ -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}?',

View file

@ -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}`}});

View file

@ -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<string> => {
try {
@ -94,7 +95,6 @@ export const observePushVerificationStatus = (database: Database): Observable<st
export const getCommonSystemValues = async (serverDatabase: Database) => {
const systemRecords = (await serverDatabase.collections.get<SystemModel>(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<SystemModel>(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<ConfigModel>(CONFIG).query().fetch();
return fromModelToClientConfig(configList);
};
export const queryConfigValue = (database: Database, key: keyof ClientConfig) => {
return database.get<ConfigModel>(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<ClientConfig | undefined> => {
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<ConfigModel>(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<SystemModel[]> {
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),
),
),
);
};

View file

@ -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<boolean> => {
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(),
);

View file

@ -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) => {

View file

@ -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);

View file

@ -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) : '')),

View file

@ -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 {

View file

@ -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));

View file

@ -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}
/>
));

View file

@ -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}
/>
))}
</View>

View file

@ -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<Props, State> {
</View>
{recentCustomStatuses.length > 0 && (
<RecentCustomStatuses
isExpirySupported={customStatusExpirySupported}
onHandleClear={this.handleRecentCustomStatusClear}
onHandleSuggestionClick={this.handleRecentCustomStatusSuggestionClick}
recentCustomStatuses={recentCustomStatuses}
@ -386,7 +383,6 @@ class CustomStatusModal extends NavigationComponent<Props, State> {
}
<CustomStatusSuggestions
intl={intl}
isExpirySupported={customStatusExpirySupported}
onHandleCustomStatusSuggestionClick={this.handleCustomStatusSuggestionClick}
recentCustomStatuses={recentCustomStatuses}
theme={theme}
@ -404,13 +400,10 @@ class CustomStatusModal extends NavigationComponent<Props, State> {
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),
};
});

View file

@ -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'),
};
});

View file

@ -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);

View file

@ -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)),

View file

@ -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
/>
<AccountOptions
enableCustomUserStatuses={enableCustomUserStatuses}
isCustomStatusExpirySupported={customStatusExpirySupported}
isTablet={isTablet}
user={currentUser}
theme={theme}

View file

@ -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 CustomLabel from './custom_label';
import type {WithDatabaseArgs} from '@typings/database/database';
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
return {
isCustomStatusExpirySupported: observeIsCustomStatusExpirySupported(database),
};
});
export default withDatabase(enhanced(CustomLabel));

View file

@ -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
/>
<CustomLabel
customStatus={customStatus!}
isCustomStatusExpirySupported={isCustomStatusExpirySupported}
isStatusSet={Boolean(isStatusSet)}
onClearCustomStatus={clearCustomStatus}
showRetryMessage={showRetryMessage}

View file

@ -17,7 +17,6 @@ import type UserModel from '@typings/database/models/servers/user';
type AccountScreenProps = {
user: UserModel;
enableCustomUserStatuses: boolean;
isCustomStatusExpirySupported: boolean;
isTablet: boolean;
theme: Theme;
};
@ -48,7 +47,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
};
});
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 &&
<CustomStatus
isCustomStatusExpirySupported={isCustomStatusExpirySupported}
isTablet={isTablet}
currentUser={user}
/>}

View file

@ -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,
};
});

View file

@ -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';

View file

@ -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';

View file

@ -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,

View file

@ -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';

View file

@ -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';

View file

@ -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'),
};
});

View file

@ -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);

View file

@ -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) => {

View file

@ -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();

View file

@ -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
);
}

View file

@ -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<String>("id")
let value = Expression<String>("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
}

View file

@ -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()

View file

@ -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
}

View file

@ -98,7 +98,6 @@ class TestHelper {
});
const systems = await prepareCommonSystemValues(operator, {
config: {} as ClientConfig,
license: {} as ClientLicense,
currentChannelId: '',
currentTeamId: this.basicTeam!.id,

View file

@ -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[];

View file

@ -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;