from dotenv import load_dotenv load_dotenv() # Carica le variabili dal file .env """ Parking Manager Application FastAPI + SQLite + Vanilla JS """ from fastapi import FastAPI, Request from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse, RedirectResponse, JSONResponse from fastapi.middleware.cors import CORSMiddleware from contextlib import asynccontextmanager from slowapi import Limiter, _rate_limit_exceeded_handler from slowapi.util import get_remote_address from slowapi.errors import RateLimitExceeded from datetime import datetime from app import config from app.routes.auth import router as auth_router from app.routes.users import router as users_router from app.routes.offices import router as offices_router from app.routes.presence import router as presence_router from app.routes.parking import router as parking_router from database.connection import init_db # Rate limiter setup limiter = Limiter(key_func=get_remote_address) @asynccontextmanager async def lifespan(app: FastAPI): """Initialize database on startup""" def log(msg): config.logger.info(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {msg}") log("Starting Parking Manager application") # Caddy Integration Logs log("--- Caddy Integration & Handshake ---") # Step 1: Auth / Forward Auth log("1. Checking Caddy Forward Auth Configuration...") status = "ENABLED (Authelia)" if config.AUTHELIA_ENABLED else "DISABLED (Internal Auth)" log(f" - Auth Mode: {status}") if config.AUTHELIA_ENABLED: log(" - Configuring Trusted Headers from Caddy:") log(f" * User: {config.AUTHELIA_HEADER_USER}") log(f" * Name: {config.AUTHELIA_HEADER_NAME}") log(f" * Email: {config.AUTHELIA_HEADER_EMAIL}") log(f" * Groups: {config.AUTHELIA_HEADER_GROUPS}") else: log(" - No trusted headers configured (Standalone mode)") # Step 2: CORS / Origins log("2. Configuring Caddy CORS / Origins...") for origin in config.ALLOWED_ORIGINS: log(f" - Trusted Origin: {origin}") init_db() log("3. Database Connection: ESTABLISHED") # Step 3: Network / Reachability local_url = f"http://{config.HOST}:{config.PORT}" # Try to find a public URL from allowed origins (excluding localhost/ips) public_candidates = [o for o in config.ALLOWED_ORIGINS if "localhost" not in o and "127.0.0.1" not in o and not o.startswith("*")] reachable_url = public_candidates[0] if public_candidates else local_url.replace("0.0.0.0", "localhost") log("4. Finalizing Caddy Handshake...") log(f" - Listening on: {config.HOST}:{config.PORT}") log("--- Handshake Complete ---") log(f"feedback: App reachable via Caddy at {reachable_url}") yield log("Shutting down Parking Manager application") app = FastAPI(title="Parking Manager", version="1.0.0", lifespan=lifespan) # Add rate limiter app.state.limiter = limiter app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) # CORS app.add_middleware( CORSMiddleware, allow_origins=config.ALLOWED_ORIGINS, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # API Routes app.include_router(auth_router) app.include_router(users_router) app.include_router(offices_router) app.include_router(presence_router) app.include_router(parking_router) # Static Files app.mount("/css", StaticFiles(directory=str(config.FRONTEND_DIR / "css")), name="css") app.mount("/js", StaticFiles(directory=str(config.FRONTEND_DIR / "js")), name="js") app.mount("/assets", StaticFiles(directory=str(config.FRONTEND_DIR / "assets")), name="assets") # Page Routes @app.get("/") async def index(): """Landing page""" return FileResponse(config.FRONTEND_DIR / "pages" / "landing.html") @app.get("/login") async def login_page(): """Login page""" return FileResponse(config.FRONTEND_DIR / "pages" / "login.html") @app.get("/register") async def register_page(): """Register page""" return FileResponse(config.FRONTEND_DIR / "pages" / "register.html") @app.get("/dashboard") async def dashboard(): """Dashboard - redirect to team calendar""" return RedirectResponse(url="/team-calendar", status_code=302) @app.get("/presence") async def presence_page(): """My Presence page""" return FileResponse(config.FRONTEND_DIR / "pages" / "presence.html") @app.get("/team-calendar") async def team_calendar_page(): """Team Calendar page""" return FileResponse(config.FRONTEND_DIR / "pages" / "team-calendar.html") @app.get("/team-rules") async def team_rules_page(): """Team Rules page""" return FileResponse(config.FRONTEND_DIR / "pages" / "team-rules.html") @app.get("/admin/users") async def admin_users_page(): """Admin Users page""" return FileResponse(config.FRONTEND_DIR / "pages" / "admin-users.html") @app.get("/admin/offices") async def admin_offices_page(): """Admin Offices page""" return FileResponse(config.FRONTEND_DIR / "pages" / "admin-offices.html") @app.get("/profile") async def profile_page(): """Profile page""" return FileResponse(config.FRONTEND_DIR / "pages" / "profile.html") @app.get("/settings") async def settings_page(): """Settings page""" return FileResponse(config.FRONTEND_DIR / "pages" / "settings.html") @app.get("/parking-settings") async def parking_settings_page(): """Parking Settings page""" return FileResponse(config.FRONTEND_DIR / "pages" / "parking-settings.html") @app.get("/favicon.svg") async def favicon(): """Favicon""" return FileResponse(config.FRONTEND_DIR / "favicon.svg", media_type="image/svg+xml") @app.get("/health") async def health(): """Health check""" return {"status": "ok"} if __name__ == "__main__": import uvicorn uvicorn.run("main:app", host=config.HOST, port=config.PORT, reload=True)