lazyflat/README.md
EiSiMo 0c18f0870a 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>
2026-04-21 17:29:24 +02:00

87 lines
4.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# wohnungsdidi
Combined deployment of **flat-alert** (reliable scraper) and **flat-apply**
(experimental autoapplier) behind a single authenticated web UI.
## Architecture
Three isolated containers on one internal Docker network:
```
┌─────────┐ POST /internal/flats ┌─────────┐ POST /apply ┌─────────┐
│ alert │ ────────────────────────► │ web │ ────────────────► │ apply │
│ scraper │ │ UI+DB │ │ browser │
└─────────┘ └─────────┘ └─────────┘
│ HTTPS (Coolify / Traefik)
user (auth)
```
* **`alert/`** — scrapes inberlinwohnen.de (unchanged logic) and posts each
discovered flat to `web`. Zero external dependencies on `apply`, so `apply`
crashes can never bring the alerter down.
* **`apply/`** — FastAPI wrapper around the experimental Playwright applier.
Only accepts requests with a shared `X-Internal-Api-Key`. Not exposed publicly.
* **`web/`** — FastAPI + Jinja + HTMX dashboard. The only public service.
Owns the SQLite database, auth, and orchestration.
## Safety / isolation
Because `apply/` is still experimental, the system is hardened around it:
| Control | Behavior |
|-----------------------|-----------------------------------------------------------------------|
| Separate container | A crashed `apply` does not take `alert` or `web` with it. |
| Internal-only network | `apply` is not reachable from the internet; requires internal API key.|
| Default mode `manual` | New flats are just shown in the UI; apply runs **only** on click. |
| Circuit breaker | N consecutive apply failures auto-disable further apply calls. |
| Kill switch | One-click button in the UI that blocks all apply activity. |
| `SUBMIT_FORMS=False` | Default for `apply` — runs the full flow **without** final submit. |
| Audit log | Every auth event, mode change, and apply is recorded. |
## Web security
* Argon2id password hashes (`argon2-cffi`), constant-time compare.
* Session cookie: signed with `itsdangerous`, `HttpOnly`, `Secure`, `SameSite=Strict`.
* CSRF: synchronizer token bound to the session on every state-changing form.
* Login rate limit (in-memory, per-IP).
* Strict `Content-Security-Policy`, `X-Frame-Options: DENY`, `noindex`.
* `/internal/*` endpoints gated by a shared `INTERNAL_API_KEY` and never exposed by Coolify.
## Deployment on Coolify
1. **Create repo**: push this monorepo to `ssh://git@git.moritz.run:2222/moritz/wohnungsdidi.git`.
2. **New Coolify resource***Docker Compose* → point it at this repo. Coolify
will read `docker-compose.yml` and deploy all three services on one network.
3. **Domain**: set `flat.lab.moritz.run` on the `web` service only. Coolify
(Traefik) handles TLS. Do **not** set a domain on `alert` or `apply`.
4. **Secrets**: paste the environment variables from `.env.example` into
Coolify's env UI. At minimum you need:
* `AUTH_USERNAME`, `AUTH_PASSWORD_HASH`
* `SESSION_SECRET`, `INTERNAL_API_KEY`
* `GMAPS_API_KEY`, `BERLIN_WOHNEN_USERNAME`, `BERLIN_WOHNEN_PASSWORD`
* personal info + WBS for `apply`
5. **Generate the password hash**:
```bash
python -c "from argon2 import PasswordHasher; print(PasswordHasher().hash('<your-password>'))"
```
6. **Generate secrets**:
```bash
python -c "import secrets; print(secrets.token_urlsafe(48))" # SESSION_SECRET
python -c "import secrets; print(secrets.token_urlsafe(48))" # INTERNAL_API_KEY
```
7. First apply launch should stay in **manual** mode with
`SUBMIT_FORMS=False` until each provider is verified end-to-end.
## Local development
```bash
cp .env.example .env
# fill in AUTH_PASSWORD_HASH, SESSION_SECRET, INTERNAL_API_KEY, creds
docker compose up -d --build
# open http://localhost:8000 (set COOKIE_SECURE=false for plain http!)
```
To also expose the web port locally, add `ports: ["8000:8000"]` under `web:` in
`docker-compose.yml` (the Coolify production compose doesn't publish host ports).