Fix subscription errors (#81)
* pref: merge filters in one * chore: load nit * feat: restore individual order
This commit is contained in:
parent
a3299b63c4
commit
889152a80b
8 changed files with 138 additions and 66 deletions
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
10
services.py
10
services.py
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
25
tasks.py
25
tasks.py
|
|
@ -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()
|
||||||
|
|
|
||||||
44
views_api.py
44
views_api.py
|
|
@ -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:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue