Upload 4 files
Browse files- README.md +117 -10
- main.py +2000 -0
- requirements.txt +12 -0
- test_app.py +105 -0
README.md
CHANGED
|
@@ -1,10 +1,117 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SmartHeal FastAPI Application
|
| 2 |
+
|
| 3 |
+
This is a FastAPI conversion of the original Flask SmartHeal application, maintaining all the same endpoints and functionality.
|
| 4 |
+
|
| 5 |
+
## Setup Instructions
|
| 6 |
+
|
| 7 |
+
1. **Install Dependencies**
|
| 8 |
+
```bash
|
| 9 |
+
pip install -r requirements.txt
|
| 10 |
+
```
|
| 11 |
+
|
| 12 |
+
2. **Environment Configuration**
|
| 13 |
+
- Copy `.env.example` to `.env`
|
| 14 |
+
- Fill in your database and Twilio credentials in the `.env` file
|
| 15 |
+
|
| 16 |
+
3. **Run the Application**
|
| 17 |
+
```bash
|
| 18 |
+
uvicorn main:app --host 0.0.0.0 --port 8000 --reload
|
| 19 |
+
```
|
| 20 |
+
|
| 21 |
+
## API Endpoints
|
| 22 |
+
|
| 23 |
+
The application maintains all the original endpoints from the Flask version:
|
| 24 |
+
|
| 25 |
+
### Organization Endpoints
|
| 26 |
+
- `POST /send_email` - Send license key via email
|
| 27 |
+
- `POST /verify_license_key` - Verify license key
|
| 28 |
+
- `POST /create_pin` - Create PIN for organization
|
| 29 |
+
- `POST /fetch_data` - Fetch organization data
|
| 30 |
+
- `POST /save_additional_data` - Save department and location
|
| 31 |
+
- `POST /verify_pin` - Verify PIN
|
| 32 |
+
- `POST /send_otp` - Send OTP
|
| 33 |
+
- `POST /change_pin_org` - Change organization PIN
|
| 34 |
+
- `POST /forgot_pin_org` - Reset organization PIN
|
| 35 |
+
- `GET /organisation_details` - Get organization details
|
| 36 |
+
- `POST /store_org_image` - Store organization image
|
| 37 |
+
- `GET /get_org_image` - Get organization image
|
| 38 |
+
- `POST /update_org_profile` - Update organization profile
|
| 39 |
+
- `POST /org/forgot/pin/otp` - Send OTP for PIN reset
|
| 40 |
+
|
| 41 |
+
### Medical User Endpoints
|
| 42 |
+
- `POST /med_send_email` - Send license key for medical users
|
| 43 |
+
- `POST /med_verify_license_key` - Verify medical user license key
|
| 44 |
+
- `POST /med_create_pin` - Create PIN for medical user
|
| 45 |
+
- `POST /med_fetch_data` - Fetch medical user data
|
| 46 |
+
- `POST /save_med_data` - Save medical user data
|
| 47 |
+
- `POST /med_verify_pin` - Verify medical user PIN
|
| 48 |
+
- `POST /med_send_otp` - Send OTP for medical user
|
| 49 |
+
- `POST /change_pin_med` - Change medical user PIN
|
| 50 |
+
- `POST /forgot_pin_med` - Reset medical user PIN
|
| 51 |
+
- `GET /med_details` - Get medical user details
|
| 52 |
+
- `POST /store_med_image` - Store medical user image
|
| 53 |
+
- `GET /get_med_image` - Get medical user image
|
| 54 |
+
- `POST /update_med_profile` - Update medical user profile
|
| 55 |
+
- `POST /med/forgot/pin/otp` - Send OTP for PIN reset
|
| 56 |
+
|
| 57 |
+
### Patient Management Endpoints
|
| 58 |
+
- `POST /add_patient` - Add new patient
|
| 59 |
+
- `POST /add_patient_v2` - Add new patient (v2)
|
| 60 |
+
- `GET /get_all_patient_details` - Get all patient details
|
| 61 |
+
- `GET /search_patient` - Search for patient
|
| 62 |
+
- `GET /patient_details` - Get specific patient details
|
| 63 |
+
- `POST /update_patient_details` - Update patient details
|
| 64 |
+
- `POST /store_image` - Store patient image
|
| 65 |
+
- `GET /get_image` - Get patient image
|
| 66 |
+
- `POST /save_notes` - Save patient notes
|
| 67 |
+
- `POST /save_notes_v2` - Save patient notes (v2)
|
| 68 |
+
- `POST /sort_patients` - Sort patients by criteria
|
| 69 |
+
|
| 70 |
+
### Wound Management Endpoints
|
| 71 |
+
- `POST /add_wound_details` - Add wound details
|
| 72 |
+
- `POST /add_wound_details_v2` - Add wound details with image (v2)
|
| 73 |
+
- `POST /add_wound_details_v3` - Add wound details with multiple images (v3)
|
| 74 |
+
- `POST /store_wound_image` - Store wound image
|
| 75 |
+
- `GET /get_wound_image` - Get wound image
|
| 76 |
+
- `GET /wound_progress_timeline` - Get wound progress timeline
|
| 77 |
+
|
| 78 |
+
### Appointment Management Endpoints
|
| 79 |
+
- `POST /update_scheduled_date` - Update appointment date
|
| 80 |
+
- `POST /update_scheduled_date_v2` - Update appointment date (v2)
|
| 81 |
+
- `GET /total_appointments_till_date` - Get total appointments till date
|
| 82 |
+
- `GET /total_appointments_till_month` - Get total appointments till month
|
| 83 |
+
- `POST /total_appointments` - Get total appointments in date range
|
| 84 |
+
- `GET /total_appointments_v2` - Get total appointments in date range (v2)
|
| 85 |
+
- `POST /get_appointment_count` - Get appointment count for specific date
|
| 86 |
+
|
| 87 |
+
### Administrative Endpoints
|
| 88 |
+
- `POST /admin_add_practitioner` - Add practitioner (admin)
|
| 89 |
+
- `POST /admin_add_practitioner_v2` - Add practitioner (admin v2)
|
| 90 |
+
- `GET /generate_prescription` - Generate prescription
|
| 91 |
+
|
| 92 |
+
## Key Changes from Flask to FastAPI
|
| 93 |
+
|
| 94 |
+
1. **Request Handling**: Replaced Flask's `request.json` with Pydantic models for request validation
|
| 95 |
+
2. **Response Handling**: Replaced Flask's `jsonify()` with FastAPI's `JSONResponse`
|
| 96 |
+
3. **File Uploads**: Replaced Flask's file handling with FastAPI's `UploadFile`
|
| 97 |
+
4. **Dependency Injection**: Used FastAPI's dependency injection for database sessions and JWT verification
|
| 98 |
+
5. **Error Handling**: Replaced Flask's error handling with FastAPI's `HTTPException`
|
| 99 |
+
6. **Static Files**: Replaced Flask's static file serving with FastAPI's `StaticFiles`
|
| 100 |
+
|
| 101 |
+
## Authentication
|
| 102 |
+
|
| 103 |
+
The application uses JWT tokens for authentication. Include the token in the Authorization header:
|
| 104 |
+
```
|
| 105 |
+
Authorization: Bearer <your_jwt_token>
|
| 106 |
+
```
|
| 107 |
+
|
| 108 |
+
## File Uploads
|
| 109 |
+
|
| 110 |
+
The application supports file uploads for:
|
| 111 |
+
- Patient images
|
| 112 |
+
- Wound images
|
| 113 |
+
- Organization images
|
| 114 |
+
- Medical practitioner images
|
| 115 |
+
|
| 116 |
+
Files are stored in the `uploads/` directory with organized subdirectories.
|
| 117 |
+
|
main.py
ADDED
|
@@ -0,0 +1,2000 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI, Request, Depends, HTTPException, status, File, UploadFile, Form
|
| 2 |
+
from fastapi.responses import JSONResponse, FileResponse
|
| 3 |
+
from fastapi.staticfiles import StaticFiles
|
| 4 |
+
from sqlalchemy import create_engine, text
|
| 5 |
+
from sqlalchemy.orm import sessionmaker
|
| 6 |
+
import pymysql
|
| 7 |
+
import jwt
|
| 8 |
+
import random
|
| 9 |
+
import string
|
| 10 |
+
import datetime
|
| 11 |
+
import os
|
| 12 |
+
import uuid
|
| 13 |
+
from dotenv import load_dotenv
|
| 14 |
+
from twilio.rest import Client
|
| 15 |
+
import requests
|
| 16 |
+
from werkzeug.utils import secure_filename
|
| 17 |
+
from pydantic import BaseModel
|
| 18 |
+
from typing import Optional
|
| 19 |
+
|
| 20 |
+
load_dotenv()
|
| 21 |
+
|
| 22 |
+
app = FastAPI()
|
| 23 |
+
|
| 24 |
+
# Database credentials
|
| 25 |
+
DB_HOST = os.getenv('host')
|
| 26 |
+
DB_DATABASE = os.getenv('db')
|
| 27 |
+
DB_USERNAME = os.getenv('username')
|
| 28 |
+
DB_PASSWORD = os.getenv('psswd')
|
| 29 |
+
DATABASE_URL = f"mysql+pymysql://{DB_USERNAME}:{DB_PASSWORD}@{DB_HOST}/{DB_DATABASE}"
|
| 30 |
+
engine = create_engine(DATABASE_URL, pool_pre_ping=True)
|
| 31 |
+
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
| 32 |
+
|
| 33 |
+
account_sid = os.getenv('acc_sid')
|
| 34 |
+
auth_token = os.getenv('auth_token')
|
| 35 |
+
twilio_number = os.getenv('tn')
|
| 36 |
+
|
| 37 |
+
JWT_SECRET_KEY = 'CkOPcOppyh31sQcisbyOM3RKD4C2G7SzQmuG5LePt9XBarsxgjm0fc7uOECcqoGm'
|
| 38 |
+
|
| 39 |
+
# Configuration for file uploads
|
| 40 |
+
UPLOAD_FOLDER = 'uploads'
|
| 41 |
+
if not os.path.exists(UPLOAD_FOLDER):
|
| 42 |
+
os.makedirs(UPLOAD_FOLDER)
|
| 43 |
+
|
| 44 |
+
app.mount("/uploads", StaticFiles(directory=UPLOAD_FOLDER), name="uploads")
|
| 45 |
+
|
| 46 |
+
BASE_URL = 'https://api.smartheal.waysdatalabs.com' # This might need to be updated based on deployment
|
| 47 |
+
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
|
| 48 |
+
|
| 49 |
+
# Dependency to get DB session
|
| 50 |
+
def get_db():
|
| 51 |
+
db = SessionLocal()
|
| 52 |
+
try:
|
| 53 |
+
yield db
|
| 54 |
+
finally:
|
| 55 |
+
db.close()
|
| 56 |
+
|
| 57 |
+
# Utility function to check allowed file extensions
|
| 58 |
+
def allowed_file(filename):
|
| 59 |
+
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
| 60 |
+
|
| 61 |
+
def generate_session_id():
|
| 62 |
+
return str(uuid.uuid4())
|
| 63 |
+
|
| 64 |
+
def generate_license_key():
|
| 65 |
+
return ''.join(random.choices(string.ascii_uppercase + string.digits, k=12))
|
| 66 |
+
|
| 67 |
+
def generate_patient_id():
|
| 68 |
+
with SessionLocal() as session:
|
| 69 |
+
result = session.execute(text("SELECT MAX(id) FROM patients")).fetchone()
|
| 70 |
+
last_id = result[0] if result[0] is not None else 0
|
| 71 |
+
prefix = "AB"
|
| 72 |
+
formatted_id = f"{prefix}000{last_id + 1}"
|
| 73 |
+
return formatted_id
|
| 74 |
+
|
| 75 |
+
def send_sms(phone, otp):
|
| 76 |
+
client = Client(account_sid, auth_token)
|
| 77 |
+
message = client.messages.create(
|
| 78 |
+
body=f"Your verification code is: {otp}. Don't share this code with anyone; our employees will never ask for the code.",
|
| 79 |
+
from_=twilio_number,
|
| 80 |
+
to=phone
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
def generate_otp():
|
| 84 |
+
return str(random.randint(1000, 9999))
|
| 85 |
+
|
| 86 |
+
def update_otp_in_database(session, phone, otp, expiry_time, updated_at):
|
| 87 |
+
try:
|
| 88 |
+
query = text("UPDATE organisations SET otp= :otp, otp_expiry= :expiry_time, updated_at = :updated_at WHERE phone= :phone")
|
| 89 |
+
session.execute(query, {'otp': otp, 'expiry_time': expiry_time, 'phone': phone, 'updated_at': updated_at})
|
| 90 |
+
session.commit()
|
| 91 |
+
except Exception as e:
|
| 92 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 93 |
+
|
| 94 |
+
def update_otp_in_med_database(session, phone, otp, expiry_time, updated_at):
|
| 95 |
+
try:
|
| 96 |
+
query = text("UPDATE users SET otp= :otp, otp_expiry= :expiry_time, updated_at = :updated_at WHERE phone= :phone")
|
| 97 |
+
session.execute(query, {'otp': otp, 'expiry_time': expiry_time, 'phone': phone, 'updated_at': updated_at})
|
| 98 |
+
session.commit()
|
| 99 |
+
except Exception as e:
|
| 100 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 101 |
+
|
| 102 |
+
# JWT Authentication Dependency
|
| 103 |
+
def verify_jwt_token(request: Request):
|
| 104 |
+
auth_header = request.headers.get('Authorization')
|
| 105 |
+
if not auth_header or not auth_header.startswith('Bearer '):
|
| 106 |
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Invalid Authorization header')
|
| 107 |
+
token = auth_header.split(' ')[1]
|
| 108 |
+
try:
|
| 109 |
+
payload = jwt.decode(token, JWT_SECRET_KEY, algorithms=['HS256'])
|
| 110 |
+
return payload
|
| 111 |
+
except jwt.ExpiredSignatureError:
|
| 112 |
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Token has expired')
|
| 113 |
+
except jwt.InvalidTokenError:
|
| 114 |
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Invalid token')
|
| 115 |
+
|
| 116 |
+
# Pydantic Models for Request Body Validation
|
| 117 |
+
class SendEmailRequest(BaseModel):
|
| 118 |
+
name: str
|
| 119 |
+
email: str
|
| 120 |
+
c_code: str
|
| 121 |
+
phone: str
|
| 122 |
+
|
| 123 |
+
class VerifyLicenseKeyRequest(BaseModel):
|
| 124 |
+
email: str
|
| 125 |
+
license_key: str
|
| 126 |
+
|
| 127 |
+
class CreatePinRequest(BaseModel):
|
| 128 |
+
license_key: str
|
| 129 |
+
email: str
|
| 130 |
+
pin: str
|
| 131 |
+
name: Optional[str] = None
|
| 132 |
+
c_code: Optional[str] = None
|
| 133 |
+
phone: Optional[str] = None
|
| 134 |
+
|
| 135 |
+
class FetchDataRequest(BaseModel):
|
| 136 |
+
email: str
|
| 137 |
+
|
| 138 |
+
class SaveAdditionalDataRequest(BaseModel):
|
| 139 |
+
department: str
|
| 140 |
+
location: str
|
| 141 |
+
email: str
|
| 142 |
+
latitude: float
|
| 143 |
+
longitude: float
|
| 144 |
+
|
| 145 |
+
class AddWoundDetailsRequest(BaseModel):
|
| 146 |
+
length: float
|
| 147 |
+
breadth: float
|
| 148 |
+
depth: float
|
| 149 |
+
area: float
|
| 150 |
+
moisture: str
|
| 151 |
+
wound_location: Optional[str] = None
|
| 152 |
+
tissue: Optional[str] = None
|
| 153 |
+
exudate: Optional[str] = None
|
| 154 |
+
periwound: Optional[str] = None
|
| 155 |
+
periwound_type: Optional[str] = None
|
| 156 |
+
patient_id: str
|
| 157 |
+
type: Optional[str] = None
|
| 158 |
+
category: Optional[str] = None
|
| 159 |
+
edge: Optional[str] = None
|
| 160 |
+
infection: Optional[str] = None
|
| 161 |
+
last_dressing_date: Optional[str] = None
|
| 162 |
+
|
| 163 |
+
class AddPatientRequest(BaseModel):
|
| 164 |
+
name: str
|
| 165 |
+
dob: str
|
| 166 |
+
gender: str
|
| 167 |
+
age: int
|
| 168 |
+
height: float
|
| 169 |
+
weight: float
|
| 170 |
+
email: str
|
| 171 |
+
doctor: str
|
| 172 |
+
|
| 173 |
+
class SearchPatientRequest(BaseModel):
|
| 174 |
+
name: str
|
| 175 |
+
|
| 176 |
+
class GeneratePrescriptionRequest(BaseModel):
|
| 177 |
+
patient_id: str
|
| 178 |
+
|
| 179 |
+
class VerifyPinRequest(BaseModel):
|
| 180 |
+
email: str
|
| 181 |
+
pin: str
|
| 182 |
+
|
| 183 |
+
class SendOtpRequest(BaseModel):
|
| 184 |
+
phone: str
|
| 185 |
+
|
| 186 |
+
class UpdateScheduledDateRequest(BaseModel):
|
| 187 |
+
email: str
|
| 188 |
+
patient_id: str
|
| 189 |
+
doctor: str
|
| 190 |
+
scheduled_date: str
|
| 191 |
+
|
| 192 |
+
class TotalAppointmentsTillDateRequest(BaseModel):
|
| 193 |
+
date: str
|
| 194 |
+
|
| 195 |
+
class TotalAppointmentsTillMonthRequest(BaseModel):
|
| 196 |
+
year: int
|
| 197 |
+
month: int
|
| 198 |
+
|
| 199 |
+
class ChangePinRequest(BaseModel):
|
| 200 |
+
email: str
|
| 201 |
+
current_pin: str
|
| 202 |
+
new_pin: str
|
| 203 |
+
|
| 204 |
+
class ForgotPinRequest(BaseModel):
|
| 205 |
+
email: str
|
| 206 |
+
otp: int
|
| 207 |
+
new_pin: str
|
| 208 |
+
|
| 209 |
+
class OrganisationDetailsRequest(BaseModel):
|
| 210 |
+
email: str
|
| 211 |
+
|
| 212 |
+
class UpdatePatientDetailsRequest(BaseModel):
|
| 213 |
+
patient_id: str
|
| 214 |
+
allergies: Optional[str] = None
|
| 215 |
+
past_history: Optional[str] = None
|
| 216 |
+
|
| 217 |
+
class PatientDetailsRequest(BaseModel):
|
| 218 |
+
patient_id: str
|
| 219 |
+
|
| 220 |
+
class StoreImageRequest(BaseModel):
|
| 221 |
+
patient_id: str
|
| 222 |
+
|
| 223 |
+
class GetImageRequest(BaseModel):
|
| 224 |
+
patient_id: str
|
| 225 |
+
|
| 226 |
+
class StoreWoundImageRequest(BaseModel):
|
| 227 |
+
patient_id: str
|
| 228 |
+
|
| 229 |
+
class GetWoundImageRequest(BaseModel):
|
| 230 |
+
patient_id: str
|
| 231 |
+
|
| 232 |
+
class StoreMedImageRequest(BaseModel):
|
| 233 |
+
email: str
|
| 234 |
+
|
| 235 |
+
class GetMedImageRequest(BaseModel):
|
| 236 |
+
email: str
|
| 237 |
+
|
| 238 |
+
class StoreOrgImageRequest(BaseModel):
|
| 239 |
+
email: str
|
| 240 |
+
|
| 241 |
+
class GetOrgImageRequest(BaseModel):
|
| 242 |
+
email: str
|
| 243 |
+
|
| 244 |
+
class SaveNotesRequest(BaseModel):
|
| 245 |
+
patient_id: str
|
| 246 |
+
notes: str
|
| 247 |
+
|
| 248 |
+
class TotalAppointmentsRequest(BaseModel):
|
| 249 |
+
start_date: str
|
| 250 |
+
end_date: str
|
| 251 |
+
doctor: str
|
| 252 |
+
|
| 253 |
+
class AdminAddPractitionerRequest(BaseModel):
|
| 254 |
+
name: str
|
| 255 |
+
email: str
|
| 256 |
+
c_code: str
|
| 257 |
+
phone: str
|
| 258 |
+
org: str
|
| 259 |
+
|
| 260 |
+
class UpdateOrgProfileRequest(BaseModel):
|
| 261 |
+
email: str
|
| 262 |
+
name: Optional[str] = None
|
| 263 |
+
department: Optional[str] = None
|
| 264 |
+
about: Optional[str] = None
|
| 265 |
+
location: Optional[str] = None
|
| 266 |
+
latitude: Optional[float] = None
|
| 267 |
+
longitude: Optional[float] = None
|
| 268 |
+
|
| 269 |
+
class UpdateMedProfileRequest(BaseModel):
|
| 270 |
+
email: str
|
| 271 |
+
name: Optional[str] = None
|
| 272 |
+
department: Optional[str] = None
|
| 273 |
+
about: Optional[str] = None
|
| 274 |
+
location: Optional[str] = None
|
| 275 |
+
latitude: Optional[float] = None
|
| 276 |
+
longitude: Optional[float] = None
|
| 277 |
+
|
| 278 |
+
class AddWoundDetailsV2Request(BaseModel):
|
| 279 |
+
length: float
|
| 280 |
+
breadth: float
|
| 281 |
+
depth: float
|
| 282 |
+
area: float
|
| 283 |
+
moisture: str
|
| 284 |
+
wound_location: Optional[str] = None
|
| 285 |
+
tissue: Optional[str] = None
|
| 286 |
+
exudate: Optional[str] = None
|
| 287 |
+
periwound: Optional[str] = None
|
| 288 |
+
periwound_type: Optional[str] = None
|
| 289 |
+
patient_id: str
|
| 290 |
+
type: Optional[str] = None
|
| 291 |
+
category: Optional[str] = None
|
| 292 |
+
edge: Optional[str] = None
|
| 293 |
+
infection: Optional[str] = None
|
| 294 |
+
last_dressing_date: Optional[str] = None
|
| 295 |
+
|
| 296 |
+
class WoundProgressTimelineRequest(BaseModel):
|
| 297 |
+
patient_id: str
|
| 298 |
+
|
| 299 |
+
class AddPatientV2Request(BaseModel):
|
| 300 |
+
name: str
|
| 301 |
+
dob: str
|
| 302 |
+
gender: str
|
| 303 |
+
age: int
|
| 304 |
+
height: float
|
| 305 |
+
weight: float
|
| 306 |
+
email: str
|
| 307 |
+
doctor: str
|
| 308 |
+
role: str
|
| 309 |
+
|
| 310 |
+
class SaveNotesV2Request(BaseModel):
|
| 311 |
+
patient_id: str
|
| 312 |
+
notes: Optional[str] = None
|
| 313 |
+
remarks: Optional[str] = None
|
| 314 |
+
|
| 315 |
+
class AdminAddPractitionerV2Request(BaseModel):
|
| 316 |
+
name: str
|
| 317 |
+
email: str
|
| 318 |
+
c_code: str
|
| 319 |
+
phone: str
|
| 320 |
+
org_email: str
|
| 321 |
+
|
| 322 |
+
class SortPatientsRequest(BaseModel):
|
| 323 |
+
email: str
|
| 324 |
+
date: Optional[str] = None
|
| 325 |
+
|
| 326 |
+
class GetAppointmentCountRequest(BaseModel):
|
| 327 |
+
email: str
|
| 328 |
+
date: str
|
| 329 |
+
|
| 330 |
+
class AddWoundDetailsV3Request(BaseModel):
|
| 331 |
+
length: float
|
| 332 |
+
breadth: float
|
| 333 |
+
depth: float
|
| 334 |
+
area: float
|
| 335 |
+
moisture: str
|
| 336 |
+
wound_location: Optional[str] = None
|
| 337 |
+
tissue: Optional[str] = None
|
| 338 |
+
exudate: Optional[str] = None
|
| 339 |
+
periwound: Optional[str] = None
|
| 340 |
+
periwound_type: Optional[str] = None
|
| 341 |
+
patient_id: str
|
| 342 |
+
type: Optional[str] = None
|
| 343 |
+
category: Optional[str] = None
|
| 344 |
+
edge: Optional[str] = None
|
| 345 |
+
infection: Optional[str] = None
|
| 346 |
+
last_dressing_date: Optional[str] = None
|
| 347 |
+
|
| 348 |
+
|
| 349 |
+
# Endpoints
|
| 350 |
+
|
| 351 |
+
@app.post("/send_email")
|
| 352 |
+
async def send_email(request_data: SendEmailRequest, db: SessionLocal = Depends(get_db)):
|
| 353 |
+
name = request_data.name
|
| 354 |
+
email = request_data.email
|
| 355 |
+
c_code = request_data.c_code
|
| 356 |
+
phone = request_data.phone
|
| 357 |
+
|
| 358 |
+
try:
|
| 359 |
+
with db as session:
|
| 360 |
+
query = text("SELECT email, phone FROM organisations WHERE email = :email OR phone = :phone")
|
| 361 |
+
existing_user = session.execute(query, {'email': email, 'phone': phone}).fetchone()
|
| 362 |
+
|
| 363 |
+
if existing_user:
|
| 364 |
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Email or phone already exists. Please login.')
|
| 365 |
+
else:
|
| 366 |
+
license_key = generate_license_key()
|
| 367 |
+
|
| 368 |
+
email_payload = {
|
| 369 |
+
'Recipient': email,
|
| 370 |
+
'Subject': 'License key for SmartHeal',
|
| 371 |
+
'Body': f'Your license key is: {license_key}',
|
| 372 |
+
'ApiKey': '6A7339A3-E70B-4A8D-AA23-0264125F4959'
|
| 373 |
+
}
|
| 374 |
+
|
| 375 |
+
email_response = requests.post(
|
| 376 |
+
'https://api.waysdatalabs.com/api/EmailSender/SendMail',
|
| 377 |
+
headers={},
|
| 378 |
+
data=email_payload
|
| 379 |
+
)
|
| 380 |
+
|
| 381 |
+
if email_response.status_code == 200:
|
| 382 |
+
return JSONResponse(content={'message': 'License key sent to email successfully', 'license_key': license_key}, status_code=status.HTTP_200_OK)
|
| 383 |
+
else:
|
| 384 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail='Failed to send email')
|
| 385 |
+
except Exception as e:
|
| 386 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 387 |
+
|
| 388 |
+
@app.post("/verify_license_key")
|
| 389 |
+
async def verify_license_key_endpoint(request_data: VerifyLicenseKeyRequest, db: SessionLocal = Depends(get_db)):
|
| 390 |
+
email = request_data.email
|
| 391 |
+
license_key = request_data.license_key
|
| 392 |
+
|
| 393 |
+
try:
|
| 394 |
+
with db as session:
|
| 395 |
+
query = text("SELECT licence_key FROM organisations WHERE email = :email")
|
| 396 |
+
result = session.execute(query, {'email': email}).fetchone()
|
| 397 |
+
if result and result.licence_key == license_key:
|
| 398 |
+
token = jwt.encode({'email': email, 'exp': datetime.datetime.utcnow() + datetime.timedelta(days=30)}, JWT_SECRET_KEY, algorithm='HS256')
|
| 399 |
+
return JSONResponse(content={'message': 'License key verified successfully', 'token': token}, status_code=status.HTTP_200_OK)
|
| 400 |
+
else:
|
| 401 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid license key')
|
| 402 |
+
except Exception as e:
|
| 403 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 404 |
+
|
| 405 |
+
@app.post("/create_pin")
|
| 406 |
+
async def create_pin_endpoint(request_data: CreatePinRequest, db: SessionLocal = Depends(get_db)):
|
| 407 |
+
license_key = request_data.license_key
|
| 408 |
+
email = request_data.email
|
| 409 |
+
pin = request_data.pin
|
| 410 |
+
name = request_data.name
|
| 411 |
+
c_code = request_data.c_code
|
| 412 |
+
phone = request_data.phone
|
| 413 |
+
|
| 414 |
+
created_at = datetime.datetime.utcnow()
|
| 415 |
+
updated_at = datetime.datetime.utcnow()
|
| 416 |
+
|
| 417 |
+
try:
|
| 418 |
+
with db as session:
|
| 419 |
+
token = jwt.encode({'email': email, 'exp': datetime.datetime.utcnow() + datetime.timedelta(days=30)}, JWT_SECRET_KEY, algorithm='HS256')
|
| 420 |
+
query = text("INSERT INTO organisations (name, email, c_code, phone, uuid, licence_key, pin, created_at, updated_at) VALUES (:name, :email, :c_code, :phone, :uuid, :license_key, :pin, :created_at, :updated_at)")
|
| 421 |
+
session.execute(query, {
|
| 422 |
+
'name': name,
|
| 423 |
+
'email': email,
|
| 424 |
+
'c_code': c_code,
|
| 425 |
+
'phone': phone,
|
| 426 |
+
'uuid': generate_session_id(),
|
| 427 |
+
'license_key': license_key,
|
| 428 |
+
'pin': pin,
|
| 429 |
+
'created_at': created_at,
|
| 430 |
+
'updated_at': updated_at
|
| 431 |
+
})
|
| 432 |
+
session.commit()
|
| 433 |
+
return JSONResponse(content={'message': 'PIN created and data saved successfully', 'token': token}, status_code=status.HTTP_200_OK)
|
| 434 |
+
except Exception as e:
|
| 435 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 436 |
+
|
| 437 |
+
@app.post("/fetch_data")
|
| 438 |
+
async def fetch_name_phone(request_data: FetchDataRequest, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
|
| 439 |
+
email = request_data.email
|
| 440 |
+
|
| 441 |
+
try:
|
| 442 |
+
with db as session:
|
| 443 |
+
query = text("SELECT name, phone FROM organisations WHERE email = :email")
|
| 444 |
+
result = session.execute(query, {'email': email}).fetchall()
|
| 445 |
+
result_dicts = [{'name': row[0], 'phone': row[1]} for row in result]
|
| 446 |
+
return JSONResponse(content=result_dicts, status_code=status.HTTP_200_OK)
|
| 447 |
+
except Exception as e:
|
| 448 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 449 |
+
|
| 450 |
+
@app.post("/save_additional_data")
|
| 451 |
+
async def save_department_location(request_data: SaveAdditionalDataRequest, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
|
| 452 |
+
department = request_data.department
|
| 453 |
+
location = request_data.location
|
| 454 |
+
email = request_data.email
|
| 455 |
+
latitude = request_data.latitude
|
| 456 |
+
longitude = request_data.longitude
|
| 457 |
+
|
| 458 |
+
try:
|
| 459 |
+
with db as session:
|
| 460 |
+
query = text("UPDATE organisations SET departments = :department, location = :location, latitude = :latitude, longitude = :longitude WHERE email = :email;")
|
| 461 |
+
session.execute(query, {'department': department, 'location': location, 'latitude': latitude, 'longitude': longitude, 'email': email})
|
| 462 |
+
session.commit()
|
| 463 |
+
return JSONResponse(content={'message': 'Department, location, latitude, and longitude saved successfully'}, status_code=status.HTTP_200_OK)
|
| 464 |
+
except Exception as e:
|
| 465 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 466 |
+
|
| 467 |
+
@app.post("/add_wound_details")
|
| 468 |
+
async def add_wound_details(request_data: AddWoundDetailsRequest, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
|
| 469 |
+
length = request_data.length
|
| 470 |
+
breadth = request_data.breadth
|
| 471 |
+
depth = request_data.depth
|
| 472 |
+
area = request_data.area
|
| 473 |
+
moisture = request_data.moisture
|
| 474 |
+
wound_location = request_data.wound_location
|
| 475 |
+
tissue = request_data.tissue
|
| 476 |
+
exudate = request_data.exudate
|
| 477 |
+
periwound = request_data.periwound
|
| 478 |
+
periwound_type = request_data.periwound_type
|
| 479 |
+
patient_id = request_data.patient_id
|
| 480 |
+
type = request_data.type
|
| 481 |
+
category = request_data.category
|
| 482 |
+
edge = request_data.edge
|
| 483 |
+
infection = request_data.infection
|
| 484 |
+
last_dressing_date = request_data.last_dressing_date
|
| 485 |
+
|
| 486 |
+
try:
|
| 487 |
+
with db as session:
|
| 488 |
+
query = text("UPDATE wounds SET height = :length, width = :breadth, depth = :depth, area = :area, moisture = :moisture, position = :wound_location, tissue = :tissue, exudate = :exudate, periwound = :periwound, periwound_type = :periwound_type, type = :type, category = :category, edges = :edge, infection = :infection, last_dressing = :last_dressing_date WHERE patient_id = :patient_id;")
|
| 489 |
+
session.execute(query, {'length': length, 'breadth': breadth, 'depth': depth, 'area': area, 'moisture': moisture, 'wound_location': wound_location, 'tissue': tissue, 'exudate': exudate, 'periwound': periwound, 'periwound_type': periwound_type, 'type': type, 'category': category, 'edge': edge, 'infection': infection, 'last_dressing_date': last_dressing_date, 'patient_id': patient_id})
|
| 490 |
+
session.commit()
|
| 491 |
+
return JSONResponse(content={'message': 'Wound details added successfully'}, status_code=status.HTTP_200_OK)
|
| 492 |
+
except Exception as e:
|
| 493 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 494 |
+
|
| 495 |
+
@app.get("/get_all_patient_details")
|
| 496 |
+
async def get_all_patient_details(email: str, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
|
| 497 |
+
try:
|
| 498 |
+
with db as session:
|
| 499 |
+
query = text("SELECT * FROM patients WHERE email = :email")
|
| 500 |
+
patient_details = session.execute(query, {'email': email}).fetchall()
|
| 501 |
+
patient_dicts = [dict(row._mapping) for row in patient_details]
|
| 502 |
+
return JSONResponse(content={'patient_details': patient_dicts}, status_code=status.HTTP_200_OK)
|
| 503 |
+
except Exception as e:
|
| 504 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 505 |
+
|
| 506 |
+
@app.post("/add_patient")
|
| 507 |
+
async def add_patient_endpoint(request_data: AddPatientRequest, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
|
| 508 |
+
name = request_data.name
|
| 509 |
+
dob = request_data.dob
|
| 510 |
+
gender = request_data.gender
|
| 511 |
+
age = request_data.age
|
| 512 |
+
height = request_data.height
|
| 513 |
+
weight = request_data.weight
|
| 514 |
+
email = request_data.email
|
| 515 |
+
doctor = request_data.doctor
|
| 516 |
+
patient_id = generate_patient_id()
|
| 517 |
+
|
| 518 |
+
created_at = datetime.datetime.utcnow()
|
| 519 |
+
updated_at = datetime.datetime.utcnow()
|
| 520 |
+
scheduled_date = datetime.datetime.utcnow()
|
| 521 |
+
|
| 522 |
+
try:
|
| 523 |
+
with db as session:
|
| 524 |
+
uuid_val = generate_session_id()
|
| 525 |
+
pat_query = text("INSERT INTO patients (name, dob, gender, age, height, weight, email, doctor, uuid, patient_id, created_at, updated_at, scheduled_date) VALUES (:name, :dob, :gender, :age, :height, :weight, :email, :doctor, :uuid, :patient_id, :created_at, :updated_at, :scheduled_date)")
|
| 526 |
+
session.execute(pat_query, {'name': name, 'dob': dob, 'gender': gender, 'age': age, 'height': height, 'weight': weight, 'email': email, 'doctor': doctor, 'uuid': uuid_val, 'patient_id': patient_id, 'created_at': created_at, 'updated_at': updated_at, 'scheduled_date': scheduled_date})
|
| 527 |
+
wound_query = text("INSERT INTO wounds ( uuid, patient_id, created_at, updated_at) VALUES (:uuid, :patient_id, :created_at, :updated_at)")
|
| 528 |
+
session.execute(wound_query, {'uuid': uuid_val, 'patient_id': patient_id, 'created_at': created_at, 'updated_at': updated_at})
|
| 529 |
+
session.commit()
|
| 530 |
+
return JSONResponse(content={'message': 'Patient added successfully', 'patient_id': patient_id}, status_code=status.HTTP_200_OK)
|
| 531 |
+
except Exception as e:
|
| 532 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 533 |
+
|
| 534 |
+
@app.get("/search_patient")
|
| 535 |
+
async def search_patient_endpoint(name: str, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
|
| 536 |
+
if not name:
|
| 537 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Missing required fields')
|
| 538 |
+
|
| 539 |
+
try:
|
| 540 |
+
with db as session:
|
| 541 |
+
query = text("SELECT * FROM patients WHERE name = :name")
|
| 542 |
+
patients = session.execute(query, {'name': name}).fetchall()
|
| 543 |
+
|
| 544 |
+
if not patients:
|
| 545 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='No patients found with this name')
|
| 546 |
+
|
| 547 |
+
patients_dicts = [dict(row._mapping) for row in patients]
|
| 548 |
+
return JSONResponse(content={'patients': patients_dicts}, status_code=status.HTTP_200_OK)
|
| 549 |
+
except Exception as e:
|
| 550 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 551 |
+
|
| 552 |
+
@app.get("/generate_prescription")
|
| 553 |
+
async def generate_prescription_endpoint(patient_id: str, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
|
| 554 |
+
if not patient_id:
|
| 555 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Missing required fields')
|
| 556 |
+
|
| 557 |
+
try:
|
| 558 |
+
with db as session:
|
| 559 |
+
patient_query = text("SELECT * FROM patients WHERE patient_id = :patient_id ")
|
| 560 |
+
patient = session.execute(patient_query, {'patient_id': patient_id}).fetchone()
|
| 561 |
+
if not patient:
|
| 562 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='No patient found with this ID')
|
| 563 |
+
|
| 564 |
+
wound_query = text("SELECT * FROM patients WHERE patient_id = :patient_id ")
|
| 565 |
+
wounds = session.execute(wound_query, {'patient_id': patient_id}).fetchall()
|
| 566 |
+
|
| 567 |
+
wound_category = []
|
| 568 |
+
for wound in wounds:
|
| 569 |
+
area = wound.height * wound.width
|
| 570 |
+
if area <= 5:
|
| 571 |
+
dimension = 'Small'
|
| 572 |
+
elif 5 < area <= 20:
|
| 573 |
+
dimension = 'Medium'
|
| 574 |
+
else:
|
| 575 |
+
dimension = 'Large'
|
| 576 |
+
wound_category.append((wound.wound_type, dimension))
|
| 577 |
+
|
| 578 |
+
medication_details = []
|
| 579 |
+
for wound_type, dimension in wound_category:
|
| 580 |
+
medication_query = text("""
|
| 581 |
+
SELECT * FROM WoundMedications
|
| 582 |
+
WHERE WoundType = :wound_type AND WoundDimensions = :dimension
|
| 583 |
+
""")
|
| 584 |
+
medications = session.execute(medication_query, {'wound_type': wound_type, 'dimension': dimension}).fetchall()
|
| 585 |
+
medication_details.extend(medications)
|
| 586 |
+
|
| 587 |
+
prescription = {
|
| 588 |
+
'patient_details': dict(patient._mapping),
|
| 589 |
+
'wound_details': [dict(w._mapping) for w in wounds],
|
| 590 |
+
'medication_details': [dict(m._mapping) for m in medication_details]
|
| 591 |
+
}
|
| 592 |
+
|
| 593 |
+
return JSONResponse(content={'prescription': prescription}, status_code=status.HTTP_200_OK)
|
| 594 |
+
except Exception as e:
|
| 595 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 596 |
+
|
| 597 |
+
@app.post("/verify_pin")
|
| 598 |
+
async def verify_pin_endpoint(request_data: VerifyPinRequest, db: SessionLocal = Depends(get_db)):
|
| 599 |
+
email = request_data.email
|
| 600 |
+
pin = request_data.pin
|
| 601 |
+
|
| 602 |
+
try:
|
| 603 |
+
with db as session:
|
| 604 |
+
query = text("SELECT pin FROM organisations WHERE email = :email")
|
| 605 |
+
result = session.execute(query, {'email': email}).fetchone()
|
| 606 |
+
|
| 607 |
+
if result:
|
| 608 |
+
if result.pin == pin:
|
| 609 |
+
return JSONResponse(content={'message': 'Pin verified successfully'}, status_code=status.HTTP_200_OK)
|
| 610 |
+
else:
|
| 611 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid pin')
|
| 612 |
+
else:
|
| 613 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Email not found')
|
| 614 |
+
except Exception as e:
|
| 615 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 616 |
+
|
| 617 |
+
@app.post("/send_otp")
|
| 618 |
+
async def send_otp_endpoint(request_data: SendOtpRequest, db: SessionLocal = Depends(get_db)):
|
| 619 |
+
phone = request_data.phone
|
| 620 |
+
|
| 621 |
+
try:
|
| 622 |
+
with db as session:
|
| 623 |
+
query = text("SELECT * FROM organisations WHERE phone = :phone")
|
| 624 |
+
organisation = session.execute(query, {'phone': phone}).fetchone()
|
| 625 |
+
|
| 626 |
+
if organisation:
|
| 627 |
+
phone_with_code = organisation.c_code + organisation.phone
|
| 628 |
+
otp = "1234" # generate_otp()
|
| 629 |
+
# send_sms(phone_with_code, otp)
|
| 630 |
+
|
| 631 |
+
updated_at = datetime.datetime.utcnow()
|
| 632 |
+
expiry_time = datetime.datetime.utcnow() + datetime.timedelta(minutes=5)
|
| 633 |
+
update_otp_in_database(session, phone, otp, expiry_time, updated_at)
|
| 634 |
+
|
| 635 |
+
token = jwt.encode({'email': organisation.email, 'exp': datetime.datetime.utcnow() + datetime.timedelta(days=30)}, JWT_SECRET_KEY, algorithm='HS256')
|
| 636 |
+
return JSONResponse(content={'status': 200, 'message': 'OTP Sent on mobile.', 'token': token, 'otp': otp, 'email': organisation.email, 'name': organisation.name, 'pin': organisation.pin}, status_code=status.HTTP_200_OK)
|
| 637 |
+
else:
|
| 638 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='OOPS! Phone Does Not Exist!')
|
| 639 |
+
except Exception as e:
|
| 640 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 641 |
+
|
| 642 |
+
@app.post("/med_send_email")
|
| 643 |
+
async def med_send_email(request_data: SendEmailRequest, db: SessionLocal = Depends(get_db)):
|
| 644 |
+
name = request_data.name
|
| 645 |
+
email = request_data.email
|
| 646 |
+
c_code = request_data.c_code
|
| 647 |
+
phone = request_data.phone
|
| 648 |
+
|
| 649 |
+
try:
|
| 650 |
+
with db as session:
|
| 651 |
+
query = text("SELECT email, phone FROM users WHERE email = :email OR phone = :phone")
|
| 652 |
+
existing_user = session.execute(query, {'email': email, 'phone': phone}).fetchone()
|
| 653 |
+
if existing_user:
|
| 654 |
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Email or phone already exists. Please login.')
|
| 655 |
+
else:
|
| 656 |
+
license_key = generate_license_key()
|
| 657 |
+
|
| 658 |
+
email_payload = {
|
| 659 |
+
'Recipient': email,
|
| 660 |
+
'Subject': 'License key for SmartHeal',
|
| 661 |
+
'Body': f'Your license key is: {license_key}',
|
| 662 |
+
'ApiKey': '6A7339A3-E70B-4A8D-AA23-0264125F4959'
|
| 663 |
+
}
|
| 664 |
+
|
| 665 |
+
email_response = requests.post(
|
| 666 |
+
'https://api.waysdatalabs.com/api/EmailSender/SendMail',
|
| 667 |
+
headers={},
|
| 668 |
+
data=email_payload
|
| 669 |
+
)
|
| 670 |
+
|
| 671 |
+
if email_response.status_code == 200:
|
| 672 |
+
return JSONResponse(content={'message': 'License key sent to email successfully', 'license_key': license_key}, status_code=status.HTTP_200_OK)
|
| 673 |
+
else:
|
| 674 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail='Failed to send email')
|
| 675 |
+
except Exception as e:
|
| 676 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 677 |
+
|
| 678 |
+
@app.post("/med_verify_license_key")
|
| 679 |
+
async def med_verify_license_key(request_data: VerifyLicenseKeyRequest, db: SessionLocal = Depends(get_db)):
|
| 680 |
+
email = request_data.email
|
| 681 |
+
license_key = request_data.license_key
|
| 682 |
+
|
| 683 |
+
try:
|
| 684 |
+
with db as session:
|
| 685 |
+
query = text("SELECT licence_key FROM users WHERE email = :email")
|
| 686 |
+
result = session.execute(query, {'email': email}).fetchone()
|
| 687 |
+
if result and result.licence_key == license_key:
|
| 688 |
+
token = jwt.encode({'email': email, 'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=24)}, JWT_SECRET_KEY, algorithm='HS256')
|
| 689 |
+
return JSONResponse(content={'message': 'License key verified successfully', 'token': token}, status_code=status.HTTP_200_OK)
|
| 690 |
+
else:
|
| 691 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid license key')
|
| 692 |
+
except Exception as e:
|
| 693 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 694 |
+
|
| 695 |
+
@app.post("/med_create_pin")
|
| 696 |
+
async def med_create_pin(request_data: CreatePinRequest, db: SessionLocal = Depends(get_db)):
|
| 697 |
+
license_key = request_data.license_key
|
| 698 |
+
email = request_data.email
|
| 699 |
+
pin = request_data.pin
|
| 700 |
+
name = request_data.name
|
| 701 |
+
c_code = request_data.c_code
|
| 702 |
+
phone = request_data.phone
|
| 703 |
+
|
| 704 |
+
created_at = datetime.datetime.utcnow()
|
| 705 |
+
updated_at = datetime.datetime.utcnow()
|
| 706 |
+
|
| 707 |
+
try:
|
| 708 |
+
with db as session:
|
| 709 |
+
org = "0"
|
| 710 |
+
token = jwt.encode({'email': email, 'exp': datetime.datetime.utcnow() + datetime.timedelta(days=30)}, JWT_SECRET_KEY, algorithm='HS256')
|
| 711 |
+
query = text("INSERT INTO users (name, email, c_code, phone, uuid, licence_key, pin, org, created_at, updated_at) VALUES (:name, :email, :c_code, :phone, :uuid, :license_key, :pin, :org, :created_at, :updated_at)")
|
| 712 |
+
session.execute(query, {
|
| 713 |
+
'name': name,
|
| 714 |
+
'email': email,
|
| 715 |
+
'c_code': c_code,
|
| 716 |
+
'phone': phone,
|
| 717 |
+
'uuid': generate_session_id(),
|
| 718 |
+
'license_key': license_key,
|
| 719 |
+
'pin': pin,
|
| 720 |
+
'org': org,
|
| 721 |
+
'created_at': created_at,
|
| 722 |
+
'updated_at': updated_at
|
| 723 |
+
})
|
| 724 |
+
session.commit()
|
| 725 |
+
return JSONResponse(content={'message': 'PIN created and data saved successfully', 'token': token}, status_code=status.HTTP_200_OK)
|
| 726 |
+
except Exception as e:
|
| 727 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 728 |
+
|
| 729 |
+
@app.post("/med_fetch_data")
|
| 730 |
+
async def med_fetch_name_phone(request_data: FetchDataRequest, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
|
| 731 |
+
email = request_data.email
|
| 732 |
+
|
| 733 |
+
try:
|
| 734 |
+
with db as session:
|
| 735 |
+
query = text("SELECT name, phone FROM users WHERE email = :email")
|
| 736 |
+
result = session.execute(query, {'email': email}).fetchall()
|
| 737 |
+
result_dicts = [{'name': row[0], 'phone': row[1]} for row in result]
|
| 738 |
+
return JSONResponse(content=result_dicts, status_code=status.HTTP_200_OK)
|
| 739 |
+
except Exception as e:
|
| 740 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 741 |
+
|
| 742 |
+
@app.post("/save_med_data")
|
| 743 |
+
async def med_save_department_location(request_data: Request, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
|
| 744 |
+
data = await request_data.json()
|
| 745 |
+
speciality = data.get('speciality')
|
| 746 |
+
location = data.get('location')
|
| 747 |
+
email = data.get('email')
|
| 748 |
+
latitude = data.get('latitude')
|
| 749 |
+
longitude = data.get('longitude')
|
| 750 |
+
org = data.get('org')
|
| 751 |
+
designation = data.get('designation')
|
| 752 |
+
|
| 753 |
+
if not (speciality and location and latitude and longitude and org and email):
|
| 754 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Missing required fields')
|
| 755 |
+
|
| 756 |
+
try:
|
| 757 |
+
with db as session:
|
| 758 |
+
query = text("UPDATE users SET speciality = :speciality, location = :location, latitude = :latitude, longitude = :longitude, org = :org, designation = :designation WHERE email = :email;")
|
| 759 |
+
session.execute(query, {'speciality': speciality, 'location': location, 'latitude': latitude, 'longitude': longitude, 'org': org, 'designation': designation, 'email': email})
|
| 760 |
+
session.commit()
|
| 761 |
+
return JSONResponse(content={'message': 'Speciality, location, organisation and designation saved successfully'}, status_code=status.HTTP_200_OK)
|
| 762 |
+
except Exception as e:
|
| 763 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 764 |
+
|
| 765 |
+
@app.post("/med_verify_pin")
|
| 766 |
+
async def med_verify_pin(request_data: VerifyPinRequest, db: SessionLocal = Depends(get_db)):
|
| 767 |
+
email = request_data.email
|
| 768 |
+
pin = request_data.pin
|
| 769 |
+
|
| 770 |
+
try:
|
| 771 |
+
with db as session:
|
| 772 |
+
query = text("SELECT pin FROM users WHERE email = :email")
|
| 773 |
+
result = session.execute(query, {'email': email}).fetchone()
|
| 774 |
+
|
| 775 |
+
if result:
|
| 776 |
+
if result.pin == pin:
|
| 777 |
+
return JSONResponse(content={'message': 'Pin verified successfully'}, status_code=status.HTTP_200_OK)
|
| 778 |
+
else:
|
| 779 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid pin')
|
| 780 |
+
else:
|
| 781 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Email not found')
|
| 782 |
+
except Exception as e:
|
| 783 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 784 |
+
|
| 785 |
+
@app.post("/med_send_otp")
|
| 786 |
+
async def med_send_otp(request_data: SendOtpRequest, db: SessionLocal = Depends(get_db)):
|
| 787 |
+
phone = request_data.phone
|
| 788 |
+
|
| 789 |
+
try:
|
| 790 |
+
with db as session:
|
| 791 |
+
query = text("SELECT * FROM users WHERE phone = :phone")
|
| 792 |
+
user = session.execute(query, {'phone': phone}).fetchone()
|
| 793 |
+
|
| 794 |
+
if user:
|
| 795 |
+
phone_with_code = user.c_code + user.phone
|
| 796 |
+
otp = "1234" # generate_otp()
|
| 797 |
+
# send_sms(phone_with_code, otp)
|
| 798 |
+
|
| 799 |
+
updated_at = datetime.datetime.utcnow()
|
| 800 |
+
expiry_time = datetime.datetime.utcnow() + datetime.timedelta(minutes=5)
|
| 801 |
+
update_otp_in_med_database(session, phone, otp, expiry_time, updated_at)
|
| 802 |
+
|
| 803 |
+
token = jwt.encode({'email': user.email, 'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=24)}, JWT_SECRET_KEY, algorithm='HS256')
|
| 804 |
+
return JSONResponse(content={'status': 200, 'message': 'OTP Sent on mobile.', 'token': token, 'otp': otp, 'email': user.email, 'name': user.name, 'pin': user.pin}, status_code=status.HTTP_200_OK)
|
| 805 |
+
else:
|
| 806 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='OOPS! Phone Does Not Exist!')
|
| 807 |
+
except Exception as e:
|
| 808 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 809 |
+
|
| 810 |
+
@app.post("/update_scheduled_date")
|
| 811 |
+
async def update_scheduled_date_endpoint(request_data: UpdateScheduledDateRequest, db: SessionLocal = Depends(get_db)):
|
| 812 |
+
email = request_data.email
|
| 813 |
+
patient_id = request_data.patient_id
|
| 814 |
+
doctor = request_data.doctor
|
| 815 |
+
scheduled_date = request_data.scheduled_date
|
| 816 |
+
|
| 817 |
+
try:
|
| 818 |
+
with db as session:
|
| 819 |
+
query = text("UPDATE patients SET scheduled_date = :scheduled_date WHERE email = :email AND patient_id = :patient_id AND doctor = :doctor")
|
| 820 |
+
result = session.execute(query, {'scheduled_date': scheduled_date, 'email': email, 'patient_id': patient_id, 'doctor': doctor})
|
| 821 |
+
session.commit()
|
| 822 |
+
|
| 823 |
+
if result.rowcount == 0:
|
| 824 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='No matching record found')
|
| 825 |
+
|
| 826 |
+
return JSONResponse(content={'message': 'Scheduled date updated successfully'}, status_code=status.HTTP_200_OK)
|
| 827 |
+
except Exception as e:
|
| 828 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 829 |
+
|
| 830 |
+
@app.get("/total_appointments_till_date")
|
| 831 |
+
async def total_appointments_till_date_endpoint(date: str, db: SessionLocal = Depends(get_db)):
|
| 832 |
+
if not date:
|
| 833 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Date parameter is required')
|
| 834 |
+
|
| 835 |
+
try:
|
| 836 |
+
with db as session:
|
| 837 |
+
query = text("SELECT COUNT(*) as total_appointments FROM patients WHERE scheduled_date <= :date")
|
| 838 |
+
result = session.execute(query, {'date': date}).fetchone()
|
| 839 |
+
|
| 840 |
+
total_appointments = result[0]
|
| 841 |
+
|
| 842 |
+
return JSONResponse(content={'total_appointments': total_appointments}, status_code=status.HTTP_200_OK)
|
| 843 |
+
except Exception as e:
|
| 844 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 845 |
+
|
| 846 |
+
@app.get("/total_appointments_till_month")
|
| 847 |
+
async def total_appointments_till_month_endpoint(year: int, month: int, db: SessionLocal = Depends(get_db)):
|
| 848 |
+
if not (year and month):
|
| 849 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Year and month parameters are required')
|
| 850 |
+
|
| 851 |
+
try:
|
| 852 |
+
with db as session:
|
| 853 |
+
query = text("""
|
| 854 |
+
SELECT COUNT(*) as total_appointments
|
| 855 |
+
FROM patients
|
| 856 |
+
WHERE YEAR(scheduled_date) = :year AND MONTH(scheduled_date) = :month
|
| 857 |
+
""")
|
| 858 |
+
result = session.execute(query, {'year': year, 'month': month}).fetchone()
|
| 859 |
+
|
| 860 |
+
total_appointments = result[0]
|
| 861 |
+
|
| 862 |
+
return JSONResponse(content={'total_appointments': total_appointments}, status_code=status.HTTP_200_OK)
|
| 863 |
+
except Exception as e:
|
| 864 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 865 |
+
|
| 866 |
+
@app.post("/change_pin_org")
|
| 867 |
+
async def change_pin_org(request_data: ChangePinRequest, db: SessionLocal = Depends(get_db)):
|
| 868 |
+
email = request_data.email
|
| 869 |
+
current_pin = request_data.current_pin
|
| 870 |
+
new_pin = request_data.new_pin
|
| 871 |
+
|
| 872 |
+
try:
|
| 873 |
+
with db as session:
|
| 874 |
+
query = text("SELECT pin FROM organisations WHERE email = :email")
|
| 875 |
+
result = session.execute(query, {'email': email}).fetchone()
|
| 876 |
+
|
| 877 |
+
if result:
|
| 878 |
+
if result.pin == current_pin:
|
| 879 |
+
update_query = text("UPDATE organisations SET pin = :new_pin WHERE email = :email")
|
| 880 |
+
session.execute(update_query, {'new_pin': new_pin, 'email': email})
|
| 881 |
+
session.commit()
|
| 882 |
+
return JSONResponse(content={'message': 'Pin updated successfully'}, status_code=status.HTTP_200_OK)
|
| 883 |
+
else:
|
| 884 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid current pin')
|
| 885 |
+
else:
|
| 886 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Email not found')
|
| 887 |
+
except Exception as e:
|
| 888 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 889 |
+
|
| 890 |
+
@app.post("/forgot_pin_org")
|
| 891 |
+
async def forgot_pin_org(request_data: ForgotPinRequest, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
|
| 892 |
+
email = request_data.email
|
| 893 |
+
otp = request_data.otp
|
| 894 |
+
new_pin = request_data.new_pin
|
| 895 |
+
|
| 896 |
+
try:
|
| 897 |
+
with db as session:
|
| 898 |
+
query = text("SELECT otp FROM organisations WHERE email = :email")
|
| 899 |
+
result = session.execute(query, {'email': email}).fetchone()
|
| 900 |
+
|
| 901 |
+
if result:
|
| 902 |
+
if result.otp == otp:
|
| 903 |
+
update_query = text("UPDATE organisations SET pin = :new_pin WHERE email = :email")
|
| 904 |
+
session.execute(update_query, {'new_pin': new_pin, 'email': email})
|
| 905 |
+
session.commit()
|
| 906 |
+
return JSONResponse(content={'message': 'Pin updated successfully'}, status_code=status.HTTP_200_OK)
|
| 907 |
+
else:
|
| 908 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid OTP')
|
| 909 |
+
else:
|
| 910 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Email not found')
|
| 911 |
+
except Exception as e:
|
| 912 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 913 |
+
|
| 914 |
+
@app.get("/organisation_details")
|
| 915 |
+
async def organisation_details(email: str, db: SessionLocal = Depends(get_db)):
|
| 916 |
+
if not email:
|
| 917 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Email parameter is required')
|
| 918 |
+
|
| 919 |
+
try:
|
| 920 |
+
with db as session:
|
| 921 |
+
query = text("""
|
| 922 |
+
SELECT o.name, o.departments, o.location, o.latitude, o.longitude, o.about,
|
| 923 |
+
o.profile_photo_path, COUNT(p.email) as patient_count
|
| 924 |
+
FROM organisations o
|
| 925 |
+
LEFT JOIN patients p ON o.email = p.email
|
| 926 |
+
WHERE o.email = :email
|
| 927 |
+
""")
|
| 928 |
+
result = session.execute(query, {'email': email}).fetchone()
|
| 929 |
+
|
| 930 |
+
if result:
|
| 931 |
+
response = {
|
| 932 |
+
'name': result.name,
|
| 933 |
+
'departments': result.departments,
|
| 934 |
+
'location': result.location,
|
| 935 |
+
'latitude': result.latitude,
|
| 936 |
+
'longitude': result.longitude,
|
| 937 |
+
'about': result.about,
|
| 938 |
+
'profile_photo_path': result.profile_photo_path,
|
| 939 |
+
'patient_count': result.patient_count
|
| 940 |
+
}
|
| 941 |
+
return JSONResponse(content=response, status_code=status.HTTP_200_OK)
|
| 942 |
+
else:
|
| 943 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Organisation not found')
|
| 944 |
+
except Exception as e:
|
| 945 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 946 |
+
|
| 947 |
+
@app.post("/change_pin_med")
|
| 948 |
+
async def change_pin_med(request_data: ChangePinRequest, db: SessionLocal = Depends(get_db)):
|
| 949 |
+
email = request_data.email
|
| 950 |
+
current_pin = request_data.current_pin
|
| 951 |
+
new_pin = request_data.new_pin
|
| 952 |
+
|
| 953 |
+
try:
|
| 954 |
+
with db as session:
|
| 955 |
+
query = text("SELECT pin FROM users WHERE email = :email")
|
| 956 |
+
result = session.execute(query, {'email': email}).fetchone()
|
| 957 |
+
|
| 958 |
+
if result:
|
| 959 |
+
if result.pin == current_pin:
|
| 960 |
+
update_query = text("UPDATE users SET pin = :new_pin WHERE email = :email")
|
| 961 |
+
session.execute(update_query, {'new_pin': new_pin, 'email': email})
|
| 962 |
+
session.commit()
|
| 963 |
+
return JSONResponse(content={'message': 'Pin updated successfully'}, status_code=status.HTTP_200_OK)
|
| 964 |
+
else:
|
| 965 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid current pin')
|
| 966 |
+
else:
|
| 967 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Email not found')
|
| 968 |
+
except Exception as e:
|
| 969 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 970 |
+
|
| 971 |
+
@app.post("/forgot_pin_med")
|
| 972 |
+
async def forgot_pin_med(request_data: ForgotPinRequest, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
|
| 973 |
+
email = request_data.email
|
| 974 |
+
otp = request_data.otp
|
| 975 |
+
new_pin = request_data.new_pin
|
| 976 |
+
|
| 977 |
+
try:
|
| 978 |
+
with db as session:
|
| 979 |
+
query = text("SELECT otp FROM users WHERE email = :email")
|
| 980 |
+
result = session.execute(query, {'email': email}).fetchone()
|
| 981 |
+
|
| 982 |
+
if result:
|
| 983 |
+
if result.otp == otp:
|
| 984 |
+
update_query = text("UPDATE users SET pin = :new_pin WHERE email = :email")
|
| 985 |
+
session.execute(update_query, {'new_pin': new_pin, 'email': email})
|
| 986 |
+
session.commit()
|
| 987 |
+
return JSONResponse(content={'message': 'Pin updated successfully'}, status_code=status.HTTP_200_OK)
|
| 988 |
+
else:
|
| 989 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid OTP')
|
| 990 |
+
else:
|
| 991 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Email not found')
|
| 992 |
+
except Exception as e:
|
| 993 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 994 |
+
|
| 995 |
+
@app.get("/med_details")
|
| 996 |
+
async def med_details(email: str, db: SessionLocal = Depends(get_db)):
|
| 997 |
+
if not email:
|
| 998 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Email parameter is required')
|
| 999 |
+
|
| 1000 |
+
try:
|
| 1001 |
+
with db as session:
|
| 1002 |
+
query = text("""
|
| 1003 |
+
SELECT o.name, o.departments, o.location, o.latitude, o.longitude, o.about,
|
| 1004 |
+
o.profile_photo_path, COUNT(p.email) as patient_count
|
| 1005 |
+
FROM users o
|
| 1006 |
+
LEFT JOIN patients p ON o.email = p.email
|
| 1007 |
+
WHERE o.email = :email
|
| 1008 |
+
""")
|
| 1009 |
+
result = session.execute(query, {'email': email}).fetchone()
|
| 1010 |
+
|
| 1011 |
+
if result:
|
| 1012 |
+
response = {
|
| 1013 |
+
'name': result.name,
|
| 1014 |
+
'departments': result.departments,
|
| 1015 |
+
'location': result.location,
|
| 1016 |
+
'latitude': result.latitude,
|
| 1017 |
+
'longitude': result.longitude,
|
| 1018 |
+
'about': result.about,
|
| 1019 |
+
'profile_photo_path': result.profile_photo_path,
|
| 1020 |
+
'patient_count': result.patient_count
|
| 1021 |
+
}
|
| 1022 |
+
return JSONResponse(content=response, status_code=status.HTTP_200_OK)
|
| 1023 |
+
else:
|
| 1024 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Organisation not found')
|
| 1025 |
+
except Exception as e:
|
| 1026 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 1027 |
+
|
| 1028 |
+
@app.post("/update_patient_details")
|
| 1029 |
+
async def update_patient_details(request_data: UpdatePatientDetailsRequest, db: SessionLocal = Depends(get_db)):
|
| 1030 |
+
patient_id = request_data.patient_id
|
| 1031 |
+
allergies = request_data.allergies
|
| 1032 |
+
past_history = request_data.past_history
|
| 1033 |
+
|
| 1034 |
+
try:
|
| 1035 |
+
with db as session:
|
| 1036 |
+
query = text("""
|
| 1037 |
+
UPDATE patients
|
| 1038 |
+
SET allergy = :allergies, illness = :past_history
|
| 1039 |
+
WHERE patient_id = :patient_id
|
| 1040 |
+
""")
|
| 1041 |
+
session.execute(query, {
|
| 1042 |
+
'allergies': allergies,
|
| 1043 |
+
'past_history': past_history,
|
| 1044 |
+
'patient_id': patient_id
|
| 1045 |
+
})
|
| 1046 |
+
session.commit()
|
| 1047 |
+
|
| 1048 |
+
return JSONResponse(content={'message': 'Patient details updated successfully'}, status_code=status.HTTP_200_OK)
|
| 1049 |
+
except Exception as e:
|
| 1050 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 1051 |
+
|
| 1052 |
+
@app.get("/patient_details")
|
| 1053 |
+
async def get_patient_details(patient_id: str, db: SessionLocal = Depends(get_db)):
|
| 1054 |
+
if not patient_id:
|
| 1055 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Patient ID parameter is required')
|
| 1056 |
+
|
| 1057 |
+
try:
|
| 1058 |
+
with db as session:
|
| 1059 |
+
patient_query = text("""
|
| 1060 |
+
SELECT *
|
| 1061 |
+
FROM patients
|
| 1062 |
+
WHERE patient_id = :patient_id
|
| 1063 |
+
""")
|
| 1064 |
+
patient_result = session.execute(patient_query, {'patient_id': patient_id}).fetchone()
|
| 1065 |
+
|
| 1066 |
+
if patient_result:
|
| 1067 |
+
wound_query = text("""
|
| 1068 |
+
SELECT *
|
| 1069 |
+
FROM wounds
|
| 1070 |
+
WHERE patient_id = :patient_id
|
| 1071 |
+
""")
|
| 1072 |
+
wound_results = session.execute(wound_query, {'patient_id': patient_id}).fetchall()
|
| 1073 |
+
|
| 1074 |
+
wound_details = [
|
| 1075 |
+
{
|
| 1076 |
+
'length': wound.height,
|
| 1077 |
+
'breadth': wound.width,
|
| 1078 |
+
'depth': wound.depth,
|
| 1079 |
+
'area': wound.area,
|
| 1080 |
+
'wound_location': wound.position,
|
| 1081 |
+
'tissue': wound.tissue,
|
| 1082 |
+
'exudate': wound.exudate,
|
| 1083 |
+
'periwound':wound.periwound,
|
| 1084 |
+
'periwound_type': wound.periwound_type,
|
| 1085 |
+
'image': wound.image,
|
| 1086 |
+
'moisture': wound.moisture
|
| 1087 |
+
} for wound in wound_results
|
| 1088 |
+
]
|
| 1089 |
+
|
| 1090 |
+
patient_details = {
|
| 1091 |
+
'patient_id': patient_result.patient_id,
|
| 1092 |
+
'name': patient_result.name,
|
| 1093 |
+
'age': patient_result.age,
|
| 1094 |
+
'gender': patient_result.gender,
|
| 1095 |
+
'dob': patient_result.dob,
|
| 1096 |
+
'profile_photo_path': patient_result.profile_photo_path,
|
| 1097 |
+
'allergies': patient_result.allergy,
|
| 1098 |
+
'past_history': patient_result.illness,
|
| 1099 |
+
'doctor_name': patient_result.added_by,
|
| 1100 |
+
'register_date': patient_result.created_at,
|
| 1101 |
+
'wound_details': wound_details
|
| 1102 |
+
}
|
| 1103 |
+
return JSONResponse(content=patient_details, status_code=status.HTTP_200_OK)
|
| 1104 |
+
else:
|
| 1105 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Patient not found')
|
| 1106 |
+
except Exception as e:
|
| 1107 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 1108 |
+
|
| 1109 |
+
@app.post("/store_image")
|
| 1110 |
+
async def store_image(patient_id: str = Form(...), image: UploadFile = File(...), db: SessionLocal = Depends(get_db)):
|
| 1111 |
+
if not allowed_file(image.filename):
|
| 1112 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid file type')
|
| 1113 |
+
|
| 1114 |
+
filename = secure_filename(image.filename)
|
| 1115 |
+
|
| 1116 |
+
try:
|
| 1117 |
+
patient_folder = os.path.join(UPLOAD_FOLDER, 'patients', patient_id)
|
| 1118 |
+
os.makedirs(patient_folder, exist_ok=True)
|
| 1119 |
+
|
| 1120 |
+
image_path = os.path.join(patient_folder, filename)
|
| 1121 |
+
with open(image_path, "wb") as buffer:
|
| 1122 |
+
buffer.write(await image.read())
|
| 1123 |
+
|
| 1124 |
+
image_url = f"{BASE_URL}/uploads/patients/{patient_id}/{filename}"
|
| 1125 |
+
|
| 1126 |
+
with db as session:
|
| 1127 |
+
query = text("UPDATE patients SET profile_photo_path = :image_url WHERE patient_id = :patient_id")
|
| 1128 |
+
session.execute(query, {'image_url': image_url, 'patient_id': patient_id})
|
| 1129 |
+
session.commit()
|
| 1130 |
+
|
| 1131 |
+
return JSONResponse(content={
|
| 1132 |
+
'message': 'Image stored successfully',
|
| 1133 |
+
'image_url': image_url
|
| 1134 |
+
}, status_code=status.HTTP_200_OK)
|
| 1135 |
+
|
| 1136 |
+
except Exception as e:
|
| 1137 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 1138 |
+
|
| 1139 |
+
@app.get("/get_image")
|
| 1140 |
+
async def get_image(patient_id: str, db: SessionLocal = Depends(get_db)):
|
| 1141 |
+
if not patient_id:
|
| 1142 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Patient ID parameter is required')
|
| 1143 |
+
|
| 1144 |
+
try:
|
| 1145 |
+
with db as session:
|
| 1146 |
+
query = text("SELECT profile_photo_path FROM patients WHERE patient_id = :patient_id")
|
| 1147 |
+
result = session.execute(query, {'patient_id': patient_id}).fetchone()
|
| 1148 |
+
|
| 1149 |
+
if result and result.profile_photo_path:
|
| 1150 |
+
image_url = result.profile_photo_path
|
| 1151 |
+
filename = image_url.split('/')[-1]
|
| 1152 |
+
|
| 1153 |
+
file_path = os.path.join(UPLOAD_FOLDER, 'patients', patient_id, filename)
|
| 1154 |
+
if os.path.exists(file_path):
|
| 1155 |
+
return FileResponse(file_path)
|
| 1156 |
+
else:
|
| 1157 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Image file not found on server')
|
| 1158 |
+
else:
|
| 1159 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Image not found for the given patient ID')
|
| 1160 |
+
except Exception as e:
|
| 1161 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 1162 |
+
|
| 1163 |
+
@app.post("/store_wound_image")
|
| 1164 |
+
async def store_wound_image(patient_id: str = Form(...), image: UploadFile = File(...), db: SessionLocal = Depends(get_db)):
|
| 1165 |
+
if not allowed_file(image.filename):
|
| 1166 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid file type')
|
| 1167 |
+
|
| 1168 |
+
filename = secure_filename(image.filename)
|
| 1169 |
+
|
| 1170 |
+
try:
|
| 1171 |
+
patient_folder = os.path.join(UPLOAD_FOLDER, 'wounds', patient_id)
|
| 1172 |
+
os.makedirs(patient_folder, exist_ok=True)
|
| 1173 |
+
|
| 1174 |
+
image_path = os.path.join(patient_folder, filename)
|
| 1175 |
+
with open(image_path, "wb") as buffer:
|
| 1176 |
+
buffer.write(await image.read())
|
| 1177 |
+
|
| 1178 |
+
image_url = f"{BASE_URL}/uploads/wounds/{patient_id}/{filename}"
|
| 1179 |
+
|
| 1180 |
+
with db as session:
|
| 1181 |
+
query = text("UPDATE wounds SET image = :image_url WHERE patient_id = :patient_id")
|
| 1182 |
+
session.execute(query, {'image_url': image_url, 'patient_id': patient_id})
|
| 1183 |
+
session.commit()
|
| 1184 |
+
|
| 1185 |
+
return JSONResponse(content={
|
| 1186 |
+
'message': 'Image stored successfully',
|
| 1187 |
+
'image_url': image_url
|
| 1188 |
+
}, status_code=status.HTTP_200_OK)
|
| 1189 |
+
|
| 1190 |
+
except Exception as e:
|
| 1191 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 1192 |
+
|
| 1193 |
+
@app.get("/get_wound_image")
|
| 1194 |
+
async def get_wound_image(patient_id: str, db: SessionLocal = Depends(get_db)):
|
| 1195 |
+
if not patient_id:
|
| 1196 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Patient ID parameter is required')
|
| 1197 |
+
|
| 1198 |
+
try:
|
| 1199 |
+
with db as session:
|
| 1200 |
+
query = text("SELECT image FROM wounds WHERE patient_id = :patient_id")
|
| 1201 |
+
result = session.execute(query, {'patient_id': patient_id}).fetchone()
|
| 1202 |
+
|
| 1203 |
+
if result and result.image:
|
| 1204 |
+
image_url = result.image
|
| 1205 |
+
filename = image_url.split('/')[-1]
|
| 1206 |
+
|
| 1207 |
+
file_path = os.path.join(UPLOAD_FOLDER, 'wounds', patient_id, filename)
|
| 1208 |
+
if os.path.exists(file_path):
|
| 1209 |
+
return FileResponse(file_path)
|
| 1210 |
+
else:
|
| 1211 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Image file not found on server')
|
| 1212 |
+
else:
|
| 1213 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Image not found for the given patient ID')
|
| 1214 |
+
except Exception as e:
|
| 1215 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 1216 |
+
|
| 1217 |
+
@app.post("/store_med_image")
|
| 1218 |
+
async def store_med_image(email: str = Form(...), image: UploadFile = File(...), db: SessionLocal = Depends(get_db)):
|
| 1219 |
+
if not allowed_file(image.filename):
|
| 1220 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid file type')
|
| 1221 |
+
|
| 1222 |
+
filename = secure_filename(image.filename)
|
| 1223 |
+
|
| 1224 |
+
try:
|
| 1225 |
+
med_practitioner_folder = os.path.join(UPLOAD_FOLDER, 'medical_practitioners', email)
|
| 1226 |
+
os.makedirs(med_practitioner_folder, exist_ok=True)
|
| 1227 |
+
|
| 1228 |
+
image_path = os.path.join(med_practitioner_folder, filename)
|
| 1229 |
+
with open(image_path, "wb") as buffer:
|
| 1230 |
+
buffer.write(await image.read())
|
| 1231 |
+
|
| 1232 |
+
image_url = f"{BASE_URL}/uploads/medical_practitioners/{email}/{filename}"
|
| 1233 |
+
|
| 1234 |
+
with db as session:
|
| 1235 |
+
query = text("UPDATE users SET profile_photo_path = :image_url WHERE email = :email")
|
| 1236 |
+
session.execute(query, {'image_url': image_url, 'email': email})
|
| 1237 |
+
session.commit()
|
| 1238 |
+
|
| 1239 |
+
return JSONResponse(content={'message': 'Image stored and path updated successfully', 'image_url': image_url}, status_code=status.HTTP_200_OK)
|
| 1240 |
+
except Exception as e:
|
| 1241 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 1242 |
+
|
| 1243 |
+
@app.get("/get_med_image")
|
| 1244 |
+
async def get_med_image(email: str, db: SessionLocal = Depends(get_db)):
|
| 1245 |
+
if not email:
|
| 1246 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Email parameter is required')
|
| 1247 |
+
|
| 1248 |
+
try:
|
| 1249 |
+
with db as session:
|
| 1250 |
+
query = text("SELECT profile_photo_path FROM users WHERE email = :email")
|
| 1251 |
+
result = session.execute(query, {'email': email}).fetchone()
|
| 1252 |
+
|
| 1253 |
+
if result and result.profile_photo_path:
|
| 1254 |
+
image_url = result.profile_photo_path
|
| 1255 |
+
filename = image_url.split('/')[-1]
|
| 1256 |
+
|
| 1257 |
+
file_path = os.path.join(UPLOAD_FOLDER, 'medical_practitioners', email, filename)
|
| 1258 |
+
if os.path.exists(file_path):
|
| 1259 |
+
return FileResponse(file_path)
|
| 1260 |
+
else:
|
| 1261 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Image file not found on server')
|
| 1262 |
+
else:
|
| 1263 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Image not found for the given email')
|
| 1264 |
+
except Exception as e:
|
| 1265 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 1266 |
+
|
| 1267 |
+
@app.post("/store_org_image")
|
| 1268 |
+
async def store_org_image(email: str = Form(...), image: UploadFile = File(...), db: SessionLocal = Depends(get_db)):
|
| 1269 |
+
if not allowed_file(image.filename):
|
| 1270 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid file type')
|
| 1271 |
+
|
| 1272 |
+
filename = secure_filename(image.filename)
|
| 1273 |
+
|
| 1274 |
+
try:
|
| 1275 |
+
org_folder = os.path.join(UPLOAD_FOLDER, 'organisations', email)
|
| 1276 |
+
os.makedirs(org_folder, exist_ok=True)
|
| 1277 |
+
|
| 1278 |
+
image_path = os.path.join(org_folder, filename)
|
| 1279 |
+
with open(image_path, "wb") as buffer:
|
| 1280 |
+
buffer.write(await image.read())
|
| 1281 |
+
|
| 1282 |
+
image_url = f"{BASE_URL}/uploads/organisations/{email}/{filename}"
|
| 1283 |
+
|
| 1284 |
+
with db as session:
|
| 1285 |
+
query = text("UPDATE organisations SET profile_photo_path = :image_url WHERE email = :email")
|
| 1286 |
+
session.execute(query, {'image_url': image_url, 'email': email})
|
| 1287 |
+
session.commit()
|
| 1288 |
+
|
| 1289 |
+
return JSONResponse(content={'message': 'Image stored and path updated successfully', 'image_url': image_url}, status_code=status.HTTP_200_OK)
|
| 1290 |
+
except Exception as e:
|
| 1291 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 1292 |
+
|
| 1293 |
+
@app.get("/get_org_image")
|
| 1294 |
+
async def get_org_image(email: str, db: SessionLocal = Depends(get_db)):
|
| 1295 |
+
if not email:
|
| 1296 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Email parameter is required')
|
| 1297 |
+
|
| 1298 |
+
try:
|
| 1299 |
+
with db as session:
|
| 1300 |
+
query = text("SELECT profile_photo_path FROM organisations WHERE email = :email")
|
| 1301 |
+
result = session.execute(query, {'email': email}).fetchone()
|
| 1302 |
+
|
| 1303 |
+
if result and result.profile_photo_path:
|
| 1304 |
+
image_url = result.profile_photo_path
|
| 1305 |
+
filename = image_url.split('/')[-1]
|
| 1306 |
+
|
| 1307 |
+
file_path = os.path.join(UPLOAD_FOLDER, 'organisations', email, filename)
|
| 1308 |
+
if os.path.exists(file_path):
|
| 1309 |
+
return FileResponse(file_path)
|
| 1310 |
+
else:
|
| 1311 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Image file not found on server')
|
| 1312 |
+
else:
|
| 1313 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Image not found for the given email')
|
| 1314 |
+
except Exception as e:
|
| 1315 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 1316 |
+
|
| 1317 |
+
@app.post("/save_notes")
|
| 1318 |
+
async def save_notes(request_data: SaveNotesRequest, db: SessionLocal = Depends(get_db)):
|
| 1319 |
+
patient_id = request_data.patient_id
|
| 1320 |
+
notes = request_data.notes
|
| 1321 |
+
|
| 1322 |
+
try:
|
| 1323 |
+
with db as session:
|
| 1324 |
+
query = text("UPDATE patients SET notes = :notes WHERE patient_id = :patient_id")
|
| 1325 |
+
session.execute(query, {'notes': notes, 'patient_id': patient_id})
|
| 1326 |
+
session.commit()
|
| 1327 |
+
|
| 1328 |
+
return JSONResponse(content={'message': 'Notes saved successfully'}, status_code=status.HTTP_200_OK)
|
| 1329 |
+
except Exception as e:
|
| 1330 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 1331 |
+
|
| 1332 |
+
@app.post("/total_appointments")
|
| 1333 |
+
async def total_appointments(request_data: TotalAppointmentsRequest, db: SessionLocal = Depends(get_db)):
|
| 1334 |
+
start_date_str = request_data.start_date
|
| 1335 |
+
end_date_str = request_data.end_date
|
| 1336 |
+
doctor = request_data.doctor
|
| 1337 |
+
|
| 1338 |
+
try:
|
| 1339 |
+
start_date = datetime.datetime.strptime(start_date_str, '%Y-%m-%d')
|
| 1340 |
+
end_date = datetime.datetime.strptime(end_date_str, '%Y-%m-%d')
|
| 1341 |
+
|
| 1342 |
+
with db as session:
|
| 1343 |
+
query = text("""
|
| 1344 |
+
SELECT DATE(scheduled_date) as date, COUNT(*) as total_appointments
|
| 1345 |
+
FROM patients
|
| 1346 |
+
WHERE scheduled_date BETWEEN :start_date AND :end_date
|
| 1347 |
+
AND doctor = :doctor
|
| 1348 |
+
GROUP BY DATE(scheduled_date)
|
| 1349 |
+
ORDER BY DATE(scheduled_date)
|
| 1350 |
+
""")
|
| 1351 |
+
|
| 1352 |
+
results = session.execute(query, {
|
| 1353 |
+
'start_date': start_date,
|
| 1354 |
+
'end_date': end_date,
|
| 1355 |
+
'doctor': doctor
|
| 1356 |
+
}).fetchall()
|
| 1357 |
+
|
| 1358 |
+
appointments_by_day = {}
|
| 1359 |
+
|
| 1360 |
+
for row in results:
|
| 1361 |
+
date_str = row.date.strftime('%Y-%m-%d')
|
| 1362 |
+
appointments_by_day[date_str] = row.total_appointments
|
| 1363 |
+
|
| 1364 |
+
current_date = start_date
|
| 1365 |
+
while current_date <= end_date:
|
| 1366 |
+
date_str = current_date.strftime('%Y-%m-%d')
|
| 1367 |
+
if date_str not in appointments_by_day:
|
| 1368 |
+
appointments_by_day[date_str] = 0
|
| 1369 |
+
current_date += datetime.timedelta(days=1)
|
| 1370 |
+
|
| 1371 |
+
return JSONResponse(content={'appointments_by_day': appointments_by_day}, status_code=status.HTTP_200_OK)
|
| 1372 |
+
|
| 1373 |
+
except Exception as e:
|
| 1374 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 1375 |
+
|
| 1376 |
+
@app.post("/admin_add_practitioner")
|
| 1377 |
+
async def admin_add_practitioner(request_data: AdminAddPractitionerRequest, db: SessionLocal = Depends(get_db)):
|
| 1378 |
+
name = request_data.name
|
| 1379 |
+
email = request_data.email
|
| 1380 |
+
c_code = request_data.c_code
|
| 1381 |
+
phone = request_data.phone
|
| 1382 |
+
org = request_data.org
|
| 1383 |
+
|
| 1384 |
+
try:
|
| 1385 |
+
with db as session:
|
| 1386 |
+
query = text("SELECT email FROM users WHERE email = :email")
|
| 1387 |
+
existing_email = session.execute(query, {'email': email}).fetchone()
|
| 1388 |
+
|
| 1389 |
+
query = text("SELECT phone FROM users WHERE phone = :phone")
|
| 1390 |
+
existing_phone = session.execute(query, {'phone': phone}).fetchone()
|
| 1391 |
+
|
| 1392 |
+
if existing_email:
|
| 1393 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Email already exists. Please login.')
|
| 1394 |
+
elif existing_phone:
|
| 1395 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Phone number already exists. Please use another phone number.')
|
| 1396 |
+
else:
|
| 1397 |
+
uuid_val = generate_session_id()
|
| 1398 |
+
license_key = generate_license_key()
|
| 1399 |
+
|
| 1400 |
+
query = text("INSERT INTO users (name, email, c_code, phone, uuid, licence_key, org) VALUES (:name, :email, :c_code, :phone, :uuid, :license_key, :org)")
|
| 1401 |
+
session.execute(query, {
|
| 1402 |
+
'name': name,
|
| 1403 |
+
'email': email,
|
| 1404 |
+
'c_code': c_code,
|
| 1405 |
+
'phone': phone,
|
| 1406 |
+
'uuid': uuid_val,
|
| 1407 |
+
'license_key': license_key,
|
| 1408 |
+
'org': org
|
| 1409 |
+
})
|
| 1410 |
+
session.commit()
|
| 1411 |
+
return JSONResponse(content={'message': 'Data added successfully'}, status_code=status.HTTP_200_OK)
|
| 1412 |
+
except Exception as e:
|
| 1413 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 1414 |
+
|
| 1415 |
+
@app.post("/update_org_profile")
|
| 1416 |
+
async def update_org_profile(request_data: UpdateOrgProfileRequest, db: SessionLocal = Depends(get_db)):
|
| 1417 |
+
name = request_data.name
|
| 1418 |
+
department = request_data.department
|
| 1419 |
+
about = request_data.about
|
| 1420 |
+
location = request_data.location
|
| 1421 |
+
latitude = request_data.latitude
|
| 1422 |
+
longitude = request_data.longitude
|
| 1423 |
+
email = request_data.email
|
| 1424 |
+
|
| 1425 |
+
if email is None:
|
| 1426 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Email is required')
|
| 1427 |
+
|
| 1428 |
+
try:
|
| 1429 |
+
with db as session:
|
| 1430 |
+
query = text("SELECT email FROM organisations WHERE email = :email")
|
| 1431 |
+
existing_org = session.execute(query, {'email': email}).fetchone()
|
| 1432 |
+
|
| 1433 |
+
if not existing_org:
|
| 1434 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Organisation not found')
|
| 1435 |
+
|
| 1436 |
+
update_query = text("""
|
| 1437 |
+
UPDATE organisations
|
| 1438 |
+
SET name = :name,
|
| 1439 |
+
departments = :department,
|
| 1440 |
+
about = :about,
|
| 1441 |
+
location = :location,
|
| 1442 |
+
latitude = :latitude,
|
| 1443 |
+
longitude = :longitude
|
| 1444 |
+
WHERE email = :email
|
| 1445 |
+
""")
|
| 1446 |
+
session.execute(update_query, {
|
| 1447 |
+
'name': name,
|
| 1448 |
+
'department': department,
|
| 1449 |
+
'about': about,
|
| 1450 |
+
'location': location,
|
| 1451 |
+
'latitude': latitude,
|
| 1452 |
+
'longitude': longitude,
|
| 1453 |
+
'email': email
|
| 1454 |
+
})
|
| 1455 |
+
session.commit()
|
| 1456 |
+
return JSONResponse(content={'message': 'Organisation profile updated successfully'}, status_code=status.HTTP_200_OK)
|
| 1457 |
+
except Exception as e:
|
| 1458 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 1459 |
+
|
| 1460 |
+
@app.post("/update_med_profile")
|
| 1461 |
+
async def update_med_profile(request_data: UpdateMedProfileRequest, db: SessionLocal = Depends(get_db)):
|
| 1462 |
+
name = request_data.name
|
| 1463 |
+
department = request_data.department
|
| 1464 |
+
about = request_data.about
|
| 1465 |
+
location = request_data.location
|
| 1466 |
+
latitude = request_data.latitude
|
| 1467 |
+
longitude = request_data.longitude
|
| 1468 |
+
email = request_data.email
|
| 1469 |
+
|
| 1470 |
+
if email is None:
|
| 1471 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Email is required')
|
| 1472 |
+
|
| 1473 |
+
try:
|
| 1474 |
+
with db as session:
|
| 1475 |
+
query = text("SELECT email FROM users WHERE email = :email")
|
| 1476 |
+
existing_user = session.execute(query, {'email': email}).fetchone()
|
| 1477 |
+
|
| 1478 |
+
if not existing_user:
|
| 1479 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Medical user not found')
|
| 1480 |
+
|
| 1481 |
+
update_query = text("""
|
| 1482 |
+
UPDATE users
|
| 1483 |
+
SET name = :name,
|
| 1484 |
+
departments = :department,
|
| 1485 |
+
about = :about,
|
| 1486 |
+
location = :location,
|
| 1487 |
+
latitude = :latitude,
|
| 1488 |
+
longitude = :longitude
|
| 1489 |
+
WHERE email = :email
|
| 1490 |
+
""")
|
| 1491 |
+
session.execute(update_query, {
|
| 1492 |
+
'name': name,
|
| 1493 |
+
'department': department,
|
| 1494 |
+
'about': about,
|
| 1495 |
+
'location': location,
|
| 1496 |
+
'latitude': latitude,
|
| 1497 |
+
'longitude': longitude,
|
| 1498 |
+
'email': email
|
| 1499 |
+
})
|
| 1500 |
+
session.commit()
|
| 1501 |
+
return JSONResponse(content={'message': 'Medical profile updated successfully'}, status_code=status.HTTP_200_OK)
|
| 1502 |
+
except Exception as e:
|
| 1503 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 1504 |
+
|
| 1505 |
+
@app.post("/add_wound_details_v2")
|
| 1506 |
+
async def add_wound_details_v2(request_data: AddWoundDetailsV2Request = Depends(), image: UploadFile = File(...), db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
|
| 1507 |
+
length = request_data.length
|
| 1508 |
+
breadth = request_data.breadth
|
| 1509 |
+
depth = request_data.depth
|
| 1510 |
+
area = request_data.area
|
| 1511 |
+
moisture = request_data.moisture
|
| 1512 |
+
wound_location = request_data.wound_location
|
| 1513 |
+
tissue = request_data.tissue
|
| 1514 |
+
exudate = request_data.exudate
|
| 1515 |
+
periwound = request_data.periwound
|
| 1516 |
+
periwound_type = request_data.periwound_type
|
| 1517 |
+
patient_id = request_data.patient_id
|
| 1518 |
+
type = request_data.type
|
| 1519 |
+
category = request_data.category
|
| 1520 |
+
edge = request_data.edge
|
| 1521 |
+
infection = request_data.infection
|
| 1522 |
+
last_dressing_date = request_data.last_dressing_date
|
| 1523 |
+
|
| 1524 |
+
updated_at = datetime.datetime.utcnow()
|
| 1525 |
+
|
| 1526 |
+
if not allowed_file(image.filename):
|
| 1527 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid file type')
|
| 1528 |
+
|
| 1529 |
+
filename = secure_filename(image.filename)
|
| 1530 |
+
|
| 1531 |
+
try:
|
| 1532 |
+
with db as session:
|
| 1533 |
+
query = text("SELECT area, id, created_at FROM wounds WHERE patient_id = :patient_id")
|
| 1534 |
+
result = session.execute(query, {'patient_id': patient_id}).fetchone()
|
| 1535 |
+
|
| 1536 |
+
if not result:
|
| 1537 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Patient not found')
|
| 1538 |
+
|
| 1539 |
+
existing_area = result[0]
|
| 1540 |
+
if not existing_area:
|
| 1541 |
+
existing_area = 0
|
| 1542 |
+
wound_id = result[1]
|
| 1543 |
+
created_at = result[2]
|
| 1544 |
+
area_difference = float(area) - float(existing_area)
|
| 1545 |
+
if area_difference > 0:
|
| 1546 |
+
size_variation = 'wound area increased'
|
| 1547 |
+
elif area_difference < 0:
|
| 1548 |
+
size_variation = 'wound area reduced'
|
| 1549 |
+
else:
|
| 1550 |
+
size_variation = 'wound area same'
|
| 1551 |
+
|
| 1552 |
+
patient_folder = os.path.join(UPLOAD_FOLDER, 'wounds', patient_id)
|
| 1553 |
+
os.makedirs(patient_folder, exist_ok=True)
|
| 1554 |
+
image_path = os.path.join(patient_folder, filename)
|
| 1555 |
+
with open(image_path, "wb") as buffer:
|
| 1556 |
+
buffer.write(await image.read())
|
| 1557 |
+
|
| 1558 |
+
image_url = f"{BASE_URL}/uploads/wounds/{patient_id}/{filename}"
|
| 1559 |
+
|
| 1560 |
+
query1 = text("""
|
| 1561 |
+
INSERT INTO wound_images (depth, width, height, uuid, updated_at, patient_id, size_variation, image, wound_id, created_at, area)
|
| 1562 |
+
VALUES (:depth, :breadth, :length, :uuid, :updated_at, :patient_id, :size_variation, :image_url, :wound_id, :created_at, :area)
|
| 1563 |
+
""")
|
| 1564 |
+
session.execute(query1, {
|
| 1565 |
+
'depth': depth, 'breadth': breadth, 'length': length, 'uuid': generate_session_id(),
|
| 1566 |
+
'updated_at': updated_at, 'patient_id': patient_id, 'size_variation': size_variation,
|
| 1567 |
+
'image_url': image_url, 'wound_id': wound_id, 'created_at': created_at, 'area': area
|
| 1568 |
+
})
|
| 1569 |
+
|
| 1570 |
+
query2 = text("""
|
| 1571 |
+
UPDATE wounds SET height = :length, width = :breadth, depth = :depth, area = :area,
|
| 1572 |
+
moisture = :moisture, position = :wound_location, tissue = :tissue, exudate = :exudate,
|
| 1573 |
+
periwound = :periwound, periwound_type = :periwound_type, type = :type, category = :category,
|
| 1574 |
+
edges = :edge, infection = :infection, updated_at = :updated_at, last_dressing = :last_dressing_date,
|
| 1575 |
+
image = :image_url
|
| 1576 |
+
WHERE patient_id = :patient_id
|
| 1577 |
+
""")
|
| 1578 |
+
session.execute(query2, {
|
| 1579 |
+
'length': length, 'breadth': breadth, 'depth': depth, 'area': area,
|
| 1580 |
+
'moisture': moisture, 'wound_location': wound_location, 'tissue': tissue,
|
| 1581 |
+
'exudate': exudate, 'periwound': periwound, 'periwound_type': periwound_type,
|
| 1582 |
+
'type': type, 'category': category, 'edge': edge, 'infection': infection,
|
| 1583 |
+
'updated_at': updated_at, 'last_dressing_date': last_dressing_date, 'patient_id': patient_id,
|
| 1584 |
+
'image_url': image_url
|
| 1585 |
+
})
|
| 1586 |
+
|
| 1587 |
+
session.commit()
|
| 1588 |
+
return JSONResponse(content={
|
| 1589 |
+
'message': 'Wound details and image stored successfully',
|
| 1590 |
+
'image_url': image_url
|
| 1591 |
+
}, status_code=status.HTTP_200_OK)
|
| 1592 |
+
|
| 1593 |
+
except Exception as e:
|
| 1594 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 1595 |
+
|
| 1596 |
+
@app.get("/wound_progress_timeline")
|
| 1597 |
+
async def get_wound_details_v2(patient_id: str, db: SessionLocal = Depends(get_db)):
|
| 1598 |
+
if not patient_id:
|
| 1599 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='patient_id is required')
|
| 1600 |
+
|
| 1601 |
+
try:
|
| 1602 |
+
with db as session:
|
| 1603 |
+
query = text("""
|
| 1604 |
+
SELECT * FROM wound_images WHERE patient_id = :patient_id ORDER BY updated_at DESC """)
|
| 1605 |
+
results = session.execute(query, {'patient_id': patient_id}).fetchall()
|
| 1606 |
+
|
| 1607 |
+
wound_details = []
|
| 1608 |
+
for row in results:
|
| 1609 |
+
wound_details.append({
|
| 1610 |
+
'length': row.height,
|
| 1611 |
+
'breadth': row.width,
|
| 1612 |
+
'depth': row.depth,
|
| 1613 |
+
'size_variation': row.size_variation,
|
| 1614 |
+
'image': row.image,
|
| 1615 |
+
'patient_id': row.patient_id,
|
| 1616 |
+
'area': row.area,
|
| 1617 |
+
'updated_at': row.updated_at.isoformat()
|
| 1618 |
+
})
|
| 1619 |
+
|
| 1620 |
+
return JSONResponse(content=wound_details, status_code=status.HTTP_200_OK)
|
| 1621 |
+
|
| 1622 |
+
except Exception as e:
|
| 1623 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 1624 |
+
|
| 1625 |
+
@app.post("/med/forgot/pin/otp")
|
| 1626 |
+
async def med_forgot_pin_otp(request_data: SendOtpRequest, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
|
| 1627 |
+
phone = request_data.phone
|
| 1628 |
+
|
| 1629 |
+
try:
|
| 1630 |
+
with db as session:
|
| 1631 |
+
query = text("SELECT * FROM users WHERE phone = :phone")
|
| 1632 |
+
user = session.execute(query, {'phone': phone}).fetchone()
|
| 1633 |
+
|
| 1634 |
+
if user:
|
| 1635 |
+
phone_with_code = user.c_code + user.phone
|
| 1636 |
+
otp = "1234" # generate_otp()
|
| 1637 |
+
# send_sms(phone_with_code, otp)
|
| 1638 |
+
|
| 1639 |
+
updated_at = datetime.datetime.utcnow()
|
| 1640 |
+
expiry_time = datetime.datetime.utcnow() + datetime.timedelta(minutes=5)
|
| 1641 |
+
update_otp_in_med_database(session, phone, otp, expiry_time, updated_at)
|
| 1642 |
+
|
| 1643 |
+
return JSONResponse(content={'status': 200, 'message': 'OTP Sent on mobile.', 'otp': otp}, status_code=status.HTTP_200_OK)
|
| 1644 |
+
else:
|
| 1645 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='OOPS! Phone Does Not Exist!')
|
| 1646 |
+
except Exception as e:
|
| 1647 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 1648 |
+
|
| 1649 |
+
@app.post("/org/forgot/pin/otp")
|
| 1650 |
+
async def org_forgot_pin_otp(request_data: SendOtpRequest, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
|
| 1651 |
+
phone = request_data.phone
|
| 1652 |
+
|
| 1653 |
+
try:
|
| 1654 |
+
with db as session:
|
| 1655 |
+
query = text("SELECT * FROM organisations WHERE phone = :phone")
|
| 1656 |
+
user = session.execute(query, {'phone': phone}).fetchone()
|
| 1657 |
+
|
| 1658 |
+
if user:
|
| 1659 |
+
phone_with_code = user.c_code + user.phone
|
| 1660 |
+
otp = "1234" # generate_otp()
|
| 1661 |
+
# send_sms(phone_with_code, otp)
|
| 1662 |
+
|
| 1663 |
+
updated_at = datetime.datetime.utcnow()
|
| 1664 |
+
expiry_time = datetime.datetime.utcnow() + datetime.timedelta(minutes=5)
|
| 1665 |
+
update_otp_in_database(session, phone, otp, expiry_time, updated_at)
|
| 1666 |
+
|
| 1667 |
+
return JSONResponse(content={'status': 200, 'message': 'OTP Sent on mobile.', 'otp': otp}, status_code=status.HTTP_200_OK)
|
| 1668 |
+
else:
|
| 1669 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='OOPS! Phone Does Not Exist!')
|
| 1670 |
+
except Exception as e:
|
| 1671 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 1672 |
+
|
| 1673 |
+
@app.post("/update_scheduled_date_v2")
|
| 1674 |
+
async def update_scheduled_date_v2(request_data: UpdateScheduledDateRequest, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
|
| 1675 |
+
email = request_data.email
|
| 1676 |
+
patient_id = request_data.patient_id
|
| 1677 |
+
scheduled_date = request_data.scheduled_date
|
| 1678 |
+
|
| 1679 |
+
try:
|
| 1680 |
+
with db as session:
|
| 1681 |
+
query = text("UPDATE patients SET scheduled_date = :scheduled_date WHERE email = :email AND patient_id = :patient_id")
|
| 1682 |
+
result = session.execute(query, {'scheduled_date': scheduled_date, 'email': email, 'patient_id': patient_id})
|
| 1683 |
+
session.commit()
|
| 1684 |
+
|
| 1685 |
+
if result.rowcount == 0:
|
| 1686 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='No matching record found')
|
| 1687 |
+
|
| 1688 |
+
return JSONResponse(content={'message': 'Scheduled date updated successfully'}, status_code=status.HTTP_200_OK)
|
| 1689 |
+
except Exception as e:
|
| 1690 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 1691 |
+
|
| 1692 |
+
@app.get("/total_appointments_v2")
|
| 1693 |
+
async def total_appointments_v2(start_date: str, end_date: str, email: str, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
|
| 1694 |
+
try:
|
| 1695 |
+
start_date_dt = datetime.datetime.strptime(start_date, '%Y-%m-%d')
|
| 1696 |
+
end_date_dt = datetime.datetime.strptime(end_date, '%Y-%m-%d')
|
| 1697 |
+
|
| 1698 |
+
with db as session:
|
| 1699 |
+
query = text("""
|
| 1700 |
+
SELECT DATE(scheduled_date) as date, COUNT(*) as total_appointments
|
| 1701 |
+
FROM patients
|
| 1702 |
+
WHERE scheduled_date BETWEEN :start_date AND :end_date
|
| 1703 |
+
AND email = :email
|
| 1704 |
+
GROUP BY DATE(scheduled_date)
|
| 1705 |
+
ORDER BY DATE(scheduled_date)
|
| 1706 |
+
""")
|
| 1707 |
+
|
| 1708 |
+
results = session.execute(query, {
|
| 1709 |
+
'start_date': start_date_dt,
|
| 1710 |
+
'end_date': end_date_dt,
|
| 1711 |
+
'email': email
|
| 1712 |
+
}).fetchall()
|
| 1713 |
+
|
| 1714 |
+
appointments_by_day = {}
|
| 1715 |
+
|
| 1716 |
+
for row in results:
|
| 1717 |
+
date_str = row.date.strftime('%Y-%m-%d')
|
| 1718 |
+
appointments_by_day[date_str] = row.total_appointments
|
| 1719 |
+
|
| 1720 |
+
current_date = start_date_dt
|
| 1721 |
+
while current_date <= end_date_dt:
|
| 1722 |
+
date_str = current_date.strftime('%Y-%m-%d')
|
| 1723 |
+
if date_str not in appointments_by_day:
|
| 1724 |
+
appointments_by_day[date_str] = 0
|
| 1725 |
+
current_date += datetime.timedelta(days=1)
|
| 1726 |
+
|
| 1727 |
+
return JSONResponse(content={'appointments_by_day': appointments_by_day}, status_code=status.HTTP_200_OK)
|
| 1728 |
+
|
| 1729 |
+
except Exception as e:
|
| 1730 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 1731 |
+
|
| 1732 |
+
@app.post("/add_patient_v2")
|
| 1733 |
+
async def add_patient_v2(request_data: AddPatientV2Request, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
|
| 1734 |
+
name = request_data.name
|
| 1735 |
+
dob = request_data.dob
|
| 1736 |
+
gender = request_data.gender
|
| 1737 |
+
age = request_data.age
|
| 1738 |
+
height = request_data.height
|
| 1739 |
+
weight = request_data.weight
|
| 1740 |
+
email = request_data.email
|
| 1741 |
+
doctor = request_data.doctor
|
| 1742 |
+
role = request_data.role
|
| 1743 |
+
patient_id = generate_patient_id()
|
| 1744 |
+
|
| 1745 |
+
created_at = datetime.datetime.utcnow()
|
| 1746 |
+
updated_at = datetime.datetime.utcnow()
|
| 1747 |
+
scheduled_date = datetime.datetime.utcnow()
|
| 1748 |
+
|
| 1749 |
+
try:
|
| 1750 |
+
with db as session:
|
| 1751 |
+
uuid_val = generate_session_id()
|
| 1752 |
+
if role == "3":
|
| 1753 |
+
user_query = text("SELECT id FROM users WHERE email = :email AND name = :doctor")
|
| 1754 |
+
user_result = session.execute(user_query, {'email': email, 'doctor': doctor}).fetchone()
|
| 1755 |
+
if not user_result:
|
| 1756 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Doctor not found')
|
| 1757 |
+
doctor_id = user_result.id
|
| 1758 |
+
|
| 1759 |
+
pat_query = text("INSERT INTO patients (name, dob, gender, age, height, weight, email, doctor, added_by, uuid, patient_id, created_at, updated_at, scheduled_date) VALUES (:name, :dob, :gender, :age, :height, :weight, :email, :doctor_id, :doctor, :uuid, :patient_id, :created_at, :updated_at, :scheduled_date)")
|
| 1760 |
+
session.execute(pat_query, {'name': name, 'dob': dob, 'gender': gender, 'age': age, 'height': height, 'weight': weight, 'email': email, 'doctor_id': doctor_id, 'doctor': doctor, 'uuid': uuid_val, 'patient_id': patient_id, 'created_at': created_at, 'updated_at': updated_at, 'scheduled_date': scheduled_date})
|
| 1761 |
+
|
| 1762 |
+
wound_query = text("INSERT INTO wounds (uuid, patient_id, created_at, updated_at) VALUES (:uuid, :patient_id, :created_at, :updated_at)")
|
| 1763 |
+
session.execute(wound_query, {'uuid': uuid_val, 'patient_id': patient_id, 'created_at': created_at, 'updated_at': updated_at})
|
| 1764 |
+
|
| 1765 |
+
elif role == "5":
|
| 1766 |
+
org_query = text("SELECT id FROM organisations WHERE email = :email AND name = :doctor")
|
| 1767 |
+
org_result = session.execute(org_query, {'email': email, 'doctor': doctor}).fetchone()
|
| 1768 |
+
if not org_result:
|
| 1769 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Organisation not found')
|
| 1770 |
+
org_id = org_result.id
|
| 1771 |
+
|
| 1772 |
+
pat_query = text("INSERT INTO patients (name, dob, gender, age, height, weight, email, org, added_by, uuid, patient_id, created_at, updated_at, scheduled_date) VALUES (:name, :dob, :gender, :age, :height, :weight, :email, :org_id, :doctor, :uuid, :patient_id, :created_at, :updated_at, :scheduled_date)")
|
| 1773 |
+
session.execute(pat_query, {'name': name, 'dob': dob, 'gender': gender, 'age': age, 'height': height, 'weight': weight, 'email': email, 'org_id': org_id, 'doctor': doctor, 'uuid': uuid_val, 'patient_id': patient_id, 'created_at': created_at, 'updated_at': updated_at, 'scheduled_date': scheduled_date})
|
| 1774 |
+
|
| 1775 |
+
wound_query = text("INSERT INTO wounds (uuid, patient_id, created_at, updated_at) VALUES (:uuid, :patient_id, :created_at, :updated_at)")
|
| 1776 |
+
session.execute(wound_query, {'uuid': uuid_val, 'patient_id': patient_id, 'created_at': created_at, 'updated_at': updated_at})
|
| 1777 |
+
|
| 1778 |
+
else:
|
| 1779 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid role')
|
| 1780 |
+
|
| 1781 |
+
session.commit()
|
| 1782 |
+
|
| 1783 |
+
return JSONResponse(content={'message': 'Patient added successfully', 'patient_id': patient_id}, status_code=status.HTTP_200_OK)
|
| 1784 |
+
except Exception as e:
|
| 1785 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 1786 |
+
|
| 1787 |
+
@app.post("/save_notes_v2")
|
| 1788 |
+
async def save_notes_v2(request_data: SaveNotesV2Request, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
|
| 1789 |
+
patient_id = request_data.patient_id
|
| 1790 |
+
notes = request_data.notes
|
| 1791 |
+
remarks = request_data.remarks
|
| 1792 |
+
|
| 1793 |
+
try:
|
| 1794 |
+
with db as session:
|
| 1795 |
+
query = text("UPDATE patients SET notes = :notes, remarks = :remarks WHERE patient_id = :patient_id")
|
| 1796 |
+
session.execute(query, {'notes': notes, 'remarks': remarks, 'patient_id': patient_id})
|
| 1797 |
+
session.commit()
|
| 1798 |
+
|
| 1799 |
+
return JSONResponse(content={'message': 'Notes saved successfully'}, status_code=status.HTTP_200_OK)
|
| 1800 |
+
except Exception as e:
|
| 1801 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 1802 |
+
|
| 1803 |
+
@app.post("/admin_add_practitioner_v2")
|
| 1804 |
+
async def admin_add_practitioner_v2(request_data: AdminAddPractitionerV2Request, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
|
| 1805 |
+
name = request_data.name
|
| 1806 |
+
email = request_data.email
|
| 1807 |
+
c_code = request_data.c_code
|
| 1808 |
+
phone = request_data.phone
|
| 1809 |
+
org_email = request_data.org_email
|
| 1810 |
+
created_at = datetime.datetime.utcnow()
|
| 1811 |
+
updated_at = datetime.datetime.utcnow()
|
| 1812 |
+
|
| 1813 |
+
try:
|
| 1814 |
+
with db as session:
|
| 1815 |
+
query = text("SELECT email FROM users WHERE email = :email")
|
| 1816 |
+
existing_email = session.execute(query, {'email': email}).fetchone()
|
| 1817 |
+
|
| 1818 |
+
query = text("SELECT phone FROM users WHERE phone = :phone")
|
| 1819 |
+
existing_phone = session.execute(query, {'phone': phone}).fetchone()
|
| 1820 |
+
|
| 1821 |
+
if existing_email:
|
| 1822 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Email already exists. Please login.')
|
| 1823 |
+
elif existing_phone:
|
| 1824 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Phone number already exists. Please use another phone number.')
|
| 1825 |
+
else:
|
| 1826 |
+
org_query = text("SELECT id FROM organisations WHERE email = :org_email")
|
| 1827 |
+
org_result = session.execute(org_query, {'org_email': org_email}).fetchone()
|
| 1828 |
+
if not org_result:
|
| 1829 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Organisation not found')
|
| 1830 |
+
org_id = org_result.id
|
| 1831 |
+
uuid_val = generate_session_id()
|
| 1832 |
+
license_key = generate_license_key()
|
| 1833 |
+
|
| 1834 |
+
query = text("INSERT INTO users (name, email, c_code, phone, uuid, licence_key, org, updated_at, created_at) VALUES (:name, :email, :c_code, :phone, :uuid, :license_key, :org_id, :updated_at, :created_at)")
|
| 1835 |
+
session.execute(query, {
|
| 1836 |
+
'name': name,
|
| 1837 |
+
'email': email,
|
| 1838 |
+
'c_code': c_code,
|
| 1839 |
+
'phone': phone,
|
| 1840 |
+
'uuid': uuid_val,
|
| 1841 |
+
'license_key': license_key,
|
| 1842 |
+
'org_id': org_id,
|
| 1843 |
+
'updated_at': updated_at,
|
| 1844 |
+
'created_at': created_at
|
| 1845 |
+
})
|
| 1846 |
+
session.commit()
|
| 1847 |
+
return JSONResponse(content={'message': 'Data added successfully'}, status_code=status.HTTP_200_OK)
|
| 1848 |
+
except Exception as e:
|
| 1849 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 1850 |
+
|
| 1851 |
+
@app.post("/sort_patients")
|
| 1852 |
+
async def sort_patients(request_data: SortPatientsRequest, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
|
| 1853 |
+
email = request_data.email
|
| 1854 |
+
date_str = request_data.date
|
| 1855 |
+
|
| 1856 |
+
try:
|
| 1857 |
+
with db as session:
|
| 1858 |
+
if date_str:
|
| 1859 |
+
appointment_date = datetime.datetime.strptime(date_str, '%Y-%m-%d').date()
|
| 1860 |
+
query = text("""
|
| 1861 |
+
SELECT * FROM patients WHERE scheduled_date = :appointment_date AND email = :email
|
| 1862 |
+
""")
|
| 1863 |
+
results = session.execute(query, {'appointment_date': appointment_date, 'email': email}).fetchall()
|
| 1864 |
+
else:
|
| 1865 |
+
query = text("""
|
| 1866 |
+
SELECT * FROM patients WHERE email = :email
|
| 1867 |
+
""")
|
| 1868 |
+
results = session.execute(query, {'email': email}).fetchall()
|
| 1869 |
+
|
| 1870 |
+
patient_dicts = [dict(row._mapping) for row in results]
|
| 1871 |
+
return JSONResponse(content={'patients': patient_dicts}, status_code=status.HTTP_200_OK)
|
| 1872 |
+
except Exception as e:
|
| 1873 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 1874 |
+
|
| 1875 |
+
@app.post("/get_appointment_count")
|
| 1876 |
+
async def get_appointment_count(request_data: GetAppointmentCountRequest, db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
|
| 1877 |
+
email = request_data.email
|
| 1878 |
+
date_str = request_data.date
|
| 1879 |
+
|
| 1880 |
+
try:
|
| 1881 |
+
appointment_date = datetime.datetime.strptime(date_str, '%Y-%m-%d').date()
|
| 1882 |
+
with db as session:
|
| 1883 |
+
query = text("""
|
| 1884 |
+
SELECT COUNT(*) as count FROM patients WHERE scheduled_date = :appointment_date AND email = :email
|
| 1885 |
+
""")
|
| 1886 |
+
result = session.execute(query, {'appointment_date': appointment_date, 'email': email}).fetchone()
|
| 1887 |
+
|
| 1888 |
+
total_appointments = result.count if result else 0
|
| 1889 |
+
|
| 1890 |
+
return JSONResponse(content={
|
| 1891 |
+
'title': f'The total number of appointments booked is {total_appointments}',
|
| 1892 |
+
'description': f'The appointments are scheduled for {appointment_date}'
|
| 1893 |
+
}, status_code=status.HTTP_200_OK)
|
| 1894 |
+
except Exception as e:
|
| 1895 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 1896 |
+
|
| 1897 |
+
@app.post("/add_wound_details_v3")
|
| 1898 |
+
async def add_wound_details_v3(request_data: AddWoundDetailsV3Request = Depends(), image: UploadFile = File(..., alias="image"), api_image: UploadFile = File(..., alias="api_image"), db: SessionLocal = Depends(get_db), payload: dict = Depends(verify_jwt_token)):
|
| 1899 |
+
length = request_data.length
|
| 1900 |
+
breadth = request_data.breadth
|
| 1901 |
+
depth = request_data.depth
|
| 1902 |
+
area = request_data.area
|
| 1903 |
+
moisture = request_data.moisture
|
| 1904 |
+
wound_location = request_data.wound_location
|
| 1905 |
+
tissue = request_data.tissue
|
| 1906 |
+
exudate = request_data.exudate
|
| 1907 |
+
periwound = request_data.periwound
|
| 1908 |
+
periwound_type = request_data.periwound_type
|
| 1909 |
+
patient_id = request_data.patient_id
|
| 1910 |
+
type = request_data.type
|
| 1911 |
+
category = request_data.category
|
| 1912 |
+
edge = request_data.edge
|
| 1913 |
+
infection = request_data.infection
|
| 1914 |
+
last_dressing_date = request_data.last_dressing_date
|
| 1915 |
+
|
| 1916 |
+
updated_at = datetime.datetime.utcnow()
|
| 1917 |
+
|
| 1918 |
+
if not allowed_file(image.filename) or not allowed_file(api_image.filename):
|
| 1919 |
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail='Invalid file type for one or both images')
|
| 1920 |
+
|
| 1921 |
+
normal_image_filename = secure_filename(image.filename)
|
| 1922 |
+
api_image_filename = secure_filename(api_image.filename)
|
| 1923 |
+
|
| 1924 |
+
try:
|
| 1925 |
+
with db as session:
|
| 1926 |
+
query = text("SELECT area, id, created_at FROM wounds WHERE patient_id = :patient_id")
|
| 1927 |
+
result = session.execute(query, {'patient_id': patient_id}).fetchone()
|
| 1928 |
+
|
| 1929 |
+
if not result:
|
| 1930 |
+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail='Patient not found')
|
| 1931 |
+
|
| 1932 |
+
existing_area = result[0]
|
| 1933 |
+
if not existing_area:
|
| 1934 |
+
existing_area = 0
|
| 1935 |
+
wound_id = result[1]
|
| 1936 |
+
created_at = result[2]
|
| 1937 |
+
area_difference = float(area) - float(existing_area)
|
| 1938 |
+
if area_difference > 0:
|
| 1939 |
+
size_variation = 'wound area increased'
|
| 1940 |
+
elif area_difference < 0:
|
| 1941 |
+
size_variation = 'wound area reduced'
|
| 1942 |
+
else:
|
| 1943 |
+
size_variation = 'wound area same'
|
| 1944 |
+
|
| 1945 |
+
patient_folder = os.path.join(UPLOAD_FOLDER, 'wounds', patient_id)
|
| 1946 |
+
os.makedirs(patient_folder, exist_ok=True)
|
| 1947 |
+
normal_image_path = os.path.join(patient_folder, normal_image_filename)
|
| 1948 |
+
with open(normal_image_path, "wb") as buffer:
|
| 1949 |
+
buffer.write(await image.read())
|
| 1950 |
+
|
| 1951 |
+
api_patient_folder = os.path.join(UPLOAD_FOLDER, 'assessed_wounds', patient_id)
|
| 1952 |
+
os.makedirs(api_patient_folder, exist_ok=True)
|
| 1953 |
+
api_image_path = os.path.join(api_patient_folder, api_image_filename)
|
| 1954 |
+
with open(api_image_path, "wb") as buffer:
|
| 1955 |
+
buffer.write(await api_image.read())
|
| 1956 |
+
|
| 1957 |
+
image_url = f"{BASE_URL}/uploads/wounds/{patient_id}/{normal_image_filename}"
|
| 1958 |
+
api_image_url = f"{BASE_URL}/uploads/assessed_wounds/{patient_id}/{api_image_filename}"
|
| 1959 |
+
|
| 1960 |
+
query1 = text("""
|
| 1961 |
+
INSERT INTO wound_images (depth, width, height, uuid, updated_at, patient_id, size_variation, image, wound_id, created_at, area, photo_assessment)
|
| 1962 |
+
VALUES (:depth, :breadth, :length, :uuid, :updated_at, :patient_id, :size_variation, :image_url, :wound_id, :created_at, :area, :api_image_url)
|
| 1963 |
+
""")
|
| 1964 |
+
session.execute(query1, {
|
| 1965 |
+
'depth': depth, 'breadth': breadth, 'length': length, 'uuid': generate_session_id(),
|
| 1966 |
+
'updated_at': updated_at, 'patient_id': patient_id, 'size_variation': size_variation,
|
| 1967 |
+
'image_url': image_url, 'wound_id': wound_id, 'created_at': created_at, 'area': area,
|
| 1968 |
+
'api_image_url': api_image_url
|
| 1969 |
+
})
|
| 1970 |
+
|
| 1971 |
+
query2 = text("""
|
| 1972 |
+
UPDATE wounds SET height = :length, width = :breadth, depth = :depth, area = :area,
|
| 1973 |
+
moisture = :moisture, position = :wound_location, tissue = :tissue, exudate = :exudate,
|
| 1974 |
+
periwound = :periwound, periwound_type = :periwound_type, type = :type, category = :category,
|
| 1975 |
+
edges = :edge, infection = :infection, updated_at = :updated_at, last_dressing = :last_dressing_date,
|
| 1976 |
+
image = :image_url, photo_assessment = :api_image_url
|
| 1977 |
+
WHERE patient_id = :patient_id
|
| 1978 |
+
""")
|
| 1979 |
+
session.execute(query2, {
|
| 1980 |
+
'length': length, 'breadth': breadth, 'depth': depth, 'area': area,
|
| 1981 |
+
'moisture': moisture, 'wound_location': wound_location, 'tissue': tissue,
|
| 1982 |
+
'exudate': exudate, 'periwound': periwound, 'periwound_type': periwound_type,
|
| 1983 |
+
'type': type, 'category': category, 'edge': edge, 'infection': infection,
|
| 1984 |
+
'updated_at': updated_at, 'last_dressing_date': last_dressing_date, 'patient_id': patient_id,
|
| 1985 |
+
'image_url': image_url, 'api_image_url': api_image_url
|
| 1986 |
+
})
|
| 1987 |
+
|
| 1988 |
+
session.commit()
|
| 1989 |
+
return JSONResponse(content={
|
| 1990 |
+
'message': 'Wound details and images stored successfully',
|
| 1991 |
+
'image_url': image_url,
|
| 1992 |
+
'photo_assessment': api_image_url
|
| 1993 |
+
}, status_code=status.HTTP_200_OK)
|
| 1994 |
+
|
| 1995 |
+
except Exception as e:
|
| 1996 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
| 1997 |
+
|
| 1998 |
+
|
| 1999 |
+
|
| 2000 |
+
|
requirements.txt
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi==0.104.1
|
| 2 |
+
uvicorn[standard]==0.24.0
|
| 3 |
+
sqlalchemy==2.0.23
|
| 4 |
+
pymysql==1.1.0
|
| 5 |
+
python-jose[cryptography]==3.3.0
|
| 6 |
+
python-multipart==0.0.6
|
| 7 |
+
python-dotenv==1.0.0
|
| 8 |
+
twilio==8.10.3
|
| 9 |
+
requests==2.31.0
|
| 10 |
+
werkzeug==3.0.1
|
| 11 |
+
pydantic==2.5.0
|
| 12 |
+
|
test_app.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Test script to validate the FastAPI application
|
| 4 |
+
"""
|
| 5 |
+
import sys
|
| 6 |
+
import os
|
| 7 |
+
|
| 8 |
+
# Add the current directory to Python path
|
| 9 |
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
| 10 |
+
|
| 11 |
+
def test_import():
|
| 12 |
+
"""Test if the main module can be imported without errors"""
|
| 13 |
+
try:
|
| 14 |
+
from main import app
|
| 15 |
+
print("β
Successfully imported FastAPI app")
|
| 16 |
+
return True
|
| 17 |
+
except Exception as e:
|
| 18 |
+
print(f"β Failed to import FastAPI app: {e}")
|
| 19 |
+
return False
|
| 20 |
+
|
| 21 |
+
def test_endpoints():
|
| 22 |
+
"""Test if endpoints are properly defined"""
|
| 23 |
+
try:
|
| 24 |
+
from main import app
|
| 25 |
+
routes = [route.path for route in app.routes]
|
| 26 |
+
print(f"β
Found {len(routes)} routes defined")
|
| 27 |
+
|
| 28 |
+
# Check for some key endpoints
|
| 29 |
+
key_endpoints = [
|
| 30 |
+
"/send_email",
|
| 31 |
+
"/verify_license_key",
|
| 32 |
+
"/add_patient",
|
| 33 |
+
"/add_wound_details",
|
| 34 |
+
"/get_all_patient_details"
|
| 35 |
+
]
|
| 36 |
+
|
| 37 |
+
missing_endpoints = []
|
| 38 |
+
for endpoint in key_endpoints:
|
| 39 |
+
if endpoint not in routes:
|
| 40 |
+
missing_endpoints.append(endpoint)
|
| 41 |
+
|
| 42 |
+
if missing_endpoints:
|
| 43 |
+
print(f"β Missing endpoints: {missing_endpoints}")
|
| 44 |
+
return False
|
| 45 |
+
else:
|
| 46 |
+
print("β
All key endpoints are defined")
|
| 47 |
+
return True
|
| 48 |
+
|
| 49 |
+
except Exception as e:
|
| 50 |
+
print(f"β Failed to check endpoints: {e}")
|
| 51 |
+
return False
|
| 52 |
+
|
| 53 |
+
def test_pydantic_models():
|
| 54 |
+
"""Test if Pydantic models are properly defined"""
|
| 55 |
+
try:
|
| 56 |
+
from main import SendEmailRequest, AddPatientRequest, AddWoundDetailsRequest
|
| 57 |
+
print("β
Pydantic models imported successfully")
|
| 58 |
+
|
| 59 |
+
# Test model instantiation
|
| 60 |
+
test_email_request = SendEmailRequest(
|
| 61 |
+
name="Test",
|
| 62 |
+
email="test@example.com",
|
| 63 |
+
c_code="+1",
|
| 64 |
+
phone="1234567890"
|
| 65 |
+
)
|
| 66 |
+
print("β
Pydantic model validation works")
|
| 67 |
+
return True
|
| 68 |
+
|
| 69 |
+
except Exception as e:
|
| 70 |
+
print(f"β Failed to test Pydantic models: {e}")
|
| 71 |
+
return False
|
| 72 |
+
|
| 73 |
+
def main():
|
| 74 |
+
"""Run all tests"""
|
| 75 |
+
print("π§ͺ Testing FastAPI Application...")
|
| 76 |
+
print("=" * 50)
|
| 77 |
+
|
| 78 |
+
tests = [
|
| 79 |
+
("Import Test", test_import),
|
| 80 |
+
("Endpoints Test", test_endpoints),
|
| 81 |
+
("Pydantic Models Test", test_pydantic_models)
|
| 82 |
+
]
|
| 83 |
+
|
| 84 |
+
passed = 0
|
| 85 |
+
total = len(tests)
|
| 86 |
+
|
| 87 |
+
for test_name, test_func in tests:
|
| 88 |
+
print(f"\nπ Running {test_name}...")
|
| 89 |
+
if test_func():
|
| 90 |
+
passed += 1
|
| 91 |
+
|
| 92 |
+
print("\n" + "=" * 50)
|
| 93 |
+
print(f"π Test Results: {passed}/{total} tests passed")
|
| 94 |
+
|
| 95 |
+
if passed == total:
|
| 96 |
+
print("π All tests passed! FastAPI application is ready.")
|
| 97 |
+
return True
|
| 98 |
+
else:
|
| 99 |
+
print("β οΈ Some tests failed. Please check the errors above.")
|
| 100 |
+
return False
|
| 101 |
+
|
| 102 |
+
if __name__ == "__main__":
|
| 103 |
+
success = main()
|
| 104 |
+
sys.exit(0 if success else 1)
|
| 105 |
+
|