diff --git a/native/redux/edit-thread-avatar-permission-migration.js b/native/redux/edit-thread-avatar-permission-migration.js new file mode 100644 --- /dev/null +++ b/native/redux/edit-thread-avatar-permission-migration.js @@ -0,0 +1,114 @@ +// @flow + +import type { + MemberInfo, + RawThreadInfo, + ThreadCurrentUserInfo, + RoleInfo, +} from 'lib/types/thread-types.js'; +import { threadTypes } from 'lib/types/thread-types.js'; + +type ThreadStoreThreadInfos = { +[id: string]: RawThreadInfo }; +type TargetMemberInfo = MemberInfo | ThreadCurrentUserInfo; + +const threadTypesWhereMembersRoleHasEditAvatarPermissions = new Set([ + threadTypes.SIDEBAR, + threadTypes.LOCAL, + threadTypes.COMMUNITY_ROOT, + threadTypes.COMMUNITY_OPEN_SUBTHREAD, + threadTypes.COMMUNITY_SECRET_SUBTHREAD, +]); + +function addEditThreadAvatarPermissionsToUser( + threadInfo: RawThreadInfo, + member: TargetMemberInfo, + threadID: string, +): TargetMemberInfo { + let updatedPermissions; + + if (member.role && threadInfo.roles[member.role].name === 'Members') { + updatedPermissions = { + ...member.permissions, + edit_thread_avatar: + threadTypesWhereMembersRoleHasEditAvatarPermissions.has(threadInfo.type) + ? { value: true, source: threadID } + : { value: false, source: null }, + }; + } else if (member.permissions['edit_thread_color']) { + updatedPermissions = { + ...member.permissions, + edit_thread_avatar: member.permissions['edit_thread_color'], + }; + } + + return updatedPermissions + ? { + ...member, + permissions: updatedPermissions, + } + : member; +} + +function addEditThreadAvatarPermissionsToRole( + role: RoleInfo, + threadInfo: RawThreadInfo, +): RoleInfo { + if (role.name === 'Admins') { + return { + ...role, + permissions: { + ...role.permissions, + edit_thread_avatar: true, + descendant_edit_thread_avatar: true, + }, + }; + } + + if ( + threadTypesWhereMembersRoleHasEditAvatarPermissions.has(threadInfo.type) + ) { + return { + ...role, + permissions: { ...role.permissions, edit_thread_avatar: true }, + }; + } + + return role; +} + +function persistMigrationForThreadAvatarPermission( + threadInfos: ThreadStoreThreadInfos, +): ThreadStoreThreadInfos { + const newThreadInfos = {}; + for (const threadID in threadInfos) { + const threadInfo: RawThreadInfo = threadInfos[threadID]; + const updatedMembers = threadInfo.members.map(member => + addEditThreadAvatarPermissionsToUser(threadInfo, member, threadID), + ); + + const updatedCurrentUser = addEditThreadAvatarPermissionsToUser( + threadInfo, + threadInfo.currentUser, + threadID, + ); + + const updatedRoles = {}; + for (const roleID in threadInfo.roles) { + updatedRoles[roleID] = addEditThreadAvatarPermissionsToRole( + threadInfo.roles[roleID], + threadInfo, + ); + } + + const updatedThreadInfo = { + ...threadInfo, + members: updatedMembers, + currentUser: updatedCurrentUser, + roles: updatedRoles, + }; + newThreadInfos[threadID] = updatedThreadInfo; + } + return newThreadInfos; +} + +export { persistMigrationForThreadAvatarPermission }; diff --git a/native/redux/persist.js b/native/redux/persist.js --- a/native/redux/persist.js +++ b/native/redux/persist.js @@ -41,6 +41,7 @@ convertThreadStoreOperationsToClientDBOperations, } from 'lib/utils/thread-ops-utils.js'; +import { persistMigrationForThreadAvatarPermission } from './edit-thread-avatar-permission-migration.js'; import { migrateThreadStoreForEditThreadPermissions } from './edit-thread-permission-migration.js'; import { persistMigrationForManagePinsThreadPermission } from './manage-pins-permission-migration.js'; import type { AppState } from './state-types.js'; @@ -502,6 +503,58 @@ return { ...state, cookie: null }; } + return state; + }, + [37]: (state: AppState) => { + // 1. Get threads from SQLite `threads` table. + const clientDBThreadInfos = commCoreModule.getAllThreadsSync(); + + // 2. Translate `ClientDBThreadInfo`s to `RawThreadInfo`s. + const rawThreadInfos = clientDBThreadInfos.map( + convertClientDBThreadInfoToRawThreadInfo, + ); + + // 3. Convert `rawThreadInfos` to a map of `threadID` => `threadInfo`. + const threadIDToThreadInfo = rawThreadInfos.reduce((acc, threadInfo) => { + acc[threadInfo.id] = threadInfo; + return acc; + }, {}); + + // 4. Add `threadPermission` to each `threadInfo`. + const rawThreadInfosWithUpdatedPermissions = + persistMigrationForThreadAvatarPermission(threadIDToThreadInfo); + + // 5. Convert the updated `threadInfos` back into an array. + const updatedRawThreadInfos = Object.keys( + rawThreadInfosWithUpdatedPermissions, + ).map(id => rawThreadInfosWithUpdatedPermissions[id]); + + // 6. Translate `RawThreadInfo`s to `ClientDBThreadInfo`s. + const convertedClientDBThreadInfos = updatedRawThreadInfos.map( + convertRawThreadInfoToClientDBThreadInfo, + ); + + // 7. Construct `ClientDBThreadStoreOperation`s to clear SQLite `threads` + // table and repopulate with `ClientDBThreadInfo`s. + const operations: $ReadOnlyArray = [ + { + type: 'remove_all', + }, + ...convertedClientDBThreadInfos.map((thread: ClientDBThreadInfo) => ({ + type: 'replace', + payload: thread, + })), + ]; + + // 8. Try processing `ClientDBThreadStoreOperation`s and log out if + // `processThreadStoreOperationsSync(...)` throws an exception. + try { + commCoreModule.processThreadStoreOperationsSync(operations); + } catch (exception) { + console.log(exception); + return { ...state, cookie: null }; + } + return state; }, };