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() @app.get("/") def root(): logger.debug("Root endpoint accessed") return {"message": "🚀 FastAPI with MongoDB + JWT is running."} @app.post("/login") 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 @app.post("/create-doctor") 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 @app.get("/test-backend") 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) @app.get("/admin") async def admin_dashboard(): logger.debug("Admin dashboard accessed") return RedirectResponse(url="/admin-auth", status_code=307) @app.on_event("startup") 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)