from fastapi import FastAPI, Request, HTTPException from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates from .routes import invoices from app.db.database import init_db, rate_limiter import os from fastapi.middleware.cors import CORSMiddleware from starlette.middleware.base import BaseHTTPMiddleware from dotenv import load_dotenv import logging from typing import Callable import time # Set up logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # Load environment variables load_dotenv() # Get the absolute path to the app directory BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) class RateLimitMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next: Callable): # Get client IP client_ip = request.client.host # Check rate limit if not rate_limiter.is_allowed(client_ip): logger.warning(f"Rate limit exceeded for IP: {client_ip}") raise HTTPException( status_code=429, detail="Too many requests. Please try again later." ) # Process request start_time = time.time() response = await call_next(request) process_time = time.time() - start_time # Log request details logger.info( f"Request: {request.method} {request.url.path} " f"Client: {client_ip} " f"Process time: {process_time:.2f}s" ) return response app = FastAPI( title="Invoice Generator", description="API for generating invoices", version="1.0.0" ) # Add rate limiting middleware app.add_middleware(RateLimitMiddleware) # Configure CORS with more specific settings app.add_middleware( CORSMiddleware, allow_origins=["*"], # In production, replace with specific domains allow_credentials=True, allow_methods=["*"], allow_headers=["*"], max_age=3600, # Cache preflight requests for 1 hour ) # Mount static files with absolute path app.mount("/static", StaticFiles(directory=os.path.join(BASE_DIR, "app/static")), name="static") # Templates with absolute path templates = Jinja2Templates(directory=os.path.join(BASE_DIR, "app/templates")) # Include routers app.include_router(invoices.router) @app.on_event("startup") async def startup_event(): logger.info("Starting application...") await init_db() logger.info("Application started successfully") @app.on_event("shutdown") async def shutdown_event(): logger.info("Shutting down application...") # Root endpoint to serve the HTML page @app.get("/") async def root(request: Request): return templates.TemplateResponse("index.html", {"request": request}) # Health check endpoint @app.get("/health") async def health_check(): return {"status": "healthy", "timestamp": time.time()} @app.get("/history") async def history_page(request: Request): return templates.TemplateResponse("history.html", {"request": request})