Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unique Resident IDs #200

Merged
merged 13 commits into from
Dec 3, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions backend/app/rest/residents_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ def add_resident():
400,
)

# Check for the existence of a resident prior to adding them
fmt_resident_id = resident.get("initial") + str(resident.get("room_num"))
try:
res = residents_service.get_residents(False, 1, 10, fmt_resident_id)
if len(res["residents"]) > 0:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When adding a new resident, we simply want to check for a result length greater than 0 (if there's even one other resident that has the same ID, then this is a duplicate resident => reject)

return jsonify({"error": "Resident already 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
Expand All @@ -41,6 +51,17 @@ def update_resident(resident_id):
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 = updated_resident.get("initial") + str(updated_resident.get("room_num"))
try:
res = residents_service.get_residents(False, 1, 10, fmt_resident_id)
if len(res["residents"]) == 1 and res["residents"][0]["id"] != resident_id:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When editing a resident, there are two cases for which the result of get_residents could be >=1 in size:

  1. There was no change to the resident ID (i.e. a resident with ID AA123 has their building num changed ; ID remains AA123 and so when we get residents with the ID fmt_resident_id = AA123, then we get 1)

  2. We changed another resident to have a pre-existing resident ID (there are residents AA123 and AA321 ; resident AA123 has their room number changed to 321, resulting in two duplicate entries ; thus when we fetch the residents with the ID AA321, we end up with a result of 1)

We want case #2 to be marked as INVALID but case #1 to be VALID. As such, our checking logic must check for:

  • If there's already a result being returned
  • If the result has the same database ID as us (if it's the same, then we're editing the same record, meaning this change is valid ; if the database IDs are different, then this means that there is a different resident with the same formatted ID as us)

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(
Expand Down
19 changes: 17 additions & 2 deletions frontend/src/APIClients/ResidentAPIClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
CountResidentsResponse,
CreateResidentParams,
EditResidentParams,
ErrorResponse,
} from "../types/ResidentTypes";
import { getLocalStorageObjProperty } from "../utils/LocalStorageUtils";
import baseAPIClient from "./BaseAPIClient";
Expand Down Expand Up @@ -60,7 +61,7 @@ const createResident = async ({
roomNum,
dateJoined,
buildingId,
}: CreateResidentParams): Promise<boolean> => {
}: CreateResidentParams): Promise<boolean | ErrorResponse> => {
try {
const bearerToken = `Bearer ${getLocalStorageObjProperty(
AUTHENTICATED_USER_KEY,
Expand All @@ -73,6 +74,13 @@ const createResident = async ({
);
return true;
} catch (error) {
const axiosErr = (error as any) as AxiosError;

if (axiosErr.response && axiosErr.response.status === 409) {
return {
errMessage: "Resident with the specified user ID already exists."
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, we're propagating the error message displayed in the toast through the API client

};
}
return false;
}
};
Expand Down Expand Up @@ -103,7 +111,7 @@ const editResident = async ({
dateJoined,
buildingId,
dateLeft,
}: EditResidentParams): Promise<boolean> => {
}: EditResidentParams): Promise<boolean | ErrorResponse> => {
try {
const bearerToken = `Bearer ${getLocalStorageObjProperty(
AUTHENTICATED_USER_KEY,
Expand All @@ -116,6 +124,13 @@ const editResident = async ({
);
return true;
} catch (error) {
const axiosErr = (error as any) as AxiosError;

if (axiosErr.response && axiosErr.response.status === 409) {
return {
errMessage: "Resident with the specified user ID already exists."
};
}
return false;
}
};
Expand Down
24 changes: 19 additions & 5 deletions frontend/src/components/forms/CreateResident.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ import {
import { AddIcon } from "@chakra-ui/icons";
import { SingleDatepicker } from "chakra-dayzed-datepicker";
import { Col, Row } from "react-bootstrap";
import CreateToast from "../common/Toasts";

import selectStyle from "../../theme/forms/selectStyles";
import { singleDatePickerStyle } from "../../theme/forms/datePickerStyles";
import ResidentAPIClient from "../../APIClients/ResidentAPIClient";
import { convertToString } from "../../helper/dateHelpers";
import isResidentErrorResponse from "../../helper/residentError";

type Props = {
getRecords: (pageNumber: number) => Promise<void>;
Expand Down Expand Up @@ -61,17 +63,30 @@ const CreateResident = ({
const [isOpen, setIsOpen] = useState(false);
const [showAlert, setShowAlert] = useState(false);

const newToast = CreateToast();

const ROOM_ERROR_TEXT = `Room Number is required and must only contain numbers.`;
const addResident = async () => {
await ResidentAPIClient.createResident({
const res = await ResidentAPIClient.createResident({
initial: initials.toUpperCase(),
roomNum: parseInt(roomNumber, 10),
dateJoined: convertToString(moveInDate),
buildingId,
});
getRecords(1);
countResidents();
setUserPageNum(1);

if (isResidentErrorResponse(res)) {
newToast(
"Error creating resident",
res.errMessage,
"error"
)
}
else if (res) {
getRecords(1);
countResidents();
setUserPageNum(1);
setShowAlert(true)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We move the call to here since the showAlert state controls the visibility of a success alert ; we only want the success state to be displayed if we get back a boolean result with value true

}
};

const handleInitialsChange = (e: { target: { value: unknown } }) => {
Expand Down Expand Up @@ -146,7 +161,6 @@ const CreateResident = ({

addResident();
setIsOpen(false);
setShowAlert(true);
};

// Timer to remove alert
Expand Down
13 changes: 11 additions & 2 deletions frontend/src/components/forms/EditResident.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import selectStyle from "../../theme/forms/selectStyles";
import { singleDatePickerStyle } from "../../theme/forms/datePickerStyles";
import CreateToast from "../common/Toasts";
import { convertToDate, convertToString } from "../../helper/dateHelpers";
import isResidentErrorResponse from '../../helper/residentError'

// TODO: Connect to Buidings table
const BUILDINGS = [
Expand Down Expand Up @@ -80,14 +81,22 @@ const EditResident = ({
dateLeft: moveOutDate ? convertToString(moveOutDate) : undefined,
});

if (res != null) {
if (isResidentErrorResponse(res)) {
newToast(
"Error updating resident",
res.errMessage,
"error"
)
}
else if (res !== null && res) {
newToast(
"Resident updated",
"Resident has been successfully updated",
"success",
);
getRecords(userPageNum)
} else {
}
else {
newToast(
"Error updating resident",
"Resident was unable to be updated",
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/helper/residentError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {
ErrorResponse,
} from "../types/ResidentTypes";

const isResidentErrorResponse = (res: boolean | ErrorResponse) : res is ErrorResponse => {
return (typeof res !== 'boolean' && 'errMessage' in res);
}

export default isResidentErrorResponse
4 changes: 4 additions & 0 deletions frontend/src/types/ResidentTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,7 @@ export type CreateResidentParams = Omit<
export type EditResidentParams = Omit<Resident, "residentId" | "building"> & {
buildingId: number;
};

export type ErrorResponse = {
errMessage: string;
}
Loading