File size: 3,860 Bytes
e97bd0e
20fa64f
e97bd0e
20fa64f
e97bd0e
20fa64f
ba58433
3bc3e37
c1917a8
ba58433
41751c4
 
2ff64f6
 
 
 
41751c4
 
e97bd0e
41751c4
 
 
 
c293a5f
20fa64f
 
41751c4
 
f40b2cf
ef8ae4e
 
 
 
 
41751c4
 
 
 
 
 
 
3bc3e37
 
2ff64f6
20fa64f
41751c4
3bc3e37
 
5fb1edb
 
 
c1917a8
 
5fb1edb
c1917a8
 
 
 
 
41751c4
 
 
 
c293a5f
20fa64f
 
3bc3e37
ba58433
 
 
 
 
 
 
 
 
68ee171
2ff64f6
 
 
 
 
 
 
 
20fa64f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import os
from pathlib import Path

from app.core.env_loader import load_application_env

load_application_env()

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from contextlib import asynccontextmanager

# Load configuration FIRST so every module can use it
from app.config import load_settings
settings = load_settings()

# Import the new database functions
from app.core.database import connect_to_mongo, close_mongo_connection

# Import all route modules
from app.api.routes import router as main_router
from app.api.routes.auth import router as auth_router
from app.api.routes.chat_sessions import router as chat_sessions_router
from app.api.routes.phd_canvas import router as phd_canvas_router
from app.api.routes.user_profile import router as user_profile_router
from app.api.routes.onboarding import router as onboarding_router

import logging

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)

@asynccontextmanager
async def lifespan(app: FastAPI):
    # Startup
    await connect_to_mongo()
    yield
    # Shutdown
    await close_mongo_connection()

app = FastAPI(
    title=f"{settings.app.title} Backend",
    version="2.0.0",
    lifespan=lifespan
)

cors_origins = os.getenv("CORS_ORIGINS", "http://localhost:3000").split(",")
cors_origins = [origin.strip() for origin in cors_origins]  # Clean whitespace

app.add_middleware(
    CORSMiddleware,
    allow_origins=cors_origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Include all routers
app.include_router(main_router)
app.include_router(auth_router, prefix="/auth", tags=["authentication"])
app.include_router(chat_sessions_router, prefix="/api", tags=["chat-sessions"])
app.include_router(phd_canvas_router, prefix="/api", tags=["phd-canvas"])
app.include_router(user_profile_router, prefix="/api", tags=["user-profile"])
app.include_router(onboarding_router, prefix="/api", tags=["onboarding"])

# Serve bundled avatar images
_avatars_dir = Path(__file__).resolve().parent / "assets" / "avatars"
if _avatars_dir.is_dir():
    app.mount(
        "/api/avatars/bundled",
        StaticFiles(directory=_avatars_dir),
        name="bundled-avatars",
    )


# ---------------------------------------------------------------------------
# Public configuration endpoint — serves the frontend-safe subset
# ---------------------------------------------------------------------------
@app.get("/api/config")
def get_public_config():
    """Return the public (non-secret) application configuration."""
    return settings.get_frontend_config()


# ---------------------------------------------------------------------------
# Static SPA mount — Hugging Face Spaces / single-container deployment
# ---------------------------------------------------------------------------
# When the Docker build copies the React production bundle into ``./static``
# (sibling of this app/ directory), expose it at "/" so the API and the
# SPA share the FastAPI origin. In local development the static dir is
# absent and a JSON banner is returned at "/" instead.
_static_dir = Path(__file__).resolve().parent.parent / "static"
_should_mount_static = _static_dir.is_dir()

if _should_mount_static:
    app.mount(
        "/",
        StaticFiles(directory=str(_static_dir), html=True),
        name="spa",
    )
else:
    @app.get("/")
    def root():
        return {
            "message": f"{settings.app.title} Backend",
            "version": "2.0.0",
            "features": [
                "User Authentication",
                "Persistent Chat Sessions (SQLite via aiosqlite)",
                "Ollama Support",
                "Gemini API Support",
                "Configurable Personas",
            ],
        }