mirror of
https://github.com/ipnet-mesh/meshcore-hub.git
synced 2026-05-08 06:14:47 +02:00
Remove internal UUID fields from API responses
Internal database UUIDs (id, node_id, receiver_node_id) were being exposed in API responses. These are implementation details that should not be visible to API consumers. The canonical identifier for nodes is the 64-char hex public_key. Changes: - Remove id, node_id from NodeTagRead, NodeRead schemas - Remove id from MemberRead schema - Remove id, receiver_node_id, node_id from MessageRead, AdvertisementRead, TracePathRead, TelemetryRead schemas - Update web map component to use public_key instead of member.id for owner filtering - Update tests to not assert on removed fields
This commit is contained in:
@@ -83,7 +83,6 @@ class MemberUpdate(BaseModel):
|
||||
class MemberRead(BaseModel):
|
||||
"""Schema for reading a member."""
|
||||
|
||||
id: str = Field(..., description="Member UUID")
|
||||
name: str = Field(..., description="Member's display name")
|
||||
callsign: Optional[str] = Field(default=None, description="Amateur radio callsign")
|
||||
role: Optional[str] = Field(default=None, description="Member's role")
|
||||
|
||||
@@ -9,10 +9,6 @@ from pydantic import BaseModel, Field
|
||||
class MessageRead(BaseModel):
|
||||
"""Schema for reading a message."""
|
||||
|
||||
id: str = Field(..., description="Message UUID")
|
||||
receiver_node_id: Optional[str] = Field(
|
||||
default=None, description="Receiving interface node UUID"
|
||||
)
|
||||
receiver_public_key: Optional[str] = Field(
|
||||
default=None, description="Receiving interface node public key"
|
||||
)
|
||||
@@ -85,14 +81,9 @@ class MessageFilters(BaseModel):
|
||||
class AdvertisementRead(BaseModel):
|
||||
"""Schema for reading an advertisement."""
|
||||
|
||||
id: str = Field(..., description="Advertisement UUID")
|
||||
receiver_node_id: Optional[str] = Field(
|
||||
default=None, description="Receiving interface node UUID"
|
||||
)
|
||||
receiver_public_key: Optional[str] = Field(
|
||||
default=None, description="Receiving interface node public key"
|
||||
)
|
||||
node_id: Optional[str] = Field(default=None, description="Advertised node UUID")
|
||||
public_key: str = Field(..., description="Advertised public key")
|
||||
name: Optional[str] = Field(default=None, description="Advertised name")
|
||||
adv_type: Optional[str] = Field(default=None, description="Node type")
|
||||
@@ -116,10 +107,6 @@ class AdvertisementList(BaseModel):
|
||||
class TracePathRead(BaseModel):
|
||||
"""Schema for reading a trace path."""
|
||||
|
||||
id: str = Field(..., description="Trace path UUID")
|
||||
receiver_node_id: Optional[str] = Field(
|
||||
default=None, description="Receiving interface node UUID"
|
||||
)
|
||||
receiver_public_key: Optional[str] = Field(
|
||||
default=None, description="Receiving interface node public key"
|
||||
)
|
||||
@@ -153,14 +140,9 @@ class TracePathList(BaseModel):
|
||||
class TelemetryRead(BaseModel):
|
||||
"""Schema for reading a telemetry record."""
|
||||
|
||||
id: str = Field(..., description="Telemetry UUID")
|
||||
receiver_node_id: Optional[str] = Field(
|
||||
default=None, description="Receiving interface node UUID"
|
||||
)
|
||||
receiver_public_key: Optional[str] = Field(
|
||||
default=None, description="Receiving interface node public key"
|
||||
)
|
||||
node_id: Optional[str] = Field(default=None, description="Reporting node UUID")
|
||||
node_public_key: str = Field(..., description="Reporting node public key")
|
||||
parsed_data: Optional[dict] = Field(
|
||||
default=None, description="Decoded sensor readings"
|
||||
|
||||
@@ -41,8 +41,6 @@ class NodeTagUpdate(BaseModel):
|
||||
class NodeTagRead(BaseModel):
|
||||
"""Schema for reading a node tag."""
|
||||
|
||||
id: str = Field(..., description="Tag UUID")
|
||||
node_id: str = Field(..., description="Parent node UUID")
|
||||
key: str = Field(..., description="Tag name/key")
|
||||
value: Optional[str] = Field(default=None, description="Tag value")
|
||||
value_type: str = Field(..., description="Value type hint")
|
||||
@@ -56,7 +54,6 @@ class NodeTagRead(BaseModel):
|
||||
class NodeRead(BaseModel):
|
||||
"""Schema for reading a node."""
|
||||
|
||||
id: str = Field(..., description="Node UUID")
|
||||
public_key: str = Field(..., description="Node's 64-character hex public key")
|
||||
name: Optional[str] = Field(default=None, description="Node display name")
|
||||
adv_type: Optional[str] = Field(default=None, description="Advertisement type")
|
||||
|
||||
@@ -43,13 +43,14 @@ async def map_data(request: Request) -> JSONResponse:
|
||||
if members_response.status_code == 200:
|
||||
members_data = members_response.json()
|
||||
for member in members_data.get("items", []):
|
||||
member_info = {
|
||||
"id": member.get("id"),
|
||||
"name": member.get("name"),
|
||||
"callsign": member.get("callsign"),
|
||||
}
|
||||
members_list.append(member_info)
|
||||
# Only include members with public_key (required for node ownership)
|
||||
if member.get("public_key"):
|
||||
member_info = {
|
||||
"public_key": member.get("public_key"),
|
||||
"name": member.get("name"),
|
||||
"callsign": member.get("callsign"),
|
||||
}
|
||||
members_list.append(member_info)
|
||||
members_by_key[member["public_key"]] = member_info
|
||||
else:
|
||||
logger.warning(
|
||||
|
||||
@@ -211,7 +211,7 @@
|
||||
|
||||
// Owner filter
|
||||
if (ownerFilter) {
|
||||
if (!node.owner || node.owner.id !== ownerFilter) return false;
|
||||
if (!node.owner || node.owner.public_key !== ownerFilter) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -269,12 +269,12 @@
|
||||
const ownersWithNodes = new Set();
|
||||
allNodes.forEach(node => {
|
||||
if (node.owner) {
|
||||
ownersWithNodes.add(node.owner.id);
|
||||
ownersWithNodes.add(node.owner.public_key);
|
||||
}
|
||||
});
|
||||
|
||||
// Filter members to only those who own nodes on the map
|
||||
const relevantMembers = allMembers.filter(m => ownersWithNodes.has(m.id));
|
||||
const relevantMembers = allMembers.filter(m => ownersWithNodes.has(m.public_key));
|
||||
|
||||
// Sort by name
|
||||
relevantMembers.sort((a, b) => a.name.localeCompare(b.name));
|
||||
@@ -282,7 +282,7 @@
|
||||
// Add options
|
||||
relevantMembers.forEach(member => {
|
||||
const option = document.createElement('option');
|
||||
option.value = member.id;
|
||||
option.value = member.public_key;
|
||||
option.textContent = member.callsign
|
||||
? `${member.name} (${member.callsign})`
|
||||
: member.name;
|
||||
|
||||
@@ -49,7 +49,6 @@ class TestGetAdvertisement:
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["id"] == sample_advertisement.id
|
||||
assert data["public_key"] == sample_advertisement.public_key
|
||||
|
||||
def test_get_advertisement_not_found(self, client_no_auth):
|
||||
|
||||
@@ -51,7 +51,6 @@ class TestGetMessage:
|
||||
response = client_no_auth.get(f"/api/v1/messages/{sample_message.id}")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["id"] == sample_message.id
|
||||
assert data["text"] == sample_message.text
|
||||
|
||||
def test_get_message_not_found(self, client_no_auth):
|
||||
|
||||
@@ -45,7 +45,6 @@ class TestGetTelemetry:
|
||||
response = client_no_auth.get(f"/api/v1/telemetry/{sample_telemetry.id}")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["id"] == sample_telemetry.id
|
||||
assert data["node_public_key"] == sample_telemetry.node_public_key
|
||||
|
||||
def test_get_telemetry_not_found(self, client_no_auth):
|
||||
|
||||
@@ -31,7 +31,6 @@ class TestGetTracePath:
|
||||
response = client_no_auth.get(f"/api/v1/trace-paths/{sample_trace_path.id}")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["id"] == sample_trace_path.id
|
||||
assert data["path_hashes"] == sample_trace_path.path_hashes
|
||||
|
||||
def test_get_trace_path_not_found(self, client_no_auth):
|
||||
|
||||
Reference in New Issue
Block a user