Gekidou - Sidebar - Adds actions, queries, db models and rest client (#5953)
* Adds actions, queries, db models and rest client * move fetching categories inside fetchMyChannelsForTeam * code cleanup * deconstruct categoriesWithOrder * DMs fetching for changing teams * Fix bad merge and address feedback Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
This commit is contained in:
parent
0a45fd9e13
commit
9f5f73b264
11 changed files with 269 additions and 14 deletions
43
app/actions/local/category.ts
Normal file
43
app/actions/local/category.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Model} from '@nozbe/watermelondb';
|
||||
|
||||
import {prepareCategories, prepareCategoryChannels} from '@app/queries/servers/categories';
|
||||
import DatabaseManager from '@database/manager';
|
||||
|
||||
export const storeCategories = async (serverUrl: string, categories: CategoryWithChannels[], prepareRecordsOnly = false) => {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
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() as Model[];
|
||||
|
||||
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};
|
||||
}
|
||||
}
|
||||
|
||||
return {models: flattenedModels};
|
||||
};
|
||||
36
app/actions/remote/category.ts
Normal file
36
app/actions/remote/category.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {storeCategories} from '@actions/local/category';
|
||||
import NetworkManager from '@init/network_manager';
|
||||
|
||||
import {forceLogoutIfNecessary} from './session';
|
||||
|
||||
import type {Client} from '@client/rest';
|
||||
|
||||
export type CategoriesRequest = {
|
||||
categories?: CategoryWithChannels[];
|
||||
error?: unknown;
|
||||
}
|
||||
|
||||
export const fetchCategories = async (serverUrl: string, teamId: string, fetchOnly = false): Promise<CategoriesRequest> => {
|
||||
let client: Client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const {categories} = await client.getCategories('me', teamId);
|
||||
|
||||
if (!fetchOnly) {
|
||||
storeCategories(serverUrl, categories);
|
||||
}
|
||||
|
||||
return {categories};
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
import {Model} from '@nozbe/watermelondb';
|
||||
import {IntlShape} from 'react-intl';
|
||||
|
||||
import {storeCategories} from '@actions/local/category';
|
||||
import {storeMyChannelsForTeam, switchToChannel} from '@actions/local/channel';
|
||||
import {General} from '@constants';
|
||||
import DatabaseManager from '@database/manager';
|
||||
|
|
@ -30,6 +31,7 @@ import type MyTeamModel from '@typings/database/models/servers/my_team';
|
|||
import type TeamModel from '@typings/database/models/servers/team';
|
||||
|
||||
export type MyChannelsRequest = {
|
||||
categories?: CategoryWithChannels[];
|
||||
channels?: Channel[];
|
||||
memberships?: ChannelMembership[];
|
||||
error?: unknown;
|
||||
|
|
@ -195,16 +197,20 @@ export const fetchMyChannelsForTeam = async (serverUrl: string, teamId: string,
|
|||
}
|
||||
|
||||
try {
|
||||
let [channels, memberships]: [Channel[], ChannelMembership[]] = await Promise.all([
|
||||
const [allChannels, channelMemberships, categoriesWithOrder] = await Promise.all([
|
||||
client.getMyChannels(teamId, includeDeleted, since),
|
||||
client.getMyChannelMembers(teamId),
|
||||
client.getCategories('me', teamId),
|
||||
]);
|
||||
|
||||
let channels = allChannels;
|
||||
let memberships = channelMemberships;
|
||||
if (excludeDirect) {
|
||||
channels = channels.filter((c) => c.type !== General.GM_CHANNEL && c.type !== General.DM_CHANNEL);
|
||||
}
|
||||
|
||||
const channelIds = new Set<string>(channels.map((c) => c.id));
|
||||
const {categories} = categoriesWithOrder;
|
||||
memberships = memberships.reduce((result: ChannelMembership[], m: ChannelMembership) => {
|
||||
if (channelIds.has(m.channel_id)) {
|
||||
result.push(m);
|
||||
|
|
@ -214,9 +220,10 @@ export const fetchMyChannelsForTeam = async (serverUrl: string, teamId: string,
|
|||
|
||||
if (!fetchOnly) {
|
||||
storeMyChannelsForTeam(serverUrl, teamId, channels, memberships);
|
||||
storeCategories(serverUrl, categories);
|
||||
}
|
||||
|
||||
return {channels, memberships};
|
||||
return {channels, memberships, categories};
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {Events} from '@constants';
|
|||
import DatabaseManager from '@database/manager';
|
||||
import NetworkManager from '@init/network_manager';
|
||||
import {prepareMyChannelsForTeam, queryDefaultChannelForTeam} from '@queries/servers/channel';
|
||||
import {prepareCommonSystemValues, queryCurrentTeamId} from '@queries/servers/system';
|
||||
import {prepareCommonSystemValues, queryCurrentTeamId, queryWebSocketLastDisconnected} from '@queries/servers/system';
|
||||
import {addTeamToTeamHistory, prepareDeleteTeam, prepareMyTeams, queryNthLastChannelFromTeam, queryTeamsById, syncTeamTable} from '@queries/servers/team';
|
||||
import {isTablet} from '@utils/helpers';
|
||||
|
||||
|
|
@ -262,7 +262,12 @@ export const removeUserFromTeam = async (serverUrl: string, teamId: string, user
|
|||
};
|
||||
|
||||
export const handleTeamChange = async (serverUrl: string, teamId: string) => {
|
||||
const {operator, database} = DatabaseManager.serverDatabases[serverUrl];
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {database} = operator;
|
||||
const currentTeamId = await queryCurrentTeamId(database);
|
||||
|
||||
if (currentTeamId === teamId) {
|
||||
|
|
@ -292,7 +297,10 @@ export const handleTeamChange = async (serverUrl: string, teamId: string) => {
|
|||
await operator.batchRecords(models);
|
||||
}
|
||||
|
||||
const {channels, memberships, error} = await fetchMyChannelsForTeam(serverUrl, teamId);
|
||||
// If WebSocket is not disconnected we fetch everything since this moment
|
||||
const lastDisconnectedAt = (await queryWebSocketLastDisconnected(database)) || Date.now();
|
||||
const {channels, memberships, error} = await fetchMyChannelsForTeam(serverUrl, teamId, true, lastDisconnectedAt, false, true);
|
||||
|
||||
if (error) {
|
||||
DeviceEventEmitter.emit(Events.TEAM_LOAD_ERROR, serverUrl, error);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,6 +109,18 @@ export default class ClientBase {
|
|||
return `${this.getTeamMembersRoute(teamId)}/${userId}`;
|
||||
}
|
||||
|
||||
getCategoriesRoute(userId: string, teamId: string) {
|
||||
return `${this.getUserRoute(userId)}/teams/${teamId}/channels/categories`;
|
||||
}
|
||||
|
||||
getCategoriesOrderRoute(userId: string, teamId: string) {
|
||||
return `${this.getCategoriesRoute(userId, teamId)}/order`;
|
||||
}
|
||||
|
||||
getCategoryRoute(userId: string, teamId: string, categoryId: string) {
|
||||
return `${this.getCategoriesRoute(userId, teamId)}/${categoryId}`;
|
||||
}
|
||||
|
||||
getChannelsRoute() {
|
||||
return `${this.urlVersion}/channels`;
|
||||
}
|
||||
|
|
|
|||
31
app/client/rest/categories.ts
Normal file
31
app/client/rest/categories.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
export interface ClientCategoriesMix {
|
||||
getCategories: (userId: string, teamId: string) => Promise<CategoriesWithOrder>;
|
||||
getCategoriesOrder: (userId: string, teamId: string) => Promise<string[]>;
|
||||
getCategory: (userId: string, teamId: string, categoryId: string) => Promise<Category>;
|
||||
}
|
||||
|
||||
const ClientCategories = (superclass: any) => class extends superclass {
|
||||
getCategories = async (userId: string, teamId: string) => {
|
||||
return this.doFetch(
|
||||
`${this.getCategoriesRoute(userId, teamId)}`,
|
||||
{method: 'get'},
|
||||
);
|
||||
};
|
||||
getCategoriesOrder = async (userId: string, teamId: string) => {
|
||||
return this.doFetch(
|
||||
`${this.getCategoriesOrderRoute(userId, teamId)}`,
|
||||
{method: 'get'},
|
||||
);
|
||||
};
|
||||
getCategory = async (userId: string, teamId: string, categoryId: string) => {
|
||||
return this.doFetch(
|
||||
`${this.getCategoryRoute(userId, teamId, categoryId)}`,
|
||||
{method: 'get'},
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
export default ClientCategories;
|
||||
|
|
@ -5,6 +5,7 @@ import mix from '@utils/mix';
|
|||
|
||||
import ClientApps, {ClientAppsMix} from './apps';
|
||||
import ClientBase from './base';
|
||||
import ClientCategories, {ClientCategoriesMix} from './categories';
|
||||
import ClientChannels, {ClientChannelsMix} from './channels';
|
||||
import {DEFAULT_LIMIT_AFTER, DEFAULT_LIMIT_BEFORE, HEADER_X_VERSION_ID} from './constants';
|
||||
import ClientEmojis, {ClientEmojisMix} from './emojis';
|
||||
|
|
@ -22,6 +23,7 @@ import type {APIClientInterface} from '@mattermost/react-native-network-client';
|
|||
|
||||
interface Client extends ClientBase,
|
||||
ClientAppsMix,
|
||||
ClientCategoriesMix,
|
||||
ClientChannelsMix,
|
||||
ClientEmojisMix,
|
||||
ClientFilesMix,
|
||||
|
|
@ -37,6 +39,7 @@ interface Client extends ClientBase,
|
|||
|
||||
class Client extends mix(ClientBase).with(
|
||||
ClientApps,
|
||||
ClientCategories,
|
||||
ClientChannels,
|
||||
ClientEmojis,
|
||||
ClientFiles,
|
||||
|
|
|
|||
|
|
@ -67,19 +67,17 @@ export default class CategoryModel extends Model implements CategoryInterface {
|
|||
@children(CATEGORY_CHANNEL) categoryChannels!: Query<CategoryChannelModel>;
|
||||
|
||||
/** categoryChannelsBySortOrder : Retrieves assocated category channels sorted by sort_order */
|
||||
@lazy categoryChannelsBySortOrder = this.categoryChannels.collection.query(Q.sortBy('sort_order', Q.asc));
|
||||
@lazy categoryChannelsBySortOrder = this.categoryChannels.collection.query(
|
||||
Q.where('category_id', this.id),
|
||||
Q.sortBy('sort_order', Q.asc),
|
||||
);
|
||||
|
||||
/** channels : Retrieves all the channels that are part of this category */
|
||||
@lazy channels = this.collections.
|
||||
get<ChannelModel>(CHANNEL).
|
||||
query(
|
||||
Q.experimentalJoinTables([CHANNEL, CATEGORY_CHANNEL]),
|
||||
Q.on(CATEGORY_CHANNEL,
|
||||
Q.and(
|
||||
Q.on(CHANNEL, Q.where('delete_at', Q.eq(0))),
|
||||
Q.where('category_id', this.id),
|
||||
),
|
||||
),
|
||||
Q.on(CATEGORY_CHANNEL, 'category_id', this.id),
|
||||
Q.where('delete_at', Q.eq(0)),
|
||||
Q.sortBy('display_name'),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {MM_TABLES} from '@constants/database';
|
|||
import type CategoryModel from '@typings/database/models/servers/category';
|
||||
import type CategoryChannelInterface from '@typings/database/models/servers/category_channel';
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
import type MyChannelModel from '@typings/database/models/servers/my_channel';
|
||||
|
||||
const {CATEGORY_CHANNEL, CATEGORY, MY_CHANNEL, CHANNEL} = MM_TABLES.SERVER;
|
||||
|
||||
|
|
@ -50,5 +51,5 @@ export default class CategoryChannelModel extends Model implements CategoryChann
|
|||
@immutableRelation(CHANNEL, 'channel_id') channel!: Relation<ChannelModel>;
|
||||
|
||||
/** myChannel : The related myChannel */
|
||||
@immutableRelation(MY_CHANNEL, 'channel_id') myChannel!: Relation<ChannelModel>;
|
||||
@immutableRelation(MY_CHANNEL, 'channel_id') myChannel!: Relation<MyChannelModel>;
|
||||
}
|
||||
|
|
|
|||
103
app/queries/servers/categories.ts
Normal file
103
app/queries/servers/categories.ts
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Database, Q} from '@nozbe/watermelondb';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
|
||||
import type ServerDataOperator from '@database/operator/server_data_operator';
|
||||
import type CategoryModel from '@typings/database/models/servers/category';
|
||||
|
||||
const {SERVER: {CATEGORY}} = MM_TABLES;
|
||||
|
||||
export const queryCategoryById = async (database: Database, categoryId: string) => {
|
||||
try {
|
||||
const record = (await database.collections.get<CategoryModel>(CATEGORY).find(categoryId));
|
||||
return record;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
export const queryCategoriesById = async (database: Database, categoryIds: string[]): Promise<CategoryModel[]> => {
|
||||
try {
|
||||
const records = (await database.get<CategoryModel>(CATEGORY).query(Q.where('id', Q.oneOf(categoryIds))).fetch());
|
||||
return records;
|
||||
} catch {
|
||||
return Promise.resolve([] as CategoryModel[]);
|
||||
}
|
||||
};
|
||||
|
||||
export const queryCategoriesByType = async (database: Database, type: CategoryType): Promise<CategoryModel[]> => {
|
||||
try {
|
||||
const records = (await database.get<CategoryModel>(CATEGORY).query(Q.where('type', type)).fetch());
|
||||
return records;
|
||||
} catch {
|
||||
return Promise.resolve([] as CategoryModel[]);
|
||||
}
|
||||
};
|
||||
|
||||
export const queryCategoriesByTeamId = async (database: Database, teamId: string): Promise<CategoryModel[]> => {
|
||||
try {
|
||||
const records = (await database.get<CategoryModel>(CATEGORY).query(Q.where('team_id', teamId)).fetch());
|
||||
return records;
|
||||
} catch {
|
||||
return Promise.resolve([] as CategoryModel[]);
|
||||
}
|
||||
};
|
||||
|
||||
export const queryCategoriesByTypeTeamId = async (database: Database, type: CategoryType, teamId: string): Promise<CategoryModel[]> => {
|
||||
try {
|
||||
const records = await database.get<CategoryModel>(CATEGORY).query(
|
||||
Q.where('team_id', teamId),
|
||||
Q.where('type', type),
|
||||
).fetch();
|
||||
return records;
|
||||
} catch {
|
||||
return Promise.resolve([] as CategoryModel[]);
|
||||
}
|
||||
};
|
||||
|
||||
export const queryAllCategories = async (database: Database): Promise<CategoryModel[]> => {
|
||||
try {
|
||||
const records = await database.get<CategoryModel>(CATEGORY).query().fetch();
|
||||
return records;
|
||||
} catch {
|
||||
return Promise.resolve([] as CategoryModel[]);
|
||||
}
|
||||
};
|
||||
|
||||
export const prepareCategories = (operator: ServerDataOperator, categories: CategoryWithChannels[]) => {
|
||||
try {
|
||||
const categoryRecords = operator.handleCategories({categories, prepareRecordsOnly: true});
|
||||
return [categoryRecords];
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
export const prepareCategoryChannels = (
|
||||
operator: ServerDataOperator,
|
||||
categories: CategoryWithChannels[],
|
||||
) => {
|
||||
try {
|
||||
const categoryChannels: CategoryChannel[] = [];
|
||||
|
||||
categories.forEach((category) => {
|
||||
category.channel_ids.forEach((channelId, index) => {
|
||||
categoryChannels.push({
|
||||
id: `${category.team_id}_${channelId}`,
|
||||
category_id: category.id,
|
||||
channel_id: channelId,
|
||||
sort_order: index,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const categoryChannelRecords = operator.handleCategoryChannels({categoryChannels, prepareRecordsOnly: true});
|
||||
|
||||
return [categoryChannelRecords];
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import ServerDataOperator from '@database/operator/server_data_operator';
|
||||
|
||||
import {prepareCategories, prepareCategoryChannels} from './categories';
|
||||
import {prepareDeleteChannel, prepareMyChannelsForTeam} from './channel';
|
||||
import {prepareMyPreferences} from './preference';
|
||||
import {prepareDeleteTeam, prepareMyTeams} from './team';
|
||||
|
|
@ -49,6 +50,18 @@ export const prepareModels = async ({operator, initialTeamId, removeTeams, remov
|
|||
}
|
||||
}
|
||||
|
||||
if (chData?.categories?.length) {
|
||||
const categoryModels = prepareCategories(operator, chData.categories);
|
||||
if (categoryModels) {
|
||||
modelPromises.push(...categoryModels);
|
||||
}
|
||||
|
||||
const categoryChannelModels = prepareCategoryChannels(operator, chData.categories);
|
||||
if (categoryChannelModels) {
|
||||
modelPromises.push(...categoryChannelModels);
|
||||
}
|
||||
}
|
||||
|
||||
if (initialTeamId && chData?.channels?.length) {
|
||||
const channelModels = await prepareMyChannelsForTeam(operator, initialTeamId, chData.channels, chData.memberships || []);
|
||||
if (channelModels) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue