From 65cf98b4e2d4a10bdc2ee8956419d67046b18a9d Mon Sep 17 00:00:00 2001 From: Helen Guan Date: Mon, 26 Jun 2023 19:57:37 -0400 Subject: [PATCH 01/19] Create buildings table --- backend/app/models/__init__.py | 9 +++ backend/app/models/buildings.py | 30 +++++++++ backend/app/models/log_records.py | 4 +- backend/app/models/residents.py | 9 ++- backend/app/models/user.py | 7 +- backend/app/resources/auth_dto.py | 10 ++- backend/app/rest/log_records_routes.py | 12 ++-- backend/app/rest/residents_routes.py | 47 +++++++++---- backend/app/rest/user_routes.py | 3 + .../implementations/log_records_service.py | 20 +++--- .../implementations/residents_service.py | 29 ++++---- .../services/implementations/user_service.py | 18 ++--- .../services/interfaces/residents_service.py | 18 ++--- ...d51_add_status_and_email_to_users_table.py | 50 ++++++++------ .../a5d22b31faab_add_resident_table.py | 67 ++++++++++++------- .../c24644595836_adding_buildings_table.py | 34 ++++++++++ .../fd734d591b67_added_log_records_to_db.py | 8 ++- 17 files changed, 256 insertions(+), 119 deletions(-) create mode 100644 backend/app/models/buildings.py create mode 100644 backend/migrations/versions/c24644595836_adding_buildings_table.py diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py index a0ea1eee..97678b55 100644 --- a/backend/app/models/__init__.py +++ b/backend/app/models/__init__.py @@ -12,6 +12,7 @@ def init_app(app): from .log_records import LogRecords from .tag import Tag from .residents import Residents + from .buildings import Buildings app.app_context().push() db.init_app(app) @@ -26,3 +27,11 @@ def init_app(app): # recreate tables db.create_all() + + building1 = Buildings(id=1, address="144 Erb St. East", name="Seniors") + building2 = Buildings(id=2, address="362 Erb St. West", name="Permanent") + building3 = Buildings(id=3, address="402 Erb St. West", name="Adults") + db.session.add(building1) + db.session.add(building2) + db.session.add(building3) + db.session.commit() diff --git a/backend/app/models/buildings.py b/backend/app/models/buildings.py new file mode 100644 index 00000000..340a7b11 --- /dev/null +++ b/backend/app/models/buildings.py @@ -0,0 +1,30 @@ +from . import db +from sqlalchemy import inspect, cast, String +from sqlalchemy.orm.properties import ColumnProperty + + +class Buildings(db.Model): + __tablename__ = "buildings" + id = db.Column(db.Integer, primary_key=True, nullable=False) + address = db.Column(db.String, nullable=False) + name = db.Column(db.String, nullable=False) + + def to_dict(self, include_relationships=False): + # define the entities table + cls = type(self) + + mapper = inspect(cls) + formatted = {} + for column in mapper.attrs: + field = column.key + attr = getattr(self, field) + # if it's a regular column, extract the value + if isinstance(column, ColumnProperty): + formatted[field] = attr + # otherwise, it's a relationship field + # (currently not applicable, but may be useful for entity groups) + elif include_relationships: + # recursively format the relationship + # don't format the relationship's relationships + formatted[field] = [obj.to_dict() for obj in attr] + return formatted diff --git a/backend/app/models/log_records.py b/backend/app/models/log_records.py index 947244f4..e8438195 100644 --- a/backend/app/models/log_records.py +++ b/backend/app/models/log_records.py @@ -7,14 +7,14 @@ class LogRecords(db.Model): __tablename__ = "log_records" log_id = db.Column(db.Integer, primary_key=True, nullable=False) employee_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False) - resident_id = db.Column(db.Integer,db.ForeignKey("residents.id"), nullable=False) + resident_id = db.Column(db.Integer, db.ForeignKey("residents.id"), nullable=False) datetime = db.Column(db.DateTime(timezone=True), nullable=False) flagged = db.Column(db.Boolean, nullable=False) attn_to = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=True) # TODO: replace open String fields with VarChar(NUM_CHARS) note = db.Column(db.String, nullable=False) tags = db.Column(db.ARRAY(db.String), nullable=True) - building = db.Column(db.String, nullable=False) + building_id = db.Column(db.Integer, db.ForeignKey("buildings.id"), nullable=False) def to_dict(self, include_relationships=False): # define the entities table diff --git a/backend/app/models/residents.py b/backend/app/models/residents.py index 804525a0..ae74b7ff 100644 --- a/backend/app/models/residents.py +++ b/backend/app/models/residents.py @@ -2,6 +2,7 @@ from sqlalchemy import inspect, cast, String from sqlalchemy.orm.properties import ColumnProperty + class Residents(db.Model): __tablename__ = "residents" id = db.Column(db.Integer, primary_key=True, nullable=False) @@ -9,10 +10,8 @@ class Residents(db.Model): room_num = db.Column(db.Integer, nullable=False) date_joined = db.Column(db.Date, nullable=False) date_left = db.Column(db.Date, nullable=True) - building = db.Column( - db.Enum("144", "402", "362", name="buildings"), nullable=False - ) - + building_id = db.Column(db.Integer, db.ForeignKey("buildings.id"), nullable=False) + resident_id = db.column_property(initial + cast(room_num, String)) def to_dict(self, include_relationships=False): @@ -33,4 +32,4 @@ def to_dict(self, include_relationships=False): # recursively format the relationship # don't format the relationship's relationships formatted[field] = [obj.to_dict() for obj in attr] - return formatted + return formatted diff --git a/backend/app/models/user.py b/backend/app/models/user.py index b60bd7c1..5fb184f7 100644 --- a/backend/app/models/user.py +++ b/backend/app/models/user.py @@ -14,13 +14,16 @@ class User(db.Model): role = db.Column( db.Enum("Admin", "Regular Staff", "Relief Staff", name="roles"), nullable=False ) - user_status = db.Column(db.Enum("Invited", "Active", "Deactivated", name="user_statuses"), nullable=False) + user_status = db.Column( + db.Enum("Invited", "Active", "Deactivated", name="user_statuses"), + nullable=False, + ) email = db.Column(db.String, nullable=False) __table_args__ = ( db.CheckConstraint( "(user_status = 'Invited' AND auth_id IS NULL) OR (user_status != 'Invited' AND auth_id IS NOT NULL)", - name="check_auth_id_nullable" + name="check_auth_id_nullable", ), ) diff --git a/backend/app/resources/auth_dto.py b/backend/app/resources/auth_dto.py index ac53d750..a1bb86eb 100644 --- a/backend/app/resources/auth_dto.py +++ b/backend/app/resources/auth_dto.py @@ -4,7 +4,15 @@ class AuthDTO(Token, UserDTO): def __init__( - self, access_token, refresh_token, id, first_name, last_name, email, role, user_status + self, + access_token, + refresh_token, + id, + first_name, + last_name, + email, + role, + user_status, ): Token.__init__(self, access_token, refresh_token) UserDTO.__init__(self, id, first_name, last_name, email, role, user_status) diff --git a/backend/app/rest/log_records_routes.py b/backend/app/rest/log_records_routes.py index 1b078c73..a6266259 100644 --- a/backend/app/rest/log_records_routes.py +++ b/backend/app/rest/log_records_routes.py @@ -38,18 +38,22 @@ def get_log_records(): try: page_number = int(request.args.get("page_number")) except: - try: - return_all = True if request.args.get("return_all").casefold() == "true" else False + try: + return_all = ( + True if request.args.get("return_all").casefold() == "true" else False + ) except: pass try: filters = json.loads(request.args.get("filters")) - except: + except: filters = None try: - log_records = log_records_service.get_log_records(page_number, return_all, filters) + log_records = log_records_service.get_log_records( + page_number, return_all, filters + ) return jsonify(log_records), 201 except Exception as e: error_message = getattr(e, "message", None) diff --git a/backend/app/rest/residents_routes.py b/backend/app/rest/residents_routes.py index 53013fd4..3eae4673 100644 --- a/backend/app/rest/residents_routes.py +++ b/backend/app/rest/residents_routes.py @@ -20,46 +20,67 @@ def add_resident(): except Exception as e: error_message = getattr(e, "message", None) return jsonify({"error": (error_message if error_message else str(e))}), 500 - -#resident_id is the primary key in the table + + +# resident_id is the primary key in the table @blueprint.route("/", methods=["PUT"], strict_slashes=False) @require_authorization_by_role({"Admin"}) def update_resident(resident_id): """ - Update an existing resident record based on the id + Update an existing resident record based on the id """ updated_resident = request.json try: - updated_resident = residents_service.update_resident(resident_id, updated_resident) - return jsonify({"message": "Resident record with id {resident_id} updated sucessfully".format(resident_id=resident_id)}), 201 + updated_resident = residents_service.update_resident( + resident_id, updated_resident + ) + return ( + jsonify( + { + "message": "Resident record with id {resident_id} updated sucessfully".format( + resident_id=resident_id + ) + } + ), + 201, + ) except Exception as e: error_message = getattr(e, "message", None) return jsonify({"error": (error_message if error_message else str(e))}), 500 - + + @blueprint.route("/", methods=["DELETE"], strict_slashes=False) @require_authorization_by_role({"Admin"}) def delete_resident(resident_id): """ - Delete a resident record based on id + Delete a resident record based on id """ try: residents_service.delete_resident(resident_id) - return jsonify({"message": "Resident with id {resident_id} deleted sucessfully".format(resident_id=resident_id)}), 201 + return ( + jsonify( + { + "message": "Resident with id {resident_id} deleted sucessfully".format( + resident_id=resident_id + ) + } + ), + 201, + ) except Exception as e: error_message = getattr(e, "message", None) - return jsonify({"error": (error_message if error_message else str(e))}), 500 + return jsonify({"error": (error_message if error_message else str(e))}), 500 + @blueprint.route("/", methods=["GET"], strict_slashes=False) def get_resident(): """ Get residents. - """ + """ try: resident_id = request.args.get("resident_id") - residents_results = residents_service.get_resident(resident_id) + residents_results = residents_service.get_resident(resident_id) return jsonify(residents_results), 201 except Exception as e: error_message = getattr(e, "message", None) return jsonify({"error": (error_message if error_message else str(e))}), 500 - - \ No newline at end of file diff --git a/backend/app/rest/user_routes.py b/backend/app/rest/user_routes.py index a77b2f0a..19f3a6cf 100644 --- a/backend/app/rest/user_routes.py +++ b/backend/app/rest/user_routes.py @@ -95,6 +95,7 @@ def get_users(): 500, ) + @blueprint.route("/user-status", methods=["GET"], strict_slashes=False) def get_user_status(): try: @@ -105,6 +106,7 @@ def get_user_status(): error_message = getattr(e, "message", None) return jsonify({"error": (error_message if error_message else str(e))}), 500 + @blueprint.route("/invite-user", methods=["POST"], strict_slashes=False) @require_authorization_by_role({"Admin"}) @validate_request("CreateInvitedUserDTO") @@ -120,6 +122,7 @@ def create_user(): error_message = getattr(e, "message", None) return jsonify({"error": (error_message if error_message else str(e))}), 500 + @blueprint.route("/activate-user", methods=["POST"], strict_slashes=False) @require_authorization_by_role({"Admin"}) @validate_request("CreateUserDTO") diff --git a/backend/app/services/implementations/log_records_service.py b/backend/app/services/implementations/log_records_service.py index b10ff3e2..1df1cc2f 100644 --- a/backend/app/services/implementations/log_records_service.py +++ b/backend/app/services/implementations/log_records_service.py @@ -48,7 +48,7 @@ def to_json_list(self, logs): "attn_to": log[5], "note": log[6], "tags": log[7], - "building": log[8], + "building_id": log[8], "employee_first_name": log[9], "employee_last_name": log[10], "attn_to_first_name": log[11], @@ -59,8 +59,8 @@ def to_json_list(self, logs): except Exception as postgres_error: raise postgres_error - def filter_by_building(self, building): - return f"\nbuilding='{building}'" + def filter_by_building_id(self, building_id): + return f"\nbuilding_id='{building_id}'" def filter_by_employee_id(self, employee_id): if type(employee_id) == list: @@ -88,8 +88,12 @@ def filter_by_attn_to(self, attn_to): def filter_by_date_range(self, date_range): if len(date_range) > 0: - start_date = datetime.strptime(date_range[0], "%Y-%m-%d").replace(hour=0, minute=0) - end_date = datetime.strptime(date_range[len(date_range) - 1], "%Y-%m-%d").replace(hour=23, minute=59) + start_date = datetime.strptime(date_range[0], "%Y-%m-%d").replace( + hour=0, minute=0 + ) + end_date = datetime.strptime( + date_range[len(date_range) - 1], "%Y-%m-%d" + ).replace(hour=23, minute=59) return f"\ndatetime>='{start_date}' AND datetime<='{end_date}'" def filter_by_tags(self, tags): @@ -119,7 +123,7 @@ def get_log_records(self, page_number, return_all, filters=None): logs.attn_to,\n \ logs.note,\n \ logs.tags,\n \ - logs.building,\n \ + logs.building_id,\n \ employees.first_name AS employee_first_name,\n \ employees.last_name AS employee_last_name,\n \ attn_tos.first_name AS attn_to_first_name,\n \ @@ -132,7 +136,7 @@ def get_log_records(self, page_number, return_all, filters=None): is_first_filter = True options = { - "building": self.filter_by_building, + "building_id": self.filter_by_building_id, "employee_id": self.filter_by_employee_id, "resident_id": self.filter_by_resident_id, "attn_to": self.filter_by_attn_to, @@ -218,7 +222,7 @@ def update_log_record(self, log_id, updated_log_record): LogRecords.employee_id: updated_log_record["employee_id"], LogRecords.resident_id: updated_log_record["resident_id"], LogRecords.flagged: updated_log_record["flagged"], - LogRecords.building: updated_log_record["building"], + LogRecords.building_id: updated_log_record["building_is"], } ) if not updated_log_record: diff --git a/backend/app/services/implementations/residents_service.py b/backend/app/services/implementations/residents_service.py index 827cea76..cd5e5ffb 100644 --- a/backend/app/services/implementations/residents_service.py +++ b/backend/app/services/implementations/residents_service.py @@ -5,6 +5,7 @@ from sqlalchemy import select, cast, Date import json + class ResidentsService(IResidentsService): """ Residents implementation with residents management methods @@ -13,7 +14,7 @@ class ResidentsService(IResidentsService): def __init__(self, logger): """ Create an instance of ResidentsService - + :param logger: application's logger instance :type logger: logger """ @@ -25,19 +26,19 @@ def to_json_list(self, residents_results): for resident in residents_results: residents_list.append( { - "id": resident.id, - "initial": resident.initial, - "room_num": resident.room_num, - "date_joined": str(resident.date_joined), - "date_left": str(resident.date_left), - "building": resident.building, - "resident_id": resident.resident_id + "id": resident.id, + "initial": resident.initial, + "room_num": resident.room_num, + "date_joined": str(resident.date_joined), + "date_left": str(resident.date_left), + "building_id": resident.building_id, + "resident_id": resident.resident_id, } ) return json.dumps(residents_list) except Exception as postgres_error: raise postgres_error - + def add_resident(self, resident): new_resident = resident try: @@ -47,7 +48,7 @@ def add_resident(self, resident): return resident except Exception as postgres_error: raise postgres_error - + def update_resident(self, resident_id, updated_resident): if "date_left" in updated_resident: Residents.query.filter_by(id=resident_id).update( @@ -60,7 +61,7 @@ def update_resident(self, resident_id, updated_resident): Residents.initial: updated_resident["initial"], Residents.room_num: updated_resident["room_num"], Residents.date_joined: updated_resident["date_joined"], - Residents.building: updated_resident["building"], + Residents.building_id: updated_resident["building_id"], } ) if not updated_resident: @@ -84,10 +85,10 @@ def delete_resident(self, resident_id): def get_resident(self, resident_id=None): try: if resident_id: - residents_results = Residents.query.filter_by(resident_id = resident_id) + residents_results = Residents.query.filter_by(resident_id=resident_id) else: residents_results = Residents.query.all() - + return self.to_json_list(residents_results) except Exception as postgres_error: - raise postgres_error \ No newline at end of file + raise postgres_error diff --git a/backend/app/services/implementations/user_service.py b/backend/app/services/implementations/user_service.py index 36cb3f1b..d032b810 100644 --- a/backend/app/services/implementations/user_service.py +++ b/backend/app/services/implementations/user_service.py @@ -128,15 +128,13 @@ def get_users(self): raise e return user_dtos - + def get_user_status_by_email(self, email): try: user = User.query.filter_by(email=email).first() if not user: - raise Exception( - "user with email {email} not found".format(email) - ) + raise Exception("user with email {email} not found".format(email)) return user.user_status except Exception as e: @@ -147,7 +145,7 @@ def get_user_status_by_email(self, email): ) ) raise e - + def create_invited_user(self, user): try: user_entry = User.query.filter_by(email=user.email).first() @@ -164,7 +162,7 @@ def create_invited_user(self, user): user_entry = User(**postgres_invited_user) db.session.add(user_entry) db.session.commit() - + user_dict = UserService.__user_to_dict_and_remove_auth_id(user_entry) return UserDTO(**user_dict) @@ -177,13 +175,12 @@ def create_invited_user(self, user): ) ) raise e - def activate_user(self, user, auth_id=None, signup_method="PASSWORD"): firebase_user = None try: - if(self.get_user_status_by_email(user.email) == "Invited"): + if self.get_user_status_by_email(user.email) == "Invited": if signup_method == "PASSWORD": firebase_user = firebase_admin.auth.create_user( email=user.email, password=user.password @@ -194,10 +191,7 @@ def activate_user(self, user, auth_id=None, signup_method="PASSWORD"): try: User.query.filter_by(email=user.email).update( - { - User.auth_id: firebase_user.uid, - User.user_status: "Active" - } + {User.auth_id: firebase_user.uid, User.user_status: "Active"} ) db.session.commit() user_entry = User.query.filter_by(email=user.email).first() diff --git a/backend/app/services/interfaces/residents_service.py b/backend/app/services/interfaces/residents_service.py index 7739d154..45d712e5 100644 --- a/backend/app/services/interfaces/residents_service.py +++ b/backend/app/services/interfaces/residents_service.py @@ -1,9 +1,11 @@ from abc import ABC, abstractmethod + class IResidentsService(ABC): """ ResidentsService interface with residents methods """ + @abstractmethod def to_json_list(self, residents_results): """ @@ -11,7 +13,7 @@ def to_json_list(self, residents_results): :param residents_results: residents retrieved from resident table :type id: objects from resident table """ - + pass @abstractmethod @@ -24,12 +26,12 @@ def add_resident(self, id, initial, room_num, date_joined, date_left, building): :type initial: string :param room_num: room number which the resident resides in :type room_num: integer - :param date_joined: the date the resident joined + :param date_joined: the date the resident joined :type date_joined: date - :param date_left: the date the resident left, if exists + :param date_left: the date the resident left, if exists :type date_left: date - :param building: the building in which the resident is staying - :type building: Enum("144", "402", "362) + :param building: the building_id in which the resident is staying + :type building: int :raises Exception: if resident creation fails """ pass @@ -61,8 +63,8 @@ def get_resident(self, resident_id, id): """ Gets residents in json format. :param resident_id: id of resident to be deleted in the format of initial+room_num - :type resident_id: string + :type resident_id: string :param id: id of resident to be deleted, the primary key of the resident - :type resident_id: initial + :type resident_id: initial """ - pass \ No newline at end of file + pass diff --git a/backend/migrations/versions/a2a0a16b6d51_add_status_and_email_to_users_table.py b/backend/migrations/versions/a2a0a16b6d51_add_status_and_email_to_users_table.py index 27bd8b26..92447730 100644 --- a/backend/migrations/versions/a2a0a16b6d51_add_status_and_email_to_users_table.py +++ b/backend/migrations/versions/a2a0a16b6d51_add_status_and_email_to_users_table.py @@ -11,28 +11,34 @@ # revision identifiers, used by Alembic. -revision = 'a2a0a16b6d51' -down_revision = '82f36cdf325f' +revision = "a2a0a16b6d51" +down_revision = "82f36cdf325f" branch_labels = None depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - user_statuses = postgresql.ENUM('Invited', 'Active', 'Deactivated', name='user_statuses') + user_statuses = postgresql.ENUM( + "Invited", "Active", "Deactivated", name="user_statuses" + ) user_statuses.create(op.get_bind()) - with op.batch_alter_table('users', schema=None) as batch_op: - batch_op.add_column(sa.Column('user_status', sa.Enum('Invited', 'Active', 'Deactivated', name='user_statuses'), nullable=False)) - batch_op.add_column(sa.Column('email', sa.String(), nullable=False)) - batch_op.alter_column('auth_id', - existing_type=sa.VARCHAR(), - nullable=True) - + with op.batch_alter_table("users", schema=None) as batch_op: + batch_op.add_column( + sa.Column( + "user_status", + sa.Enum("Invited", "Active", "Deactivated", name="user_statuses"), + nullable=False, + ) + ) + batch_op.add_column(sa.Column("email", sa.String(), nullable=False)) + batch_op.alter_column("auth_id", existing_type=sa.VARCHAR(), nullable=True) + op.create_check_constraint( - 'check_auth_id_nullable', - "users", - "(user_status = 'Invited' AND auth_id IS NULL) OR (user_status != 'Invited' AND auth_id IS NOT NULL)", + "check_auth_id_nullable", + "users", + "(user_status = 'Invited' AND auth_id IS NULL) OR (user_status != 'Invited' AND auth_id IS NOT NULL)", ) # ### end Alembic commands ### @@ -40,15 +46,15 @@ def upgrade(): def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.drop_constraint('check_auth_id_nullable', 'users', type_='check') - with op.batch_alter_table('users', schema=None) as batch_op: - batch_op.alter_column('auth_id', - existing_type=sa.VARCHAR(), - nullable=False) - batch_op.drop_column('email') - batch_op.drop_column('user_status') - - user_statuses = postgresql.ENUM('INVITED', 'ACTIVE', 'DEACTIVATED', name='user_statuses') + op.drop_constraint("check_auth_id_nullable", "users", type_="check") + with op.batch_alter_table("users", schema=None) as batch_op: + batch_op.alter_column("auth_id", existing_type=sa.VARCHAR(), nullable=False) + batch_op.drop_column("email") + batch_op.drop_column("user_status") + + user_statuses = postgresql.ENUM( + "INVITED", "ACTIVE", "DEACTIVATED", name="user_statuses" + ) user_statuses.drop(op.get_bind()) # ### end Alembic commands ### diff --git a/backend/migrations/versions/a5d22b31faab_add_resident_table.py b/backend/migrations/versions/a5d22b31faab_add_resident_table.py index edc2768f..62e69775 100644 --- a/backend/migrations/versions/a5d22b31faab_add_resident_table.py +++ b/backend/migrations/versions/a5d22b31faab_add_resident_table.py @@ -10,43 +10,58 @@ # revision identifiers, used by Alembic. -revision = 'a5d22b31faab' -down_revision = 'a2a0a16b6d51' +revision = "a5d22b31faab" +down_revision = "a2a0a16b6d51" branch_labels = None depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.create_table('residents', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('initial', sa.String(), nullable=False), - sa.Column('room_num', sa.Integer(), nullable=False), - sa.Column('date_joined', sa.Date(), nullable=False), - sa.Column('date_left', sa.Date(), nullable=True), - sa.Column('building', sa.Enum('144', '402', '362', name='buildings'), nullable=False), - sa.PrimaryKeyConstraint('id') + op.create_table( + "residents", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("initial", sa.String(), nullable=False), + sa.Column("room_num", sa.Integer(), nullable=False), + sa.Column("date_joined", sa.Date(), nullable=False), + sa.Column("date_left", sa.Date(), nullable=True), + sa.Column("building_id", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint( + ["building_id"], + ["buildings.id"], + ), + sa.PrimaryKeyConstraint("id"), ) - with op.batch_alter_table('log_records', schema=None) as batch_op: - batch_op.add_column(sa.Column('resident_id', sa.Integer(), nullable=False)) - batch_op.drop_constraint('log_records_employee_id_fkey', type_='foreignkey') - batch_op.create_foreign_key(None, 'users', ['employee_id'], ['id']) - batch_op.create_foreign_key(None, 'residents', ['resident_id'], ['id']) - batch_op.drop_column('resident_first_name') - batch_op.drop_column('resident_last_name') + with op.batch_alter_table("log_records", schema=None) as batch_op: + batch_op.add_column(sa.Column("resident_id", sa.Integer(), nullable=False)) + batch_op.drop_constraint("log_records_employee_id_fkey", type_="foreignkey") + batch_op.create_foreign_key(None, "users", ["employee_id"], ["id"]) + batch_op.create_foreign_key(None, "residents", ["resident_id"], ["id"]) + batch_op.drop_column("resident_first_name") + batch_op.drop_column("resident_last_name") # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table('log_records', schema=None) as batch_op: - batch_op.add_column(sa.Column('resident_first_name', sa.VARCHAR(), autoincrement=False, nullable=False)) - batch_op.add_column(sa.Column('resident_last_name', sa.VARCHAR(), autoincrement=False, nullable=False)) - batch_op.drop_constraint(None, type_='foreignkey') - batch_op.drop_constraint(None, type_='foreignkey') - batch_op.create_foreign_key('log_records_employee_id_fkey', 'users', ['employee_id'], ['id']) - batch_op.drop_column('resident_id') - - op.drop_table('residents') + with op.batch_alter_table("log_records", schema=None) as batch_op: + batch_op.add_column( + sa.Column( + "resident_first_name", sa.VARCHAR(), autoincrement=False, nullable=False + ) + ) + batch_op.add_column( + sa.Column( + "resident_last_name", sa.VARCHAR(), autoincrement=False, nullable=False + ) + ) + batch_op.drop_constraint(None, type_="foreignkey") + batch_op.drop_constraint(None, type_="foreignkey") + batch_op.create_foreign_key( + "log_records_employee_id_fkey", "users", ["employee_id"], ["id"] + ) + batch_op.drop_column("resident_id") + + op.drop_table("residents") # ### end Alembic commands ### diff --git a/backend/migrations/versions/c24644595836_adding_buildings_table.py b/backend/migrations/versions/c24644595836_adding_buildings_table.py new file mode 100644 index 00000000..54954765 --- /dev/null +++ b/backend/migrations/versions/c24644595836_adding_buildings_table.py @@ -0,0 +1,34 @@ +"""adding buildings table + +Revision ID: c24644595836 +Revises: a5d22b31faab +Create Date: 2023-06-26 23:12:08.724039 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = "c24644595836" +down_revision = "58313513d17f" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "buildings", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("address", sa.String(), nullable=False), + sa.Column("name", sa.String(), nullable=False), + sa.PrimaryKeyConstraint("id"), + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("buildings") + # ### end Alembic commands ### diff --git a/backend/migrations/versions/fd734d591b67_added_log_records_to_db.py b/backend/migrations/versions/fd734d591b67_added_log_records_to_db.py index a5f07ec3..9ea6cab9 100644 --- a/backend/migrations/versions/fd734d591b67_added_log_records_to_db.py +++ b/backend/migrations/versions/fd734d591b67_added_log_records_to_db.py @@ -11,7 +11,7 @@ # revision identifiers, used by Alembic. revision = "fd734d591b67" -down_revision = "58313513d17f" +down_revision = "c24644595836" branch_labels = None depends_on = None @@ -29,7 +29,11 @@ def upgrade(): sa.Column("attn_to", sa.Integer(), nullable=True), sa.Column("note", sa.String(), nullable=False), sa.Column("tags", sa.ARRAY(sa.String()), nullable=True), - sa.Column("building", sa.String(), nullable=False), + sa.Column("building_id", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint( + ["building_id"], + ["buildings.id"], + ), sa.ForeignKeyConstraint( ["attn_to"], ["users.id"], From 7a8a620f16b16d745436b9c12996d61cc07804c6 Mon Sep 17 00:00:00 2001 From: Helen Guan Date: Thu, 29 Jun 2023 21:07:09 -0400 Subject: [PATCH 02/19] Set up buildings table to have prexisting building --- backend/app/models/__init__.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py index 97678b55..8ec7ca9e 100644 --- a/backend/app/models/__init__.py +++ b/backend/app/models/__init__.py @@ -20,6 +20,17 @@ def init_app(app): erase_db_and_sync = app.config["TESTING"] + if Buildings.query.count() == 0: + buildings_array = [ + Buildings(id=1, address="144 Erb St. East", name="144"), + Buildings(id=2, address="362 Erb St. West", name="362"), + Buildings(id=3, address="402 Erb St. West", name="402"), + ] + for building in buildings_array: + db.session.add(building) + + db.session.commit() + if erase_db_and_sync: # drop tables db.reflect() @@ -27,11 +38,3 @@ def init_app(app): # recreate tables db.create_all() - - building1 = Buildings(id=1, address="144 Erb St. East", name="Seniors") - building2 = Buildings(id=2, address="362 Erb St. West", name="Permanent") - building3 = Buildings(id=3, address="402 Erb St. West", name="Adults") - db.session.add(building1) - db.session.add(building2) - db.session.add(building3) - db.session.commit() From 1b33a9ad5baf97e70474bed5c3ffe951766dc34d Mon Sep 17 00:00:00 2001 From: Helen Guan Date: Mon, 17 Jul 2023 21:21:53 -0400 Subject: [PATCH 03/19] Create script --- backend/app/models/__init__.py | 11 ----------- scripts/add-buildings.sh | 5 +++++ 2 files changed, 5 insertions(+), 11 deletions(-) create mode 100644 scripts/add-buildings.sh diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py index 8ec7ca9e..b332d8ac 100644 --- a/backend/app/models/__init__.py +++ b/backend/app/models/__init__.py @@ -20,17 +20,6 @@ def init_app(app): erase_db_and_sync = app.config["TESTING"] - if Buildings.query.count() == 0: - buildings_array = [ - Buildings(id=1, address="144 Erb St. East", name="144"), - Buildings(id=2, address="362 Erb St. West", name="362"), - Buildings(id=3, address="402 Erb St. West", name="402"), - ] - for building in buildings_array: - db.session.add(building) - - db.session.commit() - if erase_db_and_sync: # drop tables db.reflect() diff --git a/scripts/add-buildings.sh b/scripts/add-buildings.sh new file mode 100644 index 00000000..0fb70faa --- /dev/null +++ b/scripts/add-buildings.sh @@ -0,0 +1,5 @@ +#!/bin/bash +docker exec -it SHOW-database /bin/bash -c "psql -U postgres -d show_db" +INSERT INTO buildings (id, address, name) VALUES (1, '144 Erb St. East', '144'); +INSERT INTO buildings (id, address, name) VALUES (2, '362 Erb St. West', '362'); +INSERT INTO buildings (id, address, name) VALUES (3, '402 Erb St. West', '402'); From 41b645372165e04574f7da7fa09a78e0cdda3506 Mon Sep 17 00:00:00 2001 From: Helen Guan <87948268+helenellyx@users.noreply.github.com> Date: Thu, 20 Jul 2023 18:57:21 -0400 Subject: [PATCH 04/19] Change building_id to building --- backend/app/services/implementations/log_records_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/app/services/implementations/log_records_service.py b/backend/app/services/implementations/log_records_service.py index 77cf3927..dd4b9e9e 100644 --- a/backend/app/services/implementations/log_records_service.py +++ b/backend/app/services/implementations/log_records_service.py @@ -48,7 +48,7 @@ def to_json_list(self, logs): "attn_to": log[5], "note": log[6], "tags": log[7], - "building_id": log[8], + "building": log[8], "employee_first_name": log[9], "employee_last_name": log[10], "attn_to_first_name": log[11], From 8c7f62d04d56615968f84c0268fc2ff62a3b2b7e Mon Sep 17 00:00:00 2001 From: Safewaan Date: Mon, 24 Jul 2023 17:25:34 -0400 Subject: [PATCH 05/19] move script to seeding dir --- scripts/add-buildings.sh | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 scripts/add-buildings.sh diff --git a/scripts/add-buildings.sh b/scripts/add-buildings.sh deleted file mode 100644 index 0fb70faa..00000000 --- a/scripts/add-buildings.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -docker exec -it SHOW-database /bin/bash -c "psql -U postgres -d show_db" -INSERT INTO buildings (id, address, name) VALUES (1, '144 Erb St. East', '144'); -INSERT INTO buildings (id, address, name) VALUES (2, '362 Erb St. West', '362'); -INSERT INTO buildings (id, address, name) VALUES (3, '402 Erb St. West', '402'); From f1dc59a022a7b4daa6a566247cd42c3b72f509a0 Mon Sep 17 00:00:00 2001 From: Helen Guan <87948268+helenellyx@users.noreply.github.com> Date: Mon, 24 Jul 2023 20:26:38 -0400 Subject: [PATCH 06/19] Update backend/app/services/implementations/log_records_service.py Co-authored-by: Safwaan Chowdhury <57375371+Safewaan@users.noreply.github.com> --- backend/app/services/implementations/log_records_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/app/services/implementations/log_records_service.py b/backend/app/services/implementations/log_records_service.py index dd4b9e9e..115f7ed8 100644 --- a/backend/app/services/implementations/log_records_service.py +++ b/backend/app/services/implementations/log_records_service.py @@ -144,7 +144,7 @@ def get_log_records( logs.attn_to,\n \ logs.note,\n \ logs.tags,\n \ - logs.building_id,\n \ + buildings.name AS building_name,\n \ employees.first_name AS employee_first_name,\n \ employees.last_name AS employee_last_name,\n \ attn_tos.first_name AS attn_to_first_name,\n \ From bf0b6254d857ccc8d134879312f411d44b2a5a87 Mon Sep 17 00:00:00 2001 From: Helen Guan Date: Mon, 24 Jul 2023 20:27:09 -0400 Subject: [PATCH 07/19] Add JOIN statement --- backend/app/services/implementations/log_records_service.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/app/services/implementations/log_records_service.py b/backend/app/services/implementations/log_records_service.py index dd4b9e9e..81449f8b 100644 --- a/backend/app/services/implementations/log_records_service.py +++ b/backend/app/services/implementations/log_records_service.py @@ -152,7 +152,8 @@ def get_log_records( FROM log_records logs\n \ LEFT JOIN users attn_tos ON logs.attn_to = attn_tos.id\n \ JOIN users employees ON logs.employee_id = employees.id \n \ - JOIN residents ON logs.resident_id = residents.id" + JOIN residents ON logs.resident_id = residents.id \n \ + JOIN buildings on logs.building_id = buildings.id" sql += self.filter_log_records(filters) @@ -244,4 +245,4 @@ def update_log_record(self, log_id, updated_log_record): raise Exception( "Log record with id {log_id} not found".format(log_id=log_id) ) - db.session.commit() \ No newline at end of file + db.session.commit() From 5b983b291aea5787702c12f90161f781e1add987 Mon Sep 17 00:00:00 2001 From: Kelly Pham Date: Sun, 8 Oct 2023 22:27:59 -0400 Subject: [PATCH 08/19] fix features to use building_id instead of building --- .../implementations/log_records_service.py | 6 +-- frontend/src/APIClients/LogRecordAPIClient.ts | 16 +++---- frontend/src/APIClients/ResidentAPIClient.ts | 8 ++-- frontend/src/components/forms/CreateLog.tsx | 20 ++++---- .../src/components/forms/CreateResident.tsx | 20 ++++---- frontend/src/components/forms/EditLog.tsx | 22 ++++----- .../src/components/forms/EditResident.tsx | 24 +++++----- .../components/pages/HomePage/HomePage.tsx | 8 ++-- .../pages/HomePage/SearchAndFilters.tsx | 6 +-- frontend/src/types/BuildingTypes.ts | 2 +- frontend/src/types/LogRecordTypes.ts | 10 ++-- frontend/src/types/ResidentTypes.ts | 2 +- seeding/create-buildings.sh | 47 +++++++++++++++++++ seeding/create-log-records.sh | 4 +- seeding/create-residents.sh | 4 +- 15 files changed, 124 insertions(+), 75 deletions(-) create mode 100644 seeding/create-buildings.sh diff --git a/backend/app/services/implementations/log_records_service.py b/backend/app/services/implementations/log_records_service.py index 5fa551a2..a2d7ef01 100644 --- a/backend/app/services/implementations/log_records_service.py +++ b/backend/app/services/implementations/log_records_service.py @@ -47,7 +47,7 @@ def to_json_list(self, logs): "attn_to": log[5], "note": log[6], "tags": log[7], - "building": log[8], + "building_id": log[8], "employee_first_name": log[9], "employee_last_name": log[10], "attn_to_first_name": log[11], @@ -59,7 +59,7 @@ def to_json_list(self, logs): raise postgres_error def filter_by_building_id(self, building_id): - return f"\nbuilding_id='{building_id}'" + return f"\nlogs.building_id='{building_id}'" def filter_by_employee_id(self, employee_id): if type(employee_id) == list: @@ -152,7 +152,7 @@ def get_log_records( logs.attn_to,\n \ logs.note,\n \ logs.tags,\n \ - buildings.name AS building_name,\n \ + buildings.id AS building_id,\n \ employees.first_name AS employee_first_name,\n \ employees.last_name AS employee_last_name,\n \ attn_tos.first_name AS attn_to_first_name,\n \ diff --git a/frontend/src/APIClients/LogRecordAPIClient.ts b/frontend/src/APIClients/LogRecordAPIClient.ts index 6db0555b..369f05fc 100644 --- a/frontend/src/APIClients/LogRecordAPIClient.ts +++ b/frontend/src/APIClients/LogRecordAPIClient.ts @@ -12,7 +12,7 @@ import { } from "../types/LogRecordTypes"; const countLogRecords = async ({ - building = "", + buildingId, employeeId = [], attnTo = [], dateRange = [], @@ -28,7 +28,7 @@ const countLogRecords = async ({ const { data } = await baseAPIClient.get(`/log_records/count`, { params: { filters: { - building, + buildingId, employeeId, attnTo, dateRange, @@ -47,7 +47,7 @@ const countLogRecords = async ({ }; const filterLogRecords = async ({ - building = "", + buildingId, employeeId = [], attnTo = [], dateRange = [], @@ -66,7 +66,7 @@ const filterLogRecords = async ({ const { data } = await baseAPIClient.get(`/log_records`, { params: { filters: { - building, + buildingId, employeeId, attnTo, dateRange, @@ -94,7 +94,7 @@ const createLog = async ({ flagged, note, tags, - building, + buildingId, attnTo, }: CreateLogRecordParams): Promise => { try { @@ -111,7 +111,7 @@ const createLog = async ({ flagged, note, tags, - building, + buildingId, attnTo, }, { headers: { Authorization: bearerToken } }, @@ -145,7 +145,7 @@ const editLogRecord = async ({ flagged, note, tags, - building, + buildingId, attnTo, }: EditLogRecordParams): Promise => { try { @@ -163,7 +163,7 @@ const editLogRecord = async ({ note, attnTo, tags, - building, + buildingId, }, { headers: { Authorization: bearerToken } }, ); diff --git a/frontend/src/APIClients/ResidentAPIClient.ts b/frontend/src/APIClients/ResidentAPIClient.ts index 069c2d6a..684575f6 100644 --- a/frontend/src/APIClients/ResidentAPIClient.ts +++ b/frontend/src/APIClients/ResidentAPIClient.ts @@ -58,7 +58,7 @@ const createResident = async ({ initial, roomNum, dateJoined, - building, + buildingId, }: CreateResidentParams): Promise => { try { const bearerToken = `Bearer ${getLocalStorageObjProperty( @@ -67,7 +67,7 @@ const createResident = async ({ )}`; await baseAPIClient.post( "/residents", - { initial, roomNum, dateJoined, building }, + { initial, roomNum, dateJoined, buildingId }, { headers: { Authorization: bearerToken } }, ); return true; @@ -100,7 +100,7 @@ const editResident = async ({ initial, roomNum, dateJoined, - building, + buildingId, dateLeft, }: Resident): Promise => { try { @@ -110,7 +110,7 @@ const editResident = async ({ )}`; await baseAPIClient.put( `/residents/${id}`, - { initial, roomNum, dateJoined, building, dateLeft }, + { initial, roomNum, dateJoined, buildingId, dateLeft }, { headers: { Authorization: bearerToken } }, ); return true; diff --git a/frontend/src/components/forms/CreateLog.tsx b/frontend/src/components/forms/CreateLog.tsx index 0ed53021..a2ade598 100644 --- a/frontend/src/components/forms/CreateLog.tsx +++ b/frontend/src/components/forms/CreateLog.tsx @@ -57,9 +57,9 @@ type AlertDataOptions = { // Ideally we should be storing this information in the database const BUILDINGS = [ - { label: "144 Erb St. West", value: "144" }, - { label: "362 Erb St. West", value: "362" }, - { label: "402 Erb St. West", value: "402" }, + { label: "144 Erb St. West", value: 1 }, + { label: "362 Erb St. West", value: 2 }, + { label: "402 Erb St. West", value: 3 }, ]; const ALERT_DATA: AlertDataOptions = { @@ -119,7 +119,7 @@ const CreateLog = ({ getRecords, countRecords, setUserPageNum }: Props) => { hour12: false, }), ); - const [building, setBuilding] = useState(""); + const [buildingId, setBuildingId] = useState(null); const [resident, setResident] = useState(-1); const [tags, setTags] = useState([]); const [attnTo, setAttnTo] = useState(-1); @@ -163,10 +163,10 @@ const CreateLog = ({ getRecords, countRecords, setUserPageNum }: Props) => { }; const handleBuildingChange = ( - selectedOption: SingleValue<{ label: string; value: string }>, + selectedOption: SingleValue<{ label: string; value: number }>, ) => { if (selectedOption !== null) { - setBuilding(selectedOption.value); + setBuildingId(selectedOption.value); } setBuildingError(selectedOption === null); @@ -244,7 +244,7 @@ const CreateLog = ({ getRecords, countRecords, setUserPageNum }: Props) => { hour12: false, }), ); - setBuilding(""); + setBuildingId(null); setResident(-1); setTags([]); setAttnTo(-1); @@ -271,7 +271,7 @@ const CreateLog = ({ getRecords, countRecords, setUserPageNum }: Props) => { setEmployeeError(!employee.label); setDateError(date === null); setTimeError(time === ""); - setBuildingError(building === ""); + setBuildingError(buildingId === null); setResidentError(resident === -1); setNotesError(notes === ""); @@ -280,7 +280,7 @@ const CreateLog = ({ getRecords, countRecords, setUserPageNum }: Props) => { !employee.label || date === null || time === "" || - building === "" || + buildingId === null || resident === -1 || notes === "" ) { @@ -299,7 +299,7 @@ const CreateLog = ({ getRecords, countRecords, setUserPageNum }: Props) => { flagged, note: notes, tags, - building, + buildingId, attnTo: attentionTo, }); if (res != null) { diff --git a/frontend/src/components/forms/CreateResident.tsx b/frontend/src/components/forms/CreateResident.tsx index b0e77fff..e95389f9 100644 --- a/frontend/src/components/forms/CreateResident.tsx +++ b/frontend/src/components/forms/CreateResident.tsx @@ -32,16 +32,16 @@ import { convertToString } from "../../helper/dateHelpers"; // TODO: Connect to Buidings table const BUILDINGS = [ - { label: "144", value: "144" }, - { label: "362", value: "362" }, - { label: "402", value: "402" }, + { label: "144 Erb St. W", value: 1 }, + { label: "362 Erb St. W", value: 2 }, + { label: "402 Erb St. W", value: 3 }, ]; const CreateResident = (): React.ReactElement => { const [initials, setInitials] = useState(""); const [roomNumber, setRoomNumber] = useState(""); const [moveInDate, setMoveInDate] = useState(new Date()); - const [building, setBuilding] = useState(""); + const [buildingId, setBuildingId] = useState(0); const [initialsError, setInitialsError] = useState(false); const [roomNumberError, setRoomNumberError] = useState(false); @@ -57,7 +57,7 @@ const CreateResident = (): React.ReactElement => { initial: initials.toUpperCase(), roomNum: parseInt(roomNumber, 10), dateJoined: convertToString(moveInDate), - building, + buildingId }); }; @@ -85,10 +85,10 @@ const CreateResident = (): React.ReactElement => { }; const handleBuildingChange = ( - selectedOption: SingleValue<{ label: string; value: string }>, + selectedOption: SingleValue<{ label: string; value: number }>, ) => { if (selectedOption !== null) { - setBuilding(selectedOption.value); + setBuildingId(selectedOption.value); setBuildingError(false); } }; @@ -100,7 +100,7 @@ const CreateResident = (): React.ReactElement => { setInitials(""); setRoomNumber(""); setMoveInDate(new Date()); - setBuilding(""); + setBuildingId(0); // Reset the error states setInitialsError(false); @@ -119,14 +119,14 @@ const CreateResident = (): React.ReactElement => { const handleSubmit = () => { setInitialsError(initials.length !== 2); setRoomNumberError(roomNumber.length !== 3); - setBuildingError(building === ""); + setBuildingError(buildingId === 0); // Prevents form submission if any required values are incorrect if ( initials.length !== 2 || roomNumber.length !== 3 || moveInDateError || - building === "" + buildingId === 0 ) { return; } diff --git a/frontend/src/components/forms/EditLog.tsx b/frontend/src/components/forms/EditLog.tsx index a1fe7a2b..15dbf015 100644 --- a/frontend/src/components/forms/EditLog.tsx +++ b/frontend/src/components/forms/EditLog.tsx @@ -59,9 +59,9 @@ type AlertDataOptions = { // Ideally we should be storing this information in the database const BUILDINGS = [ - { label: "144 Erb St. West", value: "144" }, - { label: "362 Erb St. West", value: "362" }, - { label: "402 Erb St. West", value: "402" }, + { label: "144 Erb St. West", value: 1 }, + { label: "362 Erb St. West", value: 2 }, + { label: "402 Erb St. West", value: 3 }, ]; const ALERT_DATA: AlertDataOptions = { @@ -118,7 +118,7 @@ const EditLog = ({ hour12: false, }), ); - const [building, setBuilding] = useState(""); + const [buildingId, setBuildingId] = useState(logRecord.buildingId); const [resident, setResident] = useState(-1); const [tags, setTags] = useState([]); const [attnTo, setAttnTo] = useState(-1); @@ -157,10 +157,10 @@ const EditLog = ({ }; const handleBuildingChange = ( - selectedOption: SingleValue<{ label: string; value: string }>, + selectedOption: SingleValue<{ label: string; value: number }>, ) => { if (selectedOption !== null) { - setBuilding(selectedOption.value); + setBuildingId(selectedOption.value); } setBuildingError(selectedOption === null); @@ -209,7 +209,7 @@ const EditLog = ({ hour12: false, }), ); - setBuilding(logRecord.building); + setBuildingId(logRecord.buildingId); const residentId = residentOptions.find( (item) => item.label === logRecord.residentId, )?.value; @@ -233,7 +233,7 @@ const EditLog = ({ setEmployeeError(!employee.label); setDateError(date === null); setTimeError(time === ""); - setBuildingError(building === ""); + setBuildingError(buildingId === undefined); setResidentError(resident === -1); setNotesError(notes === ""); @@ -242,7 +242,7 @@ const EditLog = ({ !employee.label || date === null || time === "" || - building === "" || + buildingId === undefined || resident === -1 || notes === "" ) { @@ -257,7 +257,7 @@ const EditLog = ({ flagged, note: notes, tags, - building, + buildingId, attnTo: attnTo === -1 ? undefined : attnTo, }); if (res) { @@ -347,7 +347,7 @@ const EditLog = ({ onChange={handleBuildingChange} styles={selectStyle} defaultValue={BUILDINGS.find( - (item) => item.value === building, + (item) => item.value === buildingId, )} /> Building is required. diff --git a/frontend/src/components/forms/EditResident.tsx b/frontend/src/components/forms/EditResident.tsx index d626e98c..2b309140 100644 --- a/frontend/src/components/forms/EditResident.tsx +++ b/frontend/src/components/forms/EditResident.tsx @@ -37,9 +37,9 @@ import { convertToDate, convertToString } from "../../helper/dateHelpers"; // TODO: Connect to Buidings table const BUILDINGS = [ - { label: "144", value: "144" }, - { label: "362", value: "362" }, - { label: "402", value: "402" }, + { label: "144 Erb St. W", value: 1 }, + { label: "362 Erb St. W", value: 2 }, + { label: "402 Erb St. W", value: 3 }, ]; type Props = { @@ -52,7 +52,7 @@ const EditResident = ({ resident, isOpen, toggleClose }: Props) => { const [initials, setInitials] = useState(""); const [roomNumber, setRoomNumber] = useState(-1); const [moveInDate, setMoveInDate] = useState(new Date()); - const [userBuilding, setUserBuilding] = useState(""); + const [buildingId, setBuildingId] = useState(resident.buildingId); const [moveOutDate, setMoveOutDate] = useState(); const [initialsError, setInitialsError] = useState(false); @@ -69,7 +69,7 @@ const EditResident = ({ resident, isOpen, toggleClose }: Props) => { initial: initials.toUpperCase(), roomNum: roomNumber, dateJoined: convertToString(moveInDate), - building: userBuilding, + buildingId, dateLeft: moveOutDate ? convertToString(moveOutDate) : undefined, }); @@ -123,10 +123,10 @@ const EditResident = ({ resident, isOpen, toggleClose }: Props) => { }; const handleBuildingChange = ( - selectedOption: SingleValue<{ label: string; value: string }>, + selectedOption: SingleValue<{ label: string; value: number }>, ) => { if (selectedOption !== null) { - setUserBuilding(selectedOption.value); + setBuildingId(selectedOption.value); setBuildingError(false); } }; @@ -137,7 +137,7 @@ const EditResident = ({ resident, isOpen, toggleClose }: Props) => { setInitials(resident.initial); setRoomNumber(resident.roomNum); setMoveInDate(convertToDate(resident.dateJoined)); - setUserBuilding(resident.building); + setBuildingId(resident.buildingId); setMoveOutDate( resident.dateLeft ? convertToDate(resident.dateLeft) : undefined, ); @@ -161,7 +161,7 @@ const EditResident = ({ resident, isOpen, toggleClose }: Props) => { setMoveOutDateError(true); return; } - if (userBuilding === "") { + if (buildingId === undefined) { setBuildingError(true); return; } @@ -178,7 +178,7 @@ const EditResident = ({ resident, isOpen, toggleClose }: Props) => { setInitials(resident.initial); setRoomNumber(resident.roomNum); setMoveInDate(convertToDate(resident.dateJoined)); - setUserBuilding(resident.building); + setBuildingId(resident.buildingId); setMoveOutDate( resident.dateLeft ? convertToDate(resident.dateLeft) : undefined, ); @@ -240,7 +240,9 @@ const EditResident = ({ resident, isOpen, toggleClose }: Props) => { Building | null; export type CountLogRecordFilters = { - buildingId?: number; + buildingId?: number[]; employeeId?: number[]; attnTo?: number[]; dateRange?: string[]; From 3a7b1a0af35a64a11befa3419748ce74f17002cc Mon Sep 17 00:00:00 2001 From: Connor Bechthold Date: Sat, 4 Nov 2023 18:27:40 -0400 Subject: [PATCH 15/19] final touchups --- .../implementations/log_records_service.py | 51 ++++++++++--------- .../a5d22b31faab_add_resident_table.py | 1 + frontend/src/components/forms/CreateLog.tsx | 8 +-- .../src/components/forms/CreateResident.tsx | 8 +-- frontend/src/components/forms/EditLog.tsx | 10 ++-- .../src/components/forms/EditResident.tsx | 4 +- .../pages/HomePage/LogRecordsTable.tsx | 48 +++++++++-------- frontend/src/helper/CSVConverter.tsx | 4 +- frontend/src/types/LogRecordTypes.ts | 12 +++-- seeding/create-buildings.sh | 47 ----------------- 10 files changed, 75 insertions(+), 118 deletions(-) delete mode 100644 seeding/create-buildings.sh diff --git a/backend/app/services/implementations/log_records_service.py b/backend/app/services/implementations/log_records_service.py index 6d06091c..b3e903b5 100644 --- a/backend/app/services/implementations/log_records_service.py +++ b/backend/app/services/implementations/log_records_service.py @@ -4,9 +4,7 @@ from ...models import db from datetime import datetime from pytz import timezone -from sqlalchemy import select, cast, Date, text -import os - +from sqlalchemy import text class LogRecordsService(ILogRecordsService): """ @@ -53,23 +51,25 @@ def to_json_list(self, logs): logs_list.append( { "log_id": log[0], - "resident_id": log[2], - "datetime": str(log[3].astimezone(timezone("US/Eastern"))), - "flagged": log[4], - "attn_to": { - "id": log[5], - "first_name": log[11], - "last_name": log[12] - }, "employee": { "id": log[1], - "first_name": log[9], - "last_name": log[10] + "first_name": log[2], + "last_name": log[3] }, - "note": log[6], - "tags": log[7], - "building_id": log[8], - "building": log[9], + "resident_id": log[4], + "attn_to": { + "id": log[5], + "first_name": log[6], + "last_name": log[7] + } if log[5] else None, + "building": { + "id": log[8], + "name": log[9] + }, + "tags": log[10] if log[10] else [], + "note": log[11], + "flagged": log[12], + "datetime": str(log[13].astimezone(timezone("US/Eastern"))), } ) return logs_list @@ -179,17 +179,18 @@ def get_log_records( sql = "SELECT\n \ logs.log_id,\n \ logs.employee_id,\n \ - CONCAT(residents.initial, residents.room_num) AS resident_id,\n \ - logs.datetime,\n \ - logs.flagged,\n \ - logs.attn_to,\n \ - logs.note,\n \ - t.tag_names, \n \ - logs.building,\n \ employees.first_name AS employee_first_name,\n \ employees.last_name AS employee_last_name,\n \ + CONCAT(residents.initial, residents.room_num) AS resident_id,\n \ + logs.attn_to,\n \ attn_tos.first_name AS attn_to_first_name,\n \ - attn_tos.last_name AS attn_to_last_name\n \ + attn_tos.last_name AS attn_to_last_name,\n \ + buildings.id AS building_id,\n \ + buildings.name AS building_name,\n \ + t.tag_names, \n \ + logs.note,\n \ + logs.flagged,\n \ + logs.datetime\n \ FROM log_records logs\n \ LEFT JOIN users attn_tos ON logs.attn_to = attn_tos.id\n \ JOIN users employees ON logs.employee_id = employees.id\n \ diff --git a/backend/migrations/versions/a5d22b31faab_add_resident_table.py b/backend/migrations/versions/a5d22b31faab_add_resident_table.py index 64614021..a12c199c 100644 --- a/backend/migrations/versions/a5d22b31faab_add_resident_table.py +++ b/backend/migrations/versions/a5d22b31faab_add_resident_table.py @@ -50,6 +50,7 @@ def upgrade(): def downgrade(): # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint("check_date_left_valid", "residents", type_="check") with op.batch_alter_table("log_records", schema=None) as batch_op: batch_op.add_column( sa.Column( diff --git a/frontend/src/components/forms/CreateLog.tsx b/frontend/src/components/forms/CreateLog.tsx index 5e3e8014..06317325 100644 --- a/frontend/src/components/forms/CreateLog.tsx +++ b/frontend/src/components/forms/CreateLog.tsx @@ -119,7 +119,7 @@ const CreateLog = ({ getRecords, countRecords, setUserPageNum }: Props) => { hour12: false, }), ); - const [buildingId, setBuildingId] = useState(null); + const [buildingId, setBuildingId] = useState(-1); const [resident, setResident] = useState(-1); const [tags, setTags] = useState([]); const [attnTo, setAttnTo] = useState(-1); @@ -244,7 +244,7 @@ const CreateLog = ({ getRecords, countRecords, setUserPageNum }: Props) => { hour12: false, }), ); - setBuildingId(null); + setBuildingId(-1); setResident(-1); setTags([]); setAttnTo(-1); @@ -271,7 +271,7 @@ const CreateLog = ({ getRecords, countRecords, setUserPageNum }: Props) => { setEmployeeError(!employee.label); setDateError(date === null); setTimeError(time === ""); - setBuildingError(buildingId === null); + setBuildingError(buildingId === -1); setResidentError(resident === -1); setNotesError(notes === ""); @@ -280,7 +280,7 @@ const CreateLog = ({ getRecords, countRecords, setUserPageNum }: Props) => { !employee.label || date === null || time === "" || - buildingId === null || + buildingId === -1 || resident === -1 || notes === "" ) { diff --git a/frontend/src/components/forms/CreateResident.tsx b/frontend/src/components/forms/CreateResident.tsx index 02e5df89..2ec744ca 100644 --- a/frontend/src/components/forms/CreateResident.tsx +++ b/frontend/src/components/forms/CreateResident.tsx @@ -41,7 +41,7 @@ const CreateResident = (): React.ReactElement => { const [initials, setInitials] = useState(""); const [roomNumber, setRoomNumber] = useState(""); const [moveInDate, setMoveInDate] = useState(new Date()); - const [buildingId, setBuildingId] = useState(0); + const [buildingId, setBuildingId] = useState(-1); const [initialsError, setInitialsError] = useState(false); const [roomNumberError, setRoomNumberError] = useState(false); @@ -100,7 +100,7 @@ const CreateResident = (): React.ReactElement => { setInitials(""); setRoomNumber(""); setMoveInDate(new Date()); - setBuildingId(0); + setBuildingId(-1); // Reset the error states setInitialsError(false); @@ -119,14 +119,14 @@ const CreateResident = (): React.ReactElement => { const handleSubmit = () => { setInitialsError(initials.length !== 2); setRoomNumberError(roomNumber.length !== 3); - setBuildingError(buildingId === 0); + setBuildingError(buildingId === -1); // Prevents form submission if any required values are incorrect if ( initials.length !== 2 || roomNumber.length !== 3 || moveInDateError || - buildingId === 0 + buildingId === -1 ) { return; } diff --git a/frontend/src/components/forms/EditLog.tsx b/frontend/src/components/forms/EditLog.tsx index 330360fc..64554146 100644 --- a/frontend/src/components/forms/EditLog.tsx +++ b/frontend/src/components/forms/EditLog.tsx @@ -118,7 +118,7 @@ const EditLog = ({ hour12: false, }), ); - const [buildingId, setBuildingId] = useState(logRecord.buildingId); + const [buildingId, setBuildingId] = useState(-1); const [resident, setResident] = useState(-1); const [tags, setTags] = useState([]); const [attnTo, setAttnTo] = useState(-1); @@ -209,13 +209,13 @@ const EditLog = ({ hour12: false, }), ); - setBuildingId(logRecord.buildingId); + setBuildingId(logRecord.building.id); const residentId = residentOptions.find( (item) => item.label === logRecord.residentId, )?.value; setResident(residentId !== undefined ? residentId : -1); setTags(logRecord.tags); - setAttnTo(logRecord.attnTo !== undefined ? logRecord.attnTo.id : -1); + setAttnTo(logRecord.attnTo ? logRecord.attnTo.id : -1); setNotes(logRecord.note); setFlagged(logRecord.flagged); @@ -233,7 +233,7 @@ const EditLog = ({ setEmployeeError(!employee.label); setDateError(date === null); setTimeError(time === ""); - setBuildingError(buildingId === undefined); + setBuildingError(buildingId === -1); setResidentError(resident === -1); setNotesError(notes === ""); @@ -242,7 +242,7 @@ const EditLog = ({ !employee.label || date === null || time === "" || - buildingId === undefined || + buildingId === -1 || resident === -1 || notes === "" ) { diff --git a/frontend/src/components/forms/EditResident.tsx b/frontend/src/components/forms/EditResident.tsx index 1152a85e..02413bae 100644 --- a/frontend/src/components/forms/EditResident.tsx +++ b/frontend/src/components/forms/EditResident.tsx @@ -52,7 +52,7 @@ const EditResident = ({ resident, isOpen, toggleClose }: Props) => { const [initials, setInitials] = useState(""); const [roomNumber, setRoomNumber] = useState(-1); const [moveInDate, setMoveInDate] = useState(new Date()); - const [buildingId, setBuildingId] = useState(resident.buildingId); + const [buildingId, setBuildingId] = useState(-1); const [moveOutDate, setMoveOutDate] = useState(); const [initialsError, setInitialsError] = useState(false); @@ -162,7 +162,7 @@ const EditResident = ({ resident, isOpen, toggleClose }: Props) => { setMoveOutDateError(true); return; } - if (buildingId === undefined) { + if (buildingId === -1) { setBuildingError(true); return; } diff --git a/frontend/src/components/pages/HomePage/LogRecordsTable.tsx b/frontend/src/components/pages/HomePage/LogRecordsTable.tsx index 62598c57..48ae5d01 100644 --- a/frontend/src/components/pages/HomePage/LogRecordsTable.tsx +++ b/frontend/src/components/pages/HomePage/LogRecordsTable.tsx @@ -171,37 +171,35 @@ const LogRecordsTable = ({ {`${record.employee.firstName} ${record.employee.lastName}`} - {record.attnTo && - record.attnTo.firstName !== null && - record.attnTo.lastName !== null + {record.attnTo ? `${record.attnTo.firstName} ${record.attnTo.lastName}` : ""} {(authenticatedUser?.role === "Admin" || authenticatedUser?.id === record.employee.id) && ( - - } - w="36px" - variant="ghost" - /> - - handleEditToggle(record.logId)} - > - Edit Log Record - - handleDeleteToggle(record.logId)} - > - Delete Log Record - - - - )} + + } + w="36px" + variant="ghost" + /> + + handleEditToggle(record.logId)} + > + Edit Log Record + + handleDeleteToggle(record.logId)} + > + Delete Log Record + + + + )} diff --git a/frontend/src/helper/CSVConverter.tsx b/frontend/src/helper/CSVConverter.tsx index 46ab535a..634cabe3 100644 --- a/frontend/src/helper/CSVConverter.tsx +++ b/frontend/src/helper/CSVConverter.tsx @@ -3,8 +3,8 @@ import { CSVLog } from "../types/CSVLog"; const convertToCSVLog = (logRecord: LogRecord): CSVLog => { return { - attnTo: logRecord.attnTo != null ? `${logRecord.attnTo.firstName} ${logRecord.attnTo.lastName}` : "", - building: logRecord.building, + attnTo: logRecord.attnTo ? `${logRecord.attnTo.firstName} ${logRecord.attnTo.lastName}` : "", + building: logRecord.building.name, datetime: logRecord.datetime, employee: `${logRecord.employee.firstName} ${logRecord.employee.lastName}`, flagged: logRecord.flagged, diff --git a/frontend/src/types/LogRecordTypes.ts b/frontend/src/types/LogRecordTypes.ts index d43aaf5b..72373f1a 100644 --- a/frontend/src/types/LogRecordTypes.ts +++ b/frontend/src/types/LogRecordTypes.ts @@ -4,12 +4,16 @@ type NameRecord = { lastName: string; }; +type BuildingRecord = { + id: number; + name: string; +} + export type LogRecord = { logId: number; - attnTo?: NameRecord; + attnTo: NameRecord | null; employee: NameRecord; - building: string; - buildingId: number; + building: BuildingRecord; datetime: string; flagged: boolean; note: string; @@ -28,7 +32,7 @@ export type GetLogRecordCountResponse = { export type PostLogRecordsResponse = Pick< LogRecord, | "attnTo" - | "buildingId" + | "building" | "datetime" | "employee" | "flagged" diff --git a/seeding/create-buildings.sh b/seeding/create-buildings.sh deleted file mode 100644 index 282d8784..00000000 --- a/seeding/create-buildings.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/bash - -# If you're on Windows, run bash create-buildings.sh -w -# Otherwise, run bash create-buildings.sh - -# Import common functions -SEEDING_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -source $SEEDING_DIR/seed.sh - -# Run SQL script in docker container -run_sql_script " -DELETE FROM buildings; -INSERT INTO buildings ( - id, - address, - name -) -VALUES ( - 1, - '144 Erb St. W', - '144' - ); - -INSERT INTO buildings ( -id, -address, -name -) -VALUES ( - 2, - '362 Erb St. W', - '362' - ); - -INSERT INTO buildings ( -id, -address, -name -) -VALUES ( - 3, - '402 Erb St. W', - '402' - ); -" - -echo "Buildings seeding complete" From 4f5a8d55d267116a753b74a81de425d233897f1a Mon Sep 17 00:00:00 2001 From: Connor Bechthold Date: Sat, 4 Nov 2023 19:10:39 -0400 Subject: [PATCH 16/19] final touchups --- backend/app/models/residents.py | 6 +++++- .../implementations/residents_service.py | 2 +- frontend/src/APIClients/ResidentAPIClient.ts | 3 ++- frontend/src/components/forms/EditResident.tsx | 8 +++----- .../ResidentDirectoryTable.tsx | 2 +- frontend/src/types/ResidentTypes.ts | 17 ++++++++++++++--- 6 files changed, 26 insertions(+), 12 deletions(-) diff --git a/backend/app/models/residents.py b/backend/app/models/residents.py index f376d798..1c491587 100644 --- a/backend/app/models/residents.py +++ b/backend/app/models/residents.py @@ -32,7 +32,11 @@ def to_dict(self, include_relationships=False): attr = getattr(self, field) # if it's a regular column, extract the value if isinstance(column, ColumnProperty): - if (field == "date_joined" or field == "date_left") and attr: + if (field == "building_id"): + formatted["building"] = { + "id": attr + } + elif (field == "date_joined" or field == "date_left") and attr: formatted[field] = attr.strftime("%Y-%m-%d") else: formatted[field] = attr diff --git a/backend/app/services/implementations/residents_service.py b/backend/app/services/implementations/residents_service.py index 29031bde..af689b83 100644 --- a/backend/app/services/implementations/residents_service.py +++ b/backend/app/services/implementations/residents_service.py @@ -30,7 +30,7 @@ def to_residents_json_list(self, resident_results): resident["date_left"] = resident.date_left.strftime("%Y-%m-%d") resident_dict = resident.to_dict() - resident_dict["building"] = building + resident_dict["building"]["name"] = building residents_json_list.append(resident_dict) return residents_json_list diff --git a/frontend/src/APIClients/ResidentAPIClient.ts b/frontend/src/APIClients/ResidentAPIClient.ts index 684575f6..df95a686 100644 --- a/frontend/src/APIClients/ResidentAPIClient.ts +++ b/frontend/src/APIClients/ResidentAPIClient.ts @@ -5,6 +5,7 @@ import { GetResidentsReponse, CountResidentsResponse, CreateResidentParams, + EditResidentParams, } from "../types/ResidentTypes"; import { getLocalStorageObjProperty } from "../utils/LocalStorageUtils"; import baseAPIClient from "./BaseAPIClient"; @@ -102,7 +103,7 @@ const editResident = async ({ dateJoined, buildingId, dateLeft, -}: Resident): Promise => { +}: EditResidentParams): Promise => { try { const bearerToken = `Bearer ${getLocalStorageObjProperty( AUTHENTICATED_USER_KEY, diff --git a/frontend/src/components/forms/EditResident.tsx b/frontend/src/components/forms/EditResident.tsx index 02413bae..3fd0311e 100644 --- a/frontend/src/components/forms/EditResident.tsx +++ b/frontend/src/components/forms/EditResident.tsx @@ -65,13 +65,11 @@ const EditResident = ({ resident, isOpen, toggleClose }: Props) => { const editResident = async () => { const res = await ResidentAPIClient.editResident({ id: resident.id, - residentId: resident.residentId, initial: initials.toUpperCase(), roomNum: roomNumber, dateJoined: convertToString(moveInDate), buildingId, - dateLeft: moveOutDate ? convertToString(moveOutDate) : undefined, - building: resident.building + dateLeft: moveOutDate ? convertToString(moveOutDate) : undefined }); if (res != null) { @@ -138,7 +136,7 @@ const EditResident = ({ resident, isOpen, toggleClose }: Props) => { setInitials(resident.initial); setRoomNumber(resident.roomNum); setMoveInDate(convertToDate(resident.dateJoined)); - setBuildingId(resident.buildingId); + setBuildingId(resident.building.id); setMoveOutDate( resident.dateLeft ? convertToDate(resident.dateLeft) : undefined, ); @@ -179,7 +177,7 @@ const EditResident = ({ resident, isOpen, toggleClose }: Props) => { setInitials(resident.initial); setRoomNumber(resident.roomNum); setMoveInDate(convertToDate(resident.dateJoined)); - setBuildingId(resident.buildingId); + setBuildingId(resident.building.id); setMoveOutDate( resident.dateLeft ? convertToDate(resident.dateLeft) : undefined, ); diff --git a/frontend/src/components/pages/ResidentDirectory/ResidentDirectoryTable.tsx b/frontend/src/components/pages/ResidentDirectory/ResidentDirectoryTable.tsx index 234d6f55..36a865bd 100644 --- a/frontend/src/components/pages/ResidentDirectory/ResidentDirectoryTable.tsx +++ b/frontend/src/components/pages/ResidentDirectory/ResidentDirectoryTable.tsx @@ -140,7 +140,7 @@ const ResidentDirectoryTable = ({ {resident.residentId!} {status} - {resident.building} + {resident.building.name} {startDate.date} {endDate ? endDate.date : ""} diff --git a/frontend/src/types/ResidentTypes.ts b/frontend/src/types/ResidentTypes.ts index 50939647..317700a7 100644 --- a/frontend/src/types/ResidentTypes.ts +++ b/frontend/src/types/ResidentTypes.ts @@ -1,3 +1,8 @@ +type BuildingRecord = { + id: number; + name: string; +} + export type Resident = { id: number; residentId: string; @@ -5,8 +10,7 @@ export type Resident = { roomNum: number; dateJoined: string; dateLeft?: string; - buildingId: number; - building: string; + building: BuildingRecord; }; export type ResidentLabel = { @@ -25,4 +29,11 @@ export type CountResidentsResponse = { export type CreateResidentParams = Omit< Resident, "id" | "residentId" | "dateLeft" | "building" ->; +> + & { buildingId: number } + +export type EditResidentParams = Omit< + Resident, + "residentId" | "building" +> + & { buildingId: number } From e41b1d51a86bc65ffd95ee23463ef79e314bd024 Mon Sep 17 00:00:00 2001 From: Connor Bechthold Date: Sat, 4 Nov 2023 19:19:21 -0400 Subject: [PATCH 17/19] remove redundant date check --- backend/app/services/implementations/residents_service.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/app/services/implementations/residents_service.py b/backend/app/services/implementations/residents_service.py index af689b83..bf3cd5f3 100644 --- a/backend/app/services/implementations/residents_service.py +++ b/backend/app/services/implementations/residents_service.py @@ -26,8 +26,6 @@ def to_residents_json_list(self, resident_results): residents_json_list = [] for result in resident_results: resident, building = result[0], result[1] - if resident.date_left: - resident["date_left"] = resident.date_left.strftime("%Y-%m-%d") resident_dict = resident.to_dict() resident_dict["building"]["name"] = building From 8d2df425757a70bb4f9ae787a99af38e905ea54d Mon Sep 17 00:00:00 2001 From: Connor Bechthold Date: Sat, 4 Nov 2023 19:28:12 -0400 Subject: [PATCH 18/19] run linter there's so many changes --- backend/app/models/log_record_tags.py | 4 +- backend/app/models/log_records.py | 4 +- backend/app/models/residents.py | 6 +- backend/app/models/tags.py | 4 +- backend/app/rest/auth_routes.py | 4 +- .../services/implementations/auth_service.py | 4 +- .../implementations/log_records_service.py | 32 +++--- .../implementations/residents_service.py | 5 +- .../exceptions/firebase_exceptions.py | 14 +-- backend/app/utilities/firebase_rest_client.py | 20 ++-- ...5ad7_create_junction_table_between_log_.py | 44 ++++++--- .../versions/8b5132609f1f_merging.py | 4 +- frontend/src/APIClients/AuthAPIClient.ts | 16 +-- frontend/src/components/auth/Authy.tsx | 36 +++---- .../src/components/forms/EditResident.tsx | 2 +- frontend/src/components/forms/Login.tsx | 98 +++++++++---------- frontend/src/components/forms/Signup.tsx | 17 ++-- .../components/pages/HomePage/HomePage.tsx | 2 +- .../pages/HomePage/LogRecordsTable.tsx | 44 ++++----- .../pages/HomePage/SearchAndFilters.tsx | 8 +- .../ResidentDirectoryTable.tsx | 5 +- frontend/src/helper/CSVConverter.tsx | 4 +- frontend/src/helper/authErrorMessage.ts | 12 +-- frontend/src/theme/common/textStyles.tsx | 4 +- frontend/src/types/AuthTypes.ts | 3 +- frontend/src/types/LogRecordTypes.ts | 2 +- frontend/src/types/ResidentTypes.ts | 13 +-- 27 files changed, 225 insertions(+), 186 deletions(-) diff --git a/backend/app/models/log_record_tags.py b/backend/app/models/log_record_tags.py index 2096f0e3..e052e234 100644 --- a/backend/app/models/log_record_tags.py +++ b/backend/app/models/log_record_tags.py @@ -8,7 +8,9 @@ class LogRecordTag(db.Model): __tablename__ = "log_record_tag" log_record_tag_id = db.Column(db.Integer, primary_key=True, nullable=False) - log_record_id = db.Column(db.Integer, db.ForeignKey("log_records.log_id"), nullable=False) + log_record_id = db.Column( + db.Integer, db.ForeignKey("log_records.log_id"), nullable=False + ) tag_id = db.Column(db.Integer, db.ForeignKey("tags.tag_id"), nullable=False) def to_dict(self, include_relationships=False): diff --git a/backend/app/models/log_records.py b/backend/app/models/log_records.py index 57402139..aab98794 100644 --- a/backend/app/models/log_records.py +++ b/backend/app/models/log_records.py @@ -14,7 +14,9 @@ class LogRecords(db.Model): # TODO: replace open String fields with VarChar(NUM_CHARS) note = db.Column(db.String, nullable=False) building_id = db.Column(db.Integer, db.ForeignKey("buildings.id"), nullable=False) - tags = db.relationship("Tag", secondary="log_record_tag", back_populates="log_records") + tags = db.relationship( + "Tag", secondary="log_record_tag", back_populates="log_records" + ) building = db.relationship("Buildings", back_populates="log_record") def to_dict(self, include_relationships=False): diff --git a/backend/app/models/residents.py b/backend/app/models/residents.py index 1c491587..8e2bbc3a 100644 --- a/backend/app/models/residents.py +++ b/backend/app/models/residents.py @@ -32,10 +32,8 @@ def to_dict(self, include_relationships=False): attr = getattr(self, field) # if it's a regular column, extract the value if isinstance(column, ColumnProperty): - if (field == "building_id"): - formatted["building"] = { - "id": attr - } + if field == "building_id": + formatted["building"] = {"id": attr} elif (field == "date_joined" or field == "date_left") and attr: formatted[field] = attr.strftime("%Y-%m-%d") else: diff --git a/backend/app/models/tags.py b/backend/app/models/tags.py index 7636d127..18a6194d 100644 --- a/backend/app/models/tags.py +++ b/backend/app/models/tags.py @@ -10,7 +10,9 @@ class Tag(db.Model): tag_id = db.Column(db.Integer, primary_key=True, nullable=False) name = db.Column(db.String, nullable=False) status = db.Column(db.Enum("Deleted", "Active", name="status"), nullable=False) - log_records = db.relationship("LogRecords", secondary="log_record_tag", back_populates="tags") + log_records = db.relationship( + "LogRecords", secondary="log_record_tag", back_populates="tags" + ) def to_dict(self, include_relationships=False): # define the entities table diff --git a/backend/app/rest/auth_routes.py b/backend/app/rest/auth_routes.py index 587a6faa..cf6d78d2 100644 --- a/backend/app/rest/auth_routes.py +++ b/backend/app/rest/auth_routes.py @@ -1,7 +1,7 @@ import os from ..utilities.exceptions.firebase_exceptions import ( - InvalidPasswordException, - TooManyLoginAttemptsException + InvalidPasswordException, + TooManyLoginAttemptsException, ) from flask import Blueprint, current_app, jsonify, request diff --git a/backend/app/services/implementations/auth_service.py b/backend/app/services/implementations/auth_service.py index e2ee2a80..1b752008 100644 --- a/backend/app/services/implementations/auth_service.py +++ b/backend/app/services/implementations/auth_service.py @@ -1,8 +1,8 @@ import firebase_admin.auth from ...utilities.exceptions.firebase_exceptions import ( - InvalidPasswordException, - TooManyLoginAttemptsException + InvalidPasswordException, + TooManyLoginAttemptsException, ) from ..interfaces.auth_service import IAuthService from ...resources.auth_dto import AuthDTO diff --git a/backend/app/services/implementations/log_records_service.py b/backend/app/services/implementations/log_records_service.py index b3e903b5..d44100e2 100644 --- a/backend/app/services/implementations/log_records_service.py +++ b/backend/app/services/implementations/log_records_service.py @@ -6,6 +6,7 @@ from pytz import timezone from sqlalchemy import text + class LogRecordsService(ILogRecordsService): """ LogRecordsService implementation with log records management methods @@ -35,12 +36,12 @@ def add_record(self, log_record): return log_record except Exception as postgres_error: raise postgres_error - + def construct_tags(self, log_record, tag_names): for tag_name in tag_names: tag = Tag.query.filter_by(name=tag_name).first() - if not tag: + if not tag: raise Exception(f"Tag with name {tag_name} does not exist") log_record.tags.append(tag) @@ -54,18 +55,17 @@ def to_json_list(self, logs): "employee": { "id": log[1], "first_name": log[2], - "last_name": log[3] + "last_name": log[3], }, "resident_id": log[4], "attn_to": { "id": log[5], "first_name": log[6], - "last_name": log[7] - } if log[5] else None, - "building": { - "id": log[8], - "name": log[9] - }, + "last_name": log[7], + } + if log[5] + else None, + "building": {"id": log[8], "name": log[9]}, "tags": log[10] if log[10] else [], "note": log[11], "flagged": log[12], @@ -80,7 +80,9 @@ def filter_by_building_id(self, building_id): if type(building_id) == list: sql_statement = f"\nlogs.building_id={building_id[0]}" for i in range(1, len(building_id)): - sql_statement = sql_statement + f"\nOR logs.building_id={building_id[i]}" + sql_statement = ( + sql_statement + f"\nOR logs.building_id={building_id[i]}" + ) return sql_statement return f"\logs.building_id={building_id}" @@ -163,7 +165,7 @@ def filter_log_records(self, filters=None): if filters.get(filter): sql = sql + "\nAND " + options[filter](filters.get(filter)) return sql - + def join_tag_attributes(self): return "\nLEFT JOIN\n \ (SELECT logs.log_id, ARRAY_AGG(tags.name) AS tag_names FROM log_records logs\n \ @@ -171,7 +173,7 @@ def join_tag_attributes(self): JOIN tags ON lrt.tag_id = tags.tag_id\n \ GROUP BY logs.log_id \n \ ) t ON logs.log_id = t.log_id\n" - + def get_log_records( self, page_number, return_all, results_per_page=10, filters=None ): @@ -196,7 +198,7 @@ def get_log_records( JOIN users employees ON logs.employee_id = employees.id\n \ JOIN residents ON logs.resident_id = residents.id\n \ JOIN buildings on logs.building_id = buildings.id" - + sql += self.join_tag_attributes() sql += self.filter_log_records(filters) @@ -223,7 +225,7 @@ def count_log_records(self, filters=None): FROM log_records logs\n \ LEFT JOIN users attn_tos ON logs.attn_to = attn_tos.id\n \ JOIN users employees ON logs.employee_id = employees.id" - + sql += f"\n{self.join_tag_attributes()}" sql += self.filter_log_records(filters) @@ -262,7 +264,7 @@ def update_log_record(self, log_id, updated_log_record): ) if "tags" in updated_log_record: log_record = LogRecords.query.filter_by(log_id=log_id).first() - if (log_record): + if log_record: log_record.tags = [] self.construct_tags(log_record, updated_log_record["tags"]) else: diff --git a/backend/app/services/implementations/residents_service.py b/backend/app/services/implementations/residents_service.py index bf3cd5f3..b9d0a982 100644 --- a/backend/app/services/implementations/residents_service.py +++ b/backend/app/services/implementations/residents_service.py @@ -122,8 +122,9 @@ def get_residents( ) else: residents_results = ( - Residents.query - .join(Buildings, Buildings.id == Residents.building_id) + Residents.query.join( + Buildings, Buildings.id == Residents.building_id + ) .limit(results_per_page) .offset((page_number - 1) * results_per_page) .with_entities(Residents, Buildings.name.label("building")) diff --git a/backend/app/utilities/exceptions/firebase_exceptions.py b/backend/app/utilities/exceptions/firebase_exceptions.py index 8bd6fdf4..c57d7a0b 100644 --- a/backend/app/utilities/exceptions/firebase_exceptions.py +++ b/backend/app/utilities/exceptions/firebase_exceptions.py @@ -2,15 +2,17 @@ class InvalidPasswordException(Exception): """ Raised when an invalid password is entered """ + def __init__(self): - self.message = "Incorrect password. Please try again." - super().__init__(self.message) + self.message = "Incorrect password. Please try again." + super().__init__(self.message) + class TooManyLoginAttemptsException(Exception): """ - Raised when there have been too many failed attempts to login + Raised when there have been too many failed attempts to login """ - def __init__(self): - self.message = "Too many failed login attempts. Please try again later." - super().__init__(self.message) + def __init__(self): + self.message = "Too many failed login attempts. Please try again later." + super().__init__(self.message) diff --git a/backend/app/utilities/firebase_rest_client.py b/backend/app/utilities/firebase_rest_client.py index 21dc2d20..60b59cff 100644 --- a/backend/app/utilities/firebase_rest_client.py +++ b/backend/app/utilities/firebase_rest_client.py @@ -2,8 +2,8 @@ import requests from ..utilities.exceptions.firebase_exceptions import ( - InvalidPasswordException, - TooManyLoginAttemptsException + InvalidPasswordException, + TooManyLoginAttemptsException, ) from ..resources.token import Token @@ -57,13 +57,21 @@ def sign_in_with_password(self, email, password): # Raise an invalid password exception # The corresponding error message from Firebase is INVALID_PASSWORD - if ("error" in response_json and response_json["error"]["code"] == 400 and response_json["error"]["message"] == "INVALID_PASSWORD"): + if ( + "error" in response_json + and response_json["error"]["code"] == 400 + and response_json["error"]["message"] == "INVALID_PASSWORD" + ): raise InvalidPasswordException - # The corresponding error message from Firebase is + # The corresponding error message from Firebase is # 'TOO_MANY_ATTEMPTS_TRY_LATER : Access to this account has been temporarily disabled due to many failed login attempts. You can immediately restore it by resetting your password or you can try again later.' - elif ("error" in response_json and response_json["error"]["code"] == 400 and response_json["error"]["message"][:27] == "TOO_MANY_ATTEMPTS_TRY_LATER"): + elif ( + "error" in response_json + and response_json["error"]["code"] == 400 + and response_json["error"]["message"][:27] == "TOO_MANY_ATTEMPTS_TRY_LATER" + ): raise TooManyLoginAttemptsException - elif "error" in response_json and response.status_code != 200: + elif "error" in response_json and response.status_code != 200: error_message = [ "Failed to sign-in via Firebase REST API, status code =", str(response.status_code), diff --git a/backend/migrations/versions/65a56c245ad7_create_junction_table_between_log_.py b/backend/migrations/versions/65a56c245ad7_create_junction_table_between_log_.py index b9c70c29..40960948 100644 --- a/backend/migrations/versions/65a56c245ad7_create_junction_table_between_log_.py +++ b/backend/migrations/versions/65a56c245ad7_create_junction_table_between_log_.py @@ -10,32 +10,46 @@ from sqlalchemy.dialects import postgresql # revision identifiers, used by Alembic. -revision = '65a56c245ad7' -down_revision = '82f36cdf325f' +revision = "65a56c245ad7" +down_revision = "82f36cdf325f" branch_labels = None depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.create_table('log_record_tag', - sa.Column('log_record_tag_id', sa.Integer(), nullable=False), - sa.Column('log_record_id', sa.Integer(), nullable=False), - sa.Column('tag_id', sa.Integer(), nullable=False), - sa.ForeignKeyConstraint(['log_record_id'], ['log_records.log_id'], ), - sa.ForeignKeyConstraint(['tag_id'], ['tags.tag_id'], ), - sa.PrimaryKeyConstraint('log_record_tag_id') + op.create_table( + "log_record_tag", + sa.Column("log_record_tag_id", sa.Integer(), nullable=False), + sa.Column("log_record_id", sa.Integer(), nullable=False), + sa.Column("tag_id", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint( + ["log_record_id"], + ["log_records.log_id"], + ), + sa.ForeignKeyConstraint( + ["tag_id"], + ["tags.tag_id"], + ), + sa.PrimaryKeyConstraint("log_record_tag_id"), ) - with op.batch_alter_table('log_records', schema=None) as batch_op: - batch_op.drop_column('tags') + with op.batch_alter_table("log_records", schema=None) as batch_op: + batch_op.drop_column("tags") # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table('log_records', schema=None) as batch_op: - batch_op.add_column(sa.Column('tags', postgresql.ARRAY(sa.VARCHAR()), autoincrement=False, nullable=True)) - - op.drop_table('log_record_tag') + with op.batch_alter_table("log_records", schema=None) as batch_op: + batch_op.add_column( + sa.Column( + "tags", + postgresql.ARRAY(sa.VARCHAR()), + autoincrement=False, + nullable=True, + ) + ) + + op.drop_table("log_record_tag") # ### end Alembic commands ### diff --git a/backend/migrations/versions/8b5132609f1f_merging.py b/backend/migrations/versions/8b5132609f1f_merging.py index 99188538..10153b85 100644 --- a/backend/migrations/versions/8b5132609f1f_merging.py +++ b/backend/migrations/versions/8b5132609f1f_merging.py @@ -10,8 +10,8 @@ # revision identifiers, used by Alembic. -revision = '8b5132609f1f' -down_revision = ('24fad25f60e3', '65a56c245ad7') +revision = "8b5132609f1f" +down_revision = ("24fad25f60e3", "65a56c245ad7") branch_labels = None depends_on = None diff --git a/frontend/src/APIClients/AuthAPIClient.ts b/frontend/src/APIClients/AuthAPIClient.ts index c516a56b..bde196bc 100644 --- a/frontend/src/APIClients/AuthAPIClient.ts +++ b/frontend/src/APIClients/AuthAPIClient.ts @@ -4,9 +4,13 @@ import { OperationVariables, } from "@apollo/client"; import { AxiosError } from "axios"; -import getLoginErrMessage from '../helper/authErrorMessage' +import getLoginErrMessage from "../helper/authErrorMessage"; import AUTHENTICATED_USER_KEY from "../constants/AuthConstants"; -import { AuthenticatedUser, AuthTokenResponse, ErrorResponse } from "../types/AuthTypes"; +import { + AuthenticatedUser, + AuthTokenResponse, + ErrorResponse, +} from "../types/AuthTypes"; import baseAPIClient from "./BaseAPIClient"; import { getLocalStorageObjProperty, @@ -29,13 +33,13 @@ const login = async ( if (axiosErr.response && axiosErr.response.status === 401) { return { errCode: axiosErr.response.status, - errMessage: getLoginErrMessage(axiosErr.response) - } + errMessage: getLoginErrMessage(axiosErr.response), + }; } return { errCode: 500, - errMessage: "Error logging in. Please try again." - } + errMessage: "Error logging in. Please try again.", + }; } }; diff --git a/frontend/src/components/auth/Authy.tsx b/frontend/src/components/auth/Authy.tsx index 2bc5b52b..4c4805ba 100644 --- a/frontend/src/components/auth/Authy.tsx +++ b/frontend/src/components/auth/Authy.tsx @@ -1,13 +1,6 @@ import React, { useContext, useState, useRef } from "react"; import { Redirect } from "react-router-dom"; -import { - Box, - Button, - Flex, - Input, - Text, - VStack -} from "@chakra-ui/react"; +import { Box, Button, Flex, Input, Text, VStack } from "@chakra-ui/react"; import authAPIClient from "../../APIClients/AuthAPIClient"; import AUTHENTICATED_USER_KEY from "../../constants/AuthConstants"; import { HOME_PAGE } from "../../constants/Routes"; @@ -73,10 +66,19 @@ const Authy = ({ return ( <> - + One last step! - In order to protect your account, please enter the authorization code from the Twilio Authy application. + + In order to protect your account, please enter the authorization + code from the Twilio Authy application. + {boxIndexes.map((boxIndex) => { return ( @@ -93,19 +95,17 @@ const Authy = ({ {authCode.length > boxIndex ? authCode[boxIndex] : " "} - ) + ); })} diff --git a/frontend/src/components/forms/EditResident.tsx b/frontend/src/components/forms/EditResident.tsx index 3fd0311e..1332b54c 100644 --- a/frontend/src/components/forms/EditResident.tsx +++ b/frontend/src/components/forms/EditResident.tsx @@ -69,7 +69,7 @@ const EditResident = ({ resident, isOpen, toggleClose }: Props) => { roomNum: roomNumber, dateJoined: convertToString(moveInDate), buildingId, - dateLeft: moveOutDate ? convertToString(moveOutDate) : undefined + dateLeft: moveOutDate ? convertToString(moveOutDate) : undefined, }); if (res != null) { diff --git a/frontend/src/components/forms/Login.tsx b/frontend/src/components/forms/Login.tsx index 6069f94a..cfd37f26 100644 --- a/frontend/src/components/forms/Login.tsx +++ b/frontend/src/components/forms/Login.tsx @@ -6,7 +6,7 @@ import { Text, FormControl, FormErrorMessage, - Input + Input, } from "@chakra-ui/react"; import { Redirect, useHistory } from "react-router-dom"; import authAPIClient from "../../APIClients/AuthAPIClient"; @@ -16,7 +16,6 @@ import AuthContext from "../../contexts/AuthContext"; import { ErrorResponse, AuthTokenResponse } from "../../types/AuthTypes"; import commonApiClient from "../../APIClients/CommonAPIClient"; - type CredentialsProps = { email: string; setEmail: (email: string) => void; @@ -27,9 +26,11 @@ type CredentialsProps = { setToggle: (toggle: boolean) => void; }; -const isLoginErrorResponse = (res: AuthTokenResponse | ErrorResponse) : res is ErrorResponse => { - return (res !== null && 'errCode' in res); -} +const isLoginErrorResponse = ( + res: AuthTokenResponse | ErrorResponse, +): res is ErrorResponse => { + return res !== null && "errCode" in res; +}; const Login = ({ email, @@ -52,40 +53,38 @@ const Login = ({ const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; if (loginClicked) { if (emailRegex.test(inputValue)) { - setEmailError(false) + setEmailError(false); } else { - setEmailError(true) + setEmailError(true); } } - setEmail(inputValue) + setEmail(inputValue); // Clear password error on changing the email - setPasswordError(false) - setPasswordErrStr("") + setPasswordError(false); + setPasswordErrStr(""); }; const handlePasswordChange = (e: React.ChangeEvent) => { const inputValue = e.target.value as string; - setPassword(inputValue) + setPassword(inputValue); // Clear password error on changing the password - setPasswordError(false) + setPasswordError(false); setPasswordErrStr(""); }; const onLogInClick = async () => { - setLoginClicked(true) + setLoginClicked(true); const isInvited = await commonApiClient.isUserInvited(email); if (isInvited) { - const loginResponse: AuthTokenResponse | ErrorResponse = await authAPIClient.login( - email, - password, - ); + const loginResponse: + | AuthTokenResponse + | ErrorResponse = await authAPIClient.login(email, password); if (isLoginErrorResponse(loginResponse)) { setPasswordError(true); setPasswordErrStr(loginResponse.errMessage); - } - else if (loginResponse) { + } else if (loginResponse) { const { requiresTwoFa, authUser } = loginResponse; if (requiresTwoFa) { setToggle(!toggle); @@ -114,38 +113,38 @@ const Login = ({ return ( - - + + Log In - - - Please enter a valid email. - - - - {passwordErrorStr} - + + + Please enter a valid email. + + + + {passwordErrorStr} + - + Not a member yet? @@ -182,4 +178,4 @@ const Login = ({ return <>; }; -export default Login \ No newline at end of file +export default Login; diff --git a/frontend/src/components/forms/Signup.tsx b/frontend/src/components/forms/Signup.tsx index 103680db..bc7cfd4d 100644 --- a/frontend/src/components/forms/Signup.tsx +++ b/frontend/src/components/forms/Signup.tsx @@ -1,7 +1,7 @@ import React, { useContext } from "react"; import { Redirect, useHistory } from "react-router-dom"; import { Box, Button, Flex, Input, Text } from "@chakra-ui/react"; -import authAPIClient from "../../APIClients/AuthAPIClient" +import authAPIClient from "../../APIClients/AuthAPIClient"; import { HOME_PAGE, LOGIN_PAGE } from "../../constants/Routes"; import AuthContext from "../../contexts/AuthContext"; import commonApiClient from "../../APIClients/CommonAPIClient"; @@ -138,14 +138,19 @@ const Signup = ({