import json import re import time from typing import Optional from config import utc_now from db import get_db from auth import user_payload ROOM_SLUG_RE = re.compile(r"^[a-z0-9](?:[a-z0-9-]{0,46}[a-z0-9])?$") MESSAGE_SELECT = """ SELECT m.id, m.room_id, m.channel_id, m.direct_conversation_id, m.user_id, m.username, m.text, m.created_at, m.attachment_id, m.parent_message_id, ( SELECT COUNT(*) FROM messages AS thread_reply WHERE thread_reply.parent_message_id = m.id ) AS thread_reply_count, a.original_name AS attachment_original_name, a.mime_type AS attachment_mime_type, a.size_bytes AS attachment_size_bytes, u.role AS user_role, us.name_color AS user_name_color FROM messages AS m JOIN users AS u ON u.id = m.user_id LEFT JOIN user_settings AS us ON us.user_id = u.id LEFT JOIN attachments AS a ON a.id = m.attachment_id """ def team_page_select() -> str: has_parent_page = False try: with get_db() as db: rows = db.execute("PRAGMA table_info(pages)").fetchall() has_parent_page = any(row["name"] == "parent_page_id" for row in rows) except Exception: has_parent_page = False if has_parent_page: return """ SELECT p.id, p.team_id, p.channel_id, p.parent_page_id, parent.slug AS parent_page_slug, parent.title AS parent_page_title, p.slug, p.title, p.body, p.visibility, p.page_type, p.created_by_user_id, p.updated_by_user_id, p.created_at, p.updated_at, p.is_active, t.slug AS team_slug, t.name AS team_name, c.slug AS channel_slug, c.name AS channel_name FROM pages AS p JOIN teams AS t ON t.id = p.team_id LEFT JOIN channels AS c ON c.id = p.channel_id LEFT JOIN pages AS parent ON parent.id = p.parent_page_id """ return """ SELECT p.id, p.team_id, p.channel_id, NULL AS parent_page_id, NULL AS parent_page_slug, NULL AS parent_page_title, p.slug, p.title, p.body, p.visibility, p.page_type, p.created_by_user_id, p.updated_by_user_id, p.created_at, p.updated_at, p.is_active, t.slug AS team_slug, t.name AS team_name, c.slug AS channel_slug, c.name AS channel_name FROM pages AS p JOIN teams AS t ON t.id = p.team_id LEFT JOIN channels AS c ON c.id = p.channel_id """ def team_slug_from_name(name: str) -> str: return re.sub(r"[^a-z0-9-]+", "-", name.lower()).strip("-")[:48].strip("-") def page_slug_from_title(title: str) -> str: return re.sub(r"[^a-z0-9-]+", "-", title.lower()).strip("-")[:48].strip("-") def room_by_id(room_id: int): with get_db() as db: return db.execute( """ SELECT id, slug, name, description, visibility, created_by_user_id, is_active FROM rooms WHERE id = ? AND is_active = 1 """, (room_id,), ).fetchone() def get_room_by_slug(slug: str): with get_db() as db: return db.execute( """ SELECT id, slug, name, description, visibility, created_by_user_id, is_active FROM rooms WHERE slug = ? AND is_active = 1 """, (slug,), ).fetchone() def room_member(room_id: int, user_id: int) -> bool: with get_db() as db: row = db.execute( """ SELECT 1 FROM room_members WHERE room_id = ? AND user_id = ? """, (room_id, user_id), ).fetchone() return bool(row) def room_owner(room_id: int, user_id: int) -> bool: with get_db() as db: row = db.execute( """ SELECT 1 FROM room_members WHERE room_id = ? AND user_id = ? AND membership_role = 'owner' """, (room_id, user_id), ).fetchone() return bool(row) def can_access_room(room, user: Optional[dict]) -> bool: if room["visibility"] == "public": return True if not user: return False if user["role"] in {"moderator", "admin"}: return True return room_member(room["id"], user["id"]) def can_write_room(room, user: Optional[dict]) -> bool: if not user or user["status"] != "active": return False return can_access_room(room, user) def can_manage_room(room, user: Optional[dict]) -> bool: if not user: return False if user["role"] == "admin": return True return room_owner(room["id"], user["id"]) def room_payload(room, user: Optional[dict]) -> dict: return { "id": room["id"], "slug": room["slug"], "name": room["name"], "description": room["description"], "visibility": room["visibility"], "can_read": can_access_room(room, user), "can_write": can_write_room(room, user), "can_manage": can_manage_room(room, user), } def get_team_by_slug(slug: str): with get_db() as db: return db.execute( """ SELECT id, slug, name, description, created_by_user_id, created_at, is_active FROM teams WHERE slug = ? AND is_active = 1 """, (slug,), ).fetchone() def get_channel_by_id(channel_id: int): with get_db() as db: return db.execute( """ SELECT c.id, c.team_id, c.legacy_room_id, c.slug, c.name, c.description, c.created_by_user_id, c.created_at, c.is_active, t.slug AS team_slug, t.name AS team_name, t.description AS team_description, t.is_active AS team_is_active FROM channels AS c JOIN teams AS t ON t.id = c.team_id WHERE c.id = ? AND c.is_active = 1 AND t.is_active = 1 """, (channel_id,), ).fetchone() def get_channel_by_slug(team_slug: str, channel_slug: str): with get_db() as db: return db.execute( """ SELECT c.id, c.team_id, c.legacy_room_id, c.slug, c.name, c.description, c.created_by_user_id, c.created_at, c.is_active, t.slug AS team_slug, t.name AS team_name, t.description AS team_description, t.is_active AS team_is_active FROM channels AS c JOIN teams AS t ON t.id = c.team_id WHERE t.slug = ? AND c.slug = ? AND t.is_active = 1 AND c.is_active = 1 """, (team_slug, channel_slug), ).fetchone() def get_channel_by_legacy_room_id(room_id: int): with get_db() as db: return db.execute( """ SELECT c.id, c.team_id, c.legacy_room_id, c.slug, c.name, c.description, c.created_by_user_id, c.created_at, c.is_active, t.slug AS team_slug, t.name AS team_name, t.description AS team_description, t.is_active AS team_is_active FROM channels AS c JOIN teams AS t ON t.id = c.team_id WHERE c.legacy_room_id = ? AND c.is_active = 1 AND t.is_active = 1 """, (room_id,), ).fetchone() def team_member_role(team_id: int, user_id: int) -> Optional[str]: with get_db() as db: row = db.execute( """ SELECT membership_role FROM team_members WHERE team_id = ? AND user_id = ? """, (team_id, user_id), ).fetchone() return row["membership_role"] if row else None def is_team_member(team_id: int, user_id: int) -> bool: return team_member_role(team_id, user_id) is not None def is_team_admin(team_id: int, user: Optional[dict]) -> bool: if not user: return False if user["role"] == "admin": return True return team_member_role(team_id, user["id"]) in {"owner", "admin"} def can_edit_team(team_id: int, user: Optional[dict]) -> bool: return is_team_admin(team_id, user) def can_access_team(team, user: Optional[dict]) -> bool: if not team["is_active"]: return False if user and user["role"] in {"moderator", "admin"}: return True return bool(user and is_team_member(team["id"], user["id"])) def legacy_room_for_channel(channel): if not channel["legacy_room_id"]: return None return room_by_id(channel["legacy_room_id"]) def can_access_channel(channel, user: Optional[dict]) -> bool: if user and user["role"] in {"moderator", "admin"}: return True legacy_room = legacy_room_for_channel(channel) if legacy_room: return can_access_room(legacy_room, user) return bool(user and is_team_member(channel["team_id"], user["id"])) def can_write_channel(channel, user: Optional[dict]) -> bool: return bool(user and user["status"] == "active" and can_access_channel(channel, user)) def team_payload(team, user: Optional[dict]) -> dict: membership_role = team_member_role(team["id"], user["id"]) if user else None return { "id": team["id"], "slug": team["slug"], "name": team["name"], "description": team["description"], "membership_role": membership_role, "can_read": can_access_team(team, user), "can_manage": is_team_admin(team["id"], user), } def channel_payload(channel, user: Optional[dict]) -> dict: return { "id": channel["id"], "team_id": channel["team_id"], "team_slug": channel["team_slug"], "team_name": channel["team_name"], "legacy_room_id": channel["legacy_room_id"], "slug": channel["slug"], "name": channel["name"], "description": channel["description"], "can_read": can_access_channel(channel, user), "can_write": can_write_channel(channel, user), "can_manage": is_team_admin(channel["team_id"], user), } def get_dm_for_user(dm_id: int, user_id: int): with get_db() as db: row = db.execute( """ SELECT dc.id, dc.created_at FROM direct_conversations AS dc JOIN direct_conversation_members AS dcm ON dcm.direct_conversation_id = dc.id WHERE dc.id = ? AND dcm.user_id = ? """, (dm_id, user_id), ).fetchone() return row def get_dm_other_user(dm_id: int, user_id: int): with get_db() as db: row = db.execute( """ SELECT u.id, u.username, u.role, up.display_name, up.status_message, us.name_color, us.presence_mode FROM direct_conversation_members AS dcm JOIN users AS u ON u.id = dcm.user_id LEFT JOIN user_profiles AS up ON up.user_id = u.id LEFT JOIN user_settings AS us ON us.user_id = u.id WHERE dcm.direct_conversation_id = ? AND dcm.user_id != ? LIMIT 1 """, (dm_id, user_id), ).fetchone() return row def validate_dm_access(dm_id: int, user: Optional[dict]) -> bool: if not user: return False return bool(get_dm_for_user(dm_id, user["id"])) def dm_payload(dm_id: int, user_id: int, resolve_name_color) -> dict: dm = get_dm_for_user(dm_id, user_id) other = get_dm_other_user(dm_id, user_id) return { "id": dm["id"], "created_at": dm["created_at"], "other_user": { "id": other["id"], "username": other["username"], "display_name": other["display_name"] or other["username"], "role": other["role"], "name_color": other["name_color"], "resolved_name_color": resolve_name_color(other["role"], other["name_color"]), "status_message": other["status_message"] or "", "presence_mode": other["presence_mode"] or "online", }, } def find_or_create_dm(user_a_id: int, user_b_id: int, now_iso_func) -> int: low_id, high_id = sorted([int(user_a_id), int(user_b_id)]) with get_db() as db: existing = db.execute( """ SELECT dc.id FROM direct_conversations AS dc JOIN direct_conversation_members AS m1 ON m1.direct_conversation_id = dc.id AND m1.user_id = ? JOIN direct_conversation_members AS m2 ON m2.direct_conversation_id = dc.id AND m2.user_id = ? WHERE dc.member_pair_key = ? LIMIT 1 """, (low_id, high_id, f"{low_id}:{high_id}"), ).fetchone() if existing: return existing["id"] with get_db() as db: db.execute("BEGIN IMMEDIATE") existing = db.execute( """ SELECT id FROM direct_conversations WHERE member_pair_key = ? LIMIT 1 """, (f"{low_id}:{high_id}",), ).fetchone() if existing: db.commit() return existing["id"] cursor = db.execute( """ INSERT INTO direct_conversations ( member_pair_key, created_at ) VALUES (?, ?) """, (f"{low_id}:{high_id}", now_iso_func()), ) dm_id = cursor.lastrowid db.execute( """ INSERT INTO direct_conversation_members ( direct_conversation_id, user_id, added_at ) VALUES (?, ?, ?) """, (dm_id, low_id, now_iso_func()), ) db.execute( """ INSERT INTO direct_conversation_members ( direct_conversation_id, user_id, added_at ) VALUES (?, ?, ?) """, (dm_id, high_id, now_iso_func()), ) db.commit() return dm_id def update_presence( db_write_func, user_id: int, view_name: Optional[str] = None, room_id: Optional[int] = None, channel_id: Optional[int] = None, direct_conversation_id: Optional[int] = None, ) -> None: db_write_func( """ INSERT INTO presence_events ( user_id, updated_at, view_name, room_id, channel_id, direct_conversation_id ) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT(user_id) DO UPDATE SET updated_at = excluded.updated_at, view_name = excluded.view_name, room_id = excluded.room_id, channel_id = excluded.channel_id, direct_conversation_id = excluded.direct_conversation_id """, ( user_id, time.time(), view_name, room_id, channel_id, direct_conversation_id, ), ) def room_presence_count(ttl_seconds: int, room_id: int) -> int: cutoff = time.time() - ttl_seconds with get_db() as db: row = db.execute( """ SELECT COUNT(*) AS count FROM presence_events WHERE room_id = ? AND updated_at >= ? """, (room_id, cutoff), ).fetchone() return row["count"] if row else 0 def channel_presence_count(ttl_seconds: int, channel_id: int) -> int: cutoff = time.time() - ttl_seconds with get_db() as db: row = db.execute( """ SELECT COUNT(*) AS count FROM presence_events WHERE channel_id = ? AND updated_at >= ? """, (channel_id, cutoff), ).fetchone() return row["count"] if row else 0 def dm_presence_count(ttl_seconds: int, dm_id: int) -> int: cutoff = time.time() - ttl_seconds with get_db() as db: row = db.execute( """ SELECT COUNT(*) AS count FROM presence_events WHERE direct_conversation_id = ? AND updated_at >= ? """, (dm_id, cutoff), ).fetchone() return row["count"] if row else 0