From d1963fc70f633c4da7067e25134b5f5cfa02d1fa Mon Sep 17 00:00:00 2001 From: Lloyd Date: Mon, 10 Nov 2025 20:37:39 +0000 Subject: [PATCH] feat: add endpoint to retrieve adverts by contact type with optional filters --- repeater/data_acquisition/sqlite_handler.py | 58 ++++++++++++++++++++- repeater/web/api_endpoints.py | 40 ++++++++++++-- 2 files changed, 94 insertions(+), 4 deletions(-) diff --git a/repeater/data_acquisition/sqlite_handler.py b/repeater/data_acquisition/sqlite_handler.py index 6cba314..9c66abd 100644 --- a/repeater/data_acquisition/sqlite_handler.py +++ b/repeater/data_acquisition/sqlite_handler.py @@ -544,4 +544,60 @@ class SQLiteHandler: "tx_total": 0, "drop_total": 0, "type_counts": {} - } \ No newline at end of file + } + + def get_adverts_by_contact_type(self, contact_type: str, limit: Optional[int] = None, hours: Optional[int] = None) -> List[dict]: + + try: + with sqlite3.connect(self.sqlite_path) as conn: + conn.row_factory = sqlite3.Row + + query = """ + SELECT id, timestamp, pubkey, node_name, is_repeater, route_type, + contact_type, latitude, longitude, first_seen, last_seen, + rssi, snr, advert_count, is_new_neighbor + FROM adverts + WHERE contact_type = ? + """ + params = [contact_type] + + if hours is not None: + cutoff = time.time() - (hours * 3600) + query += " AND timestamp > ?" + params.append(cutoff) + + query += " ORDER BY timestamp DESC" + + if limit is not None: + query += " LIMIT ?" + params.append(limit) + + rows = conn.execute(query, params).fetchall() + + adverts = [] + for row in rows: + advert = { + "id": row["id"], + "timestamp": row["timestamp"], + "pubkey": row["pubkey"], + "node_name": row["node_name"], + "is_repeater": bool(row["is_repeater"]), + "route_type": row["route_type"], + "contact_type": row["contact_type"], + "latitude": row["latitude"], + "longitude": row["longitude"], + "first_seen": row["first_seen"], + "last_seen": row["last_seen"], + "rssi": row["rssi"], + "snr": row["snr"], + "advert_count": row["advert_count"], + "is_new_neighbor": bool(row["is_new_neighbor"]) + } + adverts.append(advert) + + logger.debug(f"Found {len(adverts)} adverts with contact_type '{contact_type}'") + return adverts + + except Exception as e: + logger.error(f"Failed to get adverts by contact_type '{contact_type}': {e}") + return [] \ No newline at end of file diff --git a/repeater/web/api_endpoints.py b/repeater/web/api_endpoints.py index 07705f5..2975b06 100644 --- a/repeater/web/api_endpoints.py +++ b/repeater/web/api_endpoints.py @@ -335,8 +335,8 @@ class APIEndpoints: # Use SQLite directly for packet type graph data since RRD data is too sparse storage = self._get_storage() - # Get packet type stats from SQLite - stats = storage._get_packet_type_stats_sqlite(params['hours']) + # Get packet type stats directly from SQLite handler to avoid RRD formatting issues + stats = storage.sqlite_handler.get_packet_type_stats(params['hours']) if 'error' in stats: return self._error(stats['error']) @@ -621,4 +621,38 @@ class APIEndpoints: return generate() - cad_calibration_stream._cp_config = {'response.stream': True} \ No newline at end of file + cad_calibration_stream._cp_config = {'response.stream': True} + + @cherrypy.expose + @cherrypy.tools.json_out() + @cors_enabled + def adverts_by_contact_type(self, contact_type=None, limit=None, hours=None): + + try: + if not contact_type: + return self._error("contact_type parameter is required") + + limit_int = int(limit) if limit is not None else None + hours_int = int(hours) if hours is not None else None + + storage = self._get_storage() + adverts = storage.sqlite_handler.get_adverts_by_contact_type( + contact_type=contact_type, + limit=limit_int, + hours=hours_int + ) + + return self._success(adverts, + count=len(adverts), + contact_type=contact_type, + filters={ + "contact_type": contact_type, + "limit": limit_int, + "hours": hours_int + }) + + except ValueError as e: + return self._error(f"Invalid parameter format: {e}") + except Exception as e: + logger.error(f"Error getting adverts by contact type: {e}") + return self._error(e) \ No newline at end of file