diff --git a/labconnect/helpers.py b/labconnect/helpers.py index 688daf96..647fb836 100644 --- a/labconnect/helpers.py +++ b/labconnect/helpers.py @@ -87,16 +87,20 @@ def format_credits(credit_1, credit_2, credit_3, credit_4): credits_output.append("4") # Handle different cases - if len(credits_output) == 0: - return None + if len(credits_output) == 4: + return "1-4 Credits" elif len(credits_output) == 1: return ( f"{credits_output[0]} Credit" if credit_1 else f"{credits_output[0]} Credits" ) - elif len(credits_output) == 4: - return "1-4 Credits" + elif credits_output == ["1", "2", "3"]: + return "1-3 Credits" + elif credits_output == ["2", "3", "4"]: + return "2-4 Credits" + elif len(credits_output) == 0: + return None else: return f"{','.join(credits_output)} Credits" diff --git a/labconnect/main/opportunity_routes.py b/labconnect/main/opportunity_routes.py index 72eab379..adfda2bc 100644 --- a/labconnect/main/opportunity_routes.py +++ b/labconnect/main/opportunity_routes.py @@ -2,7 +2,7 @@ from flask import abort, request from flask_jwt_extended import get_jwt_identity, jwt_required -from sqlalchemy import func +from sqlalchemy import func, case from labconnect import db from labconnect.helpers import ( @@ -143,31 +143,6 @@ def packageIndividualOpportunity(opportunityInfo): return data -def packageOpportunityCard(opportunity): - # get professor and department by getting Leads and LabManager - query = db.session.execute( - db.select(Leads, LabManager, User.first_name, User.last_name) - .where(Leads.opportunity_id == opportunity.id) - .join(LabManager, Leads.lab_manager_id == LabManager.id) - .join(User, LabManager.id == User.lab_manager_id) - ) - - data = query.all() - - professorInfo = ", ".join(item[1].getName() for item in data) - - card = { - "id": opportunity.id, - "title": opportunity.name, - "professor": professorInfo, - "season": opportunity.semester, - "location": "TBA", - "year": opportunity.year, - } - - return card - - @main_blueprint.get("/getOpportunity/") def getOpportunity(opp_id: int): # query database for opportunity and recommended class years @@ -200,68 +175,76 @@ def getOpportunity(opp_id: int): @main_blueprint.get("/opportunity/filter") -def getOpportunities(): - # Handle GET requests for fetching default active opportunities - data = db.session.execute( - db.select(Opportunities) - .where(Opportunities.active) - .limit(20) - .order_by(Opportunities.last_updated.desc()) - .distinct() - ).scalars() - result = [serialize_opportunity(opportunity) for opportunity in data] - return result - - -@main_blueprint.post("/opportunity/filter") +@jwt_required() def filterOpportunities(): - # Handle POST requests for filtering opportunities - json_request_data = request.get_json() - - if not json_request_data: - abort(400) - - filters = json_request_data.get("filters", None) - + # Handle GET requests for filtering opportunities using query parameters + filters = request.args.to_dict(flat=False) + user_id = get_jwt_identity() data = None - if filters is None: - data = db.session.execute(db.select(Opportunities).limit(20)).scalars() - - elif not isinstance(filters, list): - abort(400) - - else: - where_conditions = [] - query = ( - db.select(Opportunities) - .where(Opportunities.active) - .limit(20) - .order_by(Opportunities.last_updated) - .distinct() + query = ( + db.select( + Opportunities, + func.json_agg( + func.json_build_object( + "first_name", + User.first_name, + "last_name", + User.last_name, + "preferred_name", + User.preferred_name, + ) + ).label("lab_managers"), + case((UserSavedOpportunities.user_id.isnot(None), True), else_=False).label( + "is_saved" + ), + ) + .join(Leads, Opportunities.id == Leads.opportunity_id) + .join(LabManager, Leads.lab_manager_id == LabManager.id) + .join(User, LabManager.id == User.lab_manager_id) + .outerjoin( + RecommendsMajors, Opportunities.id == RecommendsMajors.opportunity_id ) - for given_filter in filters: - field = given_filter.get("field", None) - value = given_filter.get("value", None) + .outerjoin( + UserSavedOpportunities, + db.and_( + Opportunities.id == UserSavedOpportunities.opportunity_id, + UserSavedOpportunities.user_id == user_id, # filter for current user + ), + ) + .where(Opportunities.active) + .group_by(Opportunities.id, UserSavedOpportunities.user_id) + .order_by(Opportunities.last_updated) + .limit(20) + ) + if filters is not None or filters != {}: + where_conditions = [] + for field, value in filters.items(): if field and value: field = field.lower() + value = value[0].split(",") # Location filter + # not in use yet if field == "location": - if value.lower() == "remote": - where_conditions.append(Opportunities.location == "REMOTE") - else: - where_conditions.append(Opportunities.location != "REMOTE") + for location in value: + if location.lower() == "remote": + where_conditions.append(Opportunities.location == "REMOTE") + else: + where_conditions.append(Opportunities.location != "REMOTE") # Class year filter - elif field == "class_year": + elif field == "years": if not isinstance(value, list): abort(400) + years = list(map(int, filter(str.isdigit, value))) + if len(years) == 0: + abort(400) query = query.join( RecommendsClassYears, Opportunities.id == RecommendsClassYears.opportunity_id, - ).where(RecommendsClassYears.class_year.in_(value)) + ).where(RecommendsClassYears.class_year.in_(years)) # Credits filter elif field == "credits": @@ -269,17 +252,17 @@ def filterOpportunities(): abort(400) credit_conditions = [] for credit in value: - if credit == 1: + if credit == "1": credit_conditions.append(Opportunities.one_credit.is_(True)) - elif credit == 2: + elif credit == "2": credit_conditions.append( Opportunities.two_credits.is_(True) ) - elif credit == 3: + elif credit == "3": credit_conditions.append( Opportunities.three_credits.is_(True) ) - elif credit == 4: + elif credit == "4": credit_conditions.append( Opportunities.four_credits.is_(True) ) @@ -291,12 +274,10 @@ def filterOpportunities(): elif field == "majors": if not isinstance(value, list): abort(400) - query = query.join( - RecommendsMajors, - Opportunities.id == RecommendsMajors.opportunity_id, - ).where(RecommendsMajors.major_code.in_(value)) + where_conditions.append(RecommendsMajors.major_code.in_(value)) # Departments filter + # not currently in use elif field == "departments": if not isinstance(value, list): abort(400) @@ -307,16 +288,11 @@ def filterOpportunities(): ) # Pay filter - elif field == "pay": - if not isinstance(value, dict): + elif field == "hourlypay": + pay = value[0] + if not pay.isnumeric(): abort(400) - min_pay = value.get("min") - max_pay = value.get("max") - if min_pay is None: - min_pay = 0 - if max_pay is None: - max_pay = float("inf") - where_conditions.append(Opportunities.pay.between(min_pay, max_pay)) + where_conditions.append(Opportunities.pay >= float(pay)) # Other fields else: @@ -328,29 +304,29 @@ def filterOpportunities(): abort(400) query = query.where(*where_conditions) - data = db.session.execute(query).scalars() + + data = db.session.execute(query).all() if not data: abort(404) - result = [serialize_opportunity(opportunity) for opportunity in data] + result = [ + serialize_opportunity( + opportunity[0], + lab_managers=", ".join( + [ + f"{name.get('preferred_name', None) or name.get('first_name')} {name.get('last_name')}" + for name in opportunity[1] + ] + ), + saved=opportunity[2], + ) + for opportunity in data + ] return result -# Jobs page -@main_blueprint.get("/getOpportunityCards") -def getOpportunityCards(): - # query database for opportunity - query = db.session.execute(db.select(Opportunities).where(Opportunities.active)) - - data = query.fetchall() - # return data in the below format if opportunity is found - cards = {"data": [packageOpportunityCard(opportunity[0]) for opportunity in data]} - - return cards - - @main_blueprint.get("/staff/opportunities/") def getLabManagerOpportunityCards(rcs_id: str) -> list[dict[str, str]]: query = ( @@ -388,6 +364,7 @@ def getLabManagerOpportunityCards(rcs_id: str) -> list[dict[str, str]]: @main_blueprint.get("/profile/opportunities/") +@jwt_required() def getProfileOpportunities(rcs_id: str) -> list[dict[str, str]]: query = ( db.select( diff --git a/labconnect/main/routes.py b/labconnect/main/routes.py index 7b595322..b16dab68 100644 --- a/labconnect/main/routes.py +++ b/labconnect/main/routes.py @@ -1,5 +1,3 @@ -# from typing import Any - from typing import NoReturn from flask import abort, request from flask_jwt_extended import get_jwt_identity, jwt_required diff --git a/labconnect/serializers.py b/labconnect/serializers.py index fb0dcda4..c404463a 100644 --- a/labconnect/serializers.py +++ b/labconnect/serializers.py @@ -6,7 +6,9 @@ def serialize_course(course: Courses) -> str: return f"{course.code} {course.name}" -def serialize_opportunity(opportunity: Opportunities) -> dict: +def serialize_opportunity( + opportunity: Opportunities, lab_managers: str = "", saved: bool = False +) -> dict: return { "id": opportunity.id, "name": opportunity.name, @@ -25,4 +27,6 @@ def serialize_opportunity(opportunity: Opportunities) -> dict: "active": opportunity.active, "last_updated": opportunity.last_updated, "location": opportunity.location, + "lab_managers": lab_managers, + "saved": saved, } diff --git a/misc/LabConnect_Logo.png b/misc/LabConnect_Logo.png index 19fbf655..3c3120db 100644 Binary files a/misc/LabConnect_Logo.png and b/misc/LabConnect_Logo.png differ