Spaces:
Sleeping
Sleeping
| from fastapi import FastAPI, Request, HTTPException | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import RedirectResponse | |
| from api import api_router | |
| import gradio as gr | |
| import aiohttp | |
| import asyncio | |
| import logging | |
| import time | |
| from typing import Optional | |
| from pydantic import BaseModel | |
| # Configure logging | |
| logging.basicConfig( | |
| level=logging.DEBUG, | |
| format="%(asctime)s - %(levelname)s - %(name)s - %(message)s" | |
| ) | |
| logger = logging.getLogger(__name__) | |
| logger.debug("Initializing application") | |
| app = FastAPI() | |
| # CORS Configuration (restrict in production) | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| app.include_router(api_router) | |
| # Constants | |
| BACKEND_URL = "https://rocketfarmstudios-cps-api.hf.space" | |
| ADMIN_EMAIL = "yakdhanali97@gmail.com" | |
| ADMIN_PASSWORD = "123456" | |
| MAX_TOKEN_RETRIES = 3 | |
| TOKEN_RETRY_DELAY = 2 # seconds | |
| TOKEN_EXPIRY = 3600 # 1 hour default expiry | |
| # Pydantic models | |
| class LoginPayload(BaseModel): | |
| username: str | |
| password: str | |
| device_token: str | |
| class DoctorPayload(BaseModel): | |
| full_name: str | |
| email: str | |
| license_number: str | |
| password: str | |
| specialty: str | |
| class TokenManager: | |
| def __init__(self): | |
| self.token = None | |
| self.last_refresh = 0 | |
| self.expires_in = TOKEN_EXPIRY | |
| self.lock = asyncio.Lock() | |
| async def _make_login_request(self) -> Optional[str]: | |
| try: | |
| async with aiohttp.ClientSession() as session: | |
| payload = LoginPayload( | |
| username=ADMIN_EMAIL, | |
| password=ADMIN_PASSWORD, | |
| device_token="admin-device-token" | |
| ) | |
| login_url = f"{BACKEND_URL.rstrip('/')}/auth/login" | |
| logger.debug(f"Sending login request to {login_url} with payload: {payload.dict()}") | |
| async with session.post( | |
| login_url, | |
| json=payload.dict(), | |
| timeout=15, | |
| headers={"Content-Type": "application/json"} | |
| ) as response: | |
| logger.debug(f"Login response status: {response.status}, URL: {response.url}") | |
| if response.status == 200: | |
| data = await response.json() | |
| token = data.get("access_token") | |
| if not token: | |
| logger.error("No access_token in response") | |
| return None | |
| logger.info(f"Received token: {token[:10]}...") | |
| return token | |
| else: | |
| error = await response.text() | |
| logger.error(f"Login failed: {response.status} - {error}, URL: {response.url}") | |
| return None | |
| except aiohttp.ClientError as e: | |
| logger.error(f"Login request error: {str(e)}, URL: {login_url}") | |
| return None | |
| async def refresh_token(self) -> str: | |
| async with self.lock: | |
| for attempt in range(MAX_TOKEN_RETRIES): | |
| 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) * TOKEN_RETRY_DELAY) | |
| 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: | |
| 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() | |
| def root(): | |
| logger.debug("Root endpoint accessed") | |
| return {"message": "π FastAPI with MongoDB + JWT is running."} | |
| async def redirect_login(request: Request): | |
| logger.info("Redirecting /login to /auth/login") | |
| return RedirectResponse(url="/auth/login", status_code=307) | |
| async def async_create_doctor(full_name: str, email: str, license_number: str, specialty: str, password: str): | |
| try: | |
| # Validate inputs | |
| if not all([full_name, email, license_number, specialty, password]): | |
| logger.error("Doctor creation failed: All fields are required") | |
| raise HTTPException(status_code=422, detail="All fields are required") | |
| token = await token_manager.get_token() | |
| payload = DoctorPayload( | |
| full_name=full_name, | |
| email=email, | |
| license_number=license_number, | |
| password=password, | |
| specialty=specialty | |
| ) | |
| headers = { | |
| "Authorization": f"Bearer {token}", | |
| "Content-Type": "application/json" | |
| } | |
| doctor_url = f"{BACKEND_URL.rstrip('/')}/auth/admin/doctors" | |
| logger.debug(f"Sending doctor creation request to {doctor_url} with payload: {payload.dict()}") | |
| async with aiohttp.ClientSession() as session: | |
| async with session.post( | |
| doctor_url, | |
| json=payload.dict(), | |
| headers=headers, | |
| timeout=15 | |
| ) as response: | |
| logger.debug(f"Doctor creation response status: {response.status}, URL: {response.url}") | |
| if response.status == 201: | |
| return "β Doctor created successfully!" | |
| elif response.status == 401: | |
| logger.warning("Token expired, attempting refresh...") | |
| token = await token_manager.refresh_token() | |
| headers["Authorization"] = f"Bearer {token}" | |
| async with session.post( | |
| doctor_url, | |
| json=payload.dict(), | |
| headers=headers, | |
| timeout=15 | |
| ) as retry_response: | |
| logger.debug(f"Retry doctor creation response status: {retry_response.status}, URL: {retry_response.url}") | |
| if retry_response.status == 201: | |
| return "β Doctor created successfully!" | |
| error_detail = await retry_response.text() | |
| return f"β Error: {error_detail} (Status: {retry_response.status})" | |
| error_detail = await response.text() | |
| return f"β Error: {error_detail} (Status: {response.status})" | |
| except HTTPException as e: | |
| logger.error(f"Doctor creation failed: {str(e)}") | |
| return f"β Error: {str(e)}" | |
| except Exception as e: | |
| logger.error(f"Doctor creation failed: {str(e)}") | |
| return f"β System Error: {str(e)}" | |
| def sync_create_doctor(full_name: str, email: str, license_number: str, specialty: str, password: str): | |
| return asyncio.run(async_create_doctor(full_name, email, license_number, specialty, password)) | |
| # New endpoint for public doctor creation | |
| async def create_doctor(payload: DoctorPayload): | |
| result = await async_create_doctor( | |
| full_name=payload.full_name, | |
| email=payload.email, | |
| license_number=payload.license_number, | |
| specialty=payload.specialty, | |
| password=payload.password | |
| ) | |
| return {"result": result} | |
| # Test endpoint to verify backend connectivity | |
| async def test_backend(): | |
| try: | |
| async with aiohttp.ClientSession() as session: | |
| test_url = f"{BACKEND_URL.rstrip('/')}/" | |
| logger.debug(f"Testing backend connectivity to {test_url}") | |
| async with session.get(test_url, timeout=5) as response: | |
| logger.debug(f"Test backend response status: {response.status}, URL: {response.url}") | |
| return {"status": response.status, "url": str(response.url), "message": await response.text()} | |
| except aiohttp.ClientError as e: | |
| logger.error(f"Test backend error: {str(e)}") | |
| return {"status": "error", "message": str(e)} | |
| admin_ui = gr.Blocks( | |
| css=""" | |
| .gradio-container { | |
| font-family: Arial, sans-serif; | |
| max-width: 800px; | |
| margin: 0 auto; | |
| padding: 2rem; | |
| } | |
| .input-group { | |
| margin-bottom: 1.5rem; | |
| } | |
| input, select { | |
| width: 100%; | |
| padding: 0.5rem; | |
| margin-bottom: 1rem; | |
| } | |
| button { | |
| background-color: #4a6fa5; | |
| color: white; | |
| padding: 0.75rem; | |
| border: none; | |
| border-radius: 4px; | |
| cursor: pointer; | |
| width: 100%; | |
| } | |
| """ | |
| ) | |
| with admin_ui: | |
| gr.Markdown("# Doctor Account Creator") | |
| with gr.Column(): | |
| full_name = gr.Textbox(label="Full Name", placeholder="e.g., Dr. John Doe") | |
| email = gr.Textbox(label="Email", placeholder="e.g., john.doe@example.com") | |
| matricule = gr.Textbox(label="License Number", placeholder="e.g., 12345") | |
| specialty = gr.Dropdown( | |
| label="Specialty", | |
| choices=["General Practice", "Cardiology", "Neurology", "Pediatrics"], | |
| value="General Practice" | |
| ) | |
| password = gr.Textbox(label="Password", type="password", placeholder="Enter a secure password") | |
| submit_btn = gr.Button("Create Account") | |
| output = gr.Textbox(label="Status", interactive=False) | |
| submit_btn.click( | |
| fn=sync_create_doctor, | |
| inputs=[full_name, email, matricule, specialty, password], | |
| outputs=output | |
| ) | |
| app = gr.mount_gradio_app(app, admin_ui, path="/admin-auth") | |
| # Modified admin dashboard (no authentication) | |
| async def admin_dashboard(): | |
| logger.debug("Admin dashboard accessed") | |
| return RedirectResponse(url="/admin-auth", status_code=307) | |
| async def startup_event(): | |
| """Initialize token but don't fail startup""" | |
| try: | |
| await token_manager.get_token() | |
| logger.info("Initial token fetch successful") | |
| except Exception as e: | |
| logger.error(f"Initial token fetch failed: {str(e)}") | |
| if __name__ == "__main__": | |
| logger.info("Starting application") | |
| import uvicorn | |
| uvicorn.run(app, host="0.0.0.0", port=7860) |