mirror of
https://github.com/pyMC-dev/pyMC_Repeater.git
synced 2026-08-06 17:03:32 +02:00
feat: add packet retrieval by ID endpoint and corresponding database methods
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user