diff --git a/repeater/data_acquisition/storage_collector.py b/repeater/data_acquisition/storage_collector.py index 4f8bb7b..fbce9a3 100644 --- a/repeater/data_acquisition/storage_collector.py +++ b/repeater/data_acquisition/storage_collector.py @@ -186,6 +186,27 @@ class StorageCollector: def get_neighbors(self) -> dict: return self.sqlite_handler.get_neighbors() + + def get_node_name_by_pubkey(self, pubkey: str) -> Optional[str]: + """ + Lookup node name from adverts table by public key. + + Args: + pubkey: Public key in hex string format + + Returns: + Node name if found, None otherwise + """ + try: + with self.sqlite_handler.get_connection() as conn: + result = conn.execute( + "SELECT node_name FROM adverts WHERE pubkey = ? AND node_name IS NOT NULL ORDER BY last_seen DESC LIMIT 1", + (pubkey,) + ).fetchone() + return result[0] if result else None + except Exception as e: + logger.debug(f"Could not lookup node name for {pubkey[:8] if pubkey else 'None'}: {e}") + return None def cleanup_old_data(self, days: int = 7): self.sqlite_handler.cleanup_old_data(days) diff --git a/repeater/handler_helpers/room_server.py b/repeater/handler_helpers/room_server.py index bf6b80f..6f6ecdf 100644 --- a/repeater/handler_helpers/room_server.py +++ b/repeater/handler_helpers/room_server.py @@ -207,12 +207,24 @@ class RoomServer: f"{client_pubkey[:4].hex()}: {message_text[:50]}" ) - # Update client activity timestamp (they're clearly active if posting) - # This prevents them from being evicted while they're actively posting + # Log authenticated clients count for debugging distribution + all_clients = self.acl.get_all_clients() + logger.info( + f"Room '{self.room_name}': Message stored, will distribute to " + f"{len(all_clients)} authenticated client(s)" + ) + + # Update client's sync_since to this message's timestamp + # This prevents the author from receiving their own message back + # Also update activity timestamp (they're clearly active if posting) + logger.debug( + f"Room '{self.room_name}': Updating author's sync_since to {post_timestamp} " + f"to prevent echo" + ) self.db.upsert_client_sync( room_hash=f"0x{self.room_hash:02X}", client_pubkey=client_pubkey.hex(), - sync_since=0, # Will be preserved if already set + sync_since=post_timestamp, # Don't send this message back to author last_activity=time.time() ) @@ -488,9 +500,15 @@ class RoomServer: # Get all clients for this room all_clients = self.acl.get_all_clients() if not all_clients: + logger.debug(f"Room '{self.room_name}': No authenticated clients found") self.next_push_time = time.time() + 1.0 # Check again in 1 second continue + logger.debug( + f"Room '{self.room_name}': Found {len(all_clients)} authenticated client(s), " + f"checking for unsynced messages" + ) + # SAFETY: Limit number of clients if len(all_clients) > MAX_CLIENTS_PER_ROOM: logger.warning( @@ -555,6 +573,12 @@ class RoomServer: last_activity=time.time() ) + # Log the sync check for debugging + logger.debug( + f"Room '{self.room_name}': Checking client 0x{client.id.get_public_key()[0]:02X} " + f"for messages newer than sync_since={sync_since:.1f}" + ) + # Find next unsynced message for this client unsynced = self.db.get_unsynced_messages( room_hash=f"0x{self.room_hash:02X}", @@ -565,6 +589,10 @@ class RoomServer: if unsynced: post = unsynced[0] + logger.debug( + f"Room '{self.room_name}': Client 0x{client.id.get_public_key()[0]:02X} " + f"has unsynced message #{post['id']}, post_timestamp={post['post_timestamp']:.1f}" + ) # Check if enough time has passed since post creation now = time.time() if now >= post['post_timestamp'] + POST_SYNC_DELAY_SECS: diff --git a/repeater/web/api_endpoints.py b/repeater/web/api_endpoints.py index 955304e..5a816d5 100644 --- a/repeater/web/api_endpoints.py +++ b/repeater/web/api_endpoints.py @@ -2069,6 +2069,12 @@ class APIEndpoints: } } """ + # Enable CORS for this endpoint only if configured + self._set_cors_headers() + + if cherrypy.request.method == "OPTIONS": + return "" + try: room_info = self._get_room_server_by_name_or_hash(room_name, room_hash) room_server = room_info['room_server'] @@ -2094,7 +2100,8 @@ class APIEndpoints: offset=int(offset) ) - # Format messages with author prefix + # Format messages with author prefix and lookup sender names + storage = self._get_storage() formatted_messages = [] for msg in messages: author_pubkey = msg['author_pubkey'] @@ -2108,6 +2115,13 @@ class APIEndpoints: 'txt_type': msg['txt_type'], 'created_at': msg.get('created_at', msg['post_timestamp']) } + + # Lookup sender name from adverts table + if author_pubkey: + author_name = storage.get_node_name_by_pubkey(author_pubkey) + if author_name: + formatted_msg['author_name'] = author_name + formatted_messages.append(formatted_msg) return self._success({ @@ -2146,6 +2160,12 @@ class APIEndpoints: Returns: {"success": true, "data": {"message_id": 123}} """ + # Enable CORS for this endpoint only if configured + self._set_cors_headers() + + if cherrypy.request.method == "OPTIONS": + return "" + try: self._require_post() @@ -2268,6 +2288,12 @@ class APIEndpoints: } } """ + # Enable CORS for this endpoint only if configured + self._set_cors_headers() + + if cherrypy.request.method == "OPTIONS": + return "" + try: if not self.daemon_instance or not hasattr(self.daemon_instance, 'text_helper'): return self._error("Text helper not available") @@ -2399,6 +2425,12 @@ class APIEndpoints: } } """ + # Enable CORS for this endpoint only if configured + self._set_cors_headers() + + if cherrypy.request.method == "OPTIONS": + return "" + try: # Reuse room_stats logic but return only clients stats = self.room_stats(room_name=room_name, room_hash=room_hash) @@ -2431,6 +2463,12 @@ class APIEndpoints: Returns: {"success": true} """ + # Enable CORS for this endpoint only if configured + self._set_cors_headers() + + if cherrypy.request.method == "OPTIONS": + return "" + try: if cherrypy.request.method != "DELETE": cherrypy.response.status = 405 @@ -2473,6 +2511,12 @@ class APIEndpoints: Returns: {"success": true, "data": {"deleted_count": 123}} """ + # Enable CORS for this endpoint only if configured + self._set_cors_headers() + + if cherrypy.request.method == "OPTIONS": + return "" + try: if cherrypy.request.method != "DELETE": cherrypy.response.status = 405