Ali2206 commited on
Commit
257cf3e
·
verified ·
1 Parent(s): 3f94d61

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +95 -147
app.py CHANGED
@@ -13,10 +13,7 @@ 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'
@@ -29,50 +26,35 @@ pwd_context = CryptContext(
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()
@@ -82,9 +64,8 @@ class TokenManager:
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:
@@ -93,34 +74,23 @@ class TokenManager:
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(
125
  CORSMiddleware,
126
  allow_origins=["*"],
@@ -129,27 +99,12 @@ 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("/")
151
  async def root():
152
- return {"status": "active", "service": "Medical Admin Portal"}
 
 
 
153
 
154
  @app.post("/login")
155
  async def handle_login():
@@ -157,77 +112,80 @@ async def handle_login():
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():
@@ -238,13 +196,9 @@ with gr.Blocks(title="Doctor Management", css=".gradio-container {max-width: 800
238
  "Pediatrics", "Orthopedics", "Dermatology"
239
  ]
240
  )
241
- password = gr.Textbox(
242
- label="Password",
243
- type="password",
244
- info="Minimum 8 characters"
245
- )
246
 
247
- submit_btn = gr.Button("Create Account", variant="primary")
248
  output = gr.Textbox(label="Status", interactive=False)
249
 
250
  submit_btn.click(
@@ -253,20 +207,10 @@ with gr.Blocks(title="Doctor Management", css=".gradio-container {max-width: 800
253
  outputs=output
254
  )
255
 
256
- # Mount Gradio interface
257
  app = gr.mount_gradio_app(app, admin_ui, path="/admin")
258
 
259
- # ======================
260
- # Admin Dashboard
261
- # ======================
262
-
263
  @app.get("/admin-auth")
264
- async def admin_auth(
265
- email: str,
266
- password: str,
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
@@ -276,24 +220,29 @@ async def admin_auth(
276
  """)
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__":
299
  import uvicorn
@@ -301,6 +250,5 @@ if __name__ == "__main__":
301
  app,
302
  host="0.0.0.0",
303
  port=7860,
304
- log_level="info",
305
- timeout_keep_alive=60
306
  )
 
13
  from passlib.exc import UnknownHashError
14
  from pydantic import BaseModel
15
 
16
+ # Configure logging
 
 
 
17
  logging.basicConfig(
18
  level=logging.INFO,
19
  format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
 
26
  deprecated="auto"
27
  )
28
 
29
+ # Configuration
30
  BACKEND_URL = os.getenv("BACKEND_URL", "https://rocketfarmstudios-cps-api.hf.space")
31
  ADMIN_EMAIL = os.getenv("ADMIN_EMAIL", "yakdhanali97@gmail.com")
32
  ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD", "123456")
33
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  class TokenManager:
35
  def __init__(self):
36
+ self.token = None
37
+ self.last_refresh = 0
38
+ self.expires_in = 3600
39
  self.lock = asyncio.Lock()
40
+ self.initialized = False
41
+ self.available = False
42
 
43
  async def _make_login_request(self) -> Optional[str]:
 
 
 
 
 
 
 
44
  try:
45
  async with aiohttp.ClientSession() as session:
46
  async with session.post(
47
  f"{BACKEND_URL}/auth/login",
48
+ json={
49
+ "username": ADMIN_EMAIL,
50
+ "password": ADMIN_PASSWORD,
51
+ "device_token": "admin-console"
52
+ },
53
  timeout=aiohttp.ClientTimeout(total=10)
54
  ) as response:
55
  if response.status == 200:
56
  data = await response.json()
57
+ self.available = True
58
  return data.get("access_token")
59
  else:
60
  error = await response.text()
 
64
  logger.error(f"Login request error: {str(e)}")
65
  return None
66
 
67
+ async def refresh_token(self) -> Optional[str]:
68
+ async with self.lock:
 
69
  for attempt in range(3):
70
  token = await self._make_login_request()
71
  if token:
 
74
  logger.info("Successfully refreshed admin token")
75
  return token
76
 
77
+ wait_time = min(5, (attempt + 1) * 2)
78
  logger.warning(f"Attempt {attempt + 1} failed, retrying in {wait_time}s...")
79
  await asyncio.sleep(wait_time)
80
 
81
+ self.available = False
82
+ return None
 
 
83
 
84
+ async def get_token(self) -> Optional[str]:
85
+ if not self.initialized or not self.token or (time.time() - self.last_refresh) > (self.expires_in - 60):
86
+ self.initialized = True
87
  return await self.refresh_token()
88
  return self.token
89
 
90
  token_manager = TokenManager()
91
 
92
+ app = FastAPI(title="Medical Admin Portal")
 
 
93
 
 
 
 
 
 
 
 
94
  app.add_middleware(
95
  CORSMiddleware,
96
  allow_origins=["*"],
 
99
  allow_headers=["*"],
100
  )
101
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  @app.get("/")
103
  async def root():
104
+ return {
105
+ "status": "running",
106
+ "admin_api_available": token_manager.available
107
+ }
108
 
109
  @app.post("/login")
110
  async def handle_login():
 
112
 
113
  @app.get("/health")
114
  async def health_check():
115
+ return {
116
+ "status": "healthy",
117
+ "admin_api": "available" if token_manager.available else "unavailable"
118
+ }
 
 
119
 
120
+ async def create_doctor_api(doctor_data: Dict[str, Any]) -> Dict[str, Any]:
121
+ if not token_manager.available:
122
+ return {
123
+ "success": False,
124
+ "error": "Admin API currently unavailable"
125
+ }
126
 
 
 
127
  token = await token_manager.get_token()
128
+ if not token:
129
+ return {
130
+ "success": False,
131
+ "error": "Failed to obtain admin token"
132
+ }
133
+
134
  headers = {
135
  "Authorization": f"Bearer {token}",
136
  "Content-Type": "application/json"
137
  }
138
 
139
+ try:
140
+ async with aiohttp.ClientSession() as session:
141
+ async with session.post(
142
+ f"{BACKEND_URL}/auth/admin/doctors",
143
+ json=doctor_data,
144
+ headers=headers,
145
+ timeout=aiohttp.ClientTimeout(total=15)
146
+ ) as response:
147
+ if response.status == 201:
148
+ return {"success": True, "message": "Doctor created successfully"}
149
+
150
+ error_detail = await response.text()
151
+ return {
152
+ "success": False,
153
+ "error": f"Error {response.status}: {error_detail}"
154
+ }
155
+ except Exception as e:
156
+ logger.error(f"Doctor creation API error: {str(e)}")
157
+ return {
158
+ "success": False,
159
+ "error": f"API communication error: {str(e)}"
160
+ }
161
 
162
  def sync_create_doctor(full_name: str, email: str, matricule: str, password: str, specialty: str) -> str:
 
163
  try:
164
+ doctor_data = {
165
+ "full_name": full_name,
166
+ "email": email,
167
+ "license_number": matricule,
168
+ "password": password,
169
+ "specialty": specialty
170
+ }
171
 
 
172
  result = asyncio.run(create_doctor_api(doctor_data))
173
 
174
  if result["success"]:
175
  return "✅ " + result["message"]
176
+ return "❌ " + result.get("error", "Unknown error")
 
177
 
178
  except Exception as e:
179
  logger.error(f"Doctor creation failed: {str(e)}")
180
  return f"❌ System error: {str(e)}"
181
 
182
+ with gr.Blocks(title="Doctor Management") as admin_ui:
183
  gr.Markdown("# 👨‍⚕️ Doctor Account Creator")
184
 
185
  with gr.Row():
186
  with gr.Column():
187
+ full_name = gr.Textbox(label="Full Name")
188
+ email = gr.Textbox(label="Email")
189
  license_num = gr.Textbox(label="License Number")
190
 
191
  with gr.Column():
 
196
  "Pediatrics", "Orthopedics", "Dermatology"
197
  ]
198
  )
199
+ password = gr.Textbox(label="Password", type="password")
 
 
 
 
200
 
201
+ submit_btn = gr.Button("Create Account")
202
  output = gr.Textbox(label="Status", interactive=False)
203
 
204
  submit_btn.click(
 
207
  outputs=output
208
  )
209
 
 
210
  app = gr.mount_gradio_app(app, admin_ui, path="/admin")
211
 
 
 
 
 
212
  @app.get("/admin-auth")
213
+ async def admin_auth(email: str, password: str, response: Response):
 
 
 
 
 
214
  if (email != ADMIN_EMAIL or
215
  not verify_password(password, pwd_context.hash(ADMIN_PASSWORD))):
216
  response.status_code = 401
 
220
  """)
221
  return RedirectResponse(url="/admin")
222
 
 
 
 
 
223
  @app.on_event("startup")
224
  async def startup_event():
225
+ """Try to connect to backend but don't fail startup"""
226
  try:
227
  logger.info("Testing backend connection...")
228
  await token_manager.get_token()
229
+ if token_manager.available:
230
+ logger.info("Backend connection successful")
231
+ else:
232
+ logger.warning("Backend connection failed - running in degraded mode")
233
  except Exception as e:
234
+ logger.error(f"Startup connection test error: {str(e)}")
235
+ token_manager.available = False
236
 
237
+ def verify_password(plain_password: str, hashed_password: str) -> bool:
238
+ try:
239
+ return pwd_context.verify(plain_password, hashed_password)
240
+ except UnknownHashError:
241
+ logger.error(f"Unrecognized hash format")
242
+ return False
243
+ except Exception as e:
244
+ logger.error(f"Password verification error: {str(e)}")
245
+ return False
246
 
247
  if __name__ == "__main__":
248
  import uvicorn
 
250
  app,
251
  host="0.0.0.0",
252
  port=7860,
253
+ log_level="info"
 
254
  )