lazyflat: combined alert + apply behind authenticated web UI
Three isolated services (alert scraper, apply HTTP worker, web UI+DB) with argon2 auth, signed cookies, CSRF, rate-limited login, kill switch, apply circuit breaker, audit log, and strict CSP. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
69f2f1f635
46 changed files with 4183 additions and 0 deletions
83
apply/main.py
Normal file
83
apply/main.py
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
import logging
|
||||
from contextlib import asynccontextmanager
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from fastapi import Depends, FastAPI, Header, HTTPException, status
|
||||
from pydantic import BaseModel
|
||||
from rich.console import Console
|
||||
from rich.logging import RichHandler
|
||||
|
||||
import providers
|
||||
from classes.application_result import ApplicationResult
|
||||
from language import _
|
||||
from settings import INTERNAL_API_KEY, log_settings
|
||||
|
||||
|
||||
def setup_logging():
|
||||
logging.basicConfig(
|
||||
level=logging.WARNING,
|
||||
format="%(message)s",
|
||||
datefmt="[%X]",
|
||||
handlers=[RichHandler(markup=True, console=Console(width=110))],
|
||||
)
|
||||
logging.getLogger("flat-apply").setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
logger = logging.getLogger("flat-apply")
|
||||
setup_logging()
|
||||
|
||||
|
||||
class ApplyRequest(BaseModel):
|
||||
url: str
|
||||
|
||||
|
||||
class ApplyResponse(BaseModel):
|
||||
success: bool
|
||||
message: str
|
||||
|
||||
|
||||
def require_api_key(x_internal_api_key: str | None = Header(default=None)) -> None:
|
||||
if not INTERNAL_API_KEY:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="apply service has no INTERNAL_API_KEY configured",
|
||||
)
|
||||
if x_internal_api_key != INTERNAL_API_KEY:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="invalid api key")
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(_app: FastAPI):
|
||||
log_settings()
|
||||
logger.info(f"apply ready, providers: {sorted(providers.PROVIDERS)}")
|
||||
yield
|
||||
|
||||
|
||||
app = FastAPI(lifespan=lifespan, title="lazyflat-apply")
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
def health():
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
@app.post("/apply", response_model=ApplyResponse, dependencies=[Depends(require_api_key)])
|
||||
async def apply(req: ApplyRequest):
|
||||
url = req.url.strip()
|
||||
domain = urlparse(url).netloc.lower().removeprefix("www.")
|
||||
logger.info(f"apply request for domain={domain} url={url}")
|
||||
|
||||
if domain not in providers.PROVIDERS:
|
||||
logger.warning(f"unsupported provider: {domain}")
|
||||
result = ApplicationResult(False, message=_("unsupported_association"))
|
||||
return ApplyResponse(success=result.success, message=str(result))
|
||||
|
||||
try:
|
||||
provider = providers.PROVIDERS[domain]
|
||||
result = await provider.apply_for_flat(url)
|
||||
logger.info(f"application result: {repr(result)}")
|
||||
except Exception as e:
|
||||
logger.exception("error while applying")
|
||||
result = ApplicationResult(False, f"Script Error:\n{e}")
|
||||
|
||||
return ApplyResponse(success=result.success, message=str(result))
|
||||
Loading…
Add table
Add a link
Reference in a new issue