diff --git a/keyserver/src/database/migration-config.js b/keyserver/src/database/migration-config.js --- a/keyserver/src/database/migration-config.js +++ b/keyserver/src/database/migration-config.js @@ -14,6 +14,7 @@ threadPermissions, userSurfacedPermissions, type ThreadRolePermissionsBlob, + type UserSurfacedPermission, } from 'lib/types/thread-permission-types.js'; import { threadTypes } from 'lib/types/thread-types-enum.js'; import { permissionsToRemoveInMigration } from 'lib/utils/migration-utils.js'; @@ -22,8 +23,8 @@ toggleUserSurfacedPermission, } from 'lib/utils/role-utils.js'; +import { processMessagesInDBForSearch } from './search-utils.js'; import { dbQuery, SQL } from '../database/database.js'; -import { processMessagesInDBForSearch } from '../database/search-utils.js'; import { deleteThread } from '../deleters/thread-deleters.js'; import { fetchAllPrimaryInviteLinks } from '../fetchers/link-fetchers.js'; import { fetchPickledOlmAccount } from '../fetchers/olm-account-fetchers.js'; @@ -981,58 +982,8 @@ }, { version: 69, - migrationPromise: async () => { - const [result] = await dbQuery(SQL` - SELECT r.id, r.permissions - FROM threads t - LEFT JOIN roles r ON r.thread = t.id - WHERE t.community IS NULL - AND t.type != ${threadTypes.GENESIS} - AND r.special_role != ${specialRoles.ADMIN_ROLE} - `); - - // We accidentally removed ADD_MEMBERS in an earlier migration, - // so we make sure to bring it back here - const rolePermissionsToUpdate = new Map< - string, - ThreadRolePermissionsBlob, - >(); - for (const row of result) { - const { id, permissions: permissionsString } = row; - const permissions = JSON.parse(permissionsString); - const userSurfaced = - userSurfacedPermissionsFromRolePermissions(permissions); - if (userSurfaced.has(userSurfacedPermissions.ADD_MEMBERS)) { - continue; - } - const newPermissions = toggleUserSurfacedPermission( - permissions, - userSurfacedPermissions.ADD_MEMBERS, - ); - rolePermissionsToUpdate.set(id, newPermissions); - } - - if (rolePermissionsToUpdate.size > 0) { - const updateQuery = SQL` - UPDATE roles - SET permissions = CASE id - `; - for (const [id, permissions] of rolePermissionsToUpdate) { - console.log(`updating ${id} to ${JSON.stringify(permissions)}`); - const permissionsBlob = JSON.stringify(permissions); - updateQuery.append(SQL` - WHEN ${id} THEN ${permissionsBlob} - `); - } - updateQuery.append(SQL` - ELSE permissions - END - `); - await dbQuery(updateQuery); - } - - await updateRolesAndPermissionsForAllThreads(); - }, + migrationPromise: () => + addNewUserSurfacedPermission(userSurfacedPermissions.ADD_MEMBERS), migrationType: 'wrap_in_transaction_and_block_requests', }, { @@ -1108,6 +1059,15 @@ ), migrationType: 'run_simultaneously_with_requests', }, + { + version: 73, + migrationPromise: () => + // This function calls updateRolesAndPermissionsForAllThreads which + // should grant DELETE_OWN_MESSAGES and DELETE_ALL_MESSAGES to all the + // admins + addNewUserSurfacedPermission(userSurfacedPermissions.DELETE_OWN_MESSAGES), + migrationType: 'run_simultaneously_with_requests', + }, ]; const versions: $ReadOnlyArray<number> = migrations.map( migration => migration.version, @@ -1254,6 +1214,61 @@ return !!process.env.COMM_DATABASE_HOST; } +async function addNewUserSurfacedPermission( + permission: UserSurfacedPermission, +) { + // We're filtering out admin roles because a new permission should be set + // to true in baseAdminPermissions inside + // getRolePermissionBlobsForCommunityRoot + const [result] = await dbQuery(SQL` + SELECT r.id, r.permissions + FROM threads t + LEFT JOIN roles r ON r.thread = t.id + WHERE t.community IS NULL + AND t.type != ${threadTypes.GENESIS} + AND r.special_role != ${specialRoles.ADMIN_ROLE} + `); + + const rolePermissionsToUpdate = new Map<string, ThreadRolePermissionsBlob>(); + for (const row of result) { + const { id, permissions: permissionsString } = row; + const permissions = JSON.parse(permissionsString); + const userSurfaced = + userSurfacedPermissionsFromRolePermissions(permissions); + if (userSurfaced.has(permission)) { + continue; + } + const newPermissions = toggleUserSurfacedPermission( + permissions, + permission, + ); + rolePermissionsToUpdate.set(id, newPermissions); + } + + if (rolePermissionsToUpdate.size > 0) { + const updateQuery = SQL` + UPDATE roles + SET permissions = CASE id + `; + for (const [id, permissions] of rolePermissionsToUpdate) { + console.log(`updating ${id} to ${JSON.stringify(permissions)}`); + const permissionsBlob = JSON.stringify(permissions); + updateQuery.append(SQL` + WHEN ${id} THEN ${permissionsBlob} + `); + } + updateQuery.append(SQL` + ELSE permissions + END + `); + await dbQuery(updateQuery); + } + + // Calling this also results in recalculating roles for admins, if + // baseAdminPermissions was also changed + await updateRolesAndPermissionsForAllThreads(); +} + export { migrations, newDatabaseVersion, diff --git a/keyserver/src/fetchers/thread-fetchers.js b/keyserver/src/fetchers/thread-fetchers.js --- a/keyserver/src/fetchers/thread-fetchers.js +++ b/keyserver/src/fetchers/thread-fetchers.js @@ -10,7 +10,10 @@ getContainingThreadID, getCommunity, } from 'lib/shared/thread-utils.js'; -import { hasMinCodeVersion } from 'lib/shared/version-utils.js'; +import { + hasMinCodeVersion, + NEXT_CODE_VERSION, +} from 'lib/shared/version-utils.js'; import type { AvatarDBContent, ClientAvatar } from 'lib/types/avatar-types.js'; import type { RawMessageInfo, MessageInfo } from 'lib/types/message-types.js'; import type { ThinRawThreadInfo } from 'lib/types/minimally-encoded-thread-permissions-types.js'; @@ -328,6 +331,13 @@ web: 136, }, ); + const messageDeletionUnsupported = !hasMinCodeVersion( + viewer.platformDetails, + { + native: NEXT_CODE_VERSION, + web: NEXT_CODE_VERSION, + }, + ); const threadInfos: { [string]: LegacyThinRawThreadInfo | ThinRawThreadInfo, @@ -350,6 +360,7 @@ stripMemberPermissions: stripMemberPermissions, canDisplayFarcasterThreadAvatars, dontFilterMissingKnowOf, + messageDeletionUnsupported, }, ); if (threadInfo) { diff --git a/lib/shared/thread-utils.js b/lib/shared/thread-utils.js --- a/lib/shared/thread-utils.js +++ b/lib/shared/thread-utils.js @@ -762,6 +762,7 @@ +stripMemberPermissions?: boolean, +canDisplayFarcasterThreadAvatars?: boolean, +dontFilterMissingKnowOf?: boolean, + +messageDeletionUnsupported?: boolean, }; function rawThreadInfoFromServerThreadInfo( @@ -787,6 +788,7 @@ const canDisplayFarcasterThreadAvatars = options?.canDisplayFarcasterThreadAvatars; const dontFilterMissingKnowOf = options?.dontFilterMissingKnowOf ?? false; + const filterDeleteMessagePermissions = options?.messageDeletionUnsupported; const filterThreadPermissions = ( innerThreadPermissions: ThreadPermissionsInfo, @@ -828,7 +830,10 @@ threadPermissions.VOICED_IN_ANNOUNCEMENT_CHANNELS, ].includes(k)) || (filterManageFarcasterChannelTagsPermission && - [threadPermissions.MANAGE_FARCASTER_CHANNEL_TAGS].includes(k)), + [threadPermissions.MANAGE_FARCASTER_CHANNEL_TAGS].includes(k)) || + (filterDeleteMessagePermissions && + (k.endsWith(threadPermissions.DELETE_OWN_MESSAGES) || + k.endsWith(threadPermissions.DELETE_ALL_MESSAGES))), )(innerThreadPermissions); };