multi-user: users, per-user profiles/filters/notifications, tab UI, apply forensics
* DB: users + user_profiles/filters/notifications/preferences; applications gets user_id + forensics_json + profile_snapshot_json; new errors table with 14d retention; schema versioning via MIGRATIONS list * auth: password hashes in DB (argon2); env vars seed first admin; per-user sessions; CSRF bound to user id * apply: personal info/WBS moved out of env into the request body; providers take an ApplyContext with Profile + submit_forms; full Playwright recorder (step log, console, page errors, network, screenshots, final HTML) * web: five top-level tabs (Wohnungen/Bewerbungen/Logs/Fehler/Einstellungen); settings sub-tabs profil/filter/benachrichtigungen/account/benutzer; per-user matching, auto-apply and notifications (UI/Telegram/SMTP); red auto-apply switch on Wohnungen tab; forensics detail view for bewerbungen and fehler; retention background thread Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e663386a19
commit
c630b500ef
36 changed files with 2763 additions and 1113 deletions
|
|
@ -1,22 +1,37 @@
|
|||
"""Abstract provider interface. Concrete providers live next to this file."""
|
||||
import asyncio
|
||||
from abc import ABC, abstractmethod
|
||||
import logging
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
|
||||
from actions import Recorder
|
||||
from classes.application_result import ApplicationResult
|
||||
from classes.profile import Profile
|
||||
|
||||
logger = logging.getLogger("flat-apply")
|
||||
|
||||
|
||||
@dataclass
|
||||
class ApplyContext:
|
||||
"""Per-request state passed into every provider call."""
|
||||
profile: Profile
|
||||
submit_forms: bool
|
||||
recorder: Recorder
|
||||
|
||||
|
||||
class Provider(ABC):
|
||||
@property
|
||||
@abstractmethod
|
||||
def domain(self) -> str:
|
||||
"""every flat provider needs a domain"""
|
||||
pass
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def apply_for_flat(self, url: str) -> ApplicationResult:
|
||||
"""every flat provider needs to be able to apply for flats"""
|
||||
pass
|
||||
async def apply_for_flat(self, url: str, ctx: ApplyContext) -> ApplicationResult:
|
||||
...
|
||||
|
||||
def test_apply(self, url):
|
||||
print(asyncio.run(self.apply_for_flat(url)))
|
||||
def test_apply(self, url, profile: Profile | None = None, submit_forms: bool = False):
|
||||
rec = Recorder(url)
|
||||
ctx = ApplyContext(profile or Profile(), submit_forms=submit_forms, recorder=rec)
|
||||
result = asyncio.run(self.apply_for_flat(url, ctx))
|
||||
print(repr(result))
|
||||
return result, rec.to_json()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue