File size: 4,853 Bytes
69be42f
 
 
 
 
 
 
 
 
 
dc3879e
 
69be42f
 
 
 
 
dc3879e
 
69be42f
 
 
dc3879e
 
 
69be42f
 
 
 
 
 
dc3879e
69be42f
 
 
 
 
 
dc3879e
 
 
69be42f
 
dc3879e
69be42f
 
dc3879e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69be42f
 
 
 
 
 
 
 
 
 
 
 
dc3879e
 
69be42f
 
 
 
 
 
 
dc3879e
69be42f
 
 
 
 
 
 
 
dc3879e
 
 
 
 
 
69be42f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dc3879e
69be42f
 
 
 
 
 
 
 
dc3879e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69be42f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
"""FastAPI application entry point.

[Task]: T047
[From]: specs/001-user-auth/plan.md
"""
import logging
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from datetime import datetime
import time

from core.database import init_db, engine
from core.config import get_settings
from api.auth import router as auth_router
from api.tasks import router as tasks_router
from api.chat import router as chat_router
from core.logging import setup_logging, get_logger

settings = get_settings()

# Setup structured logging
setup_logging()
logger = get_logger(__name__)


@asynccontextmanager
async def lifespan(app: FastAPI):
    """Application lifespan manager.

    Handles startup and shutdown events with graceful connection cleanup.
    """
    # Startup
    logger.info("Starting up application...")
    init_db()
    logger.info("Database initialized")

    # Track background tasks for graceful shutdown
    background_tasks = set()

    yield

    # Shutdown - Graceful shutdown handler
    logger.info("Shutting down application...")

    # Close database connections
    try:
        logger.info("Closing database connections...")
        await engine.dispose()
        logger.info("Database connections closed")
    except Exception as e:
        logger.error(f"Error closing database: {e}")

    # Wait for background tasks to complete (with timeout)
    if background_tasks:
        logger.info(f"Waiting for {len(background_tasks)} background tasks to complete...")
        try:
            # Wait up to 10 seconds for tasks to complete
            import asyncio
            await asyncio.wait_for(asyncio.gather(*background_tasks, return_exceptions=True), timeout=10.0)
            logger.info("All background tasks completed")
        except asyncio.TimeoutError:
            logger.warning("Background tasks did not complete in time, forcing shutdown...")

    logger.info("Application shutdown complete")


# Create FastAPI application
app = FastAPI(
    title="Todo List API",
    description="REST API for managing tasks with JWT authentication",
    version="1.0.0",
    lifespan=lifespan,
)

# Add CORS middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=[settings.frontend_url],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Include routers
app.include_router(auth_router)  # Authentication endpoints
app.include_router(tasks_router)  # Task management endpoints
app.include_router(chat_router)  # AI chat endpoints (Phase III)


@app.get("/")
async def root():
    """Root endpoint."""
    return {
        "message": "Todo List API",
        "status": "running",
        "version": "2.0.0",
        "authentication": "JWT",
        "features": {
            "task_management": "REST API for CRUD operations",
            "ai_chatbot": "Natural language task creation and listing"
        }
    }


@app.get("/health")
async def health_check():
    """Health check endpoint.

    Verifies database connectivity and application status.
    Returns 503 if database is unavailable.

    [Task]: T048
    [From]: specs/001-user-auth/plan.md
    """
    from sqlmodel import select
    from models.user import User
    from sqlmodel import Session

    # Try to get database session
    try:
        # Create a simple query to test database connection
        with Session(engine) as session:
            # Execute a simple query (doesn't matter if it returns data)
            session.exec(select(User).limit(1))
        return {"status": "healthy", "database": "connected", "timestamp": datetime.utcnow().isoformat()}
    except Exception as e:
        logger.error(f"Health check failed: {e}")
        raise HTTPException(
            status_code=503,
            detail="Service unavailable - database connection failed"
        )


@app.get("/metrics")
async def metrics():
    """Metrics endpoint for monitoring.

    Returns basic application metrics for Kubernetes health probes.
    """
    return {
        "status": "running",
        "timestamp": datetime.utcnow().isoformat(),
        "uptime_seconds": time.time(),
        "version": "1.0.0",
        "database": "connected"  # Simplified - in production would check actual DB status
    }


# Global exception handler
@app.exception_handler(HTTPException)
async def http_exception_handler(request, exc):
    """Global HTTP exception handler.

    Returns consistent error format for all HTTP exceptions.

    [Task]: T046
    [From]: specs/001-user-auth/research.md
    """
    return JSONResponse(
        status_code=exc.status_code,
        content={
            "error": {
                "status_code": exc.status_code,
                "detail": exc.detail
            }
        }
    )