From c43eef64f38280f3691062f56ea901c1d246d24c Mon Sep 17 00:00:00 2001 From: Xitlaly-P Date: Sun, 11 May 2025 18:29:48 -0400 Subject: [PATCH 01/21] yes Co-authored-by: Vinita vinitaacharya2004@gmail.com --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cd7b58d..8c1f887 100644 --- a/README.md +++ b/README.md @@ -8,4 +8,4 @@ Then, install the needed libraries through the requirements.txt file: pip install -r requirements.txt Run the File: -python server.py +python server.py \ No newline at end of file From 1d9b71244d19097bfb49dd9426b8a1faa43dac1e Mon Sep 17 00:00:00 2001 From: Cassandra Lanjwal Date: Sun, 11 May 2025 21:47:53 -0400 Subject: [PATCH 02/21] pictures get downloaded to the bucket hooray also vinita get comments returns doctor_id and patient_id --- routes/community_routes.py | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/routes/community_routes.py b/routes/community_routes.py index e44bcdd..4171789 100644 --- a/routes/community_routes.py +++ b/routes/community_routes.py @@ -1,9 +1,17 @@ from flask import Blueprint, request, jsonify from db import mysql import bcrypt, base64 +import os +from google.cloud import storage +import time comm_bp = Blueprint('comm_bp', __name__) +credentials_path = os.getenv("GOOGLE_APPLICATION_CREDENTIALS") +os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = credentials_path +GCS_BUCKET = "image-bucket-490" +storage_client = storage.Client() + # get a post by post_id @comm_bp.route('/posts/', methods=['GET']) def get_posts(post_id): @@ -268,9 +276,22 @@ def add_post(): meal_name = data.get('meal_name') meal_calories = data.get('meal_calories') description = data.get('description') - picture = data.get('picture') + # picture = data.get('picture') add_tag = data.get('add_tag') + meal_picture_url = None + picture = data.get('picture') # Base64 encoded image data + if picture: + try: + picture = base64.b64decode(picture) + filename = f"meals/{data['meal_name']}_{data['user_id']}_{int(time.time())}.png" + bucket = storage_client.bucket(GCS_BUCKET) + blob = bucket.blob(filename) + blob.upload_from_string(picture, content_type='image/png') + + meal_picture_url = f"https://storage.googleapis.com/{GCS_BUCKET}/{filename}" + except Exception as e: + return jsonify({"error": f"Failed to upload image: {str(e)}"}), 400 cursor = mysql.connection.cursor() try: @@ -284,7 +305,7 @@ def add_post(): cursor.execute(""" INSERT INTO COMMUNITY_POST (meal_id, user_id, description, picture, add_tag) VALUES (%s, %s, %s, %s, %s) - """, (meal_id, user_id, description, picture, add_tag)) + """, (meal_id, user_id, description, meal_picture_url, add_tag)) mysql.connection.commit() @@ -295,7 +316,7 @@ def add_post(): "meal_name": meal_name, "meal_calories": meal_calories, "description": description, - "picture": picture, + "picture": meal_picture_url, "add_tag": add_tag }), 201 @@ -677,7 +698,8 @@ def get_comments(post_id): query = """ SELECT PC.comment_id, PC.post_id, PC.user_id, PC.comment_text, PC.created_at, COALESCE(P.first_name, D.first_name) AS first_name, - COALESCE(P.last_name, D.last_name) AS last_name + COALESCE(P.last_name, D.last_name) AS last_name, + U.doctor_id, U.patient_id FROM POST_COMMENTS AS PC JOIN USER AS U ON PC.user_id = U.user_id LEFT JOIN PATIENT AS P ON U.patient_id = P.patient_id @@ -697,7 +719,9 @@ def get_comments(post_id): "comment_text": comment[3], "created_at": comment[4], "first_name": comment[5], - "last_name": comment[6] + "last_name": comment[6], + "doctor_id": comment[7], + "patient_id": comment[8] }) return jsonify(result), 200 From 92077915cdb6b0ab361a808682066ac80ad40b78 Mon Sep 17 00:00:00 2001 From: Cassandra Lanjwal Date: Sun, 11 May 2025 22:19:02 -0400 Subject: [PATCH 03/21] pictures should post with front end changes hopefully --- routes/community_routes.py | 39 +++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/routes/community_routes.py b/routes/community_routes.py index 4171789..bdfd857 100644 --- a/routes/community_routes.py +++ b/routes/community_routes.py @@ -68,18 +68,18 @@ def get_posts(post_id): mysql.connection.commit() - post_picture = post[4] - if post_picture: - if isinstance(post_picture, str): - post_picture = post_picture.encode('utf-8') - post_picture = base64.b64encode(post_picture).decode('utf-8') + # post_picture = post[4] + # if post_picture: + # if isinstance(post_picture, str): + # post_picture = post_picture.encode('utf-8') + # post_picture = base64.b64encode(post_picture).decode('utf-8') result = { "post_id": post[0], "meal_id": post[1], "user_id": post[2], "description": post[3], - "picture": post_picture, + "picture": post[4], "created_at": post[5], "meal_name": post[6], "meal_calories": post[7], @@ -141,18 +141,18 @@ def get_posts_by_user(user_id): result = [] for post in posts: - post_picture = post[4] - if post_picture: - if isinstance(post_picture, str): - post_picture = post_picture.encode('utf-8') - post_picture = base64.b64encode(post_picture).decode('utf-8') + # post_picture = post[4] + # if post_picture: + # if isinstance(post_picture, str): + # post_picture = post_picture.encode('utf-8') + # post_picture = base64.b64encode(post_picture).decode('utf-8') result.append({ "post_id": post[0], "meal_id": post[1], "user_id": post[2], "description": post[3], - "picture": post_picture, + "picture": post[4], "created_at": post[5], "meal_name": post[6], "meal_calories": post[7], @@ -210,18 +210,18 @@ def get_all_posts(): result = [] for post in posts: - post_picture = post[4] - if post_picture: - if isinstance(post_picture, str): - post_picture = post_picture.encode('utf-8') - post_picture = base64.b64encode(post_picture).decode('utf-8') + # post_picture = post[4] + # if post_picture: + # if isinstance(post_picture, str): + # post_picture = post_picture.encode('utf-8') + # post_picture = base64.b64encode(post_picture).decode('utf-8') result.append({ "post_id": post[0], "meal_id": post[1], "user_id": post[2], "description": post[3], - "picture": post_picture, + "picture": post[4], "created_at": post[5], "meal_name": post[6], "meal_calories": post[7], @@ -284,7 +284,8 @@ def add_post(): if picture: try: picture = base64.b64decode(picture) - filename = f"meals/{data['meal_name']}_{data['user_id']}_{int(time.time())}.png" + meal_name_formatted = meal_name.replace(" ", "_") + filename = f"meals/{meal_name_formatted}_{data['user_id']}_{int(time.time())}.png" bucket = storage_client.bucket(GCS_BUCKET) blob = bucket.blob(filename) blob.upload_from_string(picture, content_type='image/png') From c727438d84cb2359b8e924bf464d35bb55982512 Mon Sep 17 00:00:00 2001 From: Cassandra Lanjwal Date: Sun, 11 May 2025 22:44:37 -0400 Subject: [PATCH 04/21] image_key added --- image_key.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 image_key.json diff --git a/image_key.json b/image_key.json new file mode 100644 index 0000000..c2dbc52 --- /dev/null +++ b/image_key.json @@ -0,0 +1,13 @@ +{ + "type": "service_account", + "project_id": "meta-territory-459219-r0", + "private_key_id": "d4b6009a2cf211d1ce5e9286103e87671443f165", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDqW8GqKdZMLO6M\nXR5sYo/v7FTGGWbrXDBO0YsSixKiCyDMlP3wYFL0N6iZMNZaoDY5KRLYNLidipf7\n7fFRcOSiKFEy1iH4IpunFc2MuSIJdpxO3k77R1aVX6yomdfBxsDvFuKnUHqwodC6\nzPqC0ChR2yrY76KMDobIcjSr7Dwt1cjT35y04w+vuDMtie0c+y8arDKWHa3St+R1\nULF0IrEk8yLrWEzd+s6T/k3XcXgMpfoiHJPwmDaKuGJmNnhDY2U1ykXBDI/tI1Hh\nCakR47vu7z0/PVn8blTGgL+KoOB0+B+0K4Clk3+HJNcoMqikxOpgAdlhFyD9qHBa\nCTb/t76vAgMBAAECggEASClLpw0tzpPROggcK4xnvJz892hv782ZlFNiKqoG6T1a\n8pN345GNSWRv9gd7ITmOqKgUhN0q8Vtw8+IuuYB07ddTNT+JAyII4nhMq+V/bQGI\nHKLIGAg9yU3F2L5SGHkOBDB8Rr95n7SblEE0a54TpCzDczvKRazdZJAB1uQtHYFM\n2fFWxdGsBGl+M/MTos2So/z+d+5izwJL3R7xUsaeqpa85nTaGOum54ch3Oe6XAKR\nPuBZE35hWh6zy0dIh1yQAKAP8L8p1hAHKAJ/F8a2iXfaLtJCMDUHZbrhxZHGfRwj\nrI3WX5zDRDEdtGw7Gp7pCeaCCX3k5g/JgtbKvsn/tQKBgQD+rCbkzi5htOvy4aI2\nke92omx9LN4ZTHLVm1KX4h/8T7rLA3QcsEOO15HN4FDgXN7GxKRS2nTDI8Yas9fZ\nHAcLjgsACddbEd8gffLaUOkMdB4t6DpUh13ky4ySE70gbAjZB6yD0KvFM6tjkndN\nJnP3U9dxGZ2GSF1ZA72hCYAqHQKBgQDrlH8YXVdHq9U0w/nViXN2VGpHDIo1rymG\nPEspnZS//noFCZhhqEyA7yYeWFIvhifkJBBS2V22vxup0WYgxCdppM2bWXs2wa4e\nkrLAL8nvW8gJxLqIVzRh3RmLaykkgRb/xlzp33O1UMyUKZO03oI6CQTWj1FIZqsp\nkmK7B5gSOwKBgBjA3t7qc3OnxC9NYxjuwlnDm8vm6oeG+LpuaG6+2eEKVtuYsFaB\ng2+E4HUkUngcTsL/JgGvnh3BgmXiCR7fLIgg/Ey6qCX/7LIZy9sQdjZxquVHZzQE\nHEo6xqUEKiPWnF2L82a0h52ZUhVRjSsAi9EEYzJpDPiaXAhk5q+aXsy5AoGBAI7f\nFl1ubnp3CezgVepdW/sNow7H2iJIwwQ4GNV6TjZKXTIk6ZeAfDVN1G5e3ikYUaUZ\nAhVc4mrsaBiLeEay77wwSMnIcON6fuDn0ySfVvalUSIrf30kdg5MCyv0p5UN/SbN\n5Jep/wCdqhTJSl1M/Om5goelj4Q7T978eog3SND1AoGAPxhsHlEZZVk1yLYT/6BU\nBhRgQ5UAqvVESPo/3y/wlKHbEBsUo3KpmDT1Psf075s+Go8cAndkDhzMXn+5wzSP\n1jqeMS/yAFKxBK59VBD8TznM7aelS19b+9dNSz7pz8ey87O0GPFBogfx17BoW6hK\n8zM/zIAMA95a+KN5BnrS/5E=\n-----END PRIVATE KEY-----\n", + "client_email": "cassie-490@meta-territory-459219-r0.iam.gserviceaccount.com", + "client_id": "105188056042474012280", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/cassie-490%40meta-territory-459219-r0.iam.gserviceaccount.com", + "universe_domain": "googleapis.com" +} From c211d1d5bd6567a927e08faada5ffd2fecfae7d7 Mon Sep 17 00:00:00 2001 From: Cassandra Lanjwal Date: Sun, 11 May 2025 22:48:06 -0400 Subject: [PATCH 05/21] oops made changes to this file too --- app.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app.py b/app.py index fe53b97..076d3a2 100644 --- a/app.py +++ b/app.py @@ -26,6 +26,9 @@ swagger = Swagger(app) socketio = SocketIO(app, cors_allowed_origins="*") +# loading google_application credentials from env variable +os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = os.getenv("GOOGLE_APPLICATION_CREDENTIALS") + # MySQL config app.config['MYSQL_HOST'] = config.MYSQL_HOST app.config['MYSQL_USER'] = config.MYSQL_USER From 2719dc0bd1818d8f0ad096c27dfe3574e8cb3e60 Mon Sep 17 00:00:00 2001 From: Cassandra Lanjwal Date: Sun, 11 May 2025 23:39:19 -0400 Subject: [PATCH 06/21] pictures for doc --- routes/doctor_routes.py | 84 ++++++++++++++++++++++------------------- 1 file changed, 45 insertions(+), 39 deletions(-) diff --git a/routes/doctor_routes.py b/routes/doctor_routes.py index dcd1940..b0d107c 100644 --- a/routes/doctor_routes.py +++ b/routes/doctor_routes.py @@ -2,13 +2,17 @@ from rabbitmq_utils import send_medication_request from db import mysql import bcrypt, base64 -# from google.cloud import storage +from google.cloud import storage import time +import os doctor_bp = Blueprint('doctor_bp', __name__) -# GCS_BUCKET = "clinic-db-bucket" -# storage_client = storage.Client() +credentials_path = os.getenv("GOOGLE_APPLICATION_CREDENTIALS") +os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = credentials_path +GCS_BUCKET = "image-bucket-490" +storage_client = storage.Client() + @doctor_bp.route('/register-doctor', methods=['POST']) def register_doctor(): """ @@ -42,21 +46,20 @@ def register_doctor(): password = data.get('password') hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()) - # Optional: handle doctor picture later + doctor_picture_url = None doctor_picture = data.get('doctor_picture') # Base64 encoded image data - # doctor_picture_url = None - # if doctor_picture: - # try: - # doctor_picture = base64.b64decode(doctor_picture) - # filename = f"doctors/{data['first_name']}_{int(time.time())}.png" - # bucket = storage_client.bucket(GCS_BUCKET) - # blob = bucket.blob(filename) - # blob.upload_from_string(doctor_picture, content_type='image/png') - # doctor_picture_url = blob.public_url - # except Exception as e: - # return jsonify({"error": f"Failed to upload image: {str(e)}"}), 400 - - # Prepare SQL + if doctor_picture: + try: + doctor_picture = base64.b64decode(doctor_picture) + filename = f"doctors/{data['first_name']}_{data['last_name']}_{int(time.time())}.png" + bucket = storage_client.bucket(GCS_BUCKET) + blob = bucket.blob(filename) + blob.upload_from_string(doctor_picture, content_type='image/png') + + doctor_picture_url = f"https://storage.googleapis.com/{GCS_BUCKET}/{filename}" + except Exception as e: + return jsonify({"error": f"Failed to upload image: {str(e)}"}), 400 + query = """ INSERT INTO DOCTOR ( first_name, last_name, email, password, description, license_num, @@ -83,7 +86,7 @@ def register_doctor(): data['zipcode'], data['city'], data['state'], - doctor_picture # or doctor_picture_url if using GCS + doctor_picture_url # or doctor_picture_url if using GCS ) try: @@ -122,12 +125,6 @@ def get_doctor(doctor_id): doctor = cursor.fetchone() if doctor: - doctor_picture = doctor[18] # Adjusted index due to added fields - if doctor_picture: - if isinstance(doctor_picture, str): - doctor_picture = doctor_picture.encode('utf-8') - doctor_picture = base64.b64encode(doctor_picture).decode('utf-8') - return jsonify({ "doctor_id": doctor[0], "first_name": doctor[1], @@ -147,7 +144,7 @@ def get_doctor(doctor_id): "zipcode": doctor[15], "city": doctor[16], "state": doctor[17], - "doctor_picture": doctor_picture, + "doctor_picture": doctor[18], "accepting_patients": doctor[19], "doctor_rating": doctor[20], }), 200 @@ -253,11 +250,11 @@ def get_all_doctors(): result = [] for doc in doctors: - doctor_picture = doc[18] # Corrected index due to password removal - if doctor_picture: - if isinstance(doctor_picture, str): - doctor_picture = doctor_picture.encode('utf-8') - doctor_picture = base64.b64encode(doctor_picture).decode('utf-8') + # doctor_picture = doc[18] # Corrected index due to password removal + # if doctor_picture: + # if isinstance(doctor_picture, str): + # doctor_picture = doctor_picture.encode('utf-8') + # doctor_picture = base64.b64encode(doctor_picture).decode('utf-8') result.append({ "doctor_id": doc[0], @@ -278,7 +275,7 @@ def get_all_doctors(): "zipcode": doc[15], "city": doc[16], "state": doc[17], - "doctor_picture": doctor_picture, + "doctor_picture": doc[18], "accepting_patients": doc[19], "doctor_rating": doc[20], # created_at and updated_at are fetched but not returned @@ -585,12 +582,6 @@ def get_patients_by_doctor(doctor_id): result = [] for pat in patients: - profile_pic = pat[8] - if profile_pic: - if isinstance(profile_pic, str): - profile_pic = profile_pic.encode('utf-8') - profile_pic = base64.b64encode(profile_pic).decode('utf-8') - result.append({ "patient_id": pat[0], "doctor_id": pat[1], @@ -600,7 +591,7 @@ def get_patients_by_doctor(doctor_id): "last_name": pat[5], "medical_conditions": pat[6], "pharmacy_id": pat[7], - "profile_pic": profile_pic, + "profile_pic": pat[8], "past_procedures": pat[9], "blood_type": pat[10], "health_goals": pat[11], @@ -804,6 +795,20 @@ def edit_doctor(): city = data.get('city') state = data.get('state') + doctor_picture_url = None + doctor_picture = data.get('doctor_picture') # Base64 encoded image data + if doctor_picture: + try: + doctor_picture = base64.b64decode(doctor_picture) + filename = f"doctors/{data['first_name']}_{data['last_name']}_{int(time.time())}.png" + bucket = storage_client.bucket(GCS_BUCKET) + blob = bucket.blob(filename) + blob.upload_from_string(doctor_picture, content_type='image/png') + + doctor_picture_url = f"https://storage.googleapis.com/{GCS_BUCKET}/{filename}" + except Exception as e: + return jsonify({"error": f"Failed to upload image: {str(e)}"}), 400 + cursor = mysql.connection.cursor() try: cursor.execute(""" @@ -821,12 +826,13 @@ def edit_doctor(): zipcode = %s, city = %s, state = %s, + doctor_picture = %s, updated_at = NOW() WHERE doctor_id = %s """, ( first_name, last_name, email, description, years_of_practice, specialty, payment_fee, gender, - phone_number, address, zipcode, city, state, doctor_id + phone_number, address, zipcode, city, state, doctor_picture_url, doctor_id )) mysql.connection.commit() From f29490810bf3d726fc718e4fe3f36f8e6a7e4bf0 Mon Sep 17 00:00:00 2001 From: Cassandra Lanjwal Date: Mon, 12 May 2025 12:00:57 -0400 Subject: [PATCH 07/21] added swagger to chat routes --- routes/chat.py | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/routes/chat.py b/routes/chat.py index bc8a803..1d229fe 100644 --- a/routes/chat.py +++ b/routes/chat.py @@ -8,6 +8,39 @@ @chat_bp.route('/chat/send', methods=['POST']) def send_chat_message(): + """ + Send a chat message between patient and doctor + --- + tags: + - Chat + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - appointment_id + - sender + - text + properties: + appointment_id: + type: integer + sender: + type: string + enum: [patient, doctor] + text: + type: string + responses: + 200: + description: Message saved successfully + 400: + description: Invalid input + 404: + description: Appointment or user not found + 500: + description: Internal server error + """ data = request.get_json() appointment_id = data.get('appointment_id') # maps to appt_id sender_type = data.get('sender') # "patient" or "doctor" @@ -62,6 +95,41 @@ def send_chat_message(): @chat_bp.route('/chat/', methods=['GET']) def get_chat_messages(appointment_id): + """ + Retrieve chat messages for a specific appointment + --- + tags: + - Chat + parameters: + - name: appointment_id + in: path + required: true + schema: + type: integer + responses: + 200: + description: List of chat messages + content: + application/json: + schema: + type: array + items: + type: object + properties: + sender_id: + type: integer + receiver_id: + type: integer + message: + type: string + sent_at: + type: string + format: date-time + 404: + description: No messages found + 500: + description: Internal server error + """ cursor = mysql.connection.cursor() try: cursor.execute(""" @@ -86,6 +154,36 @@ def get_chat_messages(appointment_id): @chat_bp.route('/user', methods=['GET']) def get_user_by_role_id(): + """ + Get user info by patient_id or doctor_id + --- + tags: + - Chat + parameters: + - name: patient_id + in: query + required: false + schema: + type: string + - name: doctor_id + in: query + required: false + schema: + type: string + responses: + 200: + description: User object + content: + application/json: + schema: + type: object + 400: + description: Missing required parameter + 404: + description: User not found + 500: + description: Internal server error + """ patient_id = request.args.get('patient_id') doctor_id = request.args.get('doctor_id') From 461a55020627807f880d19c7e7f7feba32aed10f Mon Sep 17 00:00:00 2001 From: Cassandra Lanjwal Date: Mon, 12 May 2025 12:39:50 -0400 Subject: [PATCH 08/21] updated swagger and updated try/except blocks --- routes/community_routes.py | 302 ++++++++++++++++++++++++------------- 1 file changed, 200 insertions(+), 102 deletions(-) diff --git a/routes/community_routes.py b/routes/community_routes.py index bdfd857..27e5038 100644 --- a/routes/community_routes.py +++ b/routes/community_routes.py @@ -17,9 +17,22 @@ def get_posts(post_id): """ Retrieve community post by ID + --- tags: - Community + parameters: + - name: post_id + in: path + required: true + schema: + type: integer + responses: + 200: + description: Post data returned + 404: + description: Post not found """ + cursor = mysql.connection.cursor() query = """ SELECT CP.post_id, CP.meal_id, CP.user_id, CP.description, CP.picture, CP.created_at, @@ -96,8 +109,20 @@ def get_posts(post_id): def get_posts_by_user(user_id): """ Retrieve community posts by user ID + --- tags: - Community + parameters: + - name: user_id + in: path + required: true + schema: + type: integer + responses: + 200: + description: List of posts + 404: + description: No posts found """ cursor = mysql.connection.cursor() query = """ @@ -169,6 +194,12 @@ def get_posts_by_user(user_id): def get_all_posts(): """ Retrieve all community posts + --- + tags: + - Community + responses: + 200: + description: List of all community posts """ cursor = mysql.connection.cursor() query = """ @@ -693,38 +724,41 @@ def get_comments(post_id): 200: description: List of comments 400: - description: Error retrieving comments + description: Failed to fetch comments """ cursor = mysql.connection.cursor() - query = """ - SELECT PC.comment_id, PC.post_id, PC.user_id, PC.comment_text, PC.created_at, - COALESCE(P.first_name, D.first_name) AS first_name, - COALESCE(P.last_name, D.last_name) AS last_name, - U.doctor_id, U.patient_id - FROM POST_COMMENTS AS PC - JOIN USER AS U ON PC.user_id = U.user_id - LEFT JOIN PATIENT AS P ON U.patient_id = P.patient_id - LEFT JOIN DOCTOR AS D ON U.doctor_id = D.doctor_id - WHERE PC.post_id = %s - ORDER BY PC.created_at DESC; - """ - cursor.execute(query, (post_id,)) - comments = cursor.fetchall() - - result = [] - for comment in comments: - result.append({ - "comment_id": comment[0], - "post_id": comment[1], - "user_id": comment[2], - "comment_text": comment[3], - "created_at": comment[4], - "first_name": comment[5], - "last_name": comment[6], - "doctor_id": comment[7], - "patient_id": comment[8] - }) - return jsonify(result), 200 + try: + query = """ + SELECT PC.comment_id, PC.post_id, PC.user_id, PC.comment_text, PC.created_at, + COALESCE(P.first_name, D.first_name) AS first_name, + COALESCE(P.last_name, D.last_name) AS last_name, + U.doctor_id, U.patient_id + FROM POST_COMMENTS AS PC + JOIN USER AS U ON PC.user_id = U.user_id + LEFT JOIN PATIENT AS P ON U.patient_id = P.patient_id + LEFT JOIN DOCTOR AS D ON U.doctor_id = D.doctor_id + WHERE PC.post_id = %s + ORDER BY PC.created_at DESC; + """ + cursor.execute(query, (post_id,)) + comments = cursor.fetchall() + + result = [] + for comment in comments: + result.append({ + "comment_id": comment[0], + "post_id": comment[1], + "user_id": comment[2], + "comment_text": comment[3], + "created_at": comment[4], + "first_name": comment[5], + "last_name": comment[6], + "doctor_id": comment[7], + "patient_id": comment[8] + }) + return jsonify(result), 200 + except Exception as e: + return jsonify({"Failed to fetch comments. Error": str(e)}), 400 # Save a post/meal @comm_bp.route('/posts/save', methods=['POST']) @@ -765,25 +799,25 @@ def save_post(): cursor = mysql.connection.cursor() try: - # Insert into saved posts table - cursor.execute(""" - INSERT INTO SAVED_MEAL (user_id, meal_id, post_id) - VALUES (%s, %s, %s) - """, (user_id, post_id, meal_id)) + # Insert into saved posts table + cursor.execute(""" + INSERT INTO SAVED_MEAL (user_id, meal_id, post_id) + VALUES (%s, %s, %s) + """, (user_id, post_id, meal_id)) - mysql.connection.commit() + mysql.connection.commit() - return jsonify({ - "message": "Post saved successfully.", - "post_id": post_id, - "user_id": user_id - }), 201 + return jsonify({ + "message": "Post saved successfully.", + "post_id": post_id, + "user_id": user_id + }), 201 except Exception as e: - mysql.connection.rollback() - return jsonify({"error": str(e)}), 400 + mysql.connection.rollback() + return jsonify({"Failed to save post. Error": str(e)}), 400 finally: - cursor.close() + cursor.close() # get saved posts/meals by id @comm_bp.route('/posts/save/', methods=['GET']) @@ -791,6 +825,73 @@ def get_saved(user_id): """ Get saved posts/meals by user_id --- + tags: + - Community + parameters: + - name: user_id + in: path + required: true + schema: + type: integer + responses: + 200: + description: List of saved posts + 400: + description: Failed to fetch saved posts + """ + try: + cursor = mysql.connection.cursor() + query = """ + SELECT SM.saved_meal_id, SM.user_id, SM.meal_id, SM.post_id, + M.meal_name, M.meal_calories, + COALESCE(P.first_name, D.first_name) AS first_name, + COALESCE(P.last_name, D.last_name) AS last_name, + CONCAT_WS(', ', + ( + SELECT GROUP_CONCAT(DISTINCT MP.meal_plan_name SEPARATOR ', ') + FROM MEAL_PLAN_ENTRY AS MPE + JOIN MEAL_PLAN AS MP ON MPE.meal_plan_id = MP.meal_plan_id + WHERE MPE.meal_id = CP.meal_id + ), + CP.add_tag + ) AS tag + FROM SAVED_MEAL AS SM + JOIN MEAL AS M ON SM.meal_id = M.meal_id + JOIN USER AS U ON SM.user_id = U.user_id + JOIN COMMUNITY_POST AS CP ON SM.post_id = CP.post_id + LEFT JOIN PATIENT AS P ON U.patient_id = P.patient_id + LEFT JOIN DOCTOR AS D ON U.doctor_id = D.doctor_id + WHERE SM.user_id = %s + ORDER BY SM.saved_meal_id DESC; + """ + cursor.execute(query, (user_id,)) + saved_posts = cursor.fetchall() + + result = [] + for post in saved_posts: + result.append({ + "saved_meal_id": post[0], + "user_id": post[1], + "meal_id": post[2], + "post_id": post[3], + "meal_name": post[4], + "meal_calories": post[5], + "first_name": post[6], + "last_name": post[7], + "tag": post[8] + }) + return jsonify(result), 200 + except Exception as e: + return jsonify({"Failed to fetch posts due to this error": str(e)}), 400 + finally: + cursor.close() + +# if post is saved +@comm_bp.route('/posts/is-saved', methods=['POST']) +def is_saved(): + """ + Check if a post is saved by a user + --- tags: - Community requestBody: @@ -800,69 +901,18 @@ def get_saved(user_id): schema: type: object required: - - post_id - user_id + - post_id properties: - post_id: - type: integer user_id: type: integer - example: - post_id: 5 - user_id: 1 + post_id: + type: integer responses: - 201: - description: Post saved successfully + 200: + description: Result indicating if post is saved 400: - description: Input error or database failure - """ - cursor = mysql.connection.cursor() - query = """ - SELECT SM.saved_meal_id, SM.user_id, SM.meal_id, SM.post_id, - M.meal_name, M.meal_calories, - COALESCE(P.first_name, D.first_name) AS first_name, - COALESCE(P.last_name, D.last_name) AS last_name, - CONCAT_WS(', ', - ( - SELECT GROUP_CONCAT(DISTINCT MP.meal_plan_name SEPARATOR ', ') - FROM MEAL_PLAN_ENTRY AS MPE - JOIN MEAL_PLAN AS MP ON MPE.meal_plan_id = MP.meal_plan_id - WHERE MPE.meal_id = CP.meal_id - ), - CP.add_tag - ) AS tag - FROM SAVED_MEAL AS SM - JOIN MEAL AS M ON SM.meal_id = M.meal_id - JOIN USER AS U ON SM.user_id = U.user_id - JOIN COMMUNITY_POST AS CP ON SM.post_id = CP.post_id - LEFT JOIN PATIENT AS P ON U.patient_id = P.patient_id - LEFT JOIN DOCTOR AS D ON U.doctor_id = D.doctor_id - WHERE SM.user_id = %s - ORDER BY SM.saved_meal_id DESC; - """ - cursor.execute(query, (user_id,)) - saved_posts = cursor.fetchall() - - result = [] - for post in saved_posts: - result.append({ - "saved_meal_id": post[0], - "user_id": post[1], - "meal_id": post[2], - "post_id": post[3], - "meal_name": post[4], - "meal_calories": post[5], - "first_name": post[6], - "last_name": post[7], - "tag": post[8] - }) - return jsonify(result), 200 - -# if post is saved -@comm_bp.route('/posts/is-saved', methods=['POST']) -def is_saved(): - """ - Check if a post is saved by a user + description: Input error """ data = request.get_json() user_id = data.get('user_id') @@ -883,7 +933,29 @@ def is_saved(): @comm_bp.route('/posts/is-liked', methods=['POST']) def is_liked(): """ - Check if a post is saved by a user + Check if a post is liked by a user + --- + tags: + - Community + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - user_id + - post_id + properties: + user_id: + type: integer + post_id: + type: integer + responses: + 200: + description: Result indicating if post is liked + 400: + description: Input error """ data = request.get_json() user_id = data.get('user_id') @@ -903,6 +975,32 @@ def is_liked(): @comm_bp.route('/posts/unsave', methods=['DELETE']) def unsave_post(): + """ + Unsave a post + --- + tags: + - Community + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - user_id + - post_id + properties: + user_id: + type: integer + post_id: + type: integer + responses: + 200: + description: Post unsaved successfully + 400: + description: Input error + """ + data = request.get_json() user_id = data.get('user_id') post_id = data.get('post_id') From 4d73aad434fe42eb6da41a179bd5c361c4f75396 Mon Sep 17 00:00:00 2001 From: Cassandra Lanjwal Date: Mon, 12 May 2025 14:37:53 -0400 Subject: [PATCH 09/21] fixed doctor unit tests --- .coverage | Bin 53248 -> 53248 bytes .gitignore | 3 +- config.py | 2 +- requirements.txt | Bin 1494 -> 2638 bytes routes/community_routes.py | 2 +- routes/doctor_routes.py | 119 +++++++++++++++++++++++++++++++++---- routes/patient_routes.py | 8 +-- tests/test_doctor.py | 15 ++--- 8 files changed, 125 insertions(+), 24 deletions(-) diff --git a/.coverage b/.coverage index 4ce44ca4f96b38104ac3ec32f0e0207e779d41b1..038992034fe875833a5abb441e58885f56c67463 100644 GIT binary patch delta 917 zcmZ{hKWGzC9LIlm@BT&a-bme~q#_x5ok zBea3ZhACXsyvb@IeW39iV(`Ljk^aAg*G{Q&10oK zxOL;lrs_QVy|Mu4z3;!4rU2UG4iDQ~fu;Qgnpe@j|D7uX>`9gcq8+byUdrZQP+}A1Fs}v!{|bI@zBhb*e8#+&d24tzHVX;}@EQkkurM@gI$Onr7N-^! z$0U~)B^Jf_=cT$7WtOGJxa237=BDPA6vvoY8i25SQGV&<+5S>i$r*_ydIgoC>_F}M zBx|=VNi8nP%u5HF5XS~I!I5MWCa?1oHjZQk>NO`>uVZd%Vop5JCqP#KT@b?pG{u%= zQzjeu3mV5V1NGXIuD8fv$T*q_sMnfQy;{a$j6i*cr0KiuuO-Z@$-uvoSCjV&zYm`Y zUq9bl-dbQ95acxqW@*%9B+Wcv5}0fg$6*x4(x^|mR;1`>k7sFgB;AC`>*7R=qF5Tu zN!N-N`>e4njkctlGT9(r$S97b(Vh&gMe)K$F)WSNq-u@V28FjFCrMGY`F6avf{+CR z|4;sAepNmp-c!86yxzPPz^GT?trz5AVdPZdh+|^_fd)n(Vq*si@v&bkWn*RJY|LWQ zV+PAXq@hfhPy$Sf;Q*WkVj%GnKzs;*Si-;np+TfD5F0>c84?&6c!2g#-rYNcR}m%% zq!TtL^ocOmmoS0clfe{c$k6q`he6=S|9U$f2IjIK|Nmz*#GDn|;0kmVkHn4Z|Nr;% zEMQ>xkk1g!Exb*bxglNqiyzy8u!3r~16on{QyD_;ypLv@9)qSHnGY^Hn-9~;MKEtcpu^4zP+_KHncTfjkRv=yJZ0Dfm=t2YV6Tg@L}{J zEV*@VW6vz>foG+?*F`U2t?dRF8jol7_joINiRz_Y!Me63UW*O|(IteXN3W4-iZ@a8 z5ig{~)3;-<#!N%+g!u~8UE+Tgz9WjKEe7IK9Q!ZbS^^_8pTb6rr(H&3VwR!jp$B_p zbktnhoo61}44?Ek@%ZR*3>+=^djzH{mwIA7_?*Wjf z?M`ag^=;653uN4gQO87u@3_Ik?MxW0%aBKlv9{}}LJlgq0FMp+B4TNY3OI3Y0xhdf z;G2}qe#o{abA`{ISC9;}! z?jbK5ISQV2FXS;0V#H`y!R?p)?HB=BilX`f~LmNq5Blt|cS6Tw_UA9dLCZITXmBdFcDq|_ZFIE9|0&Lf`OmY%DL zk3V(>PITY3o1rsqHWkRv8g-IJ$m+o7s66zz1@Gb}Z6WU#J*lMzN*IG#Dnb`rie`sG z%HJqy`}o|`R9fVv+W8wL{}plSD$)M1LJNeI>y6KVdqj=!U1BJow<HRz_P gUn)ZK8<}`J_&bQ3$!^6>OyC}P(#8cF>WKXP2RD0t^Z)<= delta 84 zcmX>na*dnm|G$YUb(_@~r5Go7G5JkyVo{P$XDDUJW5{GkX2@qK0" responses: 201: description: Doctor registered successfully! 400: - description: Validation error + description: Validation error or image upload failure 500: description: Server/database error """ @@ -156,12 +243,29 @@ def login_doctor(): """ Doctor Login --- + tags: + - Doctor + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - email + - password + properties: + email: { type: string } + password: { type: string } + example: + email: "alice@example.com" + password: "password123" responses: - 200: + 200: description: Login Successful with the Doctor ID. - 401: + 401: description: Invalid credentials. - 404: + 404: description: Doctor not found. """ data = request.get_json() @@ -850,7 +954,6 @@ def edit_doctor(): def get_top_doctors(): """ Get the top 3 rated doctors - --- tags: - Doctor @@ -882,8 +985,4 @@ def get_top_doctors(): except Exception as e: return jsonify({"error": str(e)}), 400 finally: - cursor.close() - - - - + cursor.close() \ No newline at end of file diff --git a/routes/patient_routes.py b/routes/patient_routes.py index d9d881d..02cc6df 100644 --- a/routes/patient_routes.py +++ b/routes/patient_routes.py @@ -570,7 +570,7 @@ def remove_patient_doctor(patient_id): # ------------------ LOGIN ENDPOINTS --------------------------------------- #login patient with pw -''' + @patient_bp.route('/login-patient', methods=['POST']) def login_patient(): """ @@ -663,13 +663,13 @@ def login_patient(): return jsonify({"error": "Patient not found"}), 404 finally: cursor.close() -''' +''' #login patient w/o pw @patient_bp.route('/login-patient', methods=['POST']) def login_patient(): """ - Login a patient using email + Login a patient using email for testing purposes --- tags: @@ -718,7 +718,7 @@ def login_patient(): return jsonify({"message": "Login successful", "patient_id": patient[0]}), 200 else: return jsonify({"error": "Patient not found"}), 404 - +''' #---------------------------- DAILY + WEEKLY SURVEY END POINTS ------------------------------------ # add to daily survey diff --git a/tests/test_doctor.py b/tests/test_doctor.py index 6e42bd8..9a109e3 100644 --- a/tests/test_doctor.py +++ b/tests/test_doctor.py @@ -142,11 +142,11 @@ def test_get_all_doctors_success(client): 1, "Alice", "Nguyen", "alice@example.com", "Cardiology specialist", "MD123", "2028-12-31", "1980-01-15", "Harvard", "Cardiology", 10, 200.0, "Female", "1234567890", "123 Lane", "10001", "New York", "NY", - b"binarypicdata", # [18] doctor_picture - True, # [19] accepting_patients - 4.9, # [20] doctor_rating - "2021-01-01", # [21] created_at - "2022-01-01" # [22] updated_at + "data:image/png;base64,binarypicdata", # <-- now a string + True, + 4.9, + "2021-01-01", + "2022-01-01" ) ] @@ -158,7 +158,7 @@ def test_get_all_doctors_success(client): data = response.get_json() assert isinstance(data, list) assert data[0]['first_name'] == "Alice" - assert data[0]['doctor_picture'] == base64.b64encode(b"binarypicdata").decode() + assert data[0]['doctor_picture'] == "data:image/png;base64,binarypicdata" assert data[0]['accepting_patients'] is True assert data[0]['doctor_rating'] == 4.9 @@ -425,7 +425,7 @@ def test_get_patients_by_doctor_success(client): mock_cursor.fetchall.return_value = [ ( 1, 1, "patient@example.com", "1234567890", - "John", "Doe", "Asthma", 2, b"picdata", + "John", "Doe", "Asthma", 2, "data:image/png;base64,picdata", "Appendectomy", "A+", "Weight loss", "High", "BlueCross", "12345", "2025-12-31", 200.50, "2024-01-01", "2024-04-01" @@ -434,6 +434,7 @@ def test_get_patients_by_doctor_success(client): mock_cursor_factory.return_value = mock_cursor response = client.get('/doc_patients/1') + assert response.get_json()[0]['profile_pic'] == "data:image/png;base64,picdata" assert response.status_code == 200 assert isinstance(response.get_json(), list) From 2f5b82358c4d740fed2454ca83d2ea6f28bf665c Mon Sep 17 00:00:00 2001 From: Cassandra Lanjwal Date: Mon, 12 May 2025 14:55:08 -0400 Subject: [PATCH 10/21] "fix yaml" --- .coverage | Bin 53248 -> 53248 bytes .github/workflows/test.yml | 1 + .gitignore | 3 +-- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.coverage b/.coverage index 038992034fe875833a5abb441e58885f56c67463..9c2420bd9e4b758f2dc39d7de65d78e03ef09ce9 100644 GIT binary patch delta 16 XcmZozz}&Eac|%=4W76jO{zeA?Iz9&J delta 16 XcmZozz}&Eac|%=4W7X#R{zeA?I?e|H diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2d8c6c4..4f3b12f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,6 +15,7 @@ jobs: MYSQL_PASSWORD: ${{ secrets.MYSQL_PASSWORD }} MYSQL_DB: ${{ secrets.MYSQL_DB }} RABBITMQ_URL: ${{ secrets.RABBITMQ_URL }} + GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} steps: - name: Check out code diff --git a/.gitignore b/.gitignore index bb837aa..976b7d1 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,4 @@ __pycache__/ *.pyc *.pyo .env -config.py -.coverage \ No newline at end of file +config.py \ No newline at end of file From c0b6b6cdc70f55dc853dc596623e0d33559cbf61 Mon Sep 17 00:00:00 2001 From: Cassandra Lanjwal Date: Mon, 12 May 2025 15:23:33 -0400 Subject: [PATCH 11/21] edit swagger for doctor and patient routes --- routes/doctor_routes.py | 555 +++++++++++++++++++++++++++++++++++---- routes/patient_routes.py | 46 ++-- 2 files changed, 523 insertions(+), 78 deletions(-) diff --git a/routes/doctor_routes.py b/routes/doctor_routes.py index 7a0776e..c795f31 100644 --- a/routes/doctor_routes.py +++ b/routes/doctor_routes.py @@ -188,16 +188,69 @@ def register_doctor(): @doctor_bp.route('/doctor/', methods=['GET']) def get_doctor(doctor_id): """ - Retrieve a doctor's information by their ID number + Retrieve a doctor's info by their ID --- + tags: + - Doctor + parameters: + - name: doctor_id + in: path + required: true + schema: + type: integer responses: 200: - description: return doctor information including doctor_id, first_name, last_name, email, description, license_num, - license_exp_date, dob, med_school, specialty, years_of_practice, payment_fee, - gender, phone_number, address, zipcode, city, state, doctor_picture, - accepting_patients, doctor_rating + description: Doctor information returned successfully + content: + application/json: + schema: + type: object + properties: + doctor_id: { type: integer } + first_name: { type: string } + last_name: { type: string } + email: { type: string } + description: { type: string } + license_num: { type: string } + license_exp_date: { type: string, format: date } + dob: { type: string, format: date } + med_school: { type: string } + specialty: { type: string } + years_of_practice: { type: integer } + payment_fee: { type: number } + gender: { type: string } + phone_number: { type: string } + address: { type: string } + zipcode: { type: string } + city: { type: string } + state: { type: string } + doctor_picture: { type: string } + accepting_patients: { type: boolean } + doctor_rating: { type: number } + example: + doctor_id: 1 + first_name: "Alice" + last_name: "Nguyen" + email: "alice@example.com" + description: "Cardiology specialist" + license_num: "MD123" + license_exp_date: "2028-12-31" + dob: "1980-01-15" + med_school: "Harvard" + specialty: "Cardiology" + years_of_practice: 10 + payment_fee: 200.0 + gender: "Female" + phone_number: "1234567890" + address: "123 Lane" + zipcode: "10001" + city: "New York" + state: "NY" + doctor_picture: "https://storage.googleapis.com/doctor/doctor1.png" + accepting_patients: true + doctor_rating: 4.9 404: - description: Doctor not found. + description: Doctor not found """ cursor = mysql.connection.cursor() query = """ @@ -309,13 +362,39 @@ def login_doctor(): @doctor_bp.route('/doctor/', methods=['DELETE']) def delete_doctor(doctor_id): """ - Delete Doctor by their ID number + Delete a doctor by their ID number --- + tags: + - Doctor + parameters: + - name: doctor_id + in: path + required: true + schema: + type: integer responses: 200: - description: Doctor with denoted ID has been deleted. + description: Doctor deleted successfully + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: + message: "Doctor with ID 1 has been deleted." 404: - description: Doctor not found. + description: Doctor not found + content: + application/json: + schema: + type: object + properties: + error: + type: string + example: + error: "Doctor not found" """ cursor = mysql.connection.cursor() @@ -337,9 +416,61 @@ def get_all_doctors(): """ Retrieve all doctors' information --- + tags: + - Doctor responses: 200: - description: All doctors' information is returned. + description: List of all registered doctors + content: + application/json: + schema: + type: array + items: + type: object + properties: + doctor_id: { type: integer } + first_name: { type: string } + last_name: { type: string } + email: { type: string } + description: { type: string } + license_num: { type: string } + license_exp_date: { type: string, format: date } + dob: { type: string, format: date } + med_school: { type: string } + specialty: { type: string } + years_of_practice: { type: integer } + payment_fee: { type: number } + gender: { type: string } + phone_number: { type: string } + address: { type: string } + zipcode: { type: string } + city: { type: string } + state: { type: string } + doctor_picture: { type: string } + accepting_patients: { type: boolean } + doctor_rating: { type: number } + example: + - doctor_id: 1 + first_name: "Alice" + last_name: "Nguyen" + email: "alice@example.com" + description: "Cardiology specialist" + license_num: "MD123" + license_exp_date: "2028-12-31" + dob: "1980-01-15" + med_school: "Harvard" + specialty: "Cardiology" + years_of_practice: 10 + payment_fee: 200.0 + gender: "Female" + phone_number: "1234567890" + address: "123 Lane" + zipcode: "10001" + city: "New York" + state: "NY" + doctor_picture: "https://storage.googleapis.com/bucket/doctor1.png" + accepting_patients: true + doctor_rating: 4.9 """ cursor = mysql.connection.cursor() query = """ @@ -434,13 +565,49 @@ def get_appointments_by_doctor(doctor_id): @doctor_bp.route('/doc-appointments-status/', methods=['PATCH']) def update_appointment_status(appointment_id): """ - Updates the appointment status: accepts (1) or sets to 2 if denied. + Update the acceptance status of an appointment --- + tags: + - Appointment + parameters: + - name: appointment_id + in: path + required: true + schema: + type: integer + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - accepted + properties: + accepted: + type: integer + description: 1 to accept, 0 to deny (will be set to 2) + example: + accepted: 1 responses: 200: - description: Appointment status updated successfully + description: Status update message + content: + application/json: + schema: + type: object + properties: + message: + type: string 400: - description: Error message based on what went wrong. + description: Invalid input or database error + content: + application/json: + schema: + type: object + properties: + error: + type: string """ data = request.get_json() new_status = data.get('accepted') @@ -485,17 +652,51 @@ def update_appointment_status(appointment_id): @doctor_bp.route('/prescription/add', methods=['POST']) def add_prescription(): """ - Prescribe a medicine to a patient with the quantity + Prescribe a medicine to a patient --- + tags: + - Prescription + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - patient_id + - medicine_id + - quantity + properties: + patient_id: + type: integer + medicine_id: + type: integer + quantity: + type: integer + minimum: 1 + example: + patient_id: 1 + medicine_id: 3 + quantity: 2 responses: - 200: - description: Prescription added successfully. - 400: - description: patient_id, medicine_id, and quantity are required. - 400: - description: quantity must be a positive integer. + 201: + description: Prescription added successfully + content: + application/json: + schema: + type: object + properties: + message: + type: string 400: - description: Error message based on what went wrong. + description: Validation or database error + content: + application/json: + schema: + type: object + properties: + error: + type: string """ data = request.get_json() patient_id = data.get('patient_id') @@ -534,15 +735,58 @@ def add_prescription(): @doctor_bp.route('/doctor-accepting-status/', methods=['PATCH']) def update_accepting_status(doctor_id): """ - Handles if doctor is accepting patients or not. + Update whether a doctor is accepting patients --- + tags: + - Doctor + parameters: + - name: doctor_id + in: path + required: true + schema: + type: integer + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - accepting_patients + properties: + accepting_patients: + type: integer + description: 1 for accepting, 0 for not accepting + example: + accepting_patients: 1 responses: 200: - description: Doctor's accepting status updated successfully. + description: Doctor's accepting status updated + content: + application/json: + schema: + type: object + properties: + message: + type: string 404: - description: Doctor not found or no change made. + description: Doctor not found or unchanged + content: + application/json: + schema: + type: object + properties: + error: + type: string 400: - description: Error message based on what went wrong. + description: Invalid input or database error + content: + application/json: + schema: + type: object + properties: + error: + type: string """ data = request.get_json() new_status = data.get('accepting_patients') @@ -574,15 +818,56 @@ def update_accepting_status(doctor_id): @doctor_bp.route('/appointment//add_note', methods=['PATCH']) def add_appointment_note(appt_id): """ - Handles if doctor is accepting patients or not. + Add a note to an appointment --- + tags: + - Appointment + parameters: + - name: appt_id + in: path + required: true + schema: + type: integer + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - doctor_appointment_note + properties: + doctor_appointment_note: + type: string + example: + doctor_appointment_note: "Patient advised to follow up in 2 weeks." responses: 200: - description: Doctor's appointment note added successfully, with the appointment id and doctor appointment note. + description: Note added successfully + content: + application/json: + schema: + type: object + properties: + message: { type: string } + appt_id: { type: integer } + doctor_appointment_note: { type: string } 404: description: Appointment not found + content: + application/json: + schema: + type: object + properties: + error: { type: string } 400: - description: Error message based on what went wrong. + description: Invalid input or database error + content: + application/json: + schema: + type: object + properties: + error: { type: string } """ data = request.get_json() note = data.get('doctor_appointment_note') @@ -618,15 +903,37 @@ def add_appointment_note(appt_id): @doctor_bp.route('/doctor//rating', methods=['GET']) def get_doctor_average_rating(doctor_id): """ - Compute the average rating for a doctor based on patient appointments. + Get average rating for a doctor based on appointment reviews --- + tags: + - Doctor + parameters: + - name: doctor_id + in: path + required: true + schema: + type: integer responses: 200: - description: Average rating retrieved successfully with the doctor_id and average_rating. - 200: - description: This doctor has no ratings yet with the doctor_id and the average_rating as None + description: Doctor rating retrieved or no ratings found + content: + application/json: + schema: + type: object + properties: + message: { type: string } + doctor_id: { type: integer } + average_rating: + type: number + nullable: true 400: - description: Error message based on what went wrong. + description: Retrieval error + content: + application/json: + schema: + type: object + properties: + error: { type: string } """ cursor = mysql.connection.cursor() @@ -661,13 +968,27 @@ def get_doctor_average_rating(doctor_id): @doctor_bp.route('/doc_patients/', methods=['GET']) def get_patients_by_doctor(doctor_id): """ - Get all patients assigned to a specific doctor + Get all patients assigned to a doctor --- + tags: + - Doctor + parameters: + - name: doctor_id + in: path + required: true + schema: + type: integer responses: 200: - description: return information about the doctor's patient based on the doctor id and patient id. + description: List of patients assigned to the doctor + content: + application/json: + schema: + type: array + items: + type: object 404: - description: Error, no description. + description: No patients found for this doctor """ cursor = mysql.connection.cursor() query = """ @@ -713,13 +1034,33 @@ def get_patients_by_doctor(doctor_id): @doctor_bp.route('/doc-past/', methods=['GET']) def get_past_appointments_by_doctor(doctor_id): """ - Get past appointments by doctor ID + Get past appointments for a doctor --- + tags: + - Appointment + parameters: + - name: doctor_id + in: path + required: true + schema: + type: integer responses: 200: - description: return information about the past appointments based on the doctor id and patient id. + description: List of past appointments + content: + application/json: + schema: + type: array + items: + type: object 400: - description: Error message based on what went wrong. + description: Retrieval error + content: + application/json: + schema: + type: object + properties: + error: { type: string } """ cursor = mysql.connection.cursor() @@ -756,13 +1097,33 @@ def get_past_appointments_by_doctor(doctor_id): @doctor_bp.route('/doc-upcoming/', methods=['GET']) def get_upcoming_appointments_by_doctor(doctor_id): """ - Get upcoming appointments by doctor ID + Get upcoming accepted appointments for a doctor --- + tags: + - Appointment + parameters: + - name: doctor_id + in: path + required: true + schema: + type: integer responses: 200: - description: return information about the upcoming appointments based on the doctor id and patient id. + description: List of upcoming accepted appointments + content: + application/json: + schema: + type: array + items: + type: object 400: - description: Error message based on what went wrong. + description: Error retrieving appointments + content: + application/json: + schema: + type: object + properties: + error: { type: string } """ cursor = mysql.connection.cursor() @@ -799,13 +1160,33 @@ def get_upcoming_appointments_by_doctor(doctor_id): @doctor_bp.route('/requested-appointments/', methods=['GET']) def get_requested_appointments(doctor_id): """ - Get upcoming requested (not yet accepted) appointments by doctor ID + Get requested (not yet accepted) upcoming appointments for a doctor --- + tags: + - Appointment + parameters: + - name: doctor_id + in: path + required: true + schema: + type: integer responses: 200: - description: Returns upcoming appointments that have not been accepted yet for the specified doctor. + description: List of upcoming requested appointments + content: + application/json: + schema: + type: array + items: + type: object 400: - description: Error message based on what went wrong. + description: Error retrieving appointments + content: + application/json: + schema: + type: object + properties: + error: { type: string } """ cursor = mysql.connection.cursor() @@ -846,15 +1227,35 @@ def get_requested_appointments(doctor_id): @doctor_bp.route('/request-prescription', methods=['POST']) def request_prescription(): """ - Prescription request for a patient that goes to the pharmacy + Send a prescription request to the pharmacy --- + tags: + - Prescription + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - appt_id + - medicine_id + - quantity + properties: + appt_id: { type: integer } + medicine_id: { type: integer } + quantity: { type: integer } + example: + appt_id: 7 + medicine_id: 2 + quantity: 30 responses: 200: - description: Prescription request sent successfully. + description: Prescription request sent successfully 400: description: Missing required fields 500: - description: Error message based on what went wrong. + description: Server error during processing """ data = request.json required_fields = ['appt_id', 'medicine_id', 'quantity'] @@ -872,15 +1273,51 @@ def request_prescription(): @doctor_bp.route('/edit-doctor', methods=['PUT']) def edit_doctor(): """ - Can edit doctor information. + Edit an existing doctor's information --- + tags: + - Doctor + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - doctor_id + - first_name + - last_name + - email + properties: + doctor_id: { type: integer } + first_name: { type: string } + last_name: { type: string } + email: { type: string } + description: { type: string } + years_of_practice: { type: integer } + specialty: { type: string } + payment_fee: { type: number } + gender: { type: string } + phone_number: { type: string } + address: { type: string } + zipcode: { type: string } + city: { type: string } + state: { type: string } + doctor_picture: { type: string, description: "Base64-encoded image" } + example: + doctor_id: 3 + first_name: "Jane" + last_name: "Smith" + email: "jane@example.com" + gender: "Female" + city: "Chicago" responses: 200: - description: Doctor information updated successfully. + description: Doctor info updated successfully 400: - description: Missing required fields + description: Validation or image upload error 500: - description: Error message based on what went wrong. + description: Database update error """ data = request.get_json() print(data) @@ -953,17 +1390,23 @@ def edit_doctor(): @doctor_bp.route('/top-doctors', methods=['GET']) def get_top_doctors(): """ - Get the top 3 rated doctors + Retrieve the top 3 highest-rated doctors --- tags: - Doctor responses: 200: - description: Successfully retrieved top 3 doctors. + description: Top 3 rated doctors returned successfully + content: + application/json: + schema: + type: array + items: + type: object 400: - description: Error occurred. + description: Database error 404: - description: Ratings not found. + description: No ratings found """ cursor = mysql.connection.cursor() diff --git a/routes/patient_routes.py b/routes/patient_routes.py index 02cc6df..64f1312 100644 --- a/routes/patient_routes.py +++ b/routes/patient_routes.py @@ -726,7 +726,6 @@ def login_patient(): def add_daily_survey(): """ Submit a new daily survey entry for a patient - --- tags: - Survey @@ -820,7 +819,6 @@ def add_daily_survey(): def get_daily_surveys(patient_id): """ Retrieve all daily survey records for a specific patient - --- tags: - Survey @@ -884,7 +882,6 @@ def get_daily_surveys(patient_id): def add_weekly_survey(): """ Submit a new weekly survey entry for a patient - --- tags: - Survey @@ -954,7 +951,6 @@ def add_weekly_survey(): def get_weekly_surveys(patient_id): """ Retrieve all weekly survey records for a specific patient - --- tags: - Survey @@ -962,7 +958,8 @@ def get_weekly_surveys(patient_id): - name: patient_id in: path required: true - type: integer + schema: + type: integer description: ID of the patient responses: 200: @@ -986,6 +983,17 @@ def get_weekly_surveys(patient_id): weight_change: type: number format: float + example: + - ws_id: 1 + patient_id: 5 + week_start: "2024-04-01" + blood_pressure: "120/80" + weight_change: -1.2 + - ws_id: 2 + patient_id: 5 + week_start: "2024-04-08" + blood_pressure: "118/76" + weight_change: 0.5 400: description: Retrieval failed """ @@ -1012,7 +1020,6 @@ def get_weekly_surveys(patient_id): def add_appointment(): """ Create a new patient appointment - --- tags: - Appointment @@ -1045,21 +1052,10 @@ def add_appointment(): type: string accepted: type: integer - enum: [0, 1] - default: 0 + description: "0 for pending/denied, 1 for accepted" meal_prescribed: type: string - enum: - - Low Carb - - Keto - - Paleo - - Mediterranean - - Vegan - - Vegetarian - - Gluten-Free - - Dairy-Free - - Whole30 - - Flexitarian + description: "Optional dietary plan suggested by doctor" example: patient_id: 1 doctor_id: 2 @@ -1076,6 +1072,7 @@ def add_appointment(): 400: description: Failed to create appointment """ + data = request.get_json() cursor = mysql.connection.cursor() @@ -1118,19 +1115,24 @@ def add_appointment(): def get_all_appointments(patient_id): """ Get all appointments for a specific patient - --- tags: - Appointment parameters: - name: patient_id in: path - type: integer required: true - description: ID of the patient + schema: + type: integer responses: 200: description: List of appointments + content: + application/json: + schema: + type: array + items: + type: object 400: description: Retrieval failed """ From 95c8bd098acd9d7734b991db65b7775279c76160 Mon Sep 17 00:00:00 2001 From: Cassandra Lanjwal Date: Mon, 12 May 2025 22:10:21 -0400 Subject: [PATCH 12/21] swagger changes to community, doctor, patient, and testing files --- routes/community_routes.py | 59 ++++++++++- routes/doctor_routes.py | 90 ++++++++++++---- routes/patient_routes.py | 204 ++++++++++++++++++++++++++++++++----- routes/testing.py | 2 + 4 files changed, 310 insertions(+), 45 deletions(-) diff --git a/routes/community_routes.py b/routes/community_routes.py index 57e57b6..be1c2ed 100644 --- a/routes/community_routes.py +++ b/routes/community_routes.py @@ -200,6 +200,59 @@ def get_all_posts(): responses: 200: description: List of all community posts + content: + application/json: + schema: + type: array + items: + type: object + properties: + post_id: + type: integer + meal_id: + type: integer + user_id: + type: integer + description: + type: string + picture: + type: string + nullable: true + description: URL or encoded string of the image + created_at: + type: string + format: date-time + meal_name: + type: string + meal_calories: + type: integer + first_name: + type: string + last_name: + type: string + tag: + type: string + description: Comma-separated tags from meal plans or custom tags + like_count: + type: integer + description: Number of likes on the post + comment_count: + type: integer + description: Number of comments on the post + example: + - post_id: 101 + meal_id: 12 + user_id: 5 + description: "Great low-carb recipe!" + picture: "https://storage.googleapis.com/mybucket/post101.jpg" + created_at: "2025-05-12T14:00:00" + meal_name: "Grilled Chicken Salad" + meal_calories: 320 + first_name: "Alex" + last_name: "Kim" + tag: "Keto, Protein Rich" + like_count: 14 + comment_count: 3 """ cursor = mysql.connection.cursor() query = """ @@ -371,11 +424,13 @@ def get_liked_posts(): parameters: - name: patient_id in: query - type: integer + schema: + type: integer required: false - name: doctor_id in: query - type: integer + schema: + type: integer required: false responses: 200: diff --git a/routes/doctor_routes.py b/routes/doctor_routes.py index c795f31..01cc40c 100644 --- a/routes/doctor_routes.py +++ b/routes/doctor_routes.py @@ -104,7 +104,7 @@ def register_doctor(): zipcode: "94043" city: "Mountain View" state: "CA" - doctor_picture: "" + doctor_picture: "https://storage.googleapis.com/doctors/file" responses: 201: description: Doctor registered successfully! @@ -299,27 +299,29 @@ def login_doctor(): tags: - Doctor requestBody: - required: true - content: + required: true + content: application/json: - schema: + schema: type: object required: - - email - - password + - email + - password properties: - email: { type: string } - password: { type: string } - example: + email: + type: string + password: + type: string + example: email: "alice@example.com" password: "password123" responses: - 200: - description: Login Successful with the Doctor ID. - 401: - description: Invalid credentials. - 404: - description: Doctor not found. + 200: + description: Login Successful with the Doctor ID. + 401: + description: Invalid credentials. + 404: + description: Doctor not found. """ data = request.get_json() email = data.get('email') @@ -524,11 +526,63 @@ def get_appointments_by_doctor(doctor_id): """ Get appointments by doctor ID --- + tags: + - Appointment + parameters: + - name: doctor_id + in: path + required: true + schema: + type: integer + description: ID of the doctor responses: 200: - description: Information about the appointments is returned + description: List of appointments for the doctor + content: + application/json: + schema: + type: array + items: + type: object + properties: + patient_appt_id: + type: integer + patient_id: + type: integer + appointment_datetime: + type: string + format: date-time + reason_for_visit: + type: string + current_medications: + type: string + exercise_frequency: + type: string + doctor_appointment_note: + type: string + accepted: + type: number + meal_prescribed: + type: string + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + patient_first_name: + type: string + patient_last_name: + type: string 400: - description: Error message based on what went wrong. + description: Error retrieving appointments + content: + application/json: + schema: + type: object + properties: + error: + type: string """ cursor = mysql.connection.cursor() @@ -1428,4 +1482,4 @@ def get_top_doctors(): except Exception as e: return jsonify({"error": str(e)}), 400 finally: - cursor.close() \ No newline at end of file + cursor.close() diff --git a/routes/patient_routes.py b/routes/patient_routes.py index 64f1312..f7f737b 100644 --- a/routes/patient_routes.py +++ b/routes/patient_routes.py @@ -273,6 +273,90 @@ def get_patient(patient_id): @patient_bp.route('/init-patient-survey', methods=['POST']) def init_patient_survey(): + """ + Submit initial survey for a patient + + --- + tags: + - Patient + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - patient_id + - mobile_number + - dob + - blood_type + - patient_address + - patient_zipcode + - patient_city + - patient_state + properties: + patient_id: + type: integer + mobile_number: + type: string + dob: + type: string + format: date + gender: + type: string + height: + type: number + weight: + type: number + activity: + type: number + health_goals: + type: string + dietary_restrictions: + type: string + blood_type: + type: string + patient_address: + type: string + patient_zipcode: + type: string + patient_city: + type: string + patient_state: + type: string + medical_conditions: + type: string + family_history: + type: string + past_procedures: + type: string + favorite_meal: + type: string + example: + patient_id: 1 + mobile_number: "555-1111" + dob: "1990-01-01" + gender: "Male" + height: 180 + weight: 75 + activity: 3 + health_goals: "Lose weight" + dietary_restrictions: "None" + blood_type: "O+" + patient_address: "123 Main St" + patient_zipcode: "10001" + patient_city: "New York" + patient_state: "NY" + medical_conditions: "Asthma" + family_history: "Diabetes" + past_procedures: "Appendectomy" + favorite_meal: "Grilled chicken" + responses: + 201: + description: Survey data submitted + 400: + description: Submission failed + """ data = request.get_json() cursor = mysql.connection.cursor() @@ -1375,31 +1459,31 @@ def get_single_appointment_by_id(appointment_id): query = """ SELECT - PA.*, - P.first_name AS patient_name, - D.first_name AS doctor_name, - S.mobile_number, - S.dob, - S.gender AS survey_gender, - S.height AS survey_height, - S.weight AS survey_weight, - S.activity, - S.health_goals, - S.dietary_restrictions, - S.blood_type, - S.patient_address, - S.patient_zipcode, - S.patient_city, - S.patient_state, - S.medical_conditions, - S.family_history, - S.past_procedures, - S.favorite_meal -FROM PATIENT_APPOINTMENT PA -JOIN PATIENT P ON PA.patient_id = P.patient_id -JOIN DOCTOR D ON PA.doctor_id = D.doctor_id -LEFT JOIN PATIENT_INIT_SURVEY S ON P.patient_id = S.patient_id -WHERE PA.patient_appt_id = %s + PA.*, + P.first_name AS patient_name, + D.first_name AS doctor_name, + S.mobile_number, + S.dob, + S.gender AS survey_gender, + S.height AS survey_height, + S.weight AS survey_weight, + S.activity, + S.health_goals, + S.dietary_restrictions, + S.blood_type, + S.patient_address, + S.patient_zipcode, + S.patient_city, + S.patient_state, + S.medical_conditions, + S.family_history, + S.past_procedures, + S.favorite_meal + FROM PATIENT_APPOINTMENT PA + JOIN PATIENT P ON PA.patient_id = P.patient_id + JOIN DOCTOR D ON PA.doctor_id = D.doctor_id + LEFT JOIN PATIENT_INIT_SURVEY S ON P.patient_id = S.patient_id + WHERE PA.patient_appt_id = %s """ @@ -1615,6 +1699,76 @@ def add_patient_bill(): def get_all_bills_for_patient(patient_id): """ Retrieve all billing records (charges and credits) with running balance for a patient + + --- + tags: + - Billing + parameters: + - name: patient_id + in: path + required: true + schema: + type: integer + description: ID of the patient + responses: + 200: + description: List of charges and credits with running balance + content: + application/json: + schema: + type: array + items: + type: object + properties: + id: + type: integer + description: Unique ID of the bill or credit + article: + type: string + description: Description or label for the charge or credit + created_at: + type: string + format: date-time + description: Timestamp of when the charge or credit was created + doctor_bill: + type: number + nullable: true + description: Doctor’s fee for the appointment + pharm_bill: + type: number + nullable: true + description: Total cost of prescribed medicines + credit: + type: number + nullable: true + description: Amount credited to the patient’s account + current_bill: + type: number + description: Patient's balance after this transaction + example: + - id: 10 + article: "Appt 3" + created_at: "2025-05-12T10:00:00" + doctor_bill: 75.0 + pharm_bill: 45.0 + credit: "" + current_bill: -120.0 + - id: 7 + article: "credit" + created_at: "2025-05-10T08:30:00" + doctor_bill: "" + pharm_bill: "" + credit: 100.0 + current_bill: -45.0 + 400: + description: Failed to retrieve billing records + content: + application/json: + schema: + type: object + properties: + error: + type: string """ cursor = mysql.connection.cursor() diff --git a/routes/testing.py b/routes/testing.py index dc45ca1..107cc26 100644 --- a/routes/testing.py +++ b/routes/testing.py @@ -8,6 +8,8 @@ def test_db_connection(): """ Test database connection --- + tags: + - Testing responses: 200: description: Successfully connected to the database From faa8f97285547f65fd1c71f2a0c520601f77cf34 Mon Sep 17 00:00:00 2001 From: Cassandra Lanjwal Date: Tue, 13 May 2025 08:53:04 -0400 Subject: [PATCH 13/21] adding/changing endpoints that would change if meal plan was assigned by meal_id --- routes/doctor_routes.py | 103 ++++++++++++++++++++++++++++++++++++--- routes/meal_routes.py | 54 ++++++++++++++++++++ routes/patient_routes.py | 32 +++++++----- 3 files changed, 170 insertions(+), 19 deletions(-) diff --git a/routes/doctor_routes.py b/routes/doctor_routes.py index 01cc40c..5bdc0d0 100644 --- a/routes/doctor_routes.py +++ b/routes/doctor_routes.py @@ -520,6 +520,7 @@ def get_all_doctors(): return jsonify(result), 200 +# need to test this # get appointments by doctor @doctor_bp.route('/doc-appointments/', methods=['GET']) def get_appointments_by_doctor(doctor_id): @@ -563,7 +564,7 @@ def get_appointments_by_doctor(doctor_id): accepted: type: number meal_prescribed: - type: string + type: int created_at: type: string format: date-time @@ -596,13 +597,14 @@ def get_appointments_by_doctor(doctor_id): pa.exercise_frequency, pa.doctor_appointment_note, pa.accepted, - pa.meal_prescribed, pa.created_at, pa.updated_at, p.first_name AS patient_first_name, - p.last_name AS patient_last_name + p.last_name AS patient_last_name, + mp.meal_plan_title AS meal_prescribed FROM PATIENT_APPOINTMENT pa JOIN PATIENT p ON pa.patient_id = p.patient_id + JOIN MEAL_PLAN mp ON pa.meal_prescribed = mp.meal_plan_id WHERE p.doctor_id = %s ORDER BY pa.appointment_datetime DESC """ @@ -785,6 +787,82 @@ def add_prescription(): finally: cursor.close() +# need to test this +@doctor_bp.route('/appointment/meal', methods=['POST']) +def assign_plan_to_patient(): + """ + Assign a meal plan to a patient + --- + tags: + - Appointment + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - appt_id + - meal_plan_id + properties: + appt_id: + type: integer + meal_plan_id: + type: integer + example: + appt_id: 1 + meal_plan_id: 2 + responses: + 200: + description: Meal plan assigned successfully + content: + application/json: + schema: + type: object + properties: + message: + type: string + 400: + description: Validation or database error + content: + application/json: + schema: + type: object + properties: + error: + type: string + """ + data = request.get_json() + appt_id = data.get('appt_id') + meal_plan_id = data.get('meal_plan_id') + + if not appt_id or not meal_plan_id: + return jsonify({"error": "appt_id and meal_plan_id are required."}), 400 + + cursor = mysql.connection.cursor() + + query = """ + UPDATE PATIENT_APPOINTMENT + SET meal_prescribed = %s, updated_at = CURRENT_TIMESTAMP + WHERE appt_id = %s + """ + values = (meal_plan_id, appt_id) + + try: + cursor.execute(query, values) + mysql.connection.commit() + + if cursor.rowcount == 0: + return jsonify({"error": "No appointment found for this patient."}), 404 + + return jsonify({"message": "Meal plan assigned successfully."}), 200 + + except Exception as e: + mysql.connection.rollback() + return jsonify({"error": str(e)}), 400 + finally: + cursor.close() + # accepting patients - general @doctor_bp.route('/doctor-accepting-status/', methods=['PATCH']) def update_accepting_status(doctor_id): @@ -1085,6 +1163,8 @@ def get_patients_by_doctor(doctor_id): return jsonify(result), 200 if result else 404 +# need to test this +# changed meal_plan_prescribed to get meal_plans in db @doctor_bp.route('/doc-past/', methods=['GET']) def get_past_appointments_by_doctor(doctor_id): """ @@ -1128,13 +1208,14 @@ def get_past_appointments_by_doctor(doctor_id): pa.exercise_frequency, pa.doctor_appointment_note, pa.accepted, - pa.meal_prescribed, pa.created_at, pa.updated_at, p.first_name AS patient_first_name, - p.last_name AS patient_last_name + p.last_name AS patient_last_name, + mp.meal_plan_title AS meal_prescribed FROM PATIENT_APPOINTMENT pa JOIN PATIENT p ON pa.patient_id = p.patient_id + JOIN MEAL_PLAN mp ON pa.meal_prescribed = mp.meal_plan_id WHERE p.doctor_id = %s AND pa.appointment_datetime < NOW() ORDER BY pa.appointment_datetime DESC """ @@ -1148,6 +1229,8 @@ def get_past_appointments_by_doctor(doctor_id): except Exception as e: return jsonify({"error": str(e)}), 400 +# need to test this +# changed meal_plan_prescribed to get meal_plans in db @doctor_bp.route('/doc-upcoming/', methods=['GET']) def get_upcoming_appointments_by_doctor(doctor_id): """ @@ -1191,13 +1274,14 @@ def get_upcoming_appointments_by_doctor(doctor_id): pa.exercise_frequency, pa.doctor_appointment_note, pa.accepted, - pa.meal_prescribed, pa.created_at, pa.updated_at, p.first_name AS patient_first_name, p.last_name AS patient_last_name + mp.meal_plan_title AS meal_prescribed FROM PATIENT_APPOINTMENT pa JOIN PATIENT p ON pa.patient_id = p.patient_id + JOIN MEAL_PLAN mp ON pa.meal_prescribed = mp.meal_plan_id WHERE p.doctor_id = %s AND pa.appointment_datetime >= NOW() AND pa.accepted = 1 ORDER BY pa.appointment_datetime ASC """ @@ -1211,6 +1295,8 @@ def get_upcoming_appointments_by_doctor(doctor_id): except Exception as e: return jsonify({"error": str(e)}), 400 +# need to test this +# changed meal_plan_prescribed to get meal_plans in db @doctor_bp.route('/requested-appointments/', methods=['GET']) def get_requested_appointments(doctor_id): """ @@ -1254,13 +1340,14 @@ def get_requested_appointments(doctor_id): pa.exercise_frequency, pa.doctor_appointment_note, pa.accepted, - pa.meal_prescribed, pa.created_at, pa.updated_at, p.first_name AS patient_first_name, - p.last_name AS patient_last_name + p.last_name AS patient_last_name, + mp.meal_plan_title AS meal_prescribed FROM PATIENT_APPOINTMENT pa JOIN PATIENT p ON pa.patient_id = p.patient_id + JOIN MEAL_PLAN mp ON pa.meal_prescribed = mp.meal_plan_id WHERE p.doctor_id = %s AND pa.appointment_datetime >= NOW() AND (pa.accepted = 0 OR pa.accepted IS NULL) diff --git a/routes/meal_routes.py b/routes/meal_routes.py index 3a3b107..52af929 100644 --- a/routes/meal_routes.py +++ b/routes/meal_routes.py @@ -585,3 +585,57 @@ def get_patient_meal_plans(patient_id): return jsonify(meal_plans), 200 +# need to test this +# get all meal plans in database +@meal_bp.route('/get-doctor-meal-plans/', methods=['GET']) +def get_doctor_meal_plans(): + """ + Get all meal plans + + --- + tags: + - Appointment + responses: + 200: + description: List of meal plans created by the doctor + content: + application/json: + schema: + type: array + items: + type: object + properties: + meal_plan_id: + type: integer + title: + type: string + tag: + type: string + made_by: + type: string + example: + - meal_plan_id: 1 + title: "Keto Kickstart" + tag: "Keto" + made_by: "Dr. Alex Kim" + - meal_plan_id: 2 + title: "Plant Power" + tag: "Vegan" + made_by: "Jamie Rivera" + 404: + description: Doctor not found or no meal plans available + """ + + cursor = mysql.connection.cursor(DictCursor) + + cursor.execute(""" + SELECT * + FROM MEAL_PLAN mp + """) + meal_plans = cursor.fetchall() + cursor.close() + + if not meal_plans: + return jsonify({'message': 'No meal plans found.'}), 404 + + return jsonify(meal_plans), 200 \ No newline at end of file diff --git a/routes/patient_routes.py b/routes/patient_routes.py index f7f737b..1de80c6 100644 --- a/routes/patient_routes.py +++ b/routes/patient_routes.py @@ -1138,7 +1138,7 @@ def add_appointment(): type: integer description: "0 for pending/denied, 1 for accepted" meal_prescribed: - type: string + type: int description: "Optional dietary plan suggested by doctor" example: patient_id: 1 @@ -1149,7 +1149,7 @@ def add_appointment(): exercise_frequency: "3x/week" doctor_appointment_note: "" accepted: 0 - meal_prescribed: "Mediterranean" + meal_prescribed: 1 responses: 201: description: Appointment created successfully @@ -1639,15 +1639,24 @@ def add_patient_bill(): pharm_bill = float(pharm_result[0]) if pharm_result else 0.0 # Determine appointment count for article naming - cursor.execute(""" - SELECT COUNT(*) - FROM PATIENT_BILL pb - JOIN PATIENT_APPOINTMENT pa ON pb.appt_id = pa.patient_appt_id - WHERE pa.patient_id = %s - """, (patient_id,)) - count_result = cursor.fetchone() - appt_number = (count_result[0] or 0) + 1 - article_name = f"Appt {appt_number}" + if appt_id > 551: + cursor.execute(""" + SELECT COUNT(*) + FROM PATIENT_BILL pb + JOIN PATIENT_APPOINTMENT pa ON pb.appt_id = pa.patient_appt_id + WHERE pa.patient_id = %s + """, (patient_id,)) + count_result = cursor.fetchone() + appt_number = (count_result[0] or 0) + 1 + article_name = f"Appt {appt_number}" + else: + cursor.execute(""" + SELECT article + FROM PATIENT_BILL + WHERE appt_id = %s + """, (appt_id,)) + article_result = cursor.fetchone() + article_name = f"Appt {article_result}" # Insert the new bill (charge only) current_bill = doctor_bill + pharm_bill @@ -1954,6 +1963,7 @@ def make_general_payment(patient_id): "message": "General payment recorded successfully.", "patient_id": patient_id, "credit": credit, + "pharm_bill": "", "article": "Credit Card Payment", "new_balance": updated_balance }), 201 From 139e94ed62a4990ac9428e6af61f6aaa72eff159 Mon Sep 17 00:00:00 2001 From: Cassandra Lanjwal Date: Tue, 13 May 2025 12:36:05 -0400 Subject: [PATCH 14/21] lots of changes, mainly to add meal plans during appointments. --- .coverage | Bin 53248 -> 53248 bytes routes/doctor_routes.py | 23 +++++++++++++++-------- routes/meal_routes.py | 28 +++++++++++++++++++++++++++- routes/patient_routes.py | 8 +++----- 4 files changed, 45 insertions(+), 14 deletions(-) diff --git a/.coverage b/.coverage index 9c2420bd9e4b758f2dc39d7de65d78e03ef09ce9..d6127e5ec5e64c7b92b88b15818989ce6a1f6b29 100644 GIT binary patch delta 656 zcmZozz}&Eac>`;MFwas3{-69>d}nx<@&@yq;5opvbhDs9FVAF=UKMr@HdaQ?CV|QJ zy-M{A5WohdK@>9*n}LA`!9yrYfC@rs2?+N9RFna%&;Z11U<_adTGpt*x({T~y?8bT zgKzzeK;}O7>%2zOCg1GU5&|g%g9A+}EI`#Q9+SEIv|vsH$~1s90D%LLE@);2IWXYi zA6_8Ua7*o)Fp?7+fMOs3b*c`-pXcYn0vbQkLG%Hpntu@G0UyB502;yC@V^4+*aZ)c zGca&5{4iu_U}C6~U;x_R;H}kV(P{jOUy6Z&6{!1sA_Kz}U7-F0AhS>aJZvTag&Vf7 zt(QU#M!1t;o`HrL$dFn8_q#ILTZ8<=AQ8h13NH)hb(7`$!?=8yfV^f2rhiqF>-)u_ zLDInZ`~UyzK*a~H<}gN^fV9D5Eg^yV|9|@=h!{fv1H*@WFnhyRgG6gXF!RApuDz3? zAqo_L25!UJ2j8FpxjLhEI!Hd`&iiPfFW3rzCW5qTXfZJS00t7n0tEda9^}t$kU(zu TU(bGkk-^4Vl40}Ler^W`;MFs}#$|4;rBelb2~-rc&Xq#5DOr zk5Zxm0|H1Om_Qa1j|VPs0Lp^WP~8oTP+_pb1Tcr$jTLB1BhTchUPq250Tv*qMPc&V zUafkV!6HC)<`G~cM9-8$xhEs00Yrz}oQtk0hAQ^$$!ZFxu6#fdp9p z!whX;W%&O&fq{Xmj)!3ZLxVgsg9gKcc98paELqes*<$g(qy>x&0YLr-88!xoyN7@* zg!h0PATSUD5+AmG^-l(K5kvxv39$|CZ3cz~KmSLoeAs*$q@aOu@|nK)dL|}ND6=sA zOJas;1JVI3>Ff><{{R1Jz`%3k|Ns3!=Qpgr$+Rs4q>owp$N&Em8G!-wk2fJZ@laV3 z!-t&KdnXxgaOIt6h;V(qo2Q}ccWpKU^N#=j=d%HQ@x#1', methods=['GET']) def get_upcoming_appointments_by_doctor(doctor_id): @@ -1278,10 +1289,8 @@ def get_upcoming_appointments_by_doctor(doctor_id): pa.updated_at, p.first_name AS patient_first_name, p.last_name AS patient_last_name - mp.meal_plan_title AS meal_prescribed FROM PATIENT_APPOINTMENT pa JOIN PATIENT p ON pa.patient_id = p.patient_id - JOIN MEAL_PLAN mp ON pa.meal_prescribed = mp.meal_plan_id WHERE p.doctor_id = %s AND pa.appointment_datetime >= NOW() AND pa.accepted = 1 ORDER BY pa.appointment_datetime ASC """ @@ -1343,11 +1352,9 @@ def get_requested_appointments(doctor_id): pa.created_at, pa.updated_at, p.first_name AS patient_first_name, - p.last_name AS patient_last_name, - mp.meal_plan_title AS meal_prescribed + p.last_name AS patient_last_name FROM PATIENT_APPOINTMENT pa JOIN PATIENT p ON pa.patient_id = p.patient_id - JOIN MEAL_PLAN mp ON pa.meal_prescribed = mp.meal_plan_id WHERE p.doctor_id = %s AND pa.appointment_datetime >= NOW() AND (pa.accepted = 0 OR pa.accepted IS NULL) diff --git a/routes/meal_routes.py b/routes/meal_routes.py index 52af929..2cad9c8 100644 --- a/routes/meal_routes.py +++ b/routes/meal_routes.py @@ -638,4 +638,30 @@ def get_doctor_meal_plans(): if not meal_plans: return jsonify({'message': 'No meal plans found.'}), 404 - return jsonify(meal_plans), 200 \ No newline at end of file + return jsonify(meal_plans), 200 + +@meal_bp.route('/clear-meals', methods=['POST']) +def clear_meals_for_day(): + """ + Clear meals for a specific day in a meal plan + """ + data = request.get_json() + meal_plan_id = data.get('meal_plan_id') + day_of_week = data.get('day_of_week') + + if not meal_plan_id or not day_of_week: + return jsonify({'error': 'meal_plan_id and day_of_week are required'}), 400 + + cursor = mysql.connection.cursor() + try: + cursor.execute(""" + DELETE FROM MEAL_PLAN_ENTRY + WHERE meal_plan_id = %s AND day_of_week = %s + """, (meal_plan_id, day_of_week)) + mysql.connection.commit() + return jsonify({'message': 'Meals cleared successfully'}), 200 + except Exception as e: + mysql.connection.rollback() + return jsonify({'error': str(e)}), 400 + finally: + cursor.close() \ No newline at end of file diff --git a/routes/patient_routes.py b/routes/patient_routes.py index 1de80c6..25bbe0d 100644 --- a/routes/patient_routes.py +++ b/routes/patient_routes.py @@ -1149,7 +1149,6 @@ def add_appointment(): exercise_frequency: "3x/week" doctor_appointment_note: "" accepted: 0 - meal_prescribed: 1 responses: 201: description: Appointment created successfully @@ -1169,9 +1168,8 @@ def add_appointment(): current_medications, exercise_frequency, doctor_appointment_note, - accepted, - meal_prescribed - ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) + accepted + ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) """ values = ( @@ -1183,7 +1181,7 @@ def add_appointment(): data.get('exercise_frequency'), data.get('doctor_appointment_note'), data.get('accepted', 0), - data.get('meal_prescribed') + # data.get('meal_prescribed') ) try: From 3fbf5a426658f55db298f9cd887d55ed5bee2718 Mon Sep 17 00:00:00 2001 From: Cassandra Lanjwal Date: Tue, 13 May 2025 12:44:29 -0400 Subject: [PATCH 15/21] clear meals added --- routes/meal_routes.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/routes/meal_routes.py b/routes/meal_routes.py index 3a3b107..cd9a2a9 100644 --- a/routes/meal_routes.py +++ b/routes/meal_routes.py @@ -585,3 +585,25 @@ def get_patient_meal_plans(patient_id): return jsonify(meal_plans), 200 +@meal_bp.route('/clear-meals', methods=['POST']) +def clear_meals_for_day(): + data = request.get_json() + meal_plan_id = data.get('meal_plan_id') + day_of_week = data.get('day_of_week') + + if not meal_plan_id or not day_of_week: + return jsonify({'error': 'meal_plan_id and day_of_week are required'}), 400 + + cursor = mysql.connection.cursor() + try: + cursor.execute(""" + DELETE FROM MEAL_PLAN_ENTRY + WHERE meal_plan_id = %s AND day_of_week = %s + """, (meal_plan_id, day_of_week)) + mysql.connection.commit() + return jsonify({'message': 'Meals cleared successfully'}), 200 + except Exception as e: + mysql.connection.rollback() + return jsonify({'error': str(e)}), 400 + finally: + cursor.close() \ No newline at end of file From 610307594801b06d6c1e55fd18f63c47cd3cbcc2 Mon Sep 17 00:00:00 2001 From: mike8385 <146226367+mike8385@users.noreply.github.com> Date: Tue, 13 May 2025 12:50:55 -0400 Subject: [PATCH 16/21] Made a delete mealplan button --- routes/meal_routes.py | 46 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/routes/meal_routes.py b/routes/meal_routes.py index 2cad9c8..2d48178 100644 --- a/routes/meal_routes.py +++ b/routes/meal_routes.py @@ -663,5 +663,51 @@ def clear_meals_for_day(): except Exception as e: mysql.connection.rollback() return jsonify({'error': str(e)}), 400 + finally: + cursor.close() + +@meal_bp.route('/delete-meal-plan/', methods=['DELETE']) +def delete_meal_plan(meal_plan_id): + """ + Delete a meal plan and all its entries + + --- + tags: + - Meal Plan + parameters: + - name: meal_plan_id + in: path + type: integer + required: true + description: ID of the meal plan to delete + responses: + 200: + description: Meal plan deleted successfully + 404: + description: Meal plan not found + 500: + description: Internal server error + """ + cursor = mysql.connection.cursor() + try: + # First delete all entries associated with this meal plan + cursor.execute("DELETE FROM MEAL_PLAN_ENTRY WHERE meal_plan_id = %s", (meal_plan_id,)) + + # Then delete from PATIENT_PLANS if it exists there + cursor.execute("DELETE FROM PATIENT_PLANS WHERE meal_plan_id = %s", (meal_plan_id,)) + + # Finally delete the meal plan itself + cursor.execute("DELETE FROM MEAL_PLAN WHERE meal_plan_id = %s", (meal_plan_id,)) + + mysql.connection.commit() + + if cursor.rowcount == 0: + return jsonify({'error': 'Meal plan not found'}), 404 + + return jsonify({'message': 'Meal plan deleted successfully'}), 200 + + except Exception as e: + mysql.connection.rollback() + return jsonify({'error': str(e)}), 500 finally: cursor.close() \ No newline at end of file From a931f8d7896a5f01a3881d5c2c26d46c907798e9 Mon Sep 17 00:00:00 2001 From: Cassandra Lanjwal Date: Tue, 13 May 2025 13:22:37 -0400 Subject: [PATCH 17/21] credit card payment to credit fix --- routes/patient_routes.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/routes/patient_routes.py b/routes/patient_routes.py index 25bbe0d..e27eb88 100644 --- a/routes/patient_routes.py +++ b/routes/patient_routes.py @@ -1884,7 +1884,7 @@ def make_general_payment(patient_id): message: General payment recorded successfully. patient_id: 1 credit: 100.0 - article: Credit Card Payment + article: credit new_balance: -20.0 400: description: Invalid payment (e.g., overpayment or bad input) @@ -1928,7 +1928,8 @@ def make_general_payment(patient_id): "requested_payment": credit, "maximum_allowed": -current_balance }), 400 - + + credit = -credit # Store as negative for accounting # Insert credit payment cursor.execute(""" INSERT INTO PATIENT_CREDIT (patient_id, amount) @@ -1962,7 +1963,7 @@ def make_general_payment(patient_id): "patient_id": patient_id, "credit": credit, "pharm_bill": "", - "article": "Credit Card Payment", + "article": "credit", "new_balance": updated_balance }), 201 From 391905e7925bcbc36c9eee6f3deb36d54f7b72a5 Mon Sep 17 00:00:00 2001 From: Cassandra Lanjwal Date: Tue, 13 May 2025 13:31:44 -0400 Subject: [PATCH 18/21] get doctor meal plans added --- routes/meal_routes.py | 44 +++++++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/routes/meal_routes.py b/routes/meal_routes.py index 2d48178..7ba4389 100644 --- a/routes/meal_routes.py +++ b/routes/meal_routes.py @@ -587,14 +587,14 @@ def get_patient_meal_plans(patient_id): # need to test this # get all meal plans in database -@meal_bp.route('/get-doctor-meal-plans/', methods=['GET']) -def get_doctor_meal_plans(): +@meal_bp.route('/get-doctor-meal-plans/', methods=['GET']) +def get_doctor_meal_plans(doctor_id): """ Get all meal plans --- tags: - - Appointment + - Meal Plan responses: 200: description: List of meal plans created by the doctor @@ -628,16 +628,40 @@ def get_doctor_meal_plans(): cursor = mysql.connection.cursor(DictCursor) - cursor.execute(""" - SELECT * - FROM MEAL_PLAN mp - """) + # Step 1: get user_id of the patient + cursor.execute("SELECT user_id FROM USER WHERE doctor_id = %s", (doctor_id,)) + result = cursor.fetchone() + if not result: + return jsonify({"error": "Doctor not found"}), 404 + + user_id = result['user_id'] + + # Step 2: query meal plans (created or assigned) + query = """ + SELECT DISTINCT + mp.meal_plan_id, + mp.meal_plan_title AS title, + mp.meal_plan_name AS tag, + mp.created_at, + CASE + WHEN d.first_name IS NOT NULL THEN CONCAT('Dr. ', d.first_name, ' ', d.last_name) + ELSE CONCAT(p.first_name, ' ', p.last_name) + END AS made_by + FROM MEAL_PLAN mp + JOIN USER u ON mp.made_by = u.user_id + LEFT JOIN DOCTOR d ON u.doctor_id = d.doctor_id + LEFT JOIN PATIENT p ON u.patient_id = p.patient_id + WHERE mp.made_by = %s + OR mp.meal_plan_id IN ( + SELECT meal_plan_id FROM PATIENT_PLANS WHERE user_id = %s + ) + ORDER BY mp.meal_plan_id DESC; + """ + + cursor.execute(query, (user_id, user_id)) meal_plans = cursor.fetchall() cursor.close() - if not meal_plans: - return jsonify({'message': 'No meal plans found.'}), 404 - return jsonify(meal_plans), 200 @meal_bp.route('/clear-meals', methods=['POST']) From 4ff6c054b8cc3cc966ace3fc33451ae9885564a1 Mon Sep 17 00:00:00 2001 From: Cassandra Lanjwal Date: Tue, 13 May 2025 13:32:39 -0400 Subject: [PATCH 19/21] undid neg credit --- routes/patient_routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/patient_routes.py b/routes/patient_routes.py index e27eb88..69388d0 100644 --- a/routes/patient_routes.py +++ b/routes/patient_routes.py @@ -1929,7 +1929,7 @@ def make_general_payment(patient_id): "maximum_allowed": -current_balance }), 400 - credit = -credit # Store as negative for accounting + # credit = -credit # Store as negative for accounting # Insert credit payment cursor.execute(""" INSERT INTO PATIENT_CREDIT (patient_id, amount) From 675b0e157af9286a456d615b9e75d5f9258c3643 Mon Sep 17 00:00:00 2001 From: Cassandra Lanjwal Date: Tue, 13 May 2025 13:37:34 -0400 Subject: [PATCH 20/21] commented out max balance --- routes/patient_routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/patient_routes.py b/routes/patient_routes.py index 69388d0..aa1b6a3 100644 --- a/routes/patient_routes.py +++ b/routes/patient_routes.py @@ -1926,7 +1926,7 @@ def make_general_payment(patient_id): "error": "Payment exceeds outstanding balance.", "current_balance": current_balance, "requested_payment": credit, - "maximum_allowed": -current_balance + # "maximum_allowed": -current_balance }), 400 # credit = -credit # Store as negative for accounting From b65d56033c38a6adf73ebc93c4c6461e88103ae9 Mon Sep 17 00:00:00 2001 From: Cassandra Lanjwal Date: Tue, 13 May 2025 14:15:03 -0400 Subject: [PATCH 21/21] updated get_doctor_meal_plans --- routes/meal_routes.py | 53 +++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/routes/meal_routes.py b/routes/meal_routes.py index 7ba4389..a78ca58 100644 --- a/routes/meal_routes.py +++ b/routes/meal_routes.py @@ -628,41 +628,40 @@ def get_doctor_meal_plans(doctor_id): cursor = mysql.connection.cursor(DictCursor) - # Step 1: get user_id of the patient + # Step 1: Get the doctor’s user_id from USER table cursor.execute("SELECT user_id FROM USER WHERE doctor_id = %s", (doctor_id,)) - result = cursor.fetchone() - if not result: + user = cursor.fetchone() + if not user: return jsonify({"error": "Doctor not found"}), 404 + user_id = user['user_id'] - user_id = result['user_id'] - - # Step 2: query meal plans (created or assigned) + # Step 2: Get all meal plans created by OR saved by the doctor query = """ - SELECT DISTINCT - mp.meal_plan_id, - mp.meal_plan_title AS title, - mp.meal_plan_name AS tag, - mp.created_at, - CASE - WHEN d.first_name IS NOT NULL THEN CONCAT('Dr. ', d.first_name, ' ', d.last_name) - ELSE CONCAT(p.first_name, ' ', p.last_name) - END AS made_by - FROM MEAL_PLAN mp - JOIN USER u ON mp.made_by = u.user_id - LEFT JOIN DOCTOR d ON u.doctor_id = d.doctor_id - LEFT JOIN PATIENT p ON u.patient_id = p.patient_id - WHERE mp.made_by = %s - OR mp.meal_plan_id IN ( - SELECT meal_plan_id FROM PATIENT_PLANS WHERE user_id = %s - ) - ORDER BY mp.meal_plan_id DESC; - """ + SELECT DISTINCT + mp.meal_plan_id, + mp.meal_plan_title AS title, + mp.meal_plan_name AS tag, + mp.created_at, + CASE + WHEN d.first_name IS NOT NULL THEN CONCAT('Dr. ', d.first_name, ' ', d.last_name) + ELSE CONCAT(p.first_name, ' ', p.last_name) + END AS made_by + FROM MEAL_PLAN mp + JOIN USER u ON mp.made_by = u.user_id + LEFT JOIN DOCTOR d ON u.doctor_id = d.doctor_id + LEFT JOIN PATIENT p ON u.patient_id = p.patient_id + WHERE mp.made_by = %s + OR mp.meal_plan_id IN ( + SELECT meal_plan_id FROM PATIENT_PLANS WHERE user_id = %s + ) + ORDER BY mp.meal_plan_id DESC; + """ cursor.execute(query, (user_id, user_id)) - meal_plans = cursor.fetchall() + results = cursor.fetchall() cursor.close() - return jsonify(meal_plans), 200 + return jsonify(results), 200 @meal_bp.route('/clear-meals', methods=['POST']) def clear_meals_for_day():