From 087a3c4c432e57c64f1fb54952503c8cda13fcf0 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 5 Dec 2025 18:09:29 +0000 Subject: [PATCH] Messages list: swap Time/Type columns, add receiver node links - Swap Time and Type columns (Type now first) - Add receiver_name and receiver_friendly_name to MessageRead schema - Update messages API to fetch receiver node names and tags - Make Receiver column a link showing name with public key prefix --- src/meshcore_hub/api/routes/messages.py | 49 ++++++++++++++++++-- src/meshcore_hub/common/schemas/messages.py | 4 ++ src/meshcore_hub/web/templates/messages.html | 19 +++++--- 3 files changed, 61 insertions(+), 11 deletions(-) diff --git a/src/meshcore_hub/api/routes/messages.py b/src/meshcore_hub/api/routes/messages.py index 98bfc04..79a461a 100644 --- a/src/meshcore_hub/api/routes/messages.py +++ b/src/meshcore_hub/api/routes/messages.py @@ -5,7 +5,7 @@ from typing import Optional from fastapi import APIRouter, HTTPException, Query from sqlalchemy import func, select -from sqlalchemy.orm import aliased +from sqlalchemy.orm import aliased, selectinload from meshcore_hub.api.auth import RequireRead from meshcore_hub.api.dependencies import DbSession @@ -15,6 +15,16 @@ from meshcore_hub.common.schemas.messages import MessageList, MessageRead router = APIRouter() +def _get_friendly_name(node: Optional[Node]) -> Optional[str]: + """Extract friendly_name tag from a node's tags.""" + if not node or not node.tags: + return None + for tag in node.tags: + if tag.key == "friendly_name": + return tag.value + return None + + @router.get("", response_model=MessageList) async def list_messages( _: RequireRead, @@ -36,9 +46,12 @@ async def list_messages( ReceiverNode = aliased(Node) # Build query with receiver node join - query = select(Message, ReceiverNode.public_key.label("receiver_pk")).outerjoin( - ReceiverNode, Message.receiver_node_id == ReceiverNode.id - ) + query = select( + Message, + ReceiverNode.public_key.label("receiver_pk"), + ReceiverNode.name.label("receiver_name"), + ReceiverNode.id.label("receiver_id"), + ).outerjoin(ReceiverNode, Message.receiver_node_id == ReceiverNode.id) if message_type: query = query.where(Message.message_type == message_type) @@ -96,13 +109,39 @@ async def list_messages( for public_key, value in session.execute(friendly_name_query).all(): friendly_names[public_key[:12]] = value + # Collect receiver node IDs to fetch tags + receiver_ids = set() + for row in results: + if row.receiver_id: + receiver_ids.add(row.receiver_id) + + # Fetch receiver nodes with tags + receivers_by_id: dict[str, Node] = {} + if receiver_ids: + receivers_query = ( + select(Node) + .where(Node.id.in_(receiver_ids)) + .options(selectinload(Node.tags)) + ) + receivers = session.execute(receivers_query).scalars().all() + receivers_by_id = {n.id: n for n in receivers} + # Build response with sender info and received_by items = [] - for m, receiver_pk in results: + for row in results: + m = row[0] + receiver_pk = row.receiver_pk + receiver_name = row.receiver_name + receiver_node = ( + receivers_by_id.get(row.receiver_id) if row.receiver_id else None + ) + msg_dict = { "id": m.id, "receiver_node_id": m.receiver_node_id, "received_by": receiver_pk, + "receiver_name": receiver_name, + "receiver_friendly_name": _get_friendly_name(receiver_node), "message_type": m.message_type, "pubkey_prefix": m.pubkey_prefix, "sender_name": ( diff --git a/src/meshcore_hub/common/schemas/messages.py b/src/meshcore_hub/common/schemas/messages.py index 0ab0a63..105b751 100644 --- a/src/meshcore_hub/common/schemas/messages.py +++ b/src/meshcore_hub/common/schemas/messages.py @@ -12,6 +12,10 @@ class MessageRead(BaseModel): received_by: Optional[str] = Field( default=None, description="Receiving interface node public key" ) + receiver_name: Optional[str] = Field(default=None, description="Receiver node name") + receiver_friendly_name: Optional[str] = Field( + default=None, description="Receiver friendly name from tags" + ) message_type: str = Field(..., description="Message type (contact, channel)") pubkey_prefix: Optional[str] = Field( default=None, description="Sender's public key prefix (12 chars)" diff --git a/src/meshcore_hub/web/templates/messages.html b/src/meshcore_hub/web/templates/messages.html index 684443c..8fe1425 100644 --- a/src/meshcore_hub/web/templates/messages.html +++ b/src/meshcore_hub/web/templates/messages.html @@ -53,8 +53,8 @@ - + @@ -64,9 +64,6 @@ {% for msg in messages %} - + -
Time TypeTime From/Channel Message Receiver
- {{ msg.received_at[:19].replace('T', ' ') if msg.received_at else '-' }} - {% if msg.message_type == 'channel' %} Channel @@ -74,6 +71,9 @@ Direct {% endif %} + {{ msg.received_at[:19].replace('T', ' ') if msg.received_at else '-' }} + {% if msg.message_type == 'channel' %} CH{{ msg.channel_idx }} @@ -86,9 +86,16 @@ {% endif %} {{ msg.text or '-' }} + {% if msg.received_by %} - {{ msg.received_by[:8] }}... + + {% if msg.receiver_friendly_name or msg.receiver_name %} +
{{ msg.receiver_friendly_name or msg.receiver_name }}
+
{{ msg.received_by[:16] }}...
+ {% else %} + {{ msg.received_by[:16] }}... + {% endif %} +
{% else %} - {% endif %}