Page MenuHomePhorge

D8564.1765133326.diff
No OneTemporary

Size
12 KB
Referenced Files
None
Subscribers
None

D8564.1765133326.diff

diff --git a/keyserver/src/creators/role-creator.js b/keyserver/src/creators/role-creator.js
--- a/keyserver/src/creators/role-creator.js
+++ b/keyserver/src/creators/role-creator.js
@@ -27,6 +27,7 @@
} 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';
type InitialRoles = {
+default: RoleInfo,
@@ -93,7 +94,7 @@
throw new ServerError('invalid_credentials');
}
- const { community, name, permissions, action } = request;
+ const { community, name, permissions } = request;
for (const permission of permissions) {
if (!userSurfacedPermissionsSet.has(permission)) {
@@ -135,16 +136,43 @@
const row = [id, community, name, permissionsBlob, time];
let query = SQL``;
- if (action === 'create_role') {
+ if (request.action === 'create_role') {
query = SQL`
INSERT INTO roles (id, thread, name, permissions, creation_time)
VALUES (${row})
`;
- } else if (action === 'edit_role') {
- throw new ServerError("unimplemented: can't edit roles yet");
- }
- await dbQuery(query);
+ await dbQuery(query);
+ } else if (request.action === 'edit_role') {
+ const { existingRoleID } = request;
+
+ query = SQL`
+ UPDATE roles
+ SET name = ${name}, permissions = ${permissionsBlob}
+ WHERE id = ${existingRoleID}
+ `;
+
+ await dbQuery(query);
+
+ // The `updateRole` needs to occur after the role has been updated
+ // in the database because it will want the most up to date role info
+ // (permissions / name)
+ const membersWithRole = threadInfo.members
+ .filter(memberInfo => memberInfo.role === existingRoleID)
+ .map(memberInfo => memberInfo.id);
+
+ if (membersWithRole.length > 0) {
+ await updateRole(
+ viewer,
+ {
+ threadID: community,
+ role: existingRoleID,
+ memberIDs: membersWithRole,
+ },
+ { silenceNewMessages: true, forcePermissionRecalculation: true },
+ );
+ }
+ }
const fetchServerThreadInfosResult = await fetchServerThreadInfos({
threadID: community,
diff --git a/keyserver/src/responders/thread-responders.js b/keyserver/src/responders/thread-responders.js
--- a/keyserver/src/responders/thread-responders.js
+++ b/keyserver/src/responders/thread-responders.js
@@ -351,12 +351,22 @@
);
}
-const roleModificationRequestInputValidator = tShape<RoleModificationRequest>({
- community: tID,
- name: t.String,
- permissions: t.list(userSurfacedPermissionValidator),
- action: t.enums.of(['create_role', 'edit_role']),
-});
+const roleModificationRequestInputValidator: TUnion<RoleModificationRequest> =
+ t.union([
+ tShape({
+ community: tID,
+ name: t.String,
+ permissions: t.list(userSurfacedPermissionValidator),
+ action: t.enums.of(['create_role']),
+ }),
+ tShape({
+ community: tID,
+ existingRoleID: tID,
+ name: t.String,
+ permissions: t.list(userSurfacedPermissionValidator),
+ action: t.enums.of(['edit_role']),
+ }),
+ ]);
export const roleModificationResultValidator: TInterface<RoleModificationResult> =
tShape<RoleModificationResult>({
diff --git a/keyserver/src/updaters/thread-permission-updaters.js b/keyserver/src/updaters/thread-permission-updaters.js
--- a/keyserver/src/updaters/thread-permission-updaters.js
+++ b/keyserver/src/updaters/thread-permission-updaters.js
@@ -76,6 +76,7 @@
// -1 role means to set the user as a "ghost" (former member)
type ChangeRoleOptions = {
+setNewMembersToUnread?: boolean,
+ +forcePermissionRecalculation?: boolean,
};
type ChangeRoleMemberInfo = {
permissionsFromParent?: ?ThreadPermissionsBlob,
@@ -90,6 +91,7 @@
const intent = role === -1 || role === 0 ? 'leave' : 'join';
const setNewMembersToUnread =
options?.setNewMembersToUnread && intent === 'join';
+ const forcePermissionRecalculation = options?.forcePermissionRecalculation;
if (userIDs.length === 0) {
return {
@@ -198,7 +200,11 @@
const oldPermissionsForChildren =
existingMembership?.oldPermissionsForChildren ?? null;
- if (existingMembership && oldRole === intendedRole) {
+ if (
+ existingMembership &&
+ oldRole === intendedRole &&
+ !forcePermissionRecalculation
+ ) {
// If the old role is the same as the new one, we have nothing to update
continue;
} else if (Number(oldRole) > 0 && role === null) {
diff --git a/keyserver/src/updaters/thread-updaters.js b/keyserver/src/updaters/thread-updaters.js
--- a/keyserver/src/updaters/thread-updaters.js
+++ b/keyserver/src/updaters/thread-updaters.js
@@ -62,10 +62,18 @@
import type { Viewer } from '../session/viewer.js';
import RelationshipChangeset from '../utils/relationship-changeset.js';
+type UpdateRoleOptions = {
+ +silenceNewMessages?: boolean,
+ +forcePermissionRecalculation?: boolean,
+};
async function updateRole(
viewer: Viewer,
request: RoleChangeRequest,
+ options?: UpdateRoleOptions,
): Promise<ChangeThreadSettingsResult> {
+ const silenceNewMessages = options?.silenceNewMessages;
+ const forcePermissionRecalculation = options?.forcePermissionRecalculation;
+
if (!viewer.loggedIn) {
throw new ServerError('not_logged_in');
}
@@ -136,19 +144,36 @@
throw new ServerError('invalid_parameters');
}
- const changeset = await changeRole(request.threadID, memberIDs, request.role);
- const { viewerUpdates } = await commitMembershipChangeset(viewer, changeset);
+ const changeset = await changeRole(
+ request.threadID,
+ memberIDs,
+ request.role,
+ {
+ forcePermissionRecalculation: !!forcePermissionRecalculation,
+ },
+ );
- const messageData = {
- type: messageTypes.CHANGE_ROLE,
- threadID: request.threadID,
- creatorID: viewer.userID,
- time: Date.now(),
- userIDs: memberIDs,
- newRole: request.role,
- roleName: threadInfo.roles[request.role].name,
- };
- const newMessageInfos = await createMessages(viewer, [messageData]);
+ const { viewerUpdates } = await commitMembershipChangeset(
+ viewer,
+ changeset,
+ forcePermissionRecalculation
+ ? { changedThreadIDs: new Set([request.threadID]) }
+ : undefined,
+ );
+
+ let newMessageInfos = [];
+ if (!silenceNewMessages) {
+ const messageData = {
+ type: messageTypes.CHANGE_ROLE,
+ threadID: request.threadID,
+ creatorID: viewer.userID,
+ time: Date.now(),
+ userIDs: memberIDs,
+ newRole: request.role,
+ roleName: threadInfo.roles[request.role].name,
+ };
+ newMessageInfos = await createMessages(viewer, [messageData]);
+ }
return { updatesResult: { newUpdates: viewerUpdates }, newMessageInfos };
}
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
@@ -397,13 +397,23 @@
+threadID: string,
};
-export type RoleModificationRequest = {
+type CreateRoleAction = {
+community: string,
+name: string,
+permissions: $ReadOnlyArray<UserSurfacedPermission>,
- +action: 'create_role' | 'edit_role',
+ +action: 'create_role',
};
+type EditRoleAction = {
+ +community: string,
+ +existingRoleID: string,
+ +name: string,
+ +permissions: $ReadOnlyArray<UserSurfacedPermission>,
+ +action: 'edit_role',
+};
+
+export type RoleModificationRequest = CreateRoleAction | EditRoleAction;
+
export type RoleModificationResult = {
+threadInfo: RawThreadInfo,
+updatesResult: {
diff --git a/native/roles/create-roles-header-right-button.react.js b/native/roles/create-roles-header-right-button.react.js
--- a/native/roles/create-roles-header-right-button.react.js
+++ b/native/roles/create-roles-header-right-button.react.js
@@ -1,6 +1,7 @@
// @flow
import { useNavigation } from '@react-navigation/native';
+import invariant from 'invariant';
import * as React from 'react';
import { TouchableOpacity, Text } from 'react-native';
@@ -23,7 +24,8 @@
};
function CreateRolesHeaderRightButton(props: Props): React.Node {
- const { threadInfo, action, roleName, rolePermissions } = props.route.params;
+ const { threadInfo, action, existingRoleID, roleName, rolePermissions } =
+ props.route.params;
const { setRoleCreationFailed } = props;
const navigation = useNavigation();
const styles = useStyles(unboundStyles);
@@ -37,19 +39,33 @@
);
const onPressCreate = React.useCallback(() => {
- if (threadRoleNames.includes(roleName)) {
+ if (threadRoleNames.includes(roleName) && action === 'create_role') {
setRoleCreationFailed(true);
return;
}
- dispatchActionPromise(
- modifyCommunityRoleActionTypes,
- callModifyCommunityRole({
+ let callModifyCommunityRoleParams = {};
+ if (action === 'create_role') {
+ callModifyCommunityRoleParams = {
+ community: threadInfo.id,
+ action,
+ name: roleName,
+ permissions: [...rolePermissions],
+ };
+ } else {
+ invariant(existingRoleID, 'existingRoleID should be set');
+ callModifyCommunityRoleParams = {
community: threadInfo.id,
action,
+ existingRoleID,
name: roleName,
permissions: [...rolePermissions],
- }),
+ };
+ }
+
+ dispatchActionPromise(
+ modifyCommunityRoleActionTypes,
+ callModifyCommunityRole(callModifyCommunityRoleParams),
);
navigation.goBack();
@@ -58,6 +74,7 @@
dispatchActionPromise,
threadInfo,
action,
+ existingRoleID,
roleName,
rolePermissions,
navigation,
@@ -65,6 +82,7 @@
threadRoleNames,
]);
+ const headerRightText = action === 'create_role' ? 'Create' : 'Save';
const shouldHeaderRightBeDisabled = roleName.length === 0;
const createButton = React.useMemo(() => {
const textStyle = shouldHeaderRightBeDisabled
@@ -76,11 +94,12 @@
onPress={onPressCreate}
disabled={shouldHeaderRightBeDisabled}
>
- <Text style={textStyle}>Create</Text>
+ <Text style={textStyle}>{headerRightText}</Text>
</TouchableOpacity>
);
}, [
shouldHeaderRightBeDisabled,
+ headerRightText,
styles.createButtonDisabled,
styles.createButton,
onPressCreate,
diff --git a/native/roles/create-roles-screen.react.js b/native/roles/create-roles-screen.react.js
--- a/native/roles/create-roles-screen.react.js
+++ b/native/roles/create-roles-screen.react.js
@@ -26,6 +26,7 @@
export type CreateRolesScreenParams = {
+threadInfo: ThreadInfo,
+action: 'create_role' | 'edit_role',
+ +existingRoleID?: string,
+roleName: string,
+rolePermissions: $ReadOnlySet<UserSurfacedPermission>,
};
@@ -43,6 +44,7 @@
const {
threadInfo,
action,
+ existingRoleID,
roleName: defaultRoleName,
rolePermissions: defaultRolePermissions,
} = props.route.params;
@@ -123,10 +125,18 @@
props.navigation.setParams({
threadInfo,
action,
+ existingRoleID,
roleName: customRoleName,
rolePermissions: selectedPermissions,
}),
- [props.navigation, threadInfo, action, customRoleName, selectedPermissions],
+ [
+ props.navigation,
+ threadInfo,
+ action,
+ existingRoleID,
+ customRoleName,
+ selectedPermissions,
+ ],
);
const filteredUserSurfacedPermissionOptions =
diff --git a/native/roles/role-panel-entry.react.js b/native/roles/role-panel-entry.react.js
--- a/native/roles/role-panel-entry.react.js
+++ b/native/roles/role-panel-entry.react.js
@@ -1,6 +1,7 @@
// @flow
import { useActionSheet } from '@expo/react-native-action-sheet';
+import invariant from 'invariant';
import * as React from 'react';
import { View, Text, TouchableOpacity, Platform } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
@@ -28,6 +29,15 @@
props;
const styles = useStyles(unboundStyles);
+ const existingRoleID = React.useMemo(
+ () =>
+ Object.keys(threadInfo.roles).find(
+ roleID => threadInfo.roles[roleID].name === roleName,
+ ),
+ [roleName, threadInfo.roles],
+ );
+ invariant(existingRoleID, 'Role ID must exist for an existing role');
+
const options = React.useMemo(() => {
const availableOptions = ['Edit role'];
@@ -50,12 +60,20 @@
navigation.navigate(CreateRolesScreenRouteName, {
threadInfo,
action: 'edit_role',
+ existingRoleID,
roleName,
rolePermissions,
});
}
},
- [navigation, options, roleName, rolePermissions, threadInfo],
+ [
+ navigation,
+ options,
+ existingRoleID,
+ roleName,
+ rolePermissions,
+ threadInfo,
+ ],
);
const activeTheme = useSelector(state => state.globalThemeInfo.activeTheme);

File Metadata

Mime Type
text/plain
Expires
Sun, Dec 7, 6:48 PM (20 h, 53 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5842289
Default Alt Text
D8564.1765133326.diff (12 KB)

Event Timeline