mirror of
https://github.com/ipnet-mesh/meshcore-hub.git
synced 2026-08-07 01:13:11 +02:00
Add node name lookups to advertisements list
- Join with Node table to get node names and tags for both source and receiver nodes - Display friendly_name (from tags), node_name, or advertised name with priority in that order - Show name with public key preview for both Node and Received By columns
This commit is contained in:
@@ -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 AdvertisementList, Advertisemen
|
||||
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=AdvertisementList)
|
||||
async def list_advertisements(
|
||||
_: RequireRead,
|
||||
@@ -29,13 +39,23 @@ async def list_advertisements(
|
||||
offset: int = Query(0, ge=0, description="Page offset"),
|
||||
) -> AdvertisementList:
|
||||
"""List advertisements with filtering and pagination."""
|
||||
# Alias for receiver node join
|
||||
# Aliases for node joins
|
||||
ReceiverNode = aliased(Node)
|
||||
SourceNode = aliased(Node)
|
||||
|
||||
# Build query with receiver node join
|
||||
query = select(
|
||||
Advertisement, ReceiverNode.public_key.label("receiver_pk")
|
||||
).outerjoin(ReceiverNode, Advertisement.receiver_node_id == ReceiverNode.id)
|
||||
# Build query with both receiver and source node joins
|
||||
query = (
|
||||
select(
|
||||
Advertisement,
|
||||
ReceiverNode.public_key.label("receiver_pk"),
|
||||
ReceiverNode.name.label("receiver_name"),
|
||||
ReceiverNode.id.label("receiver_id"),
|
||||
SourceNode.name.label("source_name"),
|
||||
SourceNode.id.label("source_id"),
|
||||
)
|
||||
.outerjoin(ReceiverNode, Advertisement.receiver_node_id == ReceiverNode.id)
|
||||
.outerjoin(SourceNode, Advertisement.node_id == SourceNode.id)
|
||||
)
|
||||
|
||||
if public_key:
|
||||
query = query.where(Advertisement.public_key == public_key)
|
||||
@@ -59,16 +79,38 @@ async def list_advertisements(
|
||||
# Execute
|
||||
results = session.execute(query).all()
|
||||
|
||||
# Build response with received_by
|
||||
# Collect node IDs to fetch tags
|
||||
node_ids = set()
|
||||
for row in results:
|
||||
if row.receiver_id:
|
||||
node_ids.add(row.receiver_id)
|
||||
if row.source_id:
|
||||
node_ids.add(row.source_id)
|
||||
|
||||
# Fetch nodes with tags
|
||||
nodes_by_id: dict[str, Node] = {}
|
||||
if node_ids:
|
||||
nodes_query = (
|
||||
select(Node).where(Node.id.in_(node_ids)).options(selectinload(Node.tags))
|
||||
)
|
||||
nodes = session.execute(nodes_query).scalars().all()
|
||||
nodes_by_id = {n.id: n for n in nodes}
|
||||
|
||||
# Build response with node details
|
||||
items = []
|
||||
for adv, receiver_pk in results:
|
||||
for row in results:
|
||||
adv = row[0]
|
||||
receiver_node = nodes_by_id.get(row.receiver_id) if row.receiver_id else None
|
||||
source_node = nodes_by_id.get(row.source_id) if row.source_id else None
|
||||
|
||||
data = {
|
||||
"id": adv.id,
|
||||
"receiver_node_id": adv.receiver_node_id,
|
||||
"received_by": receiver_pk,
|
||||
"node_id": adv.node_id,
|
||||
"received_by": row.receiver_pk,
|
||||
"receiver_name": row.receiver_name,
|
||||
"receiver_friendly_name": _get_friendly_name(receiver_node),
|
||||
"public_key": adv.public_key,
|
||||
"name": adv.name,
|
||||
"node_name": row.source_name,
|
||||
"node_friendly_name": _get_friendly_name(source_node),
|
||||
"adv_type": adv.adv_type,
|
||||
"flags": adv.flags,
|
||||
"received_at": adv.received_at,
|
||||
@@ -92,9 +134,18 @@ async def get_advertisement(
|
||||
) -> AdvertisementRead:
|
||||
"""Get a single advertisement by ID."""
|
||||
ReceiverNode = aliased(Node)
|
||||
SourceNode = aliased(Node)
|
||||
query = (
|
||||
select(Advertisement, ReceiverNode.public_key.label("receiver_pk"))
|
||||
select(
|
||||
Advertisement,
|
||||
ReceiverNode.public_key.label("receiver_pk"),
|
||||
ReceiverNode.name.label("receiver_name"),
|
||||
ReceiverNode.id.label("receiver_id"),
|
||||
SourceNode.name.label("source_name"),
|
||||
SourceNode.id.label("source_id"),
|
||||
)
|
||||
.outerjoin(ReceiverNode, Advertisement.receiver_node_id == ReceiverNode.id)
|
||||
.outerjoin(SourceNode, Advertisement.node_id == SourceNode.id)
|
||||
.where(Advertisement.id == advertisement_id)
|
||||
)
|
||||
result = session.execute(query).one_or_none()
|
||||
@@ -102,14 +153,34 @@ async def get_advertisement(
|
||||
if not result:
|
||||
raise HTTPException(status_code=404, detail="Advertisement not found")
|
||||
|
||||
adv, receiver_pk = result
|
||||
adv = result[0]
|
||||
|
||||
# Fetch nodes with tags for friendly names
|
||||
node_ids = []
|
||||
if result.receiver_id:
|
||||
node_ids.append(result.receiver_id)
|
||||
if result.source_id:
|
||||
node_ids.append(result.source_id)
|
||||
|
||||
nodes_by_id: dict[str, Node] = {}
|
||||
if node_ids:
|
||||
nodes_query = (
|
||||
select(Node).where(Node.id.in_(node_ids)).options(selectinload(Node.tags))
|
||||
)
|
||||
nodes = session.execute(nodes_query).scalars().all()
|
||||
nodes_by_id = {n.id: n for n in nodes}
|
||||
|
||||
receiver_node = nodes_by_id.get(result.receiver_id) if result.receiver_id else None
|
||||
source_node = nodes_by_id.get(result.source_id) if result.source_id else None
|
||||
|
||||
data = {
|
||||
"id": adv.id,
|
||||
"receiver_node_id": adv.receiver_node_id,
|
||||
"received_by": receiver_pk,
|
||||
"node_id": adv.node_id,
|
||||
"received_by": result.receiver_pk,
|
||||
"receiver_name": result.receiver_name,
|
||||
"receiver_friendly_name": _get_friendly_name(receiver_node),
|
||||
"public_key": adv.public_key,
|
||||
"name": adv.name,
|
||||
"node_name": result.source_name,
|
||||
"node_friendly_name": _get_friendly_name(source_node),
|
||||
"adv_type": adv.adv_type,
|
||||
"flags": adv.flags,
|
||||
"received_at": adv.received_at,
|
||||
|
||||
@@ -84,8 +84,18 @@ class AdvertisementRead(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"
|
||||
)
|
||||
public_key: str = Field(..., description="Advertised public key")
|
||||
name: Optional[str] = Field(default=None, description="Advertised name")
|
||||
node_name: Optional[str] = Field(
|
||||
default=None, description="Node name from nodes table"
|
||||
)
|
||||
node_friendly_name: Optional[str] = Field(
|
||||
default=None, description="Node friendly name from tags"
|
||||
)
|
||||
adv_type: Optional[str] = Field(default=None, description="Node type")
|
||||
flags: Optional[int] = Field(default=None, description="Capability flags")
|
||||
received_at: datetime = Field(..., description="When received")
|
||||
|
||||
@@ -48,8 +48,8 @@
|
||||
<tr class="hover">
|
||||
<td>
|
||||
<a href="/nodes/{{ ad.public_key }}" class="link link-hover">
|
||||
{% if ad.name %}
|
||||
<div class="font-medium">{{ ad.name }}</div>
|
||||
{% if ad.node_friendly_name or ad.node_name or ad.name %}
|
||||
<div class="font-medium">{{ ad.node_friendly_name or ad.node_name or ad.name }}</div>
|
||||
<div class="text-xs font-mono opacity-70">{{ ad.public_key[:16] }}...</div>
|
||||
{% else %}
|
||||
<span class="font-mono text-sm">{{ ad.public_key[:16] }}...</span>
|
||||
@@ -58,8 +58,13 @@
|
||||
</td>
|
||||
<td>
|
||||
{% if ad.received_by %}
|
||||
<a href="/nodes/{{ ad.received_by }}" class="link link-hover font-mono text-sm" title="{{ ad.received_by }}">
|
||||
{{ ad.received_by[:16] }}...
|
||||
<a href="/nodes/{{ ad.received_by }}" class="link link-hover">
|
||||
{% if ad.receiver_friendly_name or ad.receiver_name %}
|
||||
<div class="font-medium">{{ ad.receiver_friendly_name or ad.receiver_name }}</div>
|
||||
<div class="text-xs font-mono opacity-70">{{ ad.received_by[:16] }}...</div>
|
||||
{% else %}
|
||||
<span class="font-mono text-sm">{{ ad.received_by[:16] }}...</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
{% else %}
|
||||
<span class="opacity-50">-</span>
|
||||
|
||||
Reference in New Issue
Block a user