feat(collab): v4.9.0 Collaboration - commentaires inline, mentions @, notifications in-app + email
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
"""FlowDeck — Notification service (v4.9.0 collaboration).
|
||||
|
||||
Creates in-app notifications (mentions, comments, page changes) and triggers
|
||||
email delivery via :mod:`app.services.mailer` when the target user has opted in.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
|
||||
from app.db import get_conn
|
||||
from app.services import mailer
|
||||
|
||||
|
||||
def create_notification(
|
||||
user_id: int,
|
||||
actor_id: int | None,
|
||||
ntype: str,
|
||||
title: str,
|
||||
message: str,
|
||||
resource_type: str = "page",
|
||||
resource_id: int = 0,
|
||||
url: str = "",
|
||||
conn=None,
|
||||
commit: bool = True,
|
||||
) -> int | None:
|
||||
"""Insert a notification row. Returns the new id (or None if skipped).
|
||||
|
||||
``conn`` may be supplied to join an existing transaction (caller controls
|
||||
commit). Otherwise a dedicated connection is opened and committed.
|
||||
"""
|
||||
if not user_id:
|
||||
return None
|
||||
if conn is not None:
|
||||
cur = conn.execute(
|
||||
"""INSERT INTO notifications
|
||||
(user_id, actor_id, ntype, title, message, resource_type, resource_id, url)
|
||||
VALUES (?,?,?,?,?,?,?,?)""",
|
||||
(user_id, actor_id, ntype, title, message, resource_type, resource_id, url),
|
||||
)
|
||||
if commit:
|
||||
conn.commit()
|
||||
return cur.lastrowid
|
||||
with get_conn() as conn:
|
||||
cur = conn.execute(
|
||||
"""INSERT INTO notifications
|
||||
(user_id, actor_id, ntype, title, message, resource_type, resource_id, url)
|
||||
VALUES (?,?,?,?,?,?,?,?)""",
|
||||
(user_id, actor_id, ntype, title, message, resource_type, resource_id, url),
|
||||
)
|
||||
conn.commit()
|
||||
return cur.lastrowid
|
||||
|
||||
|
||||
_MENTION_RE = re.compile(r"(?:^|\s)@([\w\-\.]+)")
|
||||
|
||||
|
||||
def extract_mentions(text: str) -> list[str]:
|
||||
"""Return the set of @login handles mentioned in ``text`` (lowercase)."""
|
||||
return list(dict.fromkeys(m.lower() for m in _MENTION_RE.findall(text or "")))
|
||||
|
||||
|
||||
def process_mentions(
|
||||
text: str,
|
||||
actor_id: int,
|
||||
ntype: str,
|
||||
title: str,
|
||||
message: str,
|
||||
resource_type: str = "page",
|
||||
resource_id: int = 0,
|
||||
url: str = "",
|
||||
conn=None,
|
||||
) -> list[int]:
|
||||
"""Create notifications for every user @-mentioned in ``text``.
|
||||
|
||||
Returns the list of mentioned user ids that were notified.
|
||||
"""
|
||||
handles = extract_mentions(text)
|
||||
if not handles:
|
||||
return []
|
||||
notified = []
|
||||
if conn is not None:
|
||||
_do_mentions(conn, handles, actor_id, ntype, title, message,
|
||||
resource_type, resource_id, url, notified)
|
||||
return notified
|
||||
with get_conn() as conn:
|
||||
_do_mentions(conn, handles, actor_id, ntype, title, message,
|
||||
resource_type, resource_id, url, notified)
|
||||
conn.commit()
|
||||
return notified
|
||||
|
||||
|
||||
def _do_mentions(conn, handles, actor_id, ntype, title, message,
|
||||
resource_type, resource_id, url, notified):
|
||||
placeholders = ",".join("?" * len(handles))
|
||||
rows = conn.execute(
|
||||
"SELECT id, login, email FROM users WHERE lower(login) IN (%s)" % placeholders,
|
||||
handles,
|
||||
).fetchall()
|
||||
for row in rows:
|
||||
if row["id"] == actor_id:
|
||||
continue
|
||||
create_notification(
|
||||
row["id"], actor_id, ntype, title, message,
|
||||
resource_type, resource_id, url, conn=conn, commit=False,
|
||||
)
|
||||
notified.append(row["id"])
|
||||
mailer.notify_user(
|
||||
row["id"], subject=title, body_text=message, cta_url=url or "",
|
||||
)
|
||||
|
||||
|
||||
def get_user_prefs(user_id: int, conn=None) -> dict:
|
||||
if conn is not None:
|
||||
row = conn.execute(
|
||||
"SELECT notification_prefs FROM users WHERE id=?", (user_id,)
|
||||
).fetchone()
|
||||
else:
|
||||
with get_conn() as conn:
|
||||
row = conn.execute(
|
||||
"SELECT notification_prefs FROM users WHERE id=?", (user_id,)
|
||||
).fetchone()
|
||||
if not row or not row["notification_prefs"]:
|
||||
return {"comments": True, "mentions": True}
|
||||
try:
|
||||
prefs = json.loads(row["notification_prefs"])
|
||||
except (TypeError, json.JSONDecodeError):
|
||||
prefs = {}
|
||||
return {"comments": bool(prefs.get("comments", True)),
|
||||
"mentions": bool(prefs.get("mentions", True))}
|
||||
|
||||
|
||||
def set_user_prefs(user_id: int, prefs: dict, conn=None) -> dict:
|
||||
if conn is not None:
|
||||
conn.execute(
|
||||
"UPDATE users SET notification_prefs=? WHERE id=?",
|
||||
(json.dumps(prefs), user_id),
|
||||
)
|
||||
conn.commit()
|
||||
else:
|
||||
with get_conn() as conn:
|
||||
conn.execute(
|
||||
"UPDATE users SET notification_prefs=? WHERE id=?",
|
||||
(json.dumps(prefs), user_id),
|
||||
)
|
||||
conn.commit()
|
||||
return prefs
|
||||
Reference in New Issue
Block a user