diff --git a/client_manager.py b/client_manager.py index a717a48..d149884 100644 --- a/client_manager.py +++ b/client_manager.py @@ -4,7 +4,13 @@ from typing import Any, Callable, List from fastapi import WebSocket from loguru import logger -from .crud import create_event, get_event, get_events, mark_events_deleted +from .crud import ( + create_event, + delete_events, + get_event, + get_events, + mark_events_deleted, +) from .models import NostrEvent, NostrEventType, NostrFilter @@ -36,7 +42,7 @@ class NostrClientConnection: await self.websocket.accept() while True: json_data = await self.websocket.receive_text() - print('### received: ', json_data) + print("### received: ", json_data) try: data = json.loads(json_data) @@ -53,7 +59,6 @@ class NostrClientConnection: await self.websocket.send_text(json.dumps(resp)) return True return False - async def __handle_message(self, data: List) -> List: if len(data) < 2: @@ -76,10 +81,12 @@ class NostrClientConnection: resp_nip20: List[Any] = ["OK", e.id] try: e.check_signature() + if e.is_meta_event(): + await delete_events("111", NostrFilter(kinds=[0], authors=[e.pubkey])) await create_event("111", e) await self.broadcast_event(self, e) if e.is_delete_event(): - await self.__delete_event(e) + await self.__handle_delete_event(e) resp_nip20 += [True, ""] except ValueError: resp_nip20 += [False, "invalid: wrong event `id` or `sig`"] @@ -90,7 +97,7 @@ class NostrClientConnection: await self.websocket.send_text(json.dumps(resp_nip20)) - async def __delete_event(self, event: NostrEvent): + async def __handle_delete_event(self, event: NostrEvent): # NIP 09 filter = NostrFilter(authors=[event.pubkey]) filter.ids = [t[1] for t in event.tags if t[0] == "e"] diff --git a/crud.py b/crud.py index 2711aac..ed5c619 100644 --- a/crud.py +++ b/crud.py @@ -60,6 +60,14 @@ async def mark_events_deleted(relay_id: str, filter: NostrFilter): await db.execute(f"""UPDATE nostrrelay.events SET deleted=true WHERE {" AND ".join(where)}""", tuple(values)) +async def delete_events(relay_id: str, filter: NostrFilter): + if filter.is_empty(): + return None + _, where, values = build_where_clause(relay_id, filter) + + query = f"""DELETE from nostrrelay.events WHERE {" AND ".join(where)}""" + await db.execute(query, tuple(values)) + async def create_event_tags( relay_id: str, event_id: str, tag_name: str, tag_value: str, extra_values: Optional[str] @@ -109,7 +117,7 @@ def build_select_events_query(relay_id:str, filter:NostrFilter): ORDER BY created_at DESC """ - # todo: check range + # todo: check & enforce range if filter.limit and filter.limit > 0: query += f" LIMIT {filter.limit}" diff --git a/models.py b/models.py index 9c16c03..b2c2ac3 100644 --- a/models.py +++ b/models.py @@ -59,6 +59,9 @@ class NostrEvent(BaseModel): id = hashlib.sha256(data.encode()).hexdigest() return id + def is_meta_event(self) -> bool: + return self.kind == 0 + def is_delete_event(self) -> bool: return self.kind == 5 diff --git a/tests/fixture/clients.json b/tests/fixture/clients.json index 7b9f1df..7fa2f95 100644 --- a/tests/fixture/clients.json +++ b/tests/fixture/clients.json @@ -18,6 +18,24 @@ true, "" ], + "meta_update": [ + "EVENT", + { + "id": "2928f73760ac3a60affdf51d04169680472a8594b4584f087f497dcf6a28d12a", + "pubkey": "0b29ecc73ba400e5b4bd1e4cb0d8f524e9958345749197ca21c8da38d0622816", + "created_at": 1675673494, + "kind": 0, + "tags": [], + "content": "{\"name\":\"Alice\",\"about\":\"Uses Hamstr\"}", + "sig": "938313418d6d8b16b43213b3347c64925cbc1846e4447b4d878be9b865fe4b78f276ac399ea6b0aa81ed88fb18c992f2fae9e4f70c35c49202e576c54a0dc89c" + } + ], + "meta_update_response": [ + "OK", + "2928f73760ac3a60affdf51d04169680472a8594b4584f087f497dcf6a28d12a", + true, + "" + ], "post01": [ "EVENT", { diff --git a/tests/test_clients.py b/tests/test_clients.py index 3af109c..194a006 100644 --- a/tests/test_clients.py +++ b/tests/test_clients.py @@ -1,7 +1,8 @@ import asyncio -from json import dumps, loads - import pytest + +from json import dumps, loads +from copy import deepcopy from fastapi import WebSocket from lnbits.extensions.nostrrelay.client_manager import ( @@ -80,10 +81,11 @@ async def alice_wires_meta_and_post01(ws_alice: MockWebSocket): await ws_alice.wire_mock_data(alice["meta"]) await ws_alice.wire_mock_data(alice["post01"]) await ws_alice.wire_mock_data(alice["post01"]) + await ws_alice.wire_mock_data(alice["meta_update"]) await asyncio.sleep(0.5) assert ( - len(ws_alice.sent_messages) == 3 + len(ws_alice.sent_messages) == 4 ), "Alice: Expected 3 confirmations to be sent" assert ws_alice.sent_messages[0] == dumps( alice["meta_response"] @@ -94,6 +96,9 @@ async def alice_wires_meta_and_post01(ws_alice: MockWebSocket): assert ws_alice.sent_messages[2] == dumps( alice["post01_response_duplicate"] ), "Alice: Expected failure for double posting" + assert ws_alice.sent_messages[3] == dumps( + alice["meta_update_response"] + ), "Alice: Expected confirmation for meta update" await asyncio.sleep(0.1) @@ -112,8 +117,8 @@ async def bob_wires_meta_and_folows_alice(ws_bob: MockWebSocket): bob["meta_response"] ), "Bob: Wrong confirmation for meta" assert ws_bob.sent_messages[1] == dumps( - ["EVENT", "profile", alice["meta"][1]] - ), "Bob: Wrong response for Alice's meta" + ["EVENT", "profile", alice["meta_update"][1]] + ), "Bob: Wrong response for Alice's meta (updated version)" assert ws_bob.sent_messages[2] == dumps( ["EOSE", "profile"] ), "Bob: Wrong End Of Streaming Event for profile" @@ -266,7 +271,10 @@ async def alice_writes_to_bob(ws_alice: MockWebSocket, ws_bob: MockWebSocket): ["EOSE", "notifications:d685447c43c7c18dbbea61923cf0b63e1ab46bed"] ), "Bob: Received all stored events" -async def alice_deletes_post01__bob_is_notified(ws_alice: MockWebSocket, ws_bob:MockWebSocket): + +async def alice_deletes_post01__bob_is_notified( + ws_alice: MockWebSocket, ws_bob: MockWebSocket +): ws_bob.sent_messages.clear() await ws_bob.wire_mock_data(bob["request_posts_alice"]) await asyncio.sleep(0.1) @@ -305,6 +313,3 @@ async def alice_deletes_post01__bob_is_notified(ws_alice: MockWebSocket, ws_bob: assert ( len(ws_bob.sent_messages) == 2 ), "Bob: Expected one posts from Alice plus and EOSE" - - -