File size: 3,221 Bytes
00a4363
34b0226
00a4363
 
 
 
 
 
 
 
 
34b0226
 
 
 
 
 
b74c6ae
34b0226
0649f1b
34b0226
0649f1b
34b0226
 
b74c6ae
34b0226
 
070c931
 
 
 
34b0226
 
 
 
00a4363
 
 
 
eb03313
 
 
 
 
 
 
 
 
 
 
 
00a4363
 
 
 
34b0226
00a4363
34b0226
00a4363
 
34b0226
 
 
 
 
 
00a4363
34b0226
00a4363
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
070c931
34b0226
0649f1b
00a4363
 
34b0226
 
 
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
import logging
import threading
import time
import uuid
from contextlib import asynccontextmanager

from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware

from app.config import settings
from app.llm.provider import configure_default
from app.routers import (
    artifacts,
    auth,
    billing,
    jobs,
    launchpad,
    memory,
    notebooks,
    reports,
    resurface,
    share,
    sources,
    storage,
    support,
    topics,
    unsubscribe,
    video_edit,
    video_styles,
    video_voices,
    videos,
    voice,
)
from app.routers import settings as settings_router
from app.services.storage import storage as store

log = logging.getLogger("notestack")


def _drop_color_message(record: logging.LogRecord) -> bool:
    """litellm (via dspy) adds a secret redaction filter to uvicorn.error that formats the message
    and clears record.args. uvicorn's colored formatter then swaps in its color_message template
    and prints it with no args ("Uvicorn running on %s://%s:%d"). Without the template it prints
    the already formatted, redacted message."""
    record.__dict__.pop("color_message", None)
    return True


logging.getLogger("uvicorn.error").addFilter(_drop_color_message)


@asynccontextmanager
async def lifespan(app: FastAPI):
    logging.basicConfig(level=logging.INFO)
    configure_default()
    if settings.env == "development" and (settings.r2_endpoint_url or settings.use_local_storage):
        try:
            store.ensure_bucket()  # MinIO or the local storage folder
        except Exception:
            log.warning("could not ensure local bucket", exc_info=True)
    stop = threading.Event()
    if settings.worker_in_api:
        from app.worker import start_background

        start_background(stop)
        log.info("worker running inside the API (RUN_WORKER_IN_API)")
    yield
    stop.set()


app = FastAPI(title="Notestack API", version="0.1.0", lifespan=lifespan)
app.add_middleware(
    CORSMiddleware,
    allow_origins=settings.cors_origin_list,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


@app.middleware("http")
async def request_id(request: Request, call_next):
    rid = request.headers.get("x-request-id") or uuid.uuid4().hex[:12]
    start = time.perf_counter()
    response = await call_next(request)
    response.headers["x-request-id"] = rid
    log.info("%s %s %s %.0fms rid=%s", request.method, request.url.path, response.status_code,
             (time.perf_counter() - start) * 1000, rid)
    return response


@app.get("/api/health")
def health():
    return {"ok": True, "llm": settings.llm_model, "billing_enabled": settings.billing_enabled}


# video_templates (custom templates) is switched off: users pick only from the built-in templates.
for r in (auth, billing, storage, sources, notebooks, artifacts, jobs, unsubscribe, topics, voice, resurface,
          launchpad, videos, video_edit, video_voices, video_styles, settings_router, memory, support, reports, share):
    app.include_router(r.router)
app.include_router(sources.documents_router)
app.include_router(launchpad.links_router)
if settings.use_local_storage:
    app.include_router(storage.local_router)