File size: 2,797 Bytes
43a618c
aaabbda
 
 
 
 
 
 
 
 
 
43a618c
 
 
aaabbda
 
43a618c
aaabbda
 
 
 
 
43a618c
aaabbda
 
 
 
43a618c
aaabbda
 
 
 
 
 
 
43a618c
aaabbda
 
 
 
 
 
 
 
 
 
43a618c
 
 
 
 
 
aaabbda
 
 
 
 
 
 
 
 
43a618c
aaabbda
 
43a618c
 
 
 
 
aaabbda
 
43a618c
aaabbda
 
 
43a618c
 
 
 
 
 
 
 
 
 
 
 
 
 
aaabbda
 
 
43a618c
 
 
 
 
aaabbda
 
43a618c
 
 
 
aaabbda
 
 
 
 
 
 
 
43a618c
aaabbda
 
 
43a618c
 
 
 
 
 
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
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
    }