[Gekidou] Sidebar Categories (Database only) (#5909)

This commit is contained in:
Shaz Amjad
2022-02-01 00:30:10 +11:00
committed by GitHub
parent aa84ccd808
commit fc29b4b974
24 changed files with 743 additions and 6 deletions

View File

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

View File

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

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

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

View File

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

View File

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

View File

@@ -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 */

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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