Ali2206 commited on
Commit
ce76c19
·
verified ·
1 Parent(s): 546d08e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +175 -154
app.py CHANGED
@@ -1,48 +1,124 @@
1
- from fastapi import FastAPI, Request, HTTPException, Response
 
2
  from fastapi.middleware.cors import CORSMiddleware
3
  from fastapi.responses import RedirectResponse, HTMLResponse
4
- from api import api_router
5
  import gradio as gr
6
  import requests
7
  import logging
8
  import time
 
 
9
  from typing import Optional, Dict, Any
10
  from passlib.context import CryptContext
11
  from passlib.exc import UnknownHashError
 
12
 
13
  # ======================
14
  # Configuration
15
  # ======================
16
 
17
- # Configure logging
18
  logging.basicConfig(
19
- level=logging.DEBUG,
20
  format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
21
  )
22
  logger = logging.getLogger(__name__)
23
 
24
- # Password hashing context
25
  pwd_context = CryptContext(
26
- schemes=["bcrypt", "sha256_crypt"], # Supported schemes
27
- deprecated="auto",
28
- bcrypt__rounds=12,
29
- sha256_crypt__rounds=10000
30
  )
31
 
32
- # Constants
33
- BACKEND_URL = "https://rocketfarmstudios-cps-api.hf.space"
34
- ADMIN_CREDENTIALS = {
35
- "email": "yakdhanali97@gmail.com",
36
- "password": "123456"
37
- }
38
- MAX_TOKEN_RETRIES = 3
39
- TOKEN_RETRY_DELAY = 2 # seconds with exponential backoff
40
 
41
  # ======================
42
- # Application Setup
43
  # ======================
44
 
45
- app = FastAPI(title="Medical Admin Portal")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
  # CORS Configuration
48
  app.add_middleware(
@@ -53,81 +129,22 @@ app.add_middleware(
53
  allow_headers=["*"],
54
  )
55
 
56
- app.include_router(api_router)
57
-
58
  # ======================
59
  # Security Utilities
60
  # ======================
61
 
62
  def verify_password(plain_password: str, hashed_password: str) -> bool:
63
- """Safely verify password against stored hash with multiple fallbacks"""
64
  try:
65
  return pwd_context.verify(plain_password, hashed_password)
66
  except UnknownHashError:
67
- logger.warning(f"Unrecognized hash format: {hashed_password[:15]}...")
68
- # Try common legacy formats if needed
69
- if hashed_password.startswith("$2a$") or hashed_password.startswith("$2b$"):
70
- try:
71
- return pwd_context.verify(plain_password, hashed_password.replace("$2a$", "$2b$"))
72
- except Exception:
73
- pass
74
  return False
75
  except Exception as e:
76
  logger.error(f"Password verification error: {str(e)}")
77
  return False
78
 
79
  # ======================
80
- # Token Management
81
- # ======================
82
-
83
- class TokenManager:
84
- def __init__(self):
85
- self.token = None
86
- self.last_refresh = 0
87
- self.expires_in = 3600
88
- self.backend_url = "https://rocketfarmstudios-cps-api.hf.space"
89
- self.credentials = {
90
- "username": "yakdhanali97@gmail.com",
91
- "password": "123456",
92
- "device_token": "admin-device-token"
93
- }
94
-
95
- async def _try_login(self, session):
96
- try:
97
- async with session.post(
98
- f"{self.backend_url}/auth/login",
99
- json=self.credentials,
100
- timeout=10
101
- ) as response:
102
- if response.status == 200:
103
- data = await response.json()
104
- return data.get("access_token")
105
- else:
106
- error = await response.text()
107
- logger.error(f"Login failed: {response.status} - {error}")
108
- return None
109
- except Exception as e:
110
- logger.error(f"Login error: {str(e)}")
111
- return None
112
-
113
- async def refresh_token(self):
114
- async with aiohttp.ClientSession() as session:
115
- for attempt in range(3):
116
- token = await self._try_login(session)
117
- if token:
118
- return token
119
- await asyncio.sleep(2 ** attempt) # Exponential backoff
120
- raise Exception("Failed to obtain admin token after multiple attempts")
121
-
122
- async def get_token(self):
123
- if not self.token or (time.time() - self.last_refresh) > (self.expires_in - 60):
124
- self.token = await self.refresh_token()
125
- self.last_refresh = time.time()
126
- return self.token
127
- token_manager = TokenManager()
128
-
129
- # ======================
130
- # Core Routes
131
  # ======================
132
 
133
  @app.get("/")
@@ -135,94 +152,83 @@ async def root():
135
  return {"status": "active", "service": "Medical Admin Portal"}
136
 
137
  @app.post("/login")
138
- async def handle_login(request: Request):
139
  return RedirectResponse(url="/auth/login", status_code=307)
140
 
 
 
 
 
 
 
 
 
 
141
  # ======================
142
- # Admin Functions
143
  # ======================
144
 
145
- def create_doctor(
146
- full_name: str,
147
- email: str,
148
- matricule: str,
149
- password: str,
150
- specialty: str
151
- ) -> str:
152
- """Create new doctor account with error handling"""
153
- try:
154
- token = token_manager.get_token()
155
- headers = {
156
- "Authorization": f"Bearer {token}",
157
- "Content-Type": "application/json"
158
- }
159
-
160
- payload = {
161
- "full_name": full_name,
162
- "email": email,
163
- "license_number": matricule,
164
- "password": password,
165
- "specialty": specialty
166
- }
167
 
168
- response = requests.post(
 
169
  f"{BACKEND_URL}/auth/admin/doctors",
170
- json=payload,
171
  headers=headers,
172
- timeout=15
173
- )
174
-
175
- if response.status_code == 201:
176
- return " Doctor created successfully!"
177
-
178
- # Handle token expiration
179
- if response.status_code == 401:
180
- token = token_manager.refresh_token()
181
- headers["Authorization"] = f"Bearer {token}"
182
- response = requests.post(
183
- f"{BACKEND_URL}/auth/admin/doctors",
184
- json=payload,
185
- headers=headers,
186
- timeout=15
187
- )
188
- if response.status_code == 201:
189
- return "✅ Doctor created successfully!"
190
-
191
- error_detail = response.json().get("detail", "Unknown error")
192
- return f"❌ Error: {error_detail} (Code: {response.status_code})"
193
-
194
- except requests.exceptions.RequestException as e:
195
- return f"❌ Network error: {str(e)}"
196
- except Exception as e:
197
- logger.error(f"Doctor creation failed: {str(e)}")
198
- return f"❌ System error: {str(e)}"
199
 
200
  # ======================
201
  # Gradio Interface
202
  # ======================
203
 
204
- admin_ui = gr.Blocks(
205
- css="""
206
- .gradio-container {
207
- font-family: 'Segoe UI', sans-serif;
208
- max-width: 800px;
209
- margin: 0 auto;
210
- }
211
- .input-group {
212
- margin-bottom: 1.5rem;
213
- }
214
- """
215
- )
 
 
 
 
 
 
 
 
 
 
216
 
217
- with admin_ui:
218
- gr.Markdown("# 👨‍⚕️ Doctor Account Management")
219
- gr.Markdown("Create new doctor accounts with appropriate specialties")
220
 
221
  with gr.Row():
222
  with gr.Column():
223
  full_name = gr.Textbox(label="Full Name", placeholder="Dr. First Last")
224
  email = gr.Textbox(label="Email", placeholder="doctor@hospital.org")
225
- matricule = gr.Textbox(label="License Number")
226
 
227
  with gr.Column():
228
  specialty = gr.Dropdown(
@@ -242,8 +248,8 @@ with admin_ui:
242
  output = gr.Textbox(label="Status", interactive=False)
243
 
244
  submit_btn.click(
245
- fn=create_doctor,
246
- inputs=[full_name, email, matricule, specialty, password],
247
  outputs=output
248
  )
249
 
@@ -261,8 +267,8 @@ async def admin_auth(
261
  response: Response
262
  ):
263
  """Secure admin dashboard entry point"""
264
- if (email != ADMIN_CREDENTIALS["email"] or
265
- not verify_password(password, pwd_context.hash(ADMIN_CREDENTIALS["password"]))):
266
  response.status_code = 401
267
  return HTMLResponse("""
268
  <h1>Access Denied</h1>
@@ -271,7 +277,22 @@ async def admin_auth(
271
  return RedirectResponse(url="/admin")
272
 
273
  # ======================
274
- # Application Entry
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
275
  # ======================
276
 
277
  if __name__ == "__main__":
 
1
+ import os
2
+ from fastapi import FastAPI, HTTPException, Request, Response
3
  from fastapi.middleware.cors import CORSMiddleware
4
  from fastapi.responses import RedirectResponse, HTMLResponse
 
5
  import gradio as gr
6
  import requests
7
  import logging
8
  import time
9
+ import aiohttp
10
+ import asyncio
11
  from typing import Optional, Dict, Any
12
  from passlib.context import CryptContext
13
  from passlib.exc import UnknownHashError
14
+ from pydantic import BaseModel
15
 
16
  # ======================
17
  # Configuration
18
  # ======================
19
 
 
20
  logging.basicConfig(
21
+ level=logging.INFO,
22
  format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
23
  )
24
  logger = logging.getLogger(__name__)
25
 
26
+ # Password hashing
27
  pwd_context = CryptContext(
28
+ schemes=["bcrypt"],
29
+ deprecated="auto"
 
 
30
  )
31
 
32
+ # Environment variables (fallback to hardcoded values if not set)
33
+ BACKEND_URL = os.getenv("BACKEND_URL", "https://rocketfarmstudios-cps-api.hf.space")
34
+ ADMIN_EMAIL = os.getenv("ADMIN_EMAIL", "yakdhanali97@gmail.com")
35
+ ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD", "123456")
 
 
 
 
36
 
37
  # ======================
38
+ # Models
39
  # ======================
40
 
41
+ class DoctorCreate(BaseModel):
42
+ full_name: str
43
+ email: str
44
+ license_number: str
45
+ password: str
46
+ specialty: str
47
+
48
+ # ======================
49
+ # Token Management
50
+ # ======================
51
+
52
+ class TokenManager:
53
+ def __init__(self):
54
+ self.token: Optional[str] = None
55
+ self.last_refresh: float = 0
56
+ self.expires_in: int = 3600 # Default expiry
57
+ self.lock = asyncio.Lock()
58
+
59
+ async def _make_login_request(self) -> Optional[str]:
60
+ """Make the login request with proper error handling"""
61
+ login_payload = {
62
+ "username": ADMIN_EMAIL,
63
+ "password": ADMIN_PASSWORD,
64
+ "device_token": "admin-console"
65
+ }
66
+
67
+ try:
68
+ async with aiohttp.ClientSession() as session:
69
+ async with session.post(
70
+ f"{BACKEND_URL}/auth/login",
71
+ json=login_payload,
72
+ timeout=aiohttp.ClientTimeout(total=10)
73
+ ) as response:
74
+ if response.status == 200:
75
+ data = await response.json()
76
+ return data.get("access_token")
77
+ else:
78
+ error = await response.text()
79
+ logger.error(f"Login failed: {response.status} - {error}")
80
+ return None
81
+ except Exception as e:
82
+ logger.error(f"Login request error: {str(e)}")
83
+ return None
84
+
85
+ async def refresh_token(self) -> str:
86
+ """Obtain a new token with retry logic"""
87
+ async with self.lock: # Prevent multiple concurrent refreshes
88
+ for attempt in range(3):
89
+ token = await self._make_login_request()
90
+ if token:
91
+ self.token = token
92
+ self.last_refresh = time.time()
93
+ logger.info("Successfully refreshed admin token")
94
+ return token
95
+
96
+ wait_time = min(5, (attempt + 1) * 2) # Exponential backoff with max 5s
97
+ logger.warning(f"Attempt {attempt + 1} failed, retrying in {wait_time}s...")
98
+ await asyncio.sleep(wait_time)
99
+
100
+ raise HTTPException(
101
+ status_code=500,
102
+ detail="Failed to obtain admin token after multiple attempts"
103
+ )
104
+
105
+ async def get_token(self) -> str:
106
+ """Get current valid token, refreshing if needed"""
107
+ if not self.token or (time.time() - self.last_refresh) > (self.expires_in - 60):
108
+ return await self.refresh_token()
109
+ return self.token
110
+
111
+ token_manager = TokenManager()
112
+
113
+ # ======================
114
+ # FastAPI App
115
+ # ======================
116
+
117
+ app = FastAPI(
118
+ title="Medical Admin Portal",
119
+ description="Doctor account management system",
120
+ version="1.0.0"
121
+ )
122
 
123
  # CORS Configuration
124
  app.add_middleware(
 
129
  allow_headers=["*"],
130
  )
131
 
 
 
132
  # ======================
133
  # Security Utilities
134
  # ======================
135
 
136
  def verify_password(plain_password: str, hashed_password: str) -> bool:
 
137
  try:
138
  return pwd_context.verify(plain_password, hashed_password)
139
  except UnknownHashError:
140
+ logger.error(f"Unrecognized hash format: {hashed_password[:15]}...")
 
 
 
 
 
 
141
  return False
142
  except Exception as e:
143
  logger.error(f"Password verification error: {str(e)}")
144
  return False
145
 
146
  # ======================
147
+ # API Endpoints
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  # ======================
149
 
150
  @app.get("/")
 
152
  return {"status": "active", "service": "Medical Admin Portal"}
153
 
154
  @app.post("/login")
155
+ async def handle_login():
156
  return RedirectResponse(url="/auth/login", status_code=307)
157
 
158
+ @app.get("/health")
159
+ async def health_check():
160
+ try:
161
+ # Verify we can get a token
162
+ await token_manager.get_token()
163
+ return {"status": "healthy"}
164
+ except Exception as e:
165
+ raise HTTPException(status_code=500, detail=str(e))
166
+
167
  # ======================
168
+ # Doctor Management
169
  # ======================
170
 
171
+ async def create_doctor_api(doctor_data: DoctorCreate) -> Dict[str, Any]:
172
+ """Core doctor creation logic"""
173
+ token = await token_manager.get_token()
174
+
175
+ headers = {
176
+ "Authorization": f"Bearer {token}",
177
+ "Content-Type": "application/json"
178
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
 
180
+ async with aiohttp.ClientSession() as session:
181
+ async with session.post(
182
  f"{BACKEND_URL}/auth/admin/doctors",
183
+ json=doctor_data.dict(),
184
  headers=headers,
185
+ timeout=aiohttp.ClientTimeout(total=15)
186
+ ) as response:
187
+
188
+ if response.status == 201:
189
+ return {"success": True, "message": "Doctor created successfully"}
190
+
191
+ error_detail = await response.text()
192
+ return {
193
+ "success": False,
194
+ "error": f"Error {response.status}: {error_detail}"
195
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
 
197
  # ======================
198
  # Gradio Interface
199
  # ======================
200
 
201
+ def sync_create_doctor(full_name: str, email: str, matricule: str, password: str, specialty: str) -> str:
202
+ """Wrapper to run async code in Gradio's sync context"""
203
+ try:
204
+ doctor_data = DoctorCreate(
205
+ full_name=full_name,
206
+ email=email,
207
+ license_number=matricule,
208
+ password=password,
209
+ specialty=specialty
210
+ )
211
+
212
+ # Run async code in sync context
213
+ result = asyncio.run(create_doctor_api(doctor_data))
214
+
215
+ if result["success"]:
216
+ return "✅ " + result["message"]
217
+ else:
218
+ return "❌ " + result["error"]
219
+
220
+ except Exception as e:
221
+ logger.error(f"Doctor creation failed: {str(e)}")
222
+ return f"❌ System error: {str(e)}"
223
 
224
+ with gr.Blocks(title="Doctor Management", css=".gradio-container {max-width: 800px}") as admin_ui:
225
+ gr.Markdown("# 👨‍⚕️ Doctor Account Creator")
 
226
 
227
  with gr.Row():
228
  with gr.Column():
229
  full_name = gr.Textbox(label="Full Name", placeholder="Dr. First Last")
230
  email = gr.Textbox(label="Email", placeholder="doctor@hospital.org")
231
+ license_num = gr.Textbox(label="License Number")
232
 
233
  with gr.Column():
234
  specialty = gr.Dropdown(
 
248
  output = gr.Textbox(label="Status", interactive=False)
249
 
250
  submit_btn.click(
251
+ fn=sync_create_doctor,
252
+ inputs=[full_name, email, license_num, specialty, password],
253
  outputs=output
254
  )
255
 
 
267
  response: Response
268
  ):
269
  """Secure admin dashboard entry point"""
270
+ if (email != ADMIN_EMAIL or
271
+ not verify_password(password, pwd_context.hash(ADMIN_PASSWORD))):
272
  response.status_code = 401
273
  return HTMLResponse("""
274
  <h1>Access Denied</h1>
 
277
  return RedirectResponse(url="/admin")
278
 
279
  # ======================
280
+ # Startup Event
281
+ # ======================
282
+
283
+ @app.on_event("startup")
284
+ async def startup_event():
285
+ """Verify we can connect to the backend on startup"""
286
+ try:
287
+ logger.info("Testing backend connection...")
288
+ await token_manager.get_token()
289
+ logger.info("Backend connection successful")
290
+ except Exception as e:
291
+ logger.error(f"Startup connection test failed: {str(e)}")
292
+ raise
293
+
294
+ # ======================
295
+ # Main Application
296
  # ======================
297
 
298
  if __name__ == "__main__":