feat: add transport key management methods and API endpoints

This commit is contained in:
Lloyd
2025-11-16 20:56:29 +00:00
parent 2acd44bcf8
commit da4006d230
3 changed files with 244 additions and 3 deletions

View File

@@ -73,6 +73,20 @@ class SQLiteHandler:
)
""")
conn.execute("""
CREATE TABLE IF NOT EXISTS transport_keys (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
flood_policy TEXT NOT NULL CHECK (flood_policy IN ('allow', 'deny')),
transport_key TEXT NOT NULL,
last_used REAL,
parent_id INTEGER,
created_at REAL NOT NULL,
updated_at REAL NOT NULL,
FOREIGN KEY (parent_id) REFERENCES transport_keys(id)
)
""")
conn.execute("CREATE INDEX IF NOT EXISTS idx_packets_timestamp ON packets(timestamp)")
conn.execute("CREATE INDEX IF NOT EXISTS idx_packets_type ON packets(type)")
conn.execute("CREATE INDEX IF NOT EXISTS idx_packets_hash ON packets(packet_hash)")
@@ -80,6 +94,8 @@ class SQLiteHandler:
conn.execute("CREATE INDEX IF NOT EXISTS idx_adverts_timestamp ON adverts(timestamp)")
conn.execute("CREATE INDEX IF NOT EXISTS idx_adverts_pubkey ON adverts(pubkey)")
conn.execute("CREATE INDEX IF NOT EXISTS idx_noise_timestamp ON noise_floor(timestamp)")
conn.execute("CREATE INDEX IF NOT EXISTS idx_transport_keys_name ON transport_keys(name)")
conn.execute("CREATE INDEX IF NOT EXISTS idx_transport_keys_parent ON transport_keys(parent_id)")
conn.commit()
logger.info(f"SQLite database initialized: {self.sqlite_path}")
@@ -646,4 +662,113 @@ class SQLiteHandler:
except Exception as e:
logger.error(f"Failed to get adverts by contact_type '{contact_type}': {e}")
return []
return []
def create_transport_key(self, name: str, flood_policy: str, transport_key: str, parent_id: Optional[int] = None, last_used: Optional[float] = None) -> Optional[int]:
try:
current_time = time.time()
with sqlite3.connect(self.sqlite_path) as conn:
cursor = conn.execute("""
INSERT INTO transport_keys (name, flood_policy, transport_key, parent_id, last_used, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?)
""", (name, flood_policy, transport_key, parent_id, last_used, current_time, current_time))
return cursor.lastrowid
except Exception as e:
logger.error(f"Failed to create transport key: {e}")
return None
def get_transport_keys(self) -> List[dict]:
try:
with sqlite3.connect(self.sqlite_path) as conn:
conn.row_factory = sqlite3.Row
rows = conn.execute("""
SELECT id, name, flood_policy, transport_key, parent_id, last_used, created_at, updated_at
FROM transport_keys
ORDER BY created_at ASC
""").fetchall()
return [{
"id": row["id"],
"name": row["name"],
"flood_policy": row["flood_policy"],
"transport_key": row["transport_key"],
"parent_id": row["parent_id"],
"last_used": row["last_used"],
"created_at": row["created_at"],
"updated_at": row["updated_at"]
} for row in rows]
except Exception as e:
logger.error(f"Failed to get transport keys: {e}")
return []
def get_transport_key_by_id(self, key_id: int) -> Optional[dict]:
try:
with sqlite3.connect(self.sqlite_path) as conn:
conn.row_factory = sqlite3.Row
row = conn.execute("""
SELECT id, name, flood_policy, transport_key, parent_id, last_used, created_at, updated_at
FROM transport_keys WHERE id = ?
""", (key_id,)).fetchone()
if row:
return {
"id": row["id"],
"name": row["name"],
"flood_policy": row["flood_policy"],
"transport_key": row["transport_key"],
"parent_id": row["parent_id"],
"last_used": row["last_used"],
"created_at": row["created_at"],
"updated_at": row["updated_at"]
}
return None
except Exception as e:
logger.error(f"Failed to get transport key by id: {e}")
return None
def update_transport_key(self, key_id: int, name: Optional[str] = None, flood_policy: Optional[str] = None, transport_key: Optional[str] = None, parent_id: Optional[int] = None, last_used: Optional[float] = None) -> bool:
try:
updates = []
params = []
if name is not None:
updates.append("name = ?")
params.append(name)
if flood_policy is not None:
updates.append("flood_policy = ?")
params.append(flood_policy)
if transport_key is not None:
updates.append("transport_key = ?")
params.append(transport_key)
if parent_id is not None:
updates.append("parent_id = ?")
params.append(parent_id)
if last_used is not None:
updates.append("last_used = ?")
params.append(last_used)
if not updates:
return False
updates.append("updated_at = ?")
params.append(time.time())
params.append(key_id)
with sqlite3.connect(self.sqlite_path) as conn:
cursor = conn.execute(f"""
UPDATE transport_keys SET {', '.join(updates)}
WHERE id = ?
""", params)
return cursor.rowcount > 0
except Exception as e:
logger.error(f"Failed to update transport key: {e}")
return False
def delete_transport_key(self, key_id: int) -> bool:
try:
with sqlite3.connect(self.sqlite_path) as conn:
cursor = conn.execute("DELETE FROM transport_keys WHERE id = ?", (key_id,))
return cursor.rowcount > 0
except Exception as e:
logger.error(f"Failed to delete transport key: {e}")
return False

View File

@@ -91,4 +91,19 @@ class StorageCollector:
return self.sqlite_handler.get_noise_floor_stats(hours)
def close(self):
self.mqtt_handler.close()
self.mqtt_handler.close()
def create_transport_key(self, name: str, flood_policy: str, transport_key: str, parent_id: Optional[int] = None, last_used: Optional[float] = None) -> Optional[int]:
return self.sqlite_handler.create_transport_key(name, flood_policy, transport_key, parent_id, last_used)
def get_transport_keys(self) -> list:
return self.sqlite_handler.get_transport_keys()
def get_transport_key_by_id(self, key_id: int) -> Optional[dict]:
return self.sqlite_handler.get_transport_key_by_id(key_id)
def update_transport_key(self, key_id: int, name: Optional[str] = None, flood_policy: Optional[str] = None, transport_key: Optional[str] = None, parent_id: Optional[int] = None, last_used: Optional[float] = None) -> bool:
return self.sqlite_handler.update_transport_key(key_id, name, flood_policy, transport_key, parent_id, last_used)
def delete_transport_key(self, key_id: int) -> bool:
return self.sqlite_handler.delete_transport_key(key_id)

View File

@@ -680,4 +680,105 @@ class APIEndpoints:
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)
return self._error(e)
@cherrypy.expose
@cherrypy.tools.json_out()
@cors_enabled
def transport_keys(self):
if cherrypy.request.method == "GET":
try:
storage = self._get_storage()
keys = storage.get_transport_keys()
return self._success(keys, count=len(keys))
except Exception as e:
logger.error(f"Error getting transport keys: {e}")
return self._error(e)
elif cherrypy.request.method == "POST":
try:
data = cherrypy.request.json or {}
name = data.get("name")
flood_policy = data.get("flood_policy")
transport_key = data.get("transport_key")
parent_id = data.get("parent_id")
last_used = data.get("last_used")
if not name or not flood_policy or not transport_key:
return self._error("Missing required fields: name, flood_policy, transport_key")
if flood_policy not in ["allow", "deny"]:
return self._error("flood_policy must be 'allow' or 'deny'")
storage = self._get_storage()
key_id = storage.create_transport_key(name, flood_policy, transport_key, parent_id, last_used)
if key_id:
return self._success({"id": key_id}, message="Transport key created successfully")
else:
return self._error("Failed to create transport key")
except Exception as e:
logger.error(f"Error creating transport key: {e}")
return self._error(e)
@cherrypy.expose
@cherrypy.tools.json_out()
@cors_enabled
def transport_key(self, key_id):
if cherrypy.request.method == "GET":
try:
key_id = int(key_id)
storage = self._get_storage()
key = storage.get_transport_key_by_id(key_id)
if key:
return self._success(key)
else:
return self._error("Transport key not found")
except ValueError:
return self._error("Invalid key_id format")
except Exception as e:
logger.error(f"Error getting transport key: {e}")
return self._error(e)
elif cherrypy.request.method == "PUT":
try:
key_id = int(key_id)
data = cherrypy.request.json or {}
name = data.get("name")
flood_policy = data.get("flood_policy")
transport_key = data.get("transport_key")
parent_id = data.get("parent_id")
last_used = data.get("last_used")
if flood_policy and flood_policy not in ["allow", "deny"]:
return self._error("flood_policy must be 'allow' or 'deny'")
storage = self._get_storage()
success = storage.update_transport_key(key_id, name, flood_policy, transport_key, parent_id, last_used)
if success:
return self._success({"id": key_id}, message="Transport key updated successfully")
else:
return self._error("Failed to update transport key or key not found")
except ValueError:
return self._error("Invalid key_id format")
except Exception as e:
logger.error(f"Error updating transport key: {e}")
return self._error(e)
elif cherrypy.request.method == "DELETE":
try:
key_id = int(key_id)
storage = self._get_storage()
success = storage.delete_transport_key(key_id)
if success:
return self._success({"id": key_id}, message="Transport key deleted successfully")
else:
return self._error("Failed to delete transport key or key not found")
except ValueError:
return self._error("Invalid key_id format")
except Exception as e:
logger.error(f"Error deleting transport key: {e}")
return self._error(e)