-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
133 lines (103 loc) · 3.84 KB
/
main.py
File metadata and controls
133 lines (103 loc) · 3.84 KB
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
# app/main.py
from contextlib import asynccontextmanager
from pathlib import Path
import logging
import asyncio
from fastapi import FastAPI, Request
# from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles
from starlette.middleware.sessions import SessionMiddleware
# from fastapi.exceptions import RequestValidationError
# from starlette.exceptions import HTTPException as StarletteHTTPException
from fastapi.templating import Jinja2Templates
from app.routes import ui_router
from app.utils import db
from app.utils.cache import cleanup_expired
# -----------------------------
# Background cache cleanup task
# -----------------------------
from app.utils.config import (
CACHE_TTL,
SESSION_SECRET,
)
async def cache_health_check():
logger = logging.getLogger(__name__)
logger.info("🧹 Cache cleanup task started")
interval = max(1, CACHE_TTL // 3) # pure TTL-based
logger.info(f"🕒 Cache cleanup interval set to {interval}s")
while True:
try:
cleanup_expired()
except Exception as e:
logger.error(f"Cache cleanup error: {e}")
await asyncio.sleep(interval)
# -----------------------------
# Lifespan: env + DB connect ONCE (DB-optional)
# -----------------------------
@asynccontextmanager
async def lifespan(app: FastAPI):
logger = logging.getLogger(__name__)
logger.info("Application startup: Initializing services...")
# DB init (optional)
db_ok = db.connect_db()
if db_ok:
db.start_health_check()
logger.info("🟢 MongoDB enabled")
else:
logger.warning("🟡 MongoDB disabled (cache-only mode)")
# Cache TTL cleanup
cache_task = asyncio.create_task(cache_health_check())
logger.info("🧹 Cache TTL cleanup enabled")
logger.info("Application startup complete")
yield
logger.info("Application shutdown: Cleaning up...")
# Stop cache task
cache_task.cancel()
try:
await cache_task
except asyncio.CancelledError:
logger.info("🧹 Cache cleanup task stopped")
# Stop DB health check
try:
await db.stop_health_check()
except Exception as e:
logger.error(f"Error stopping health check: {str(e)}")
# Close Mongo client if exists
try:
if db.client is not None:
db.client.close()
logger.info("MongoDB client closed")
except Exception as e:
logger.error(f"Error closing MongoDB client: {str(e)}")
logger.info("Application shutdown complete")
app = FastAPI(title="TinyURL", lifespan=lifespan)
app.add_middleware(SessionMiddleware, secret_key=SESSION_SECRET)
templates = Jinja2Templates(directory="app/templates")
BASE_DIR = Path(__file__).resolve().parent
STATIC_DIR = BASE_DIR / "static"
app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
# QR codes are now served from /qr, which maps to assets/images/qr in the project root
PROJECT_ROOT = BASE_DIR.parent
QR_DIR = PROJECT_ROOT / "assets" / "images" / "qr"
app.mount("/qr", StaticFiles(directory=QR_DIR), name="qr")
# -----------------------------
# Global error handler
# -----------------------------
# app.exception_handler(Exception)
# sync def global_exception_handler(request: Request, exc: Exception):
# traceback.print_exc()
# return JSONResponse(
# status_code=500,
# content={"success": False, "error": "INTERNAL_SERVER_ERROR"},
# )
@app.exception_handler(404)
async def custom_404_handler(request: Request, exc):
return templates.TemplateResponse(
"404.html",
{"request": request},
status_code=404,
)
# -----------------------------
# Routers (UI + API)
# -----------------------------
app.include_router(ui_router) # UI routes at "/"