from __future__ import annotations from datetime import datetime from typing import Any class AlertRepositoryMixin: def save_alert( self, user_id: int, kind: str, title: str, content: str, available_date: str, code: str, dedupe_key: str, ) -> int: now = datetime.now().astimezone().isoformat(timespec="seconds") with self.connect() as connection: connection.execute( """ INSERT INTO alerts (user_id, kind, title, content, available_date, code, dedupe_key, is_read, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?, ?) ON CONFLICT(user_id, dedupe_key) DO UPDATE SET title=excluded.title, content=excluded.content, available_date=excluded.available_date, updated_at=excluded.updated_at """, ( int(user_id), kind, title, content, available_date, code, dedupe_key, now, now, ), ) row = connection.execute( "SELECT id FROM alerts WHERE user_id = ? AND dedupe_key = ?", (int(user_id), dedupe_key), ).fetchone() return int(row["id"]) def list_alerts( self, user_id: int, as_of: str, unread_only: bool = False, limit: int = 100 ) -> list[dict[str, Any]]: with self.connect() as connection: if unread_only: rows = connection.execute( """ SELECT id, kind, title, content, available_date, code, is_read, created_at, updated_at, read_at FROM alerts WHERE user_id = ? AND available_date <= ? AND is_read = 0 ORDER BY available_date DESC, id DESC LIMIT ? """, (int(user_id), as_of, max(1, min(300, int(limit)))), ).fetchall() else: rows = connection.execute( """ SELECT id, kind, title, content, available_date, code, is_read, created_at, updated_at, read_at FROM alerts WHERE user_id = ? ORDER BY CASE WHEN available_date > ? THEN 0 ELSE 1 END, is_read, available_date, id DESC LIMIT ? """, (int(user_id), as_of, max(1, min(300, int(limit)))), ).fetchall() return [{**dict(row), "is_read": bool(row["is_read"])} for row in rows] def count_unread_alerts(self, user_id: int, as_of: str) -> int: with self.connect() as connection: row = connection.execute( """ SELECT COUNT(*) AS total FROM alerts WHERE user_id = ? AND available_date <= ? AND is_read = 0 """, (int(user_id), as_of), ).fetchone() return int(row["total"] if row else 0) def mark_alert_read(self, user_id: int, alert_id: int) -> bool: now = datetime.now().astimezone().isoformat(timespec="seconds") with self.connect() as connection: cursor = connection.execute( """ UPDATE alerts SET is_read = 1, read_at = ?, updated_at = ? WHERE id = ? AND user_id = ? """, (now, now, int(alert_id), int(user_id)), ) return cursor.rowcount > 0 def mark_all_alerts_read(self, user_id: int, as_of: str) -> int: now = datetime.now().astimezone().isoformat(timespec="seconds") with self.connect() as connection: cursor = connection.execute( """ UPDATE alerts SET is_read = 1, read_at = ?, updated_at = ? WHERE user_id = ? AND available_date <= ? AND is_read = 0 """, (now, now, int(user_id), as_of), ) return int(cursor.rowcount) def delete_alert(self, user_id: int, alert_id: int) -> bool: with self.connect() as connection: cursor = connection.execute( "DELETE FROM alerts WHERE id = ? AND user_id = ?", (int(alert_id), int(user_id)), ) return cursor.rowcount > 0