diff --git a/app/constants/database.ts b/app/constants/database.ts index 74b14641f..3fc69705b 100644 --- a/app/constants/database.ts +++ b/app/constants/database.ts @@ -35,6 +35,7 @@ export const MM_TABLES = { TEAM_SEARCH_HISTORY: 'TeamSearchHistory', TERMS_OF_SERVICE: 'TermsOfService', THREAD: 'Thread', + THREADS_IN_TEAM: 'ThreadsInTeam', THREAD_PARTICIPANT: 'ThreadParticipant', USER: 'User', }, diff --git a/app/database/manager/__mocks__/index.ts b/app/database/manager/__mocks__/index.ts index 24b4bbe2a..64e448527 100644 --- a/app/database/manager/__mocks__/index.ts +++ b/app/database/manager/__mocks__/index.ts @@ -15,7 +15,7 @@ import {CategoryModel, CategoryChannelModel, ChannelModel, ChannelInfoModel, Cha MyChannelModel, MyChannelSettingsModel, MyTeamModel, PostModel, PostsInChannelModel, PostsInThreadModel, PreferenceModel, ReactionModel, RoleModel, SlashCommandModel, SystemModel, TeamModel, TeamChannelHistoryModel, TeamMembershipModel, TeamSearchHistoryModel, - TermsOfServiceModel, ThreadModel, ThreadParticipantModel, UserModel, + TermsOfServiceModel, ThreadModel, ThreadParticipantModel, ThreadInTeamModel, UserModel, } from '@database/models/server'; import AppDataOperator from '@database/operator/app_data_operator'; import ServerDataOperator from '@database/operator/server_data_operator'; @@ -51,7 +51,7 @@ class DatabaseManager { MyChannelModel, MyChannelSettingsModel, MyTeamModel, PostModel, PostsInChannelModel, PostsInThreadModel, PreferenceModel, ReactionModel, RoleModel, SlashCommandModel, SystemModel, TeamModel, TeamChannelHistoryModel, TeamMembershipModel, TeamSearchHistoryModel, - TermsOfServiceModel, ThreadModel, ThreadParticipantModel, UserModel, + TermsOfServiceModel, ThreadModel, ThreadParticipantModel, ThreadInTeamModel, UserModel, ]; this.databaseDirectory = ''; } diff --git a/app/database/manager/index.ts b/app/database/manager/index.ts index fba3ee382..5b7d679e4 100644 --- a/app/database/manager/index.ts +++ b/app/database/manager/index.ts @@ -16,7 +16,7 @@ import {CategoryModel, CategoryChannelModel, ChannelModel, ChannelInfoModel, Cha MyChannelModel, MyChannelSettingsModel, MyTeamModel, PostModel, PostsInChannelModel, PostsInThreadModel, PreferenceModel, ReactionModel, RoleModel, SlashCommandModel, SystemModel, TeamModel, TeamChannelHistoryModel, TeamMembershipModel, TeamSearchHistoryModel, - TermsOfServiceModel, ThreadModel, ThreadParticipantModel, UserModel, + TermsOfServiceModel, ThreadModel, ThreadParticipantModel, ThreadInTeamModel, UserModel, } from '@database/models/server'; import AppDataOperator from '@database/operator/app_data_operator'; import ServerDataOperator from '@database/operator/server_data_operator'; @@ -46,7 +46,7 @@ class DatabaseManager { MyChannelModel, MyChannelSettingsModel, MyTeamModel, PostModel, PostsInChannelModel, PostsInThreadModel, PreferenceModel, ReactionModel, RoleModel, SlashCommandModel, SystemModel, TeamModel, TeamChannelHistoryModel, TeamMembershipModel, TeamSearchHistoryModel, - TermsOfServiceModel, ThreadModel, ThreadParticipantModel, UserModel, + TermsOfServiceModel, ThreadModel, ThreadParticipantModel, ThreadInTeamModel, UserModel, ]; this.databaseDirectory = Platform.OS === 'ios' ? getIOSAppGroupDetails().appGroupDatabase : `${FileSystem.documentDirectory}databases/`; diff --git a/app/database/models/server/index.ts b/app/database/models/server/index.ts index 6167ac32d..6eb389c76 100644 --- a/app/database/models/server/index.ts +++ b/app/database/models/server/index.ts @@ -26,5 +26,6 @@ export {default as TeamModel} from './team'; export {default as TeamSearchHistoryModel} from './team_search_history'; export {default as TermsOfServiceModel} from './terms_of_service'; export {default as ThreadModel} from './thread'; +export {default as ThreadInTeamModel} from './thread_in_team'; export {default as ThreadParticipantModel} from './thread_participant'; export {default as UserModel} from './user'; diff --git a/app/database/models/server/team.ts b/app/database/models/server/team.ts index e819adb1f..5583f1dbb 100644 --- a/app/database/models/server/team.ts +++ b/app/database/models/server/team.ts @@ -1,8 +1,8 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {Relation} from '@nozbe/watermelondb'; -import {children, field, immutableRelation} from '@nozbe/watermelondb/decorators'; +import {Q, Relation} from '@nozbe/watermelondb'; +import {children, field, immutableRelation, lazy} from '@nozbe/watermelondb/decorators'; import Model, {Associations} from '@nozbe/watermelondb/Model'; import {MM_TABLES} from '@constants/database'; @@ -14,6 +14,7 @@ import type SlashCommandModel from '@typings/database/models/servers/slash_comma import type TeamChannelHistoryModel from '@typings/database/models/servers/team_channel_history'; import type TeamMembershipModel from '@typings/database/models/servers/team_membership'; import type TeamSearchHistoryModel from '@typings/database/models/servers/team_search_history'; +import type ThreadModel from '@typings/database/models/servers/thread'; const { CATEGORY, @@ -24,6 +25,8 @@ const { TEAM_CHANNEL_HISTORY, TEAM_MEMBERSHIP, TEAM_SEARCH_HISTORY, + THREADS_IN_TEAM, + THREAD, } = MM_TABLES.SERVER; /** @@ -53,6 +56,9 @@ export default class TeamModel extends Model { /** A TEAM has a 1:N relationship with TEAM_SEARCH_HISTORY. A TEAM can possess multiple search histories*/ [TEAM_SEARCH_HISTORY]: {type: 'has_many', foreignKey: 'team_id'}, + + /** A TEAM has a 1:N relationship with THREADS_IN_TEAM. A TEAM can possess multiple threads */ + [THREADS_IN_TEAM]: {type: 'has_many', foreignKey: 'team_id'}, }; /** is_allow_open_invite : Boolean flag indicating if this team is open to the public */ @@ -102,4 +108,21 @@ export default class TeamModel extends Model { /** teamSearchHistories : All the searches performed on this team */ @children(TEAM_SEARCH_HISTORY) teamSearchHistories!: TeamSearchHistoryModel[]; + + /** threads : All threads belonging to a team */ + @lazy threads = this.collections.get(THREAD).query( + Q.on(THREADS_IN_TEAM, 'team_id', this.id), + ); + + /** threads : Threads list belonging to a team */ + @lazy threadsList = this.threads.extend( + Q.where('loadedInGlobalThreads', true), + Q.sortBy('last_reply_at', Q.desc), + ); + + /** unreadThreadsList : Unread threads list belonging to a team */ + @lazy unreadThreadsList = this.threads.extend( + Q.where('unread_replies', Q.gt(0)), + Q.sortBy('last_reply_at', Q.desc), + ); } diff --git a/app/database/models/server/thread.ts b/app/database/models/server/thread.ts index cd98dd93a..e28ae7990 100644 --- a/app/database/models/server/thread.ts +++ b/app/database/models/server/thread.ts @@ -8,9 +8,10 @@ import Model, {Associations} from '@nozbe/watermelondb/Model'; import {MM_TABLES} from '@constants/database'; import type PostModel from '@typings/database/models/servers/post'; +import type ThreadInTeamModel from '@typings/database/models/servers/thread_in_team'; import type ThreadParticipantModel from '@typings/database/models/servers/thread_participant'; -const {POST, THREAD, THREAD_PARTICIPANT} = MM_TABLES.SERVER; +const {POST, THREAD, THREAD_PARTICIPANT, THREADS_IN_TEAM} = MM_TABLES.SERVER; /** * The Thread model contains thread information of a post. @@ -27,6 +28,9 @@ export default class ThreadModel extends Model { /** A THREAD can have multiple THREAD_PARTICIPANT. (relationship is 1:N)*/ [THREAD_PARTICIPANT]: {type: 'has_many', foreignKey: 'thread_id'}, + + /** A THREAD can have multiple THREADS_IN_TEAM. (relationship is 1:N)*/ + [THREADS_IN_TEAM]: {type: 'has_many', foreignKey: 'team_id'}, }; /** last_reply_at : The timestamp of when user last replied to the thread. */ @@ -53,11 +57,15 @@ export default class ThreadModel extends Model { /** participants : All the participants associated with this Thread */ @children(THREAD_PARTICIPANT) participants!: Query; + /** threadsInTeam : All the threadsInTeam associated with this Thread */ + @children(THREADS_IN_TEAM) threadsInTeam!: Query; + /** post : The root post of this thread */ @immutableRelation(POST, 'id') post!: Relation; async destroyPermanently() { await this.participants.destroyAllPermanently(); + await this.threadsInTeam.destroyAllPermanently(); super.destroyPermanently(); } } diff --git a/app/database/models/server/thread_in_team.ts b/app/database/models/server/thread_in_team.ts new file mode 100644 index 000000000..beaf8533a --- /dev/null +++ b/app/database/models/server/thread_in_team.ts @@ -0,0 +1,42 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {Relation} from '@nozbe/watermelondb'; +import {field, immutableRelation} from '@nozbe/watermelondb/decorators'; +import Model, {Associations} from '@nozbe/watermelondb/Model'; + +import {MM_TABLES} from '@constants/database'; + +import type TeamModel from '@typings/database/models/servers/team'; +import type ThreadModel from '@typings/database/models/servers/thread'; + +const {TEAM, THREAD, THREADS_IN_TEAM} = MM_TABLES.SERVER; + +/** + * ThreadInTeam model helps us to combine adjacent threads together without leaving + * gaps in between for an efficient user reading experience for threads. + */ +export default class ThreadInTeamModel extends Model { + /** table (name) : ThreadsInTeam */ + static table = THREADS_IN_TEAM; + + /** associations : Describes every relationship to this table. */ + static associations: Associations = { + + /** A TEAM can have many THREADS_IN_TEAM. (relationship is N:1)*/ + [TEAM]: {type: 'belongs_to', key: 'team_id'}, + + /** A THREAD can have many THREADS_IN_TEAM. (relationship is N:1)*/ + [THREAD]: {type: 'belongs_to', key: 'team_id'}, + }; + + /** thread_id: Associated thread identifier */ + @field('thread_id') threadId!: string; + + /** team_id: Associated team identifier */ + @field('team_id') teamId!: string; + + @immutableRelation(THREAD, 'thread_id') thread!: Relation; + + @immutableRelation(TEAM, 'team_id') team!: Relation; +} diff --git a/app/database/operator/server_data_operator/handlers/thread.test.ts b/app/database/operator/server_data_operator/handlers/thread.test.ts index f4311b98c..28ac4976f 100644 --- a/app/database/operator/server_data_operator/handlers/thread.test.ts +++ b/app/database/operator/server_data_operator/handlers/thread.test.ts @@ -3,7 +3,7 @@ import DatabaseManager from '@database/manager'; import {isRecordThreadEqualToRaw} from '@database/operator/server_data_operator/comparators'; -import {transformThreadRecord, transformThreadParticipantRecord} from '@database/operator/server_data_operator/transformers/thread'; +import {transformThreadRecord, transformThreadParticipantRecord, transformThreadInTeamRecord} from '@database/operator/server_data_operator/transformers/thread'; import ServerDataOperator from '..'; @@ -27,10 +27,13 @@ describe('*** Operator: Thread Handlers tests ***', () => { operator = DatabaseManager.serverDatabases['baseHandler.test.com'].operator; }); - it('=> HandleThreads: should write to the the Thread & ThreadParticipant table', async () => { + it('=> HandleThreads: should write to the the Thread & ThreadParticipant & ThreadsInTeam tables', async () => { + expect.assertions(4); + const spyOnBatchOperation = jest.spyOn(operator, 'batchRecords'); const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords'); const spyOnHandleThreadParticipants = jest.spyOn(operator, 'handleThreadParticipants'); + const spyOnHandleThreadInTeam = jest.spyOn(operator, 'handleThreadInTeam'); const threads = [ { @@ -47,7 +50,8 @@ describe('*** Operator: Thread Handlers tests ***', () => { }, ] as Thread[]; - await operator.handleThreads({threads, prepareRecordsOnly: false}); + const threadsMap = {team_id_1: threads}; + await operator.handleThreads({threads, prepareRecordsOnly: false, teamId: 'team_id_1'}); expect(spyOnHandleRecords).toHaveBeenCalledWith({ findMatchingRecordBy: isRecordThreadEqualToRaw, @@ -70,11 +74,18 @@ describe('*** Operator: Thread Handlers tests ***', () => { prepareRecordsOnly: true, }); + expect(spyOnHandleThreadInTeam).toHaveBeenCalledWith({ + threadsMap, + prepareRecordsOnly: true, + }); + // Only one batch operation for both tables expect(spyOnBatchOperation).toHaveBeenCalledTimes(1); }); it('=> HandleThreadParticipants: should write to the the ThreadParticipant table', async () => { + expect.assertions(1); + const spyOnPrepareRecords = jest.spyOn(operator, 'prepareRecords'); const threadsParticipants = [ @@ -97,4 +108,70 @@ describe('*** Operator: Thread Handlers tests ***', () => { tableName: 'ThreadParticipant', }); }); + + it('=> HandleThreadInTeam: should write to the the ThreadsInTeam table', async () => { + expect.assertions(1); + + const spyOnPrepareRecords = jest.spyOn(operator, 'prepareRecords'); + + const team1Threads = [ + { + id: 'thread-1', + reply_count: 2, + last_reply_at: 123, + last_viewed_at: 123, + participants: [{ + id: 'user-1', + }], + is_following: true, + unread_replies: 0, + unread_mentions: 0, + }, + { + id: 'thread-2', + reply_count: 2, + last_reply_at: 123, + last_viewed_at: 123, + participants: [{ + id: 'user-1', + }], + is_following: true, + unread_replies: 0, + unread_mentions: 0, + }, + ] as Thread[]; + + const team2Threads = [ + { + id: 'thread-2', + reply_count: 2, + last_reply_at: 123, + last_viewed_at: 123, + participants: [{ + id: 'user-1', + }], + is_following: true, + unread_replies: 0, + unread_mentions: 0, + }, + ] as Thread[]; + + const threadsMap = { + team_id_1: team1Threads, + team_id_2: team2Threads, + }; + + await operator.handleThreadInTeam({threadsMap, prepareRecordsOnly: false}); + + expect(spyOnPrepareRecords).toHaveBeenCalledWith({ + createRaws: [{ + raw: {team_id: 'team_id_1', thread_id: 'thread-2'}, + }, { + raw: {team_id: 'team_id_2', thread_id: 'thread-2'}, + }], + transformer: transformThreadInTeamRecord, + updateRaws: [], + tableName: 'ThreadsInTeam', + }); + }); }); diff --git a/app/database/operator/server_data_operator/handlers/thread.ts b/app/database/operator/server_data_operator/handlers/thread.ts index 99830daa3..ddb63a2a7 100644 --- a/app/database/operator/server_data_operator/handlers/thread.ts +++ b/app/database/operator/server_data_operator/handlers/thread.ts @@ -15,6 +15,7 @@ import {sanitizeThreadParticipants} from '@database/operator/utils/thread'; import type {HandleThreadsArgs, HandleThreadParticipantsArgs} from '@typings/database/database'; import type ThreadModel from '@typings/database/models/servers/thread'; +import type ThreadInTeamModel from '@typings/database/models/servers/thread_in_team'; import type ThreadParticipantModel from '@typings/database/models/servers/thread_participant'; const { @@ -35,7 +36,7 @@ const ThreadHandler = (superclass: any) => class extends superclass { * @param {boolean | undefined} handleThreads.prepareRecordsOnly * @returns {Promise} */ - handleThreads = async ({threads, prepareRecordsOnly = false}: HandleThreadsArgs): Promise => { + handleThreads = async ({threads, teamId, prepareRecordsOnly = false}: HandleThreadsArgs): Promise => { if (!threads.length) { throw new DataOperatorException( 'An empty "threads" array has been passed to the handleThreads method', @@ -78,6 +79,12 @@ const ThreadHandler = (superclass: any) => class extends superclass { const threadParticipants = (await this.handleThreadParticipants({threadsParticipants, prepareRecordsOnly: true})) as ThreadParticipantModel[]; batch.push(...threadParticipants); + const threadsInTeam = await this.handleThreadInTeam({ + threadsMap: {[teamId]: threads}, + prepareRecordsOnly: true, + }) as ThreadInTeamModel[]; + batch.push(...threadsInTeam); + if (batch.length && !prepareRecordsOnly) { await this.batchRecords(batch); } diff --git a/app/database/operator/server_data_operator/handlers/thread_in_team.ts b/app/database/operator/server_data_operator/handlers/thread_in_team.ts new file mode 100644 index 000000000..0c07858fd --- /dev/null +++ b/app/database/operator/server_data_operator/handlers/thread_in_team.ts @@ -0,0 +1,62 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {Q, Database} from '@nozbe/watermelondb'; + +import {MM_TABLES} from '@constants/database'; +import {transformThreadInTeamRecord} from '@database/operator/server_data_operator/transformers/thread'; +import {getRawRecordPairs} from '@database/operator/utils/general'; + +import type {HandleThreadInTeamArgs} from '@typings/database/database'; +import type ThreadInTeamModel from '@typings/database/models/servers/thread_in_team'; + +export interface ThreadInTeamHandlerMix { + handleThreadInTeam: ({threadsMap, prepareRecordsOnly}: HandleThreadInTeamArgs) => Promise; +} + +const {THREADS_IN_TEAM} = MM_TABLES.SERVER; + +const ThreadInTeamHandler = (superclass: any) => class extends superclass { + handleThreadInTeam = async ({threadsMap, prepareRecordsOnly = false}: HandleThreadInTeamArgs): Promise => { + if (!Object.keys(threadsMap).length) { + return []; + } + + const create: ThreadInTeam[] = []; + const teamIds = Object.keys(threadsMap); + for await (const teamId of teamIds) { + const chunks = await (this.database as Database).get(THREADS_IN_TEAM).query( + Q.where('team_id', teamId), + ).fetch(); + + for (const thread of threadsMap[teamId]) { + const exists = chunks.some((threadInTeam) => { + return threadInTeam.threadId === thread.id; + }); + + if (!exists) { + // create chunk + create.push({ + thread_id: thread.id, + team_id: teamId, + }); + } + } + } + + const threadsInTeam = (await this.prepareRecords({ + createRaws: getRawRecordPairs(create), + updateRaws: [], + transformer: transformThreadInTeamRecord, + tableName: THREADS_IN_TEAM, + })) as ThreadInTeamModel[]; + + if (threadsInTeam?.length && !prepareRecordsOnly) { + await this.batchRecords(threadsInTeam); + } + + return threadsInTeam; + }; +}; + +export default ThreadInTeamHandler; diff --git a/app/database/operator/server_data_operator/index.ts b/app/database/operator/server_data_operator/index.ts index 87b780d1f..28b6d9e04 100644 --- a/app/database/operator/server_data_operator/index.ts +++ b/app/database/operator/server_data_operator/index.ts @@ -10,13 +10,14 @@ import PostsInThreadHandler, {PostsInThreadHandlerMix} from '@database/operator/ import ReactionHander, {ReactionHandlerMix} from '@database/operator/server_data_operator/handlers/reaction'; import TeamHandler, {TeamHandlerMix} from '@database/operator/server_data_operator/handlers/team'; import ThreadHandler, {ThreadHandlerMix} from '@database/operator/server_data_operator/handlers/thread'; +import ThreadInTeamHandler, {ThreadInTeamHandlerMix} from '@database/operator/server_data_operator/handlers/thread_in_team'; import UserHandler, {UserHandlerMix} from '@database/operator/server_data_operator/handlers/user'; import mix from '@utils/mix'; import type {Database} from '@nozbe/watermelondb'; interface ServerDataOperator extends ServerDataOperatorBase, PostHandlerMix, PostsInChannelHandlerMix, - PostsInThreadHandlerMix, ReactionHandlerMix, UserHandlerMix, ChannelHandlerMix, CategoryHandlerMix, TeamHandlerMix, ThreadHandlerMix {} + PostsInThreadHandlerMix, ReactionHandlerMix, UserHandlerMix, ChannelHandlerMix, CategoryHandlerMix, TeamHandlerMix, ThreadHandlerMix, ThreadInTeamHandlerMix {} class ServerDataOperator extends mix(ServerDataOperatorBase).with( CategoryHandler, @@ -27,6 +28,7 @@ class ServerDataOperator extends mix(ServerDataOperatorBase).with( ReactionHander, TeamHandler, ThreadHandler, + ThreadInTeamHandler, UserHandler, ) { // eslint-disable-next-line no-useless-constructor diff --git a/app/database/operator/server_data_operator/transformers/thread.ts b/app/database/operator/server_data_operator/transformers/thread.ts index 6b93bfd17..c42f3ea63 100644 --- a/app/database/operator/server_data_operator/transformers/thread.ts +++ b/app/database/operator/server_data_operator/transformers/thread.ts @@ -7,11 +7,13 @@ import {OperationType} from '@typings/database/enums'; import type {TransformerArgs} from '@typings/database/database'; import type ThreadModel from '@typings/database/models/servers/thread'; +import type ThreadInTeamModel from '@typings/database/models/servers/thread_in_team'; import type ThreadParticipantModel from '@typings/database/models/servers/thread_participant'; const { THREAD, THREAD_PARTICIPANT, + THREADS_IN_TEAM, } = MM_TABLES.SERVER; /** @@ -71,3 +73,20 @@ export const transformThreadParticipantRecord = ({action, database, value}: Tran fieldsMapper, }) as Promise; }; + +export const transformThreadInTeamRecord = ({action, database, value}: TransformerArgs): Promise => { + const raw = value.raw as ThreadInTeam; + + const fieldsMapper = (threadInTeam: ThreadInTeamModel) => { + threadInTeam.threadId = raw.thread_id; + threadInTeam.teamId = raw.team_id; + }; + + return prepareBaseRecord({ + action, + database, + tableName: THREADS_IN_TEAM, + value, + fieldsMapper, + }) as Promise; +}; diff --git a/app/database/schema/server/index.ts b/app/database/schema/server/index.ts index 21af62733..a09e076f6 100644 --- a/app/database/schema/server/index.ts +++ b/app/database/schema/server/index.ts @@ -29,6 +29,7 @@ import { TeamSearchHistorySchema, TermsOfServiceSchema, ThreadSchema, + ThreadInTeamSchema, ThreadParticipantSchema, UserSchema, } from './table_schemas'; @@ -61,6 +62,7 @@ export const serverSchema: AppSchema = appSchema({ TeamSearchHistorySchema, TermsOfServiceSchema, ThreadSchema, + ThreadInTeamSchema, ThreadParticipantSchema, UserSchema, ], diff --git a/app/database/schema/server/table_schemas/index.ts b/app/database/schema/server/table_schemas/index.ts index aa115be47..87c9b9221 100644 --- a/app/database/schema/server/table_schemas/index.ts +++ b/app/database/schema/server/table_schemas/index.ts @@ -27,4 +27,5 @@ export {default as TeamSearchHistorySchema} from './team_search_history'; export {default as TermsOfServiceSchema} from './terms_of_service'; export {default as ThreadSchema} from './thread'; export {default as ThreadParticipantSchema} from './thread_participant'; +export {default as ThreadInTeamSchema} from './thread_in_team'; export {default as UserSchema} from './user'; diff --git a/app/database/schema/server/table_schemas/thread_in_team.ts b/app/database/schema/server/table_schemas/thread_in_team.ts new file mode 100644 index 000000000..aa0fb3d80 --- /dev/null +++ b/app/database/schema/server/table_schemas/thread_in_team.ts @@ -0,0 +1,16 @@ +// 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 {THREADS_IN_TEAM} = MM_TABLES.SERVER; + +export default tableSchema({ + name: THREADS_IN_TEAM, + columns: [ + {name: 'team_id', type: 'string', isIndexed: true}, + {name: 'thread_id', type: 'string', isIndexed: true}, + ], +}); diff --git a/app/database/schema/server/test.ts b/app/database/schema/server/test.ts index 971d933a8..a1b6c12c7 100644 --- a/app/database/schema/server/test.ts +++ b/app/database/schema/server/test.ts @@ -34,6 +34,7 @@ const { TERMS_OF_SERVICE, THREAD, THREAD_PARTICIPANT, + THREADS_IN_TEAM, USER, } = MM_TABLES.SERVER; @@ -495,6 +496,18 @@ describe('*** Test schema for SERVER database ***', () => { {name: 'user_id', type: 'string', isIndexed: true}, ], }, + [THREADS_IN_TEAM]: { + name: THREADS_IN_TEAM, + unsafeSql: undefined, + columns: { + team_id: {name: 'team_id', type: 'string', isIndexed: true}, + thread_id: {name: 'thread_id', type: 'string', isIndexed: true}, + }, + columnArray: [ + {name: 'team_id', type: 'string', isIndexed: true}, + {name: 'thread_id', type: 'string', isIndexed: true}, + ], + }, [USER]: { name: USER, unsafeSql: undefined, diff --git a/types/database/database.d.ts b/types/database/database.d.ts index d8a1e13f3..c6075e4f8 100644 --- a/types/database/database.d.ts +++ b/types/database/database.d.ts @@ -91,6 +91,7 @@ export type HandlePostsArgs = { export type HandleThreadsArgs = { threads: Thread[]; prepareRecordsOnly?: boolean; + teamId: string; }; export type HandleThreadParticipantsArgs = { @@ -98,6 +99,11 @@ export type HandleThreadParticipantsArgs = { threadsParticipants: ParticipantsPerThread[]; }; +export type HandleThreadInTeamArgs = { + threadsMap: Record; + prepareRecordsOnly?: boolean; +}; + export type SanitizeReactionsArgs = { database: Database; post_id: string; diff --git a/types/database/models/servers/thread_in_team.d.ts b/types/database/models/servers/thread_in_team.d.ts new file mode 100644 index 000000000..9e35033a7 --- /dev/null +++ b/types/database/models/servers/thread_in_team.d.ts @@ -0,0 +1,33 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {Relation} from '@nozbe/watermelondb'; +import Model, {Associations} from '@nozbe/watermelondb/Model'; + +import type TeamModel from './team'; +import type ThreadModel from './thread'; + +/** + * ThreadInTeam model helps us to determine for which team we have loaded which threads + * this was done mostly because of threads belonging in DM/GM channels, + * so they belong in multiple teams. + */ +export default class ThreadInTeamModel extends Model { + /** table (name) : ThreadsInTeam */ + static table: string; + + /** associations : Describes every relationship to this table. */ + static associations: Associations; + + /** thread_id: Associated thread identifier */ + threadId: string; + + /** teamId: Associated thread identifier */ + teamId: string; + + /** thread : The related record to the parent Thread model */ + thread: Relation; + + /** team : The related record to the parent Team model */ + team: Relation; +} diff --git a/types/database/raw_values.d.ts b/types/database/raw_values.d.ts index 5683f94a4..703ca7872 100644 --- a/types/database/raw_values.d.ts +++ b/types/database/raw_values.d.ts @@ -82,6 +82,11 @@ type TermsOfService = { text: string; }; +type ThreadInTeam = { + thread_id: string; + team_id: string; +}; + type RawValue = | AppInfo | Category @@ -109,6 +114,7 @@ type RawValue = | TeamSearchHistory | TermsOfService | Thread + | ThreadInTeam | ThreadParticipant | UserProfile | Pick