diff --git a/.DS_Store b/.DS_Store index 2d56ed1c..28be10cc 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/Frontend/ios/Podfile.lock b/Frontend/ios/Podfile.lock index fae0553f..0e15c6d8 100644 --- a/Frontend/ios/Podfile.lock +++ b/Frontend/ios/Podfile.lock @@ -37,6 +37,8 @@ PODS: - file_picker (0.0.1): - DKImagePickerController/PhotoGallery - Flutter + - file_saver (0.0.1): + - Flutter - fl_downloader (0.0.1): - Flutter - Flutter (1.0.0) @@ -93,6 +95,7 @@ DEPENDENCIES: - app_settings (from `.symlinks/plugins/app_settings/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`) + - file_saver (from `.symlinks/plugins/file_saver/ios`) - fl_downloader (from `.symlinks/plugins/fl_downloader/ios`) - Flutter (from `Flutter`) - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) @@ -128,6 +131,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/device_info_plus/ios" file_picker: :path: ".symlinks/plugins/file_picker/ios" + file_saver: + :path: ".symlinks/plugins/file_saver/ios" fl_downloader: :path: ".symlinks/plugins/fl_downloader/ios" Flutter: @@ -171,6 +176,7 @@ SPEC CHECKSUMS: DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be + file_saver: 6cdbcddd690cb02b0c1a0c225b37cd805c2bf8b6 fl_downloader: dc99aa8dd303f862cccb830087f37acc9b0156ee Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf diff --git a/Frontend/lib/mih_components/mih_objects/user_consent.dart b/Frontend/lib/mih_components/mih_objects/user_consent.dart new file mode 100644 index 00000000..f62ebb06 --- /dev/null +++ b/Frontend/lib/mih_components/mih_objects/user_consent.dart @@ -0,0 +1,29 @@ +class UserConsent { + String app_id; + DateTime privacy_policy_accepted; + DateTime terms_of_services_accepted; + + UserConsent({ + required this.app_id, + required this.privacy_policy_accepted, + required this.terms_of_services_accepted, + }); + + factory UserConsent.fromJson(Map json) { + return UserConsent( + app_id: json['app_id'], + privacy_policy_accepted: DateTime.parse(json['privacy_policy_accepted']), + terms_of_services_accepted: + DateTime.parse(json['terms_of_services_accepted']), + ); + } + + Map toJson() { + return { + 'app_id': app_id, + 'privacy_policy_accepted': privacy_policy_accepted.toIso8601String(), + 'terms_of_services_accepted': + terms_of_services_accepted.toIso8601String(), + }; + } +} diff --git a/Frontend/lib/mih_components/mih_package_components/mih_package_alert.dart b/Frontend/lib/mih_components/mih_package_components/mih_package_alert.dart index 29d94682..ce87d21f 100644 --- a/Frontend/lib/mih_components/mih_package_components/mih_package_alert.dart +++ b/Frontend/lib/mih_components/mih_package_components/mih_package_alert.dart @@ -1,3 +1,4 @@ +import 'package:go_router/go_router.dart'; import 'package:mzansi_innovation_hub/main.dart'; import 'package:flutter/material.dart'; import 'package:mzansi_innovation_hub/mih_config/mih_colors.dart'; @@ -99,7 +100,7 @@ class _MihPackageAlertState extends State { height: 50, child: IconButton( onPressed: () { - Navigator.pop(context); + context.pop(); }, icon: Icon( Icons.close, diff --git a/Frontend/lib/mih_packages/mih_home/mih_home.dart b/Frontend/lib/mih_packages/mih_home/mih_home.dart index 8747ff7e..31c2aef7 100644 --- a/Frontend/lib/mih_packages/mih_home/mih_home.dart +++ b/Frontend/lib/mih_packages/mih_home/mih_home.dart @@ -1,10 +1,17 @@ +import 'package:go_router/go_router.dart'; +import 'package:ken_logger/ken_logger.dart'; import 'package:mzansi_innovation_hub/main.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_objects/arguments.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_objects/user_consent.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_button.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_action.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_alert.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_tools.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_circle_avatar.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_objects/app_user.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_package_window.dart'; +import 'package:mzansi_innovation_hub/mih_components/mih_package_components/mih_scack_bar.dart'; import 'package:mzansi_innovation_hub/mih_components/mih_pop_up_messages/mih_loading_circle.dart'; import 'package:mzansi_innovation_hub/mih_config/mih_colors.dart'; import 'package:mzansi_innovation_hub/mih_config/mih_env.dart'; @@ -14,32 +21,13 @@ import 'package:mzansi_innovation_hub/mih_packages/mih_home/package_tools/mih_bu import 'package:mzansi_innovation_hub/mih_packages/mih_home/package_tools/mih_personal_home.dart'; import 'package:flutter/material.dart'; import 'package:mzansi_innovation_hub/mih_services/mih_service_calls.dart'; +import 'package:mzansi_innovation_hub/mih_services/mih_user_consent_services.dart'; // ignore: must_be_immutable class MihHome extends StatefulWidget { - // final AppUser signedInUser; - // final BusinessUser? businessUser; - // final Business? business; - // final Patient? patient; - // final List notifications; - // final ImageProvider? propicFile; - // final bool isUserNew; - // final bool isBusinessUser; - // final bool isBusinessUserNew; - // final bool isDevActive; final bool personalSelected; const MihHome({ super.key, - // required this.signedInUser, - // required this.businessUser, - // required this.business, - // required this.patient, - // required this.notifications, - // required this.propicFile, - // required this.isUserNew, - // required this.isBusinessUser, - // required this.isBusinessUserNew, - // required this.isDevActive, required this.personalSelected, }); @@ -52,6 +40,79 @@ class _MihHomeState extends State { late int _selcetedIndex; late bool _personalSelected; late Future profileData; + late Future futureUserConsent; + bool showUserConsent = false; + DateTime latestPrivacyPolicyDate = DateTime.parse("2024-12-01"); + DateTime latestTermOfServiceDate = DateTime.parse("2024-12-01"); + + bool showPolicyWindow(UserConsent? userConsent) { + if (userConsent == null) { + return true; + } else { + if (userConsent.privacy_policy_accepted + .isAfter(latestPrivacyPolicyDate) && + userConsent.terms_of_services_accepted + .isAfter(latestTermOfServiceDate)) { + return false; + } else { + return true; + } + } + } + + void createOrUpdateAccpetance(UserConsent? userConsent, String app_id) { + userConsent != null + ? MihUserConsentServices() + .updateUserConsentStatus( + app_id, + DateTime.now().toIso8601String(), + DateTime.now().toIso8601String(), + ) + .then((value) { + if (value == 200) { + // setState(() { + // showUserConsent = false; + // }); + context.goNamed("mihHome", extra: false); + ScaffoldMessenger.of(context).showSnackBar( + MihSnackBar( + child: Text("Thank you for accepting our Policies"), + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + MihSnackBar( + child: Text("There was an error, please try again later"), + ), + ); + } + }) + : MihUserConsentServices() + .insertUserConsentStatus( + app_id, + DateTime.now().toIso8601String(), + DateTime.now().toIso8601String(), + ) + .then((value) { + if (value == 201) { + // setState(() { + // showUserConsent = false; + // }); + context.goNamed("mihHome", extra: false); + ScaffoldMessenger.of(context).showSnackBar( + MihSnackBar( + child: Text("Thank you for accepting our Policies"), + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + MihSnackBar( + child: Text("There was an error, please try again later"), + ), + ); + } + }); + } @override void dispose() { @@ -62,6 +123,7 @@ class _MihHomeState extends State { void initState() { super.initState(); profileData = MIHApiCalls().getProfile(10, context); + futureUserConsent = MihUserConsentServices().getUserConsentStatus(); if (widget.personalSelected == true) { setState(() { _selcetedIndex = 0; @@ -96,30 +158,259 @@ class _MihHomeState extends State { ); } else if (asyncSnapshot.connectionState == ConnectionState.done && asyncSnapshot.hasData) { - return MihPackage( - appActionButton: getAction(asyncSnapshot.data!.profilePicUrl), - appTools: - getTools(asyncSnapshot.data!.signedInUser.type != "personal"), - appBody: getToolBody(asyncSnapshot.data!), - appToolTitles: getToolTitle(), - actionDrawer: getActionDrawer( - asyncSnapshot.data!.signedInUser, - asyncSnapshot.data!.profilePicUrl, - ), - selectedbodyIndex: _selcetedIndex, - onIndexChange: (newValue) { - if (_selcetedIndex == 0) { - setState(() { - _selcetedIndex = newValue; - _personalSelected = true; - }); - } else { - setState(() { - _selcetedIndex = newValue; - _personalSelected = false; - }); - } - }, + return Stack( + children: [ + MihPackage( + appActionButton: getAction(asyncSnapshot.data!.profilePicUrl), + appTools: getTools( + asyncSnapshot.data!.signedInUser.type != "personal"), + appBody: getToolBody(asyncSnapshot.data!), + appToolTitles: getToolTitle(), + actionDrawer: getActionDrawer( + asyncSnapshot.data!.signedInUser, + asyncSnapshot.data!.profilePicUrl, + ), + selectedbodyIndex: _selcetedIndex, + onIndexChange: (newValue) { + if (_selcetedIndex == 0) { + setState(() { + _selcetedIndex = newValue; + _personalSelected = true; + }); + } else { + setState(() { + _selcetedIndex = newValue; + _personalSelected = false; + }); + } + }, + ), + FutureBuilder( + future: futureUserConsent, + builder: (context, asyncSnapshotUserConsent) { + if (asyncSnapshotUserConsent.connectionState == + ConnectionState.waiting) { + showUserConsent = false; + } else if (asyncSnapshotUserConsent.connectionState == + ConnectionState.done && + asyncSnapshotUserConsent.hasData) { + showUserConsent = + showPolicyWindow(asyncSnapshotUserConsent.data); + } else if (asyncSnapshotUserConsent.connectionState == + ConnectionState.done && + !asyncSnapshotUserConsent.hasData) { + showUserConsent = true; + } else { + showUserConsent = false; + } + return Visibility( + visible: showUserConsent, + child: Container( + color: Colors.black.withValues(alpha: 0.5), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + children: [ + MihPackageWindow( + fullscreen: false, + windowTitle: + "Privacy Policy & Terms Of Service Alert!", + onWindowTapClose: () { + showDialog( + context: context, + builder: (context) { + return MihPackageAlert( + alertIcon: Icon( + Icons.warning_amber_rounded, + size: 100, + color: MihColors.getRedColor( + MzansiInnovationHub.of(context)! + .theme + .mode == + "Dark", + ), + ), + alertTitle: + "Oops, Looks like you missed a step!", + alertBody: Text( + "We're excited for you to keep using the MIH app! Before you do, please take a moment to accept our Privacy Policy and Terms of Service. Thanks for helping us keep your experience great!", + textAlign: TextAlign.center, + style: TextStyle( + color: MihColors.getSecondaryColor( + MzansiInnovationHub.of(context)! + .theme + .mode == + "Dark", + ), + fontSize: 15, + fontWeight: FontWeight.normal, + ), + ), + alertColour: MihColors.getRedColor( + MzansiInnovationHub.of(context)! + .theme + .mode == + "Dark", + ), + ); + }); + }, + windowBody: Column( + children: [ + Icon( + Icons.policy, + size: 150, + color: MihColors.getSecondaryColor( + MzansiInnovationHub.of(context)! + .theme + .mode == + "Dark", + ), + ), + const SizedBox(height: 10), + Text( + "Welcome to the MIH App", + textAlign: TextAlign.center, + style: TextStyle( + color: MihColors.getSecondaryColor( + MzansiInnovationHub.of(context)! + .theme + .mode == + "Dark", + ), + fontSize: 30, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 10), + Text( + "To keep using the MIH app, please take a moment to review and accept our Privacy Policy and Terms of Service. our agreement helps us keep things running smoothly and securely.", + textAlign: TextAlign.center, + style: TextStyle( + color: MihColors.getSecondaryColor( + MzansiInnovationHub.of(context)! + .theme + .mode == + "Dark", + ), + fontSize: 15, + fontWeight: FontWeight.normal, + ), + ), + const SizedBox(height: 20), + Center( + child: Wrap( + alignment: WrapAlignment.center, + spacing: 10, + runSpacing: 10, + children: [ + MihButton( + onPressed: () { + context.goNamed( + "aboutMih", + extra: AboutArguments( + widget.personalSelected, + 1, + ), + ); + }, + buttonColor: MihColors.getOrangeColor( + MzansiInnovationHub.of(context)! + .theme + .mode == + "Dark"), + elevation: 10, + width: 300, + child: Text( + "Privacy Policy", + style: TextStyle( + color: MihColors.getPrimaryColor( + MzansiInnovationHub.of( + context)! + .theme + .mode == + "Dark"), + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + ), + MihButton( + onPressed: () { + context.goNamed( + "aboutMih", + extra: AboutArguments( + widget.personalSelected, + 2, + ), + ); + }, + buttonColor: MihColors.getYellowColor( + MzansiInnovationHub.of(context)! + .theme + .mode == + "Dark"), + elevation: 10, + width: 300, + child: Text( + "Terms of Service", + style: TextStyle( + color: MihColors.getPrimaryColor( + MzansiInnovationHub.of( + context)! + .theme + .mode == + "Dark"), + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + ), + MihButton( + onPressed: () { + DateTime now = DateTime.now(); + KenLogger.success( + "Date Time Now: $now"); + createOrUpdateAccpetance( + asyncSnapshotUserConsent.data, + asyncSnapshot + .data!.signedInUser.app_id, + ); + }, + buttonColor: MihColors.getGreenColor( + MzansiInnovationHub.of(context)! + .theme + .mode == + "Dark"), + elevation: 10, + width: 300, + child: Text( + "Accept", + style: TextStyle( + color: MihColors.getPrimaryColor( + MzansiInnovationHub.of( + context)! + .theme + .mode == + "Dark"), + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + const SizedBox(height: 10), + ], + ), + ), + ], + ), + ), + ); + }), + ], ); } else { return MihHomeError( diff --git a/Frontend/lib/mih_services/mih_user_consent_services.dart b/Frontend/lib/mih_services/mih_user_consent_services.dart new file mode 100644 index 00000000..f4246916 --- /dev/null +++ b/Frontend/lib/mih_services/mih_user_consent_services.dart @@ -0,0 +1,57 @@ +import 'dart:convert'; + +import 'package:mzansi_innovation_hub/mih_components/mih_objects/user_consent.dart'; +import 'package:mzansi_innovation_hub/mih_config/mih_env.dart'; +import 'package:supertokens_flutter/http.dart' as http; +import 'package:supertokens_flutter/supertokens.dart'; + +class MihUserConsentServices { + Future getUserConsentStatus() async { + var app_id = await SuperTokens.getUserId(); + final response = await http.get( + Uri.parse("${AppEnviroment.baseApiUrl}/user-consent/user/$app_id")); + if (response.statusCode == 200) { + Map userMap = jsonDecode(response.body); + UserConsent userConsent = UserConsent.fromJson(userMap); + return userConsent; + } else { + return null; + } + } + + Future insertUserConsentStatus( + String app_id, + String latestPrivacyPolicyDate, + String latestTermOfServiceDate, + ) async { + UserConsent userConsent = UserConsent( + app_id: app_id, + privacy_policy_accepted: DateTime.parse(latestPrivacyPolicyDate), + terms_of_services_accepted: DateTime.parse(latestTermOfServiceDate), + ); + final response = await http.post( + Uri.parse("${AppEnviroment.baseApiUrl}/user-consent/insert/"), + headers: {"Content-Type": "application/json"}, + body: jsonEncode(userConsent.toJson()), + ); + return response.statusCode; + } + + Future updateUserConsentStatus( + String app_id, + String latestPrivacyPolicyDate, + String latestTermOfServiceDate, + ) async { + UserConsent userConsent = UserConsent( + app_id: app_id, + privacy_policy_accepted: DateTime.parse(latestPrivacyPolicyDate), + terms_of_services_accepted: DateTime.parse(latestTermOfServiceDate), + ); + final response = await http.put( + Uri.parse("${AppEnviroment.baseApiUrl}/user-consent/update"), + headers: {"Content-Type": "application/json"}, + body: jsonEncode(userConsent.toJson()), + ); + return response.statusCode; + } +} diff --git a/backend/.DS_Store b/backend/.DS_Store index 60c256ce..74465b9f 100644 Binary files a/backend/.DS_Store and b/backend/.DS_Store differ diff --git a/backend/main.py b/backend/main.py index f542a1f4..8210a6ad 100644 --- a/backend/main.py +++ b/backend/main.py @@ -18,6 +18,7 @@ import routers.access_request as access_request import routers.patient_access as patient_access import routers.mzansi_wallet as mzansi_wallet import routers.mzansi_directory as mzansi_directory +import routers.user_consent as user_consent import routers.icd10_codes as icd10_codes from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware import Middleware @@ -94,6 +95,7 @@ app.include_router(business.router) app.include_router(notifications.router) app.include_router(mzansi_wallet.router) app.include_router(mzansi_directory.router) +app.include_router(user_consent.router) app.include_router(icd10_codes.router) app.include_router(appointments.router) diff --git a/backend/mih_database/mihDbObjects.py b/backend/mih_database/mihDbObjects.py index df8c6e54..a1332903 100644 --- a/backend/mih_database/mihDbObjects.py +++ b/backend/mih_database/mihDbObjects.py @@ -1,4 +1,4 @@ -from sqlalchemy import DateTime, Column, Integer, String +from sqlalchemy import DateTime, Column, Integer, String, text from sqlalchemy.orm import declarative_base Base = declarative_base() @@ -81,4 +81,20 @@ class BookmarkedBusiness(Base): return ( f"" + ) + +class UserConsent(Base): + __tablename__ = 'user_consent' + __table_args__ = {'schema': 'app_data'} + iduser_consent = Column(Integer, primary_key=True) + app_id = Column(String(128), nullable=False,server_default=text("''")) + privacy_policy_accepted = Column(DateTime, nullable=True) + terms_of_services_accepted = Column(DateTime, nullable=True) + + def __repr__(self): + return ( + f"" ) \ No newline at end of file diff --git a/backend/routers/user_consent.py b/backend/routers/user_consent.py new file mode 100644 index 00000000..747961d9 --- /dev/null +++ b/backend/routers/user_consent.py @@ -0,0 +1,138 @@ +from fastapi import APIRouter, HTTPException, status +from pydantic import BaseModel +#from ..mih_database import dbConnection +import mih_database +import mih_database.mihDbConnections +from mih_database.mihDbObjects import UserConsent +from sqlalchemy import desc, or_ +from sqlalchemy.orm import Session +from sqlalchemy.sql import func +from sqlalchemy.exc import IntegrityError, SQLAlchemyError +#SuperToken Auth from front end +from supertokens_python.recipe.session.framework.fastapi import verify_session +from supertokens_python.recipe.session import SessionContainer +from fastapi import Depends +from datetime import datetime +import uuid + +router = APIRouter() + +class userConsentInsertRequest(BaseModel): + app_id: str + privacy_policy_accepted: datetime + terms_of_services_accepted: datetime + +class userConsentUpdateRequest(BaseModel): + app_id: str + privacy_policy_accepted: datetime + terms_of_services_accepted: datetime + +@router.get("/user-consent/user/{app_id}", tags=["User Consent"]) +async def get_user_consent(app_id: str, session: SessionContainer = Depends(verify_session())): + dbEngine = mih_database.mihDbConnections.dbAllConnect() + dbSession = Session(dbEngine) + try: + queryResults = dbSession.query(UserConsent).\ + filter(UserConsent.app_id == app_id).\ + first() + if queryResults: + return { + "idUserConsent": queryResults.iduser_consent, + "app_id": queryResults.app_id, + "privacy_policy_accepted": queryResults.privacy_policy_accepted, + "terms_of_services_accepted": queryResults.terms_of_services_accepted + } + else: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User Consent not found") + except HTTPException as http_exc: + # Re-raise HTTPException directly if it was raised within the try block + raise http_exc + except Exception as e: + print(f"An error occurred during the ORM query: {e}") + if dbSession.is_active: + dbSession.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Failed to retrieve records due to an internal server error." + ) + finally: + dbSession.close() + +@router.post("/user-consent/insert/", + tags=["User Consent"], + status_code=status.HTTP_201_CREATED) +async def insert_user_consent(itemRequest: userConsentInsertRequest, + session: SessionContainer = Depends(verify_session())): + dbEngine = mih_database.mihDbConnections.dbAllConnect() + dbSession = Session(dbEngine) + try: + newUserConsent = UserConsent( + app_id = itemRequest.app_id, + privacy_policy_accepted = itemRequest.privacy_policy_accepted, + terms_of_services_accepted = itemRequest.terms_of_services_accepted, + ) + dbSession.add(newUserConsent) + dbSession.commit() + dbSession.refresh(newUserConsent) + return {"message": "Successfully Created file Record"} + except IntegrityError as e: + dbSession.rollback() + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, # 409 Conflict is often suitable for constraint errors + detail=f"Data integrity error: The provided data violates a database constraint. Details: {e.orig}" + ) from e + except SQLAlchemyError as e: + dbSession.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"A database error occurred during insertion. Details: {e.orig}" + ) from e + except Exception as e: + dbSession.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"An unexpected error occurred: {e}" + ) from e + finally: + dbSession.close() + +@router.put("/user-consent/update/", tags=["User Consent"]) +async def update_user_consent(itemRequest: userConsentUpdateRequest, + session: SessionContainer = Depends(verify_session())): + dbEngine = mih_database.mihDbConnections.dbAllConnect() + dbSession = Session(dbEngine) + # pp_accepted_dt = datetime.strptime(itemRequest.privacy_policy_accepted, "%Y-%m-%d %H:%M:%S") + # tos_accepted_dt = datetime.strptime(itemRequest.terms_of_services_accepted, "%Y-%m-%d %H:%M:%S") + try: + existing_consent = dbSession.query(UserConsent).filter(UserConsent.app_id == itemRequest.app_id).first() + if not existing_consent: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User Consent not found") + + existing_consent.privacy_policy_accepted = itemRequest.privacy_policy_accepted + existing_consent.terms_of_services_accepted = itemRequest.terms_of_services_accepted + + dbSession.commit() + return {"message": "Successfully Updated User Consent Record"} + except HTTPException as http_exc: + # Re-raise HTTPException directly if it was raised within the try block + raise http_exc + except IntegrityError as e: + dbSession.rollback() + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail=f"Data integrity error: The provided data violates a database constraint. Details: {e.orig}" + ) from e + except SQLAlchemyError as e: + dbSession.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"A database error occurred during update. Details: {e.orig}" + ) from e + except Exception as e: + dbSession.rollback() + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"An unexpected error occurred: {e}" + ) from e + finally: + dbSession.close() \ No newline at end of file