Spaces:
Sleeping
Sleeping
import os | |
import sys | |
import shutil | |
import logging | |
from pathlib import Path | |
# Set up logging | |
logging.basicConfig(level=logging.INFO) | |
logger = logging.getLogger(__name__) | |
# Add the backend directory to the Python path | |
sys.path.append(os.path.abspath("backend")) | |
# Create necessary directories if they don't exist | |
os.makedirs("./temp_audio", exist_ok=True) | |
os.makedirs("./temp", exist_ok=True) | |
os.makedirs("./static", exist_ok=True) | |
# Check for index.html and create a simple one if it doesn't exist | |
static_path = Path("./static") | |
index_path = static_path / "index.html" | |
if not index_path.exists(): | |
logger.warning("index.html not found in static directory, creating a simple one") | |
with open(index_path, "w") as f: | |
f.write("""<!DOCTYPE html> | |
<html> | |
<head> | |
<title>PodCraft API</title> | |
<style> | |
body { font-family: Arial, sans-serif; margin: 0; padding: 0; display: flex; justify-content: center; align-items: center; height: 100vh; background-color: #f8f9fa; } | |
.container { max-width: 800px; padding: 2rem; background-color: white; border-radius: 0.5rem; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } | |
h1 { color: #6366F1; } | |
pre { background-color: #f5f5f5; padding: 1rem; border-radius: 0.25rem; overflow: auto; } | |
</style> | |
</head> | |
<body> | |
<div class="container"> | |
<h1>PodCraft API</h1> | |
<p>The PodCraft API is running. You can access the API at <a href="/docs">/docs</a>.</p> | |
<p>API Status: <a href="/api/status">/api/status</a></p> | |
</div> | |
</body> | |
</html>""") | |
# Set environment variables for MongoDB connection timeout | |
os.environ["MONGODB_CONNECT_TIMEOUT_MS"] = "5000" # 5 seconds timeout | |
os.environ["MONGODB_SERVER_SELECTION_TIMEOUT_MS"] = "5000" # 5 seconds timeout | |
# Import FastAPI and related modules | |
from fastapi import FastAPI, Request, HTTPException, Depends | |
from fastapi.responses import HTMLResponse, JSONResponse, FileResponse, Response, RedirectResponse | |
from fastapi.staticfiles import StaticFiles | |
from fastapi.middleware.cors import CORSMiddleware | |
# Function to read index.html content | |
def get_index_html(): | |
if index_path.exists(): | |
with open(index_path, "r") as f: | |
return f.read() | |
else: | |
return "<html><body><h1>Index file not found</h1></body></html>" | |
try: | |
# Try to import the backend app but don't use it directly | |
import backend.app.main | |
logger.info("Backend module imported successfully") | |
backend_available = True | |
except Exception as e: | |
logger.error(f"Error importing backend module: {str(e)}") | |
backend_available = False | |
# Create the main application | |
app = FastAPI(title="PodCraft") | |
# Add CORS middleware | |
app.add_middleware( | |
CORSMiddleware, | |
allow_origins=["*"], | |
allow_credentials=True, | |
allow_methods=["*"], | |
allow_headers=["*"], | |
) | |
# Define the root route that directly serves index.html | |
async def root(): | |
logger.info("Root route accessed, serving index.html") | |
html_content = get_index_html() | |
# Check if this is the React app by looking for specific markers | |
if "<div id=\"root\"></div>" in html_content: | |
logger.info("Detected React app, injecting URL fixer") | |
# Inject our URL fixer script before the closing body tag | |
script_tag = '<script src="/api/fix-client-urls"></script>' | |
html_content = html_content.replace('</body>', f'{script_tag}</body>') | |
return HTMLResponse(content=html_content) | |
# API status endpoint | |
async def status(): | |
return {"status": "ok", "message": "PodCraft API is running"} | |
# Serve static assets directory with correct content types | |
async def serve_assets(file_path: str): | |
file_full_path = static_path / "assets" / file_path | |
if not file_full_path.exists() or not file_full_path.is_file(): | |
logger.warning(f"Asset file not found: {file_full_path}") | |
raise HTTPException(status_code=404, detail="Asset file not found") | |
# Determine content type based on file extension | |
content_type = None | |
if file_path.endswith(".js"): | |
content_type = "application/javascript" | |
elif file_path.endswith(".css"): | |
content_type = "text/css" | |
elif file_path.endswith(".svg"): | |
content_type = "image/svg+xml" | |
elif file_path.endswith(".png"): | |
content_type = "image/png" | |
elif file_path.endswith(".jpg") or file_path.endswith(".jpeg"): | |
content_type = "image/jpeg" | |
logger.info(f"Serving asset: {file_path} with content-type: {content_type}") | |
return FileResponse(file_full_path, media_type=content_type) | |
# Special handlers for API routes that the React app calls directly | |
async def login_proxy(request: Request): | |
logger.info("Received login request, proxying to backend") | |
if backend_available: | |
# Forward the request to the backend | |
try: | |
# Get the raw request body and headers | |
body = await request.body() | |
json_data = await request.json() | |
# Import the actual login route from backend | |
from backend.app.main import login | |
# Create a mock request with the same data | |
mock_request = Request(scope=request.scope) | |
setattr(mock_request, "_json", json_data) | |
# Call the backend login route directly | |
response = await login(mock_request) | |
return response | |
except Exception as e: | |
logger.error(f"Error proxying login request: {str(e)}") | |
return JSONResponse( | |
content={"error": "Login failed", "message": str(e)}, | |
status_code=500 | |
) | |
else: | |
return JSONResponse( | |
content={"error": "Backend API not available for login"}, | |
status_code=503 | |
) | |
async def signup_proxy(request: Request): | |
logger.info("Received signup request, proxying to backend") | |
if backend_available: | |
try: | |
# Get the JSON data from the request | |
json_data = await request.json() | |
# Import the actual signup route from backend | |
from backend.app.main import signup | |
# Call the backend signup route | |
response = await signup(json_data) | |
return response | |
except Exception as e: | |
logger.error(f"Error proxying signup request: {str(e)}") | |
return JSONResponse( | |
content={"error": "Signup failed", "message": str(e)}, | |
status_code=500 | |
) | |
else: | |
return JSONResponse( | |
content={"error": "Backend API not available for signup"}, | |
status_code=503 | |
) | |
async def token_proxy(request: Request): | |
logger.info("Received token request, proxying to backend") | |
if backend_available: | |
try: | |
# Get form data from the request | |
form_data = await request.form() | |
# Import the actual token route from backend | |
from backend.app.main import login_for_access_token | |
# Call the backend token route | |
response = await login_for_access_token(form_data) | |
return response | |
except Exception as e: | |
logger.error(f"Error proxying token request: {str(e)}") | |
return JSONResponse( | |
content={"error": "Token request failed", "message": str(e)}, | |
status_code=500 | |
) | |
else: | |
return JSONResponse( | |
content={"error": "Backend API not available for token request"}, | |
status_code=503 | |
) | |
# Post-fix: Add a helper function to fix the client-side JS | |
async def fix_client_urls(): | |
""" | |
This endpoint returns JavaScript to inject into the page that rewrites | |
hardcoded localhost URLs to relative URLs | |
""" | |
return HTMLResponse(content=""" | |
<script> | |
(function() { | |
console.log("URL Fixer running..."); | |
// Override fetch to rewrite localhost URLs | |
const originalFetch = window.fetch; | |
window.fetch = function(url, options) { | |
if (typeof url === 'string' && url.includes('localhost:8000')) { | |
const newUrl = url.replace('http://localhost:8000', ''); | |
console.log(`Rewriting URL from ${url} to ${newUrl}`); | |
return originalFetch(newUrl, options); | |
} | |
return originalFetch(url, options); | |
}; | |
// Override XMLHttpRequest to rewrite localhost URLs | |
const originalOpen = XMLHttpRequest.prototype.open; | |
XMLHttpRequest.prototype.open = function(method, url, ...args) { | |
if (typeof url === 'string' && url.includes('localhost:8000')) { | |
const newUrl = url.replace('http://localhost:8000', ''); | |
console.log(`Rewriting XHR URL from ${url} to ${newUrl}`); | |
return originalOpen.call(this, method, newUrl, ...args); | |
} | |
return originalOpen.call(this, method, url, ...args); | |
}; | |
console.log("URL Fixer installed"); | |
})(); | |
</script> | |
""") | |
# Mount the static directory for other static files | |
if static_path.exists(): | |
app.mount("/static", StaticFiles(directory=str(static_path)), name="static") | |
# Catch-all route for client-side routing in the React app | |
async def catch_all(full_path: str, request: Request): | |
# Skip API paths and redirect them to the backend | |
if full_path.startswith("api/") or full_path == "docs" or full_path == "openapi.json": | |
if backend_available: | |
# Use the backend.app.main module's routes | |
return backend.app.main.app.get(full_path) | |
else: | |
return {"error": "Backend API not available"} | |
# Skip static file paths | |
if full_path.startswith("static/") or full_path.startswith("assets/"): | |
raise HTTPException(status_code=404, detail="Not found") | |
logger.info(f"Catch-all route hit for path: {full_path}, serving index.html for client-side routing") | |
return HTMLResponse(content=get_index_html()) | |
# For Hugging Face Spaces - expose the app | |
if __name__ == "__main__": | |
import uvicorn | |
port = int(os.environ.get("PORT", 7860)) | |
host = os.environ.get("HOST", "0.0.0.0") | |
logger.info(f"Starting server on {host}:{port}") | |
uvicorn.run(app, host=host, port=port) |