File size: 4,681 Bytes
b84ea83
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
Apl_GS API β€” FastAPI Application Entry Point

Run:
    python app.py

Or directly with uvicorn:
    uvicorn app:app --host 0.0.0.0 --port 5000 --reload

API Docs:
    http://localhost:5000/docs      (Swagger UI)
    http://localhost:5000/redoc     (ReDoc)

Architecture Reference:
    insights/architecture_rule.md
"""

import os
import logging
from contextlib import asynccontextmanager

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from dotenv import load_dotenv

# Load environment variables FIRST (before any config imports)
load_dotenv()

from web.route import router
from config.rate_limiter import rate_limit_middleware
from config.database_config import init_db, close_db

# ── Logging ──
logging.basicConfig(
    level=os.getenv("LOG_LEVEL", "INFO"),
    format="%(asctime)s β€” %(name)s β€” %(levelname)s β€” %(message)s",
)
logger = logging.getLogger(__name__)

# ── App Metadata ──
APP_NAME = os.getenv("APP_NAME", "Apl_GS API")
APP_VERSION = "1.0.0"
APP_ENV = os.getenv("APP_ENV", "development")
APP_DEBUG = os.getenv("APP_DEBUG", "False").lower() == "true"


# ── Lifespan (startup + shutdown) ──
@asynccontextmanager
async def lifespan(app: FastAPI):
    """Application lifecycle β€” runs on startup and shutdown."""
    # ── STARTUP ──
    logger.info(f"πŸš€ Starting {APP_NAME} v{APP_VERSION}")
    logger.info(f"πŸ“ Environment: {APP_ENV}")
    logger.info(f"πŸ”§ Debug mode: {APP_DEBUG}")
    logger.info(f"🌐 Port: {os.getenv('PORT', '5000')}")

    # Initialize database tables
    try:
        await init_db()
        logger.info("βœ… Database initialized successfully")
    except Exception as e:
        logger.error(f"❌ Database initialization failed: {e}")
        logger.warning("⚠️  API will start but database operations will fail")
        logger.warning("⚠️  Make sure PostgreSQL 15+ is installed and running!")

    yield  # Application runs here

    # ── SHUTDOWN ──
    logger.info("πŸ›‘ Shutting down Apl_GS API")
    await close_db()


# ── Create FastAPI Instance ──
app = FastAPI(
    title=APP_NAME,
    version=APP_VERSION,
    description=(
        "Apl_GS Backend API β€” Visit & Checklist Management System.\n\n"
        "**No authentication required** β€” protected by rate limiter.\n\n"
        "Built with FastAPI + PostgreSQL 15+.\n\n"
        "πŸ“– Architecture Reference: `insights/architecture_rule.md`"
    ),
    docs_url="/docs",
    redoc_url="/redoc",
    openapi_url="/openapi.json",
    lifespan=lifespan,
)

# ── CORS Middleware ──
cors_origins = os.getenv(
    "CORS_ORIGINS", "http://localhost"
).split(",")

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

# ── Rate Limiter Middleware ──
app.middleware("http")(rate_limit_middleware)

# ── Include API Routes ──
app.include_router(router)


# ── Root Endpoint ──
@app.get("/", tags=["Root"], summary="API root β€” basic info and links")
async def root():
    """
    Root endpoint β€” returns API info, version, and documentation links.
    """
    return {
        "status": "success",
        "data": {
            "name": APP_NAME,
            "version": APP_VERSION,
            "environment": APP_ENV,
            "docs": "/docs",
            "redoc": "/redoc",
            "health": "/api/v1/health",
        },
        "message": "Apl_GS API is running",
    }


# ── Global Exception Handler ──
@app.exception_handler(Exception)
async def global_exception_handler(request, exc):
    """Handle unexpected exceptions gracefully."""
    logger.error(f"Unhandled exception: {exc}", exc_info=True)
    return JSONResponse(
        status_code=500,
        content={
            "status": "error",
            "error": {
                "code": "INTERNAL_ERROR",
                "message": "An unexpected error occurred",
                "details": str(exc) if APP_DEBUG else None,
            },
        },
    )


# ── Run with Uvicorn ──
if __name__ == "__main__":
    import uvicorn

    host = os.getenv("HOST", "0.0.0.0")
    port = int(os.getenv("PORT", "5000"))

    logger.info(f"🌟 Running on http://{host}:{port}")
    logger.info(f"πŸ“š Swagger UI:  http://{host}:{port}/docs")
    logger.info(f"πŸ“š ReDoc:       http://{host}:{port}/redoc")

    uvicorn.run(
        "app:app",
        host=host,
        port=port,
        reload=(APP_ENV == "development"),
        log_level=os.getenv("LOG_LEVEL", "info").lower(),
    )