diff --git a/db_init.py b/db_init.py index ed89c97a..798e285e 100644 --- a/db_init.py +++ b/db_init.py @@ -89,7 +89,7 @@ db.session.add(row) db.session.commit() - class_years_rows = (2024, 2025, 2026, 2027, 2028, 2029, 2030, 2031) + class_years_rows = (2025, 2026, 2027, 2028, 2029, 2030, 2031) for row_item in class_years_rows: row = ClassYears(class_year=row_item, active=True) @@ -182,7 +182,7 @@ False, True, SemesterEnum.SPRING, - 2024, + 2025, date.today(), True, datetime.now(), @@ -198,7 +198,7 @@ True, True, SemesterEnum.SPRING, - 2024, + 2025, date.today(), True, datetime.now(), @@ -214,7 +214,7 @@ True, True, SemesterEnum.FALL, - 2024, + 2025, date.today(), True, datetime.now(), @@ -230,7 +230,7 @@ True, True, SemesterEnum.SUMMER, - 2024, + 2025, date.today(), True, datetime.now(), @@ -246,10 +246,10 @@ True, False, SemesterEnum.FALL, - 2024, - "2024-10-31", + 2025, + "2025-10-31", True, - "2024-10-10T10:30:00", + "2025-10-10T10:30:00", LocationEnum.JROWL, ), ) @@ -331,7 +331,7 @@ db.session.add(row) db.session.commit() - recommends_class_years_rows = ((2, 2024), (2, 2025), (2, 2026), (1, 2027)) + recommends_class_years_rows = ((3, 2025), (2, 2025), (2, 2026), (1, 2027)) for r in recommends_class_years_rows: row = RecommendsClassYears(opportunity_id=r[0], class_year=r[1]) diff --git a/docs/database_docs/Labconnect_DB.png b/docs/database_docs/Labconnect_DB.png index 1bc76fe9..b8c8c39c 100644 Binary files a/docs/database_docs/Labconnect_DB.png and b/docs/database_docs/Labconnect_DB.png differ diff --git a/labconnect/main/auth_routes.py b/labconnect/main/auth_routes.py index ca9b7d41..fae2de91 100644 --- a/labconnect/main/auth_routes.py +++ b/labconnect/main/auth_routes.py @@ -45,9 +45,9 @@ def validate_code_and_get_user_email(code: str) -> str | None: def saml_login(): # In testing skip RPI login purely for local development - if ( - current_app.config["TESTING"] - and current_app.config["FRONTEND_URL"] == "http://localhost:3000" + if current_app.config["TESTING"] and ( + current_app.config["FRONTEND_URL"] == "http://localhost:3000" + or current_app.config["FRONTEND_URL"] == "http://127.0.0.1:3000" ): # Generate JWT code = generate_temporary_code("test@rpi.edu") diff --git a/labconnect/main/opportunity_routes.py b/labconnect/main/opportunity_routes.py index 8cd59f31..4b6c1e8e 100644 --- a/labconnect/main/opportunity_routes.py +++ b/labconnect/main/opportunity_routes.py @@ -1,3 +1,5 @@ +from datetime import datetime + from flask import abort, request from flask_jwt_extended import get_jwt_identity, jwt_required @@ -8,12 +10,9 @@ Leads, Opportunities, RecommendsClassYears, - RecommendsCourses, - RecommendsMajors, User, ) - from . import main_blueprint @@ -165,7 +164,6 @@ def packageIndividualOpportunity(opportunityInfo): ) queryInfo = query.all() - # print(queryInfo) if len(queryInfo) == 0: return data @@ -507,23 +505,6 @@ def getOpportunityCards(): # abort(500) -# @main_blueprint.get("/getOpportunityByProfessor/") -# def getOpportunityByProfessor(rcs_id: str): -# # # query database for opportunity -# query = db.session.execute( -# db.select(Opportunities, Leads) -# .where(Leads.lab_manager_id == rcs_id) -# .join(Opportunities, Leads.opportunity_id == Opportunities.id) -# ) - -# data = query.all() -# print(data) - -# # return data in the below format if opportunity is found -# return {"data": [opportunity[0].to_dict() for opportunity in data]} - -# abort(500) - @main_blueprint.get("/staff/opportunities/") def getLabManagerOpportunityCards(rcs_id: str): @@ -609,92 +590,74 @@ def getLabManagerOpportunityCards(rcs_id: str): @main_blueprint.post("/createOpportunity") @jwt_required() def createOpportunity(): - data = request.get_json() - user_id = get_jwt_identity() - author = db.session.execute( - db.select(User).where(User.email == user_id) - ).scalar_one_or_none() + if not request.data or not user_id: + abort(400) - authorID = author.lab_manager_id - newPostData = data[0] + request_data = request.get_json() - # query database to see if the credentials above match - query = db.session.execute(db.select(LabManager).where(LabManager.id == authorID)) + if not request_data: + abort(400) - data = query.all()[0][0] + author = db.session.execute( + db.select(User).where(User.email == user_id[0]) + ).scalar_one_or_none() - # TODO: how do we get the opportunity id? - # if match is found, create a new opportunity with the new data provided + if author is None or author.lab_manager_id is None: + abort(400) - one = False - two = False - three = False - four = False + try: + pay = int(request_data["hourlyPay"]) + except: + pay = None - if newPostData["one_credit"]: - one = True - if newPostData["two_credits"]: - two = True - if newPostData["three_credits"]: - three = True - if newPostData["four_credits"]: - four = True + one = True if "1" in request_data["credits"] else False + two = True if "2" in request_data["credits"] else False + three = True if "3" in request_data["credits"] else False + four = True if "4" in request_data["credits"] else False - lenum = convert_to_enum(newPostData["location"]) + lenum = convert_to_enum(request_data["location"]) if lenum is None: lenum = LocationEnum.TBD - newOpportunity = Opportunities( - name=newPostData["name"], - description=newPostData["description"], - recommended_experience=newPostData["recommended_experience"], - pay=newPostData["pay"], - one_credit=one, - two_credits=two, - three_credits=three, - four_credits=four, - semester=SemesterEnum[(newPostData["semester"]).upper()], - year=newPostData["year"], - application_due=datetime.datetime.strptime( - newPostData["application_due"], "%Y-%m-%d" - ), - active=newPostData["active"], - location=lenum, - last_updated=datetime.datetime.now(), + newOpportunity = Opportunities() + newOpportunity.name = request_data["title"] + newOpportunity.description = request_data["description"] + newOpportunity.recommended_experience = request_data["recommended_experience"] + newOpportunity.pay = pay + newOpportunity.one_credit = one + newOpportunity.two_credits = two + newOpportunity.three_credits = three + newOpportunity.four_credits = four + newOpportunity.semester = SemesterEnum.FALL + newOpportunity.year = datetime.now().year + newOpportunity.application_due = datetime.strptime( + request_data["application_due"], "%Y-%m-%d" ) + newOpportunity.active = True + newOpportunity.location = lenum + newOpportunity.last_updated = datetime.now() db.session.add(newOpportunity) db.session.commit() - newLead = Leads(lab_manager_id=authorID, opportunity_id=newOpportunity.id) + newLead = Leads() + newLead.lab_manager_id = author.lab_manager_id + newLead.opportunity_id = newOpportunity.id db.session.add(newLead) - db.session.commit() - - for course in newPostData["courses"]: - newCourse = RecommendsCourses( - opportunity_id=newOpportunity.id, course_code=course - ) - db.session.add(newCourse) - db.session.commit() + for year in request_data["years"]: + if year.isdigit(): + recommended_year = int(year) + newYear = RecommendsClassYears() + newYear.opportunity_id = newOpportunity.id + newYear.class_year = recommended_year + db.session.add(newYear) - for major in newPostData["majors"]: - newMajor = RecommendsMajors(opportunity_id=newOpportunity.id, major_code=major) - db.session.add(newMajor) - db.session.commit() - - for year in newPostData["years"]: - newYear = RecommendsClassYears( - opportunity_id=newOpportunity.id, class_year=year - ) - db.session.add(newYear) - db.session.commit() - - db.session.add(newOpportunity) + db.session.commit() - return {"data": "Opportunity Created"} + return {"data": "Opportunity Created", "id": newOpportunity.id}, 200 @main_blueprint.get("/editOpportunity/") @@ -709,18 +672,18 @@ def editOpportunity_get(opportunity_id): opportunity = opportunity[0] # Query related courses - courses_data = db.session.execute( - db.select(RecommendsCourses.course_code).where( - RecommendsCourses.opportunity_id == opportunity_id - ) - ).all() + # courses_data = db.session.execute( + # db.select(RecommendsCourses.course_code).where( + # RecommendsCourses.opportunity_id == opportunity_id + # ) + # ).all() # Query related majors - majors_data = db.session.execute( - db.select(RecommendsMajors.major_code).where( - RecommendsMajors.opportunity_id == opportunity_id - ) - ).all() + # majors_data = db.session.execute( + # db.select(RecommendsMajors.major_code).where( + # RecommendsMajors.opportunity_id == opportunity_id + # ) + # ).all() # Query related class years years_data = db.session.execute( @@ -729,26 +692,44 @@ def editOpportunity_get(opportunity_id): ) ).all() + credits = [ + str(i) + for i, credit in enumerate( + [ + opportunity.one_credit, + opportunity.two_credits, + opportunity.three_credits, + opportunity.four_credits, + ], + start=1, + ) + if credit + ] + + years = [str(year.class_year) for year in years_data] if years_data else [] + # Format opportunity data as JSON opportunity_data = { "id": opportunity.id, - "name": opportunity.name, + "title": opportunity.name, + "application_due": opportunity.application_due.strftime("%Y-%m-%d"), + "type": ( + "Any" + if len(credits) > 0 and opportunity.pay and opportunity.pay > 0 + else "For Pay" if opportunity.pay and opportunity.pay > 0 else "For Credit" + ), + "hourlyPay": str(opportunity.pay), + "credits": credits, "description": opportunity.description, "recommended_experience": opportunity.recommended_experience, - "pay": opportunity.pay, - "one_credit": opportunity.one_credit, - "two_credits": opportunity.two_credits, - "three_credits": opportunity.three_credits, - "four_credits": opportunity.four_credits, - "semester": SemesterEnum(opportunity.semester), # Convert enum to string - "year": opportunity.year, - "application_due": opportunity.application_due.strftime("%Y-%m-%d"), - "active": opportunity.active, + # "semester": opportunity.semester, # Convert enum to string + # "year": opportunity.year, + # "active": opportunity.active, "location": opportunity.location, # Convert enum to string - "last_updated": opportunity.last_updated.strftime("%Y-%m-%d %H:%M:%S"), - "courses": [course.course_code for course in courses_data], - "majors": [major.major_code for major in majors_data], - "years": [year.class_year for year in years_data], + # "last_updated": opportunity.last_updated.strftime("%Y-%m-%d %H:%M:%S"), + # "courses": [course.course_code for course in courses_data], + # "majors": [major.major_code for major in majors_data], + "years": years, } return opportunity_data @@ -758,96 +739,100 @@ def editOpportunity_get(opportunity_id): @jwt_required() def editOpportunity(opportunity_id): - data = request.get_json() + user_id = get_jwt_identity() + if not request.data or not user_id: + abort(400) + + request_data = request.get_json() + + if not request_data: + abort(400) # Check if the opportunity and author exist opportunity = db.session.execute( db.select(Opportunities).where(Opportunities.id == opportunity_id) - ).first() + ).scalar_one_or_none() - if not opportunity: - return {"error": "Opportunity not found"}, 404 + if opportunity is None: + abort(400) - opportunity = opportunity[0] - data = data[0] + author = db.session.execute( + db.select(User).where(User.email == user_id[0]) + ).scalar_one_or_none() - # TODO: Add check to see if person has permission to edit opportunity - user_id = get_jwt_identity() + if author is None or author.lab_manager_id is None: + abort(400) - lab_manager = db.session.execute( - db.select(LabManager) - .join(User, User.lab_manager_id == LabManager.id) - .where(User.email == user_id) + leads = db.session.execute( + db.select(Leads) + .where(Leads.opportunity_id == opportunity_id) + .where(Leads.lab_manager_id == author.lab_manager_id) ).scalar_one_or_none() - if lab_manager: - lead = db.session.execute( - db.select(Leads).where( - Leads.lab_manager_id == lab_manager.id, - Leads.opportunity_id == opportunity.id, - ) - ).scalar_one_or_none() - if not lead: - return {"error": "Don't have permission to edit!"}, 401 - else: - return {"error": "Don't have permission to edit!"}, 401 + if leads is None: + abort(400) + + try: + pay = int(request_data["hourlyPay"]) + except: + pay = None + + one = True if "1" in request_data["credits"] else False + two = True if "2" in request_data["credits"] else False + three = True if "3" in request_data["credits"] else False + four = True if "4" in request_data["credits"] else False + + lenum = convert_to_enum(request_data["location"]) + + if lenum is None: + lenum = LocationEnum.TBD # Update fields for opportunity based on the input data - opportunity.name = data["name"] - opportunity.description = data["description"] - opportunity.recommended_experience = data["recommended_experience"] - opportunity.pay = data["pay"] - opportunity.one_credit = data["one_credit"] - opportunity.two_credits = data["two_credits"] - opportunity.three_credits = data["three_credits"] - opportunity.four_credits = data["four_credits"] - opportunity.semester = ( - SemesterEnum[(data["semester"]).upper()] - if "semester" in data - else opportunity.semester - ) - opportunity.year = data["year"] - opportunity.application_due = ( - datetime.datetime.strptime(data["application_due"], "%Y-%m-%d") - if "application_due" in data - else opportunity.application_due + opportunity.name = request_data["title"] + opportunity.description = request_data["description"] + opportunity.recommended_experience = request_data["recommended_experience"] + opportunity.pay = pay + opportunity.one_credit = one + opportunity.two_credits = two + opportunity.three_credits = three + opportunity.four_credits = four + opportunity.application_due = datetime.strptime( + request_data["application_due"], "%Y-%m-%d" ) - opportunity.active = data["active"] - opportunity.location = ( - convert_to_enum(data["location"]) - if "location" in data - else opportunity.location - ) - opportunity.last_updated = datetime.datetime.now() - - # Update related tables for courses, majors, and years - # Clear current recommendations - db.session.query(RecommendsCourses).filter_by( - opportunity_id=opportunity_id - ).delete() - db.session.query(RecommendsMajors).filter_by(opportunity_id=opportunity_id).delete() - db.session.query(RecommendsClassYears).filter_by( - opportunity_id=opportunity_id - ).delete() - - # Re-add new recommendations - for course in data["courses"]: - newCourse = RecommendsCourses(opportunity_id=opportunity.id, course_code=course) - - db.session.add(newCourse) - db.session.commit() - - for major in data["majors"]: - newMajor = RecommendsMajors(opportunity_id=opportunity.id, major_code=major) - db.session.add(newMajor) - db.session.commit() - - for year in data["years"]: - newYear = RecommendsClassYears(opportunity_id=opportunity.id, class_year=year) + # opportunity.active = data["active"] + opportunity.location = lenum + opportunity.last_updated = datetime.now() + + existing_years = { + str(year.class_year) + for year in db.session.execute( + db.select(RecommendsClassYears).where( + RecommendsClassYears.opportunity_id == opportunity_id + ) + ).scalars() + } + new_years = set(request_data["years"]) + + # Years to add + years_to_add = new_years - existing_years + for year in years_to_add: + newYear = RecommendsClassYears() + newYear.opportunity_id = opportunity.id + newYear.class_year = int(year) db.session.add(newYear) - db.session.commit() - # Commit all changes to the database + # Years to remove + years_to_remove = existing_years - new_years + if years_to_remove: + db.session.execute( + db.select(RecommendsClassYears) + .where( + RecommendsClassYears.opportunity_id == opportunity_id, + RecommendsClassYears.class_year.in_(years_to_remove), + ) + .delete(synchronize_session=False) + ) + db.session.commit() return {"data": "Opportunity Updated"}, 200 @@ -864,35 +849,24 @@ def deleteOpportunity(opportunity_id): # TODO: Add check to see if user has permission to delete opportunity user_id = get_jwt_identity() - lab_manager = db.session.execute( - db.select(LabManager) - .join(User, User.lab_manager_id == LabManager.id) - .where(User.email == user_id) + user = db.session.execute( + db.select(User).where(User.email == user_id) ).scalar_one_or_none() - if lab_manager: - lead = db.session.execute( - db.select(Leads).where( - Leads.lab_manager_id == lab_manager.id, - Leads.opportunity_id == opportunity.id, - ) - ).scalar_one_or_none() - if not lead: - return {"error": "Don't have permission to delete!"}, 401 - else: + if not user or not user.lab_manager_id: return {"error": "Don't have permission to delete!"}, 401 - # Delete related records in other tables (e.g., Leads, RecommendsCourses, RecommendsMajors, RecommendsClassYears) - db.session.query(Leads).filter_by(opportunity_id=opportunity_id).delete() - db.session.query(RecommendsCourses).filter_by( - opportunity_id=opportunity_id - ).delete() - db.session.query(RecommendsMajors).filter_by(opportunity_id=opportunity_id).delete() - db.session.query(RecommendsClassYears).filter_by( - opportunity_id=opportunity_id - ).delete() - - # Delete the opportunity itself + leads = db.session.execute( + db.select(Leads) + .where(Leads.opportunity_id == opportunity_id) + .where(Leads.lab_manager_id == user.lab_manager_id) + ).scalar_one_or_none() + + if not leads: + abort(400) + + # Delete the opportunity + # cascading delete will handle all other tables db.session.delete(opportunity) db.session.commit() diff --git a/labconnect/main/routes.py b/labconnect/main/routes.py index 5cfb9afc..0b5aa7ab 100644 --- a/labconnect/main/routes.py +++ b/labconnect/main/routes.py @@ -5,26 +5,14 @@ from labconnect import db from labconnect.models import ( - # ClassYears, - # Courses, LabManager, Leads, - # Majors, Opportunities, - # Participates, - # RecommendsClassYears, - # RecommendsCourses, - # RecommendsMajors, RPIDepartments, - # RPISchools, User, - # UserCourses, - # UserDepartments, - # UserMajors, + ClassYears, ) -# = not currently using - from . import main_blueprint @@ -307,11 +295,6 @@ def changeActiveStatus() -> dict[str, bool]: return {"activeStatus": opportunity} -# @main_blueprint.post("/create_post") -# def create_post(): -# return {"Hello": "There"} - - @main_blueprint.get("/500") def force_error(): abort(500) @@ -330,21 +313,6 @@ def force_error(): # return result -# @main_blueprint.get("/departmentsList") -# def departments() -> list[Any]: - -# data = db.session.execute( -# db.select(RPIDepartments).order_by(RPIDepartments.name) -# ).scalars() - -# if not data: -# abort(404) - -# result = [department.to_dict() for department in data] - -# return result - - # @main_blueprint.get("/majors") # def majors() -> list[Any]: @@ -383,24 +351,24 @@ def force_error(): # return result -# @main_blueprint.get("/years") -# def years() -> list[Any]: +@main_blueprint.get("/years") +def years() -> list[int]: -# data = db.session.execute( -# db.select(ClassYears) -# .order_by(ClassYears.class_year) -# .where(ClassYears.active == True) -# ).scalars() + data = db.session.execute( + db.select(ClassYears) + .order_by(ClassYears.class_year) + .where(ClassYears.active == True) + ).scalars() -# if not data: -# abort(404) + if not data: + abort(404) -# result = [year.class_year for year in data] + result = [year.class_year for year in data] -# if result == []: -# abort(404) + if result == []: + abort(404) -# return result + return result # @main_blueprint.get("/courses")