From 89f46fff35b1435aad94c5382395958006998930 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 27 Mar 2023 17:19:51 +0300 Subject: [PATCH 01/17] feat: keep customer profiles up to date --- crud.py | 55 +++++++++++++++++++++++++++++++++++++++++++ migrations.py | 14 +++++++++++ models.py | 21 +++++++++++++++++ nostr/nostr_client.py | 11 ++++++++- services.py | 48 +++++++++++++++++++++++++++++++++---- static/js/index.js | 26 ++++++++++++++++++++ tasks.py | 5 ++++ 7 files changed, 175 insertions(+), 5 deletions(-) diff --git a/crud.py b/crud.py index 2029a2e..fb70c79 100644 --- a/crud.py +++ b/crud.py @@ -5,6 +5,8 @@ from lnbits.helpers import urlsafe_short_hash from . import db from .models import ( + Customer, + CustomerProfile, DirectMessage, Merchant, MerchantConfig, @@ -602,3 +604,56 @@ async def get_public_keys_for_direct_messages(merchant_id: str) -> List[str]: (merchant_id), ) return [row[0] for row in rows] + + +######################################## CUSTOMERS ######################################## + + +async def create_customer(merchant_id: str, data: Customer) -> Customer: + await db.execute( + f""" + INSERT INTO nostrmarket.customers (merchant_id, public_key, meta) + VALUES (?, ?, ?) + """, + ( + merchant_id, + data.public_key, + json.dumps(data.profile) if data.profile else "{}", + ), + ) + + customer = await get_customer(merchant_id, data.public_key) + assert customer, "Newly created customer couldn't be retrieved" + return customer + + +async def get_customer(merchant_id: str, public_key: str) -> Optional[Customer]: + row = await db.fetchone( + "SELECT * FROM nostrmarket.customers WHERE merchant_id = ? AND public_key = ?", + ( + merchant_id, + public_key, + ), + ) + return Customer.from_row(row) if row else None + + +async def get_cusomers(merchant_id: str) -> List[Customer]: + rows = await db.fetchall( + "SELECT * FROM nostrmarket.customers WHERE merchant_id = ?", (merchant_id,) + ) + return [Customer.from_row(row) for row in rows] + + +async def get_all_customers() -> List[Customer]: + rows = await db.fetchall("SELECT * FROM nostrmarket.customers") + return [Customer.from_row(row) for row in rows] + + +async def update_customer_profile( + public_key: str, event_created_at: int, profile: CustomerProfile +): + await db.execute( + f"UPDATE nostrmarket.customers SET event_created_at = ?, meta = ? WHERE public_key = ?", + (event_created_at, json.dumps(profile.dict()), public_key), + ) diff --git a/migrations.py b/migrations.py index 530f12e..9edc050 100644 --- a/migrations.py +++ b/migrations.py @@ -125,3 +125,17 @@ async def m001_initial(db): await db.execute( "CREATE INDEX idx_event_id ON nostrmarket.direct_messages (event_id)" ) + + """ + Initial customers table. + """ + await db.execute( + """ + CREATE TABLE nostrmarket.customers ( + merchant_id TEXT NOT NULL, + public_key TEXT NOT NULL, + event_created_at INTEGER, + meta TEXT NOT NULL DEFAULT '{}' + ); + """ + ) diff --git a/models.py b/models.py index 29d91e9..fbb43de 100644 --- a/models.py +++ b/models.py @@ -427,3 +427,24 @@ class DirectMessage(PartialDirectMessage): def from_row(cls, row: Row) -> "DirectMessage": dm = cls(**dict(row)) return dm + + +######################################## CUSTOMERS ######################################## + + +class CustomerProfile(BaseModel): + name: Optional[str] + about: Optional[str] + + +class Customer(BaseModel): + merchant_id: str + public_key: str + event_created_at: Optional[int] + profile: Optional[CustomerProfile] + + @classmethod + def from_row(cls, row: Row) -> "Customer": + customer = cls(**dict(row)) + customer.profile = CustomerProfile(**json.loads(row["meta"])) + return customer diff --git a/nostr/nostr_client.py b/nostr/nostr_client.py index a2a936c..965c827 100644 --- a/nostr/nostr_client.py +++ b/nostr/nostr_client.py @@ -70,7 +70,7 @@ class NostrClient: async def subscribe_to_direct_messages(self, public_key: str, since: int): in_messages_filter = {"kind": 4, "#p": [public_key]} out_messages_filter = {"kind": 4, "authors": [public_key]} - if since != 0: + if since and since != 0: in_messages_filter["since"] = since out_messages_filter["since"] = since @@ -92,6 +92,15 @@ class NostrClient: ["REQ", f"product-events:{public_key}", product_filter] ) + async def subscribe_to_user_profile(self, public_key: str, since: int): + profile_filter = {"kind": 0, "authors": [public_key]} + if since and since != 0: + profile_filter["since"] = since + 1 + + await self.send_req_queue.put( + ["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}"]) diff --git a/services.py b/services.py index 288ed43..d653457 100644 --- a/services.py +++ b/services.py @@ -4,11 +4,15 @@ from typing import List, Optional, Tuple from loguru import logger from lnbits.core import create_invoice, get_wallet +from lnbits.core.services import websocketUpdater from . import nostr_client from .crud import ( + CustomerProfile, + create_customer, create_direct_message, create_order, + get_customer, get_merchant_by_pubkey, get_order, get_order_by_event_id, @@ -16,6 +20,7 @@ from .crud import ( get_products_by_ids, get_stalls, get_wallet_for_product, + update_customer_profile, update_order_paid_status, update_product, update_product_quantity, @@ -23,6 +28,7 @@ from .crud import ( ) from .helpers import order_from_json from .models import ( + Customer, Merchant, Nostrable, Order, @@ -85,6 +91,16 @@ async def create_new_order( extra=await OrderExtra.from_products(products), ) await create_order(merchant.id, order) + await websocketUpdater( + merchant.id, + json.dumps( + { + "type": "new-order", + "stallId": products[0].stall_id, + "customerPubkey": data.public_key, + } + ), + ) return PaymentRequest( id=data.id, payment_options=[PaymentOption(type="ln", link=invoice)] @@ -206,9 +222,11 @@ async def process_nostr_message(msg: str): type, *rest = json.loads(msg) if type.upper() == "EVENT": subscription_id, event = rest - _, merchant_public_key = subscription_id.split(":") event = NostrEvent(**event) + if event.kind == 0: + await _handle_customer_profile_update(event) if event.kind == 4: + _, merchant_public_key = subscription_id.split(":") await _handle_nip04_message(merchant_public_key, event) return except Exception as ex: @@ -235,7 +253,7 @@ async def _handle_nip04_message(merchant_public_key: str, event: NostrEvent): async def _handle_incoming_dms( event: NostrEvent, merchant: Merchant, clear_text_msg: str ): - dm_content = await _handle_dirrect_message( + dm_reply = await _handle_dirrect_message( merchant.id, merchant.public_key, event.pubkey, @@ -243,10 +261,17 @@ async def _handle_incoming_dms( event.created_at, clear_text_msg, ) - if dm_content: - dm_event = merchant.build_dm_event(dm_content, event.pubkey) + if dm_reply: + dm_event = merchant.build_dm_event(dm_reply, event.pubkey) await nostr_client.publish_nostr_event(dm_event) + customer = await get_customer(merchant.id, event.pubkey) + if not customer: + await create_customer( + merchant.id, Customer(merchant_id=merchant.id, public_key=event.pubkey) + ) + await nostr_client.subscribe_to_user_profile(event.pubkey, 0) + async def _handle_outgoing_dms( event: NostrEvent, merchant: Merchant, clear_text_msg: str @@ -308,3 +333,18 @@ async def _handle_new_order(order: PartialOrder) -> Optional[str]: return json.dumps(new_order.dict(), separators=(",", ":"), ensure_ascii=False) return None + + +async def _handle_customer_profile_update(event: NostrEvent): + try: + profile = json.loads(event.content) + await update_customer_profile( + event.pubkey, + event.created_at, + CustomerProfile( + name=profile["name"] if "name" in profile else "", + about=profile["about"] if "about" in profile else "", + ), + ) + except Exception as ex: + logger.warning(ex) diff --git a/static/js/index.js b/static/js/index.js index 58b2c5c..5d572b9 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -102,10 +102,36 @@ const merchant = async () => { }, customerSelectedForOrder: function (customerPubkey) { this.activeChatCustomer = customerPubkey + }, + waitForNotifications: function () { + try { + const scheme = location.protocol === 'http:' ? 'ws' : 'wss' + const port = location.port ? `:${location.port}` : '' + const wsUrl = `${scheme}://${document.domain}${port}/api/v1/ws/${this.merchant.id}` + const wsConnection = new WebSocket(wsUrl) + console.log('### waiting for events') + wsConnection.onmessage = e => { + console.log('### e', e) + this.$q.notify({ + timeout: 5000, + type: 'positive', + message: 'New Update', + caption: `something here` + }) + } + } catch (error) { + this.$q.notify({ + timeout: 5000, + type: 'warning', + message: 'Failed to watch for updated', + caption: `${error}` + }) + } } }, created: async function () { await this.getMerchant() + await this.waitForNotifications() } }) } diff --git a/tasks.py b/tasks.py index ad791e3..35ff479 100644 --- a/tasks.py +++ b/tasks.py @@ -4,6 +4,7 @@ from lnbits.core.models import Payment from lnbits.tasks import register_invoice_listener from .crud import ( + get_all_customers, get_last_direct_messages_time, get_last_order_time, get_public_keys_for_merchants, @@ -42,6 +43,10 @@ async def wait_for_nostr_events(nostr_client: NostrClient): await nostr_client.subscribe_to_direct_messages(p, since) + customers = await get_all_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() await process_nostr_message(message) From a719517b9cdf3805be327e55c417e17163433697 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 27 Mar 2023 17:36:08 +0300 Subject: [PATCH 02/17] feat: show customer names --- crud.py | 18 +----------------- services.py | 14 +++++++------- .../direct-messages/direct-messages.html | 2 +- .../direct-messages/direct-messages.js | 9 +++++---- views_api.py | 14 +++++--------- 5 files changed, 19 insertions(+), 38 deletions(-) diff --git a/crud.py b/crud.py index fb70c79..196a973 100644 --- a/crud.py +++ b/crud.py @@ -464,14 +464,6 @@ async def get_orders_for_stall(merchant_id: str, stall_id: str) -> List[Order]: return [Order.from_row(row) for row in rows] -async def get_public_keys_for_orders(merchant_id: str) -> List[str]: - rows = await db.fetchall( - "SELECT DISTINCT public_key FROM nostrmarket.orders WHERE merchant_id = ?", - (merchant_id,), - ) - return [row[0] for row in rows] - - async def get_last_order_time(public_key: str) -> int: row = await db.fetchone( """ @@ -598,14 +590,6 @@ async def delete_merchant_direct_messages(merchant_id: str) -> None: ) -async def get_public_keys_for_direct_messages(merchant_id: str) -> List[str]: - rows = await db.fetchall( - "SELECT DISTINCT public_key FROM nostrmarket.direct_messages WHERE merchant_id = ?", - (merchant_id), - ) - return [row[0] for row in rows] - - ######################################## CUSTOMERS ######################################## @@ -638,7 +622,7 @@ async def get_customer(merchant_id: str, public_key: str) -> Optional[Customer]: return Customer.from_row(row) if row else None -async def get_cusomers(merchant_id: str) -> List[Customer]: +async def get_customers(merchant_id: str) -> List[Customer]: rows = await db.fetchall( "SELECT * FROM nostrmarket.customers WHERE merchant_id = ?", (merchant_id,) ) diff --git a/services.py b/services.py index d653457..6c159c4 100644 --- a/services.py +++ b/services.py @@ -253,6 +253,13 @@ async def _handle_nip04_message(merchant_public_key: str, event: NostrEvent): async def _handle_incoming_dms( event: NostrEvent, merchant: Merchant, clear_text_msg: str ): + customer = await get_customer(merchant.id, event.pubkey) + if not customer: + await create_customer( + merchant.id, Customer(merchant_id=merchant.id, public_key=event.pubkey) + ) + await nostr_client.subscribe_to_user_profile(event.pubkey, 0) + dm_reply = await _handle_dirrect_message( merchant.id, merchant.public_key, @@ -265,13 +272,6 @@ async def _handle_incoming_dms( dm_event = merchant.build_dm_event(dm_reply, event.pubkey) await nostr_client.publish_nostr_event(dm_event) - customer = await get_customer(merchant.id, event.pubkey) - if not customer: - await create_customer( - merchant.id, Customer(merchant_id=merchant.id, public_key=event.pubkey) - ) - await nostr_client.subscribe_to_user_profile(event.pubkey, 0) - async def _handle_outgoing_dms( event: NostrEvent, merchant: Merchant, clear_text_msg: str diff --git a/static/components/direct-messages/direct-messages.html b/static/components/direct-messages/direct-messages.html index 43a81ac..5d4c678 100644 --- a/static/components/direct-messages/direct-messages.html +++ b/static/components/direct-messages/direct-messages.html @@ -9,7 +9,7 @@ DirectMessage: +) -> List[Customer]: try: merchant = await get_merchant_for_user(wallet.wallet.user) assert merchant, f"Merchant cannot be found" - - dm_pubkeys = await get_public_keys_for_direct_messages(merchant.id) - orders_pubkeys = await get_public_keys_for_orders(merchant.id) - - return list(dict.fromkeys(dm_pubkeys + orders_pubkeys)) + return await get_customers(merchant.id) except AssertionError as ex: raise HTTPException( From 7c6feca83188e97d705c44bab1483cdd12bfa76a Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 27 Mar 2023 18:00:42 +0300 Subject: [PATCH 03/17] fix: Vue error --- static/components/direct-messages/direct-messages.js | 9 +++------ templates/nostrmarket/index.html | 11 ++++++++++- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/static/components/direct-messages/direct-messages.js b/static/components/direct-messages/direct-messages.js index a28362e..dd33480 100644 --- a/static/components/direct-messages/direct-messages.js +++ b/static/components/direct-messages/direct-messages.js @@ -2,7 +2,7 @@ async function directMessages(path) { const template = await loadTemplateAsync(path) Vue.component('direct-messages', { name: 'direct-messages', - props: ['active-public-key', 'adminkey', 'inkey'], + props: ['adminkey', 'inkey'], template, watch: { @@ -13,6 +13,7 @@ async function directMessages(path) { data: function () { return { customers: [], + activePublicKey: null, messages: [], newMessage: '' } @@ -31,10 +32,7 @@ async function directMessages(path) { this.inkey ) this.messages = data - console.log( - '### this.messages', - this.messages.map(m => m.message) - ) + this.focusOnChatBox(this.messages.length - 1) } catch (error) { LNbits.utils.notifyApiError(error) @@ -48,7 +46,6 @@ async function directMessages(path) { this.inkey ) this.customers = data - console.log('### customers', this.customers) } catch (error) { LNbits.utils.notifyApiError(error) } diff --git a/templates/nostrmarket/index.html b/templates/nostrmarket/index.html index 94aa403..c2c8c23 100644 --- a/templates/nostrmarket/index.html +++ b/templates/nostrmarket/index.html @@ -59,6 +59,15 @@ > + + +
+
+
+
+
+
+
@@ -133,10 +142,10 @@
+
From 098cb5adf5218374b163dca07d93aba2af52d953 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 27 Mar 2023 18:09:19 +0300 Subject: [PATCH 04/17] feat: notify new customer --- services.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/services.py b/services.py index 6c159c4..f06fa1c 100644 --- a/services.py +++ b/services.py @@ -255,10 +255,7 @@ async def _handle_incoming_dms( ): customer = await get_customer(merchant.id, event.pubkey) if not customer: - await create_customer( - merchant.id, Customer(merchant_id=merchant.id, public_key=event.pubkey) - ) - await nostr_client.subscribe_to_user_profile(event.pubkey, 0) + await _handle_new_customer(event, merchant) dm_reply = await _handle_dirrect_message( merchant.id, @@ -335,6 +332,17 @@ async def _handle_new_order(order: PartialOrder) -> Optional[str]: return None +async def _handle_new_customer(event, merchant): + await create_customer( + merchant.id, Customer(merchant_id=merchant.id, public_key=event.pubkey) + ) + await nostr_client.subscribe_to_user_profile(event.pubkey, 0) + await websocketUpdater( + merchant.id, + json.dumps({"type": "new-customer"}), + ) + + async def _handle_customer_profile_update(event: NostrEvent): try: profile = json.loads(event.content) From e04b9dc4481f17943845846bec3382eefeaa980d Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 29 Mar 2023 14:20:49 +0300 Subject: [PATCH 05/17] feat: keep track of new messages --- crud.py | 14 ++++++++++++++ migrations.py | 1 + models.py | 1 + services.py | 3 +++ .../direct-messages/direct-messages.html | 2 +- .../components/direct-messages/direct-messages.js | 10 ++++++++++ views_api.py | 2 ++ 7 files changed, 32 insertions(+), 1 deletion(-) diff --git a/crud.py b/crud.py index 196a973..131bd2e 100644 --- a/crud.py +++ b/crud.py @@ -641,3 +641,17 @@ async def update_customer_profile( f"UPDATE nostrmarket.customers SET event_created_at = ?, meta = ? WHERE public_key = ?", (event_created_at, json.dumps(profile.dict()), public_key), ) + + +async def increment_customer_unread_messages(public_key: str): + await db.execute( + f"UPDATE nostrmarket.customers SET unread_messages = unread_messages + 1 WHERE public_key = ?", + (public_key,), + ) + + +async def update_customer_no_unread_messages(public_key: str): + await db.execute( + f"UPDATE nostrmarket.customers SET unread_messages = 0 WHERE public_key = ?", + (public_key,), + ) diff --git a/migrations.py b/migrations.py index 9edc050..27d4e21 100644 --- a/migrations.py +++ b/migrations.py @@ -135,6 +135,7 @@ async def m001_initial(db): merchant_id TEXT NOT NULL, public_key TEXT NOT NULL, event_created_at INTEGER, + unread_messages INTEGER NOT NULL DEFAULT 1, meta TEXT NOT NULL DEFAULT '{}' ); """ diff --git a/models.py b/models.py index fbb43de..e7565eb 100644 --- a/models.py +++ b/models.py @@ -442,6 +442,7 @@ class Customer(BaseModel): public_key: str event_created_at: Optional[int] profile: Optional[CustomerProfile] + unread_messages: int = 0 @classmethod def from_row(cls, row: Row) -> "Customer": diff --git a/services.py b/services.py index f06fa1c..46a3aba 100644 --- a/services.py +++ b/services.py @@ -20,6 +20,7 @@ from .crud import ( get_products_by_ids, get_stalls, get_wallet_for_product, + increment_customer_unread_messages, update_customer_profile, update_order_paid_status, update_product, @@ -256,6 +257,8 @@ async def _handle_incoming_dms( customer = await get_customer(merchant.id, event.pubkey) if not customer: await _handle_new_customer(event, merchant) + else: + await increment_customer_unread_messages(event.pubkey) dm_reply = await _handle_dirrect_message( merchant.id, diff --git a/static/components/direct-messages/direct-messages.html b/static/components/direct-messages/direct-messages.html index 5d4c678..409ea3a 100644 --- a/static/components/direct-messages/direct-messages.html +++ b/static/components/direct-messages/direct-messages.html @@ -9,7 +9,7 @@ Date: Wed, 29 Mar 2023 14:41:51 +0300 Subject: [PATCH 06/17] subscribe after ws conection --- __init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__init__.py b/__init__.py index 7e80766..bac1155 100644 --- a/__init__.py +++ b/__init__.py @@ -46,7 +46,7 @@ def nostrmarket_start(): async def _wait_for_nostr_events(): # wait for this extension to initialize - await asyncio.sleep(5) + await asyncio.sleep(15) await wait_for_nostr_events(nostr_client) loop = asyncio.get_event_loop() From 5b03c324f10f6fa51fb574360e5f6a38ce02e55d Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 29 Mar 2023 16:22:05 +0300 Subject: [PATCH 07/17] feat: filter orders --- crud.py | 31 +++++++--- static/components/order-list/order-list.html | 39 ++++++++++-- static/components/order-list/order-list.js | 64 ++++++++++++++++++-- templates/nostrmarket/index.html | 10 ++- views_api.py | 20 ++++-- 5 files changed, 138 insertions(+), 26 deletions(-) diff --git a/crud.py b/crud.py index 131bd2e..a4d5fb7 100644 --- a/crud.py +++ b/crud.py @@ -445,21 +445,34 @@ async def get_order_by_event_id(merchant_id: str, event_id: str) -> Optional[Ord return Order.from_row(row) if row else None -async def get_orders(merchant_id: str) -> List[Order]: +async def get_orders(merchant_id: str, **kwargs) -> List[Order]: + q = " AND ".join( + [f"{field[0]} = ?" for field in kwargs.items() if field[1] != None] + ) + values = () + if q: + q = f"AND {q}" + values = (v for v in kwargs.values() if v != None) rows = await db.fetchall( - "SELECT * FROM nostrmarket.orders WHERE merchant_id = ? ORDER BY time DESC", - (merchant_id,), + f"SELECT * FROM nostrmarket.orders WHERE merchant_id = ? {q} ORDER BY time DESC", + (merchant_id, *values), ) return [Order.from_row(row) for row in rows] -async def get_orders_for_stall(merchant_id: str, stall_id: str) -> List[Order]: +async def get_orders_for_stall( + merchant_id: str, stall_id: str, **kwargs +) -> List[Order]: + q = " AND ".join( + [f"{field[0]} = ?" for field in kwargs.items() if field[1] != None] + ) + values = () + if q: + q = f"AND {q}" + values = (v for v in kwargs.values() if v != None) rows = await db.fetchall( - "SELECT * FROM nostrmarket.orders WHERE merchant_id = ? AND stall_id = ? ORDER BY time DESC", - ( - merchant_id, - stall_id, - ), + f"SELECT * FROM nostrmarket.orders WHERE merchant_id = ? AND stall_id = ? {q} ORDER BY time DESC", + (merchant_id, stall_id, *values), ) return [Order.from_row(row) for row in rows] diff --git a/static/components/order-list/order-list.html b/static/components/order-list/order-list.html index 15465bd..1c665ff 100644 --- a/static/components/order-list/order-list.html +++ b/static/components/order-list/order-list.html @@ -1,18 +1,45 @@
-
+
+ + +
+
+ + +
+
+ + +
+
Refresh OrdersSearch Orders
-
+
({...s, expanded: false})) @@ -129,10 +160,35 @@ async function orderList(path) { }, customerSelected: function (customerPubkey) { this.$emit('customer-selected', customerPubkey) + }, + getCustomers: async function () { + try { + const {data} = await LNbits.api.request( + 'GET', + '/nostrmarket/api/v1/customers', + this.inkey + ) + this.customers = data + } catch (error) { + LNbits.utils.notifyApiError(error) + } + }, + buildCustomerLabel: function (c) { + let label = `${c.profile.name || 'unknown'} ${c.profile.about || ''}` + if (c.unread_messages) { + label += `[new: ${c.unread_messages}]` + } + label += ` (${c.public_key.slice(0, 16)}...${c.public_key.slice( + c.public_key.length - 16 + )}` + return label } }, created: async function () { - await this.getOrders() + if (this.stallId) { + await this.getOrders() + } + await this.getCustomers() } }) } diff --git a/templates/nostrmarket/index.html b/templates/nostrmarket/index.html index c2c8c23..051f524 100644 --- a/templates/nostrmarket/index.html +++ b/templates/nostrmarket/index.html @@ -62,9 +62,13 @@
-
-
-
+
+ +
diff --git a/views_api.py b/views_api.py index 3092bee..8b8a4ee 100644 --- a/views_api.py +++ b/views_api.py @@ -1,6 +1,6 @@ import json from http import HTTPStatus -from typing import List, Optional +from typing import List, Optional, Union from fastapi import Depends from fastapi.exceptions import HTTPException @@ -448,12 +448,17 @@ async def api_get_stall_products( @nostrmarket_ext.get("/api/v1/stall/order/{stall_id}") async def api_get_stall_orders( stall_id: str, + paid: Optional[bool] = None, + shipped: Optional[bool] = None, + pubkey: Optional[str] = None, wallet: WalletTypeInfo = Depends(require_invoice_key), ): try: merchant = await get_merchant_for_user(wallet.wallet.user) assert merchant, "Merchant cannot be found" - orders = await get_orders_for_stall(merchant.id, stall_id) + orders = await get_orders_for_stall( + merchant.id, stall_id, paid=paid, shipped=shipped, public_key=pubkey + ) return orders except AssertionError as ex: raise HTTPException( @@ -673,12 +678,19 @@ async def api_get_order(order_id: str, wallet: WalletTypeInfo = Depends(get_key_ @nostrmarket_ext.get("/api/v1/order") -async def api_get_orders(wallet: WalletTypeInfo = Depends(get_key_type)): +async def api_get_orders( + paid: Optional[bool] = None, + shipped: Optional[bool] = None, + pubkey: Optional[str] = None, + wallet: WalletTypeInfo = Depends(get_key_type), +): try: merchant = await get_merchant_for_user(wallet.wallet.user) assert merchant, "Merchant cannot be found" - orders = await get_orders(merchant.id) + orders = await get_orders( + merchant_id=merchant.id, paid=paid, shipped=shipped, public_key=pubkey + ) return orders except AssertionError as ex: raise HTTPException( From 5ed77e68c44ebbb243a636d0b8ebf28d531d9d35 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 29 Mar 2023 16:29:30 +0300 Subject: [PATCH 08/17] fix: show nice labels for filters --- static/components/order-list/order-list.js | 24 ++++++++++++++-------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/static/components/order-list/order-list.js b/static/components/order-list/order-list.js index 33d3b35..5d1d863 100644 --- a/static/components/order-list/order-list.js +++ b/static/components/order-list/order-list.js @@ -14,22 +14,28 @@ async function orderList(path) { filter: '', search: { publicKey: '', - isPaid: null, - isShipped: null + isPaid: { + label: 'All', + id: null + }, + isShipped: { + label: 'All', + id: null + } }, customers: [], ternaryOptions: [ { label: 'All', - value: null + id: null }, { label: 'Yes', - value: 'true' + id: 'true' }, { label: 'No', - value: 'false' + id: 'false' } ], ordersTable: { @@ -110,11 +116,11 @@ async function orderList(path) { if (this.search.publicKey) { query.push(`pubkey=${this.search.publicKey}`) } - if (this.search.isPaid) { - query.push(`paid=${this.search.isPaid}`) + if (this.search.isPaid.id) { + query.push(`paid=${this.search.isPaid.id}`) } - if (this.search.isShipped) { - query.push(`shipped=${this.search.isShipped}`) + if (this.search.isShipped.id) { + query.push(`shipped=${this.search.isShipped.id}`) } const {data} = await LNbits.api.request( 'GET', From 08e71e28e38fa5eb4b4a1e4caf0f75df0485411c Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 29 Mar 2023 16:38:12 +0300 Subject: [PATCH 09/17] feat: navigate from customer to chat --- static/components/direct-messages/direct-messages.js | 5 ++++- templates/nostrmarket/index.html | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/static/components/direct-messages/direct-messages.js b/static/components/direct-messages/direct-messages.js index 23d5ca2..ba965c5 100644 --- a/static/components/direct-messages/direct-messages.js +++ b/static/components/direct-messages/direct-messages.js @@ -2,10 +2,13 @@ async function directMessages(path) { const template = await loadTemplateAsync(path) Vue.component('direct-messages', { name: 'direct-messages', - props: ['adminkey', 'inkey'], + props: ['active-chat-customer', 'adminkey', 'inkey'], template, watch: { + activeChatCustomer: async function (n) { + this.activePublicKey = n + }, activePublicKey: async function (n) { await this.getDirectMessages(n) } diff --git a/templates/nostrmarket/index.html b/templates/nostrmarket/index.html index 051f524..74e9af8 100644 --- a/templates/nostrmarket/index.html +++ b/templates/nostrmarket/index.html @@ -148,8 +148,9 @@ - +
From 2d522f8c473edd5f35de06c368c479e6ee5dd503 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 29 Mar 2023 16:39:43 +0300 Subject: [PATCH 10/17] chore: label fix --- static/components/order-list/order-list.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/components/order-list/order-list.js b/static/components/order-list/order-list.js index 5d1d863..e40c7ef 100644 --- a/static/components/order-list/order-list.js +++ b/static/components/order-list/order-list.js @@ -49,7 +49,7 @@ async function orderList(path) { { name: 'id', align: 'left', - label: 'ID', + label: 'Order ID', field: 'id' }, { From 4974192b2153fc3dda874ce9466ad98aabe1ced7 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 29 Mar 2023 17:02:31 +0300 Subject: [PATCH 11/17] feat: navigate from chat to orders --- .../direct-messages/direct-messages.html | 16 +++++++++++++++- .../direct-messages/direct-messages.js | 3 +++ static/components/order-list/order-list.js | 11 ++++++++++- static/js/index.js | 4 ++++ templates/nostrmarket/index.html | 3 ++- 5 files changed, 34 insertions(+), 3 deletions(-) diff --git a/static/components/direct-messages/direct-messages.html b/static/components/direct-messages/direct-messages.html index 409ea3a..7a91447 100644 --- a/static/components/direct-messages/direct-messages.html +++ b/static/components/direct-messages/direct-messages.html @@ -1,7 +1,21 @@
-
Messages
+
+
+
Messages
+
+
+ Client Orders +
+
diff --git a/static/components/direct-messages/direct-messages.js b/static/components/direct-messages/direct-messages.js index ba965c5..2542ffa 100644 --- a/static/components/direct-messages/direct-messages.js +++ b/static/components/direct-messages/direct-messages.js @@ -81,6 +81,9 @@ async function directMessages(path) { LNbits.utils.notifyApiError(error) } }, + showClientOrders: function () { + this.$emit('customer-selected', this.activePublicKey) + }, selectActiveCustomer: async function () { await this.getDirectMessages(this.activePublicKey) }, diff --git a/static/components/order-list/order-list.js b/static/components/order-list/order-list.js index e40c7ef..ae50fc0 100644 --- a/static/components/order-list/order-list.js +++ b/static/components/order-list/order-list.js @@ -2,9 +2,18 @@ async function orderList(path) { const template = await loadTemplateAsync(path) Vue.component('order-list', { name: 'order-list', - props: ['stall-id', 'adminkey', 'inkey'], + props: ['stall-id', 'customer-pubkey-filter', 'adminkey', 'inkey'], template, + watch: { + customerPubkeyFilter: async function (n) { + this.search.publicKey = n + this.search.isPaid = {label: 'All', id: null} + this.search.isShipped = {label: 'All', id: null} + await this.getOrders() + } + }, + data: function () { return { orders: [], diff --git a/static/js/index.js b/static/js/index.js index 5d572b9..8b8b882 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -21,6 +21,7 @@ const merchant = async () => { merchant: {}, shippingZones: [], activeChatCustomer: '', + orderPubkey: null, showKeys: false, importKeyDialog: { show: false, @@ -103,6 +104,9 @@ const merchant = async () => { customerSelectedForOrder: function (customerPubkey) { this.activeChatCustomer = customerPubkey }, + filterOrdersForCustomer: function (customerPubkey) { + this.orderPubkey = customerPubkey + }, waitForNotifications: function () { try { const scheme = location.protocol === 'http:' ? 'ws' : 'wss' diff --git a/templates/nostrmarket/index.html b/templates/nostrmarket/index.html index 74e9af8..b9b8a82 100644 --- a/templates/nostrmarket/index.html +++ b/templates/nostrmarket/index.html @@ -66,6 +66,7 @@
@@ -149,8 +150,8 @@ :inkey="g.user.wallets[0].inkey" :adminkey="g.user.wallets[0].adminkey" :active-chat-customer="activeChatCustomer" + @customer-selected="filterOrdersForCustomer" > -
From 8d1998c33b9a319d5cba8192fe823eabab7fc6b0 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 29 Mar 2023 18:00:01 +0300 Subject: [PATCH 12/17] feat: dinamically refresh orders --- static/components/order-list/order-list.js | 22 ++++++++++++++++++++++ static/js/index.js | 21 +++++++++++++-------- templates/nostrmarket/index.html | 1 + views_api.py | 8 ++++---- 4 files changed, 40 insertions(+), 12 deletions(-) diff --git a/static/components/order-list/order-list.js b/static/components/order-list/order-list.js index ae50fc0..927fe38 100644 --- a/static/components/order-list/order-list.js +++ b/static/components/order-list/order-list.js @@ -141,6 +141,18 @@ async function orderList(path) { LNbits.utils.notifyApiError(error) } }, + getOrder: async function (orderId) { + try { + const {data} = await LNbits.api.request( + 'GET', + `/nostrmarket/api/v1/order/${orderId}`, + this.inkey + ) + return {...data, expanded: false} + } catch (error) { + LNbits.utils.notifyApiError(error) + } + }, updateOrderShipped: async function () { this.selectedOrder.shipped = !this.selectedOrder.shipped try { @@ -163,6 +175,16 @@ async function orderList(path) { } this.showShipDialog = false }, + addOrder: async function (data) { + if ( + !this.search.publicKey || + this.search.publicKey === data.customerPubkey + ) { + const order = await this.getOrder(data.orderId) + this.orders.unshift(order) + } + }, + showShipOrderDialog: function (order) { this.selectedOrder = order this.shippingMessage = order.shipped diff --git a/static/js/index.js b/static/js/index.js index 8b8b882..29ea63f 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -107,21 +107,26 @@ const merchant = async () => { filterOrdersForCustomer: function (customerPubkey) { this.orderPubkey = customerPubkey }, - waitForNotifications: function () { + waitForNotifications: async function () { try { const scheme = location.protocol === 'http:' ? 'ws' : 'wss' const port = location.port ? `:${location.port}` : '' const wsUrl = `${scheme}://${document.domain}${port}/api/v1/ws/${this.merchant.id}` const wsConnection = new WebSocket(wsUrl) console.log('### waiting for events') - wsConnection.onmessage = e => { + wsConnection.onmessage = async e => { console.log('### e', e) - this.$q.notify({ - timeout: 5000, - type: 'positive', - message: 'New Update', - caption: `something here` - }) + const data = JSON.parse(e.data) + if (data.type === 'new-order') { + this.$q.notify({ + timeout: 5000, + type: 'positive', + message: 'New Order' + }) + await this.$refs.orderListRef.addOrder(data) + } else if (data.type === 'new-customer') { + } else if (data.type === 'new-direct-message') { + } } } catch (error) { this.$q.notify({ diff --git a/templates/nostrmarket/index.html b/templates/nostrmarket/index.html index b9b8a82..3cf3f96 100644 --- a/templates/nostrmarket/index.html +++ b/templates/nostrmarket/index.html @@ -64,6 +64,7 @@
Date: Thu, 30 Mar 2023 11:22:41 +0300 Subject: [PATCH 13/17] feat: show `new` badge for order --- static/components/order-list/order-list.html | 5 ++++- static/components/order-list/order-list.js | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/static/components/order-list/order-list.html b/static/components/order-list/order-list.html index 1c665ff..231ceec 100644 --- a/static/components/order-list/order-list.html +++ b/static/components/order-list/order-list.html @@ -63,7 +63,10 @@ /> - {{toShortId(props.row.id)}} + + {{toShortId(props.row.id)}} + new {{props.row.total}} diff --git a/static/components/order-list/order-list.js b/static/components/order-list/order-list.js index 927fe38..cc77029 100644 --- a/static/components/order-list/order-list.js +++ b/static/components/order-list/order-list.js @@ -148,7 +148,7 @@ async function orderList(path) { `/nostrmarket/api/v1/order/${orderId}`, this.inkey ) - return {...data, expanded: false} + return {...data, expanded: false, isNew: true} } catch (error) { LNbits.utils.notifyApiError(error) } From 5942d135b3a4f11d26bee8ba0acf71cc9d9d8392 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 30 Mar 2023 11:36:28 +0300 Subject: [PATCH 14/17] feat: show unread count --- static/components/direct-messages/direct-messages.html | 5 ++++- static/components/direct-messages/direct-messages.js | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/static/components/direct-messages/direct-messages.html b/static/components/direct-messages/direct-messages.html index 7a91447..5f74b37 100644 --- a/static/components/direct-messages/direct-messages.html +++ b/static/components/direct-messages/direct-messages.html @@ -2,9 +2,12 @@
-
+
Messages
+
+   new +
c.unread_messages).length } catch (error) { LNbits.utils.notifyApiError(error) } @@ -86,6 +88,7 @@ async function directMessages(path) { }, selectActiveCustomer: async function () { await this.getDirectMessages(this.activePublicKey) + await this.getCustomers() }, focusOnChatBox: function (index) { setTimeout(() => { From 39bcbfb03537d25186b582e50ac2e0da6c26203b Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 30 Mar 2023 12:33:16 +0300 Subject: [PATCH 15/17] feat: show total in fiat --- .../direct-messages/direct-messages.html | 4 ++- static/components/order-list/order-list.html | 36 ++++++++++++++++--- static/components/order-list/order-list.js | 30 ++++++++++++++-- static/js/utils.js | 10 ++++++ 4 files changed, 72 insertions(+), 8 deletions(-) diff --git a/static/components/direct-messages/direct-messages.html b/static/components/direct-messages/direct-messages.html index 5f74b37..ab7eee1 100644 --- a/static/components/direct-messages/direct-messages.html +++ b/static/components/direct-messages/direct-messages.html @@ -6,7 +6,9 @@
Messages
-   new +   new
new - {{props.row.total}} - + + {{satBtc(props.row.total)}} + + + + {{orderTotal(props.row)}} {{props.row.extra.currency}} + +
Quantity
-
Name
+
Name
+
Price
+
@@ -122,13 +130,31 @@ >
{{item.quantity}}
x
-
- {{productOverview(props.row, item.product_id)}} +
+ {{productName(props.row, item.product_id)}}
+
+ {{productPrice(props.row, item.product_id)}} +
+
+
+
Exchange Rate:
+
+ +
+
+
Order ID:
diff --git a/static/components/order-list/order-list.js b/static/components/order-list/order-list.js index cc77029..fe64ec5 100644 --- a/static/components/order-list/order-list.js +++ b/static/components/order-list/order-list.js @@ -67,6 +67,12 @@ async function orderList(path) { label: 'Total', field: 'total' }, + { + name: 'fiat', + align: 'left', + label: 'Fiat', + field: 'fiat' + }, { name: 'paid', align: 'left', @@ -108,13 +114,33 @@ async function orderList(path) { 'YYYY-MM-DD HH:mm' ) }, - productOverview: function (order, productId) { + satBtc(val, showUnit = true) { + return satOrBtc(val, showUnit, true) + }, + formatFiat(value, currency) { + return Math.trunc(value) + ' ' + currency + }, + productName: function (order, productId) { product = order.extra.products.find(p => p.id === productId) if (product) { - return `${product.name} (${product.price} ${order.extra.currency})` + return product.name } return '' }, + productPrice: function (order, productId) { + product = order.extra.products.find(p => p.id === productId) + if (product) { + return `${product.price} ${order.extra.currency}` + } + return '' + }, + orderTotal: function (order) { + return order.items.reduce((t, item) => { + console.log('### t, o', t, item) + product = order.extra.products.find(p => p.id === item.product_id) + return t + item.quantity * product.price + }, 0) + }, getOrders: async function () { try { const ordersPath = this.stallId diff --git a/static/js/utils.js b/static/js/utils.js index 86c4d00..49a585a 100644 --- a/static/js/utils.js +++ b/static/js/utils.js @@ -48,6 +48,16 @@ function isJson(str) { } } +function satOrBtc(val, showUnit = true, showSats = false) { + const value = showSats + ? LNbits.utils.formatSat(val) + : val == 0 + ? 0.0 + : (val / 100000000).toFixed(8) + if (!showUnit) return value + return showSats ? value + ' sat' : value + ' BTC' +} + function timeFromNow(time) { // Get timestamps let unixTime = new Date(time).getTime() From f80a75e90c7636d0877942173d3ea5ad9c26c31b Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 30 Mar 2023 13:59:40 +0300 Subject: [PATCH 16/17] refactor: rename `shipping` to `shipping_id` --- static/components/customer-stall/customer-stall.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/components/customer-stall/customer-stall.js b/static/components/customer-stall/customer-stall.js index 6c054a2..aedbac5 100644 --- a/static/components/customer-stall/customer-stall.js +++ b/static/components/customer-stall/customer-stall.js @@ -185,7 +185,7 @@ async function customerStall(path) { items: Array.from(this.cart.products, p => { return {product_id: p[0], quantity: p[1].quantity} }), - shipping: orderData.shippingzone + shipping_id: orderData.shippingzone } orderObj.id = await hash( [orderData.pubkey, created_at, JSON.stringify(orderObj)].join(':') @@ -269,7 +269,7 @@ async function customerStall(path) { items: Array.from(this.cart.products, p => { return {product_id: p[0], quantity: p[1].quantity} }), - shipping: orderData.shippingzone + shipping_id: orderData.shippingzone } let created_at = Math.floor(Date.now() / 1000) orderObj.id = await hash( From 547c477ce6e1b8c48937a5fae742a002b64e8db3 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Mon, 3 Apr 2023 11:57:55 +0300 Subject: [PATCH 17/17] fix: shipping cost; update UI on new order --- crud.py | 6 +++-- helpers.py | 4 +--- migrations.py | 1 + models.py | 18 +++++++++++---- services.py | 23 +++++++++++++++---- .../customer-stall/customer-stall.js | 5 ++-- .../direct-messages/direct-messages.js | 7 ++++++ static/components/order-list/order-list.html | 7 ++++-- static/components/order-list/order-list.js | 5 ++-- static/js/index.js | 3 +-- templates/nostrmarket/index.html | 1 + 11 files changed, 57 insertions(+), 23 deletions(-) diff --git a/crud.py b/crud.py index a4d5fb7..3be6bf6 100644 --- a/crud.py +++ b/crud.py @@ -393,12 +393,13 @@ async def create_order(merchant_id: str, o: Order) -> Order: address, contact_data, extra_data, - order_items, + order_items, + shipping_id, stall_id, invoice_id, total ) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(event_id) DO NOTHING """, ( @@ -412,6 +413,7 @@ async def create_order(merchant_id: str, o: Order) -> Order: json.dumps(o.contact.dict() if o.contact else {}), json.dumps(o.extra.dict()), json.dumps([i.dict() for i in o.items]), + o.shipping_id, o.stall_id, o.invoice_id, o.total, diff --git a/helpers.py b/helpers.py index a81f5a5..d3c9f93 100644 --- a/helpers.py +++ b/helpers.py @@ -77,8 +77,6 @@ def copy_x(output, x32, y32, data): def order_from_json(s: str) -> Tuple[Optional[Any], Optional[str]]: try: order = json.loads(s) - return ( - (order, s) if (type(order) is dict) and "items" in order else (None, s) - ) + return (order, s) if (type(order) is dict) and "items" in order else (None, s) except ValueError: return None, s diff --git a/migrations.py b/migrations.py index 27d4e21..f928098 100644 --- a/migrations.py +++ b/migrations.py @@ -86,6 +86,7 @@ async def m001_initial(db): order_items TEXT NOT NULL, address TEXT, total REAL NOT NULL, + shipping_id TEXT NOT NULL, stall_id TEXT NOT NULL, invoice_id TEXT NOT NULL, paid BOOLEAN NOT NULL DEFAULT false, diff --git a/models.py b/models.py index e7565eb..47bc91b 100644 --- a/models.py +++ b/models.py @@ -2,7 +2,7 @@ import json import time from abc import abstractmethod from sqlite3 import Row -from typing import List, Optional +from typing import List, Optional, Tuple from pydantic import BaseModel @@ -313,6 +313,8 @@ class OrderExtra(BaseModel): products: List[ProductOverview] currency: str btc_price: str + shipping_cost: float = 0 + shipping_cost_sat: float = 0 @classmethod async def from_products(cls, products: List[Product]): @@ -329,6 +331,7 @@ class PartialOrder(BaseModel): event_created_at: Optional[int] public_key: str merchant_public_key: str + shipping_id: str items: List[OrderItem] contact: Optional[OrderContact] address: Optional[str] @@ -356,20 +359,25 @@ class PartialOrder(BaseModel): f"Order ({self.id}) has products from different stalls" ) - async def total_sats(self, products: List[Product]) -> float: + async def costs_in_sats( + self, products: List[Product], shipping_cost: float + ) -> Tuple[float, float]: product_prices = {} for p in products: product_prices[p.id] = p - amount: float = 0 # todo + product_cost: float = 0 # todo for item in self.items: price = product_prices[item.product_id].price currency = product_prices[item.product_id].config.currency or "sat" if currency != "sat": price = await fiat_amount_as_satoshis(price, currency) - amount += item.quantity * price + product_cost += item.quantity * price - return amount + if currency != "sat": + shipping_cost = await fiat_amount_as_satoshis(shipping_cost, currency) + + return product_cost, shipping_cost class Order(PartialOrder): diff --git a/services.py b/services.py index 46a3aba..827960b 100644 --- a/services.py +++ b/services.py @@ -20,6 +20,7 @@ from .crud import ( get_products_by_ids, get_stalls, get_wallet_for_product, + get_zone, increment_customer_unread_messages, update_customer_profile, update_order_paid_status, @@ -60,8 +61,12 @@ async def create_new_order( merchant.id, [p.product_id for p in data.items] ) data.validate_order_items(products) + shipping_zone = await get_zone(merchant.id, data.shipping_id) + assert shipping_zone, f"Shipping zone not found for order '{data.id}'" - total_amount = await data.total_sats(products) + product_cost_sat, shipping_cost_sat = await data.costs_in_sats( + products, shipping_zone.cost + ) wallet_id = await get_wallet_for_product(data.items[0].product_id) assert wallet_id, "Missing wallet for order `{data.id}`" @@ -75,7 +80,7 @@ async def create_new_order( payment_hash, invoice = await create_invoice( wallet_id=wallet_id, - amount=round(total_amount), + amount=round(product_cost_sat + shipping_cost_sat), memo=f"Order '{data.id}' for pubkey '{data.public_key}'", extra={ "tag": "nostrmarket", @@ -84,12 +89,16 @@ async def create_new_order( }, ) + extra = await OrderExtra.from_products(products) + extra.shipping_cost_sat = shipping_cost_sat + extra.shipping_cost = shipping_zone.cost + order = Order( **data.dict(), stall_id=products[0].stall_id, invoice_id=payment_hash, - total=total_amount, - extra=await OrderExtra.from_products(products), + total=product_cost_sat + shipping_cost_sat, + extra=extra, ) await create_order(merchant.id, order) await websocketUpdater( @@ -99,6 +108,7 @@ async def create_new_order( "type": "new-order", "stallId": products[0].stall_id, "customerPubkey": data.public_key, + "orderId": order.id, } ), ) @@ -312,6 +322,11 @@ async def _handle_dirrect_message( order["event_created_at"] = event_created_at return await _handle_new_order(PartialOrder(**order)) + await websocketUpdater( + merchant_id, + json.dumps({"type": "new-direct-message", "customerPubkey": from_pubkey}), + ) + return None except Exception as ex: logger.warning(ex) diff --git a/static/components/customer-stall/customer-stall.js b/static/components/customer-stall/customer-stall.js index aedbac5..b185d83 100644 --- a/static/components/customer-stall/customer-stall.js +++ b/static/components/customer-stall/customer-stall.js @@ -375,8 +375,9 @@ async function customerStall(path) { this.qrCodeDialog.data.message = json.message return cb() } - let payment_request = json.payment_options.find(o => o.type == 'ln') - .link + let payment_request = json.payment_options.find( + o => o.type == 'ln' + ).link if (!payment_request) return this.loading = false this.qrCodeDialog.data.payment_request = payment_request diff --git a/static/components/direct-messages/direct-messages.js b/static/components/direct-messages/direct-messages.js index a16c151..39bc7d3 100644 --- a/static/components/direct-messages/direct-messages.js +++ b/static/components/direct-messages/direct-messages.js @@ -83,6 +83,13 @@ async function directMessages(path) { LNbits.utils.notifyApiError(error) } }, + handleNewMessage: async function (data) { + if (data.customerPubkey === this.activePublicKey) { + await this.getDirectMessages(this.activePublicKey) + } else { + await this.getCustomers() + } + }, showClientOrders: function () { this.$emit('customer-selected', this.activePublicKey) }, diff --git a/static/components/order-list/order-list.html b/static/components/order-list/order-list.html index 594b3d7..3f02776 100644 --- a/static/components/order-list/order-list.html +++ b/static/components/order-list/order-list.html @@ -141,8 +141,11 @@
-
-
Exchange Rate:
+
+
Exchange Rate (1 BTC):
{ - console.log('### t, o', t, item) product = order.extra.products.find(p => p.id === item.product_id) return t + item.quantity * product.price }, 0) diff --git a/static/js/index.js b/static/js/index.js index 29ea63f..2a8c6be 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -113,9 +113,7 @@ const merchant = async () => { const port = location.port ? `:${location.port}` : '' const wsUrl = `${scheme}://${document.domain}${port}/api/v1/ws/${this.merchant.id}` const wsConnection = new WebSocket(wsUrl) - console.log('### waiting for events') wsConnection.onmessage = async e => { - console.log('### e', e) const data = JSON.parse(e.data) if (data.type === 'new-order') { this.$q.notify({ @@ -126,6 +124,7 @@ const merchant = async () => { await this.$refs.orderListRef.addOrder(data) } else if (data.type === 'new-customer') { } else if (data.type === 'new-direct-message') { + await this.$refs.directMessagesRef.handleNewMessage(data) } } } catch (error) { diff --git a/templates/nostrmarket/index.html b/templates/nostrmarket/index.html index 3cf3f96..9064c0c 100644 --- a/templates/nostrmarket/index.html +++ b/templates/nostrmarket/index.html @@ -148,6 +148,7 @@