from fastapi import FastAPI from fastapi.openapi.utils import get_openapi from fastapi.security import APIKeyHeader from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.triggers.cron import CronTrigger from app.database import init_db, cleanup_old_cache_entries from app.config import CLEANUP_CRON, CACHE_EXPIRY_HOURS from app.routes import health, browser, cache from app.services.browser import force_cleanup_old_pages from pathlib import Path def _read_version() -> str: version_file = Path(__file__).resolve().parents[1] / "version" try: v = version_file.read_text(encoding="utf-8").strip() return v or "latest" except Exception: return "latest" API_KEY_HEADER_NAME = "X-API-Key" api_key_header = APIKeyHeader(name=API_KEY_HEADER_NAME, auto_error=False) # Create FastAPI app app = FastAPI( title="Playwright API Server", version=_read_version(), description=( "FastAPI server that uses Playwright to visit pages, capture screenshots, " "extract SEO/meta information, and manage a cache.\n\n" f"Authentication: provide `{API_KEY_HEADER_NAME}` on requests." ), docs_url="/docs", redoc_url="/redoc", openapi_url="/openapi.json", swagger_ui_parameters={"persistAuthorization": True}, ) def custom_openapi(): if app.openapi_schema: return app.openapi_schema openapi_schema = get_openapi( title=app.title, version=app.version, description=app.description, routes=app.routes, ) components = openapi_schema.setdefault("components", {}) security_schemes = components.setdefault("securitySchemes", {}) security_schemes["ApiKeyAuth"] = { "type": "apiKey", "in": "header", "name": API_KEY_HEADER_NAME, "description": f"Send your API key in the `{API_KEY_HEADER_NAME}` header.", } # Apply API key auth globally (individual endpoints may still allow anonymous access; # this just documents the expected auth mechanism). openapi_schema["security"] = [{"ApiKeyAuth": []}] openapi_schema["tags"] = [ {"name": "Health", "description": "Liveness / basic health checks."}, {"name": "Browser", "description": "Playwright-powered browsing utilities."}, {"name": "Cache", "description": "Cache management and statistics."}, ] app.openapi_schema = openapi_schema return app.openapi_schema app.openapi = custom_openapi # Include routers app.include_router(health.router, tags=["Health"]) app.include_router(browser.router, tags=["Browser"]) app.include_router(cache.router, tags=["Cache"]) # Initialize scheduler for periodic cache cleanup scheduler = BackgroundScheduler() scheduler.add_job( cleanup_old_cache_entries, CronTrigger.from_crontab(CLEANUP_CRON), id='cache_cleanup_job', replace_existing=True ) # Add browser cleanup job scheduler.add_job( force_cleanup_old_pages, CronTrigger.from_crontab(CLEANUP_CRON), id='browser_cleanup_job', replace_existing=True ) # Initialize database on startup init_db() # Start the scheduler when the application starts @app.on_event("startup") def start_scheduler(): scheduler.start() print(f"Cache cleanup scheduler started with cron: {CLEANUP_CRON}") print(f"Cache entries will expire after {CACHE_EXPIRY_HOURS} hours") print("Browser cleanup scheduler started") # Shutdown the scheduler when the application stops @app.on_event("shutdown") def shutdown_scheduler(): scheduler.shutdown(wait=False) print("Cache cleanup scheduler stopped")