mail notification

This commit is contained in:
Bastian Wagner
2026-08-15 20:47:02 +02:00
parent 7c9e19ba0b
commit 2aba1265af
16 changed files with 519 additions and 2 deletions

View File

@@ -64,6 +64,7 @@ class SyncManager:
mywhoosh_factory: Callable[[MyWhooshTokenStore], Any],
garmin_factory: Callable[[str, str, Path], Any],
fit_converter: Callable[[Path, Path], Any],
notifier: Any = None,
) -> None:
self.session_factory = session_factory
self.credential_cipher = credential_cipher
@@ -71,9 +72,24 @@ class SyncManager:
self.mywhoosh_factory = mywhoosh_factory
self.garmin_factory = garmin_factory
self.fit_converter = fit_converter
self.notifier = notifier
self._locks: dict[int, asyncio.Lock] = {}
self._locks_guard = asyncio.Lock()
def _notify_action_required(self, user: Any, message: str | None) -> None:
if self.notifier is None or not user.notify_email_enabled or not user.notification_email:
return
try:
self.notifier.send(
to_address=user.notification_email,
subject=f"MyWhoosh-Garmin Sync: action required for {user.name}",
body=message or "Your sync requires attention. Check the dashboard for details.",
)
except Exception:
logger.warning(
"sync_user: failed to send action-required notification for user %s", user.id, exc_info=True
)
async def _lock_for(self, user_id: int) -> asyncio.Lock:
async with self._locks_guard:
return self._locks.setdefault(user_id, asyncio.Lock())
@@ -102,6 +118,12 @@ class SyncManager:
if user is None:
raise ValueError(f"user {user_id} not found")
# Captured before this run mutates action_reason, so the
# end-of-run notification below only fires on a transition into
# (or between) action-required states -- never repeatedly for a
# cause that's already been reported and still unresolved.
previous_action_reason = user.action_reason
sync_run_repo = SyncRunRepository(session)
run = sync_run_repo.start(user_id)
@@ -366,6 +388,8 @@ class SyncManager:
summary_error=summary_error,
)
session.commit()
if user.action_reason is not None and user.action_reason != previous_action_reason:
self._notify_action_required(user, summary_error)
return SyncOutcome(
user_id=user.id,
status=status.value,