Skip to content

Commit

Permalink
Restricting employee changes (#233)
Browse files Browse the repository at this point in the history
* bedazzling changes

* run ze linter

* remove first and last name from sign up page

* nav bar automatic dropdown

* run ze linter again

* logout user on own role change or deactivation

* clean up duplicate resident code and update warning message

* running the linter for the 100th time today (when will it end?)
  • Loading branch information
Connor Bechthold authored Feb 5, 2024
1 parent 556de08 commit 9d4249f
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 67 deletions.
50 changes: 10 additions & 40 deletions backend/app/rest/residents_routes.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from flask import Blueprint, current_app, jsonify, request
from ..middlewares.auth import require_authorization_by_role
from ..services.implementations.residents_service import ResidentsService
from datetime import datetime
from ..utilities.exceptions.duplicate_entity_exceptions import (
DuplicateResidentException,
)
import json

residents_service = ResidentsService(current_app.logger)
Expand All @@ -20,29 +22,13 @@ def add_resident():
jsonify({"date_left_error": "date_left cannot be less than date_joined"}),
400,
)

# Check for the existence of a resident prior to adding them
fmt_resident_id = resident.get("initial") + resident.get("room_num")
try:
existing_resident = residents_service.get_resident_by_id(fmt_resident_id)
if existing_resident:
return (
jsonify(
{
"error": "Resident with ID {fmt_resident_id} already exists.".format(
fmt_resident_id=fmt_resident_id
)
}
),
409,
)
except Exception as e:
error_message = getattr(e, "message", None)
return jsonify({"error": (error_message if error_message else str(e))}), 500

try:
created_resident = residents_service.add_resident(resident)
return jsonify(created_resident), 201

except DuplicateResidentException as e:
error_message = getattr(e, "message", None)
return jsonify({"error": (error_message if error_message else str(e))}), 409
except Exception as e:
error_message = getattr(e, "message", None)
return jsonify({"error": (error_message if error_message else str(e))}), 500
Expand All @@ -61,25 +47,6 @@ def update_resident(resident_id):
400,
)

# Check for the existence of a resident prior to adding them
fmt_resident_id = updated_resident.get("initial") + updated_resident.get("room_num")
try:
existing_resident = residents_service.get_resident_by_id(fmt_resident_id)
if existing_resident and existing_resident["id"] != resident_id:
return (
jsonify(
{
"error": "Resident with ID {fmt_resident_id} already exists.".format(
fmt_resident_id=fmt_resident_id
)
}
),
409,
)
except Exception as e:
error_message = getattr(e, "message", None)
return jsonify({"error": (error_message if error_message else str(e))}), 500

try:
updated_resident = residents_service.update_resident(
resident_id, updated_resident
Expand All @@ -94,6 +61,9 @@ def update_resident(resident_id):
),
201,
)
except DuplicateResidentException as e:
error_message = getattr(e, "message", None)
return jsonify({"error": (error_message if error_message else str(e))}), 409
except Exception as e:
error_message = getattr(e, "message", None)
return jsonify({"error": (error_message if error_message else str(e))}), 500
Expand Down
56 changes: 37 additions & 19 deletions backend/app/services/implementations/residents_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
from ...models.log_record_residents import LogRecordResidents
from ...models.buildings import Buildings
from ...models import db
from ...utilities.exceptions.duplicate_entity_exceptions import (
DuplicateResidentException,
)
from datetime import datetime
from sqlalchemy.sql.expression import or_, and_
from pytz import timezone
Expand Down Expand Up @@ -135,28 +138,43 @@ def add_resident(self, resident):
db.session.add(new_resident)
db.session.commit()
return resident
except Exception as postgres_error:
raise postgres_error
except Exception as e:
if type(e).__name__ == "IntegrityError":
raise DuplicateResidentException(
resident["initial"] + resident["room_num"]
)
else:
raise e

def update_resident(self, resident_id, updated_resident):
if "date_left" in updated_resident:
create_update_resident = Residents.query.filter_by(id=resident_id).update(
{
Residents.date_left: updated_resident["date_left"],
**updated_resident,
}
)
else:
create_update_resident = Residents.query.filter_by(id=resident_id).update(
{Residents.date_left: None, **updated_resident}
)
if not create_update_resident:
raise Exception(
"Resident with id {resident_id} not found".format(
resident_id=resident_id
try:
if "date_left" in updated_resident:
create_update_resident = Residents.query.filter_by(
id=resident_id
).update(
{
Residents.date_left: updated_resident["date_left"],
**updated_resident,
}
)
)
db.session.commit()
else:
create_update_resident = Residents.query.filter_by(
id=resident_id
).update({Residents.date_left: None, **updated_resident})
if not create_update_resident:
raise Exception(
"Resident with id {resident_id} not found".format(
resident_id=resident_id
)
)
db.session.commit()
except Exception as e:
if type(e).__name__ == "IntegrityError":
raise DuplicateResidentException(
updated_resident["initial"] + updated_resident["room_num"]
)
else:
raise e

def delete_resident(self, resident_id):
resident_log_records = LogRecordResidents.query.filter_by(
Expand Down
10 changes: 10 additions & 0 deletions backend/app/utilities/exceptions/duplicate_entity_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,13 @@ class DuplicateUserException(Exception):
def __init__(self, email):
message = f"User with email {email} already exists."
super().__init__(message)


class DuplicateResidentException(Exception):
"""
Raised when an duplicate resident is encountered
"""

def __init__(self, resident_id):
message = f"Resident with ID {resident_id} already exists."
super().__init__(message)
12 changes: 12 additions & 0 deletions frontend/src/components/common/ConfirmationModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
type Props = {
header: string;
message: string;
warningMessage?: string;
isOpen: boolean;
toggleClose: () => void;
action: () => Promise<void>;
Expand All @@ -22,6 +23,7 @@ type Props = {
const ConfirmationModal = ({
header,
message,
warningMessage,
isOpen,
toggleClose,
action,
Expand All @@ -40,6 +42,16 @@ const ConfirmationModal = ({
<ModalBody>
<Box>
<Text>{message}</Text>
{warningMessage && (
<Text
fontSize="12px"
fontWeight="bold"
color="#1B2A2C"
paddingTop="10px"
>
{warningMessage}
</Text>
)}
</Box>
</ModalBody>
<ModalFooter>
Expand Down
42 changes: 37 additions & 5 deletions frontend/src/components/forms/EditEmployee.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import React, { useContext, useEffect, useState } from "react";
import {
Box,
Button,
Expand All @@ -20,10 +20,14 @@ import {
RadioGroup,
Text,
} from "@chakra-ui/react";
import { useHistory } from "react-router-dom";
import { UPDATE_EMPLOYEE_ERROR } from "../../constants/ErrorMessages";
import CreateToast from "../common/Toasts";
import { User, UserRole } from "../../types/UserTypes";
import UserAPIClient from "../../APIClients/UserAPIClient";
import AuthContext from "../../contexts/AuthContext";
import AuthAPIClient from "../../APIClients/AuthAPIClient";
import { HOME_PAGE } from "../../constants/Routes";

const RoleOptions = [
UserRole.RELIEF_STAFF,
Expand All @@ -47,6 +51,7 @@ const EditEmployee = ({
toggleClose,
}: Props): React.ReactElement => {
const [isDisabled, setIsDisabled] = useState<boolean>(false);
const history = useHistory();
const newToast = CreateToast();

const [firstName, setFirstName] = useState<string>("");
Expand All @@ -61,6 +66,8 @@ const EditEmployee = ({
const [lastNameError, setLastNameError] = useState<boolean>(false);
const [adminStatusError, setAdminStatusError] = useState<boolean>(false);

const { authenticatedUser, setAuthenticatedUser } = useContext(AuthContext);

const mapRoleToState = (role: UserRole) => {
switch (role) {
case UserRole.ADMIN:
Expand Down Expand Up @@ -145,20 +152,34 @@ const EditEmployee = ({
}

if (roleOptionIndex !== undefined) {
const newRole = RoleOptions[roleOptionIndex];
const statusCode = await UserAPIClient.updateUser({
id: employee.id,
firstName,
lastName,
role: RoleOptions[roleOptionIndex],
role: newRole,
});
if (statusCode === 201) {
newToast(
"Employee updated",
"Employee has been successfully updated",
"success",
);
getRecords(userPageNum);
handleClose();

// logout the user if they're editing themselves and change their role
if (
authenticatedUser?.id === employee.id &&
authenticatedUser?.role !== newRole
) {
const success = await AuthAPIClient.logout(authenticatedUser?.id);
if (success) {
setAuthenticatedUser(null);
history.push(HOME_PAGE);
}
} else {
getRecords(userPageNum);
handleClose();
}
} else {
newToast("Error updating employee", UPDATE_EMPLOYEE_ERROR, "error");
}
Expand Down Expand Up @@ -265,10 +286,21 @@ const EditEmployee = ({
</Text>
</Checkbox>
<Text fontSize="12px" color="#1B2A2C">
Requiring Two Factor Authentication means the employee will only
Enabling two factor authentication means the employee will only
be able to access the platform while physically in the main
building.
</Text>
{authenticatedUser?.id === employee.id && (
<Text
fontSize="12px"
fontWeight="bold"
color="#1B2A2C"
paddingTop="10px"
>
Note: Changing your role will require you to login to the
application again.
</Text>
)}
</Box>
</ModalBody>
<ModalFooter>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { RefObject, useState } from "react";
import React, { RefObject, useContext, useState } from "react";
import {
Box,
IconButton,
Expand All @@ -14,12 +14,16 @@ import {
Thead,
Tr,
} from "@chakra-ui/react";
import { useHistory } from "react-router-dom";
import { VscKebabVertical } from "react-icons/vsc";
import { User, UserRole, UserStatus } from "../../../types/UserTypes";
import EditEmployee from "../../forms/EditEmployee";
import ConfirmationModal from "../../common/ConfirmationModal";
import CreateToast from "../../common/Toasts";
import UserAPIClient from "../../../APIClients/UserAPIClient";
import AuthContext from "../../../contexts/AuthContext";
import AuthAPIClient from "../../../APIClients/AuthAPIClient";
import { HOME_PAGE } from "../../../constants/Routes";

type Props = {
users: User[];
Expand Down Expand Up @@ -91,6 +95,9 @@ const EmployeeDirectoryTable = ({
const [isDeactivateModalOpen, setIsDeactivateModalOpen] = useState(false);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);

const { authenticatedUser, setAuthenticatedUser } = useContext(AuthContext);
const history = useHistory();

const newToast = CreateToast();

const handleEditClick = (employee: User) => {
Expand All @@ -108,6 +115,13 @@ const EmployeeDirectoryTable = ({
setIsDeactivateModalOpen(true);
};

const deactivateWarningMessage = (employeeId: number) => {
if (authenticatedUser?.id === employeeId) {
return "Note: Deactivating your account will log you out of the application.";
}
return "";
};

const handleDeleteClick = (employee: User) => {
setDeletingEmployee(employee);
setIsDeleteModalOpen(true);
Expand Down Expand Up @@ -146,8 +160,18 @@ const EmployeeDirectoryTable = ({
"Employee has been successfully deactivated.",
"success",
);
getRecords(userPageNum);
setIsDeactivateModalOpen(false);

// logout the user if they're deactivating themselves
if (authenticatedUser?.id === employeeId) {
const success = await AuthAPIClient.logout(authenticatedUser?.id);
if (success) {
setAuthenticatedUser(null);
history.push(HOME_PAGE);
}
} else {
getRecords(userPageNum);
setIsDeactivateModalOpen(false);
}
} else {
newToast(
"Error Deactivating Employee",
Expand Down Expand Up @@ -282,6 +306,7 @@ const EmployeeDirectoryTable = ({
message={deactivateConfirmationMessage(
`${deactivatingEmployee.firstName} ${deactivatingEmployee.lastName}`,
)}
warningMessage={deactivateWarningMessage(deactivatingEmployee.id)}
isOpen={isDeactivateModalOpen}
action={() => deactivateEmployee(deactivatingEmployee.id)}
toggleClose={() => setIsDeactivateModalOpen(false)}
Expand Down

0 comments on commit 9d4249f

Please sign in to comment.