rename to wohnungsdidi + didi logo + footer for all + seconds-only counter
- App is now called "wohnungsdidi" everywhere user-facing (page title,
nav brand, login header, notification subjects, report filename,
FastAPI titles, log messages)
- Brand dot replaced with an image of Didi (web/static/didi.webp),
rendered as a round 2.25rem avatar in _layout + login
- "Programmiert für Annika ♥" footer now shows for every logged-in user,
not only Annika
- Count-up shows only seconds ("vor 73 s") regardless of age — no
rollover to minutes/hours
- Data continuity: DB file stays /data/lazyflat.sqlite and the Docker
volume stays lazyflat_data so the rename doesn't strand existing data
- Session cookie renamed to wohnungsdidi_session (one-time logout on
rollout)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
da180bd7c7
commit
0c18f0870a
14 changed files with 34 additions and 32 deletions
10
web/app.py
10
web/app.py
|
|
@ -1,5 +1,5 @@
|
|||
"""
|
||||
lazyflat web app.
|
||||
wohnungsdidi web app.
|
||||
|
||||
Tabs:
|
||||
- / → Wohnungen
|
||||
|
|
@ -84,7 +84,7 @@ async def lifespan(_app: FastAPI):
|
|||
yield
|
||||
|
||||
|
||||
app = FastAPI(lifespan=lifespan, title="lazyflat", docs_url=None, redoc_url=None, openapi_url=None)
|
||||
app = FastAPI(lifespan=lifespan, title="wohnungsdidi", docs_url=None, redoc_url=None, openapi_url=None)
|
||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||
templates = Jinja2Templates(directory="templates")
|
||||
|
||||
|
|
@ -712,7 +712,7 @@ def bewerbung_zip(request: Request, app_id: int):
|
|||
with zipfile.ZipFile(buf, "w", compression=zipfile.ZIP_DEFLATED) as zf:
|
||||
zf.writestr(
|
||||
"README.txt",
|
||||
f"lazyflat application report\n"
|
||||
f"wohnungsdidi application report\n"
|
||||
f"application_id={a['id']}\n"
|
||||
f"flat_id={a['flat_id']}\n"
|
||||
f"provider={a['provider']}\n"
|
||||
|
|
@ -764,7 +764,7 @@ def bewerbung_zip(request: Request, app_id: int):
|
|||
zf.writestr(f"snapshots/{idx:02d}_{label}.html", html)
|
||||
|
||||
buf.seek(0)
|
||||
filename = f"lazyflat-report-{a['id']}.zip"
|
||||
filename = f"wohnungsdidi-report-{a['id']}.zip"
|
||||
return StreamingResponse(
|
||||
buf, media_type="application/zip",
|
||||
headers={"Content-Disposition": f'attachment; filename="{filename}"'},
|
||||
|
|
@ -889,7 +889,7 @@ def tab_logs_export(request: Request):
|
|||
e["ip"],
|
||||
])
|
||||
body = buf.getvalue().encode("utf-8")
|
||||
filename = "lazyflat-protokoll"
|
||||
filename = "wohnungsdidi-protokoll"
|
||||
if q.get("from"): filename += f"-{q['from']}"
|
||||
if q.get("to"): filename += f"-bis-{q['to']}"
|
||||
filename += ".csv"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
"""
|
||||
SQLite data layer for lazyflat.
|
||||
SQLite data layer for wohnungsdidi.
|
||||
|
||||
Multi-user: users, per-user profiles/filters/notifications/preferences.
|
||||
All per-user rows are 1:1 with users. Errors and forensics are retained
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ def _download_images(flat_id: str, urls: list[str], referer: str) -> int:
|
|||
r = requests.get(
|
||||
raw_url,
|
||||
headers={"Referer": referer,
|
||||
"User-Agent": "Mozilla/5.0 (lazyflat enricher)"},
|
||||
"User-Agent": "Mozilla/5.0 (wohnungsdidi enricher)"},
|
||||
timeout=IMAGE_TIMEOUT,
|
||||
stream=True,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -72,14 +72,14 @@ def on_match(user_id: int, flat: dict) -> None:
|
|||
body = f"Neue passende Wohnung: {addr}\nMiete: {rent} €\nZimmer: {rooms}\n{link}"
|
||||
md = (f"*Neue passende Wohnung*\n[{addr}]({link})\n"
|
||||
f"Miete: {rent} € · Zimmer: {rooms}\n{PUBLIC_URL}")
|
||||
notify_user(user_id, "match", subject="[lazyflat] passende Wohnung", body_plain=body, body_markdown=md)
|
||||
notify_user(user_id, "match", subject="[wohnungsdidi] passende Wohnung", body_plain=body, body_markdown=md)
|
||||
|
||||
|
||||
def on_apply_ok(user_id: int, flat: dict, message: str) -> None:
|
||||
addr = flat.get("address") or flat.get("link")
|
||||
body = f"Bewerbung erfolgreich: {addr}\n{message}"
|
||||
md = f"*Bewerbung erfolgreich*\n{addr}\n{message}"
|
||||
notify_user(user_id, "apply_ok", subject="[lazyflat] Bewerbung OK", body_plain=body, body_markdown=md)
|
||||
notify_user(user_id, "apply_ok", subject="[wohnungsdidi] Bewerbung OK", body_plain=body, body_markdown=md)
|
||||
|
||||
|
||||
def on_apply_fail(user_id: int, flat: dict, message: str) -> None:
|
||||
|
|
@ -87,5 +87,5 @@ def on_apply_fail(user_id: int, flat: dict, message: str) -> None:
|
|||
body = f"Bewerbung fehlgeschlagen: {addr}\n{message}\n{PUBLIC_URL}/fehler"
|
||||
md = (f"*Bewerbung fehlgeschlagen*\n{addr}\n{message}\n"
|
||||
f"[Fehler ansehen]({PUBLIC_URL}/fehler)")
|
||||
notify_user(user_id, "apply_fail", subject="[lazyflat] Bewerbung fehlgeschlagen",
|
||||
notify_user(user_id, "apply_fail", subject="[wohnungsdidi] Bewerbung fehlgeschlagen",
|
||||
body_plain=body, body_markdown=md)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ AUTH_PASSWORD_HASH: str = _required("AUTH_PASSWORD_HASH")
|
|||
|
||||
# --- Session cookie -----------------------------------------------------------
|
||||
SESSION_SECRET: str = getenv("SESSION_SECRET") or secrets.token_urlsafe(48)
|
||||
SESSION_COOKIE_NAME: str = "lazyflat_session"
|
||||
SESSION_COOKIE_NAME: str = "wohnungsdidi_session"
|
||||
SESSION_MAX_AGE_SECONDS: int = int(getenv("SESSION_MAX_AGE_SECONDS", str(60 * 60 * 24 * 7)))
|
||||
COOKIE_SECURE: bool = getenv("COOKIE_SECURE", "true").lower() in ("true", "1", "yes", "on")
|
||||
|
||||
|
|
@ -43,6 +43,8 @@ ALERT_SCRAPE_INTERVAL_SECONDS: int = int(getenv("ALERT_SCRAPE_INTERVAL_SECONDS",
|
|||
# --- Storage ------------------------------------------------------------------
|
||||
DATA_DIR: Path = Path(getenv("DATA_DIR", "/data"))
|
||||
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
||||
# Legacy filename — kept so existing data under /data/lazyflat.sqlite stays
|
||||
# reachable across the rename to wohnungsdidi. Not user-facing.
|
||||
DB_PATH: Path = DATA_DIR / "lazyflat.sqlite"
|
||||
|
||||
# Retention (errors / audit / application forensics). Default 14 days.
|
||||
|
|
@ -58,7 +60,7 @@ SMTP_HOST: str = getenv("SMTP_HOST", "")
|
|||
SMTP_PORT: int = int(getenv("SMTP_PORT", "587"))
|
||||
SMTP_USERNAME: str = getenv("SMTP_USERNAME", "")
|
||||
SMTP_PASSWORD: str = getenv("SMTP_PASSWORD", "")
|
||||
SMTP_FROM: str = getenv("SMTP_FROM", "lazyflat@localhost")
|
||||
SMTP_FROM: str = getenv("SMTP_FROM", "wohnungsdidi@localhost")
|
||||
SMTP_STARTTLS: bool = getenv("SMTP_STARTTLS", "true").lower() in ("true", "1", "yes", "on")
|
||||
|
||||
# --- App URL (used to build links in notifications) ---------------------------
|
||||
|
|
|
|||
|
|
@ -19,9 +19,7 @@ function fmtCountUp(iso) {
|
|||
const ts = Date.parse(iso);
|
||||
if (!iso || Number.isNaN(ts)) return "—";
|
||||
const diff = Math.max(0, Math.floor((Date.now() - ts) / 1000));
|
||||
if (diff < 60) return `vor ${diff} s`;
|
||||
if (diff < 3600) return `vor ${Math.floor(diff / 60)} min`;
|
||||
return `vor ${Math.floor(diff / 3600)} h`;
|
||||
return `vor ${diff} s`;
|
||||
}
|
||||
|
||||
function updateRelativeTimes() {
|
||||
|
|
|
|||
BIN
web/static/didi.webp
Normal file
BIN
web/static/didi.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 162 KiB |
|
|
@ -7,8 +7,8 @@
|
|||
<header class="border-b border-soft bg-white/70 backdrop-blur sticky top-0 z-10">
|
||||
<div class="max-w-6xl mx-auto px-6 py-3 flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="brand-dot"></div>
|
||||
<h1 class="text-xl font-semibold">lazyflat</h1>
|
||||
<img src="/static/didi.webp" alt="" class="brand-dot">
|
||||
<h1 class="text-xl font-semibold">wohnungsdidi</h1>
|
||||
</div>
|
||||
<div class="flex items-center gap-4 text-sm">
|
||||
<span class="text-slate-500">{{ user.username }}{% if is_admin %} · <span class="chip chip-info">Administrator</span>{% endif %}</span>
|
||||
|
|
@ -29,9 +29,7 @@
|
|||
<main class="max-w-6xl mx-auto px-6 py-6 space-y-6">
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
{% if user.username == 'annika' %}
|
||||
<footer class="text-center text-xs text-slate-500 py-6">
|
||||
Programmiert für Annika ♥
|
||||
</footer>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<title>{% block title %}lazyflat{% endblock %}</title>
|
||||
<title>{% block title %}wohnungsdidi{% endblock %}</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script src="https://unpkg.com/htmx.org@2.0.3"></script>
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
||||
|
|
@ -123,9 +123,11 @@
|
|||
.map-popup-actions .btn { padding: 0.35rem 0.7rem; font-size: 12px; }
|
||||
.map-popup-actions form { margin: 0; }
|
||||
.brand-dot {
|
||||
width: 2rem; height: 2rem; border-radius: 10px;
|
||||
background: linear-gradient(135deg, #66b7f2 0%, #2f8ae0 60%, #fbd76b 100%);
|
||||
width: 2.25rem; height: 2.25rem; border-radius: 9999px;
|
||||
object-fit: cover; display: block;
|
||||
border: 2px solid #fff;
|
||||
box-shadow: 0 1px 4px rgba(47, 138, 224, 0.35);
|
||||
background: #fff;
|
||||
}
|
||||
a { color: var(--primary); }
|
||||
a:hover { text-decoration: underline; }
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@
|
|||
<main class="flex min-h-screen items-center justify-center p-4">
|
||||
<div class="card w-full max-w-sm p-8">
|
||||
<div class="flex items-center gap-3 mb-6">
|
||||
<div class="brand-dot"></div>
|
||||
<img src="/static/didi.webp" alt="" class="brand-dot">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold leading-tight">lazyflat</h1>
|
||||
<h1 class="text-2xl font-semibold leading-tight">wohnungsdidi</h1>
|
||||
<p class="text-sm text-slate-500">Anmeldung erforderlich</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue