forked from Ivasoft/mattermost-mobile
[Gekidou] Sidebar Categories (Database only) (#5909)
This commit is contained in:
@@ -11,7 +11,7 @@ import {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 {ChannelModel, ChannelInfoModel, ChannelMembershipModel, CustomEmojiModel, DraftModel, FileModel,
|
||||
import {CategoryModel, CategoryChannelModel, ChannelModel, ChannelInfoModel, ChannelMembershipModel, CustomEmojiModel, DraftModel, FileModel,
|
||||
GroupModel, GroupMembershipModel, GroupsChannelModel, GroupsTeamModel, MyChannelModel, MyChannelSettingsModel, MyTeamModel,
|
||||
PostModel, PostsInChannelModel, PostsInThreadModel, PreferenceModel, ReactionModel, RoleModel,
|
||||
SlashCommandModel, SystemModel, TeamModel, TeamChannelHistoryModel, TeamMembershipModel, TeamSearchHistoryModel,
|
||||
@@ -47,7 +47,7 @@ class DatabaseManager {
|
||||
constructor() {
|
||||
this.appModels = [InfoModel, GlobalModel, ServersModel];
|
||||
this.serverModels = [
|
||||
ChannelModel, ChannelInfoModel, ChannelMembershipModel, CustomEmojiModel, DraftModel, FileModel,
|
||||
CategoryModel, CategoryChannelModel, ChannelModel, ChannelInfoModel, ChannelMembershipModel, CustomEmojiModel, DraftModel, FileModel,
|
||||
GroupModel, GroupMembershipModel, GroupsChannelModel, GroupsTeamModel, MyChannelModel, MyChannelSettingsModel, MyTeamModel,
|
||||
PostModel, PostsInChannelModel, PostsInThreadModel, PreferenceModel, ReactionModel, RoleModel,
|
||||
SlashCommandModel, SystemModel, TeamModel, TeamChannelHistoryModel, TeamMembershipModel, TeamSearchHistoryModel,
|
||||
|
||||
@@ -12,7 +12,7 @@ import {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 {ChannelModel, ChannelInfoModel, ChannelMembershipModel, CustomEmojiModel, DraftModel, FileModel,
|
||||
import {CategoryModel, CategoryChannelModel, ChannelModel, ChannelInfoModel, ChannelMembershipModel, CustomEmojiModel, DraftModel, FileModel,
|
||||
GroupModel, GroupMembershipModel, GroupsChannelModel, GroupsTeamModel, MyChannelModel, MyChannelSettingsModel, MyTeamModel,
|
||||
PostModel, PostsInChannelModel, PostsInThreadModel, PreferenceModel, ReactionModel, RoleModel,
|
||||
SlashCommandModel, SystemModel, TeamModel, TeamChannelHistoryModel, TeamMembershipModel, TeamSearchHistoryModel,
|
||||
@@ -42,7 +42,7 @@ class DatabaseManager {
|
||||
constructor() {
|
||||
this.appModels = [InfoModel, GlobalModel, ServersModel];
|
||||
this.serverModels = [
|
||||
ChannelModel, ChannelInfoModel, ChannelMembershipModel, CustomEmojiModel, DraftModel, FileModel,
|
||||
CategoryModel, CategoryChannelModel, ChannelModel, ChannelInfoModel, ChannelMembershipModel, CustomEmojiModel, DraftModel, FileModel,
|
||||
GroupModel, GroupMembershipModel, GroupsChannelModel, GroupsTeamModel, MyChannelModel, MyChannelSettingsModel, MyTeamModel,
|
||||
PostModel, PostsInChannelModel, PostsInThreadModel, PreferenceModel, ReactionModel, RoleModel,
|
||||
SlashCommandModel, SystemModel, TeamModel, TeamChannelHistoryModel, TeamMembershipModel, TeamSearchHistoryModel,
|
||||
|
||||
105
app/database/models/server/category.ts
Normal file
105
app/database/models/server/category.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Relation, Query, Q} from '@nozbe/watermelondb';
|
||||
import {children, field, immutableRelation, lazy} from '@nozbe/watermelondb/decorators';
|
||||
import Model, {Associations} from '@nozbe/watermelondb/Model';
|
||||
import {map, distinctUntilChanged} from 'rxjs';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
|
||||
import type CategoryInterface from '@typings/database/models/servers/category';
|
||||
import type CategoryChannelModel 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';
|
||||
import type TeamModel from '@typings/database/models/servers/team';
|
||||
|
||||
const {
|
||||
CATEGORY,
|
||||
CATEGORY_CHANNEL,
|
||||
CHANNEL,
|
||||
MY_CHANNEL,
|
||||
TEAM,
|
||||
} = MM_TABLES.SERVER;
|
||||
|
||||
/**
|
||||
* A Category holds channels for a given user in a team
|
||||
*/
|
||||
export default class CategoryModel extends Model implements CategoryInterface {
|
||||
/** table (name) : Category */
|
||||
static table = CATEGORY;
|
||||
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations = {
|
||||
|
||||
/** A CATEGORY has a 1:N relationship with CHANNEL. A CATEGORY can possess multiple channels */
|
||||
[CATEGORY_CHANNEL]: {type: 'has_many', foreignKey: 'category_id'},
|
||||
|
||||
/** A TEAM can be associated to CATEGORY (relationship is 1:N) */
|
||||
[TEAM]: {type: 'belongs_to', key: 'team_id'},
|
||||
};
|
||||
|
||||
/** display_name : The display name for the category */
|
||||
@field('display_name') displayName!: string;
|
||||
|
||||
/** type : The type of category ('channels' | 'direct_messages' | 'favorites' | 'custom') */
|
||||
@field('type') type!: CategoryType;
|
||||
|
||||
/** sort_order : The index on which to sort and display categories */
|
||||
@field('sort_order') sortOrder!: number;
|
||||
|
||||
/** sorting : The type of sorting applied to the category channels (alpha, recent, manual) */
|
||||
@field('sorting') sorting!: CategorySorting;
|
||||
|
||||
/** collapsed : Boolean flag indicating if the category is collapsed */
|
||||
@field('collapsed') collapsed!: boolean;
|
||||
|
||||
/** muted : Boolean flag indicating if the category is muted */
|
||||
@field('muted') muted!: boolean;
|
||||
|
||||
/** teamId : The team in which this category lives */
|
||||
@field('team_id') teamId!: string;
|
||||
|
||||
/** team : Retrieves information about the team that this category is a part of. */
|
||||
@immutableRelation(TEAM, 'id') team!: Relation<TeamModel>;
|
||||
|
||||
/** categoryChannels : All the CategoryChannels associated with this team */
|
||||
@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));
|
||||
|
||||
/** 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.sortBy('display_name'),
|
||||
);
|
||||
|
||||
/** myChannels : Retrieves all myChannels that are part of this category */
|
||||
@lazy myChannels = this.collections.
|
||||
get<MyChannelModel>(MY_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.sortBy('last_post_at', Q.desc),
|
||||
);
|
||||
|
||||
/** hasChannels : Returns a boolean indicating if the category has channels */
|
||||
@lazy hasChannels = this.categoryChannels.observeCount().pipe(
|
||||
map((c) => c > 0),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
}
|
||||
54
app/database/models/server/category_channel.ts
Normal file
54
app/database/models/server/category_channel.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Relation} from '@nozbe/watermelondb';
|
||||
import {field, immutableRelation, relation} from '@nozbe/watermelondb/decorators';
|
||||
import Model, {Associations} from '@nozbe/watermelondb/Model';
|
||||
|
||||
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';
|
||||
|
||||
const {CATEGORY_CHANNEL, CATEGORY, MY_CHANNEL, CHANNEL} = MM_TABLES.SERVER;
|
||||
|
||||
/**
|
||||
* The CategoryChannel model represents the 'association table' where many categories have channels and many channels are in
|
||||
* categories (relationship type N:N)
|
||||
*/
|
||||
export default class CategoryChannelModel extends Model implements CategoryChannelInterface {
|
||||
/** table (name) : CategoryChannel */
|
||||
static table = CATEGORY_CHANNEL;
|
||||
|
||||
/** associations : Describes every relationship to this table. */
|
||||
static associations: Associations = {
|
||||
|
||||
/** A CategoryChannel belongs to a CATEGORY */
|
||||
[CATEGORY]: {type: 'belongs_to', key: 'category_id'},
|
||||
|
||||
/** A CategoryChannel has a Channel */
|
||||
[CHANNEL]: {type: 'belongs_to', key: 'channel_id'},
|
||||
|
||||
/** A CategoryChannel has a MyChannel */
|
||||
[MY_CHANNEL]: {type: 'belongs_to', key: 'channel_id'},
|
||||
};
|
||||
|
||||
/** category_id : The foreign key to the related Category record */
|
||||
@field('category_id') categoryId!: string;
|
||||
|
||||
/** channel_id : The foreign key to the related Channel record */
|
||||
@field('channel_id') channelId!: string;
|
||||
|
||||
/* sort_order: The sort order for the channel in category */
|
||||
@field('sort_order') sortOrder!: number;
|
||||
|
||||
/** category : The related category */
|
||||
@relation(CATEGORY, 'category_id') category!: Relation<CategoryModel>;
|
||||
|
||||
/** channel : The related channel */
|
||||
@immutableRelation(CHANNEL, 'channel_id') channel!: Relation<ChannelModel>;
|
||||
|
||||
/** myChannel : The related myChannel */
|
||||
@immutableRelation(MY_CHANNEL, 'channel_id') myChannel!: Relation<ChannelModel>;
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import type TeamModel from '@typings/database/models/servers/team';
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
const {
|
||||
CATEGORY_CHANNEL,
|
||||
CHANNEL,
|
||||
CHANNEL_INFO,
|
||||
CHANNEL_MEMBERSHIP,
|
||||
@@ -45,6 +46,9 @@ export default class ChannelModel extends Model {
|
||||
/** A CHANNEL can be associated with multiple CHANNEL_MEMBERSHIP (relationship is 1:N) */
|
||||
[CHANNEL_MEMBERSHIP]: {type: 'has_many', foreignKey: 'channel_id'},
|
||||
|
||||
/** A CHANNEL can be associated with multiple CATEGORY_CHANNEL (relationship is 1:N) */
|
||||
[CATEGORY_CHANNEL]: {type: 'has_many', foreignKey: 'channel_id'},
|
||||
|
||||
/** A CHANNEL can be associated with multiple DRAFT (relationship is 1:N) */
|
||||
[DRAFT]: {type: 'has_many', foreignKey: 'channel_id'},
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
export {default as CategoryModel} from './category';
|
||||
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';
|
||||
|
||||
@@ -9,7 +9,7 @@ import {MM_TABLES} from '@constants/database';
|
||||
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
|
||||
const {CHANNEL, MY_CHANNEL} = MM_TABLES.SERVER;
|
||||
const {CATEGORY_CHANNEL, CHANNEL, MY_CHANNEL} = MM_TABLES.SERVER;
|
||||
|
||||
/**
|
||||
* MyChannel is an extension of the Channel model but it lists only the Channels the app's user belongs to
|
||||
@@ -20,6 +20,7 @@ export default class MyChannelModel extends Model {
|
||||
|
||||
static associations: Associations = {
|
||||
[CHANNEL]: {type: 'belongs_to', key: 'id'},
|
||||
[CATEGORY_CHANNEL]: {type: 'has_many', foreignKey: 'channel_id'},
|
||||
};
|
||||
|
||||
/** last_post_at : The timestamp for any last post on this channel */
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type CategoryModel from '@typings/database/models/servers/category';
|
||||
import type CategoryChannelModel from '@typings/database/models/servers/category_channel';
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
import type ChannelInfoModel from '@typings/database/models/servers/channel_info';
|
||||
import type ChannelMembershipModel from '@typings/database/models/servers/channel_membership';
|
||||
@@ -33,6 +35,14 @@ import type UserModel from '@typings/database/models/servers/user';
|
||||
* 'record' and the 'raw'
|
||||
*/
|
||||
|
||||
export const isRecordCategoryEqualToRaw = (record: CategoryModel, raw: Category) => {
|
||||
return raw.id === record.id;
|
||||
};
|
||||
|
||||
export const isRecordCategoryChannelEqualToRaw = (record: CategoryChannelModel, raw: CategoryChannel) => {
|
||||
return (record.id === raw.id);
|
||||
};
|
||||
|
||||
export const isRecordRoleEqualToRaw = (record: RoleModel, raw: Role) => {
|
||||
return raw.id === record.id;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {
|
||||
isRecordCategoryEqualToRaw,
|
||||
isRecordCategoryChannelEqualToRaw,
|
||||
} from '@database/operator/server_data_operator/comparators';
|
||||
import {
|
||||
transformCategoryRecord,
|
||||
transformCategoryChannelRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/category';
|
||||
|
||||
import ServerDataOperator from '..';
|
||||
|
||||
describe('*** Operator: Category Handlers tests ***', () => {
|
||||
let operator: ServerDataOperator;
|
||||
beforeAll(async () => {
|
||||
await DatabaseManager.init(['baseHandler.test.com']);
|
||||
operator = DatabaseManager.serverDatabases['baseHandler.test.com'].operator;
|
||||
});
|
||||
|
||||
it('=> handleCategories: should write to the CATEGORY table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const categories: Category[] = [
|
||||
{
|
||||
id: 'kjlw9j1ttnxwig7tnqgebg7dtipno',
|
||||
collapsed: false,
|
||||
display_name: 'Test',
|
||||
muted: false,
|
||||
sort_order: 1,
|
||||
sorting: 'recent',
|
||||
team_id: '',
|
||||
type: 'direct_messages',
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleCategories({
|
||||
categories,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'id',
|
||||
createOrUpdateRawValues: categories,
|
||||
tableName: MM_TABLES.SERVER.CATEGORY,
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordCategoryEqualToRaw,
|
||||
transformer: transformCategoryRecord,
|
||||
});
|
||||
});
|
||||
|
||||
it('=> handleCategoryChannels: should write to the CATEGORY_CHANNEL table', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords');
|
||||
const categoryChannels: CategoryChannel[] = [
|
||||
{
|
||||
id: 'team_id-channel_id',
|
||||
category_id: 'kjlw9j1ttnxwig7tnqgebg7dtipno',
|
||||
channel_id: 'channel-id',
|
||||
sort_order: 1,
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handleCategoryChannels({
|
||||
categoryChannels,
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledTimes(1);
|
||||
expect(spyOnHandleRecords).toHaveBeenCalledWith({
|
||||
fieldName: 'id',
|
||||
createOrUpdateRawValues: categoryChannels,
|
||||
tableName: MM_TABLES.SERVER.CATEGORY_CHANNEL,
|
||||
prepareRecordsOnly: false,
|
||||
findMatchingRecordBy: isRecordCategoryChannelEqualToRaw,
|
||||
transformer: transformCategoryChannelRecord,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,89 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import DataOperatorException from '@database/exceptions/data_operator_exception';
|
||||
import {
|
||||
isRecordCategoryChannelEqualToRaw,
|
||||
isRecordCategoryEqualToRaw,
|
||||
} from '@database/operator/server_data_operator/comparators';
|
||||
import {
|
||||
transformCategoryChannelRecord,
|
||||
transformCategoryRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/category';
|
||||
import {getUniqueRawsBy} from '@database/operator/utils/general';
|
||||
|
||||
import type {
|
||||
HandleCategoryChannelArgs,
|
||||
HandleCategoryArgs,
|
||||
} from '@typings/database/database';
|
||||
import type CategoryModel from '@typings/database/models/servers/category';
|
||||
import type CategoryChannelModel from '@typings/database/models/servers/category_channel';
|
||||
|
||||
const {
|
||||
CATEGORY,
|
||||
CATEGORY_CHANNEL,
|
||||
} = MM_TABLES.SERVER;
|
||||
|
||||
export interface CategoryHandlerMix {
|
||||
handleCategoryChannels: ({categoryChannels, prepareRecordsOnly}: HandleCategoryChannelArgs) => Promise<CategoryChannelModel[]>;
|
||||
handleCategories: ({categories, prepareRecordsOnly}: HandleCategoryArgs) => Promise<CategoryModel[]>;
|
||||
}
|
||||
|
||||
const CategoryHandler = (superclass: any) => class extends superclass {
|
||||
/**
|
||||
* handleCategories: Handler responsible for the Create/Update operations occurring on the Category table from the 'Server' schema
|
||||
* @param {HandleCategoryArgs} categoriesArgs
|
||||
* @param {Category[]} categoriesArgs.categories
|
||||
* @param {boolean} categoriesArgs.prepareRecordsOnly
|
||||
* @throws DataOperatorException
|
||||
* @returns {Promise<CategoryModel[]>}
|
||||
*/
|
||||
handleCategories = async ({categories, prepareRecordsOnly = true}: HandleCategoryArgs): Promise<CategoryModel[]> => {
|
||||
if (!categories.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "categories" array has been passed to the handleCategories method',
|
||||
);
|
||||
}
|
||||
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: categories, key: 'id'});
|
||||
|
||||
return this.handleRecords({
|
||||
fieldName: 'id',
|
||||
findMatchingRecordBy: isRecordCategoryEqualToRaw,
|
||||
transformer: transformCategoryRecord,
|
||||
createOrUpdateRawValues,
|
||||
tableName: CATEGORY,
|
||||
prepareRecordsOnly,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* handleCategoryChannels: Handler responsible for the Create/Update operations occurring on the CategoryChannel table from the 'Server' schema
|
||||
* @param {HandleCategoryChannelArgs} categoriesArgs
|
||||
* @param {CategoryChannel[]} categoriesArgs.categorychannels
|
||||
* @param {boolean} categoriesArgs.prepareRecordsOnly
|
||||
* @throws DataOperatorException
|
||||
* @returns {Promise<CategoryChannelModel[]>}
|
||||
*/
|
||||
handleCategoryChannels = async ({categoryChannels, prepareRecordsOnly = true}: HandleCategoryChannelArgs): Promise<CategoryModel[]> => {
|
||||
if (!categoryChannels.length) {
|
||||
throw new DataOperatorException(
|
||||
'An empty "categoryChannels" array has been passed to the handleCategories method',
|
||||
);
|
||||
}
|
||||
|
||||
const createOrUpdateRawValues = getUniqueRawsBy({raws: categoryChannels, key: 'id'});
|
||||
|
||||
return this.handleRecords({
|
||||
fieldName: 'id',
|
||||
findMatchingRecordBy: isRecordCategoryChannelEqualToRaw,
|
||||
transformer: transformCategoryChannelRecord,
|
||||
createOrUpdateRawValues,
|
||||
tableName: CATEGORY_CHANNEL,
|
||||
prepareRecordsOnly,
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export default CategoryHandler;
|
||||
@@ -2,6 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import ServerDataOperatorBase from '@database/operator/server_data_operator/handlers';
|
||||
import CategoryHandler, {CategoryHandlerMix} from '@database/operator/server_data_operator/handlers/category';
|
||||
import ChannelHandler, {ChannelHandlerMix} from '@database/operator/server_data_operator/handlers/channel';
|
||||
import GroupHandler, {GroupHandlerMix} from '@database/operator/server_data_operator/handlers/group';
|
||||
import PostHandler, {PostHandlerMix} from '@database/operator/server_data_operator/handlers/post';
|
||||
@@ -14,9 +15,10 @@ import mix from '@utils/mix';
|
||||
import type {Database} from '@nozbe/watermelondb';
|
||||
|
||||
interface ServerDataOperator extends ServerDataOperatorBase, PostHandlerMix, PostsInChannelHandlerMix,
|
||||
PostsInThreadHandlerMix, UserHandlerMix, GroupHandlerMix, ChannelHandlerMix, TeamHandlerMix {}
|
||||
PostsInThreadHandlerMix, UserHandlerMix, GroupHandlerMix, ChannelHandlerMix, CategoryHandlerMix, TeamHandlerMix {}
|
||||
|
||||
class ServerDataOperator extends mix(ServerDataOperatorBase).with(
|
||||
CategoryHandler,
|
||||
ChannelHandler,
|
||||
GroupHandler,
|
||||
PostHandler,
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {
|
||||
transformCategoryRecord,
|
||||
transformCategoryChannelRecord,
|
||||
} from '@database/operator/server_data_operator/transformers/category';
|
||||
import {createTestConnection} from '@database/operator/utils/create_test_connection';
|
||||
import {OperationType} from '@typings/database/enums';
|
||||
|
||||
describe('*** CATEGORY Prepare Records Test ***', () => {
|
||||
it('=> transformCategoryRecord: should return an array of type CategoryModel', async () => {
|
||||
// expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'category_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await transformCategoryRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
record: undefined,
|
||||
raw: {
|
||||
id: 'kow9j1ttnxwig7tnqgebg7dtipno',
|
||||
display_name: 'Test',
|
||||
sorting: 'recent',
|
||||
sort_order: 0,
|
||||
muted: false,
|
||||
collapsed: false,
|
||||
type: 'custom',
|
||||
team_id: '',
|
||||
} as Category,
|
||||
},
|
||||
});
|
||||
|
||||
expect(preparedRecords).toBeTruthy();
|
||||
expect(preparedRecords.collection.modelClass.name).toBe('CategoryModel');
|
||||
});
|
||||
|
||||
it('=> transformCategoryChannelRecord: should return an array of type CategoryChannelModel', async () => {
|
||||
// expect.assertions(3);
|
||||
|
||||
const database = await createTestConnection({databaseName: 'category_prepare_records', setActive: true});
|
||||
expect(database).toBeTruthy();
|
||||
|
||||
const preparedRecords = await transformCategoryChannelRecord({
|
||||
action: OperationType.CREATE,
|
||||
database: database!,
|
||||
value: {
|
||||
record: undefined,
|
||||
raw: {
|
||||
id: 'team_id-channel_id',
|
||||
category_id: 'kow9j1ttnxwig7tnqgebg7dtipno',
|
||||
channel_id: 'channel_id',
|
||||
sort_order: 0,
|
||||
} as CategoryChannel,
|
||||
},
|
||||
});
|
||||
|
||||
expect(preparedRecords).toBeTruthy();
|
||||
expect(preparedRecords.collection.modelClass.name).toBe('CategoryChannelModel');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import {prepareBaseRecord} from '@database/operator/server_data_operator/transformers/index';
|
||||
import {OperationType} from '@typings/database/enums';
|
||||
|
||||
import type {TransformerArgs} from '@typings/database/database';
|
||||
import type CategoryModel from '@typings/database/models/servers/category';
|
||||
import type CategoryChannelModel from '@typings/database/models/servers/category_channel';
|
||||
|
||||
const {
|
||||
CATEGORY,
|
||||
CATEGORY_CHANNEL,
|
||||
} = MM_TABLES.SERVER;
|
||||
|
||||
/**
|
||||
* transformCategoryRecord: Prepares a record of the SERVER database 'Category' table for update or create actions.
|
||||
* @param {TransformerArgs} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<CategoryModel>}
|
||||
*/
|
||||
export const transformCategoryRecord = ({action, database, value}: TransformerArgs): Promise<CategoryModel> => {
|
||||
const raw = value.raw as Category;
|
||||
const record = value.record as CategoryModel;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
// id of category comes from server response
|
||||
const fieldsMapper = (category: CategoryModel) => {
|
||||
category._raw.id = isCreateAction ? (raw?.id ?? category.id) : record.id;
|
||||
category.displayName = raw.display_name;
|
||||
category.sorting = raw.sorting || 'recent';
|
||||
category.sortOrder = raw.sort_order === 0 ? 0 : raw.sort_order / 10; // Sort order from server is in multiples of 10
|
||||
category.muted = raw.muted ?? false;
|
||||
category.collapsed = isCreateAction ? false : record.collapsed;
|
||||
category.type = raw.type;
|
||||
category.teamId = raw.team_id;
|
||||
};
|
||||
|
||||
return prepareBaseRecord({
|
||||
action,
|
||||
database,
|
||||
tableName: CATEGORY,
|
||||
value,
|
||||
fieldsMapper,
|
||||
}) as Promise<CategoryModel>;
|
||||
};
|
||||
|
||||
/**
|
||||
* transformCategoryChannelRecord: Prepares a record of the SERVER database 'CategoryChannel' table for update or create actions.
|
||||
* @param {TransformerArgs} operator
|
||||
* @param {Database} operator.database
|
||||
* @param {RecordPair} operator.value
|
||||
* @returns {Promise<CategoryChannelModel>}
|
||||
*/
|
||||
export const transformCategoryChannelRecord = ({action, database, value}: TransformerArgs): Promise<CategoryChannelModel> => {
|
||||
const raw = value.raw as CategoryChannel;
|
||||
const record = value.record as CategoryChannelModel;
|
||||
const isCreateAction = action === OperationType.CREATE;
|
||||
|
||||
// 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 = (categoryChannel: CategoryChannelModel) => {
|
||||
categoryChannel._raw.id = isCreateAction ? (raw?.id ?? categoryChannel.id) : record.id;
|
||||
categoryChannel.channelId = raw.channel_id;
|
||||
categoryChannel.categoryId = raw.category_id;
|
||||
categoryChannel.sortOrder = raw.sort_order;
|
||||
};
|
||||
|
||||
return prepareBaseRecord({
|
||||
action,
|
||||
database,
|
||||
tableName: CATEGORY_CHANNEL,
|
||||
value,
|
||||
fieldsMapper,
|
||||
}) as Promise<CategoryChannelModel>;
|
||||
};
|
||||
@@ -4,6 +4,8 @@
|
||||
import {AppSchema, appSchema} from '@nozbe/watermelondb';
|
||||
|
||||
import {
|
||||
CategorySchema,
|
||||
CategoryChannelSchema,
|
||||
ChannelInfoSchema,
|
||||
ChannelMembershipSchema,
|
||||
ChannelSchema,
|
||||
@@ -36,6 +38,8 @@ import {
|
||||
export const serverSchema: AppSchema = appSchema({
|
||||
version: 1,
|
||||
tables: [
|
||||
CategorySchema,
|
||||
CategoryChannelSchema,
|
||||
ChannelInfoSchema,
|
||||
ChannelMembershipSchema,
|
||||
ChannelSchema,
|
||||
|
||||
21
app/database/schema/server/table_schemas/category.ts
Normal file
21
app/database/schema/server/table_schemas/category.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
// 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';
|
||||
|
||||
const {CATEGORY} = MM_TABLES.SERVER;
|
||||
|
||||
export default tableSchema({
|
||||
name: CATEGORY,
|
||||
columns: [
|
||||
{name: 'display_name', type: 'string'},
|
||||
{name: 'type', type: 'string'},
|
||||
{name: 'sort_order', type: 'number'},
|
||||
{name: 'sorting', type: 'string'},
|
||||
{name: 'muted', type: 'boolean'},
|
||||
{name: 'collapsed', type: 'boolean'},
|
||||
{name: 'team_id', type: 'string', isIndexed: true},
|
||||
],
|
||||
});
|
||||
17
app/database/schema/server/table_schemas/category_channel.ts
Normal file
17
app/database/schema/server/table_schemas/category_channel.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
// 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';
|
||||
|
||||
const {CATEGORY_CHANNEL} = MM_TABLES.SERVER;
|
||||
|
||||
export default tableSchema({
|
||||
name: CATEGORY_CHANNEL,
|
||||
columns: [
|
||||
{name: 'category_id', type: 'string', isIndexed: true},
|
||||
{name: 'channel_id', type: 'string', isIndexed: true},
|
||||
{name: 'sort_order', type: 'number'},
|
||||
],
|
||||
});
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
export {default as CategorySchema} from './category';
|
||||
export {default as CategoryChannelSchema} from './category_channel';
|
||||
export {default as ChannelInfoSchema} from './channel_info';
|
||||
export {default as ChannelMembershipSchema} from './channel_membership';
|
||||
export {default as ChannelSchema} from './channel';
|
||||
|
||||
@@ -8,6 +8,8 @@ import {MM_TABLES} from '@constants/database';
|
||||
import {serverSchema} from './index';
|
||||
|
||||
const {
|
||||
CATEGORY,
|
||||
CATEGORY_CHANNEL,
|
||||
CHANNEL,
|
||||
CHANNEL_INFO,
|
||||
CHANNEL_MEMBERSHIP,
|
||||
@@ -42,8 +44,45 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
expect(serverSchema).toEqual({
|
||||
version: 1,
|
||||
tables: {
|
||||
[CATEGORY]: {
|
||||
name: CATEGORY,
|
||||
unsafeSql: undefined,
|
||||
columns: {
|
||||
display_name: {name: 'display_name', type: 'string'},
|
||||
type: {name: 'type', type: 'string'},
|
||||
sort_order: {name: 'sort_order', type: 'number'},
|
||||
sorting: {name: 'sorting', type: 'string'},
|
||||
muted: {name: 'muted', type: 'boolean'},
|
||||
collapsed: {name: 'collapsed', type: 'boolean'},
|
||||
team_id: {name: 'team_id', type: 'string', isIndexed: true},
|
||||
},
|
||||
columnArray: [
|
||||
{name: 'display_name', type: 'string'},
|
||||
{name: 'type', type: 'string'},
|
||||
{name: 'sort_order', type: 'number'},
|
||||
{name: 'sorting', type: 'string'},
|
||||
{name: 'muted', type: 'boolean'},
|
||||
{name: 'collapsed', type: 'boolean'},
|
||||
{name: 'team_id', type: 'string', isIndexed: true},
|
||||
],
|
||||
},
|
||||
[CATEGORY_CHANNEL]: {
|
||||
name: CATEGORY_CHANNEL,
|
||||
unsafeSql: undefined,
|
||||
columns: {
|
||||
category_id: {name: 'category_id', type: 'string', isIndexed: true},
|
||||
channel_id: {name: 'channel_id', type: 'string', isIndexed: true},
|
||||
sort_order: {name: 'sort_order', type: 'number'},
|
||||
},
|
||||
columnArray: [
|
||||
{name: 'category_id', type: 'string', isIndexed: true},
|
||||
{name: 'channel_id', type: 'string', isIndexed: true},
|
||||
{name: 'sort_order', type: 'number'},
|
||||
],
|
||||
},
|
||||
[CHANNEL_INFO]: {
|
||||
name: CHANNEL_INFO,
|
||||
unsafeSql: undefined,
|
||||
columns: {
|
||||
guest_count: {name: 'guest_count', type: 'number'},
|
||||
header: {name: 'header', type: 'string'},
|
||||
@@ -61,6 +100,7 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
},
|
||||
[CHANNEL]: {
|
||||
name: CHANNEL,
|
||||
unsafeSql: undefined,
|
||||
columns: {
|
||||
create_at: {name: 'create_at', type: 'number'},
|
||||
creator_id: {name: 'creator_id', type: 'string', isIndexed: true},
|
||||
@@ -92,6 +132,7 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
},
|
||||
[CHANNEL_MEMBERSHIP]: {
|
||||
name: CHANNEL_MEMBERSHIP,
|
||||
unsafeSql: undefined,
|
||||
columns: {
|
||||
channel_id: {name: 'channel_id', type: 'string', isIndexed: true},
|
||||
user_id: {name: 'user_id', type: 'string', isIndexed: true},
|
||||
@@ -103,6 +144,7 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
},
|
||||
[CUSTOM_EMOJI]: {
|
||||
name: CUSTOM_EMOJI,
|
||||
unsafeSql: undefined,
|
||||
columns: {
|
||||
name: {name: 'name', type: 'string'},
|
||||
},
|
||||
@@ -110,6 +152,7 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
},
|
||||
[MY_CHANNEL]: {
|
||||
name: MY_CHANNEL,
|
||||
unsafeSql: undefined,
|
||||
columns: {
|
||||
last_post_at: {name: 'last_post_at', type: 'number'},
|
||||
last_viewed_at: {name: 'last_viewed_at', type: 'number'},
|
||||
@@ -133,6 +176,7 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
},
|
||||
[MY_CHANNEL_SETTINGS]: {
|
||||
name: MY_CHANNEL_SETTINGS,
|
||||
unsafeSql: undefined,
|
||||
columns: {
|
||||
notify_props: {name: 'notify_props', type: 'string'},
|
||||
},
|
||||
@@ -142,6 +186,7 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
},
|
||||
[POSTS_IN_CHANNEL]: {
|
||||
name: POSTS_IN_CHANNEL,
|
||||
unsafeSql: undefined,
|
||||
columns: {
|
||||
channel_id: {name: 'channel_id', type: 'string', isIndexed: true},
|
||||
earliest: {name: 'earliest', type: 'number'},
|
||||
@@ -155,6 +200,7 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
},
|
||||
[DRAFT]: {
|
||||
name: DRAFT,
|
||||
unsafeSql: undefined,
|
||||
columns: {
|
||||
channel_id: {name: 'channel_id', type: 'string', isIndexed: true},
|
||||
files: {name: 'files', type: 'string'},
|
||||
@@ -170,6 +216,7 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
},
|
||||
[FILE]: {
|
||||
name: FILE,
|
||||
unsafeSql: undefined,
|
||||
columns: {
|
||||
extension: {name: 'extension', type: 'string'},
|
||||
height: {name: 'height', type: 'number'},
|
||||
@@ -195,6 +242,7 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
},
|
||||
[POSTS_IN_THREAD]: {
|
||||
name: POSTS_IN_THREAD,
|
||||
unsafeSql: undefined,
|
||||
columns: {
|
||||
root_id: {name: 'root_id', type: 'string', isIndexed: true},
|
||||
earliest: {name: 'earliest', type: 'number'},
|
||||
@@ -208,6 +256,7 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
},
|
||||
[POST]: {
|
||||
name: POST,
|
||||
unsafeSql: undefined,
|
||||
columns: {
|
||||
channel_id: {name: 'channel_id', type: 'string', isIndexed: true},
|
||||
create_at: {name: 'create_at', type: 'number'},
|
||||
@@ -245,6 +294,7 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
},
|
||||
[GROUP]: {
|
||||
name: GROUP,
|
||||
unsafeSql: undefined,
|
||||
columns: {
|
||||
allow_reference: {name: 'allow_reference', type: 'boolean'},
|
||||
delete_at: {name: 'delete_at', type: 'number'},
|
||||
@@ -260,6 +310,7 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
},
|
||||
[GROUPS_CHANNEL]: {
|
||||
name: GROUPS_CHANNEL,
|
||||
unsafeSql: undefined,
|
||||
columns: {
|
||||
channel_id: {name: 'channel_id', type: 'string', isIndexed: true},
|
||||
group_id: {name: 'group_id', type: 'string', isIndexed: true},
|
||||
@@ -275,6 +326,7 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
},
|
||||
[GROUPS_TEAM]: {
|
||||
name: GROUPS_TEAM,
|
||||
unsafeSql: undefined,
|
||||
columns: {
|
||||
group_id: {name: 'group_id', type: 'string', isIndexed: true},
|
||||
team_id: {name: 'team_id', type: 'string', isIndexed: true},
|
||||
@@ -286,6 +338,7 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
},
|
||||
[GROUP_MEMBERSHIP]: {
|
||||
name: GROUP_MEMBERSHIP,
|
||||
unsafeSql: undefined,
|
||||
columns: {
|
||||
group_id: {name: 'group_id', type: 'string', isIndexed: true},
|
||||
user_id: {name: 'user_id', type: 'string', isIndexed: true},
|
||||
@@ -297,6 +350,7 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
},
|
||||
[PREFERENCE]: {
|
||||
name: PREFERENCE,
|
||||
unsafeSql: undefined,
|
||||
columns: {
|
||||
category: {name: 'category', type: 'string', isIndexed: true},
|
||||
name: {name: 'name', type: 'string'},
|
||||
@@ -312,6 +366,7 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
},
|
||||
[REACTION]: {
|
||||
name: REACTION,
|
||||
unsafeSql: undefined,
|
||||
columns: {
|
||||
create_at: {name: 'create_at', type: 'number'},
|
||||
emoji_name: {name: 'emoji_name', type: 'string'},
|
||||
@@ -327,6 +382,7 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
},
|
||||
[MY_TEAM]: {
|
||||
name: MY_TEAM,
|
||||
unsafeSql: undefined,
|
||||
columns: {
|
||||
roles: {name: 'roles', type: 'string'},
|
||||
},
|
||||
@@ -336,6 +392,7 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
},
|
||||
[ROLE]: {
|
||||
name: ROLE,
|
||||
unsafeSql: undefined,
|
||||
columns: {
|
||||
name: {name: 'name', type: 'string'},
|
||||
permissions: {name: 'permissions', type: 'string'},
|
||||
@@ -347,6 +404,7 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
},
|
||||
[SLASH_COMMAND]: {
|
||||
name: SLASH_COMMAND,
|
||||
unsafeSql: undefined,
|
||||
columns: {
|
||||
is_auto_complete: {name: 'is_auto_complete', type: 'boolean'},
|
||||
description: {name: 'description', type: 'string'},
|
||||
@@ -372,6 +430,7 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
},
|
||||
[SYSTEM]: {
|
||||
name: SYSTEM,
|
||||
unsafeSql: undefined,
|
||||
columns: {
|
||||
value: {name: 'value', type: 'string'},
|
||||
},
|
||||
@@ -381,6 +440,7 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
},
|
||||
[TEAM]: {
|
||||
name: TEAM,
|
||||
unsafeSql: undefined,
|
||||
columns: {
|
||||
is_allow_open_invite: {
|
||||
name: 'is_allow_open_invite',
|
||||
@@ -415,6 +475,7 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
},
|
||||
[TEAM_CHANNEL_HISTORY]: {
|
||||
name: TEAM_CHANNEL_HISTORY,
|
||||
unsafeSql: undefined,
|
||||
columns: {
|
||||
channel_ids: {name: 'channel_ids', type: 'string'},
|
||||
},
|
||||
@@ -424,6 +485,7 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
},
|
||||
[TEAM_MEMBERSHIP]: {
|
||||
name: TEAM_MEMBERSHIP,
|
||||
unsafeSql: undefined,
|
||||
columns: {
|
||||
team_id: {name: 'team_id', type: 'string', isIndexed: true},
|
||||
user_id: {name: 'user_id', type: 'string', isIndexed: true},
|
||||
@@ -435,6 +497,7 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
},
|
||||
[TEAM_SEARCH_HISTORY]: {
|
||||
name: TEAM_SEARCH_HISTORY,
|
||||
unsafeSql: undefined,
|
||||
columns: {
|
||||
created_at: {name: 'created_at', type: 'number'},
|
||||
display_term: {name: 'display_term', type: 'string'},
|
||||
@@ -450,6 +513,7 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
},
|
||||
[TERMS_OF_SERVICE]: {
|
||||
name: TERMS_OF_SERVICE,
|
||||
unsafeSql: undefined,
|
||||
columns: {
|
||||
accepted_at: {name: 'accepted_at', type: 'number'},
|
||||
},
|
||||
@@ -457,6 +521,7 @@ describe('*** Test schema for SERVER database ***', () => {
|
||||
},
|
||||
[USER]: {
|
||||
name: USER,
|
||||
unsafeSql: undefined,
|
||||
columns: {
|
||||
auth_service: {name: 'auth_service', type: 'string'},
|
||||
update_at: {name: 'update_at', type: 'number'},
|
||||
|
||||
Reference in New Issue
Block a user