Skip to content

Commit 5b73871

Browse files
author
Connor Bechthold
authored
Bug squashing (#225)
* fix email verification bug * fix overflow and flagged checkbox bugs * show employee full name in log record modals * increase z index on navbar so it doesn't overlap with filter components * fix date bugs with home page and csv convertor * touch up signup, login, and user flows for non-admin users * touchup spinner and employee create/edit form limitations * run ze linter * fix employee name display bug
1 parent 3fe92d3 commit 5b73871

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+762
-789
lines changed

backend/app/rest/tags_routes.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99

1010
@blueprint.route("/", methods=["GET"], strict_slashes=False)
11-
@require_authorization_by_role({"Admin"})
11+
@require_authorization_by_role({"Relief Staff", "Regular Staff", "Admin"})
1212
def get_tags():
1313
"""
1414
Get tags.

backend/app/rest/user_routes.py

+5-7
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from ..services.implementations.user_service import UserService
1414
from ..utilities.csv_utils import generate_csv_from_list
1515
from ..utilities.exceptions.auth_exceptions import UserNotInvitedException
16+
from ..utilities.exceptions.duplicate_entity_exceptions import DuplicateUserException
1617

1718

1819
user_service = UserService(current_app.logger)
@@ -136,15 +137,12 @@ def create_user():
136137
user = CreateInvitedUserDTO(**request.json)
137138
created_user = user_service.create_invited_user(user)
138139
return jsonify(created_user.__dict__), 201
140+
except DuplicateUserException as e:
141+
error_message = getattr(e, "message", None)
142+
return jsonify({"error": (error_message if error_message else str(e))}), 409
139143
except Exception as e:
140144
error_message = getattr(e, "message", None)
141-
status_code = None
142-
if str(e) == "User already exists":
143-
status_code = 409
144-
145-
return jsonify({"error": (error_message if error_message else str(e))}), (
146-
status_code if status_code else 500
147-
)
145+
return jsonify({"error": (error_message if error_message else str(e))}), 500
148146

149147

150148
@blueprint.route("/activate-user", methods=["POST"], strict_slashes=False)

backend/app/services/implementations/log_records_service.py

+15-17
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from ...models.tags import Tag
66
from ...models import db
77
from datetime import datetime
8-
from pytz import timezone
8+
from pytz import timezone, utc
99
from sqlalchemy import text
1010

1111

@@ -32,6 +32,10 @@ def add_record(self, log_record):
3232
del new_log_record["residents"]
3333
del new_log_record["tags"]
3434

35+
new_log_record["datetime"] = datetime.fromisoformat(
36+
new_log_record["datetime"].replace("Z", "+00:00")
37+
).replace(tzinfo=utc)
38+
3539
try:
3640
new_log_record = LogRecords(**new_log_record)
3741
self.construct_residents(new_log_record, residents)
@@ -85,7 +89,7 @@ def to_json_list(self, logs):
8589
"tags": log[10] if log[10] else [],
8690
"note": log[11],
8791
"flagged": log[12],
88-
"datetime": str(log[13].astimezone(timezone("US/Eastern"))),
92+
"datetime": log[13].isoformat(),
8993
}
9094
)
9195
return logs_list
@@ -128,21 +132,15 @@ def filter_by_attn_tos(self, attn_tos):
128132

129133
def filter_by_date_range(self, date_range):
130134
sql = ""
131-
if len(date_range) > 0:
132-
if date_range[0] != "":
133-
start_date = datetime.strptime(date_range[0], "%Y-%m-%d").replace(
134-
hour=0, minute=0
135-
)
136-
sql += f"\ndatetime>='{start_date}'"
137-
if date_range[-1] != "":
138-
end_date = datetime.strptime(
139-
date_range[len(date_range) - 1], "%Y-%m-%d"
140-
).replace(hour=23, minute=59)
141-
142-
if sql == "":
143-
sql += f"\ndatetime<='{end_date}'"
144-
else:
145-
sql += f"\nAND datetime<='{end_date}'"
135+
if date_range[0] is not None:
136+
start_date = date_range[0].replace("Z", "+00:00")
137+
sql += f"\ndatetime>='{start_date}'"
138+
if date_range[-1] is not None:
139+
end_date = date_range[-1].replace("Z", "+00:00")
140+
if sql == "":
141+
sql += f"\ndatetime<='{end_date}'"
142+
else:
143+
sql += f"\nAND datetime<='{end_date}'"
146144
return sql
147145

148146
def filter_by_tags(self, tags):

backend/app/services/implementations/residents_service.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -205,11 +205,15 @@ def get_residents(self, return_all, page_number, results_per_page, filters=None)
205205
)
206206

207207
if not return_all:
208-
residents_results = residents_results.order_by(Residents.last_modified.desc()).limit(results_per_page).offset(
209-
(page_number - 1) * results_per_page
208+
residents_results = (
209+
residents_results.order_by(Residents.last_modified.desc())
210+
.limit(results_per_page)
211+
.offset((page_number - 1) * results_per_page)
210212
)
211213
else:
212-
residents_results = residents_results.order_by(Residents.last_modified.desc()).all()
214+
residents_results = residents_results.order_by(
215+
Residents.last_modified.desc()
216+
).all()
213217

214218
return {
215219
"residents": self.to_residents_json_list(

backend/app/services/implementations/user_service.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
UserNotInvitedException,
99
EmailAlreadyInUseException,
1010
)
11+
from ...utilities.exceptions.duplicate_entity_exceptions import DuplicateUserException
1112

1213

1314
class UserService(IUserService):
@@ -197,10 +198,11 @@ def create_invited_user(self, user):
197198
db.session.add(user_entry)
198199
db.session.commit()
199200
else:
200-
raise Exception("User already exists")
201-
user_dict = UserService.__user_to_dict_and_remove_auth_id(user_entry)
201+
raise DuplicateUserException(user.email)
202202

203+
user_dict = UserService.__user_to_dict_and_remove_auth_id(user_entry)
203204
return UserDTO(**user_dict)
205+
204206
except Exception as e:
205207
db.session.rollback()
206208
reason = getattr(e, "message", None)

backend/app/utilities/exceptions/duplicate_entity_exceptions.py

+10
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,13 @@ class DuplicateTagException(Exception):
66
def __init__(self, tag_name):
77
message = f"Tag with name {tag_name} already exists."
88
super().__init__(message)
9+
10+
11+
class DuplicateUserException(Exception):
12+
"""
13+
Raised when an duplicate user is encountered
14+
"""
15+
16+
def __init__(self, email):
17+
message = f"User with email {email} already exists."
18+
super().__init__(message)

backend/migrations/versions/a1f05c8f324c_add_last_modified_to_residents.py

+25-14
Original file line numberDiff line numberDiff line change
@@ -10,31 +10,42 @@
1010

1111

1212
# revision identifiers, used by Alembic.
13-
revision = 'a1f05c8f324c'
14-
down_revision = '117790caec65'
13+
revision = "a1f05c8f324c"
14+
down_revision = "117790caec65"
1515
branch_labels = None
1616
depends_on = None
1717

1818

1919
def upgrade():
2020
# ### commands auto generated by Alembic - please adjust! ###
21-
with op.batch_alter_table('log_record_tag', schema=None) as batch_op:
22-
batch_op.drop_constraint('log_record_tag_tag_id_fkey', type_='foreignkey')
23-
batch_op.create_foreign_key(None, 'tags', ['tag_id'], ['tag_id'], ondelete='CASCADE')
24-
25-
with op.batch_alter_table('residents', schema=None) as batch_op:
26-
batch_op.add_column(sa.Column('last_modified', sa.DateTime(), server_default=sa.text('now()'), nullable=False))
21+
with op.batch_alter_table("log_record_tag", schema=None) as batch_op:
22+
batch_op.drop_constraint("log_record_tag_tag_id_fkey", type_="foreignkey")
23+
batch_op.create_foreign_key(
24+
None, "tags", ["tag_id"], ["tag_id"], ondelete="CASCADE"
25+
)
26+
27+
with op.batch_alter_table("residents", schema=None) as batch_op:
28+
batch_op.add_column(
29+
sa.Column(
30+
"last_modified",
31+
sa.DateTime(),
32+
server_default=sa.text("now()"),
33+
nullable=False,
34+
)
35+
)
2736

2837
# ### end Alembic commands ###
2938

3039

3140
def downgrade():
3241
# ### commands auto generated by Alembic - please adjust! ###
33-
with op.batch_alter_table('residents', schema=None) as batch_op:
34-
batch_op.drop_column('last_modified')
35-
36-
with op.batch_alter_table('log_record_tag', schema=None) as batch_op:
37-
batch_op.drop_constraint(None, type_='foreignkey')
38-
batch_op.create_foreign_key('log_record_tag_tag_id_fkey', 'tags', ['tag_id'], ['tag_id'])
42+
with op.batch_alter_table("residents", schema=None) as batch_op:
43+
batch_op.drop_column("last_modified")
44+
45+
with op.batch_alter_table("log_record_tag", schema=None) as batch_op:
46+
batch_op.drop_constraint(None, type_="foreignkey")
47+
batch_op.create_foreign_key(
48+
"log_record_tag_tag_id_fkey", "tags", ["tag_id"], ["tag_id"]
49+
)
3950

4051
# ### end Alembic commands ###

frontend/src/APIClients/AuthAPIClient.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ const login = async (
3535
}
3636
return {
3737
errCode: 500,
38-
errMessage: "Error logging in. Please try again.",
38+
errMessage: "Unable to login. Please try again.",
3939
};
4040
}
4141
};
@@ -112,7 +112,10 @@ const register = async (
112112
errMessage: getAuthErrMessage(axiosErr.response, "SIGNUP"),
113113
};
114114
}
115-
return null;
115+
return {
116+
errCode: 500,
117+
errMessage: "Error signing up. Please try again.",
118+
};
116119
}
117120
};
118121

frontend/src/APIClients/CommonAPIClient.ts

-65
This file was deleted.

frontend/src/APIClients/UserAPIClient.ts

+76
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import {
77
CountUsersResponse,
88
UpdateUserParams,
99
UserStatus,
10+
GetUserStatusResponse,
1011
} from "../types/UserTypes";
12+
import { ErrorResponse } from "../types/ErrorTypes";
1113

1214
const getUsers = async ({
1315
returnAll = false,
@@ -124,10 +126,84 @@ const deleteUser = async (userId: number): Promise<number> => {
124126
}
125127
};
126128

129+
const inviteUser = async (
130+
email: string,
131+
role: string,
132+
firstName: string,
133+
lastName: string,
134+
): Promise<boolean | ErrorResponse> => {
135+
try {
136+
const bearerToken = `Bearer ${getLocalStorageObjProperty(
137+
AUTHENTICATED_USER_KEY,
138+
"accessToken",
139+
)}`;
140+
await baseAPIClient.post(
141+
"/users/invite-user",
142+
{ email, role, firstName, lastName },
143+
{ headers: { Authorization: bearerToken } },
144+
);
145+
return true;
146+
} catch (error) {
147+
const axiosErr = (error as any) as AxiosError;
148+
149+
if (axiosErr.response && axiosErr.response.status === 409) {
150+
return {
151+
errMessage:
152+
axiosErr.response.data.error ??
153+
"User with the specified email already exists.",
154+
};
155+
}
156+
return false;
157+
}
158+
};
159+
160+
const getUserStatus = async (
161+
email: string,
162+
): Promise<UserStatus | ErrorResponse> => {
163+
try {
164+
const bearerToken = `Bearer ${getLocalStorageObjProperty(
165+
AUTHENTICATED_USER_KEY,
166+
"accessToken",
167+
)}`;
168+
const { data } = await baseAPIClient.get<GetUserStatusResponse>(
169+
"/users/user-status",
170+
{
171+
params: {
172+
email,
173+
},
174+
headers: { Authorization: bearerToken },
175+
},
176+
);
177+
if (data.email === email) {
178+
return data.userStatus;
179+
}
180+
return {
181+
errMessage:
182+
"This email address has not been invited. Please try again with a different email.",
183+
};
184+
} catch (error) {
185+
const axiosErr = (error as any) as AxiosError;
186+
187+
if (axiosErr.response && axiosErr.response.status === 403) {
188+
return {
189+
errMessage:
190+
axiosErr.response.data.error ??
191+
"This email address has not been invited. Please try again with a different email.",
192+
};
193+
}
194+
195+
return {
196+
errMessage: "Unable to get status of this user.",
197+
};
198+
}
199+
};
200+
127201
export default {
128202
getUsers,
129203
countUsers,
130204
updateUser,
131205
updateUserStatus,
132206
deleteUser,
207+
inviteUser,
208+
getUserStatus,
133209
};

0 commit comments

Comments
 (0)