From 3279cf808b4e6de5c312049a563ea14a4a80d3b7 Mon Sep 17 00:00:00 2001 From: Avinash Lingaloo Date: Fri, 2 Apr 2021 00:55:33 +0400 Subject: [PATCH] MM_33225 [v2] DataOperator Group section (#5258) * MM_30475 : ADDED default schema * MM_30475 : ADDED todo for field 'value' of default/Global entity * MM_30476 : Created schema for SERVER DB * MM_30476 : Server model [ IN PROGRESS ] * MM_30476 : Including types for group, groups_in_channel and role * MM_30476 : ADDED models for Group - @typings absolute path has been added to the tsconfig.json * MM_30476 : ADDED typings to current models * MM_30476 : ADDED typings to current models * MM_30476 : ADDED models related to TEAM section of the ERD * MM_30476 : ADDED models for User section of the ERD * MM_30476 : ADDED models for POST section of the ERD * MM_30476 : ADDED models for Channel section of the ERD * MM_30475 : Updated typings and references to MM_TABLES * MM_30476 : Verified all field names * MM_30476 : Verified every table associations * MM_30476 : Verified all relation fields * MM_30476 : Updated primary id of the main models We will override the wdb id at component level when we create a new records. This involves the models : channel, group, post, team and user. * MM_30476 : Including 1:1 relationship amongs some entities * MM_30476 : ADDED Schema Managers * The migration array will hold all the migration steps. * The initial app release (e.g. v2 )will have an empty array and subsequent releases (e.g. v2.1 ) will have the steps listed in that array. * On initialization, the database will perform the migration to accomodate for new columns/tables creation and while it will conserve the mobile phone's data, it will also make it conform to this new schema. * If a migration fails, the migration process will rollback any changes. This migration will be thoroughly tested in development before pushing it live. * Revert "MM_30476 : ADDED Schema Managers" This reverts commit a505bd5e11124e8eb8f258ce8dbb8168a535f7ae. * MM_30478 : Converted schema_manager into a function * MM_30478 : Updated schema manager and included patch for wdb * MM_30478: Updated watermelondb patch package * MM_30478 : Update function create_schema_manager to createSqliteAdaptorOptions * MM_30476 : Update constant name to reflect directory name * MM_30476 : Updated msgCount from my_channel model to message_count in server schema * MM_30482 : Added tests for schema_manager * MM_30482 : Database Manager [ IN PROGRESS ] * MM_30478 : Returning an sqliteAdapter instead of an object * MM_30476 : Apply suggestions from code review Co-authored-by: Elias Nahum * MM_30476 : Updated all imports as per instruction. * MM_30476 : Shortening object chains by destructuring * MM_30476 : Updated schema file structure * MM_30476 : Prettifying @typings folder * MM_30476 : Removing useless ids * MM_30476 : Prettify imports for decorators * MM_30476 : ADDED documentations and lazy queries to Channel and Channel_Info * MM_30476 : ADDED documentations for default schema * MM_30476 : Documentation [ IN PROGRESS ] - Following JSDoc syntax for single line comment - Removed redundant fields in the 'membership' tables and left only the @relation records. * MM_30476 : Documentations [ IN PROGRESS ] * MM_30476 : Documentations [ IN PROGRESS ] * MM_30476 : Documentations [ IN PROGRESS ] * MM_30476 : Documentations [ IN PROGRESS] Updated 1) my_team and team, 2) my_channel and channel, to each have 1:1 relationship with one another * MM_30476 : Updated all Typescript definitions * MM_30476 :Updated @relation to @immutableRelation * MM_30476 : Updated description for previous_post_id * MM_30478 : Updated patch package for wdb module * MM_30478: DB Manager [IN PROGRESS ] * MM_30478: DB Manager [IN PROGRESS] * MM_30478: DB Manager [IN PROGRESS] * MM_30478 : DB Manager [IN PROGRESS] * MM_30478 : Deleting .db file on iOS * MM_30478: Successfully deleting .db files and directory on iOS side * MM_30478 : Update definition for default/global * MM_30478 : Updated all models * MM_30478 : Doing a bit of house cleaning * MM_30478: Record of new server connection added to default/servers db * TS Definitely Typed Assignment issue is now FIXED * MM_30478 : TS Definitely Typed Assignment \n Removed all the constructors but error still in editor tabs. But this time the app is not crashing * MM_30478 : Attempt 1 [SUCCESSFUL] * MM_30478 : Removing useDefineForClassFields * MM_30478 : Retrieving the servers in a list + Improved the DB Manager and Babel config * MM_30478 : Updated babel.config.js * MM_30478 : Minor UI correction * MM_30478 : Jest and Typescript configuration * MM_30478 : A bit of housekeeping * MM_30478 : Installed WDB on Android * MM_30478 : Deletes new server record from default DB * MM_30478 : Returns subset of server db instances * MM_30478 : Code clean up * MM_30478 : Code clean up on db manager * MM_30478 : House keeping + Patch for WDB * MM_30478 : Android - Saving & Deleting in FilesDir [COMPLETED] * MM_30478 : Code clean up * MM_30478 : Code clean up * MM_30478 : Code clean up * MM_30478 : Test successful on Android device * MM_30478 : Rolling back change to jest.config.js * MM_30478 : Updated test to test_integration * MM_30478 : Fix imports * MM_30478 : Refactored the manual testscript * MM_30478 : Renamed database manager test file * MM_30478 : Code clean up * MM_30478 : Updated manual test file with a note. * MM_30482 : DataOperator [ IN PROGRESS ] * MM_30482 : DataOperator - setting up the factory [ IN PROGRESS ] * MM_30482: Code refactoring * MM_30482 : DataOperator - setting up the factory [ IN PROGRESS ] * MM_30482 : DataOperator - code clean up [ IN PROGRESS ] * MM_30482 : Minor code clean up * MM_30478 : Fixed JEST issue with TS * MM_30478 : Fixed JEST issue with TS * MM_30478 : Fixed JEST issue with TS * MM_30478 : Implementing JEST test cases * MM_30478 : Implementing JEST last test cases * MM_30478 : Jest fixing ts errors * MM_30478 : Database Manager Jest testing [ IN PROGRESS ] * MM_30482 - Fixing DataOperator [ IN PROGRESS ] * MM_30482 : Code clean up * MM_30482 - Creates multiple records [ IN PROGRESS ] * MM_30482 - Creates multiple records [ IN PROGRESS ] * MM_30482 : Update operation [ COMPLETED ] * MM_30482 : Code clean up * MM_30482 : Updated TS for Data Operator * Update mobile v2 detox deps * MM_30482 : Added factories for all isolated tables * MM_30482 : Refactored TS * MM_30482 : Refactored base factory * MM_30482 : Updated JSDoc for operateBaseRecord - Delete CASE * MM_30482 : Implementing test for Data Operator * MM_30482 : Completed tests for all isolated tables * MM_30482 : Renamed entity_factory into operators * MM_30482 : Fix all imports * MM_30482 : Update multiple records * MM_30482 : Edge case for existing records ( update instead of create ) * MM_30482 : Edge case - create instead of update * MM_30482 : Code clean up * MM_30482 : Code clean up * MM_30482 : Code clean up * MM_30482 : Code clean up * Update app/database/admin/data_operator/operators.ts Co-authored-by: Joseph Baylon * Update app/database/admin/data_operator/operators.ts Co-authored-by: Joseph Baylon * Update app/database/admin/data_operator/operators.ts Co-authored-by: Joseph Baylon * MM_30482 : Imposing usage of correct table name for isolated entities * MM_30482 : Code improvement as per Joseph reviews * MM_30482 : Updated tests to validate choice of operator service wrt tableName * MM_30482 : Updated PR as per suggestions * MM_30482 : Updated comments to follow jsdoc conventions * MM_33223 : Renamed DBInstance to DatabaseInstance * MM_33223 : ADDED Prettier * MM_33223 - Prettier formatting * MM_33223 : Prettier formatting * MM_33223 - Post section [ in progress ] * MM_33223 : PostsInThread [99% completed ] * MM_33223: Reaction entity completed * MM_33223: Added Reaction to the Post * MM_33223 : Refactored reactions utils * MM_33223 : Added previous post id to all posts * MM_33223 : Added File Metadata * MM_33223 : Code clean up * MM_33223 : Added PostMetadata * MM_33223 : Added Draft * MM_33223 - Removed Prettier * MM_33223 - Undo files changes due to Prettier * MM_33223 : Making use of MM eslint plugins * MM_33223 : PostsInChannel [ IN PROGRESS ] * MM_33223 : Including update_at in Post schema * MM_33223: Code clean up * MM_33223: Code clean up * MM_33223 : Code clean up * MM_33223: Testing Reaction [IN PROGRESS] * MM_33223 : Updated typings for RawCustomEmoji in Reactions * MM_33223 : Refactored DataOperator test * MM_33223 : Jest - handleReactions - Completed * MM_33223 : Jest - HandleDraft - Completed * MM_33223 : Jest - HandleFiles - Completed * MM_33223 : Refactored DataOperator-PostMetadata * MM_33223 : Jest - HandlePostMetadata - Completed * MM_33223 : Refactored posts into ordered and unordered * MM_33223 : Refactoring + Jest Utils [ IN PROGRESS ] * MM_33223 - Jest Utils - Completed * MM_33223 : Jest - Remaining operators - Completed * MM_33223 : Jest - Handler PostsInThread - Completed * MM_33223 : Jest - HandlePostsInChannel - Completed * MM_33223 : Refactored DataOperator class * MM_33223 : DataOperator test clean up * MM_33223 : DataOperator code clean up * MM_33223 : Jest - HandlePosts - Completed * MM_33223: JSDoc - Operators - Completed * MM_33223 : Refactoring file types.ts * MM_33223 : Refactored import statements * MM_33223 : Added @database alias * MM_33223 : Added missing JSDoc * MM_33223 : Minor code clean up * MM_33223 : Lint fixed * MM_33223 : Disable eslint rules for Notification * MM_33223 : Disable eslint rule for screens * Update app/database/admin/data_operator/index.ts Co-authored-by: Miguel Alatzar * Apply suggestions from code review Co-authored-by: Miguel Alatzar * Apply suggestions from code review Co-authored-by: Miguel Alatzar * MM_33223 : Update data_operatator as per suggestion * MM_33226: Removed optType * MM_33226 : ADDED User Handler+Operator+Jest, Included update_at field for User * MM_33226 : Preference entity - Completed * MM_33226 : Team Membership entity - Completed * MM_33226 : Team Membership - Jest - Completed * MM_33226 : Removing duplicates for TeamMembership and Preferences * MM_33226 : Refactored Custom Emojis to remove duplicates * MM_33226 : Group Membership - Completed * MM_33226 : ChannelMembership - Completed * MM_33226 : Refactored some handlers whose response have no Ids * MM_33226 : Refactoring - in progress * MM_33226 : Refactoring - in progress * MM_33226 : Refactoring - in progress * MM_33226 : Code clean up * MM_33226 : Polishing Operator tests * MM_33226 : Removing redundant test cases * MM_33226 : Polishing Operators * MM_33226 : Testing for duplicate post id in Raw values * MM_33226 : Including some error-throwing in the Database Manager * MM_33226 : Merged in DataOperator/Post-section * MM_33226 : Fixing the merging issues * MM_33226 : fixing merge issues * MM_33226 : Code polishing * MM_33226 : Enabling user notify props comment * MM_33226 : Correcting type casting * MM_33226 : Correcting data operators * MM_33225 : Group - Completed * MM_33225 : GroupsInTeam - Completed * MM_33225 : GroupsInChannel - Completed * MM_33226 : Corrections * MM_33225: Adding a todo comment * MM_33225 : Added a todo for the Group section operator * MM_33226 : Code clean up * MM_33226 : Rename oneOfField to fieldName * MM_33226 : Renaming comparators to Boolean name and oneOfField to fieldName * MM_33226 : Putting back custom emoji into handleIsolatedEntity * MM_33226 : Comparing simple arrays * MM_33226 : Renaming DiscardDuplicates to ProcessInputs * MM_33226 : Sort imports * MM_33225 : Renamed some Group comparators * MM_33226 : Types clean up * MM_33225 : Types clean up * MM_33225 : Further clean up Co-authored-by: Elias Nahum Co-authored-by: Avinash Lingaloo <> Co-authored-by: Joseph Baylon Co-authored-by: Miguel Alatzar --- .../admin/data_operator/comparators/index.ts | 18 +++ .../admin/data_operator/handlers/index.ts | 82 +++++++++- .../admin/data_operator/handlers/test.ts | 74 +++++++++ .../admin/data_operator/operators/index.ts | 151 ++++++++++++++---- .../admin/data_operator/operators/test.ts | 98 +++++++++++- types/database/database.d.ts | 75 ++++++--- 6 files changed, 447 insertions(+), 51 deletions(-) diff --git a/app/database/admin/data_operator/comparators/index.ts b/app/database/admin/data_operator/comparators/index.ts index 7d536a2f6..a498343a6 100644 --- a/app/database/admin/data_operator/comparators/index.ts +++ b/app/database/admin/data_operator/comparators/index.ts @@ -11,7 +11,10 @@ import { RawCustomEmoji, RawDraft, RawGlobal, + RawGroup, RawGroupMembership, + RawGroupsInTeam, + RawGroupsInChannel, RawPost, RawPreference, RawRole, @@ -23,7 +26,10 @@ import { } from '@typings/database/database'; import Draft from '@typings/database/draft'; import Global from '@typings/database/global'; +import Group from '@typings/database/group'; import GroupMembership from '@typings/database/group_membership'; +import GroupsInChannel from '@typings/database/groups_in_channel'; +import GroupsInTeam from '@typings/database/groups_in_team'; import Post from '@typings/database/post'; import Preference from '@typings/database/preference'; import Servers from '@typings/database/servers'; @@ -102,3 +108,15 @@ export const isRecordGroupMembershipEqualToRaw = (record: GroupMembership, raw: export const isRecordChannelMembershipEqualToRaw = (record: ChannelMembership, raw: RawChannelMembership) => { return raw.user_id === record.userId && raw.channel_id === record.channelId; }; + +export const isRecordGroupEqualToRaw = (record: Group, raw: RawGroup) => { + return raw.name === record.name && raw.display_name === record.displayName; +}; + +export const isRecordGroupsInTeamEqualToRaw = (record: GroupsInTeam, raw: RawGroupsInTeam) => { + return raw.team_id === record.teamId && raw.group_id === record.groupId; +}; + +export const isRecordGroupsInChannelEqualToRaw = (record: GroupsInChannel, raw: RawGroupsInChannel) => { + return raw.channel_id === record.channelId && raw.group_id === record.groupId; +}; diff --git a/app/database/admin/data_operator/handlers/index.ts b/app/database/admin/data_operator/handlers/index.ts index 185ddec9c..96d24ff35 100644 --- a/app/database/admin/data_operator/handlers/index.ts +++ b/app/database/admin/data_operator/handlers/index.ts @@ -11,7 +11,10 @@ import { isRecordCustomEmojiEqualToRaw, isRecordDraftEqualToRaw, isRecordGlobalEqualToRaw, + isRecordGroupEqualToRaw, isRecordGroupMembershipEqualToRaw, + isRecordGroupsInChannelEqualToRaw, + isRecordGroupsInTeamEqualToRaw, isRecordPostEqualToRaw, isRecordPreferenceEqualToRaw, isRecordRoleEqualToRaw, @@ -26,7 +29,6 @@ import CustomEmoji from '@typings/database/custom_emoji'; import { BatchOperationsArgs, DatabaseInstance, - ProcessInputsArgs, HandleEntityRecordsArgs, HandleFilesArgs, HandleIsolatedEntityArgs, @@ -37,12 +39,16 @@ import { PostImage, PrepareForDatabaseArgs, PrepareRecordsArgs, + ProcessInputsArgs, RawChannelMembership, RawCustomEmoji, RawDraft, RawEmbed, RawFile, + RawGroup, RawGroupMembership, + RawGroupsInChannel, + RawGroupsInTeam, RawPost, RawPostMetadata, RawPostsInThread, @@ -70,6 +76,9 @@ import { operateFileRecord, operateGlobalRecord, operateGroupMembershipRecord, + operateGroupRecord, + operateGroupsInChannelRecord, + operateGroupsInTeamRecord, operatePostInThreadRecord, operatePostMetadataRecord, operatePostRecord, @@ -98,6 +107,9 @@ const { CUSTOM_EMOJI, DRAFT, FILE, + GROUP, + GROUPS_IN_CHANNEL, + GROUPS_IN_TEAM, GROUP_MEMBERSHIP, POST, POSTS_IN_CHANNEL, @@ -109,6 +121,8 @@ const { USER, } = MM_TABLES.SERVER; +// TODO : We assume that we are receiving clean&correct data from the server. Hence, we can never have two posts in an array with the same post_id. This should be confirmed. + class DataOperator { /** * serverDatabase : In a multi-server configuration, this connection will be used by WebSockets and other parties to update databases other than the active one. @@ -802,6 +816,72 @@ class DataOperator { }); }; + /** + * handleGroup: Handler responsible for the Create/Update operations occurring on the GROUP entity from the 'Server' schema + * @param {RawGroup[]} groups + * @throws DataOperatorException + * @returns {Promise} + */ + handleGroup = async (groups: RawGroup[]) => { + if (!groups.length) { + throw new DataOperatorException( + 'An empty "groups" array has been passed to the handleGroup method', + ); + } + + await this.handleEntityRecords({ + comparator: isRecordGroupEqualToRaw, + fieldName: 'name', + operator: operateGroupRecord, + rawValues: groups, + tableName: GROUP, + }); + }; + + /** + * handleGroupsInTeam: Handler responsible for the Create/Update operations occurring on the GROUPS_IN_TEAM entity from the 'Server' schema + * @param {RawGroupsInTeam[]} groupsInTeam + * @throws DataOperatorException + * @returns {Promise} + */ + handleGroupsInTeam = async (groupsInTeam: RawGroupsInTeam[]) => { + if (!groupsInTeam.length) { + throw new DataOperatorException( + 'An empty "groups" array has been passed to the handleGroupsInTeam method', + ); + } + + await this.handleEntityRecords({ + comparator: isRecordGroupsInTeamEqualToRaw, + fieldName: 'group_id', + operator: operateGroupsInTeamRecord, + rawValues: groupsInTeam, + tableName: GROUPS_IN_TEAM, + }); + }; + + /** + * handleGroupsInChannel: Handler responsible for the Create/Update operations occurring on the GROUPS_IN_CHANNEL entity from the 'Server' schema + * @param {RawGroupsInChannel[]} groupsInChannel + * @throws DataOperatorException + * @returns {Promise} + */ + handleGroupsInChannel = async (groupsInChannel: RawGroupsInChannel[]) => { + if (!groupsInChannel.length) { + throw new DataOperatorException( + 'An empty "groups" array has been passed to the handleGroupsInTeam method', + ); + } + + await this.handleEntityRecords({ + comparator: isRecordGroupsInChannelEqualToRaw, + fieldName: 'group_id', + operator: operateGroupsInChannelRecord, + rawValues: groupsInChannel, + tableName: GROUPS_IN_CHANNEL, + }); + }; + /** * handleEntityRecords : Utility that processes some entities' data against values already present in the database so as to avoid duplicity. * @param {HandleEntityRecordsArgs} handleEntityRecords diff --git a/app/database/admin/data_operator/handlers/test.ts b/app/database/admin/data_operator/handlers/test.ts index fdef21069..b36746d3e 100644 --- a/app/database/admin/data_operator/handlers/test.ts +++ b/app/database/admin/data_operator/handlers/test.ts @@ -842,4 +842,78 @@ describe('*** DataOperator: Handlers tests ***', () => { expect(spyOnExecuteInDatabase).toHaveBeenCalledTimes(1); }); + + it('=> HandleGroup: should write to GROUP entity', async () => { + expect.assertions(1); + + const spyOnExecuteInDatabase = jest.spyOn(DataOperator as any, 'executeInDatabase'); + + await createConnection(true); + + await DataOperator.handleGroup([ + { + id: 'id_groupdfjdlfkjdkfdsf', + name: 'mobile_team', + display_name: 'mobile team', + description: '', + source: '', + remote_id: '', + create_at: 0, + update_at: 0, + delete_at: 0, + has_syncables: true, + }, + ]); + + expect(spyOnExecuteInDatabase).toHaveBeenCalledTimes(1); + }); + + it('=> HandleGroupsInTeam: should write to GROUPS_IN_TEAM entity', async () => { + expect.assertions(1); + + const spyOnExecuteInDatabase = jest.spyOn(DataOperator as any, 'executeInDatabase'); + + await createConnection(true); + + await DataOperator.handleGroupsInTeam([ + { + team_id: 'team_899', + team_display_name: '', + team_type: '', + group_id: 'group_id89', + auto_add: true, + create_at: 0, + delete_at: 0, + update_at: 0, + }, + ]); + + expect(spyOnExecuteInDatabase).toHaveBeenCalledTimes(1); + }); + + it('=> HandleGroupsInChannel: should write to GROUPS_IN_CHANNEL entity', async () => { + expect.assertions(1); + + const spyOnExecuteInDatabase = jest.spyOn(DataOperator as any, 'executeInDatabase'); + + await createConnection(true); + + await DataOperator.handleGroupsInChannel([ + { + auto_add: true, + channel_display_name: '', + channel_id: 'channelid', + channel_type: '', + create_at: 0, + delete_at: 0, + group_id: 'groupId', + team_display_name: '', + team_id: '', + team_type: '', + update_at: 0, + }, + ]); + + expect(spyOnExecuteInDatabase).toHaveBeenCalledTimes(1); + }); }); diff --git a/app/database/admin/data_operator/operators/index.ts b/app/database/admin/data_operator/operators/index.ts index 4943b4d1d..4c3ac2973 100644 --- a/app/database/admin/data_operator/operators/index.ts +++ b/app/database/admin/data_operator/operators/index.ts @@ -17,7 +17,10 @@ import { RawDraft, RawFile, RawGlobal, + RawGroup, RawGroupMembership, + RawGroupsInChannel, + RawGroupsInTeam, RawPost, RawPostMetadata, RawPostsInChannel, @@ -35,7 +38,10 @@ import Draft from '@typings/database/draft'; import {OperationType} from '@typings/database/enums'; import File from '@typings/database/file'; import Global from '@typings/database/global'; +import Group from '@typings/database/group'; import GroupMembership from '@typings/database/group_membership'; +import GroupsInChannel from '@typings/database/groups_in_channel'; +import GroupsInTeam from '@typings/database/groups_in_team'; import Post from '@typings/database/post'; import PostMetadata from '@typings/database/post_metadata'; import PostsInChannel from '@typings/database/posts_in_channel'; @@ -54,6 +60,9 @@ const { CUSTOM_EMOJI, DRAFT, FILE, + GROUP, + GROUPS_IN_TEAM, + GROUPS_IN_CHANNEL, GROUP_MEMBERSHIP, POST, POSTS_IN_CHANNEL, @@ -68,12 +77,14 @@ const { USER, } = MM_TABLES.SERVER; +// TODO : Include timezone_count and member_count when you have the information for the group section + /** * operateAppRecord: Prepares record of entity 'App' from the DEFAULT database for update or create actions. * @param {DataFactoryArgs} operator * @param {Database} operator.database * @param {MatchExistingRecord} operator.value - * @returns {Promise} + * @returns {Promise} */ export const operateAppRecord = async ({action, database, value}: DataFactoryArgs) => { const raw = value.raw as RawApp; @@ -101,7 +112,7 @@ export const operateAppRecord = async ({action, database, value}: DataFactoryArg * @param {DataFactoryArgs} operator * @param {Database} operator.database * @param {MatchExistingRecord} operator.value - * @returns {Promise} + * @returns {Promise} */ export const operateGlobalRecord = async ({action, database, value}: DataFactoryArgs) => { const raw = value.raw as RawGlobal; @@ -128,7 +139,7 @@ export const operateGlobalRecord = async ({action, database, value}: DataFactory * @param {DataFactoryArgs} operator * @param {Database} operator.database * @param {MatchExistingRecord} operator.value - * @returns {Promise} + * @returns {Promise} */ export const operateServersRecord = async ({action, database, value}: DataFactoryArgs) => { const raw = value.raw as RawServers; @@ -158,7 +169,7 @@ export const operateServersRecord = async ({action, database, value}: DataFactor * @param {DataFactoryArgs} operator * @param {Database} operator.database * @param {MatchExistingRecord} operator.value - * @returns {Promise} + * @returns {Promise} */ export const operateCustomEmojiRecord = async ({action, database, value}: DataFactoryArgs) => { const raw = value.raw as RawCustomEmoji; @@ -185,7 +196,7 @@ export const operateCustomEmojiRecord = async ({action, database, value}: DataFa * @param {DataFactoryArgs} operator * @param {Database} operator.database * @param {MatchExistingRecord} operator.value - * @returns {Promise} + * @returns {Promise} */ export const operateRoleRecord = async ({action, database, value}: DataFactoryArgs) => { const raw = value.raw as RawRole; @@ -213,7 +224,7 @@ export const operateRoleRecord = async ({action, database, value}: DataFactoryAr * @param {DataFactoryArgs} operator * @param {Database} operator.database * @param {MatchExistingRecord} operator.value - * @returns {Promise} + * @returns {Promise} */ export const operateSystemRecord = async ({action, database, value}: DataFactoryArgs) => { const raw = value.raw as RawSystem; @@ -241,7 +252,7 @@ export const operateSystemRecord = async ({action, database, value}: DataFactory * @param {DataFactoryArgs} operator * @param {Database} operator.database * @param {MatchExistingRecord} operator.value - * @returns {Promise} + * @returns {Promise} */ export const operateTermsOfServiceRecord = async ({action, database, value}: DataFactoryArgs) => { const raw = value.raw as RawTermsOfService; @@ -268,7 +279,7 @@ export const operateTermsOfServiceRecord = async ({action, database, value}: Dat * @param {DataFactoryArgs} operator * @param {Database} operator.database * @param {MatchExistingRecord} operator.value - * @returns {Promise} + * @returns {Promise} */ export const operatePostRecord = async ({action, database, value}: DataFactoryArgs) => { const raw = value.raw as RawPost; @@ -308,7 +319,7 @@ export const operatePostRecord = async ({action, database, value}: DataFactoryAr * @param {DataFactoryArgs} operator * @param {Database} operator.database * @param {MatchExistingRecord} operator.value - * @returns {Promise} + * @returns {Promise} */ export const operatePostInThreadRecord = async ({action, database, value}: DataFactoryArgs) => { const raw = value.raw as RawPostsInThread; @@ -335,7 +346,7 @@ export const operatePostInThreadRecord = async ({action, database, value}: DataF * @param {DataFactoryArgs} operator * @param {Database} operator.database * @param {MatchExistingRecord} operator.value - * @returns {Promise} + * @returns {Promise} */ export const operateReactionRecord = async ({action, database, value}: DataFactoryArgs) => { const raw = value.raw as RawReaction; @@ -344,7 +355,7 @@ export const operateReactionRecord = async ({action, database, value}: DataFacto // id of reaction comes from server response const generator = (reaction: Reaction) => { - reaction._raw.id = isCreateAction ? (raw?.id ?? reaction.id) : record?.id; + reaction._raw.id = isCreateAction ? reaction.id : record?.id; reaction.userId = raw.user_id; reaction.postId = raw.post_id; reaction.emojiName = raw.emoji_name; @@ -365,7 +376,7 @@ export const operateReactionRecord = async ({action, database, value}: DataFacto * @param {DataFactoryArgs} operator * @param {Database} operator.database * @param {MatchExistingRecord} operator.value - * @returns {Promise} + * @returns {Promise} */ export const operateFileRecord = async ({action, database, value}: DataFactoryArgs) => { const raw = value.raw as RawFile; @@ -400,7 +411,7 @@ export const operateFileRecord = async ({action, database, value}: DataFactoryAr * @param {DataFactoryArgs} operator * @param {Database} operator.database * @param {MatchExistingRecord} operator.value - * @returns {Promise} + * @returns {Promise} */ export const operatePostMetadataRecord = async ({action, database, value}: DataFactoryArgs) => { const raw = value.raw as RawPostMetadata; @@ -428,7 +439,7 @@ export const operatePostMetadataRecord = async ({action, database, value}: DataF * @param {DataFactoryArgs} operator * @param {Database} operator.database * @param {MatchExistingRecord} operator.value - * @returns {Promise} + * @returns {Promise} */ export const operateDraftRecord = async ({action, database, value}: DataFactoryArgs) => { const emptyFileInfo: FileInfo[] = []; @@ -457,13 +468,15 @@ export const operateDraftRecord = async ({action, database, value}: DataFactoryA * @param {DataFactoryArgs} operator * @param {Database} operator.database * @param {MatchExistingRecord} operator.value - * @returns {Promise} + * @returns {Promise} */ export const operatePostsInChannelRecord = async ({action, database, value}: DataFactoryArgs) => { const raw = value.raw as RawPostsInChannel; + const record = value.record as PostsInChannel; + const isCreateAction = action === OperationType.CREATE; const generator = (postsInChannel: PostsInChannel) => { - postsInChannel._raw.id = raw?.id ?? postsInChannel.id; + postsInChannel._raw.id = isCreateAction ? postsInChannel.id : record.id; postsInChannel.channelId = raw.channel_id; postsInChannel.earliest = raw.earliest; postsInChannel.latest = raw.latest; @@ -483,7 +496,7 @@ export const operatePostsInChannelRecord = async ({action, database, value}: Dat * @param {DataFactoryArgs} operator * @param {Database} operator.database * @param {MatchExistingRecord} operator.value - * @returns {Promise} + * @returns {Promise} */ export const operateUserRecord = async ({action, database, value}: DataFactoryArgs) => { const raw = value.raw as RawUser; @@ -526,7 +539,7 @@ export const operateUserRecord = async ({action, database, value}: DataFactoryAr * @param {DataFactoryArgs} operator * @param {Database} operator.database * @param {MatchExistingRecord} operator.value - * @returns {Promise} + * @returns {Promise} */ export const operatePreferenceRecord = async ({action, database, value}: DataFactoryArgs) => { const raw = value.raw as RawPreference; @@ -535,7 +548,7 @@ export const operatePreferenceRecord = async ({action, database, value}: DataFac // id of preference comes from server response const generator = (preference: Preference) => { - preference._raw.id = isCreateAction ? (raw?.id ?? preference.id) : record?.id; + preference._raw.id = isCreateAction ? preference.id : record?.id; preference.category = raw.category; preference.name = raw.name; preference.userId = raw.user_id; @@ -556,7 +569,7 @@ export const operatePreferenceRecord = async ({action, database, value}: DataFac * @param {DataFactoryArgs} operator * @param {Database} operator.database * @param {MatchExistingRecord} operator.value - * @returns {Promise} + * @returns {Promise} */ export const operateTeamMembershipRecord = async ({action, database, value}: DataFactoryArgs) => { const raw = value.raw as RawTeamMembership; @@ -565,7 +578,7 @@ export const operateTeamMembershipRecord = async ({action, database, value}: Dat // id of preference comes from server response const generator = (teamMembership: TeamMembership) => { - teamMembership._raw.id = isCreateAction ? (raw?.id ?? teamMembership.id) : record?.id; + teamMembership._raw.id = isCreateAction ? teamMembership.id : record?.id; teamMembership.teamId = raw.team_id; teamMembership.userId = raw.user_id; }; @@ -584,7 +597,7 @@ export const operateTeamMembershipRecord = async ({action, database, value}: Dat * @param {DataFactoryArgs} operator * @param {Database} operator.database * @param {MatchExistingRecord} operator.value - * @returns {Promise} + * @returns {Promise} */ export const operateGroupMembershipRecord = async ({action, database, value}: DataFactoryArgs) => { const raw = value.raw as RawGroupMembership; @@ -593,7 +606,7 @@ export const operateGroupMembershipRecord = async ({action, database, value}: Da // id of preference comes from server response const generator = (groupMember: GroupMembership) => { - groupMember._raw.id = isCreateAction ? (raw?.id ?? groupMember.id) : record?.id; + groupMember._raw.id = isCreateAction ? groupMember.id : record?.id; groupMember.groupId = raw.group_id; groupMember.userId = raw.user_id; }; @@ -612,7 +625,7 @@ export const operateGroupMembershipRecord = async ({action, database, value}: Da * @param {DataFactoryArgs} operator * @param {Database} operator.database * @param {MatchExistingRecord} operator.value - * @returns {Promise} + * @returns {Promise} */ export const operateChannelMembershipRecord = async ({action, database, value}: DataFactoryArgs) => { const raw = value.raw as RawChannelMembership; @@ -621,7 +634,7 @@ export const operateChannelMembershipRecord = async ({action, database, value}: // id of preference comes from server response const generator = (channelMember: ChannelMembership) => { - channelMember._raw.id = isCreateAction ? (raw?.id ?? channelMember.id) : record?.id; + channelMember._raw.id = isCreateAction ? channelMember.id : record?.id; channelMember.channelId = raw.channel_id; channelMember.userId = raw.user_id; }; @@ -635,6 +648,88 @@ export const operateChannelMembershipRecord = async ({action, database, value}: }); }; +/** + * operateGroupRecord: Prepares record of entity 'GROUP' from the SERVER database for update or create actions. + * @param {DataFactory} operator + * @param {Database} operator.database + * @param {MatchExistingRecord} operator.value + * @returns {Promise} + */ +export const operateGroupRecord = async ({action, database, value}: DataFactoryArgs) => { + const raw = value.raw as RawGroup; + const record = value.record as Group; + const isCreateAction = action === OperationType.CREATE; + + // id of preference comes from server response + const generator = (group: Group) => { + group._raw.id = isCreateAction ? (raw?.id ?? group.id) : record?.id; + group.name = raw.name; + group.displayName = raw.display_name; + }; + + return operateBaseRecord({ + action, + database, + tableName: GROUP, + value, + generator, + }); +}; + +/** + * operateGroupsInTeamRecord: Prepares record of entity 'GROUPS_IN_TEAM' from the SERVER database for update or create actions. + * @param {DataFactory} operator + * @param {Database} operator.database + * @param {MatchExistingRecord} operator.value + * @returns {Promise} + */ +export const operateGroupsInTeamRecord = async ({action, database, value}: DataFactoryArgs) => { + const raw = value.raw as RawGroupsInTeam; + const record = value.record as GroupsInTeam; + const isCreateAction = action === OperationType.CREATE; + + const generator = (groupsInTeam: GroupsInTeam) => { + groupsInTeam._raw.id = isCreateAction ? groupsInTeam.id : record?.id; + groupsInTeam.teamId = raw.team_id; + groupsInTeam.groupId = raw.group_id; + }; + + return operateBaseRecord({ + action, + database, + tableName: GROUPS_IN_TEAM, + value, + generator, + }); +}; + +/** + * operateGroupsInChannelRecord: Prepares record of entity 'GROUPS_IN_TEAM' from the SERVER database for update or create actions. + * @param {DataFactory} operator + * @param {Database} operator.database + * @param {MatchExistingRecord} operator.value + * @returns {Promise} + */ +export const operateGroupsInChannelRecord = async ({action, database, value}: DataFactoryArgs) => { + const raw = value.raw as RawGroupsInChannel; + const record = value.record as GroupsInChannel; + const isCreateAction = action === OperationType.CREATE; + + const generator = (groupsInChannel: GroupsInChannel) => { + groupsInChannel._raw.id = isCreateAction ? groupsInChannel.id : record?.id; + groupsInChannel.channelId = raw.channel_id; + groupsInChannel.groupId = raw.group_id; + }; + + return operateBaseRecord({ + action, + database, + tableName: GROUPS_IN_CHANNEL, + value, + generator, + }); +}; + /** * operateBaseRecord: This is the last step for each operator and depending on the 'action', it will either prepare an * existing record for UPDATE or prepare a collection for CREATE @@ -643,10 +738,10 @@ export const operateChannelMembershipRecord = async ({action, database, value}: * @param {Database} operatorBase.database * @param {string} operatorBase.tableName * @param {MatchExistingRecord} operatorBase.value - * @param {((model: Model) => void)} operatorBase.generator - * @returns {Promise} + * @param {((DataFactoryArgs) => void)} operatorBase.generator + * @returns {Promise} */ -const operateBaseRecord = async ({action, database, tableName, value, generator}: DataFactoryArgs) => { +const operateBaseRecord = async ({action, database, tableName, value, generator}: DataFactoryArgs): Promise => { if (action === OperationType.UPDATE) { // Two possible scenarios: // 1. We are dealing with either duplicates here and if so, we'll update instead of create diff --git a/app/database/admin/data_operator/operators/test.ts b/app/database/admin/data_operator/operators/test.ts index 02953ef41..fcf6c79c1 100644 --- a/app/database/admin/data_operator/operators/test.ts +++ b/app/database/admin/data_operator/operators/test.ts @@ -12,6 +12,9 @@ import { operateFileRecord, operateGlobalRecord, operateGroupMembershipRecord, + operateGroupRecord, + operateGroupsInChannelRecord, + operateGroupsInTeamRecord, operatePostInThreadRecord, operatePostMetadataRecord, operatePostRecord, @@ -134,7 +137,11 @@ describe('*** DataOperator: Operators tests ***', () => { database: database!, value: { record: undefined, - raw: {id: 'role-1', name: 'role-name-1', permissions: []}, + raw: { + id: 'role-1', + name: 'role-name-1', + permissions: [], + }, }, }); @@ -575,4 +582,93 @@ describe('*** DataOperator: Operators tests ***', () => { expect(preparedRecords).toBeTruthy(); expect(preparedRecords!.collection.modelClass.name).toMatch('ChannelMembership'); }); + + it('=> operateGroupRecord: should return an array of type Group', async () => { + expect.assertions(3); + + const database = await createConnection(); + expect(database).toBeTruthy(); + + const preparedRecords = await operateGroupRecord({ + action: OperationType.CREATE, + database: database!, + value: { + record: undefined, + raw: { + id: 'id_groupdfjdlfkjdkfdsf', + name: 'mobile_team', + display_name: 'mobile team', + description: '', + source: '', + remote_id: '', + create_at: 0, + update_at: 0, + delete_at: 0, + has_syncables: true, + }, + }, + }); + + expect(preparedRecords).toBeTruthy(); + expect(preparedRecords!.collection.modelClass.name).toMatch('Group'); + }); + + it('=> operateGroupsInTeamRecord: should return an array of type GroupsInTeam', async () => { + expect.assertions(3); + + const database = await createConnection(); + expect(database).toBeTruthy(); + + const preparedRecords = await operateGroupsInTeamRecord({ + action: OperationType.CREATE, + database: database!, + value: { + record: undefined, + raw: { + team_id: 'team_89', + team_display_name: '', + team_type: '', + group_id: 'group_id89', + auto_add: true, + create_at: 0, + delete_at: 0, + update_at: 0, + }, + }, + }); + + expect(preparedRecords).toBeTruthy(); + expect(preparedRecords!.collection.modelClass.name).toMatch('GroupsInTeam'); + }); + + it('=> operateGroupsInChannelRecord: should return an array of type GroupsInChannel', async () => { + expect.assertions(3); + + const database = await createConnection(); + expect(database).toBeTruthy(); + + const preparedRecords = await operateGroupsInChannelRecord({ + action: OperationType.CREATE, + database: database!, + value: { + record: undefined, + raw: { + auto_add: true, + channel_display_name: '', + channel_id: 'channelid', + channel_type: '', + create_at: 0, + delete_at: 0, + group_id: 'groupId', + team_display_name: '', + team_id: '', + team_type: '', + update_at: 0, + }, + }, + }); + + expect(preparedRecords).toBeTruthy(); + expect(preparedRecords!.collection.modelClass.name).toMatch('GroupsInChannel'); + }); }); diff --git a/types/database/database.d.ts b/types/database/database.d.ts index 329cecaaa..89f80c649 100644 --- a/types/database/database.d.ts +++ b/types/database/database.d.ts @@ -6,7 +6,7 @@ import Model from '@nozbe/watermelondb/Model'; import {Clause} from '@nozbe/watermelondb/QueryDescription'; import {Class} from '@nozbe/watermelondb/utils/common'; -import {DatabaseType, IsolatedEntities, OperationType} from './enums'; +import {DatabaseType, IsolatedEntities} from './enums'; export type MigrationEvents = { onSuccess: () => void; @@ -58,16 +58,16 @@ export type RawCustomEmoji = { create_at?: number; update_at?: number; delete_at?: number; - creator_id?: string; + creator_id: string; }; export type RawRole = { id: string; name: string; - display_name: string; - description: string; + display_name?: string; + description?: string; permissions: string[]; - scheme_managed: boolean; + scheme_managed?: boolean; }; export type RawSystem = { @@ -88,7 +88,7 @@ export type RawDraft = { channel_id: string; files?: FileInfo[]; message?: string; - root_id?: string; + root_id: string; }; export type RawEmbed = { data: {}; type: string; url: string }; @@ -97,7 +97,6 @@ export type RawPostMetadata = { data: any; type: string; postId: string; - id?: string; }; interface PostMetadataTypes { @@ -124,7 +123,6 @@ export type RawFile = { }; export type RawReaction = { - id?: string; create_at: number; delete_at: number; emoji_name: string; @@ -134,7 +132,6 @@ export type RawReaction = { }; export type RawPostsInChannel = { - id?: string; channel_id: string; earliest: number; latest: number; @@ -241,17 +238,15 @@ export type RawUser = { }; export type RawPreference = { - id?: string; - user_id: string; category: string; name: string; + user_id: string; value: string; }; export type RawTeamMembership = { delete_at: number; explicit_roles: string; - id?: string; roles: string; scheme_admin: boolean; scheme_guest: boolean; @@ -261,13 +256,11 @@ export type RawTeamMembership = { }; export type RawGroupMembership = { - id?: string; user_id: string; group_id: string; }; export type RawChannelMembership = { - id?: string; channel_id: string; user_id: string; roles: string; @@ -325,12 +318,49 @@ export type RawChannel = { }; export type RawPostsInThread = { - id?: string; earliest: number; latest?: number; post_id: string; }; +export type RawGroup = { + create_at: number, + delete_at: number, + description: string, + display_name: string, + has_syncables: boolean + id: string, + name: string, + remote_id: string, + source: string, + update_at: number, +} + +export type RawGroupsInTeam = { + auto_add: boolean, + create_at: number, + delete_at: number, + group_id: string, + team_display_name: string, + team_id: string, + team_type: string, + update_at: number +} + +export type RawGroupsInChannel = { + auto_add: boolean, + channel_display_name: string, + channel_id: string, + channel_type: string, + create_at: number, + delete_at: number, + group_id: string, + team_display_name: string, + team_id: string, + team_type: string, + update_at: number +} + export type RawValue = | RawApp | RawChannelMembership @@ -338,7 +368,10 @@ export type RawValue = | RawDraft | RawFile | RawGlobal + | RawGroup | RawGroupMembership + | RawGroupsInChannel + | RawGroupsInTeam | RawPost | RawPostMetadata | RawPostsInChannel @@ -355,11 +388,11 @@ export type RawValue = export type MatchExistingRecord = { record?: Model; raw: RawValue }; export type DataFactoryArgs = { + action: string; database: Database; generator?: (model: Model) => void; tableName?: string; value: MatchExistingRecord; - action: OperationType; }; export type PrepareForDatabaseArgs = { @@ -390,8 +423,8 @@ export type DatabaseConnectionArgs = { export type ActiveServerDatabaseArgs = { displayName: string; serverUrl: string }; export type HandleReactionsArgs = { - reactions: RawReaction[]; prepareRowsOnly: boolean; + reactions: RawReaction[]; }; export type HandleFilesArgs = { @@ -407,8 +440,8 @@ export type HandlePostMetadataArgs = { export type HandlePostsArgs = { orders: string[]; - values: RawPost[]; previousPostId?: string; + values: RawPost[]; }; export type SanitizeReactionsArgs = { @@ -419,13 +452,13 @@ export type SanitizeReactionsArgs = { export type ChainPostsArgs = { orders: string[]; - rawPosts: RawPost[]; previousPostId: string; + rawPosts: RawPost[]; }; export type SanitizePostsArgs = { - posts: RawPost[]; orders: string[]; + posts: RawPost[]; }; export type IdenticalRecordArgs = { @@ -456,8 +489,8 @@ export type HandleEntityRecordsArgs = { }; export type DatabaseInstances = { - url: string; dbInstance: DatabaseInstance; + url: string; }; export type RangeOfValueArgs = {