feat: add packet retrieval by ID endpoint and corresponding database methods

This commit is contained in:
Rightup
2026-07-02 16:18:54 +01:00
parent 823308aa3f
commit 34747d2610
5 changed files with 137 additions and 6 deletions
+31 -1
View File
@@ -671,7 +671,7 @@ class SQLiteHandler:
except Exception:
fwd_path_val = str(fwd_path)
conn.execute(
cursor = conn.execute(
"""
INSERT INTO packets (
timestamp, type, route, length, rssi, snr, score,
@@ -714,6 +714,7 @@ class SQLiteHandler:
),
)
self._invalidate_hot_caches()
return cursor.lastrowid
except Exception as e:
logger.error(f"Failed to store packet in SQLite: {e}")
@@ -992,6 +993,7 @@ class SQLiteHandler:
packets = conn.execute(
"""
SELECT
id,
timestamp, type, route, length, rssi, snr, score,
transmitted, is_duplicate, drop_reason, src_hash, dst_hash, path_hash,
transport_codes, payload, payload_length,
@@ -1044,6 +1046,7 @@ class SQLiteHandler:
base_query = """
SELECT
id,
timestamp, type, route, length, rssi, snr, score,
transmitted, is_duplicate, drop_reason, src_hash, dst_hash, path_hash,
transport_codes, payload, payload_length,
@@ -1176,6 +1179,7 @@ class SQLiteHandler:
packet = conn.execute(
"""
SELECT
id,
timestamp, type, route, length, rssi, snr, score,
transmitted, is_duplicate, drop_reason, src_hash, dst_hash, path_hash,
header, transport_codes, payload, payload_length,
@@ -1193,6 +1197,32 @@ class SQLiteHandler:
logger.error(f"Failed to get packet by hash: {e}")
return None
def get_packet_by_id(self, packet_id: int) -> Optional[dict]:
try:
with self._connect() as conn:
conn.row_factory = sqlite3.Row
packet = conn.execute(
"""
SELECT
id,
timestamp, type, route, length, rssi, snr, score,
transmitted, is_duplicate, drop_reason, src_hash, dst_hash, path_hash,
header, transport_codes, payload, payload_length,
tx_delay_ms, packet_hash, original_path, forwarded_path, raw_packet,
lbt_attempts, lbt_backoff_delays_ms, lbt_channel_busy
FROM packets
WHERE id = ?
""",
(packet_id,),
).fetchone()
return dict(packet) if packet else None
except Exception as e:
logger.error(f"Failed to get packet by id: {e}")
return None
def get_packet_type_stats(self, hours: int = 24) -> dict:
try:
now = time.time()
@@ -45,9 +45,7 @@ class StorageCollector:
letsmesh_config = config.get("letsmesh", {}) or {}
mqtt_config = config.get("mqtt", {}) or {}
has_brokers_configured = (
bool(mqtt_brokers_config.get("brokers"))
or bool(letsmesh_config)
or bool(mqtt_config)
bool(mqtt_brokers_config.get("brokers")) or bool(letsmesh_config) or bool(mqtt_config)
)
if has_brokers_configured and local_identity:
try:
@@ -217,7 +215,9 @@ class StorageCollector:
def _record_packet_blocking(self, packet_record: dict, skip_mqtt: bool):
"""Store, aggregate, update metrics, and publish one packet (writer thread)."""
self.sqlite_handler.store_packet(packet_record)
packet_id = self.sqlite_handler.store_packet(packet_record)
if packet_id is not None:
packet_record["id"] = packet_id
cumulative_counts = self.sqlite_handler.get_cumulative_counts()
self.rrd_handler.update_packet_metrics(packet_record, cumulative_counts)
self._publish_packet_sync(packet_record, skip_mqtt)
@@ -424,6 +424,9 @@ class StorageCollector:
def get_packet_by_hash(self, packet_hash: str) -> Optional[dict]:
return self.sqlite_handler.get_packet_by_hash(packet_hash)
def get_packet_by_id(self, packet_id: int) -> Optional[dict]:
return self.sqlite_handler.get_packet_by_id(packet_id)
def get_rrd_data(
self,
start_time: Optional[int] = None,
+12
View File
@@ -2962,6 +2962,18 @@ class APIEndpoints:
logger.error(f"Error getting packet by hash: {e}")
return self._error(e)
@cherrypy.expose
@cherrypy.tools.json_out()
def packet_by_id(self, packet_id=None):
try:
if packet_id is None:
return self._error("packet_id parameter required")
packet = self._get_storage().get_packet_by_id(int(packet_id))
return self._success(packet) if packet else self._error("Packet not found")
except Exception as e:
logger.error(f"Error getting packet by id: {e}")
return self._error(e)
@cherrypy.expose
@cherrypy.tools.json_out()
def rrd_data(self):
+15
View File
@@ -862,6 +862,21 @@ paths:
'200':
description: Packet details
/packet_by_id:
get:
tags: [Packets]
summary: Get packet by ID
parameters:
- name: packet_id
in: query
required: true
schema:
type: integer
description: Packet database ID to lookup
responses:
'200':
description: Packet details
/packet_type_stats:
get:
tags: [Packets]
+72 -1
View File
@@ -1,7 +1,7 @@
import base64
from pathlib import Path
import sys
import types
from pathlib import Path
import pytest
@@ -170,6 +170,77 @@ def test_store_and_delete_advert(tmp_path):
assert h.delete_advert(advert_id) is False
def test_store_packet_returns_inserted_row_id(tmp_path):
h = _make_handler(tmp_path)
packet_id = h.store_packet(
{
"timestamp": 123.0,
"type": 1,
"route": 2,
"length": 3,
"transmitted": True,
"packet_hash": "pkt-1",
}
)
assert isinstance(packet_id, int)
assert packet_id > 0
with h._connect() as conn:
row = conn.execute(
"SELECT id, type, route, length FROM packets WHERE id = ?",
(packet_id,),
).fetchone()
assert row is not None
assert row[0] == packet_id
assert row[1] == 1
assert row[2] == 2
assert row[3] == 3
def test_recent_packet_queries_include_ids_and_preserve_duplicate_hash_rows(tmp_path):
h = _make_handler(tmp_path)
first_id = h.store_packet(
{
"timestamp": 100.0,
"type": 1,
"route": 1,
"length": 8,
"transmitted": False,
"is_duplicate": False,
"packet_hash": "same-hash",
}
)
second_id = h.store_packet(
{
"timestamp": 101.0,
"type": 1,
"route": 1,
"length": 8,
"transmitted": True,
"is_duplicate": True,
"packet_hash": "same-hash",
}
)
recent = h.get_recent_packets(limit=10)
assert len(recent) == 2
assert {packet["id"] for packet in recent} == {first_id, second_id}
assert [packet["packet_hash"] for packet in recent] == ["same-hash", "same-hash"]
filtered = h.get_filtered_packets(limit=10)
assert len(filtered) == 2
assert {packet["id"] for packet in filtered} == {first_id, second_id}
by_id = h.get_packet_by_id(int(second_id))
assert by_id is not None
assert by_id["id"] == second_id
assert by_id["packet_hash"] == "same-hash"
def test_verify_api_token_last_used_throttle(tmp_path, monkeypatch):
h = _make_handler(tmp_path)
h._api_token_last_used_interval_sec = 300