[Gekidou] db manager database getters (#6377)

This commit is contained in:
Elias Nahum
2022-06-13 18:32:47 -04:00
committed by GitHub
parent 6f6dded978
commit bfffa0e10a
19 changed files with 846 additions and 868 deletions

View File

@@ -5,40 +5,37 @@ import {GLOBAL_IDENTIFIERS} from '@constants/database';
import DatabaseManager from '@database/manager';
export const storeDeviceToken = async (token: string, prepareRecordsOnly = false) => {
const operator = DatabaseManager.appDatabase?.operator;
if (!operator) {
return {error: 'No App database found'};
try {
const {operator} = DatabaseManager.getAppDatabaseAndOperator();
return operator.handleGlobal({
globals: [{id: GLOBAL_IDENTIFIERS.DEVICE_TOKEN, value: token}],
prepareRecordsOnly,
});
} catch (error) {
return {error};
}
return operator.handleGlobal({
globals: [{id: GLOBAL_IDENTIFIERS.DEVICE_TOKEN, value: token}],
prepareRecordsOnly,
});
};
export const storeMultiServerTutorial = async (prepareRecordsOnly = false) => {
const operator = DatabaseManager.appDatabase?.operator;
if (!operator) {
return {error: 'No App database found'};
try {
const {operator} = DatabaseManager.getAppDatabaseAndOperator();
return operator.handleGlobal({
globals: [{id: GLOBAL_IDENTIFIERS.MULTI_SERVER_TUTORIAL, value: 'true'}],
prepareRecordsOnly,
});
} catch (error) {
return {error};
}
return operator.handleGlobal({
globals: [{id: GLOBAL_IDENTIFIERS.MULTI_SERVER_TUTORIAL, value: 'true'}],
prepareRecordsOnly,
});
};
export const storeProfileLongPressTutorial = async (prepareRecordsOnly = false) => {
const operator = DatabaseManager.appDatabase?.operator;
if (!operator) {
return {error: 'No App database found'};
try {
const {operator} = DatabaseManager.getAppDatabaseAndOperator();
return operator.handleGlobal({
globals: [{id: GLOBAL_IDENTIFIERS.PROFILE_LONG_PRESS_TUTORIAL, value: 'true'}],
prepareRecordsOnly,
});
} catch (error) {
return {error};
}
return operator.handleGlobal({
globals: [{id: GLOBAL_IDENTIFIERS.PROFILE_LONG_PRESS_TUTORIAL, value: 'true'}],
prepareRecordsOnly,
});
};

View File

@@ -14,12 +14,8 @@ import {pluckUnique} from '@utils/helpers';
import type ChannelModel from '@typings/database/models/servers/channel';
export const deleteCategory = async (serverUrl: string, categoryId: string) => {
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
if (!database) {
return {error: `${serverUrl} database not found`};
}
try {
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const category = await getCategoryById(database, categoryId);
if (category) {
@@ -37,67 +33,59 @@ export const deleteCategory = async (serverUrl: string, categoryId: string) => {
};
export async function storeCategories(serverUrl: string, categories: CategoryWithChannels[], prune = false, prepareRecordsOnly = false) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
try {
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
if (!operator) {
return {error: `${serverUrl} database not found`};
}
const modelPromises: Array<Promise<Model[]>> = [];
const preparedCategories = prepareCategories(operator, categories);
if (preparedCategories) {
modelPromises.push(preparedCategories);
}
const preparedCategoryChannels = prepareCategoryChannels(operator, categories);
if (preparedCategoryChannels) {
modelPromises.push(preparedCategoryChannels);
}
const models = await Promise.all(modelPromises);
const flattenedModels = models.flat();
if (prune && categories.length) {
const {database} = operator;
const remoteCategoryIds = new Set(categories.map((cat) => cat.id));
// If the passed categories have more than one team, we want to update across teams
const teamIds = pluckUnique('team_id')(categories) as string[];
const localCategories = await queryCategoriesByTeamIds(database, teamIds).fetch();
localCategories.
filter((category) => category.type === 'custom').
forEach((localCategory) => {
if (!remoteCategoryIds.has(localCategory.id)) {
localCategory.prepareDestroyPermanently();
flattenedModels.push(localCategory);
}
});
}
if (prepareRecordsOnly) {
return {models: flattenedModels};
}
if (flattenedModels?.length > 0) {
try {
await operator.batchRecords(flattenedModels);
} catch (error) {
// eslint-disable-next-line no-console
console.log('FAILED TO BATCH CATEGORIES', error);
return {error};
const modelPromises: Array<Promise<Model[]>> = [];
const preparedCategories = prepareCategories(operator, categories);
if (preparedCategories) {
modelPromises.push(preparedCategories);
}
}
return {models: flattenedModels};
const preparedCategoryChannels = prepareCategoryChannels(operator, categories);
if (preparedCategoryChannels) {
modelPromises.push(preparedCategoryChannels);
}
const models = await Promise.all(modelPromises);
const flattenedModels = models.flat();
if (prune && categories.length) {
const remoteCategoryIds = new Set(categories.map((cat) => cat.id));
// If the passed categories have more than one team, we want to update across teams
const teamIds = pluckUnique('team_id')(categories) as string[];
const localCategories = await queryCategoriesByTeamIds(database, teamIds).fetch();
localCategories.
filter((category) => category.type === 'custom').
forEach((localCategory) => {
if (!remoteCategoryIds.has(localCategory.id)) {
localCategory.prepareDestroyPermanently();
flattenedModels.push(localCategory);
}
});
}
if (prepareRecordsOnly) {
return {models: flattenedModels};
}
if (flattenedModels?.length > 0) {
await operator.batchRecords(flattenedModels);
}
return {models: flattenedModels};
} catch (error) {
// eslint-disable-next-line no-console
console.log('FAILED TO STORE CATEGORIES', error);
return {error};
}
}
export const toggleCollapseCategory = async (serverUrl: string, categoryId: string) => {
const database = DatabaseManager.serverDatabases[serverUrl].database;
if (!database) {
return {error: `${serverUrl} database not found`};
}
try {
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const category = await getCategoryById(database, categoryId);
if (category) {
@@ -117,47 +105,46 @@ export const toggleCollapseCategory = async (serverUrl: string, categoryId: stri
};
export async function addChannelToDefaultCategory(serverUrl: string, channel: Channel | ChannelModel, prepareRecordsOnly = false) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
try {
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const teamId = 'teamId' in channel ? channel.teamId : channel.team_id;
const userId = await getCurrentUserId(database);
const {database} = operator;
const teamId = 'teamId' in channel ? channel.teamId : channel.team_id;
const userId = await getCurrentUserId(database);
if (!userId) {
return {error: 'no current user id'};
}
const models: Model[] = [];
const categoriesWithChannels: CategoryWithChannels[] = [];
if (isDMorGM(channel)) {
const allTeamIds = await queryMyTeams(database).fetchIds();
const categories = await queryCategoriesByTeamIds(database, allTeamIds).fetch();
const channelCategories = categories.filter((c) => c.type === DMS_CATEGORY);
for await (const cc of channelCategories) {
const cwc = await cc.toCategoryWithChannels();
cwc.channel_ids.unshift(channel.id);
categoriesWithChannels.push(cwc);
}
} else {
const categories = await queryCategoriesByTeamIds(database, [teamId]).fetch();
const channelCategory = categories.find((c) => c.type === CHANNELS_CATEGORY);
if (channelCategory) {
const cwc = await channelCategory.toCategoryWithChannels();
cwc.channel_ids.unshift(channel.id);
categoriesWithChannels.push(cwc);
if (!userId) {
return {error: 'no current user id'};
}
const ccModels = await prepareCategoryChannels(operator, categoriesWithChannels);
models.push(...ccModels);
}
const models: Model[] = [];
const categoriesWithChannels: CategoryWithChannels[] = [];
if (models.length && !prepareRecordsOnly) {
await operator.batchRecords(models);
}
if (isDMorGM(channel)) {
const allTeamIds = await queryMyTeams(database).fetchIds();
const categories = await queryCategoriesByTeamIds(database, allTeamIds).fetch();
const channelCategories = categories.filter((c) => c.type === DMS_CATEGORY);
for await (const cc of channelCategories) {
const cwc = await cc.toCategoryWithChannels();
cwc.channel_ids.unshift(channel.id);
categoriesWithChannels.push(cwc);
}
} else {
const categories = await queryCategoriesByTeamIds(database, [teamId]).fetch();
const channelCategory = categories.find((c) => c.type === CHANNELS_CATEGORY);
if (channelCategory) {
const cwc = await channelCategory.toCategoryWithChannels();
cwc.channel_ids.unshift(channel.id);
categoriesWithChannels.push(cwc);
}
return {models};
const ccModels = await prepareCategoryChannels(operator, categoriesWithChannels);
models.push(...ccModels);
}
if (models.length && !prepareRecordsOnly) {
await operator.batchRecords(models);
}
return {models};
} catch (error) {
return {error};
}
}

View File

@@ -29,14 +29,9 @@ import type ChannelModel from '@typings/database/models/servers/channel';
import type UserModel from '@typings/database/models/servers/user';
export async function switchToChannel(serverUrl: string, channelId: string, teamId?: string, skipLastUnread = false, prepareRecordsOnly = false) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
const {database} = operator;
let models: Model[] = [];
try {
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
let models: Model[] = [];
const dt = Date.now();
const isTabletDevice = await isTablet();
const system = await getCommonSystemValues(database);
@@ -113,103 +108,89 @@ export async function switchToChannel(serverUrl: string, channelId: string, team
console.log('channel switch to', channel?.displayName, channelId, (Date.now() - dt), 'ms'); //eslint-disable-line
}
}
return {models};
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed to switch to channelId', channelId, 'teamId', teamId, 'error', error);
return {error};
}
return {models};
}
export async function removeCurrentUserFromChannel(serverUrl: string, channelId: string, prepareRecordsOnly = false) {
const serverDatabase = DatabaseManager.serverDatabases[serverUrl];
if (!serverDatabase) {
return {error: `${serverUrl} database not found`};
}
try {
const {operator, database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const {operator, database} = serverDatabase;
const models: Model[] = [];
const myChannel = await getMyChannel(database, channelId);
if (myChannel) {
const channel = await myChannel.channel.fetch();
if (!channel) {
throw new Error('myChannel present but no channel on the database');
}
models.push(...await prepareDeleteChannel(channel));
let teamId = channel.teamId;
if (teamId) {
teamId = await getCurrentTeamId(database);
}
const models: Model[] = [];
const myChannel = await getMyChannel(database, channelId);
if (myChannel) {
const channel = await myChannel.channel.fetch();
if (!channel) {
return {error: 'myChannel present but no channel on the database'};
}
models.push(...await prepareDeleteChannel(channel));
let teamId = channel.teamId;
if (teamId) {
teamId = await getCurrentTeamId(database);
}
// We update the history ASAP to avoid clashes with channel switch.
await removeChannelFromTeamHistory(operator, teamId, channel.id, false);
// We update the history ASAP to avoid clashes with channel switch.
await removeChannelFromTeamHistory(operator, teamId, channel.id, false);
if (models.length && !prepareRecordsOnly) {
try {
if (models.length && !prepareRecordsOnly) {
await operator.batchRecords(models);
} catch {
// eslint-disable-next-line no-console
console.log('FAILED TO BATCH CHANGES FOR REMOVE USER FROM CHANNEL');
}
}
return {models};
} catch (error) {
// eslint-disable-next-line no-console
console.log('failed to removeCurrentUserFromChannel', error);
return {error};
}
return {models};
}
export async function setChannelDeleteAt(serverUrl: string, channelId: string, deleteAt: number) {
const serverDatabase = DatabaseManager.serverDatabases[serverUrl];
if (!serverDatabase) {
return;
}
const {operator, database} = serverDatabase;
const channel = await getChannelById(database, channelId);
if (!channel) {
return;
}
const model = channel.prepareUpdate((c) => {
c.deleteAt = deleteAt;
});
try {
const {operator, database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const channel = await getChannelById(database, channelId);
if (!channel) {
return;
}
const model = channel.prepareUpdate((c) => {
c.deleteAt = deleteAt;
});
await operator.batchRecords([model]);
} catch {
} catch (error) {
// eslint-disable-next-line no-console
console.log('FAILED TO BATCH CHANGES FOR CHANNEL DELETE AT');
console.log('FAILED TO BATCH CHANGES FOR CHANNEL DELETE AT', error);
}
}
export async function selectAllMyChannelIds(serverUrl: string) {
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
if (!database) {
try {
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
return queryAllMyChannel(database).fetchIds();
} catch {
return [];
}
return queryAllMyChannel(database).fetchIds();
}
export async function markChannelAsViewed(serverUrl: string, channelId: string, prepareRecordsOnly = false) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
const member = await getMyChannel(operator.database, channelId);
if (!member) {
return {error: 'not a member'};
}
member.prepareUpdate((m) => {
m.isUnread = false;
m.mentionsCount = 0;
m.manuallyUnread = false;
m.viewedAt = member.lastViewedAt;
m.lastViewedAt = Date.now();
});
try {
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const member = await getMyChannel(database, channelId);
if (!member) {
return {error: 'not a member'};
}
member.prepareUpdate((m) => {
m.isUnread = false;
m.mentionsCount = 0;
m.manuallyUnread = false;
m.viewedAt = member.lastViewedAt;
m.lastViewedAt = Date.now();
});
PushNotifications.cancelChannelNotifications(channelId);
if (!prepareRecordsOnly) {
await operator.batchRecords([member]);
@@ -217,53 +198,47 @@ export async function markChannelAsViewed(serverUrl: string, channelId: string,
return {member};
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed markChannelAsViewed', error);
return {error};
}
}
export async function markChannelAsUnread(serverUrl: string, channelId: string, messageCount: number, mentionsCount: number, lastViewed: number, prepareRecordsOnly = false) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
const member = await getMyChannel(operator.database, channelId);
if (!member) {
return {error: 'not a member'};
}
member.prepareUpdate((m) => {
m.viewedAt = lastViewed - 1;
m.lastViewedAt = lastViewed - 1;
m.messageCount = messageCount;
m.mentionsCount = mentionsCount;
m.manuallyUnread = true;
m.isUnread = true;
});
try {
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const member = await getMyChannel(database, channelId);
if (!member) {
return {error: 'not a member'};
}
member.prepareUpdate((m) => {
m.viewedAt = lastViewed - 1;
m.lastViewedAt = lastViewed - 1;
m.messageCount = messageCount;
m.mentionsCount = mentionsCount;
m.manuallyUnread = true;
m.isUnread = true;
});
if (!prepareRecordsOnly) {
await operator.batchRecords([member]);
}
return {member};
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed markChannelAsUnread', error);
return {error};
}
}
export async function resetMessageCount(serverUrl: string, channelId: string) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
const member = await getMyChannel(operator.database, channelId);
if (!member) {
return {error: 'not a member'};
}
try {
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const member = await getMyChannel(database, channelId);
if (!member) {
return {error: 'not a member'};
}
member.prepareUpdate((m) => {
m.messageCount = 0;
});
@@ -271,185 +246,185 @@ export async function resetMessageCount(serverUrl: string, channelId: string) {
return member;
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed resetMessageCount', error);
return {error};
}
}
export async function storeMyChannelsForTeam(serverUrl: string, teamId: string, channels: Channel[], memberships: ChannelMembership[], prepareRecordsOnly = false, isCRTEnabled?: boolean) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
const modelPromises: Array<Promise<Model[]>> = [
...await prepareMyChannelsForTeam(operator, teamId, channels, memberships, isCRTEnabled),
];
try {
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const modelPromises: Array<Promise<Model[]>> = [
...await prepareMyChannelsForTeam(operator, teamId, channels, memberships, isCRTEnabled),
];
const models = await Promise.all(modelPromises);
if (!models.length) {
return {models: []};
}
const flattenedModels = models.flat();
if (prepareRecordsOnly) {
return {models: flattenedModels};
}
if (flattenedModels.length) {
try {
await operator.batchRecords(flattenedModels);
} catch (error) {
// eslint-disable-next-line no-console
console.log('FAILED TO BATCH CHANNELS');
return {error};
const models = await Promise.all(modelPromises);
if (!models.length) {
return {models: []};
}
}
return {models: flattenedModels};
const flattenedModels = models.flat();
if (prepareRecordsOnly) {
return {models: flattenedModels};
}
if (flattenedModels.length) {
await operator.batchRecords(flattenedModels);
}
return {models: flattenedModels};
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed storeMyChannelsForTeam', error);
return {error};
}
}
export async function updateMyChannelFromWebsocket(serverUrl: string, channelMember: ChannelMembership, prepareRecordsOnly = false) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
const member = await getMyChannel(operator.database, channelMember.channel_id);
if (member) {
member.prepareUpdate((m) => {
m.roles = channelMember.roles;
});
if (!prepareRecordsOnly) {
operator.batchRecords([member]);
try {
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const member = await getMyChannel(database, channelMember.channel_id);
if (member) {
member.prepareUpdate((m) => {
m.roles = channelMember.roles;
});
if (!prepareRecordsOnly) {
operator.batchRecords([member]);
}
}
return {model: member};
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed updateMyChannelFromWebsocket', error);
return {error};
}
return {model: member};
}
export async function updateChannelInfoFromChannel(serverUrl: string, channel: Channel, prepareRecordsOnly = false) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
try {
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const newInfo = await operator.handleChannelInfo({channelInfos: [{
header: channel.header,
purpose: channel.purpose,
id: channel.id,
}],
prepareRecordsOnly: true});
if (!prepareRecordsOnly) {
operator.batchRecords(newInfo);
}
return {model: newInfo};
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed updateChannelInfoFromChannel', error);
return {error};
}
const newInfo = await operator.handleChannelInfo({channelInfos: [{
header: channel.header,
purpose: channel.purpose,
id: channel.id,
}],
prepareRecordsOnly: true});
if (!prepareRecordsOnly) {
operator.batchRecords(newInfo);
}
return {model: newInfo};
}
export async function updateLastPostAt(serverUrl: string, channelId: string, lastPostAt: number, prepareRecordsOnly = false) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
try {
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const member = await getMyChannel(database, channelId);
if (!member) {
return {error: 'not a member'};
}
const member = await getMyChannel(operator.database, channelId);
if (!member) {
return {error: 'not a member'};
}
if (lastPostAt > member.lastPostAt) {
member.prepareUpdate((m) => {
m.lastPostAt = lastPostAt;
});
if (lastPostAt > member.lastPostAt) {
member.prepareUpdate((m) => {
m.lastPostAt = lastPostAt;
});
try {
if (!prepareRecordsOnly) {
await operator.batchRecords([member]);
}
} catch (error) {
return {error};
return {member};
}
return {member};
return {member: undefined};
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed updateLastPostAt', error);
return {error};
}
return {member: undefined};
}
type User = UserProfile | UserModel;
export async function updateChannelsDisplayName(serverUrl: string, channels: ChannelModel[], users: User[], prepareRecordsOnly = false) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {};
}
try {
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const currentUser = await getCurrentUser(database);
if (!currentUser) {
return {};
}
const {database} = operator;
const currentUser = await getCurrentUser(database);
if (!currentUser) {
return {};
}
const {config, license} = await getCommonSystemValues(database);
const preferences = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT).fetch();
const displaySettings = getTeammateNameDisplaySetting(preferences, config, license);
const models: Model[] = [];
for await (const channel of channels) {
let newDisplayName = '';
if (channel.type === General.DM_CHANNEL) {
const otherUserId = getUserIdFromChannelName(currentUser.id, channel.name);
const user = users.find((u) => u.id === otherUserId);
newDisplayName = displayUsername(user, currentUser.locale, displaySettings, false);
} else {
const dbProfiles = await queryUsersOnChannel(database, channel.id).fetch();
const profileIds = new Set(dbProfiles.map((p) => p.id));
const gmUsers = users.filter((u) => profileIds.has(u.id));
if (gmUsers.length) {
const uIds = new Set(gmUsers.map((u) => u.id));
const newProfiles: Array<UserModel|UserProfile> = dbProfiles.filter((u) => !uIds.has(u.id));
newProfiles.push(...gmUsers);
newDisplayName = displayGroupMessageName(newProfiles, currentUser.locale, displaySettings, currentUser.id);
}
}
const {config, license} = await getCommonSystemValues(database);
const preferences = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT).fetch();
const displaySettings = getTeammateNameDisplaySetting(preferences, config, license);
const models: Model[] = [];
for await (const channel of channels) {
let newDisplayName = '';
if (channel.type === General.DM_CHANNEL) {
const otherUserId = getUserIdFromChannelName(currentUser.id, channel.name);
const user = users.find((u) => u.id === otherUserId);
newDisplayName = displayUsername(user, currentUser.locale, displaySettings, false);
} else {
const dbProfiles = await queryUsersOnChannel(database, channel.id).fetch();
const profileIds = new Set(dbProfiles.map((p) => p.id));
const gmUsers = users.filter((u) => profileIds.has(u.id));
if (gmUsers.length) {
const uIds = new Set(gmUsers.map((u) => u.id));
const newProfiles: Array<UserModel|UserProfile> = dbProfiles.filter((u) => !uIds.has(u.id));
newProfiles.push(...gmUsers);
newDisplayName = displayGroupMessageName(newProfiles, currentUser.locale, displaySettings, currentUser.id);
if (newDisplayName && channel.displayName !== newDisplayName) {
channel.prepareUpdate((c) => {
c.displayName = extractChannelDisplayName({
type: c.type,
display_name: newDisplayName,
fake: true,
}, c);
});
models.push(channel);
}
}
if (newDisplayName && channel.displayName !== newDisplayName) {
channel.prepareUpdate((c) => {
c.displayName = extractChannelDisplayName({
type: c.type,
display_name: newDisplayName,
fake: true,
}, c);
});
models.push(channel);
if (models.length && !prepareRecordsOnly) {
await operator.batchRecords(models);
}
}
if (models.length && !prepareRecordsOnly) {
await operator.batchRecords(models);
return {models};
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed updateChannelsDisplayName', error);
return {error};
}
return {models};
}
export async function showUnreadChannelsOnly(serverUrl: string, onlyUnreads: boolean) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
try {
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
return operator.handleSystem({
systems: [{
id: SYSTEM_IDENTIFIERS.ONLY_UNREADS,
value: JSON.stringify(onlyUnreads),
}],
prepareRecordsOnly: false,
});
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed showUnreadChannelsOnly', error);
return {error};
}
return operator.handleSystem({
systems: [{
id: SYSTEM_IDENTIFIERS.ONLY_UNREADS,
value: JSON.stringify(onlyUnreads),
}],
prepareRecordsOnly: false,
});
}
export const updateDmGmDisplayName = async (serverUrl: string) => {
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
if (!database) {
return {error: `${serverUrl} database not found`};
}
try {
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const currentUserId = await getCurrentUserId(database);
if (!currentUserId) {
return {error: 'The current user id could not be retrieved from the database'};
@@ -471,6 +446,8 @@ export const updateDmGmDisplayName = async (serverUrl: string) => {
return {channels};
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed updateDmGmDisplayName', error);
return {error};
}
};

View File

@@ -5,163 +5,157 @@ import DatabaseManager from '@database/manager';
import {getDraft} from '@queries/servers/drafts';
export async function updateDraftFile(serverUrl: string, channelId: string, rootId: string, file: FileInfo, prepareRecordsOnly = false) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
const draft = await getDraft(operator.database, channelId, rootId);
if (!draft) {
return {error: 'no draft'};
}
const i = draft.files.findIndex((v) => v.clientId === file.clientId);
if (i === -1) {
return {error: 'file not found'};
}
// We create a new list to make sure we re-render the draft input.
const newFiles = [...draft.files];
newFiles[i] = file;
draft.prepareUpdate((d) => {
d.files = newFiles;
});
try {
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const draft = await getDraft(database, channelId, rootId);
if (!draft) {
return {error: 'no draft'};
}
const i = draft.files.findIndex((v) => v.clientId === file.clientId);
if (i === -1) {
return {error: 'file not found'};
}
// We create a new list to make sure we re-render the draft input.
const newFiles = [...draft.files];
newFiles[i] = file;
draft.prepareUpdate((d) => {
d.files = newFiles;
});
if (!prepareRecordsOnly) {
await operator.batchRecords([draft]);
}
return {draft};
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed updateDraftFile', error);
return {error};
}
}
export async function removeDraftFile(serverUrl: string, channelId: string, rootId: string, clientId: string, prepareRecordsOnly = false) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
const draft = await getDraft(operator.database, channelId, rootId);
if (!draft) {
return {error: 'no draft'};
}
const i = draft.files.findIndex((v) => v.clientId === clientId);
if (i === -1) {
return {error: 'file not found'};
}
if (draft.files.length === 1 && !draft.message) {
draft.prepareDestroyPermanently();
} else {
draft.prepareUpdate((d) => {
d.files = draft.files.filter((v, index) => index !== i);
});
}
try {
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const draft = await getDraft(database, channelId, rootId);
if (!draft) {
return {error: 'no draft'};
}
const i = draft.files.findIndex((v) => v.clientId === clientId);
if (i === -1) {
return {error: 'file not found'};
}
if (draft.files.length === 1 && !draft.message) {
draft.prepareDestroyPermanently();
} else {
draft.prepareUpdate((d) => {
d.files = draft.files.filter((v, index) => index !== i);
});
}
if (!prepareRecordsOnly) {
await operator.batchRecords([draft]);
}
return {draft};
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed removeDraftFile', error);
return {error};
}
}
export async function updateDraftMessage(serverUrl: string, channelId: string, rootId: string, message: string, prepareRecordsOnly = false) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
try {
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const draft = await getDraft(database, channelId, rootId);
if (!draft) {
if (!message) {
return {};
}
const draft = await getDraft(operator.database, channelId, rootId);
if (!draft) {
if (!message) {
return {};
const newDraft: Draft = {
channel_id: channelId,
root_id: rootId,
message,
};
return operator.handleDraft({drafts: [newDraft], prepareRecordsOnly});
}
const newDraft: Draft = {
channel_id: channelId,
root_id: rootId,
message,
};
if (draft.message === message) {
return {draft};
}
return operator.handleDraft({drafts: [newDraft], prepareRecordsOnly});
}
if (draft.files.length === 0 && !message) {
draft.prepareDestroyPermanently();
} else {
draft.prepareUpdate((d) => {
d.message = message;
});
}
if (draft.message === message) {
return {draft};
}
if (draft.files.length === 0 && !message) {
draft.prepareDestroyPermanently();
} else {
draft.prepareUpdate((d) => {
d.message = message;
});
}
try {
if (!prepareRecordsOnly) {
await operator.batchRecords([draft]);
}
return {draft};
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed updateDraftMessage', error);
return {error};
}
}
export async function addFilesToDraft(serverUrl: string, channelId: string, rootId: string, files: FileInfo[], prepareRecordsOnly = false) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
const draft = await getDraft(operator.database, channelId, rootId);
if (!draft) {
const newDraft: Draft = {
channel_id: channelId,
root_id: rootId,
files,
message: '',
};
return operator.handleDraft({drafts: [newDraft], prepareRecordsOnly});
}
draft.prepareUpdate((d) => {
d.files = [...draft.files, ...files];
});
try {
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const draft = await getDraft(database, channelId, rootId);
if (!draft) {
const newDraft: Draft = {
channel_id: channelId,
root_id: rootId,
files,
message: '',
};
return operator.handleDraft({drafts: [newDraft], prepareRecordsOnly});
}
draft.prepareUpdate((d) => {
d.files = [...draft.files, ...files];
});
if (!prepareRecordsOnly) {
await operator.batchRecords([draft]);
}
return {draft};
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed addFilesToDraft', error);
return {error};
}
}
export const removeDraft = async (serverUrl: string, channelId: string, rootId = '') => {
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
if (!database) {
return {error: `${serverUrl} database not found`};
}
try {
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const draft = await getDraft(database, channelId, rootId);
if (draft) {
await database.write(async () => {
await draft.destroyPermanently();
});
}
const draft = await getDraft(database, channelId, rootId);
if (draft) {
await database.write(async () => {
await draft.destroyPermanently();
});
return {draft};
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed removeDraft', error);
return {error};
}
return {draft};
};

View File

@@ -5,21 +5,19 @@ import DatabaseManager from '@database/manager';
import {getFileById} from '@queries/servers/file';
export const updateLocalFile = async (serverUrl: string, file: FileInfo) => {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
try {
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
return operator.handleFiles({files: [file], prepareRecordsOnly: false});
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed updateLocalFile', error);
return {error};
}
return operator.handleFiles({files: [file], prepareRecordsOnly: false});
};
export const updateLocalFilePath = async (serverUrl: string, fileId: string, localPath: string) => {
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
if (!database) {
return {error: `${serverUrl} database not found`};
}
try {
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const file = await getFileById(database, fileId);
if (file) {
await database.write(async () => {
@@ -31,6 +29,8 @@ export const updateLocalFilePath = async (serverUrl: string, fileId: string, loc
return {error: undefined};
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed updateLocalFilePath', error);
return {error};
}
};

View File

@@ -5,14 +5,9 @@ import DatabaseManager from '@database/manager';
import {getPostById, queryPostsInChannel, queryPostsInThread} from '@queries/servers/post';
export const updatePostSinceCache = async (serverUrl: string, notification: NotificationWithData) => {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
try {
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
if (notification.payload?.channel_id) {
const {database} = operator;
const chunks = await queryPostsInChannel(database, notification.payload.channel_id).fetch();
if (chunks.length) {
const recent = chunks[0];
@@ -28,6 +23,8 @@ export const updatePostSinceCache = async (serverUrl: string, notification: Noti
}
return {};
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed updatePostSinceCache', error);
return {error};
}
};

View File

@@ -12,17 +12,65 @@ import type PostModel from '@typings/database/models/servers/post';
import type UserModel from '@typings/database/models/servers/user';
export const sendAddToChannelEphemeralPost = async (serverUrl: string, user: UserModel, addedUsernames: string[], messages: string[], channeId: string, postRootId = '') => {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
try {
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const timestamp = Date.now();
const posts = addedUsernames.map((addedUsername, index) => {
const message = messages[index];
return {
id: generateId(),
user_id: user.id,
channel_id: channeId,
message,
type: Post.POST_TYPES.EPHEMERAL_ADD_TO_CHANNEL as PostType,
create_at: timestamp,
edit_at: 0,
update_at: timestamp,
delete_at: 0,
is_pinned: false,
original_id: '',
hashtags: '',
pending_post_id: '',
reply_count: 0,
metadata: {},
root_id: postRootId,
props: {
username: user.username,
addedUsername,
},
} as Post;
});
const timestamp = Date.now();
const posts = addedUsernames.map((addedUsername, index) => {
const message = messages[index];
return {
await operator.handlePosts({
actionType: ActionType.POSTS.RECEIVED_NEW,
order: posts.map((p) => p.id),
posts,
});
return {posts};
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed sendAddToChannelEphemeralPost', error);
return {error};
}
};
export const sendEphemeralPost = async (serverUrl: string, message: string, channeId: string, rootId = '', userId?: string) => {
try {
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
if (!channeId) {
throw new Error('channel Id not defined');
}
let authorId = userId;
if (!authorId) {
authorId = await getCurrentUserId(database);
}
const timestamp = Date.now();
const post = {
id: generateId(),
user_id: user.id,
user_id: authorId,
channel_id: channeId,
message,
type: Post.POST_TYPES.EPHEMERAL_ADD_TO_CHANNEL as PostType,
@@ -36,122 +84,82 @@ export const sendAddToChannelEphemeralPost = async (serverUrl: string, user: Use
pending_post_id: '',
reply_count: 0,
metadata: {},
root_id: postRootId,
props: {
username: user.username,
addedUsername,
},
participants: null,
root_id: rootId,
props: {},
} as Post;
});
await operator.handlePosts({
actionType: ActionType.POSTS.RECEIVED_NEW,
order: posts.map((p) => p.id),
posts,
});
await operator.handlePosts({
actionType: ActionType.POSTS.RECEIVED_NEW,
order: [post.id],
posts: [post],
});
return {posts};
};
export const sendEphemeralPost = async (serverUrl: string, message: string, channeId: string, rootId = '', userId?: string) => {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
return {post};
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed sendEphemeralPost', error);
return {error};
}
if (!channeId) {
return {error: 'channel Id not defined'};
}
let authorId = userId;
if (!authorId) {
authorId = await getCurrentUserId(operator.database);
}
const timestamp = Date.now();
const post = {
id: generateId(),
user_id: authorId,
channel_id: channeId,
message,
type: Post.POST_TYPES.EPHEMERAL_ADD_TO_CHANNEL as PostType,
create_at: timestamp,
edit_at: 0,
update_at: timestamp,
delete_at: 0,
is_pinned: false,
original_id: '',
hashtags: '',
pending_post_id: '',
reply_count: 0,
metadata: {},
participants: null,
root_id: rootId,
props: {},
} as Post;
await operator.handlePosts({
actionType: ActionType.POSTS.RECEIVED_NEW,
order: [post.id],
posts: [post],
});
return {post};
};
export async function removePost(serverUrl: string, post: PostModel | Post) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
try {
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
if (post.type === Post.POST_TYPES.COMBINED_USER_ACTIVITY && post.props?.system_post_ids) {
const systemPostIds = getPostIdsForCombinedUserActivityPost(post.id);
const removeModels = [];
for await (const id of systemPostIds) {
const postModel = await getPostById(database, id);
if (postModel) {
const preparedPost = await prepareDeletePost(postModel);
removeModels.push(...preparedPost);
}
}
if (post.type === Post.POST_TYPES.COMBINED_USER_ACTIVITY && post.props?.system_post_ids) {
const systemPostIds = getPostIdsForCombinedUserActivityPost(post.id);
const removeModels = [];
for await (const id of systemPostIds) {
const postModel = await getPostById(operator.database, id);
if (removeModels.length) {
await operator.batchRecords(removeModels);
}
} else {
const postModel = await getPostById(database, post.id);
if (postModel) {
const preparedPost = await prepareDeletePost(postModel);
removeModels.push(...preparedPost);
if (preparedPost.length) {
await operator.batchRecords(preparedPost);
}
}
}
if (removeModels.length) {
await operator.batchRecords(removeModels);
}
} else {
const postModel = await getPostById(operator.database, post.id);
if (postModel) {
const preparedPost = await prepareDeletePost(postModel);
if (preparedPost.length) {
await operator.batchRecords(preparedPost);
}
}
return {post};
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed removePost', error);
return {error};
}
return {post};
}
export async function markPostAsDeleted(serverUrl: string, post: Post, prepareRecordsOnly = false) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
try {
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const dbPost = await getPostById(database, post.id);
if (!dbPost) {
throw new Error('Post not found');
}
const dbPost = await getPostById(operator.database, post.id);
if (!dbPost) {
return {error: 'Post not found'};
}
const model = dbPost.prepareUpdate((p) => {
p.deleteAt = Date.now();
p.message = '';
p.metadata = null;
p.props = undefined;
});
const model = dbPost.prepareUpdate((p) => {
p.deleteAt = Date.now();
p.message = '';
p.metadata = null;
p.props = undefined;
});
if (!prepareRecordsOnly) {
await operator.batchRecords([dbPost]);
if (!prepareRecordsOnly) {
await operator.batchRecords([dbPost]);
}
return {model};
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed markPostAsDeleted', error);
return {error};
}
return {model};
}

View File

@@ -9,19 +9,13 @@ import {getEmojiFirstAlias} from '@utils/emoji/helpers';
const MAXIMUM_RECENT_EMOJI = 27;
export const addRecentReaction = async (serverUrl: string, emojiNames: string[], prepareRecordsOnly = false) => {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
const {database} = operator;
if (!emojiNames.length) {
return [];
}
let recent = await getRecentReactions(database);
try {
if (!emojiNames.length) {
return [];
}
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
let recent = await getRecentReactions(database);
const recentEmojis = new Set(recent);
const aliases = emojiNames.map((e) => getEmojiFirstAlias(e));
for (const alias of aliases) {
@@ -43,6 +37,8 @@ export const addRecentReaction = async (serverUrl: string, emojiNames: string[],
prepareRecordsOnly,
});
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed addRecentReaction', error);
return {error};
}
};

View File

@@ -5,31 +5,28 @@ import DatabaseManager from '@database/manager';
import {prepareDeleteTeam, getMyTeamById, removeTeamFromTeamHistory} from '@queries/servers/team';
export async function removeUserFromTeam(serverUrl: string, teamId: string) {
const serverDatabase = DatabaseManager.serverDatabases[serverUrl];
if (!serverDatabase) {
return;
}
const {operator, database} = serverDatabase;
const myTeam = await getMyTeamById(database, teamId);
if (myTeam) {
const team = await myTeam.team.fetch();
if (!team) {
return;
}
const models = await prepareDeleteTeam(team);
const system = await removeTeamFromTeamHistory(operator, team.id, true);
if (system) {
models.push(...system);
}
if (models.length) {
try {
try {
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const myTeam = await getMyTeamById(database, teamId);
if (myTeam) {
const team = await myTeam.team.fetch();
if (!team) {
throw new Error('Team not found');
}
const models = await prepareDeleteTeam(team);
const system = await removeTeamFromTeamHistory(operator, team.id, true);
if (system) {
models.push(...system);
}
if (models.length) {
await operator.batchRecords(models);
} catch {
// eslint-disable-next-line no-console
console.log('FAILED TO BATCH CHANGES FOR REMOVE USER FROM TEAM');
}
}
return {error: undefined};
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed removeUserFromTeam', error);
return {error};
}
}

View File

@@ -23,24 +23,19 @@ import {changeOpacity, setThemeDefaults, updateThemeIfNeeded} from '@utils/theme
import type Model from '@nozbe/watermelondb/Model';
export const switchToGlobalThreads = async (serverUrl: string, teamId?: string, prepareRecordsOnly = false) => {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
const {database} = operator;
const models: Model[] = [];
let teamIdToUse = teamId;
if (!teamId) {
teamIdToUse = await getCurrentTeamId(database);
}
if (!teamIdToUse) {
return {error: 'no team to switch to'};
}
try {
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const models: Model[] = [];
let teamIdToUse = teamId;
if (!teamId) {
teamIdToUse = await getCurrentTeamId(database);
}
if (!teamIdToUse) {
throw new Error('no team to switch to');
}
await setCurrentTeamAndChannelId(operator, teamIdToUse, '');
const history = await addChannelToTeamHistory(operator, teamIdToUse, Screens.GLOBAL_THREADS, true);
models.push(...history);
@@ -55,34 +50,30 @@ export const switchToGlobalThreads = async (serverUrl: string, teamId?: string,
} else {
goToScreen(Screens.GLOBAL_THREADS, '', {}, {topBar: {visible: false}});
}
return {models};
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed switchToGlobalThreads', error);
return {error};
}
return {models};
};
export const switchToThread = async (serverUrl: string, rootId: string, isFromNotification = false) => {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
const {database} = operator;
try {
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const user = await getCurrentUser(database);
if (!user) {
return {error: 'User not found'};
throw new Error('User not found');
}
const post = await getPostById(database, rootId);
if (!post) {
return {error: 'Post not found'};
throw new Error('Post not found');
}
const channel = await getChannelById(database, post.channelId);
if (!channel) {
return {error: 'Channel not found'};
throw new Error('Channel not found');
}
const system = await getCommonSystemValues(database);
@@ -180,6 +171,8 @@ export const switchToThread = async (serverUrl: string, rootId: string, isFromNo
return {};
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed switchToThread', error);
return {error};
}
};
@@ -188,105 +181,104 @@ export const switchToThread = async (serverUrl: string, rootId: string, isFromNo
// 1. If a reply, then update the reply_count, add user as the participant
// 2. Else add the post as a thread
export async function createThreadFromNewPost(serverUrl: string, post: Post, prepareRecordsOnly = false) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
const models: Model[] = [];
if (post.root_id) {
try {
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const models: Model[] = [];
if (post.root_id) {
// Update the thread data: `reply_count`
const {model: threadModel} = await updateThread(serverUrl, post.root_id, {reply_count: post.reply_count}, true);
if (threadModel) {
models.push(threadModel);
const {model: threadModel} = await updateThread(serverUrl, post.root_id, {reply_count: post.reply_count}, true);
if (threadModel) {
models.push(threadModel);
}
// Add user as a participant to the thread
const threadParticipantModels = await operator.handleThreadParticipants({
threadsParticipants: [{
thread_id: post.root_id,
participants: [{
thread_id: post.root_id,
id: post.user_id,
}],
}],
prepareRecordsOnly: true,
skipSync: true,
});
models.push(...threadParticipantModels);
} else { // If the post is a root post, then we need to add it to the thread table
const threadModels = await prepareThreadsFromReceivedPosts(operator, [post]);
models.push(...threadModels);
}
// Add user as a participant to the thread
const threadParticipantModels = await operator.handleThreadParticipants({
threadsParticipants: [{
thread_id: post.root_id,
participants: [{
thread_id: post.root_id,
id: post.user_id,
}],
}],
prepareRecordsOnly: true,
skipSync: true,
});
models.push(...threadParticipantModels);
} else { // If the post is a root post, then we need to add it to the thread table
const threadModels = await prepareThreadsFromReceivedPosts(operator, [post]);
models.push(...threadModels);
}
if (!prepareRecordsOnly) {
await operator.batchRecords(models);
}
if (!prepareRecordsOnly) {
await operator.batchRecords(models);
return {models};
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed createThreadFromNewPost', error);
return {error};
}
return {models};
}
// On receiving threads, Along with the "threads" & "thread participants", extract and save "posts" & "users"
export async function processReceivedThreads(serverUrl: string, threads: Thread[], teamId: string, loadedInGlobalThreads = false, prepareRecordsOnly = false) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
try {
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const currentUserId = await getCurrentUserId(database);
const {database} = operator;
const currentUserId = await getCurrentUserId(database);
const posts: Post[] = [];
const users: UserProfile[] = [];
const posts: Post[] = [];
const users: UserProfile[] = [];
// Extract posts & users from the received threads
for (let i = 0; i < threads.length; i++) {
const {participants, post} = threads[i];
posts.push(post);
participants.forEach((participant) => {
if (currentUserId !== participant.id) {
users.push(participant);
}
});
}
// Extract posts & users from the received threads
for (let i = 0; i < threads.length; i++) {
const {participants, post} = threads[i];
posts.push(post);
participants.forEach((participant) => {
if (currentUserId !== participant.id) {
users.push(participant);
}
});
}
const postModels = await operator.handlePosts({
actionType: ActionType.POSTS.RECEIVED_IN_CHANNEL,
order: [],
posts,
prepareRecordsOnly: true,
});
const threadModels = await operator.handleThreads({
threads,
teamId,
prepareRecordsOnly: true,
loadedInGlobalThreads,
});
const models = [...postModels, ...threadModels];
if (users.length) {
const userModels = await operator.handleUsers({
users,
const postModels = await operator.handlePosts({
actionType: ActionType.POSTS.RECEIVED_IN_CHANNEL,
order: [],
posts,
prepareRecordsOnly: true,
});
models.push(...userModels);
}
if (!prepareRecordsOnly) {
await operator.batchRecords(models);
const threadModels = await operator.handleThreads({
threads,
teamId,
prepareRecordsOnly: true,
loadedInGlobalThreads,
});
const models = [...postModels, ...threadModels];
if (users.length) {
const userModels = await operator.handleUsers({
users,
prepareRecordsOnly: true,
});
models.push(...userModels);
}
if (!prepareRecordsOnly) {
await operator.batchRecords(models);
}
return {models};
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed processReceivedThreads', error);
return {error};
}
return {models};
}
export async function markTeamThreadsAsRead(serverUrl: string, teamId: string, prepareRecordsOnly = false) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
try {
const {database} = operator;
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const threads = await queryThreadsInTeam(database, teamId, true, true, true).fetch();
const models = threads.map((thread) => thread.prepareUpdate((record) => {
record.unreadMentions = 0;
@@ -298,35 +290,35 @@ export async function markTeamThreadsAsRead(serverUrl: string, teamId: string, p
}
return {models};
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed markTeamThreadsAsRead', error);
return {error};
}
}
export async function updateThread(serverUrl: string, threadId: string, updatedThread: Partial<Thread>, prepareRecordsOnly = false) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
try {
const {database} = operator;
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const thread = await getThreadById(database, threadId);
if (thread) {
const model = thread.prepareUpdate((record) => {
record.isFollowing = updatedThread.is_following ?? record.isFollowing;
record.replyCount = updatedThread.reply_count ?? record.replyCount;
record.lastViewedAt = updatedThread.last_viewed_at ?? record.lastViewedAt;
record.unreadMentions = updatedThread.unread_mentions ?? record.unreadMentions;
record.unreadReplies = updatedThread.unread_replies ?? record.unreadReplies;
});
if (!prepareRecordsOnly) {
await operator.batchRecords([model]);
}
return {model};
if (!thread) {
throw new Error('Thread not found');
}
return {error: 'Thread not found'};
const model = thread.prepareUpdate((record) => {
record.isFollowing = updatedThread.is_following ?? record.isFollowing;
record.replyCount = updatedThread.reply_count ?? record.replyCount;
record.lastViewedAt = updatedThread.last_viewed_at ?? record.lastViewedAt;
record.unreadMentions = updatedThread.unread_mentions ?? record.unreadMentions;
record.unreadReplies = updatedThread.unread_replies ?? record.unreadReplies;
});
if (!prepareRecordsOnly) {
await operator.batchRecords([model]);
}
return {model};
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed markTeamThreadsAsRead', error);
return {error};
}
}

View File

@@ -1,29 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {getTimeZone} from 'react-native-localize';
import type UserModel from '@typings/database/models/servers/user';
export const isTimezoneEnabled = (config: Partial<ClientConfig>) => {
return config?.ExperimentalTimezone === 'true';
};
export function getDeviceTimezone() {
return getTimeZone();
}
export const getUserTimezone = (currentUser: UserModel) => {
if (currentUser?.timezone) {
return {
...currentUser?.timezone,
useAutomaticTimezone: currentUser?.timezone?.useAutomaticTimezone === 'true',
};
}
return {
useAutomaticTimezone: true,
automaticTimezone: '',
manualTimezone: '',
};
};

View File

@@ -13,107 +13,95 @@ import type Model from '@nozbe/watermelondb/Model';
import type UserModel from '@typings/database/models/servers/user';
export async function setCurrentUserStatusOffline(serverUrl: string) {
const serverDatabase = DatabaseManager.serverDatabases[serverUrl];
if (!serverDatabase) {
return {error: `No database present for ${serverUrl}`};
}
const {database, operator} = serverDatabase;
const user = await getCurrentUser(database);
if (!user) {
return {error: `No current user for ${serverUrl}`};
}
user.prepareStatus(General.OFFLINE);
try {
await operator.batchRecords([user]);
} catch {
// eslint-disable-next-line no-console
console.log('FAILED TO BATCH CHANGES FOR SET CURRENT USER STATUS OFFLINE');
}
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const user = await getCurrentUser(database);
if (!user) {
throw new Error(`No current user for ${serverUrl}`);
}
return null;
user.prepareStatus(General.OFFLINE);
await operator.batchRecords([user]);
return null;
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed setCurrentUserStatusOffline', error);
return {error};
}
}
export async function updateLocalCustomStatus(serverUrl: string, user: UserModel, customStatus?: UserCustomStatus) {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
try {
const {operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const models: Model[] = [];
const currentProps = {...user.props, customStatus: customStatus || {}};
const userModel = user.prepareUpdate((u: UserModel) => {
u.props = currentProps;
});
const models: Model[] = [];
const currentProps = {...user.props, customStatus: customStatus || {}};
const userModel = user.prepareUpdate((u: UserModel) => {
u.props = currentProps;
});
models.push(userModel);
if (customStatus) {
const recent = await updateRecentCustomStatuses(serverUrl, customStatus, true);
if (Array.isArray(recent)) {
models.push(...recent);
}
models.push(userModel);
if (customStatus) {
const recent = await updateRecentCustomStatuses(serverUrl, customStatus, true);
if (Array.isArray(recent)) {
models.push(...recent);
}
if (customStatus.emoji) {
const recentEmojis = await addRecentReaction(serverUrl, [customStatus.emoji], true);
if (Array.isArray(recentEmojis)) {
models.push(...recentEmojis);
if (customStatus.emoji) {
const recentEmojis = await addRecentReaction(serverUrl, [customStatus.emoji], true);
if (Array.isArray(recentEmojis)) {
models.push(...recentEmojis);
}
}
}
}
try {
await operator.batchRecords(models);
} catch {
// eslint-disable-next-line no-console
console.log('FAILED TO BATCH CHANGES FOR UPDATING CUSTOM STATUS');
}
return {};
return {};
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed updateLocalCustomStatus', error);
return {error};
}
}
export const updateRecentCustomStatuses = async (serverUrl: string, customStatus: UserCustomStatus, prepareRecordsOnly = false, remove = false) => {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
const recentStatuses = await getRecentCustomStatuses(operator.database);
const index = recentStatuses.findIndex((cs) => (
cs.emoji === customStatus.emoji &&
try {
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const recentStatuses = await getRecentCustomStatuses(database);
const index = recentStatuses.findIndex((cs) => (
cs.emoji === customStatus.emoji &&
cs.text === customStatus.text &&
cs.duration === customStatus.duration
));
));
if (index !== -1) {
recentStatuses.splice(index, 1);
if (index !== -1) {
recentStatuses.splice(index, 1);
}
if (!remove) {
recentStatuses.unshift(customStatus);
}
return operator.handleSystem({
systems: [{
id: SYSTEM_IDENTIFIERS.RECENT_CUSTOM_STATUS,
value: JSON.stringify(recentStatuses),
}],
prepareRecordsOnly,
});
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed updateRecentCustomStatuses', error);
return {error};
}
if (!remove) {
recentStatuses.unshift(customStatus);
}
return operator.handleSystem({
systems: [{
id: SYSTEM_IDENTIFIERS.RECENT_CUSTOM_STATUS,
value: JSON.stringify(recentStatuses),
}],
prepareRecordsOnly,
});
};
export const updateLocalUser = async (
serverUrl: string,
userDetails: Partial<UserProfile> & { status?: string},
) => {
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
if (!database) {
return {error: `${serverUrl} database not found`};
}
try {
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const user = await getCurrentUser(database);
if (user) {
await database.write(async () => {
@@ -135,21 +123,17 @@ export const updateLocalUser = async (
});
});
}
return {user};
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed updateLocalUser', error);
return {error};
}
return {};
};
export const storeProfile = async (serverUrl: string, profile: UserProfile) => {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}
try {
const {database} = operator;
const {database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const user = await getUserById(database, profile.id);
if (user) {
return {user};
@@ -162,6 +146,8 @@ export const storeProfile = async (serverUrl: string, profile: UserProfile) => {
return {user: records[0]};
} catch (error) {
// eslint-disable-next-line no-console
console.log('Failed storeProfile', error);
return {error};
}
};

View File

@@ -3,7 +3,6 @@
import {DeviceEventEmitter} from 'react-native';
import {getDeviceTimezone, isTimezoneEnabled} from '@actions/local/timezone';
import {Database, Events} from '@constants';
import {SYSTEM_IDENTIFIERS} from '@constants/database';
import DatabaseManager from '@database/manager';
@@ -14,6 +13,7 @@ import {getDeviceToken} from '@queries/app/global';
import {getCurrentUserId, getCommonSystemValues} from '@queries/servers/system';
import EphemeralStore from '@store/ephemeral_store';
import {getCSRFFromCookie} from '@utils/security';
import {getDeviceTimezone, isTimezoneEnabled} from '@utils/timezone';
import {loginEntry} from './entry';
import {fetchDataRetentionPolicy} from './systems';

View File

@@ -7,7 +7,6 @@ import {Model} from '@nozbe/watermelondb';
import {chunk} from 'lodash';
import {updateChannelsDisplayName} from '@actions/local/channel';
import {getUserTimezone} from '@actions/local/timezone';
import {updateRecentCustomStatuses, updateLocalUser} from '@actions/local/user';
import {fetchRolesIfNeeded} from '@actions/remote/role';
import {General} from '@constants';
@@ -17,7 +16,7 @@ import NetworkManager from '@managers/network_manager';
import {getMembersCountByChannelsId, queryChannelsByTypes} from '@queries/servers/channel';
import {getCurrentTeamId, getCurrentUserId} from '@queries/servers/system';
import {getCurrentUser, getUserById, prepareUsers, queryAllUsers, queryUsersById, queryUsersByUsername} from '@queries/servers/user';
import {removeUserFromList} from '@utils/user';
import {getUserTimezoneProps, removeUserFromList} from '@utils/user';
import {forceLogoutIfNecessary} from './session';
@@ -804,7 +803,7 @@ export const autoUpdateTimezone = async (serverUrl: string, {deviceTimezone, use
return null;
}
const currentTimezone = getUserTimezone(currentUser);
const currentTimezone = getUserTimezoneProps(currentUser);
const newTimezoneExists = currentTimezone.automaticTimezone !== deviceTimezone;
if (currentTimezone.useAutomaticTimezone && newTimezoneExists) {

View File

@@ -9,7 +9,6 @@ import {Text, TextStyle} from 'react-native';
import {of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import {getUserTimezone} from '@actions/local/timezone';
import FormattedDate from '@components/formatted_date';
import FormattedText from '@components/formatted_text';
import FormattedTime from '@components/formatted_time';
@@ -19,6 +18,7 @@ import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
import {observeCurrentUser} from '@queries/servers/user';
import {getCurrentMomentForTimezone} from '@utils/helpers';
import {makeStyleSheetFromTheme} from '@utils/theme';
import {getUserTimezoneProps} from '@utils/user';
import type {WithDatabaseArgs} from '@typings/database/database';
import type UserModel from '@typings/database/models/servers/user';
@@ -46,7 +46,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
});
const CustomStatusExpiry = ({currentUser, isMilitaryTime, showPrefix, showTimeCompulsory, showToday, testID = '', textStyles = {}, theme, time, withinBrackets}: Props) => {
const userTimezone = getUserTimezone(currentUser);
const userTimezone = getUserTimezoneProps(currentUser);
const timezone = userTimezone.useAutomaticTimezone ? userTimezone.automaticTimezone : userTimezone.manualTimezone;
const styles = getStyleSheet(theme);
const currentMomentTime = getCurrentMomentForTimezone(timezone);

View File

@@ -229,6 +229,24 @@ class DatabaseManager {
return undefined;
};
public getAppDatabaseAndOperator = () => {
const app = this.appDatabase;
if (!app) {
throw new Error('App database not found');
}
return app;
};
public getServerDatabaseAndOperator = (serverUrl: string) => {
const server = this.serverDatabases[serverUrl];
if (!server) {
throw new Error(`${serverUrl} database not found`);
}
return server;
};
public getActiveServerDatabase = async (): Promise<Database|undefined> => {
const database = this.appDatabase?.database;
if (database) {

View File

@@ -307,6 +307,38 @@ class DatabaseManager {
return undefined;
};
/**
* getAppDatabaseAndOperator: Helper function that returns App the database and operator.
* use within a try/catch block
* @returns AppDatabase
* @throws Error
*/
public getAppDatabaseAndOperator = () => {
const app = this.appDatabase;
if (!app) {
throw new Error('App database not found');
}
return app;
};
/**
* getServerDatabaseAndOperator: Helper function that returns the database and operator
* for a specific server.
* use within a try/catch block
* @param serverUrl the url of the server
* @returns ServerDatabase
* @throws Error
*/
public getServerDatabaseAndOperator = (serverUrl: string) => {
const server = this.serverDatabases[serverUrl];
if (!server) {
throw new Error(`${serverUrl} database not found`);
}
return server;
};
/**
* setActiveServerDatabase: Set the new active server database.
* This method should be called when switching to another server.

12
app/utils/timezone.ts Normal file
View File

@@ -0,0 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {getTimeZone} from 'react-native-localize';
export const isTimezoneEnabled = (config: Partial<ClientConfig>) => {
return config?.ExperimentalTimezone === 'true';
};
export function getDeviceTimezone() {
return getTimeZone();
}

View File

@@ -101,6 +101,21 @@ export const getUsersByUsername = (users: UserModel[]) => {
return usersByUsername;
};
export const getUserTimezoneProps = (currentUser: UserModel) => {
if (currentUser?.timezone) {
return {
...currentUser?.timezone,
useAutomaticTimezone: currentUser?.timezone?.useAutomaticTimezone === 'true',
};
}
return {
useAutomaticTimezone: true,
automaticTimezone: '',
manualTimezone: '',
};
};
export const getUserTimezone = (user: UserModel) => {
return getTimezone(user.timezone);
};