Skip to content
Snippets Groups Projects
Commit 50ea8be2 authored by Bjarke Madsen's avatar Bjarke Madsen
Browse files

update user management to immediately edit

parent 8b8622dc
Branches
Tags
No related merge requests found
......@@ -28,9 +28,7 @@ class ROLES(Enum):
uuid_pk = Annotated[UUID, mapped_column(primary_key=True, default=lambda _: uuid4())]
int_pk_fkNREN = Annotated[int, mapped_column(ForeignKey("nren.id"), primary_key=True)]
# TODO: active should be default False and require admin approval
# (ROLES.admin) when there is a way to handle this in a GUI
active = Annotated[bool, mapped_column(db.Boolean, default=True)]
active = Annotated[bool, mapped_column(db.Boolean, default=False)]
roles = Annotated[ROLES, mapped_column(db.Enum(ROLES), default=ROLES.user)]
# annotations for many-to-many relationships
......
......@@ -102,23 +102,22 @@ def all_users_view() -> Any:
return jsonify(entries)
@routes.route('/', methods=['PUT'])
@routes.route('/<user_id>', methods=['PUT'])
@common.require_accepts_json
@login_required
@admin_required
def update_user_view() -> Any:
def update_user_view(user_id) -> Any:
"""
Handler for updating user information via PUT request.
Handler for /api/user/<user_id> via PUT request.
Request data should be in JSON format with the fields you want to update.
The response will be formatted the same way as in the current_user_view.
Example JSON request data:
{
"name": "Updated Name",
"email": "updated@example.com",
"roles": ["role1", "role2"],
"active": False
"role": "admin",
"active": False,
"nren": "1"
}
:return:
......@@ -126,15 +125,20 @@ def update_user_view() -> Any:
def _update_user_data(user: User, update_data: dict):
new_roles = update_data.get('roles', user.roles.value)
new_role = update_data.get('role', user.roles.value)
if new_roles != user.roles.value:
if user == current_user:
return jsonify({'success': False, 'message': 'Cannot change your own role.'}), 400
if 'role' in update_data:
if new_role != user.roles.value:
if user == current_user:
return jsonify({'success': False, 'message': 'Cannot change your own role.'}), 400
user.roles = new_roles
try:
new_role = ROLES(new_role)
except ValueError:
return jsonify({'success': False, 'message': 'Invalid role.'}), 400
user.roles = new_role
if 'active' in update_data:
elif 'active' in update_data:
_active = bool(update_data['active'])
if _active != user.active:
if user == current_user:
......@@ -142,25 +146,23 @@ def update_user_view() -> Any:
user.active = _active
nrens = update_data.get('nrens', None)
if nrens is not None:
elif 'nren' in update_data:
new_nrens = None
try:
new_nrens = db.session.scalars(select(NREN).filter(NREN.id.in_(update_data['nrens']))).all()
new_nrens = db.session.scalars(select(NREN).filter(NREN.id == update_data['nren'])).all()
user.nrens = [nren for nren in new_nrens]
db.session.commit()
except Exception:
return jsonify({'success': False, 'message': 'No valid NREN IDs provided.'}), 400
return jsonify({'success': True, 'message': 'User updated successfully'})
db.session.commit()
return jsonify({'success': True, 'message': 'User updated successfully', 'user': _extract_user(user)})
body = request.get_json()
if not body:
return jsonify({"success": False, 'message': 'Invalid request'}), 400
user_id = body.get("id")
if not user_id:
return jsonify({"success": False, 'message': 'No user ID provided in the request data.'}), 400
return jsonify({"success": False, 'message': 'Invalid user ID'}), 400
user = db.session.execute(select(User).filter_by(id=user_id)).scalar()
......
......@@ -8,8 +8,7 @@ export interface User {
permissions: {
admin: boolean,
active: boolean,
},
editable: boolean,
}
}
export interface Nren {
......
import React, { useState, useEffect, useContext } from "react";
import { Button, Table } from "react-bootstrap";
import { Container, Row, Table } from "react-bootstrap";
import { userContext } from "./providers/UserProvider";
import { User, Nren } from "./Schema";
......@@ -26,25 +26,27 @@ async function fetchNrens(): Promise<Nren[]> {
}
}
const saveUser = (user) => {
const updateUser = async (id, changes) => {
const body = {
id: id,
...changes
}
const requestOptions = {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(user),
body: JSON.stringify(body),
};
fetch('/api/user', requestOptions)
.then((response) => {
return response.json();
})
.catch((error) => {
const response = error.response;
response.json().then((json) => {
console.log(json);
});
});
const response = await fetch(`/api/user/${id}`, requestOptions)
const data = await response.json()
if (!response.ok) {
throw new Error(data.message);
}
return data.user;
};
......@@ -52,10 +54,8 @@ function UserManagementComponent() {
const [users, setUsers] = useState<User[]>([]);
const [nrens, setNrens] = useState<Nren[]>([]);
const { user: loggedInUser } = useContext(userContext);
console.log(loggedInUser)
useEffect(() => {
// Fetch user
fetchUsers().then((userList) => {
setUsers(userList);
});
......@@ -65,139 +65,107 @@ function UserManagementComponent() {
})
}, []);
const handleEdit = (user: User) => {
const index = users.findIndex((u) => u.id === user.id);
const updatedUsers = [...users];
updatedUsers[index].editable = true;
setUsers(updatedUsers);
};
const handleSave = (user: User) => {
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>, user: User) => {
const index = users.findIndex((u) => u.id === user.id);
const updatedUsers = [...users];
updatedUsers[index].editable = false;
setUsers(updatedUsers);
// Persist the changes to the server
saveUser(user);
};
const { name } = event.target;
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>, user: User) => {
const index = users.findIndex((u) => u.id === user.id);
const updatedUsers = [...users];
const update = {};
const { name, type } = event.target;
if (type === 'checkbox') {
user[name] = (event.target as HTMLInputElement).checked ? true : false;
if (name === 'active') {
update[name] = (event.target as HTMLInputElement).checked
} else {
user[name] = (event.target as HTMLInputElement).value;
update[name] = event.target.value;
}
if (event.target.name === 'nrens') {
updatedUsers[index].nrens = [event.target.value];
console.log("updated nren")
}
setUsers(updatedUsers);
updateUser(user.id, update).then((user) => {
updatedUsers[index] = user;
setUsers(updatedUsers);
}).catch((error) => {
alert(error.message);
});
};
const findNren = (value) => {
return nrens.find((nren) => {
return nren.id == value || nren.name == value
})?.name
})?.id
}
return (
<div>
<h1> User Management Page</h1>
<Table>
<thead>
<tr>
<th className='pt-3' style={{ border: "1px solid #ddd" }}> Id </th>
<th className='pt-3' style={{ border: "1px solid #ddd" }}> Active </th>
<th className='pt-3' style={{ border: "1px solid #ddd" }}> Roles </th>
<th className='pt-3' style={{ border: "1px solid #ddd" }}> Email</th>
<th className='pt-3' style={{ border: "1px solid #ddd" }}> Full Name</th>
<th className='pt-3' style={{ border: "1px solid #ddd" }}> OIDC Sub</th>
<th className='pt-3' style={{ border: "1px solid #ddd" }}> NREN</th>
<th className="pt-3" style={{ border: "1px solid #ddd" }}>Actions</th>
</tr>
</thead>
<tbody>
{users.map(user => (
<tr key={user.id}>
<td style={{ border: "1px dotted #ddd" }}>{user.id}</td>
<td style={{ border: "1px dotted #ddd" }}>
{user.editable ? (
<input
<Container style={{ maxWidth: '90vw', }}>
<Row>
<h1> User Management Page</h1>
<Table>
<thead>
<tr>
<th className='pt-3' style={{ border: "1px solid #ddd" }}>Id</th>
<th className='pt-3' style={{ border: "1px solid #ddd" }}>Active</th>
<th className='pt-3' style={{ border: "1px solid #ddd" }}>Role</th>
<th className='pt-3' style={{ border: "1px solid #ddd" }}>Email</th>
<th className='pt-3' style={{ border: "1px solid #ddd" }}>Full Name</th>
<th className='pt-3' style={{ border: "1px solid #ddd" }}>OIDC Sub</th>
<th className='pt-3' style={{ border: "1px solid #ddd" }}>NREN</th>
</tr>
</thead>
<tbody>
{users.map(user => (
<tr key={user.id}>
<td style={{ border: "1px dotted #ddd" }}>{user.id}</td>
<td style={{ border: "1px dotted #ddd" }}>
{user.id == loggedInUser.id ? 'Active' : <input
type="checkbox"
name="active"
checked={user.permissions.active}
onChange={(event) => handleInputChange(event, user)}
/>
) : ((user.permissions.active ? 'Active' : 'Inactive'))}
</td>
<td style={{ border: "1px dotted #ddd" }}>
{user.editable ? (
<select
name="roles"
value={user.role}
onChange={(event) => handleInputChange(event, user)}>
/>}
</td>
<td style={{ border: "1px dotted #ddd" }}>
{user.id == loggedInUser.id ? (user.role.charAt(0).toUpperCase()
+ user.role.slice(1)) : <select
name="role"
defaultValue={user.role}
onChange={(event) => handleInputChange(event, user)}>
<option value="admin">Admin</option>
<option value="user">User</option>
</select>
) : (
user.role
)}
</td>
<td style={{ border: "1px dotted #ddd" }}>
{(user.email)}
</td>
<td style={{ border: "1px dotted #ddd" }}>
{(user.name)}
</td>
<td style={{ border: "1px dotted #ddd" }}>
{(user.oidc_sub)}
</td>
<td style={{ border: '1px dotted #ddd' }}>
{user.editable ? (
nrens.length > 0 ? (
<select
name="nrens"
multiple={false}
defaultValue={user.nrens.length > 0 ? nrens.find((nren) => {
return nren.name == user.nrens[0] || nren.id == user.nrens[0]
})?.id : undefined}
onChange={(event) => handleInputChange(event, user)}>
<option>
Select NREN
</select>}
</td>
<td style={{ border: "1px dotted #ddd" }}>
{user.email}
</td>
<td style={{ border: "1px dotted #ddd" }}>
{user.name}
</td>
<td style={{ border: "1px dotted #ddd" }}>
{user.oidc_sub}
</td>
<td style={{ border: '1px dotted #ddd' }}>
<select
name="nren"
multiple={false}
defaultValue={user.nrens.length > 0 ? findNren(user.nrens[0]) : undefined}
onChange={(event) => handleInputChange(event, user)}>
<option>
Select NREN
</option>
{nrens.map((nren) => (
<option key={nren.id} value={nren.id}>
{nren.name}
</option>
{nrens.map((nren) => (
<option key={nren.id} value={nren.id}>
{nren.name}
</option>
))}
</select>
) : (
<div>No options available</div>
)
) : (
(user.nrens?.length || 0) > 0 ? findNren(user.nrens[0]) : "NREN not selected"
)}
</td>
<td style={{ border: "1px dotted #ddd" }}>
{loggedInUser?.role == 'admin' && user.editable ? (
<Button onClick={() => handleSave(user)}>Save</Button>
) : loggedInUser?.role == 'admin' && !user.editable ? (
<Button onClick={() => handleEdit(user)}>Edit</Button>
) : null}
</td>
</tr>
))}
</tbody>
))}
</select>
</td>
</tr>
))}
</tbody>
</Table>
</Row>
</Table>
</div>
</Container>
);
}
......
......@@ -12,7 +12,7 @@ async function fetchUser(): Promise<User> {
return user
}
const anonymousUser: User = { 'name': '', email: '', permissions: { admin: false, active: false }, editable: false, id: '', nrens: [], oidc_sub: '', role: '' };
const anonymousUser: User = { 'name': '', email: '', permissions: { admin: false, active: false }, id: '', nrens: [], oidc_sub: '', role: '' };
const userContext = createContext<{
user: User;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment