Enrich names-based contact pane a bit

This commit is contained in:
Jack Kingsman
2026-03-11 15:57:29 -07:00
parent e7d1f28076
commit 93369f8d64
8 changed files with 369 additions and 86 deletions
+8
View File
@@ -225,6 +225,14 @@ class ContactDetail(BaseModel):
nearest_repeaters: list[NearestRepeater] = Field(default_factory=list)
class NameOnlyContactDetail(BaseModel):
"""Channel activity summary for a sender name that is not tied to a known key."""
name: str
channel_message_count: int = 0
most_active_rooms: list[ContactActiveRoom] = Field(default_factory=list)
class Channel(BaseModel):
key: str = Field(description="Channel key (32-char hex)")
name: str
+31
View File
@@ -545,6 +545,16 @@ class MessageRepository:
row = await cursor.fetchone()
return row["cnt"] if row else 0
@staticmethod
async def count_channel_messages_by_sender_name(sender_name: str) -> int:
"""Count channel messages attributed to a display name."""
cursor = await db.conn.execute(
"SELECT COUNT(*) as cnt FROM messages WHERE type = 'CHAN' AND sender_name = ?",
(sender_name,),
)
row = await cursor.fetchone()
return row["cnt"] if row else 0
@staticmethod
async def get_channel_stats(conversation_key: str) -> dict:
"""Get channel message statistics: time-windowed counts, first message, unique senders, top senders.
@@ -632,3 +642,24 @@ class MessageRepository:
)
rows = await cursor.fetchall()
return [(row["conversation_key"], row["channel_name"], row["cnt"]) for row in rows]
@staticmethod
async def get_most_active_rooms_by_sender_name(
sender_name: str, limit: int = 5
) -> list[tuple[str, str, int]]:
"""Get channels where a display name has sent the most messages."""
cursor = await db.conn.execute(
"""
SELECT m.conversation_key, COALESCE(c.name, m.conversation_key) AS channel_name,
COUNT(*) AS cnt
FROM messages m
LEFT JOIN channels c ON m.conversation_key = c.key
WHERE m.type = 'CHAN' AND m.sender_name = ?
GROUP BY m.conversation_key
ORDER BY cnt DESC
LIMIT ?
""",
(sender_name, limit),
)
rows = await cursor.fetchall()
return [(row["conversation_key"], row["channel_name"], row["cnt"]) for row in rows]
+24
View File
@@ -14,6 +14,7 @@ from app.models import (
ContactRoutingOverrideRequest,
ContactUpsert,
CreateContactRequest,
NameOnlyContactDetail,
NearestRepeater,
TraceResponse,
)
@@ -249,6 +250,29 @@ async def get_contact_detail(public_key: str) -> ContactDetail:
)
@router.get("/name-detail", response_model=NameOnlyContactDetail)
async def get_name_only_contact_detail(
name: str = Query(min_length=1, max_length=200),
) -> NameOnlyContactDetail:
"""Get channel activity summary for a sender name without a resolved key."""
normalized_name = name.strip()
if not normalized_name:
raise HTTPException(status_code=400, detail="name is required")
chan_count = await MessageRepository.count_channel_messages_by_sender_name(normalized_name)
active_rooms_raw = await MessageRepository.get_most_active_rooms_by_sender_name(normalized_name)
most_active_rooms = [
ContactActiveRoom(channel_key=key, channel_name=room_name, message_count=count)
for key, room_name, count in active_rooms_raw
]
return NameOnlyContactDetail(
name=normalized_name,
channel_message_count=chan_count,
most_active_rooms=most_active_rooms,
)
@router.get("/{public_key}", response_model=Contact)
async def get_contact(public_key: str) -> Contact:
"""Get a specific contact by public key or prefix."""