from fastapi import FastAPI, HTTPException from fastapi.responses import JSONResponse from fastapi.exceptions import ResponseValidationError from starlette.requests import Request from fastapi.staticfiles import StaticFiles from pydantic import BaseModel from models.schemas import ( SEORequest, SEOResponse, YouTubeResponse, InstagramResponse, LinkedInResponse, FacebookResponse, TwitterResponse, TikTokResponse, PinterestResponse, GrammarRequest, GrammarResponse, BatchRequest ) from services.batch_analyzer import analyze_batch from services.analyzer import analyze_seo_content from services.youtube_analyzer import analyze_youtube from services.instagram_analyzer import analyze_instagram from services.linkedin_analyzer import analyze_linkedin from services.linkedin_api import get_oauth_url, exchange_code, get_user_info, create_post from services.facebook_analyzer import analyze_facebook from services.twitter_analyzer import analyze_twitter from services.tiktok_analyzer import analyze_tiktok from services.pinterest_analyzer import analyze_pinterest from services.grammar_analyzer import analyze_grammar import uvicorn import traceback app = FastAPI( title="Social Media SEO & Grammar API", description="Professional social media content analysis + grammar correction using Generative AI. Covers YouTube, Instagram, LinkedIn, Facebook, X/Twitter, TikTok, Pinterest, and grammar correction.", version="2.1.0" ) ERROR_MASK = "An internal error occurred. Please try again later." ERROR_MASK_VALIDATION = "The model returned an incomplete response. Please try again." @app.exception_handler(ResponseValidationError) async def validation_exception_handler(request: Request, exc: ResponseValidationError): print(f"[ValidationError] {exc}") return JSONResponse( status_code=200, content={"error": ERROR_MASK_VALIDATION}, ) def safe_analyze(analyze_fn, content: str): try: result = analyze_fn(content) if isinstance(result, dict) and result.get("error"): print(f"[Warning] Analysis completed with error: {result['error']}") return result except HTTPException: raise except Exception as e: print(f"CRITICAL SERVER ERROR: {e}\n{traceback.format_exc()}") raise HTTPException(status_code=500, detail=ERROR_MASK) # --------------------------------------------------------------------------- # Root - Serve static frontend # --------------------------------------------------------------------------- app.mount("/static", StaticFiles(directory="static"), name="static") @app.get("/") async def read_root(): from fastapi.responses import FileResponse return FileResponse("static/index.html") # --------------------------------------------------------------------------- # Generic SEO # --------------------------------------------------------------------------- @app.post("/analyze-seo", response_model=SEOResponse) def analyze_seo(request: SEORequest): return safe_analyze(analyze_seo_content, request.content) # --------------------------------------------------------------------------- # YouTube # --------------------------------------------------------------------------- @app.post("/analyze/youtube", response_model=YouTubeResponse) def analyze_youtube_route(request: SEORequest): return safe_analyze(analyze_youtube, request.content) # --------------------------------------------------------------------------- # Instagram # --------------------------------------------------------------------------- @app.post("/analyze/instagram", response_model=InstagramResponse) def analyze_instagram_route(request: SEORequest): return safe_analyze(analyze_instagram, request.content) # --------------------------------------------------------------------------- # LinkedIn # --------------------------------------------------------------------------- @app.post("/analyze/linkedin", response_model=LinkedInResponse) def analyze_linkedin_route(request: SEORequest): return safe_analyze(analyze_linkedin, request.content) @app.get("/api/linkedin/auth-url") def linkedin_auth_url(state: str = ""): url = get_oauth_url(state) return {"url": url} class TokenExchangeRequest(BaseModel): code: str @app.post("/api/linkedin/exchange-token") def linkedin_exchange_token(req: TokenExchangeRequest): token_data, err = exchange_code(req.code) if err: raise HTTPException(status_code=400, detail=err) user_info, uerr = get_user_info(token_data["access_token"]) if uerr: raise HTTPException(status_code=400, detail=uerr) return { "access_token": token_data["access_token"], "expires_at": token_data["expires_at"], "user": user_info, } class PostToLinkedInRequest(BaseModel): access_token: str author_urn: str text: str hashtags: list[str] = [] @app.post("/api/linkedin/post") def linkedin_post(req: PostToLinkedInRequest): result, err = create_post(req.access_token, req.author_urn, req.text, req.hashtags) if err: raise HTTPException(status_code=400, detail=err) return {"success": True, **result} # --------------------------------------------------------------------------- # Facebook # --------------------------------------------------------------------------- @app.post("/analyze/facebook", response_model=FacebookResponse) def analyze_facebook_route(request: SEORequest): return safe_analyze(analyze_facebook, request.content) # --------------------------------------------------------------------------- # X / Twitter # --------------------------------------------------------------------------- @app.post("/analyze/twitter", response_model=TwitterResponse) def analyze_twitter_route(request: SEORequest): return safe_analyze(analyze_twitter, request.content) # --------------------------------------------------------------------------- # TikTok # --------------------------------------------------------------------------- @app.post("/analyze/tiktok", response_model=TikTokResponse) def analyze_tiktok_route(request: SEORequest): return safe_analyze(analyze_tiktok, request.content) # --------------------------------------------------------------------------- # Pinterest # --------------------------------------------------------------------------- @app.post("/analyze/pinterest", response_model=PinterestResponse) def analyze_pinterest_route(request: SEORequest): return safe_analyze(analyze_pinterest, request.content) # --------------------------------------------------------------------------- # Grammar Correction # --------------------------------------------------------------------------- @app.post("/analyze/grammar", response_model=GrammarResponse) def grammar_correction_route(request: GrammarRequest): return safe_analyze(analyze_grammar, request.text) # --------------------------------------------------------------------------- # Batch - Multi-Platform # --------------------------------------------------------------------------- class BatchResponse(BaseModel): results: dict @app.post("/analyze/batch", response_model=BatchResponse) def batch_analyze_route(request: BatchRequest): results = safe_analyze(lambda c: analyze_batch(c, request.platforms), request.content) return {"results": results} # --------------------------------------------------------------------------- # Entrypoint # --------------------------------------------------------------------------- if __name__ == "__main__": uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)