From 941c355deb1c59091177b0d45bd0be4b4aa5da63 Mon Sep 17 00:00:00 2001 From: Lloyd Date: Mon, 11 May 2026 13:54:55 +0100 Subject: [PATCH] feat: add pagination support and count retrieval for adverts by contact type --- repeater/data_acquisition/sqlite_handler.py | 28 +++++++++++++++-- repeater/web/api_endpoints.py | 34 +++++++++++++++++++-- 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/repeater/data_acquisition/sqlite_handler.py b/repeater/data_acquisition/sqlite_handler.py index 415abd0..4c0a636 100644 --- a/repeater/data_acquisition/sqlite_handler.py +++ b/repeater/data_acquisition/sqlite_handler.py @@ -1557,12 +1557,14 @@ class SQLiteHandler: return {"rx_total": 0, "tx_total": 0, "drop_total": 0, "type_counts": {}} def get_adverts_by_contact_type( - self, contact_type: str, limit: Optional[int] = None, hours: Optional[int] = None + self, contact_type: str, limit: Optional[int] = None, offset: Optional[int] = None, hours: Optional[int] = None ) -> List[dict]: try: if limit is None: limit = 500 + if offset is None: + offset = 0 with self._connect() as conn: conn.row_factory = sqlite3.Row @@ -1584,8 +1586,9 @@ class SQLiteHandler: query += " ORDER BY timestamp DESC" if limit is not None: - query += " LIMIT ?" + query += " LIMIT ? OFFSET ?" params.append(limit) + params.append(offset) rows = conn.execute(query, params).fetchall() @@ -1617,6 +1620,27 @@ class SQLiteHandler: logger.error(f"Failed to get adverts by contact_type '{contact_type}': {e}") return [] + def get_adverts_count_by_contact_type( + self, contact_type: str, hours: Optional[int] = None + ) -> int: + """Get total count of adverts for a specific contact type.""" + try: + with self._connect() as conn: + query = "SELECT COUNT(*) as total FROM adverts WHERE contact_type = ?" + params = [contact_type] + + if hours is not None: + cutoff = time.time() - (hours * 3600) + query += " AND timestamp > ?" + params.append(cutoff) + + row = conn.execute(query, params).fetchone() + return row[0] if row else 0 + + except Exception as e: + logger.error(f"Failed to get adverts count for contact_type '{contact_type}': {e}") + return 0 + def generate_transport_key(self, name: str, key_length_bytes: int = 32) -> str: """ Generate a transport key using the proper MeshCore key derivation. diff --git a/repeater/web/api_endpoints.py b/repeater/web/api_endpoints.py index 974026c..ab40a5c 100644 --- a/repeater/web/api_endpoints.py +++ b/repeater/web/api_endpoints.py @@ -2267,25 +2267,26 @@ class APIEndpoints: @cherrypy.expose @cherrypy.tools.json_out() - def adverts_by_contact_type(self, contact_type=None, limit=None, hours=None): + def adverts_by_contact_type(self, contact_type=None, limit=None, offset=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 + offset_int = int(offset) if offset 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 + contact_type=contact_type, limit=limit_int, offset=offset_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}, + filters={"contact_type": contact_type, "limit": limit_int, "offset": offset_int, "hours": hours_int}, ) except ValueError as e: @@ -2294,6 +2295,33 @@ class APIEndpoints: logger.error(f"Error getting adverts by contact type: {e}") return self._error(e) + @cherrypy.expose + @cherrypy.tools.json_out() + def adverts_count_by_contact_type(self, contact_type=None, hours=None): + """Get the total count of adverts for a specific contact type.""" + try: + if not contact_type: + return self._error("contact_type parameter is required") + + hours_int = int(hours) if hours is not None else None + + storage = self._get_storage() + count = storage.sqlite_handler.get_adverts_count_by_contact_type( + contact_type=contact_type, hours=hours_int + ) + + return self._success( + {"count": count}, + contact_type=contact_type, + 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 count by contact type: {e}") + return self._error(e) + @cherrypy.expose @cherrypy.tools.json_out() def advert_rate_limit_stats(self):