Spaces:
Running
Running
| import os | |
| import re | |
| import traceback | |
| from fastapi import FastAPI, HTTPException, Depends, Request, status | |
| from pydantic import BaseModel | |
| from dotenv import load_dotenv | |
| from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials | |
| import jwt | |
| from jwt import PyJWKClient | |
| import base64 | |
| from slowapi import Limiter, _rate_limit_exceeded_handler | |
| from slowapi.util import get_remote_address | |
| from slowapi.errors import RateLimitExceeded | |
| # Load environment variables from .env file | |
| load_dotenv() | |
| if not os.getenv("GROQ_API_KEY") and not os.getenv("HF_TOKEN"): | |
| load_dotenv(dotenv_path='.env.example') | |
| if not os.getenv("GROQ_API_KEY") and os.getenv("HF_TOKEN"): | |
| os.environ["GROQ_API_KEY"] = os.getenv("HF_TOKEN") | |
| from humanizer import humanize_text | |
| from usage import check_usage_limit, increment_usage, get_user_profile, upgrade_user_plan, cancel_user_subscription | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi import Request | |
| import hmac | |
| import json | |
| import razorpay | |
| RAZORPAY_KEY_ID = os.getenv("RAZORPAY_KEY_ID") | |
| RAZORPAY_KEY_SECRET = os.getenv("RAZORPAY_KEY_SECRET") | |
| RAZORPAY_WEBHOOK_SECRET = os.getenv("RAZORPAY_WEBHOOK_SECRET") | |
| RAZORPAY_STARTER_PLAN_ID = os.getenv("RAZORPAY_STARTER_PLAN_ID") | |
| RAZORPAY_PRO_PLAN_ID = os.getenv("RAZORPAY_PRO_PLAN_ID") | |
| razorpay_client = razorpay.Client(auth=(RAZORPAY_KEY_ID or "", RAZORPAY_KEY_SECRET or "")) | |
| SUPABASE_URL = os.getenv("SUPABASE_URL") | |
| JWKS_URL = f"{SUPABASE_URL}/auth/v1/.well-known/jwks.json" if SUPABASE_URL else "" | |
| jwks_client = PyJWKClient(JWKS_URL) if JWKS_URL else None | |
| security = HTTPBearer() | |
| def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)): | |
| token = credentials.credentials | |
| try: | |
| if not jwks_client: | |
| raise ValueError("SUPABASE_URL is not set.") | |
| signing_key = jwks_client.get_signing_key_from_jwt(token) | |
| payload = jwt.decode( | |
| token, | |
| signing_key.key, | |
| algorithms=["HS256", "RS256", "ES256"], | |
| audience="authenticated", | |
| options={"verify_aud": True} | |
| ) | |
| user_id = payload.get("sub") | |
| if not user_id: | |
| raise HTTPException(status_code=401, detail="Invalid token claims") | |
| return user_id | |
| except Exception as e: | |
| raise HTTPException(status_code=401, detail=f"Invalid or expired token: {str(e)}") | |
| INJECTION_PATTERNS = [ | |
| r"ignore\s*(all\s*)?(previous|prior|above)\s*(instructions?|prompts?|context)", | |
| r"forget\s*(what|everything|all)\s*(you\s*)?(were\s*)?(told|said|given|know)", | |
| r"you\s*are\s*now\s*(a|an|the)", | |
| r"act\s*as\s*(if\s*)?(you\s*are\s*)?(a|an|the)", | |
| r"new\s*(system\s*)?prompt", | |
| r"override\s*(system|instructions?|prompt)", | |
| r"pretend\s*(you\s*are|to\s*be)", | |
| r"\b(DAN|STAN)\b", | |
| r"(jailbreak|developer\s*mode|god\s*mode)", | |
| r"disregard\s*(all\s*)?(previous|prior)\s*(instructions?|rules?)", | |
| r"your\s*(true|real|actual)\s*(self|purpose|goal|task)", | |
| ] | |
| def sanitize_input(text: str) -> str: | |
| for pattern in INJECTION_PATTERNS: | |
| if re.search(pattern, text, re.IGNORECASE): | |
| raise HTTPException(status_code=400, detail="Input contains disallowed content.") | |
| try: | |
| decoded = base64.b64decode(text + "==").decode("utf-8", errors="ignore") | |
| for pattern in INJECTION_PATTERNS: | |
| if re.search(pattern, decoded, re.IGNORECASE): | |
| raise HTTPException(status_code=400, detail="Input contains disallowed content.") | |
| except Exception: | |
| pass | |
| return text | |
| app = FastAPI(title="AI Humanizer API") | |
| limiter = Limiter(key_func=get_remote_address) | |
| app.state.limiter = limiter | |
| app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=[ | |
| "https://humanizer-frontend-beige.vercel.app", | |
| "http://localhost:3000" | |
| ], | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| class HumanizeRequest(BaseModel): | |
| text: str | |
| mode: str = "standard" | |
| readability: str = "neutral" | |
| purpose: str = "professional" | |
| class HumanizeResponse(BaseModel): | |
| humanized: str | |
| mode: str | |
| readability: str | |
| purpose: str | |
| usage: dict # { count, limit, plan } | |
| class CreateOrderRequest(BaseModel): | |
| plan: str | |
| class VerifyPaymentRequest(BaseModel): | |
| razorpay_order_id: str | |
| razorpay_payment_id: str | |
| razorpay_signature: str | |
| user_id: str | |
| plan: str | |
| async def humanize(request: Request, body: HumanizeRequest, user_id: str = Depends(verify_token)): | |
| # Force enhanced mode as requested | |
| body.mode = "enhanced" | |
| # Check usage limit | |
| usage = check_usage_limit(user_id) | |
| if not usage["allowed"]: | |
| raise HTTPException( | |
| status_code=402, | |
| detail={ | |
| "message": "You have reached your free limit. Please upgrade to continue.", | |
| "plan": usage["plan"], | |
| "count": usage["count"], | |
| "limit": usage["limit"], | |
| } | |
| ) | |
| clean_text = sanitize_input(body.text) | |
| if not clean_text: | |
| raise HTTPException( | |
| status_code=400, | |
| detail="Input text is empty or invalid after sanitization." | |
| ) | |
| try: | |
| humanized_text = await humanize_text( | |
| clean_text, | |
| mode=body.mode, | |
| readability=body.readability, | |
| purpose=body.purpose, | |
| ) | |
| # Increment usage after successful humanization | |
| increment_usage(user_id) | |
| return HumanizeResponse( | |
| humanized=humanized_text, | |
| mode=body.mode, | |
| readability=body.readability, | |
| purpose=body.purpose, | |
| usage={ | |
| "count": usage["count"] + 1, | |
| "limit": usage["limit"], | |
| "plan": usage["plan"], | |
| } | |
| ) | |
| except Exception as e: | |
| import traceback | |
| raise HTTPException(status_code=500, detail=traceback.format_exc()) | |
| async def get_user_plan(user_id: str = Depends(verify_token)): | |
| profile = get_user_profile(user_id) | |
| if not profile: | |
| raise HTTPException(status_code=404, detail="User not found") | |
| return {"plan": profile.get("plan", "free")} | |
| async def create_order(request: Request, body: CreateOrderRequest, user_id: str = Depends(verify_token)): | |
| if not user_id: | |
| raise HTTPException(status_code=401, detail="Unauthorized") | |
| if body.plan not in ["starter", "pro"]: | |
| raise HTTPException(status_code=400, detail="Invalid plan") | |
| # Map plans to amounts (e.g. Starter = $9 = ₹750 = 75000 paise) | |
| amount = 75000 if request.plan == "starter" else 150000 | |
| if amount < 100: | |
| raise HTTPException(status_code=400, detail="Amount must be at least 100 paise") | |
| try: | |
| order_data = { | |
| "amount": amount, | |
| "currency": "INR", | |
| "receipt": request.user_id | |
| } | |
| order = razorpay_client.order.create(order_data) | |
| return { | |
| "order_id": order["id"], | |
| "amount": amount, | |
| "currency": "INR", | |
| "key_id": RAZORPAY_KEY_ID | |
| } | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def verify_payment(request: VerifyPaymentRequest): | |
| if not request.razorpay_order_id or not request.razorpay_payment_id or not request.razorpay_signature: | |
| raise HTTPException(status_code=400, detail="Missing required fields") | |
| try: | |
| expected_signature = hmac.new( | |
| key=RAZORPAY_KEY_SECRET.encode('utf-8'), | |
| msg=(request.razorpay_order_id + "|" + request.razorpay_payment_id).encode('utf-8'), | |
| digestmod='sha256' | |
| ).hexdigest() | |
| if not hmac.compare_digest(expected_signature, request.razorpay_signature): | |
| raise HTTPException(status_code=400, detail="Invalid signature") | |
| profile = get_user_profile(request.user_id) | |
| if profile and profile.get("plan") != request.plan: | |
| upgrade_user_plan(request.user_id, request.plan) | |
| return {"status": "ok"} | |
| except HTTPException: | |
| raise | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run("main:app", host="127.0.0.1", port=8000, reload=True) | |