diff --git a/nostr/nostr_client.py b/nostr/nostr_client.py index 175a1c6..ff02c93 100644 --- a/nostr/nostr_client.py +++ b/nostr/nostr_client.py @@ -77,45 +77,56 @@ class NostrClient: async def publish_nostr_event(self, e: NostrEvent): await self.send_req_queue.put(["EVENT", e.dict()]) - async def subscribe_to_direct_messages(self, public_key: str, since: int): + async def subscribe_merchant(self, public_key: str, since = 0): + dm_filters = self._filters_for_direct_messages(public_key, since) + stall_filters = self._filters_for_stall_events(public_key, since) + product_filters = self._filters_for_product_events(public_key, since) + profile_filters = self._filters_for_user_profile(public_key, since) + + # merchant_filters = [json.dumps(f) for f in dm_filters + stall_filters + product_filters + profile_filters] + merchant_filters = dm_filters + stall_filters + product_filters + profile_filters + + await self.send_req_queue.put( + ["REQ", f"merchant:{public_key}"] + merchant_filters + ) + + logger.debug(f"Subscribed to events for: '{public_key}'.") + # print("### merchant_filters", merchant_filters) + + + def _filters_for_direct_messages(self, public_key: str, since: int) -> List: in_messages_filter = {"kinds": [4], "#p": [public_key]} out_messages_filter = {"kinds": [4], "authors": [public_key]} if since and since != 0: in_messages_filter["since"] = since out_messages_filter["since"] = since - await self.send_req_queue.put( - ["REQ", f"direct-messages-in:{public_key}", in_messages_filter] - ) - await self.send_req_queue.put( - ["REQ", f"direct-messages-out:{public_key}", out_messages_filter] - ) + return [in_messages_filter, out_messages_filter] - logger.debug(f"Subscribed to direct-messages '{public_key}'.") - - async def subscribe_to_stall_events(self, public_key: str, since: int): + def _filters_for_stall_events(self, public_key: str, since: int) -> List: stall_filter = {"kinds": [30017], "authors": [public_key]} if since and since != 0: stall_filter["since"] = since - await self.send_req_queue.put( - ["REQ", f"stall-events:{public_key}", stall_filter] - ) + return [stall_filter] - logger.debug(f"Subscribed to stall-events: '{public_key}'.") - - async def subscribe_to_product_events(self, public_key: str, since: int): + def _filters_for_product_events(self, public_key: str, since: int) -> List: product_filter = {"kinds": [30018], "authors": [public_key]} if since and since != 0: product_filter["since"] = since - await self.send_req_queue.put( - ["REQ", f"product-events:{public_key}", product_filter] - ) - logger.debug(f"Subscribed to product-events: '{public_key}'.") + return [product_filter] - async def subscribe_to_user_profile(self, public_key: str, since: int): + def _filters_for_user_profile(self, public_key: str, since: int) -> List: + profile_filter = {"kinds": [0], "authors": [public_key]} + if since and since != 0: + profile_filter["since"] = since + 1 + + return [profile_filter] + + + def subscribe_to_user_profile(self, public_key: str, since: int) -> List: profile_filter = {"kinds": [0], "authors": [public_key]} if since and since != 0: profile_filter["since"] = since + 1 @@ -127,17 +138,6 @@ class NostrClient: # ["REQ", f"user-profile-events:{public_key}", profile_filter] # ) - async def unsubscribe_from_direct_messages(self, public_key: str): - await self.send_req_queue.put(["CLOSE", f"direct-messages-in:{public_key}"]) - await self.send_req_queue.put(["CLOSE", f"direct-messages-out:{public_key}"]) - - logger.debug(f"Unsubscribed from direct-messages '{public_key}'.") - - async def unsubscribe_from_merchant_events(self, public_key: str): - await self.send_req_queue.put(["CLOSE", f"stall-events:{public_key}"]) - await self.send_req_queue.put(["CLOSE", f"product-events:{public_key}"]) - - logger.debug(f"Unsubscribed from stall-events and product-events '{public_key}'.") async def restart(self, public_keys: List[str]): await self.unsubscribe_merchants(public_keys) @@ -160,11 +160,15 @@ class NostrClient: self.ws.close() self.ws = None + async def unsubscribe_merchant(self, public_key: str): + await self.send_req_queue.put(["CLOSE", public_key]) + + logger.debug(f"Unsubscribed from merchant events: '{public_key}'.") + async def unsubscribe_merchants(self, public_keys: List[str]): for pk in public_keys: try: - await self.unsubscribe_from_direct_messages(pk) - await self.unsubscribe_from_merchant_events(pk) + await self.unsubscribe_merchant(pk) except Exception as ex: logger.warning(ex) diff --git a/services.py b/services.py index de619ad..a8cc54a 100644 --- a/services.py +++ b/services.py @@ -16,6 +16,10 @@ from .crud import ( create_product, create_stall, get_customer, + get_last_direct_messages_created_at, + get_last_order_time, + get_last_product_update_time, + get_last_stall_update_time, get_merchant_by_pubkey, get_order, get_order_by_event_id, @@ -364,6 +368,12 @@ async def extract_customer_order_from_dm( return order +async def get_last_event_date_for_merchant(id) -> int: + last_order_time = await get_last_order_time(id) + last_dm_time = await get_last_direct_messages_created_at(id) + last_stall_update = await get_last_stall_update_time(id) + last_product_update = await get_last_product_update_time(id) + return max(last_order_time, last_dm_time, last_stall_update, last_product_update) async def _handle_nip04_message(merchant_public_key: str, event: NostrEvent): merchant = await get_merchant_by_pubkey(merchant_public_key) diff --git a/static/components/direct-messages/direct-messages.html b/static/components/direct-messages/direct-messages.html index fe66c12..e4d45af 100644 --- a/static/components/direct-messages/direct-messages.html +++ b/static/components/direct-messages/direct-messages.html @@ -57,7 +57,7 @@
- +
... diff --git a/static/components/direct-messages/direct-messages.js b/static/components/direct-messages/direct-messages.js index 06cdca7..607342a 100644 --- a/static/components/direct-messages/direct-messages.js +++ b/static/components/direct-messages/direct-messages.js @@ -140,8 +140,8 @@ async function directMessages(path) { } this.getCustomersDebounced() }, - showOrderDetails: function (orderId) { - this.$emit('order-selected', orderId) + showOrderDetails: function (orderId, eventId) { + this.$emit('order-selected', { orderId, eventId }) }, showClientOrders: function () { this.$emit('customer-selected', this.activePublicKey) diff --git a/static/components/order-list/order-list.js b/static/components/order-list/order-list.js index 10681b5..285a077 100644 --- a/static/components/order-list/order-list.js +++ b/static/components/order-list/order-list.js @@ -189,12 +189,33 @@ async function orderList(path) { LNbits.utils.notifyApiError(error) } }, + restoreOrder: async function (eventId) { + console.log('### restoreOrder', eventId) + try { + this.search.restoring = true + const {data} = await LNbits.api.request( + 'PUT', + `/nostrmarket/api/v1/order/restore/${eventId}`, + this.adminkey + ) + await this.getOrders() + this.$q.notify({ + type: 'positive', + message: 'Order restored!' + }) + return data + } catch (error) { + LNbits.utils.notifyApiError(error) + } finally { + this.search.restoring = false + } + }, restoreOrders: async function () { try { this.search.restoring = true await LNbits.api.request( 'PUT', - `/nostrmarket/api/v1/order/restore`, + `/nostrmarket/api/v1/orders/restore`, this.adminkey ) await this.getOrders() @@ -269,9 +290,25 @@ async function orderList(path) { } }, - orderSelected: async function (orderId) { + orderSelected: async function (orderId, eventId) { const order = await this.getOrder(orderId) - if (!order) return + if (!order) { + LNbits.utils + .confirmDialog( + "Order could not be found. Do you want to restore it from this direct message?" + ) + .onOk(async () => { + const restoredOrder = await this.restoreOrder(eventId) + console.log('### restoredOrder', restoredOrder) + if (restoredOrder) { + restoredOrder.expanded = true + restoredOrder.isNew = false + this.orders = [restoredOrder] + } + + }) + return + } order.expanded = true order.isNew = false this.orders = [order] diff --git a/static/js/index.js b/static/js/index.js index 6d2a6cb..72f1607 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -152,8 +152,8 @@ const merchant = async () => { filterOrdersForCustomer: function (customerPubkey) { this.orderPubkey = customerPubkey }, - showOrderDetails: async function (orderId) { - await this.$refs.orderListRef.orderSelected(orderId) + showOrderDetails: async function (orderData) { + await this.$refs.orderListRef.orderSelected(orderData.orderId, orderData.eventId) }, waitForNotifications: async function () { if (!this.merchant) return diff --git a/tasks.py b/tasks.py index 12513b4..e02f781 100644 --- a/tasks.py +++ b/tasks.py @@ -1,4 +1,5 @@ from asyncio import Queue +import asyncio from lnbits.core.models import Payment from lnbits.tasks import register_invoice_listener @@ -12,7 +13,7 @@ from .crud import ( get_merchants_ids_with_pubkeys, ) from .nostr.nostr_client import NostrClient -from .services import handle_order_paid, process_nostr_message +from .services import get_last_event_date_for_merchant, handle_order_paid, process_nostr_message async def wait_for_paid_invoices(): @@ -39,23 +40,13 @@ async def on_invoice_paid(payment: Payment) -> None: async def wait_for_nostr_events(nostr_client: NostrClient): merchant_ids = await get_merchants_ids_with_pubkeys() for id, pk in merchant_ids: - last_order_time = await get_last_order_time(id) - last_dm_time = await get_last_direct_messages_created_at(id) - since = max(last_order_time, last_dm_time) + since = await get_last_event_date_for_merchant(id) + await nostr_client.subscribe_merchant(pk, since + 1) + await asyncio.sleep(0.1) # try to avoid 'too many concurrent REQ' from relays - await nostr_client.subscribe_to_direct_messages(pk, since) - - for id, pk in merchant_ids: - last_stall_update = await get_last_stall_update_time(id) - await nostr_client.subscribe_to_stall_events(pk, last_stall_update) - - for id, pk in merchant_ids: - last_product_update = await get_last_product_update_time(id) - await nostr_client.subscribe_to_product_events(pk, last_product_update) - - customers = await get_all_unique_customers() - for c in customers: - await nostr_client.subscribe_to_user_profile(c.public_key, c.event_created_at) + # customers = await get_all_unique_customers() + # for c in customers: + # await nostr_client.subscribe_to_user_profile(c.public_key, c.event_created_at) while True: message = await nostr_client.get_event() diff --git a/views_api.py b/views_api.py index 26d295a..a1cde12 100644 --- a/views_api.py +++ b/views_api.py @@ -35,12 +35,14 @@ from .crud import ( delete_zone, get_customer, get_customers, + get_direct_message_by_event_id, get_direct_messages, get_last_direct_messages_time, get_merchant_by_pubkey, get_merchant_for_user, get_merchants_ids_with_pubkeys, get_order, + get_order_by_event_id, get_orders, get_orders_for_stall, get_orders_from_direct_messages, @@ -117,9 +119,7 @@ async def api_create_merchant( ), ) - await nostr_client.subscribe_to_stall_events(data.public_key, 0) - await nostr_client.subscribe_to_product_events(data.public_key, 0) - await nostr_client.subscribe_to_direct_messages(data.public_key, 0) + await nostr_client.subscribe_merchant(data.public_key, 0) return merchant except AssertionError as ex: @@ -170,14 +170,15 @@ async def api_delete_merchant( assert merchant, "Merchant cannot be found" assert merchant.id == merchant_id, "Wrong merchant ID" + # first unsubscribe so new events are not created during the clean-up + await nostr_client.unsubscribe_merchant(merchant.public_key) + await delete_merchant_orders(merchant.id) await delete_merchant_products(merchant.id) await delete_merchant_stalls(merchant.id) await delete_merchant_direct_messages(merchant.id) await delete_merchant_zones(merchant.id) - await nostr_client.unsubscribe_from_direct_messages(merchant.public_key) - await nostr_client.unsubscribe_from_merchant_events(merchant.public_key) await delete_merchant(merchant.id) except AssertionError as ex: raise HTTPException( @@ -833,10 +834,39 @@ async def api_update_order_status( ) -@nostrmarket_ext.put("/api/v1/order/restore") +@nostrmarket_ext.put("/api/v1/order/restore/{event_id}") async def api_restore_orders( + event_id: str, wallet: WalletTypeInfo = Depends(require_admin_key), ) -> Order: + try: + merchant = await get_merchant_for_user(wallet.wallet.user) + assert merchant, "Merchant cannot be found" + + dm = await get_direct_message_by_event_id(merchant.id, event_id) + assert dm, "Canot find direct message" + + await create_or_update_order_from_dm(merchant.id, merchant.public_key, dm) + + return await get_order_by_event_id(merchant.id, event_id) + + except AssertionError as ex: + raise HTTPException( + status_code=HTTPStatus.BAD_REQUEST, + detail=str(ex), + ) + except Exception as ex: + logger.warning(ex) + raise HTTPException( + status_code=HTTPStatus.INTERNAL_SERVER_ERROR, + detail="Cannot restore order", + ) + + +@nostrmarket_ext.put("/api/v1/orders/restore") +async def api_restore_orders( + wallet: WalletTypeInfo = Depends(require_admin_key), +) -> None: try: merchant = await get_merchant_for_user(wallet.wallet.user) assert merchant, "Merchant cannot be found" @@ -849,7 +879,7 @@ async def api_restore_orders( ) except Exception as e: logger.debug( - f"Failed to restore order friom event '{dm.event_id}': '{str(e)}'." + f"Failed to restore order from event '{dm.event_id}': '{str(e)}'." ) except AssertionError as ex: