Spaces:
Sleeping
Sleeping
| import os | |
| from fastapi import FastAPI, HTTPException, Request, Response | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import RedirectResponse, HTMLResponse | |
| import gradio as gr | |
| import requests | |
| import logging | |
| import time | |
| import aiohttp | |
| import asyncio | |
| from typing import Optional, Dict, Any | |
| from passlib.context import CryptContext | |
| from passlib.exc import UnknownHashError | |
| from pydantic import BaseModel | |
| # ====================== | |
| # Configuration | |
| # ====================== | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' | |
| ) | |
| logger = logging.getLogger(__name__) | |
| # Password hashing | |
| pwd_context = CryptContext( | |
| schemes=["bcrypt"], | |
| deprecated="auto" | |
| ) | |
| # Environment variables (fallback to hardcoded values if not set) | |
| BACKEND_URL = os.getenv("BACKEND_URL", "https://rocketfarmstudios-cps-api.hf.space") | |
| ADMIN_EMAIL = os.getenv("ADMIN_EMAIL", "yakdhanali97@gmail.com") | |
| ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD", "123456") | |
| # ====================== | |
| # Models | |
| # ====================== | |
| class DoctorCreate(BaseModel): | |
| full_name: str | |
| email: str | |
| license_number: str | |
| password: str | |
| specialty: str | |
| # ====================== | |
| # Token Management | |
| # ====================== | |
| class TokenManager: | |
| def __init__(self): | |
| self.token: Optional[str] = None | |
| self.last_refresh: float = 0 | |
| self.expires_in: int = 3600 # Default expiry | |
| self.lock = asyncio.Lock() | |
| async def _make_login_request(self) -> Optional[str]: | |
| """Make the login request with proper error handling""" | |
| login_payload = { | |
| "username": ADMIN_EMAIL, | |
| "password": ADMIN_PASSWORD, | |
| "device_token": "admin-console" | |
| } | |
| try: | |
| async with aiohttp.ClientSession() as session: | |
| async with session.post( | |
| f"{BACKEND_URL}/auth/login", | |
| json=login_payload, | |
| timeout=aiohttp.ClientTimeout(total=10) | |
| ) as response: | |
| if response.status == 200: | |
| data = await response.json() | |
| return data.get("access_token") | |
| else: | |
| error = await response.text() | |
| logger.error(f"Login failed: {response.status} - {error}") | |
| return None | |
| except Exception as e: | |
| logger.error(f"Login request error: {str(e)}") | |
| return None | |
| async def refresh_token(self) -> str: | |
| """Obtain a new token with retry logic""" | |
| async with self.lock: # Prevent multiple concurrent refreshes | |
| for attempt in range(3): | |
| token = await self._make_login_request() | |
| if token: | |
| self.token = token | |
| self.last_refresh = time.time() | |
| logger.info("Successfully refreshed admin token") | |
| return token | |
| wait_time = min(5, (attempt + 1) * 2) # Exponential backoff with max 5s | |
| logger.warning(f"Attempt {attempt + 1} failed, retrying in {wait_time}s...") | |
| await asyncio.sleep(wait_time) | |
| raise HTTPException( | |
| status_code=500, | |
| detail="Failed to obtain admin token after multiple attempts" | |
| ) | |
| async def get_token(self) -> str: | |
| """Get current valid token, refreshing if needed""" | |
| if not self.token or (time.time() - self.last_refresh) > (self.expires_in - 60): | |
| return await self.refresh_token() | |
| return self.token | |
| token_manager = TokenManager() | |
| # ====================== | |
| # FastAPI App | |
| # ====================== | |
| app = FastAPI( | |
| title="Medical Admin Portal", | |
| description="Doctor account management system", | |
| version="1.0.0" | |
| ) | |
| # CORS Configuration | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # ====================== | |
| # Security Utilities | |
| # ====================== | |
| def verify_password(plain_password: str, hashed_password: str) -> bool: | |
| try: | |
| return pwd_context.verify(plain_password, hashed_password) | |
| except UnknownHashError: | |
| logger.error(f"Unrecognized hash format: {hashed_password[:15]}...") | |
| return False | |
| except Exception as e: | |
| logger.error(f"Password verification error: {str(e)}") | |
| return False | |
| # ====================== | |
| # API Endpoints | |
| # ====================== | |
| async def root(): | |
| return {"status": "active", "service": "Medical Admin Portal"} | |
| async def handle_login(): | |
| return RedirectResponse(url="/auth/login", status_code=307) | |
| async def health_check(): | |
| try: | |
| # Verify we can get a token | |
| await token_manager.get_token() | |
| return {"status": "healthy"} | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| # ====================== | |
| # Doctor Management | |
| # ====================== | |
| async def create_doctor_api(doctor_data: DoctorCreate) -> Dict[str, Any]: | |
| """Core doctor creation logic""" | |
| token = await token_manager.get_token() | |
| headers = { | |
| "Authorization": f"Bearer {token}", | |
| "Content-Type": "application/json" | |
| } | |
| async with aiohttp.ClientSession() as session: | |
| async with session.post( | |
| f"{BACKEND_URL}/auth/admin/doctors", | |
| json=doctor_data.dict(), | |
| headers=headers, | |
| timeout=aiohttp.ClientTimeout(total=15) | |
| ) as response: | |
| if response.status == 201: | |
| return {"success": True, "message": "Doctor created successfully"} | |
| error_detail = await response.text() | |
| return { | |
| "success": False, | |
| "error": f"Error {response.status}: {error_detail}" | |
| } | |
| # ====================== | |
| # Gradio Interface | |
| # ====================== | |
| def sync_create_doctor(full_name: str, email: str, matricule: str, password: str, specialty: str) -> str: | |
| """Wrapper to run async code in Gradio's sync context""" | |
| try: | |
| doctor_data = DoctorCreate( | |
| full_name=full_name, | |
| email=email, | |
| license_number=matricule, | |
| password=password, | |
| specialty=specialty | |
| ) | |
| # Run async code in sync context | |
| result = asyncio.run(create_doctor_api(doctor_data)) | |
| if result["success"]: | |
| return "β " + result["message"] | |
| else: | |
| return "β " + result["error"] | |
| except Exception as e: | |
| logger.error(f"Doctor creation failed: {str(e)}") | |
| return f"β System error: {str(e)}" | |
| with gr.Blocks(title="Doctor Management", css=".gradio-container {max-width: 800px}") as admin_ui: | |
| gr.Markdown("# π¨ββοΈ Doctor Account Creator") | |
| with gr.Row(): | |
| with gr.Column(): | |
| full_name = gr.Textbox(label="Full Name", placeholder="Dr. First Last") | |
| email = gr.Textbox(label="Email", placeholder="doctor@hospital.org") | |
| license_num = gr.Textbox(label="License Number") | |
| with gr.Column(): | |
| specialty = gr.Dropdown( | |
| label="Specialty", | |
| choices=[ | |
| "General Practice", "Cardiology", "Neurology", | |
| "Pediatrics", "Orthopedics", "Dermatology" | |
| ] | |
| ) | |
| password = gr.Textbox( | |
| label="Password", | |
| type="password", | |
| info="Minimum 8 characters" | |
| ) | |
| submit_btn = gr.Button("Create Account", variant="primary") | |
| output = gr.Textbox(label="Status", interactive=False) | |
| submit_btn.click( | |
| fn=sync_create_doctor, | |
| inputs=[full_name, email, license_num, specialty, password], | |
| outputs=output | |
| ) | |
| # Mount Gradio interface | |
| app = gr.mount_gradio_app(app, admin_ui, path="/admin") | |
| # ====================== | |
| # Admin Dashboard | |
| # ====================== | |
| async def admin_auth( | |
| email: str, | |
| password: str, | |
| response: Response | |
| ): | |
| """Secure admin dashboard entry point""" | |
| if (email != ADMIN_EMAIL or | |
| not verify_password(password, pwd_context.hash(ADMIN_PASSWORD))): | |
| response.status_code = 401 | |
| return HTMLResponse(""" | |
| <h1>Access Denied</h1> | |
| <p>Invalid admin credentials</p> | |
| """) | |
| return RedirectResponse(url="/admin") | |
| # ====================== | |
| # Startup Event | |
| # ====================== | |
| async def startup_event(): | |
| """Verify we can connect to the backend on startup""" | |
| try: | |
| logger.info("Testing backend connection...") | |
| await token_manager.get_token() | |
| logger.info("Backend connection successful") | |
| except Exception as e: | |
| logger.error(f"Startup connection test failed: {str(e)}") | |
| raise | |
| # ====================== | |
| # Main Application | |
| # ====================== | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run( | |
| app, | |
| host="0.0.0.0", | |
| port=7860, | |
| log_level="info", | |
| timeout_keep_alive=60 | |
| ) |