refactor: split web/app.py into routers
app.py was ~1300 lines with every route, helper, and middleware mixed
together. Split into:
- app.py (~100 lines): FastAPI bootstrap, lifespan, /health, security
headers, Jinja filter registration, include_router calls
- common.py: shared helpers (templates, apply_client, base_context,
_is_htmx, client_ip, require_internal, time helpers, filter helpers,
apply-gate helpers, _kick_apply / _finish_apply_background,
_bg_tasks, _spawn, _mask_secret, _has_running_application, BERLIN_TZ)
- routes/auth.py: /login (GET+POST), /logout
- routes/wohnungen.py: /, /partials/wohnungen, /partials/wohnung/{id},
/flat-images/{slug}/{idx}, /actions/apply|reject|unreject|auto-apply|
submit-forms|reset-circuit|filters|enrich-all|enrich-flat; owns
_wohnungen_context + _wohnungen_partial_or_redirect
- routes/bewerbungen.py: /bewerbungen, /bewerbungen/{id}/report.zip
- routes/einstellungen.py: /einstellungen, /einstellungen/{section},
/actions/profile|notifications|account/password|partner/*; owns
VALID_SECTIONS
- routes/admin.py: /logs redirect, /admin, /admin/{section},
/logs/export.csv, /actions/users/*|secrets; owns ADMIN_SECTIONS,
_parse_date_range, _collect_events
- routes/internal.py: /internal/flats|heartbeat|error|secrets
Route-diff before/after is empty — all 41 routes + /static mount
preserved. No behavior changes, pure mechanical split.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4f23726e8f
commit
f1e26b38d0
9 changed files with 1323 additions and 1207 deletions
77
web/routes/internal.py
Normal file
77
web/routes/internal.py
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
"""Internal service-to-service endpoints. Authenticated via INTERNAL_API_KEY
|
||||
header; never called by browsers."""
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
|
||||
import db
|
||||
import enrichment
|
||||
import notifications
|
||||
from common import _auto_apply_allowed, _kick_apply, require_internal
|
||||
from matching import flat_matches_filter, row_to_dict
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/internal/flats")
|
||||
async def internal_submit_flat(
|
||||
payload: dict,
|
||||
_guard: None = Depends(require_internal),
|
||||
):
|
||||
if not payload.get("id") or not payload.get("link"):
|
||||
raise HTTPException(400, "id and link required")
|
||||
|
||||
is_new = db.upsert_flat(payload)
|
||||
if not is_new:
|
||||
return {"status": "duplicate"}
|
||||
|
||||
# Kick LLM enrichment + image download for this fresh flat.
|
||||
enrichment.kick(str(payload["id"]))
|
||||
|
||||
for u in db.list_users():
|
||||
if u["disabled"]:
|
||||
continue
|
||||
filters = row_to_dict(db.get_filters(u["id"]))
|
||||
if not flat_matches_filter(payload, filters):
|
||||
continue
|
||||
|
||||
db.log_audit("alert", "flat_matched",
|
||||
f"user={u['username']} flat={payload['id']}",
|
||||
user_id=u["id"])
|
||||
notifications.on_match(u["id"], payload)
|
||||
|
||||
prefs = db.get_preferences(u["id"])
|
||||
if _auto_apply_allowed(prefs):
|
||||
_kick_apply(u["id"], str(payload["id"]), payload["link"], "auto")
|
||||
db.log_audit("system", "auto_apply_kick",
|
||||
f"user={u['username']} flat={payload['id']}",
|
||||
user_id=u["id"])
|
||||
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
@router.post("/internal/heartbeat")
|
||||
async def internal_heartbeat(payload: dict, _g: None = Depends(require_internal)):
|
||||
service = payload.get("service", "unknown")
|
||||
db.set_state(f"last_{service}_heartbeat", db.now_iso())
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
@router.post("/internal/error")
|
||||
async def internal_report_error(
|
||||
payload: dict,
|
||||
_g: None = Depends(require_internal),
|
||||
):
|
||||
db.log_error(
|
||||
source=payload.get("source", "unknown"),
|
||||
kind=payload.get("kind", "error"),
|
||||
summary=payload.get("summary", ""),
|
||||
context=payload.get("context"),
|
||||
)
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
@router.get("/internal/secrets")
|
||||
async def internal_secrets(_g: None = Depends(require_internal)):
|
||||
"""Give sibling services (alert) the current runtime creds that the admin
|
||||
may have edited via the UI, so no redeploy is needed when rotating."""
|
||||
return db.all_secrets()
|
||||
Loading…
Add table
Add a link
Reference in a new issue