# retoor from .core import TTLCache, bump_cache_version, datetime, db, get_table, sync_local_cache, timezone from .settings import get_int_setting, set_setting from .soft_delete import soft_delete NOTIFICATION_TYPES = [ {"key": "comment", "label": "Comments", "description": "Someone comments on your post"}, {"key": "reply", "label": "Replies", "description": "Someone replies to your comment"}, {"key": "mention", "label": "Mentions", "description": "Someone mentions you with @username"}, {"key": "vote", "label": "Upvotes", "description": "Someone ++'d your content"}, {"key": "follow", "label": "Followers", "description": "Someone starts following you"}, {"key": "message", "label": "Direct messages", "description": "Someone sends you a message"}, {"key": "badge", "label": "Badges", "description": "You earn a badge"}, {"key": "level", "label": "Level-ups", "description": "You reach a new level"}, {"key": "issue", "label": "Issue tracker", "description": "Updates on issue reports you filed"}, {"key": "reminder", "label": "Reminders", "description": "A reminder or scheduled task you asked Devii to run fires"}, {"key": "harvest_stolen", "label": "Farm raids", "description": "Someone steals a ready build from your Code Farm"}, {"key": "award", "label": "Awards", "description": "Someone gives you an award on your profile"}, {"key": "system", "label": "System alerts", "description": "Platform infrastructure alerts (e.g. the AI gateway going down)"}, ] NOTIFICATION_CHANNELS = ("in_app", "push", "telegram") _NOTIFICATION_CHANNEL_COLUMNS = { "in_app": "in_app_enabled", "push": "push_enabled", "telegram": "telegram_enabled", } _NOTIFICATION_CHANNEL_DEFAULTS = {"in_app": 1, "push": 1, "telegram": 0} _NOTIFICATION_TYPE_KEYS = {entry["key"] for entry in NOTIFICATION_TYPES} _notification_prefs_cache = TTLCache(ttl=300, max_size=500) def _notification_default(notification_type: str, channel: str) -> bool: fallback = _NOTIFICATION_CHANNEL_DEFAULTS.get(channel, 1) return get_int_setting(f"notif_default_{notification_type}_{channel}", fallback) != 0 def get_notification_default(notification_type: str, channel: str) -> bool: return _notification_default(notification_type, channel) def set_notification_default( notification_type: str, channel: str, enabled: bool ) -> None: if channel not in _NOTIFICATION_CHANNEL_COLUMNS: raise ValueError(f"Unknown notification channel: {channel}") set_setting(f"notif_default_{notification_type}_{channel}", "1" if enabled else "0") def _notification_overrides(user_uid: str) -> dict: sync_local_cache("notif_prefs", _notification_prefs_cache) cached = _notification_prefs_cache.get(user_uid) if cached is not None: return cached overrides: dict = {} if "notification_preferences" in db.tables: for row in db["notification_preferences"].find( user_uid=user_uid, deleted_at=None ): overrides[row["notification_type"]] = { channel: bool( row.get(column, _NOTIFICATION_CHANNEL_DEFAULTS.get(channel, 1)) ) for channel, column in _NOTIFICATION_CHANNEL_COLUMNS.items() } _notification_prefs_cache.set(user_uid, overrides) return overrides def notification_enabled(user_uid: str, notification_type: str, channel: str) -> bool: if channel not in _NOTIFICATION_CHANNEL_COLUMNS: return True override = _notification_overrides(user_uid).get(notification_type) if override is not None: return bool(override[channel]) return _notification_default(notification_type, channel) def get_notification_prefs(user_uid: str) -> list: overrides = _notification_overrides(user_uid) result = [] for entry in NOTIFICATION_TYPES: key = entry["key"] override = overrides.get(key) channels = { channel: bool(override[channel]) if override else _notification_default(key, channel) for channel in _NOTIFICATION_CHANNEL_COLUMNS } result.append( { "key": key, "label": entry["label"], "description": entry["description"], **channels, "customized": override is not None, } ) return result def set_notification_pref( user_uid: str, notification_type: str, channel: str, enabled: bool ) -> dict: if notification_type not in _NOTIFICATION_TYPE_KEYS: raise ValueError(f"Unknown notification type: {notification_type}") if channel not in _NOTIFICATION_CHANNEL_COLUMNS: raise ValueError(f"Unknown notification channel: {channel}") from devplacepy.utils import generate_uid table = get_table("notification_preferences") now = datetime.now(timezone.utc).isoformat() existing = table.find_one(user_uid=user_uid, notification_type=notification_type) if existing: values = { name: bool( existing.get(column, _NOTIFICATION_CHANNEL_DEFAULTS.get(name, 1)) ) for name, column in _NOTIFICATION_CHANNEL_COLUMNS.items() } else: values = { name: _notification_default(notification_type, name) for name in _NOTIFICATION_CHANNEL_COLUMNS } values[channel] = enabled columns = { column: 1 if values[name] else 0 for name, column in _NOTIFICATION_CHANNEL_COLUMNS.items() } if existing: record = { "id": existing["id"], **columns, "updated_at": now, "deleted_at": None, "deleted_by": None, } table.update(record, ["id"]) result = {**existing, **record} else: result = { "uid": generate_uid(), "user_uid": user_uid, "notification_type": notification_type, **columns, "created_at": now, "updated_at": now, "deleted_at": None, "deleted_by": None, } table.insert(result) bump_cache_version("notif_prefs") return result def reset_notification_prefs(user_uid: str, deleted_by: str | None = None) -> int: if "notification_preferences" not in db.tables: return 0 count = soft_delete( "notification_preferences", deleted_by or f"user:{user_uid}", user_uid=user_uid ) bump_cache_version("notif_prefs") return int(count) def mark_notifications_read_by_target(user_uid: str, target_url: str) -> int: if not user_uid or not target_url or "notifications" not in db.tables: return 0 notifications_table = get_table("notifications") ids = [ n["id"] for n in notifications_table.find(user_uid=user_uid, read=False) if n.get("target_url") and ( n["target_url"] == target_url or n["target_url"].startswith(f"{target_url}#") ) ] if not ids: return 0 with db: for notification_id in ids: notifications_table.update({"id": notification_id, "read": True}, ["id"]) from devplacepy.templating import clear_unread_cache clear_unread_cache(user_uid) return len(ids)