from fastapi import FastAPI, Depends, Header from fastapi.middleware.cors import CORSMiddleware from contextlib import asynccontextmanager import os, json, base64, time import firebase_admin from firebase_admin import credentials, firestore from sentence_transformers import CrossEncoder from community import router as community_router from algorithm import UpnisoAlgorithmPipeline from auth import verify_api_key from rate_limit import rate_limiter from logs import log_request # -------- GLOBALS -------- db = None pipeline = None ranker = None # -------- LIFESPAN -------- @asynccontextmanager async def lifespan(app: FastAPI): global db, pipeline, ranker # Firebase init key = os.getenv("FIREBASE_KEY_B64") if key and not firebase_admin._apps: cred = credentials.Certificate(json.loads(base64.b64decode(key))) firebase_admin.initialize_app(cred) db = firestore.client() print("✅ Firebase connected") # Algorithm pipeline = UpnisoAlgorithmPipeline() print("✅ Algorithm loaded") # AI ranker ranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L6-v2") print("✅ AI model loaded") yield # -------- APP -------- app = FastAPI( title="Upniso Backend", version="2.0", lifespan=lifespan ) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"], ) # -------- HEALTH -------- @app.get("/") def health(): return { "status": "live", "service": "Upniso API", "time": time.time() } # -------- COMMUNITY ROUTES -------- app.include_router(community_router, prefix="/api/v1") # -------- CORE FEED API (PAID) -------- @app.get("/api/v1/feed") async def feed( x_api_key: str = Header(...), key_data: dict = Depends(verify_api_key) ): # Rate limit based on plan rate_limiter( x_api_key, limit=key_data.get("limit", 50000) ) log_request(x_api_key, "/feed") if not db or not pipeline: return {"status": "error", "feed": []} creators = { d.id: d.to_dict() for d in db.collection("creators").stream() } posts = { d.id: d.to_dict() for d in db.collection("posts") .where("is_draft", "==", False) .limit(50) .stream() } ranked = pipeline.run_simulation(creators, posts) if ranker and ranked: query = "High quality merit-based content" pairs = [[query, r.get("description", "")] for r in ranked] scores = ranker.predict(pairs) for i, r in enumerate(ranked): r["ai_score"] = float(scores[i]) return { "status": "success", "plan": key_data.get("plan"), "count": len(ranked), "feed": ranked }