CPS-API / app.py
Ali2206's picture
Update app.py
37ca855 verified
raw
history blame
9.61 kB
from fastapi import FastAPI, Request, HTTPException, Response
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import RedirectResponse, HTMLResponse
from pydantic import BaseModel
import gradio as gr
import aiohttp
import asyncio
import logging
import time
import os
from typing import Optional
from fastapi import APIRouter
# Configure logging
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s - %(levelname)s - %(name)s - %(message)s"
)
logger = logging.getLogger(__name__)
logger.debug("Initializing application")
# FastAPI app
app = FastAPI()
# CORS Configuration (restrict in production)
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:7860", "https://your-production-domain.com"], # Update for production
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# API Router (assuming this is defined elsewhere)
api_router = APIRouter()
# Constants (load from environment variables for security)
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")
MAX_TOKEN_RETRIES = 3
TOKEN_RETRY_DELAY = 2 # seconds
TOKEN_EXPIRY = 3600 # 1 hour default expiry
# Pydantic model for login payload
class LoginPayload(BaseModel):
username: str
password: str
# Pydantic model for doctor creation payload
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)
async with session.post(
f"{BACKEND_URL}/auth/login",
json=payload.dict(),
timeout=10
) as response:
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
return token
else:
error = await response.text()
logger.error(f"Login failed: {response.status} - {error}")
return None
except aiohttp.ClientError as e:
logger.error(f"Login request error: {str(e)}")
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()
# Include API router
app.include_router(api_router)
@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)
def authenticate_admin(email: str = None, password: str = None):
if email != ADMIN_EMAIL or password != ADMIN_PASSWORD:
logger.warning(f"Failed admin login attempt with email: {email}")
raise HTTPException(status_code=401, detail="Unauthorized: Invalid email or password")
logger.info(f"Admin authenticated successfully: {email}")
return True
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]):
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"
}
async with aiohttp.ClientSession() as session:
async with session.post(
f"{BACKEND_URL}/auth/admin/doctors",
json=payload.dict(),
headers=headers,
timeout=10
) as response:
if response.status == 201:
return "βœ… Doctor created successfully!"
elif response.status == 401: # Token might be expired
logger.warning("Token expired, attempting refresh...")
token = await token_manager.refresh_token()
headers["Authorization"] = f"Bearer {token}"
async with session.post(
f"{BACKEND_URL}/auth/admin/doctors",
json=payload.dict(),
headers=headers,
timeout=10
) as retry_response:
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))
# Gradio UI
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")
email = gr.Textbox(label="Email")
matricule = gr.Textbox(label="License Number")
specialty = gr.Dropdown(
label="Specialty",
choices=["General Practice", "Cardiology", "Neurology", "Pediatrics"]
)
password = gr.Textbox(label="Password", type="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")
@app.get("/admin")
async def admin_dashboard(email: str = None, password: str = None, response: Response = None):
logger.debug("Admin dashboard accessed")
try:
authenticate_admin(email, password)
return RedirectResponse(url="/admin-auth", status_code=307)
except HTTPException as e:
response.status_code = 401
return HTMLResponse(content="""
<h1>401 Unauthorized</h1>
<p>Invalid admin credentials</p>
""")
@app.get("/admin-auth/gradio_api/queue/data")
async def gradio_queue_data(session_hash: str):
logger.debug(f"Gradio queue data accessed with session_hash: {session_hash}")
return {"status": "ok", "session_hash": session_hash}
@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)