import os import sys import shutil import logging import re 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) # Function to fix localhost URLs in JavaScript files def fix_js_files(): """Fix all JS files in the static directory to replace localhost:8000 with relative URLs""" assets_dir = Path("./static/assets") if not assets_dir.exists(): logger.warning(f"Assets directory not found at {assets_dir}") return logger.info(f"Searching for JS files in {assets_dir}") js_files = list(assets_dir.glob("*.js")) logger.info(f"Found {len(js_files)} JS files") for js_file in js_files: try: logger.info(f"Processing {js_file}") with open(js_file, "r", encoding="utf-8") as f: content = f.read() # Count occurrences before replacement count_before = content.count("localhost:8000") if count_before > 0: logger.info(f"Found {count_before} instances of localhost:8000 in {js_file}") # Replace localhost URLs with relative ones modified_content = content.replace("http://localhost:8000", "") modified_content = modified_content.replace("https://localhost:8000", "") # Create a backup just in case backup_file = js_file.with_suffix(".js.bak") shutil.copy(js_file, backup_file) # Write the modified content back with open(js_file, "w", encoding="utf-8") as f: f.write(modified_content) logger.info(f"Fixed {count_before} localhost URLs in {js_file}") except Exception as e: logger.error(f"Error processing {js_file}: {str(e)}") # Run the JS file fixer at startup fix_js_files() # 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(""" PodCraft API

PodCraft API

The PodCraft API is running. You can access the API at /docs.

API Status: /api/status

""") # 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 "

Index file not found

" 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 @app.get("/", response_class=HTMLResponse) async def root(): logger.info("Root route accessed, serving index.html") html_content = get_index_html() # Inject our URL rewriter scripts script_tag = ''' ''' if '' in html_content: html_content = html_content.replace('', f'{script_tag}') else: html_content = html_content + script_tag return HTMLResponse(content=html_content) # API status endpoint @app.get("/api/status") async def status(): return {"status": "ok", "message": "PodCraft API is running"} # Serve static assets directory with correct content types @app.get("/assets/{file_path:path}") 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 @app.post("/login") async def login_proxy(request: Request): logger.info("Received login request at /login endpoint") if backend_available: try: # Get the JSON data from the request user_data = await request.json() logger.info(f"Login request data: {user_data}") # Extract username and password username = user_data.get("username") password = user_data.get("password") if not username or not password: logger.error("Missing username or password in login request") return JSONResponse( content={"error": "Missing username or password"}, status_code=400 ) # Import database connection and utility functions try: from backend.app.main import users, verify_password, create_access_token from datetime import timedelta except ImportError as e: logger.error(f"Error importing backend modules: {str(e)}") return JSONResponse( content={"error": "Backend configuration error"}, status_code=500 ) # Try to find the user in the database try: user = await users.find_one({"username": username}) if not user: logger.warning(f"User not found: {username}") return JSONResponse( content={"error": "Invalid username or password"}, status_code=401 ) # Verify password if not verify_password(password, user["password"]): logger.warning(f"Invalid password for user: {username}") return JSONResponse( content={"error": "Invalid username or password"}, status_code=401 ) # Create access token access_token = create_access_token( data={"sub": username} ) return JSONResponse( content={"access_token": access_token, "token_type": "bearer"}, status_code=200 ) except Exception as db_error: logger.error(f"Database error in login: {str(db_error)}") return JSONResponse( content={"error": "Database error", "message": str(db_error)}, status_code=500 ) except Exception as e: logger.error(f"General error in login_proxy: {str(e)}") return JSONResponse( content={"error": "Login failed", "message": str(e)}, status_code=500 ) else: logger.error("Backend not available for login") return JSONResponse( content={"error": "Backend API not available for login"}, status_code=503 ) @app.post("/signup") async def signup_proxy(request: Request): logger.info("Received signup request at /signup endpoint") if backend_available: try: # Get the JSON data from the request user_data = await request.json() logger.info(f"Signup request data: {user_data}") # Extract username and password username = user_data.get("username") password = user_data.get("password") if not username or not password: logger.error("Missing username or password in signup request") return JSONResponse( content={"error": "Missing username or password"}, status_code=400 ) # Import database connection and utility functions try: from backend.app.main import users, get_password_hash except ImportError as e: logger.error(f"Error importing backend modules: {str(e)}") return JSONResponse( content={"error": "Backend configuration error"}, status_code=500 ) # Check if username exists try: existing_user = await users.find_one({"username": username}) if existing_user: logger.warning(f"Username already exists: {username}") return JSONResponse( content={"error": "Username already exists"}, status_code=400 ) # Hash the password and create user hashed_password = get_password_hash(password) # Insert the new user new_user = { "username": username, "password": hashed_password } await users.insert_one(new_user) return JSONResponse( content={"message": "User created successfully"}, status_code=201 ) except Exception as db_error: logger.error(f"Database error in signup: {str(db_error)}") return JSONResponse( content={"error": "Database error", "message": str(db_error)}, status_code=500 ) except Exception as e: logger.error(f"General error in signup_proxy: {str(e)}") return JSONResponse( content={"error": "Signup failed", "message": str(e)}, status_code=500 ) else: logger.error("Backend not available for signup") return JSONResponse( content={"error": "Backend API not available for signup"}, status_code=503 ) @app.post("/token") async def token_proxy(request: Request): logger.info("Received token request at /token endpoint") if backend_available: try: # Get form data from the request try: form_data = await request.form() username = form_data.get("username") password = form_data.get("password") if not username or not password: logger.error("Missing username or password in token request") return JSONResponse( content={"error": "Missing username or password"}, status_code=400 ) # Import database connection and utility functions try: from backend.app.main import users, verify_password, create_access_token from datetime import timedelta except ImportError as e: logger.error(f"Error importing backend modules: {str(e)}") return JSONResponse( content={"error": "Backend configuration error"}, status_code=500 ) # Try to find the user in the database try: user = await users.find_one({"username": username}) if not user: logger.warning(f"User not found: {username}") return JSONResponse( content={"error": "Invalid username or password"}, status_code=401 ) # Verify password if not verify_password(password, user["password"]): logger.warning(f"Invalid password for user: {username}") return JSONResponse( content={"error": "Invalid username or password"}, status_code=401 ) # Create access token access_token = create_access_token( data={"sub": username} ) return JSONResponse( content={"access_token": access_token, "token_type": "bearer"}, status_code=200 ) except Exception as db_error: logger.error(f"Database error in token request: {str(db_error)}") return JSONResponse( content={"error": "Database error", "message": str(db_error)}, status_code=500 ) except Exception as form_error: logger.error(f"Error processing form data in token request: {str(form_error)}") return JSONResponse( content={"error": "Invalid form data", "message": str(form_error)}, status_code=400 ) except Exception as e: logger.error(f"General error in token_proxy: {str(e)}") return JSONResponse( content={"error": "Token request failed", "message": str(e)}, status_code=500 ) else: logger.error("Backend not available for token request") return JSONResponse( content={"error": "Backend API not available for token request"}, status_code=503 ) # 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 @app.get("/{full_path:path}") 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)