File size: 6,011 Bytes
f201243 b8b7791 d4a4da7 f201243 62b339b d4a4da7 f201243 d4a4da7 f201243 d4a4da7 f201243 b7334a4 f201243 d4a4da7 7906542 d4a4da7 7906542 d4a4da7 7906542 f201243 b8b7791 f201243 b8b7791 d4a4da7 f201243 d4a4da7 f201243 d4a4da7 f201243 d4a4da7 f201243 d4a4da7 f201243 d4a4da7 f201243 45ef06a f201243 d4a4da7 f201243 d4a4da7 b7334a4 f201243 b7334a4 7551720 f201243 7551720 d4a4da7 7551720 d4a4da7 7551720 d4a4da7 addcf34 d4a4da7 7551720 b7334a4 7551720 addcf34 7551720 45ef06a 7551720 d4a4da7 7551720 d4a4da7 b7334a4 45ef06a b7334a4 d4a4da7 b7334a4 d4a4da7 b7334a4 f201243 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
"""
PsyAdGenesis - FastAPI Application
Design ads that stop the scroll. Generate high-converting ad creatives for Home Insurance and GLP-1 niches.
Saves all ads to Neon PostgreSQL database with image URLs.
"""
import sys
from pathlib import Path
# Ensure project root is on path (fixes ModuleNotFoundError when run from /app or other cwd)
_root = Path(__file__).resolve().parent
if str(_root) not in sys.path:
sys.path.insert(0, str(_root))
import os
from contextlib import asynccontextmanager
import httpx
from fastapi import FastAPI, Request, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import Response as FastAPIResponse
from starlette.middleware.gzip import GZipMiddleware
from starlette.requests import Request as StarletteRequest
from services.database import db_service
from config import settings
from api.routers import get_all_routers
# Configure logging for API
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Startup and shutdown events."""
print("Starting PsyAdGenesis...")
await db_service.connect()
yield
print("Shutting down...")
await db_service.disconnect()
app = FastAPI(
title="PsyAdGenesis",
description="Design ads that stop the scroll. Generate high-converting ad creatives using psychological triggers and AI-powered image generation.",
version="2.0.0",
lifespan=lifespan,
)
# Middleware
app.add_middleware(GZipMiddleware, minimum_size=1000)
cors_origins = [
"http://localhost:3000",
"http://127.0.0.1:3000",
]
if os.getenv("CORS_ORIGINS"):
cors_origins.extend([o.strip() for o in os.getenv("CORS_ORIGINS").split(",")])
app.add_middleware(
CORSMiddleware,
allow_origins=cors_origins,
allow_origin_regex=r"https://.*\.hf\.space",
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.middleware("http")
async def add_cache_headers(request: Request, call_next):
response = await call_next(request)
if request.url.path.startswith("/images/"):
response.headers["Cache-Control"] = "public, max-age=31536000, immutable"
return response
# Static files
os.makedirs(settings.output_dir, exist_ok=True)
app.mount("/images", StaticFiles(directory=settings.output_dir), name="images")
frontend_static_path = os.path.join(os.path.dirname(__file__), "frontend", ".next", "static")
if os.path.exists(frontend_static_path):
app.mount("/_next/static", StaticFiles(directory=frontend_static_path), name="nextjs_static")
# Include all API routers
for router in get_all_routers():
app.include_router(router)
# Frontend proxy - must be last so it doesn't intercept API routes
@app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"])
async def frontend_proxy(path: str, request: StarletteRequest):
"""
Proxy frontend requests to Next.js server.
Smart routing based on path AND HTTP method.
"""
api_only_routes = [
"auth/login", "api/correct", "api/download-image", "api/export/bulk",
"db/stats", "db/ads", "strategies", "extensive/generate", "extensive/status", "extensive/result",
]
api_post_routes = [
"generate", "generate/batch", "matrix/generate", "matrix/testing",
]
api_get_routes = [
"matrix/angles", "matrix/concepts", "matrix/angle", "matrix/concept",
"matrix/compatible", "db/ad",
]
api_post_routes_additional = ["db/ad/edit"]
if any(path == route or path.startswith(f"{route}/") for route in api_only_routes):
raise HTTPException(status_code=404, detail="API endpoint not found")
if request.method == "POST" and any(path == route or path.startswith(f"{route}/") for route in api_post_routes):
raise HTTPException(status_code=404, detail="API endpoint not found")
if request.method == "POST" and any(path == route or path.startswith(f"{route}/") for route in api_post_routes_additional):
raise HTTPException(status_code=404, detail="API endpoint not found")
if path.startswith("image/") or path.startswith("images/"):
raise HTTPException(status_code=404, detail="API endpoint not found")
if path.startswith("_next/static/"):
raise HTTPException(status_code=404, detail="Static file not found")
if request.method == "GET":
for route in api_get_routes:
if path == route or (path.startswith(f"{route}/") and "/" not in path[len(route) + 1:]):
raise HTTPException(status_code=404, detail="API endpoint not found")
try:
async with httpx.AsyncClient(timeout=30.0) as client:
nextjs_url = f"http://localhost:3000/{path}"
if request.url.query:
nextjs_url += f"?{request.url.query}"
response = await client.request(
method=request.method,
url=nextjs_url,
headers={k: v for k, v in request.headers.items() if k.lower() not in ["host", "content-length"]},
content=await request.body() if request.method in ["POST", "PUT", "PATCH"] else None,
follow_redirects=True,
)
return FastAPIResponse(
content=response.content,
status_code=response.status_code,
headers={k: v for k, v in response.headers.items() if k.lower() not in ["content-encoding", "transfer-encoding", "content-length"]},
media_type=response.headers.get("content-type"),
)
except httpx.RequestError:
raise HTTPException(
status_code=503,
detail="Frontend server is not available. Please ensure Next.js is running on port 3000.",
)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
|