Skip to content

Commit

Permalink
fixed merge conflicts
Browse files Browse the repository at this point in the history
  • Loading branch information
achandrabalan committed Nov 26, 2023
2 parents 4b80b30 + 7e1561e commit 79d6559
Show file tree
Hide file tree
Showing 46 changed files with 645 additions and 262 deletions.
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ cd supportive-housing
docker-compose up --build
```

4. Create an Admin user. In `seeding/.env`, ensure `FIRST_NAME`, `LAST_NAME`, and `EMAIL` are set (you should use your Blueprint email/any email you have access to here). Ensure `ROLE` is set to `Admin`. Run:
```bash
bash ./seeding/invite-user.sh
```
**IMPORTANT**: If you've reset your local DB and want to re-use an email, ensure it's deleted from Firebase as well (ask the PL for access if you don't have it)

5. Signup for an account on the app! Ensure that you use the values you used in Step 3. Your password can be anything you remember

6. Verify your email address. You should receive an email in your inbox with a link - once you click the link, you're good to freely use the app! You can invite any other users through the `Employee Directory` within the `Admin Controls`

## Useful Commands

### Database Migration
Expand All @@ -75,7 +85,6 @@ bash ./scripts/restart-docker.sh
### Seeding
Before running these scripts, remember to update the `.env` file to ensure you're configuring your data to your needs:
```bash
bash ./seeding/create-user.sh # Create a user with a specific name and role
bash ./seeding/create-residents.sh # Create a number of residents
bash ./seeding/create-log-records.sh # Create a number of log records
```
Expand Down
1 change: 1 addition & 0 deletions backend/app/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def init_app(app):
from .log_record_tags import LogRecordTag
from .residents import Residents
from .buildings import Buildings
from .log_record_residents import LogRecordResidents

app.app_context().push()
db.init_app(app)
Expand Down
34 changes: 34 additions & 0 deletions backend/app/models/log_record_residents.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from sqlalchemy import inspect
from sqlalchemy.orm.properties import ColumnProperty

from . import db


class LogRecordResidents(db.Model):
__tablename__ = "log_record_residents"

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
)
resident_id = db.Column(db.Integer, db.ForeignKey("residents.id"), 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
4 changes: 3 additions & 1 deletion backend/app/models/log_records.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ 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)
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)
Expand All @@ -17,6 +16,9 @@ class LogRecords(db.Model):
tags = db.relationship(
"Tag", secondary="log_record_tag", back_populates="log_records"
)
residents = db.relationship(
"Residents", secondary="log_record_residents", back_populates="log_records"
)
building = db.relationship("Buildings", back_populates="log_record")

def to_dict(self, include_relationships=False):
Expand Down
3 changes: 3 additions & 0 deletions backend/app/models/residents.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ class Residents(db.Model):
date_left = db.Column(db.Date, nullable=True)
building_id = db.Column(db.Integer, db.ForeignKey("buildings.id"), nullable=False)
building = db.relationship("Buildings", back_populates="resident")
log_records = db.relationship(
"LogRecords", secondary="log_record_residents", back_populates="residents"
)

resident_id = db.column_property(initial + cast(room_num, String))

Expand Down
5 changes: 4 additions & 1 deletion backend/app/models/tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ class Tag(db.Model):
__tablename__ = "tags"

tag_id = db.Column(db.Integer, primary_key=True, nullable=False)
name = db.Column(db.String, nullable=False)
name = db.Column(db.String, unique=True, nullable=False)
status = db.Column(db.Enum("Deleted", "Active", name="status"), nullable=False)
last_modified = db.Column(
db.DateTime, server_default=db.func.now(), onupdate=db.func.now(), nullable=False
)
log_records = db.relationship(
"LogRecords", secondary="log_record_tag", back_populates="tags"
)
Expand Down
7 changes: 6 additions & 1 deletion backend/app/models/user.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from sqlalchemy import inspect, case, null
from sqlalchemy import func, inspect, case, null
from sqlalchemy.orm.properties import ColumnProperty

from . import db
Expand All @@ -19,6 +19,11 @@ class User(db.Model):
nullable=False,
)
email = db.Column(db.String, nullable=False)
last_modified = db.deferred(
db.Column(
db.DateTime, server_default=func.now(), onupdate=func.now(), nullable=False
)
)

__table_args__ = (
db.CheckConstraint(
Expand Down
5 changes: 4 additions & 1 deletion backend/app/resources/user_dto.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
class UserDTO:
def __init__(self, id, first_name, last_name, email, role, user_status):
def __init__(
self, id, first_name, last_name, email, role, user_status, last_modified
):
self.id = id
self.first_name = first_name
self.last_name = last_name
self.email = email
self.role = role
self.user_status = user_status
self.last_modified = last_modified
4 changes: 4 additions & 0 deletions backend/app/rest/auth_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
InvalidPasswordException,
TooManyLoginAttemptsException,
)
from ..utilities.exceptions.auth_exceptions import EmailAlreadyInUseException

from flask import Blueprint, current_app, jsonify, request
from twilio.rest import Client
Expand Down Expand Up @@ -188,6 +189,9 @@ def register():
**cookie_options,
)
return response, 200
except EmailAlreadyInUseException as e:
error_message = getattr(e, "message", None)
return jsonify({"error": (error_message if error_message else str(e))}), 409
except Exception as e:
error_message = getattr(e, "message", None)
return jsonify({"error": (error_message if error_message else str(e))}), 500
Expand Down
15 changes: 15 additions & 0 deletions backend/app/rest/tags_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,18 @@ def update_tag(tag_id):
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=["POST"], strict_slashes=False)
@require_authorization_by_role({"Admin"})
def create_tag():
"""
Create a tag
"""
tag = request.json
try:
created_tag = tags_service.create_tag(tag)
return jsonify(created_tag), 201
except Exception as e:
error_message = getattr(e, "message", None)
return jsonify({"error": (error_message if error_message else str(e))}), 500
4 changes: 4 additions & 0 deletions backend/app/rest/user_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from ..services.implementations.email_service import EmailService
from ..services.implementations.user_service import UserService
from ..utilities.csv_utils import generate_csv_from_list
from ..utilities.exceptions.auth_exceptions import UserNotInvitedException


user_service = UserService(current_app.logger)
Expand Down Expand Up @@ -91,6 +92,9 @@ def get_user_status():
email = request.args.get("email")
user_status = user_service.get_user_status_by_email(email)
return jsonify({"user_status": user_status, "email": email}), 201
except UserNotInvitedException as e:
error_message = getattr(e, "message", None)
return jsonify({"error": (error_message if error_message else str(e))}), 403
except Exception as e:
error_message = getattr(e, "message", None)
return jsonify({"error": (error_message if error_message else str(e))}), 500
Expand Down
58 changes: 44 additions & 14 deletions backend/app/services/implementations/log_records_service.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from ..interfaces.log_records_service import ILogRecordsService
from ...models.log_records import LogRecords
from ...models.residents import Residents
from ...models.user import User
from ...models.tags import Tag
from ...models import db
from datetime import datetime
Expand All @@ -23,12 +25,15 @@ def __init__(self, logger):

def add_record(self, log_record):
new_log_record = log_record.copy()

residents = new_log_record["residents"]
tag_names = new_log_record["tags"]

del new_log_record["residents"]
del new_log_record["tags"]

try:
new_log_record = LogRecords(**new_log_record)
self.construct_residents(new_log_record, residents)
self.construct_tags(new_log_record, tag_names)

db.session.add(new_log_record)
Expand All @@ -37,6 +42,15 @@ def add_record(self, log_record):
except Exception as postgres_error:
raise postgres_error

def construct_residents(self, log_record, residents):
residents = list(set(residents))
for resident_id in residents:
resident = Residents.query.filter_by(id=resident_id).first()

if not resident:
raise Exception(f"Resident with id {resident_id} does not exist")
log_record.residents.append(resident)

def construct_tags(self, log_record, tag_names):
for tag_name in tag_names:
tag = Tag.query.filter_by(name=tag_name).first()
Expand All @@ -57,7 +71,7 @@ def to_json_list(self, logs):
"first_name": log[2],
"last_name": log[3],
},
"resident_id": log[4],
"residents": log[4],
"attn_to": {
"id": log[5],
"first_name": log[6],
Expand Down Expand Up @@ -94,13 +108,15 @@ def filter_by_employee_id(self, employee_id):
return sql_statement
return f"\nemployee_id={employee_id}"

def filter_by_resident_id(self, resident_id):
if type(resident_id) == list:
sql_statement = f"\nresident_id={resident_id[0]}"
for i in range(1, len(resident_id)):
sql_statement = sql_statement + f"\nOR resident_id={resident_id[i]}"
def filter_by_residents(self, residents):
if type(residents) == list:
sql_statement = f"\n'{residents[0]}'=ANY (resident_ids)"
for i in range(1, len(residents)):
sql_statement = (
sql_statement + f"\nAND '{residents[i]}'=ANY (resident_ids)"
)
return sql_statement
return f"\nresident_id={resident_id}"
return f"\n'{residents}'=ANY (resident_ids)"

def filter_by_attn_to(self, attn_to):
if type(attn_to) == list:
Expand Down Expand Up @@ -150,7 +166,7 @@ def filter_log_records(self, filters=None):
options = {
"building_id": self.filter_by_building_id,
"employee_id": self.filter_by_employee_id,
"resident_id": self.filter_by_resident_id,
"residents": self.filter_by_residents,
"attn_to": self.filter_by_attn_to,
"date_range": self.filter_by_date_range,
"tags": self.filter_by_tags,
Expand All @@ -166,6 +182,14 @@ def filter_log_records(self, filters=None):
sql = sql + "\nAND " + options[filter](filters.get(filter))
return sql

def join_resident_attributes(self):
return "\nLEFT JOIN\n \
(SELECT logs.log_id, ARRAY_AGG(residents.id) AS resident_ids, ARRAY_AGG(CONCAT(residents.initial, residents.room_num)) AS residents FROM log_records logs\n \
JOIN log_record_residents lrr ON logs.log_id = lrr.log_record_id\n \
JOIN residents ON lrr.resident_id = residents.id\n \
GROUP BY logs.log_id \n \
) r ON logs.log_id = r.log_id\n"

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 \
Expand All @@ -183,7 +207,7 @@ def get_log_records(
logs.employee_id,\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 \
r.residents,\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 \
Expand All @@ -196,9 +220,9 @@ 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\n \
JOIN buildings on logs.building_id = buildings.id"

sql += self.join_resident_attributes()
sql += self.join_tag_attributes()
sql += self.filter_log_records(filters)

Expand All @@ -225,10 +249,10 @@ 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\n \
JOIN residents ON logs.resident_id = residents.id\n \
JOIN buildings on logs.building_id = buildings.id"

sql += f"\n{self.join_tag_attributes()}"
sql += self.join_resident_attributes()
sql += self.join_tag_attributes()
sql += self.filter_log_records(filters)

num_results = db.session.execute(text(sql))
Expand All @@ -246,6 +270,7 @@ def delete_log_record(self, log_id):
raise Exception(
"Log record with id {log_id} not found".format(log_id=log_id)
)
log_record_to_delete.residents = []
log_record_to_delete.tags = []
db.session.delete(log_record_to_delete)
db.session.commit()
Expand Down Expand Up @@ -274,10 +299,15 @@ def update_log_record(self, log_id, updated_log_record):
LogRecords.tags: None,
}
)

log_record = LogRecords.query.filter_by(log_id=log_id).first()
if log_record:
log_record.residents = []
self.construct_residents(log_record, updated_log_record["residents"])

updated_log_record = LogRecords.query.filter_by(log_id=log_id).update(
{
LogRecords.employee_id: updated_log_record["employee_id"],
LogRecords.resident_id: updated_log_record["resident_id"],
LogRecords.flagged: updated_log_record["flagged"],
LogRecords.building_id: updated_log_record["building_id"],
LogRecords.note: updated_log_record["note"],
Expand Down
Loading

0 comments on commit 79d6559

Please sign in to comment.