diff --git a/keyserver/src/deleters/role-deleters.js b/keyserver/src/deleters/role-deleters.js --- a/keyserver/src/deleters/role-deleters.js +++ b/keyserver/src/deleters/role-deleters.js @@ -1,6 +1,23 @@ // @flow +import { threadPermissions } from 'lib/types/thread-permission-types.js'; +import type { + RoleDeletionRequest, + RoleDeletionResult, +} from 'lib/types/thread-types.js'; +import { updateTypes } from 'lib/types/update-types-enum.js'; +import { ServerError } from 'lib/utils/errors.js'; + +import { createUpdates } from '../creators/update-creator.js'; import { dbQuery, SQL } from '../database/database.js'; +import { + fetchServerThreadInfos, + rawThreadInfosFromServerThreadInfos, + fetchThreadInfos, +} from '../fetchers/thread-fetchers.js'; +import { checkThreadPermission } from '../fetchers/thread-permission-fetchers.js'; +import type { Viewer } from '../session/viewer.js'; +import { updateRole } from '../updaters/thread-updaters.js'; async function deleteOrphanedRoles(): Promise { await dbQuery(SQL` @@ -12,4 +29,109 @@ `); } -export { deleteOrphanedRoles }; +async function deleteRole( + viewer: Viewer, + request: RoleDeletionRequest, +): Promise { + const hasPermission = await checkThreadPermission( + viewer, + request.community, + threadPermissions.CHANGE_ROLE, + ); + if (!hasPermission) { + throw new ServerError('invalid_credentials'); + } + + const { community, roleID } = request; + + const defaultRoleQuery = SQL` + SELECT default_role + FROM threads + WHERE id = ${community} + `; + + const membersWithRoleQuery = SQL` + SELECT user + FROM memberships + WHERE thread = ${community} + AND role = ${roleID} + `; + + const [[defaultRoleResult], [membersWithRoleResult], { threadInfos }] = + await Promise.all([ + dbQuery(defaultRoleQuery), + dbQuery(membersWithRoleQuery), + fetchThreadInfos(viewer, { + threadID: community, + }), + ]); + const threadInfo = threadInfos[community]; + + if (!threadInfo) { + throw new ServerError('invalid_parameters'); + } + + const defaultRoleID = defaultRoleResult[0].default_role.toString(); + const membersWithRole = membersWithRoleResult.map(result => result.user); + const adminRoleID = Object.keys(threadInfo.roles).find( + role => threadInfo.roles[role].name === 'Admins', + ); + + if (roleID === defaultRoleID || roleID === adminRoleID) { + throw new ServerError('invalid_parameters'); + } + + if (membersWithRole.length > 0) { + await updateRole(viewer, { + threadID: community, + memberIDs: membersWithRole, + role: defaultRoleID, + }); + } + + const deleteFromRolesQuery = SQL` + DELETE FROM roles + WHERE id = ${roleID} + AND thread = ${community} + `; + + await dbQuery(deleteFromRolesQuery); + + const fetchServerThreadInfosResult = await fetchServerThreadInfos({ + threadID: community, + }); + const { threadInfos: serverThreadInfos } = fetchServerThreadInfosResult; + const serverThreadInfo = serverThreadInfos[community]; + + const time = Date.now(); + + const updateDatas = []; + for (const memberInfo of serverThreadInfo.members) { + updateDatas.push({ + type: updateTypes.UPDATE_THREAD, + userID: memberInfo.id, + time, + threadID: community, + }); + } + + const { viewerUpdates } = await createUpdates(updateDatas, { + viewer, + updatesForCurrentSession: 'return', + }); + + const { threadInfos: rawThreadInfos } = rawThreadInfosFromServerThreadInfos( + viewer, + fetchServerThreadInfosResult, + ); + const rawThreadInfo = rawThreadInfos[community]; + + return { + threadInfo: rawThreadInfo, + updatesResult: { + newUpdates: viewerUpdates, + }, + }; +} + +export { deleteOrphanedRoles, deleteRole }; diff --git a/lib/types/thread-types.js b/lib/types/thread-types.js --- a/lib/types/thread-types.js +++ b/lib/types/thread-types.js @@ -428,6 +428,25 @@ }, }; +export type RoleDeletionRequest = { + +community: string, + +roleID: string, +}; + +export type RoleDeletionResult = { + +threadInfo: RawThreadInfo, + +updatesResult: { + +newUpdates: $ReadOnlyArray, + }, +}; + +export type RoleDeletionPayload = { + +threadInfo: RawThreadInfo, + +updatesResult: { + +newUpdates: $ReadOnlyArray, + }, +}; + // We can show a max of 3 sidebars inline underneath their parent in the chat // tab. If there are more, we show a button that opens a modal to see the rest export const maxReadSidebars = 3;