Fix subscription errors (#81)

* pref: merge filters in one

* chore: load nit

* feat: restore individual order
This commit is contained in:
Vlad Stan 2023-09-12 15:03:37 +03:00 committed by GitHub
parent a3299b63c4
commit 889152a80b
8 changed files with 138 additions and 66 deletions

View file

@ -77,45 +77,56 @@ class NostrClient:
async def publish_nostr_event(self, e: NostrEvent): async def publish_nostr_event(self, e: NostrEvent):
await self.send_req_queue.put(["EVENT", e.dict()]) 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]} in_messages_filter = {"kinds": [4], "#p": [public_key]}
out_messages_filter = {"kinds": [4], "authors": [public_key]} out_messages_filter = {"kinds": [4], "authors": [public_key]}
if since and since != 0: if since and since != 0:
in_messages_filter["since"] = since in_messages_filter["since"] = since
out_messages_filter["since"] = since out_messages_filter["since"] = since
await self.send_req_queue.put( return [in_messages_filter, out_messages_filter]
["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]
)
logger.debug(f"Subscribed to direct-messages '{public_key}'.") def _filters_for_stall_events(self, public_key: str, since: int) -> List:
async def subscribe_to_stall_events(self, public_key: str, since: int):
stall_filter = {"kinds": [30017], "authors": [public_key]} stall_filter = {"kinds": [30017], "authors": [public_key]}
if since and since != 0: if since and since != 0:
stall_filter["since"] = since stall_filter["since"] = since
await self.send_req_queue.put( return [stall_filter]
["REQ", f"stall-events:{public_key}", stall_filter]
)
logger.debug(f"Subscribed to stall-events: '{public_key}'.") def _filters_for_product_events(self, public_key: str, since: int) -> List:
async def subscribe_to_product_events(self, public_key: str, since: int):
product_filter = {"kinds": [30018], "authors": [public_key]} product_filter = {"kinds": [30018], "authors": [public_key]}
if since and since != 0: if since and since != 0:
product_filter["since"] = since product_filter["since"] = since
await self.send_req_queue.put( return [product_filter]
["REQ", f"product-events:{public_key}", product_filter]
)
logger.debug(f"Subscribed to product-events: '{public_key}'.")
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]} profile_filter = {"kinds": [0], "authors": [public_key]}
if since and since != 0: if since and since != 0:
profile_filter["since"] = since + 1 profile_filter["since"] = since + 1
@ -127,17 +138,6 @@ class NostrClient:
# ["REQ", f"user-profile-events:{public_key}", profile_filter] # ["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]): async def restart(self, public_keys: List[str]):
await self.unsubscribe_merchants(public_keys) await self.unsubscribe_merchants(public_keys)
@ -160,11 +160,15 @@ class NostrClient:
self.ws.close() self.ws.close()
self.ws = None 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]): async def unsubscribe_merchants(self, public_keys: List[str]):
for pk in public_keys: for pk in public_keys:
try: try:
await self.unsubscribe_from_direct_messages(pk) await self.unsubscribe_merchant(pk)
await self.unsubscribe_from_merchant_events(pk)
except Exception as ex: except Exception as ex:
logger.warning(ex) logger.warning(ex)

View file

@ -16,6 +16,10 @@ from .crud import (
create_product, create_product,
create_stall, create_stall,
get_customer, 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_merchant_by_pubkey,
get_order, get_order,
get_order_by_event_id, get_order_by_event_id,
@ -364,6 +368,12 @@ async def extract_customer_order_from_dm(
return order 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): async def _handle_nip04_message(merchant_public_key: str, event: NostrEvent):
merchant = await get_merchant_by_pubkey(merchant_public_key) merchant = await get_merchant_by_pubkey(merchant_public_key)

View file

@ -57,7 +57,7 @@
<div> <div>
<span v-text="dm.message.message"></span> <span v-text="dm.message.message"></span>
<q-badge color="orange"> <q-badge color="orange">
<span v-text="dm.message.id" @click="showOrderDetails(dm.message.id)" class="cursor-pointer"></span> <span v-text="dm.message.id" @click="showOrderDetails(dm.message.id, dm.event_id)" class="cursor-pointer"></span>
</q-badge> </q-badge>
</div> </div>
<q-badge @click="showMessageRawData(index)" class="cursor-pointer">...</q-badge> <q-badge @click="showMessageRawData(index)" class="cursor-pointer">...</q-badge>

View file

@ -140,8 +140,8 @@ async function directMessages(path) {
} }
this.getCustomersDebounced() this.getCustomersDebounced()
}, },
showOrderDetails: function (orderId) { showOrderDetails: function (orderId, eventId) {
this.$emit('order-selected', orderId) this.$emit('order-selected', { orderId, eventId })
}, },
showClientOrders: function () { showClientOrders: function () {
this.$emit('customer-selected', this.activePublicKey) this.$emit('customer-selected', this.activePublicKey)

View file

@ -189,12 +189,33 @@ async function orderList(path) {
LNbits.utils.notifyApiError(error) 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 () { restoreOrders: async function () {
try { try {
this.search.restoring = true this.search.restoring = true
await LNbits.api.request( await LNbits.api.request(
'PUT', 'PUT',
`/nostrmarket/api/v1/order/restore`, `/nostrmarket/api/v1/orders/restore`,
this.adminkey this.adminkey
) )
await this.getOrders() 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) 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.expanded = true
order.isNew = false order.isNew = false
this.orders = [order] this.orders = [order]

View file

@ -152,8 +152,8 @@ const merchant = async () => {
filterOrdersForCustomer: function (customerPubkey) { filterOrdersForCustomer: function (customerPubkey) {
this.orderPubkey = customerPubkey this.orderPubkey = customerPubkey
}, },
showOrderDetails: async function (orderId) { showOrderDetails: async function (orderData) {
await this.$refs.orderListRef.orderSelected(orderId) await this.$refs.orderListRef.orderSelected(orderData.orderId, orderData.eventId)
}, },
waitForNotifications: async function () { waitForNotifications: async function () {
if (!this.merchant) return if (!this.merchant) return

View file

@ -1,4 +1,5 @@
from asyncio import Queue from asyncio import Queue
import asyncio
from lnbits.core.models import Payment from lnbits.core.models import Payment
from lnbits.tasks import register_invoice_listener from lnbits.tasks import register_invoice_listener
@ -12,7 +13,7 @@ from .crud import (
get_merchants_ids_with_pubkeys, get_merchants_ids_with_pubkeys,
) )
from .nostr.nostr_client import NostrClient 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(): 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): async def wait_for_nostr_events(nostr_client: NostrClient):
merchant_ids = await get_merchants_ids_with_pubkeys() merchant_ids = await get_merchants_ids_with_pubkeys()
for id, pk in merchant_ids: for id, pk in merchant_ids:
last_order_time = await get_last_order_time(id) since = await get_last_event_date_for_merchant(id)
last_dm_time = await get_last_direct_messages_created_at(id) await nostr_client.subscribe_merchant(pk, since + 1)
since = max(last_order_time, last_dm_time) await asyncio.sleep(0.1) # try to avoid 'too many concurrent REQ' from relays
await nostr_client.subscribe_to_direct_messages(pk, since) # customers = await get_all_unique_customers()
# for c in customers:
for id, pk in merchant_ids: # await nostr_client.subscribe_to_user_profile(c.public_key, c.event_created_at)
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)
while True: while True:
message = await nostr_client.get_event() message = await nostr_client.get_event()

View file

@ -35,12 +35,14 @@ from .crud import (
delete_zone, delete_zone,
get_customer, get_customer,
get_customers, get_customers,
get_direct_message_by_event_id,
get_direct_messages, get_direct_messages,
get_last_direct_messages_time, get_last_direct_messages_time,
get_merchant_by_pubkey, get_merchant_by_pubkey,
get_merchant_for_user, get_merchant_for_user,
get_merchants_ids_with_pubkeys, get_merchants_ids_with_pubkeys,
get_order, get_order,
get_order_by_event_id,
get_orders, get_orders,
get_orders_for_stall, get_orders_for_stall,
get_orders_from_direct_messages, 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_merchant(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)
return merchant return merchant
except AssertionError as ex: except AssertionError as ex:
@ -170,14 +170,15 @@ async def api_delete_merchant(
assert merchant, "Merchant cannot be found" assert merchant, "Merchant cannot be found"
assert merchant.id == merchant_id, "Wrong merchant ID" 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_orders(merchant.id)
await delete_merchant_products(merchant.id) await delete_merchant_products(merchant.id)
await delete_merchant_stalls(merchant.id) await delete_merchant_stalls(merchant.id)
await delete_merchant_direct_messages(merchant.id) await delete_merchant_direct_messages(merchant.id)
await delete_merchant_zones(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) await delete_merchant(merchant.id)
except AssertionError as ex: except AssertionError as ex:
raise HTTPException( 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( async def api_restore_orders(
event_id: str,
wallet: WalletTypeInfo = Depends(require_admin_key), wallet: WalletTypeInfo = Depends(require_admin_key),
) -> Order: ) -> 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: try:
merchant = await get_merchant_for_user(wallet.wallet.user) merchant = await get_merchant_for_user(wallet.wallet.user)
assert merchant, "Merchant cannot be found" assert merchant, "Merchant cannot be found"
@ -849,7 +879,7 @@ async def api_restore_orders(
) )
except Exception as e: except Exception as e:
logger.debug( 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: except AssertionError as ex: