Some checks failed
CI / lint-and-test (push) Has been cancelled
- Cache Graph API tokens with expiry-aware reuse in graph/auth.py - Add tenacity-based retry/backoff wrapper (utils/http.py) and apply to all Graph/source API calls - Add Pydantic request/response models (models/api.py) and FastAPI query constraints - Add unit tests for event_model, auth and integration tests for API endpoints - Configure ruff linter/formatter in pyproject.toml - Add GitHub Actions CI pipeline (.github/workflows/ci.yml) - Add requirements-dev.txt with pytest, mongomock, httpx, ruff - Clean up typing imports and fix ruff linting across codebase
96 lines
2.8 KiB
Python
96 lines
2.8 KiB
Python
import asyncio
|
|
import logging
|
|
from contextlib import suppress
|
|
from pathlib import Path
|
|
|
|
import structlog
|
|
from config import CORS_ORIGINS, ENABLE_PERIODIC_FETCH, FETCH_INTERVAL_MINUTES
|
|
from database import setup_indexes
|
|
from fastapi import FastAPI, HTTPException
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.staticfiles import StaticFiles
|
|
from routes.config import router as config_router
|
|
from routes.events import router as events_router
|
|
from routes.fetch import router as fetch_router
|
|
from routes.fetch import run_fetch
|
|
|
|
|
|
def configure_logging():
|
|
structlog.configure(
|
|
processors=[
|
|
structlog.stdlib.filter_by_level,
|
|
structlog.stdlib.add_logger_name,
|
|
structlog.stdlib.add_log_level,
|
|
structlog.stdlib.PositionalArgumentsFormatter(),
|
|
structlog.processors.TimeStamper(fmt="iso"),
|
|
structlog.processors.StackInfoRenderer(),
|
|
structlog.processors.format_exc_info,
|
|
structlog.processors.UnicodeDecoder(),
|
|
structlog.processors.JSONRenderer(),
|
|
],
|
|
context_class=dict,
|
|
logger_factory=structlog.stdlib.LoggerFactory(),
|
|
wrapper_class=structlog.stdlib.BoundLogger,
|
|
cache_logger_on_first_use=True,
|
|
)
|
|
logging.basicConfig(format="%(message)s", level=logging.INFO)
|
|
|
|
|
|
configure_logging()
|
|
logger = structlog.get_logger("aoc.fetcher")
|
|
|
|
app = FastAPI()
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=CORS_ORIGINS,
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
app.include_router(fetch_router, prefix="/api")
|
|
app.include_router(events_router, prefix="/api")
|
|
app.include_router(config_router, prefix="/api")
|
|
|
|
|
|
@app.get("/health")
|
|
async def health_check():
|
|
from database import db
|
|
try:
|
|
db.command("ping")
|
|
return {"status": "ok", "database": "connected"}
|
|
except Exception as exc:
|
|
logger.error("Health check failed", error=str(exc))
|
|
raise HTTPException(status_code=503, detail="Database unavailable") from exc
|
|
|
|
|
|
frontend_dir = Path(__file__).parent / "frontend"
|
|
app.mount("/", StaticFiles(directory=frontend_dir, html=True), name="frontend")
|
|
|
|
|
|
async def _periodic_fetch():
|
|
while True:
|
|
try:
|
|
await asyncio.to_thread(run_fetch)
|
|
logger.info("Periodic fetch completed.")
|
|
except Exception as exc:
|
|
logger.error("Periodic fetch failed", error=str(exc))
|
|
await asyncio.sleep(FETCH_INTERVAL_MINUTES * 60)
|
|
|
|
|
|
@app.on_event("startup")
|
|
async def start_periodic_fetch():
|
|
setup_indexes()
|
|
if ENABLE_PERIODIC_FETCH:
|
|
app.state.fetch_task = asyncio.create_task(_periodic_fetch())
|
|
|
|
|
|
@app.on_event("shutdown")
|
|
async def stop_periodic_fetch():
|
|
task = getattr(app.state, "fetch_task", None)
|
|
if task:
|
|
task.cancel()
|
|
with suppress(Exception):
|
|
await task
|