import html
import json
import re
import time
from typing import Optional
import markdown
from sanitizer import sanitize_html_fragment
from db import get_db
DEFAULT_ROLE_NAME_COLORS = {
"admin": "#ff7b7b",
"moderator": "#8ab4ff",
"user": "#f5f5f7",
}
PROFILE_BIO_MODES = {"txt", "md", "html"}
THEME_MODES = {"dark", "light"}
PAGE_VISIBILITIES = {"team", "public"}
PAGE_TYPES = {"project", "document", "update", "resource"}
TEAM_MEMBER_ROLES = {"owner", "admin", "member"}
PROFILE_VISIBILITIES = {"public", "members", "private"}
LAST_ACTIVE_VISIBILITIES = {"everyone", "members", "nobody"}
PRESENCE_MODES = {"online", "silent", "focus", "dnd"}
DM_LAYOUT_MODES = {"cards", "compact"}
REACTION_EMOJI_RE = re.compile(r"^.{1,16}$")
HEX_COLOR_RE = re.compile(r"^#[0-9a-fA-F]{6}$")
PAGE_LINK_RE = re.compile(r"\]\((?:/teams/[^/]+/pages/)?([a-z0-9-]{1,48})\)", re.IGNORECASE)
def valid_hex_color(value: object) -> bool:
return bool(isinstance(value, str) and HEX_COLOR_RE.fullmatch(value.strip()))
def normalized_hex_color(value: object) -> Optional[str]:
if not valid_hex_color(value):
return None
return str(value).strip().lower()
def role_default_name_color(role: Optional[str]) -> str:
return DEFAULT_ROLE_NAME_COLORS.get(str(role or "user"), DEFAULT_ROLE_NAME_COLORS["user"])
def resolve_name_color(user_role: Optional[str], explicit_color: Optional[str]) -> str:
return normalized_hex_color(explicit_color) or role_default_name_color(user_role)
def render_markdown_to_safe_html(text: str) -> str:
rendered = markdown.markdown(
text or "",
extensions=[
"extra",
"sane_lists",
"nl2br",
"tables",
"fenced_code",
],
output_format="html5",
)
return sanitize_html_fragment(rendered)
def safe_profile_bio_html(bio: str, bio_mode: str) -> str:
mode = bio_mode if bio_mode in PROFILE_BIO_MODES else "txt"
if mode == "html":
return sanitize_html_fragment(bio or "")
if mode == "md":
return render_markdown_to_safe_html(bio or "")
return "
" + html.escape(bio or "").replace("\n", "
") + "
"
def extract_page_reference_slugs(markdown_text: str) -> list[str]:
text = str(markdown_text or "")
matches = PAGE_LINK_RE.findall(text)
seen = set()
slugs = []
for slug in matches:
normalized = slug.lower().strip()
if not normalized or normalized in seen:
continue
seen.add(normalized)
slugs.append(normalized)
return slugs
def detect_website_brand(website: str) -> dict:
value = str(website or "").strip().lower()
if not value:
return {"host": "", "brand": "web", "label": "website"}
host = value
host = host.replace("https://", "").replace("http://", "")
host = host.split("/", 1)[0]
host = host.removeprefix("www.")
mappings = [
("github.com", "github", "GitHub"),
("youtube.com", "youtube", "YouTube"),
("youtu.be", "youtube", "YouTube"),
("spotify.com", "spotify", "Spotify"),
("soundcloud.com", "soundcloud", "SoundCloud"),
("twitter.com", "x", "X"),
("x.com", "x", "X"),
]
for suffix, brand, label in mappings:
if host.endswith(suffix):
return {"host": host, "brand": brand, "label": label}
return {"host": host, "brand": "web", "label": "website"}
def sqlite_table_exists(table_name: str) -> bool:
with get_db() as db:
row = db.execute(
"""
SELECT 1
FROM sqlite_master
WHERE type = 'table'
AND name = ?
""",
(table_name,),
).fetchone()
return bool(row)
def sqlite_column_exists(table_name: str, column_name: str) -> bool:
with get_db() as db:
rows = db.execute(f"PRAGMA table_info({table_name})").fetchall()
return any(row["name"] == column_name for row in rows)
def load_page_children(page_id: int) -> list[dict]:
if not sqlite_column_exists("pages", "parent_page_id"):
return []
with get_db() as db:
rows = db.execute(
"""
SELECT
id,
slug,
title,
updated_at
FROM pages
WHERE parent_page_id = ?
AND is_active = 1
ORDER BY title COLLATE NOCASE, id ASC
""",
(page_id,),
).fetchall()
return [dict(row) for row in rows]
def load_page_backlinks(page_id: int, page_slug: str, team_id: int) -> list[dict]:
pattern_1 = f"%]({page_slug})%"
pattern_2 = f"%/pages/{page_slug})%"
with get_db() as db:
rows = db.execute(
"""
SELECT
id,
slug,
title,
updated_at
FROM pages
WHERE team_id = ?
AND is_active = 1
AND id != ?
AND (body LIKE ? OR body LIKE ?)
ORDER BY updated_at DESC, id DESC
""",
(team_id, page_id, pattern_1, pattern_2),
).fetchall()
return [dict(row) for row in rows]
def load_page_comments(page_id: int) -> list[dict]:
if not sqlite_table_exists("page_comments"):
return []
with get_db() as db:
rows = db.execute(
"""
SELECT
pc.id,
pc.page_id,
pc.user_id,
pc.username,
pc.body,
pc.created_at,
pc.updated_at,
u.role AS user_role,
us.name_color AS user_name_color
FROM page_comments AS pc
JOIN users AS u
ON u.id = pc.user_id
LEFT JOIN user_settings AS us
ON us.user_id = u.id
WHERE pc.page_id = ?
AND pc.deleted_at IS NULL
ORDER BY pc.created_at ASC, pc.id ASC
""",
(page_id,),
).fetchall()
return [
{
"id": row["id"],
"page_id": row["page_id"],
"user_id": row["user_id"],
"username": row["username"],
"body": row["body"],
"created_at": row["created_at"],
"updated_at": row["updated_at"],
"user_role": row["user_role"],
"user_name_color": resolve_name_color(row["user_role"], row["user_name_color"]),
}
for row in rows
]
def reaction_summary_for_message(message_id: int, user: Optional[dict]) -> list[dict]:
with get_db() as db:
rows = db.execute(
"""
SELECT
emoji,
COUNT(*) AS count,
MAX(CASE WHEN user_id = ? THEN 1 ELSE 0 END) AS reacted
FROM message_reactions
WHERE message_id = ?
GROUP BY emoji
ORDER BY count DESC, emoji ASC
""",
(user["id"] if user else -1, message_id),
).fetchall()
return [
{
"emoji": row["emoji"],
"count": row["count"],
"reacted": bool(row["reacted"]),
}
for row in rows
]
def build_profile_privacy_context(viewer: Optional[dict], target_user_id: int) -> dict:
is_self = bool(viewer and viewer["id"] == target_user_id)
is_staff = bool(viewer and viewer["role"] in {"moderator", "admin"})
is_authenticated = bool(viewer)
return {
"is_self": is_self,
"is_staff": is_staff,
"is_authenticated": is_authenticated,
}
def can_view_profile_record(settings_row, viewer: Optional[dict], target_user_id: int) -> bool:
visibility = "public"
if settings_row and "profile_visibility" in settings_row.keys():
visibility = (settings_row["profile_visibility"] or "public").lower()
if visibility not in PROFILE_VISIBILITIES:
visibility = "public"
ctx = build_profile_privacy_context(viewer, target_user_id)
if visibility == "public":
return True
if visibility == "members":
return ctx["is_authenticated"] or ctx["is_staff"] or ctx["is_self"]
if visibility == "private":
return ctx["is_self"] or ctx["is_staff"]
return True
def can_view_last_active(settings_row, viewer: Optional[dict], target_user_id: int) -> bool:
visibility = "everyone"
if settings_row and "last_active_visibility" in settings_row.keys():
visibility = (settings_row["last_active_visibility"] or "everyone").lower()
if visibility not in LAST_ACTIVE_VISIBILITIES:
visibility = "everyone"
ctx = build_profile_privacy_context(viewer, target_user_id)
if visibility == "everyone":
return True
if visibility == "members":
return ctx["is_authenticated"] or ctx["is_staff"] or ctx["is_self"]
if visibility == "nobody":
return ctx["is_self"] or ctx["is_staff"]
return False
def profile_presence_badge_visible(settings_row, viewer: Optional[dict], target_user_id: int) -> bool:
ctx = build_profile_privacy_context(viewer, target_user_id)
if ctx["is_self"] or ctx["is_staff"]:
return True
if settings_row and "show_presence_badge" in settings_row.keys():
return bool(settings_row["show_presence_badge"])
return True
def profile_payload(user_payload_func, user_row, profile_row, settings_row, viewer: Optional[dict]) -> dict:
is_self = bool(viewer and viewer["id"] == user_row["id"])
status_payload = user_payload_func(user_row, settings_row)
bio = profile_row["bio"] or ""
bio_mode = profile_row["bio_mode"] or "txt"
explicit_name_color = settings_row["name_color"] if settings_row else None
resolved = resolve_name_color(user_row["role"], explicit_name_color)
profile_visibility = settings_row["profile_visibility"] if settings_row and "profile_visibility" in settings_row.keys() else "public"
if profile_visibility not in PROFILE_VISIBILITIES:
profile_visibility = "public"
last_active_visibility = settings_row["last_active_visibility"] if settings_row and "last_active_visibility" in settings_row.keys() else "everyone"
if last_active_visibility not in LAST_ACTIVE_VISIBILITIES:
last_active_visibility = "everyone"
presence_mode = settings_row["presence_mode"] if settings_row and "presence_mode" in settings_row.keys() else "online"
if presence_mode not in PRESENCE_MODES:
presence_mode = "online"
dm_layout_mode = settings_row["dm_layout_mode"] if settings_row and "dm_layout_mode" in settings_row.keys() else "cards"
if dm_layout_mode not in DM_LAYOUT_MODES:
dm_layout_mode = "cards"
show_presence_badge = profile_presence_badge_visible(settings_row, viewer, user_row["id"])
last_active_at = user_row["last_login_at"] if "last_login_at" in user_row.keys() else None
if not can_view_last_active(settings_row, viewer, user_row["id"]):
last_active_at = None
website = profile_row["website"] or ""
website_meta = detect_website_brand(website) if website else {"host": "", "brand": "web", "label": "website"}
return {
"id": user_row["id"],
"username": user_row["username"],
"role": user_row["role"],
"status": status_payload["status"],
"presence_mode": presence_mode,
"created_at": user_row["created_at"],
"last_active_at": last_active_at,
"display_name": profile_row["display_name"] if profile_row["display_name"] else user_row["username"],
"badge_text": (profile_row["badge_text"] or "").strip() if "badge_text" in profile_row.keys() else "",
"bio": bio,
"bio_mode": bio_mode,
"bio_html": safe_profile_bio_html(bio, bio_mode),
"website": website,
"website_meta": website_meta,
"location": profile_row["location"] or "",
"status_message": profile_row["status_message"] if "status_message" in profile_row.keys() else "",
"banner_attachment_id": profile_row["banner_attachment_id"] if "banner_attachment_id" in profile_row.keys() else None,
"banner_image_url": f"/api/attachments/{profile_row['banner_attachment_id']}/download" if ("banner_attachment_id" in profile_row.keys() and profile_row["banner_attachment_id"]) else "",
"updated_at": profile_row["updated_at"],
"is_self": is_self,
"name_color": explicit_name_color,
"resolved_name_color": resolved,
"profile_background_color": settings_row["profile_background_color"] if settings_row and "profile_background_color" in settings_row.keys() else None,
"profile_banner_color": settings_row["profile_banner_color"] if settings_row and "profile_banner_color" in settings_row.keys() else None,
"profile_visibility": profile_visibility,
"last_active_visibility": last_active_visibility,
"show_presence_badge": show_presence_badge,
"dm_layout_mode": dm_layout_mode,
}
def page_payload(page, user: Optional[dict], is_team_member_func, is_team_admin_func) -> dict:
can_read = bool(
page["visibility"] == "public"
or (
user
and (
user["role"] in {"moderator", "admin"}
or is_team_member_func(page["team_id"], user["id"])
)
)
)
rendered_html = render_markdown_to_safe_html(page["body"] or "")
parent_page_id = page["parent_page_id"] if "parent_page_id" in page.keys() else None
parent_page_slug = page["parent_page_slug"] if "parent_page_slug" in page.keys() else None
parent_page_title = page["parent_page_title"] if "parent_page_title" in page.keys() else None
return {
"id": page["id"],
"team_id": page["team_id"],
"team_slug": page["team_slug"],
"team_name": page["team_name"],
"channel_id": page["channel_id"],
"channel_slug": page["channel_slug"],
"channel_name": page["channel_name"],
"parent_page_id": parent_page_id,
"parent_page_slug": parent_page_slug,
"parent_page_title": parent_page_title,
"slug": page["slug"],
"title": page["title"],
"body": page["body"],
"body_html": rendered_html,
"visibility": page["visibility"],
"page_type": page["page_type"],
"created_by_user_id": page["created_by_user_id"],
"updated_by_user_id": page["updated_by_user_id"],
"created_at": page["created_at"],
"updated_at": page["updated_at"],
"can_read": can_read,
"can_edit": bool(
user
and (
user["role"] in {"moderator", "admin"}
or is_team_admin_func(page["team_id"], user)
)
),
"children": load_page_children(page["id"]),
"backlinks": load_page_backlinks(
page["id"],
page["slug"],
page["team_id"],
),
"references": extract_page_reference_slugs(page["body"] or ""),
"comments": load_page_comments(page["id"]),
}
def list_dms_for_user(user_id: int) -> list[dict]:
with get_db() as db:
rows = db.execute(
"""
SELECT
dc.id,
dc.created_at,
other.id AS other_user_id,
other.username AS other_username,
other.role AS other_role,
us.name_color AS other_name_color,
up.display_name AS other_display_name,
up.status_message AS other_status_message,
us.presence_mode AS other_presence_mode,
(
SELECT m.text
FROM messages AS m
WHERE m.direct_conversation_id = dc.id
ORDER BY m.id DESC
LIMIT 1
) AS last_message_text,
(
SELECT m.created_at
FROM messages AS m
WHERE m.direct_conversation_id = dc.id
ORDER BY m.id DESC
LIMIT 1
) AS last_message_created_at,
(
SELECT m.id
FROM messages AS m
WHERE m.direct_conversation_id = dc.id
ORDER BY m.id DESC
LIMIT 1
) AS last_message_id,
(
SELECT COUNT(*)
FROM messages AS m2
WHERE m2.direct_conversation_id = dc.id
AND m2.user_id != ?
AND NOT EXISTS (
SELECT 1
FROM message_receipts AS mr
WHERE mr.message_id = m2.id
AND mr.user_id = ?
)
) AS unread_count
FROM direct_conversations AS dc
JOIN direct_conversation_members AS self_member
ON self_member.direct_conversation_id = dc.id
JOIN direct_conversation_members AS other_member
ON other_member.direct_conversation_id = dc.id
AND other_member.user_id != self_member.user_id
JOIN users AS other
ON other.id = other_member.user_id
LEFT JOIN user_profiles AS up
ON up.user_id = other.id
LEFT JOIN user_settings AS us
ON us.user_id = other.id
WHERE self_member.user_id = ?
ORDER BY COALESCE(last_message_id, 0) DESC, dc.id DESC
""",
(user_id, user_id, user_id),
).fetchall()
items = []
for row in rows:
items.append(
{
"id": row["id"],
"created_at": row["created_at"],
"other_user": {
"id": row["other_user_id"],
"username": row["other_username"],
"display_name": row["other_display_name"] or row["other_username"],
"role": row["other_role"],
"name_color": row["other_name_color"],
"resolved_name_color": resolve_name_color(row["other_role"], row["other_name_color"]),
"status_message": row["other_status_message"] or "",
"presence_mode": row["other_presence_mode"] or "online",
},
"last_message_text": row["last_message_text"],
"last_message_created_at": row["last_message_created_at"],
"last_message_id": row["last_message_id"],
"unread_count": int(row["unread_count"] or 0),
}
)
return items
def richer_message_payload(row, user: Optional[dict], attachment_payload_legacy_from_message_select_row, attachment_list_for_message) -> dict:
with get_db() as db:
edited = db.execute(
"""
SELECT created_at
FROM message_edits
WHERE message_id = ?
ORDER BY id DESC
LIMIT 1
""",
(row["id"],),
).fetchone()
receipt_count = db.execute(
"""
SELECT COUNT(*) AS count
FROM message_receipts
WHERE message_id = ?
""",
(row["id"],),
).fetchone()["count"]
seen_by_user = False
is_bookmarked = False
is_pinned = False
pin_row = None
bookmark_table_exists = bool(
db.execute(
"""
SELECT 1
FROM sqlite_master
WHERE type = 'table'
AND name = 'message_bookmarks'
"""
).fetchone()
)
pin_table_exists = bool(
db.execute(
"""
SELECT 1
FROM sqlite_master
WHERE type = 'table'
AND name = 'message_pins'
"""
).fetchone()
)
if user:
seen_by_user = bool(
db.execute(
"""
SELECT 1
FROM message_receipts
WHERE message_id = ?
AND user_id = ?
""",
(row["id"], user["id"]),
).fetchone()
)
if bookmark_table_exists:
is_bookmarked = bool(
db.execute(
"""
SELECT 1
FROM message_bookmarks
WHERE message_id = ?
AND user_id = ?
""",
(row["id"], user["id"]),
).fetchone()
)
if pin_table_exists:
pin_row = db.execute(
"""
SELECT pinned_at, pinned_by_user_id
FROM message_pins
WHERE message_id = ?
""",
(row["id"],),
).fetchone()
is_pinned = bool(pin_row)
primary_attachment = attachment_payload_legacy_from_message_select_row(row)
attachments = attachment_list_for_message(row["id"])
if primary_attachment and not any(item["id"] == primary_attachment["id"] for item in attachments):
attachments.insert(0, primary_attachment)
user_role = row["user_role"] if "user_role" in row.keys() else "user"
user_name_color = row["user_name_color"] if "user_name_color" in row.keys() else None
resolved_color = resolve_name_color(user_role, user_name_color)
return {
"id": row["id"],
"room_id": row["room_id"],
"channel_id": row["channel_id"],
"direct_conversation_id": row["direct_conversation_id"] if "direct_conversation_id" in row.keys() else None,
"user_id": row["user_id"],
"username": row["username"],
"user_role": user_role,
"user_name_color": resolved_color,
"text": row["text"],
"created_at": row["created_at"],
"updated_at": edited["created_at"] if edited else None,
"is_edited": bool(edited),
"parent_message_id": row["parent_message_id"],
"thread_reply_count": row["thread_reply_count"],
"attachment": primary_attachment,
"attachments": attachments,
"read_receipt_count": receipt_count,
"seen_by_current_user": seen_by_user,
"reactions": reaction_summary_for_message(row["id"], user),
"is_dm": bool(("direct_conversation_id" in row.keys()) and row["direct_conversation_id"]),
"is_pinned": is_pinned,
"pinned_at": pin_row["pinned_at"] if pin_row else None,
"pinned_by_user_id": pin_row["pinned_by_user_id"] if pin_row else None,
"is_bookmarked": is_bookmarked,
}
def feed_message_payload(row, user: Optional[dict], attachment_payload_legacy_from_message_select_row, attachment_list_for_message) -> dict:
payload = richer_message_payload(
row,
user,
attachment_payload_legacy_from_message_select_row,
attachment_list_for_message,
)
payload.update(
{
"room_slug": row["room_slug"] if "room_slug" in row.keys() else None,
"room_name": row["room_name"] if "room_name" in row.keys() else None,
"room_visibility": row["room_visibility"] if "room_visibility" in row.keys() else None,
"team_slug": row["team_slug"] if "team_slug" in row.keys() else None,
"team_name": row["team_name"] if "team_name" in row.keys() else None,
"channel_slug": row["channel_slug"] if "channel_slug" in row.keys() else None,
"channel_name": row["channel_name"] if "channel_name" in row.keys() else None,
"dm_id": row["direct_conversation_id"] if "direct_conversation_id" in row.keys() else None,
"is_mention": bool(
user and f"@{user['username'].lower()}" in payload["text"].lower()
),
}
)
return payload