114 lines
3.5 KiB
Python
114 lines
3.5 KiB
Python
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") |