Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
seanh committed Oct 16, 2024
1 parent 03efa05 commit 91e8ac4
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 29 deletions.
1 change: 1 addition & 0 deletions h/security/permission_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
Permission.Group.EDIT: [
[p.group_matches_authenticated_client_authority],
[p.group_has_user_as_owner],
[p.group_has_user_as_admin],
],
Permission.Group.MEMBER_ADD: [[p.group_matches_authenticated_client_authority]],
Permission.Group.MEMBER_REMOVE: [[p.group_member_remove]],
Expand Down
2 changes: 2 additions & 0 deletions h/security/policy/_api_cookie.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
("api.groups", "POST"), # Create a new group.
("api.group", "PATCH"), # Edit an existing group.
("api.group_members", "GET"), # Get a list of a group's members.
("api.group_member", "DELETE"), # Remove a user from a group.
("api.group_member", "PATCH"), # Change a user's role in a group.
]


Expand Down
98 changes: 76 additions & 22 deletions h/static/scripts/group-forms/components/CreateEditGroupForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
Button,
CancelIcon,
TrashIcon,
IconButton,
Input,
RadioGroup,
Table,
Expand Down Expand Up @@ -356,6 +357,80 @@ export default function CreateEditGroupForm() {
}
} ,[])

function uppercaseFirstLetter(str) {
return str[0].toUpperCase() + str.slice(1);
}

function selectOptions(member) {
const options = []
Object.keys(member.api.role).forEach((role) => {
options.push(
<Select.Option value={role} key={role}>
{uppercaseFirstLetter(role)}
</Select.Option>
);
});
return options;
}

async function onClickDelete(api) {
await callAPI(api.url, {
method: api.method,
headers: api.headers,
parseResponseBody: false,
});
getMembers();
}

async function onChangeRole(api, newRole) {
await callAPI(api[newRole].url, {
method: api[newRole].method,
headers: api[newRole].headers,
json: api[newRole].body
});
getMembers();
}

function tableRows(members: Array<Object>) {
const rows = []

if (members.length > 0) {
Object.keys(members[0].api.role).forEach((key) => {
const value = members[0].api.role[key];
console.log(`${key}: ${value}`);
});
}
members.forEach((member) => {
rows.push(
<TableRow>
<TableCell>{member.username}</TableCell>
<TableCell>
{Object.keys(member.api.role).length > 0
? <Select value={member.role} buttonContent={uppercaseFirstLetter(member.role)} onChange={newRole => onChangeRole(member.api.role, newRole)}>
{selectOptions(member)}
</Select>
: <Select value={member.role} buttonContent={uppercaseFirstLetter(member.role)} disabled>
{selectOptions(member)}
</Select>
}
</TableCell>
<TableCell>
{member.api.delete
? <IconButton title="Delete" onClick={() => onClickDelete(member.api.delete)}>
<TrashIcon />
</IconButton>
: <IconButton title="Delete" disabled>
<TrashIcon />
</IconButton>
}
</TableCell>
</ TableRow>
);
});

return rows;
}

return (
<div className="text-grey-6 text-sm/relaxed">
<h1 className="mt-14 mb-8 text-grey-7 text-xl/none" data-testid="header">
Expand Down Expand Up @@ -425,28 +500,7 @@ export default function CreateEditGroupForm() {
</TableRow>
</TableHead>
<TableBody>
{members.map((member) => (
<TableRow>
<TableCell>{member.username}</TableCell>
<TableCell>
<Select value={member.role} buttonContent={member.role[0].toUpperCase() + member.role.slice(1)}>
<Select.Option value="owner" key="owner">
Owner
</Select.Option>
<Select.Option value="admin" key="admin">
Admin
</Select.Option>
<Select.Option value="moderator" key="moderator">
Moderator
</Select.Option>
<Select.Option value="member" key="member">
Member
</Select.Option>
</Select>
</TableCell>
<TableCell><TrashIcon /></TableCell>
</TableRow>
))}
{tableRows(members)}
</TableBody>
</Table>

Expand Down
12 changes: 8 additions & 4 deletions h/static/scripts/group-forms/utils/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,12 @@ export async function callAPI(
method = 'GET',
json = null,
headers = {},
parseResponseBody = true,
}: {
method?: string;
json?: object | null;
headers?: any;
parseResponseBody?: boolean;
} = {},
): Promise<object> {
const options: RequestInit = {
Expand Down Expand Up @@ -99,10 +101,12 @@ export async function callAPI(
let responseJSON;
let responseJSONError;

try {
responseJSON = await response.json();
} catch (jsonError) {
responseJSONError = jsonError;
if (parseResponseBody) {
try {
responseJSON = await response.json();
} catch (jsonError) {
responseJSONError = jsonError;
}
}

if (!response.ok) {
Expand Down
11 changes: 8 additions & 3 deletions h/views/api/groups.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from pyramid.httpexceptions import HTTPConflict, HTTPNoContent, HTTPNotFound
from pyramid.csrf import get_csrf_token
from sqlalchemy import select
from sqlalchemy.orm import selectinload

Expand Down Expand Up @@ -159,11 +160,13 @@ def read_members(context: GroupContext, request):
.where(GroupMembership.group == context.group)
)

csrf_token = get_csrf_token(request)

for membership in memberships:
membership_dict = {
"role": membership.role,
**UserJSONPresenter(membership.user).asdict(),
"api": {},
"api": {"role": {}},
}

# If the authenticated user has permission to remove this member from
Expand All @@ -175,13 +178,14 @@ def read_members(context: GroupContext, request):
if request.has_permission(
Permission.Group.MEMBER_REMOVE, membership_remove_context
):
membership_dict["api"]["delete"] = {
membership_dict.setdefault("api", {})["delete"] = {
"method": "DELETE",
"url": request.route_url(
"api.group_member",
pubid=context.group.pubid,
userid=membership.user.userid,
),
"headers": {"X-CSRF-Token": csrf_token},
}

# For each possible role if the authenticated user has permission to
Expand All @@ -195,14 +199,15 @@ def read_members(context: GroupContext, request):
if membership.role != role and request.has_permission(
Permission.Group.MEMBER_EDIT, edit_membership_context
):
membership_dict["api"][f"make_{role}"] = {
membership_dict.setdefault("api", {}).setdefault("role", {})[role] = {
"method": "PATCH",
"url": request.route_url(
"api.group_member",
pubid=context.group.pubid,
userid=membership.user.userid,
),
"body": {"role": role},
"headers": {"X-CSRF-Token": csrf_token},
}

membership_dicts.append(membership_dict)
Expand Down

0 comments on commit 91e8ac4

Please sign in to comment.