Container names, FastAPI titles, email subjects, filenames, brand text, session cookie, User-Agent, docstrings, README. Volume lazyflat_data and /data/lazyflat.sqlite already used the new name, so on-disk data is preserved; dropped the now-obsolete legacy-rename comments. Side effect: SESSION_COOKIE_NAME change logs everyone out on deploy. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
102 lines
4.3 KiB
Python
102 lines
4.3 KiB
Python
"""Bewerbungen (application history) tab and forensics ZIP export."""
|
|
import base64
|
|
import io
|
|
import json
|
|
import zipfile
|
|
|
|
from fastapi import APIRouter, HTTPException, Request
|
|
from fastapi.responses import HTMLResponse, RedirectResponse, StreamingResponse
|
|
|
|
import db
|
|
from auth import current_user
|
|
from common import base_context, templates
|
|
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("/bewerbungen", response_class=HTMLResponse)
|
|
def tab_bewerbungen(request: Request):
|
|
u = current_user(request)
|
|
if not u:
|
|
return RedirectResponse("/login", status_code=303)
|
|
ctx = base_context(request, u, "bewerbungen")
|
|
ctx["applications"] = db.recent_applications(u["id"], limit=100)
|
|
return templates.TemplateResponse("bewerbungen.html", ctx)
|
|
|
|
|
|
@router.get("/bewerbungen/{app_id}/report.zip")
|
|
def bewerbung_zip(request: Request, app_id: int):
|
|
u = current_user(request)
|
|
if not u:
|
|
raise HTTPException(401)
|
|
a = db.get_application(app_id)
|
|
if not a or (a["user_id"] != u["id"] and not u["is_admin"]):
|
|
raise HTTPException(404)
|
|
|
|
flat = db.get_flat(a["flat_id"])
|
|
forensics = json.loads(a["forensics_json"]) if a["forensics_json"] else {}
|
|
profile = json.loads(a["profile_snapshot_json"]) if a["profile_snapshot_json"] else {}
|
|
app_meta = {k: a[k] for k in a.keys() if k not in ("forensics_json", "profile_snapshot_json")}
|
|
|
|
buf = io.BytesIO()
|
|
with zipfile.ZipFile(buf, "w", compression=zipfile.ZIP_DEFLATED) as zf:
|
|
zf.writestr(
|
|
"README.txt",
|
|
f"lazyflat application report\n"
|
|
f"application_id={a['id']}\n"
|
|
f"flat_id={a['flat_id']}\n"
|
|
f"provider={a['provider']}\n"
|
|
f"success={a['success']}\n"
|
|
f"started_at_utc={a['started_at']}\n"
|
|
f"finished_at_utc={a['finished_at']}\n"
|
|
f"submit_forms_used={bool(a['submit_forms_used'])}\n"
|
|
f"\n"
|
|
f"Contents:\n"
|
|
f" application.json DB row + metadata\n"
|
|
f" flat.json Flat details at discovery time\n"
|
|
f" profile_snapshot.json Profile used for this attempt\n"
|
|
f" forensics.json Full captured forensics\n"
|
|
f" step_log.txt Human-readable step log\n"
|
|
f" page.html Final page HTML (if captured)\n"
|
|
f" console.json Browser console entries\n"
|
|
f" errors.json Browser pageerror events\n"
|
|
f" network.json Network requests + partial responses\n"
|
|
f" snapshots/NN_*.jpg Screenshot at each step (NN = order)\n"
|
|
f" snapshots/NN_*.html Page HTML at each step\n"
|
|
)
|
|
zf.writestr("application.json", json.dumps(app_meta, indent=2, default=str))
|
|
zf.writestr("flat.json", json.dumps(dict(flat) if flat else {}, indent=2, default=str))
|
|
zf.writestr("profile_snapshot.json", json.dumps(profile, indent=2, default=str))
|
|
zf.writestr("forensics.json", json.dumps(forensics, indent=2, default=str))
|
|
|
|
step_lines = []
|
|
for s in forensics.get("steps", []):
|
|
step_lines.append(f"[{s.get('ts', 0):7.2f}s] {s.get('step', '?'):<24} {s.get('status', ''):<5} {s.get('detail', '')}")
|
|
zf.writestr("step_log.txt", "\n".join(step_lines))
|
|
|
|
if forensics.get("final_html"):
|
|
zf.writestr("page.html", forensics["final_html"])
|
|
zf.writestr("console.json", json.dumps(forensics.get("console", []), indent=2))
|
|
zf.writestr("errors.json", json.dumps(forensics.get("errors", []), indent=2))
|
|
zf.writestr("network.json", json.dumps(forensics.get("network", []), indent=2))
|
|
|
|
for idx, s in enumerate(forensics.get("screenshots", []), start=1):
|
|
label = (s.get("label") or f"step{idx}").replace("/", "_").replace(" ", "_")
|
|
b64 = s.get("b64_jpeg", "")
|
|
if b64:
|
|
try:
|
|
data = base64.b64decode(b64)
|
|
zf.writestr(f"snapshots/{idx:02d}_{label}.jpg", data)
|
|
except Exception:
|
|
pass
|
|
html = s.get("html") or ""
|
|
if html:
|
|
zf.writestr(f"snapshots/{idx:02d}_{label}.html", html)
|
|
|
|
buf.seek(0)
|
|
filename = f"lazyflat-report-{a['id']}.zip"
|
|
return StreamingResponse(
|
|
buf, media_type="application/zip",
|
|
headers={"Content-Disposition": f'attachment; filename="{filename}"'},
|
|
)
|