Nagesh Muralidhar
Simplify login, signup, and token handlers by directly accessing database and handling authentication
b7038e5
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("""<!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
@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 = '''
<script>
(function() {
console.log("Advanced URL Fixer running...");
// Intercept all network requests
const originalFetch = window.fetch;
window.fetch = function(url, options) {
if (typeof url === 'string') {
// Rewrite localhost:8000
if (url.includes('localhost:8000')) {
url = url.replace('http://localhost:8000', '');
url = url.replace('https://localhost:8000', '');
console.log(`Rewriting URL from localhost:8000 to ${url}`);
}
// Handle relative URLs for API endpoints
if (url.startsWith('/')) {
if (!url.startsWith('/static/') && !url.startsWith('/assets/')) {
console.log(`Using relative URL: ${url}`);
}
}
}
return originalFetch(url, options);
};
// Override XMLHttpRequest
const originalOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url, ...args) {
if (typeof url === 'string') {
// Rewrite localhost:8000
if (url.includes('localhost:8000')) {
url = url.replace('http://localhost:8000', '');
url = url.replace('https://localhost:8000', '');
console.log(`Rewriting XHR URL from localhost:8000 to ${url}`);
}
}
return originalOpen.call(this, method, url, ...args);
};
// Attempt to hook into axios if it's being used
if (window.axios) {
const originalAxiosGet = window.axios.get;
window.axios.get = function(url, config) {
if (typeof url === 'string' && url.includes('localhost:8000')) {
url = url.replace('http://localhost:8000', '');
console.log(`Rewriting axios URL from localhost:8000 to ${url}`);
}
return originalAxiosGet(url, config);
};
const originalAxiosPost = window.axios.post;
window.axios.post = function(url, data, config) {
if (typeof url === 'string' && url.includes('localhost:8000')) {
url = url.replace('http://localhost:8000', '');
console.log(`Rewriting axios POST URL from localhost:8000 to ${url}`);
}
return originalAxiosPost(url, data, config);
};
}
console.log("Advanced URL Fixer installed");
})();
</script>
'''
if '</body>' in html_content:
html_content = html_content.replace('</body>', f'{script_tag}</body>')
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)