+
m.message)
)
this.focusOnChatBox(this.messages.length - 1)
-
} catch (error) {
LNbits.utils.notifyApiError(error)
}
@@ -50,14 +49,16 @@ async function directMessages(path) {
this.messages = this.messages.concat([data])
console.log('### this.messages', this.messages)
this.newMessage = ''
- this.focusOnChatBox(this.messages.length - 1)
+ this.focusOnChatBox(this.messages.length - 1)
} catch (error) {
LNbits.utils.notifyApiError(error)
}
},
- focusOnChatBox: function(index) {
+ focusOnChatBox: function (index) {
setTimeout(() => {
- const lastChatBox = document.getElementsByClassName(`chat-mesage-index-${index}`);
+ const lastChatBox = document.getElementsByClassName(
+ `chat-mesage-index-${index}`
+ )
if (lastChatBox && lastChatBox[0]) {
lastChatBox[0].scrollIntoView()
}
diff --git a/templates/nostrmarket/index.html b/templates/nostrmarket/index.html
index fa0d29d..89fc385 100644
--- a/templates/nostrmarket/index.html
+++ b/templates/nostrmarket/index.html
@@ -166,7 +166,6 @@
margin-left: auto;
width: 100%;
}
-
From c0c737378b03b2d2ed94ba2104ba110fd5c10f93 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Wed, 8 Mar 2023 14:42:43 +0200
Subject: [PATCH 08/19] refactor: extract `create_order`
---
services.py | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++
views_api.py | 74 ++----------------------------------------------
2 files changed, 82 insertions(+), 72 deletions(-)
create mode 100644 services.py
diff --git a/services.py b/services.py
new file mode 100644
index 0000000..83f064e
--- /dev/null
+++ b/services.py
@@ -0,0 +1,80 @@
+from typing import Optional
+
+from lnbits.core import create_invoice
+
+from .crud import (
+ get_merchant_for_user,
+ get_order,
+ get_order_by_event_id,
+ get_products_by_ids,
+ get_wallet_for_product,
+)
+from .models import (
+ Nostrable,
+ Order,
+ OrderExtra,
+ PartialOrder,
+ PaymentOption,
+ PaymentRequest,
+)
+from .nostr.event import NostrEvent
+from .nostr.nostr_client import publish_nostr_event
+
+
+async def create_order(user_id: str, data: PartialOrder) -> Optional[PaymentRequest]:
+ if await get_order(user_id, data.id):
+ return None
+ if data.event_id and await get_order_by_event_id(user_id, data.event_id):
+ return None
+
+ merchant = await get_merchant_for_user(user_id)
+ assert merchant, "Cannot find merchant!"
+
+ products = await get_products_by_ids(user_id, [p.product_id for p in data.items])
+ data.validate_order_items(products)
+
+ total_amount = await data.total_sats(products)
+
+ wallet_id = await get_wallet_for_product(data.items[0].product_id)
+ assert wallet_id, "Missing wallet for order `{data.id}`"
+
+ payment_hash, invoice = await create_invoice(
+ wallet_id=wallet_id,
+ amount=round(total_amount),
+ memo=f"Order '{data.id}' for pubkey '{data.pubkey}'",
+ extra={
+ "tag": "nostrmarket",
+ "order_id": data.id,
+ "merchant_pubkey": merchant.public_key,
+ },
+ )
+
+ order = Order(
+ **data.dict(),
+ stall_id=products[0].stall_id,
+ invoice_id=payment_hash,
+ total=total_amount,
+ extra=await OrderExtra.from_products(products),
+ )
+ await create_order(user_id, order)
+
+ return PaymentRequest(
+ id=data.id, payment_options=[PaymentOption(type="ln", link=invoice)]
+ )
+
+
+async def sign_and_send_to_nostr(
+ user_id: str, n: Nostrable, delete=False
+) -> NostrEvent:
+ merchant = await get_merchant_for_user(user_id)
+ assert merchant, "Cannot find merchant!"
+
+ event = (
+ n.to_nostr_delete_event(merchant.public_key)
+ if delete
+ else n.to_nostr_event(merchant.public_key)
+ )
+ event.sig = merchant.sign_hash(bytes.fromhex(event.id))
+ await publish_nostr_event(event)
+
+ return event
diff --git a/views_api.py b/views_api.py
index d3cca1e..f1fd193 100644
--- a/views_api.py
+++ b/views_api.py
@@ -6,7 +6,6 @@ from fastapi import Depends
from fastapi.exceptions import HTTPException
from loguru import logger
-from lnbits.core import create_invoice
from lnbits.decorators import (
WalletTypeInfo,
check_admin,
@@ -20,7 +19,6 @@ from . import nostrmarket_ext, scheduled_tasks
from .crud import (
create_direct_message,
create_merchant,
- create_order,
create_product,
create_stall,
create_zone,
@@ -30,15 +28,12 @@ from .crud import (
get_direct_messages,
get_merchant_for_user,
get_order,
- get_order_by_event_id,
get_orders,
get_orders_for_stall,
get_product,
get_products,
- get_products_by_ids,
get_stall,
get_stalls,
- get_wallet_for_product,
get_zone,
get_zones,
update_order_shipped_status,
@@ -49,9 +44,7 @@ from .crud import (
from .models import (
DirectMessage,
Merchant,
- Nostrable,
Order,
- OrderExtra,
OrderStatusUpdate,
PartialDirectMessage,
PartialMerchant,
@@ -59,14 +52,13 @@ from .models import (
PartialProduct,
PartialStall,
PartialZone,
- PaymentOption,
PaymentRequest,
Product,
Stall,
Zone,
)
-from .nostr.event import NostrEvent
from .nostr.nostr_client import publish_nostr_event
+from .services import create_order
######################################## MERCHANT ########################################
@@ -463,49 +455,7 @@ async def api_create_order(
) -> Optional[PaymentRequest]:
try:
# print("### new order: ", json.dumps(data.dict()))
- if await get_order(wallet.wallet.user, data.id):
- return None
- if data.event_id and await get_order_by_event_id(
- wallet.wallet.user, data.event_id
- ):
- return None
-
- merchant = await get_merchant_for_user(wallet.wallet.user)
- assert merchant, "Cannot find merchant!"
-
- products = await get_products_by_ids(
- wallet.wallet.user, [p.product_id for p in data.items]
- )
- data.validate_order_items(products)
-
- total_amount = await data.total_sats(products)
-
- wallet_id = await get_wallet_for_product(data.items[0].product_id)
- assert wallet_id, "Missing wallet for order `{data.id}`"
-
- payment_hash, invoice = await create_invoice(
- wallet_id=wallet_id,
- amount=round(total_amount),
- memo=f"Order '{data.id}' for pubkey '{data.pubkey}'",
- extra={
- "tag": "nostrmarket",
- "order_id": data.id,
- "merchant_pubkey": merchant.public_key,
- },
- )
-
- order = Order(
- **data.dict(),
- stall_id=products[0].stall_id,
- invoice_id=payment_hash,
- total=total_amount,
- extra=await OrderExtra.from_products(products),
- )
- await create_order(wallet.wallet.user, order)
-
- return PaymentRequest(
- id=data.id, payment_options=[PaymentOption(type="ln", link=invoice)]
- )
+ return await create_order(wallet.wallet.user, data)
except Exception as ex:
logger.warning(ex)
raise HTTPException(
@@ -642,23 +592,3 @@ async def api_stop(wallet: WalletTypeInfo = Depends(check_admin)):
logger.warning(ex)
return {"success": True}
-
-
-######################################## HELPERS ########################################
-
-
-async def sign_and_send_to_nostr(
- user_id: str, n: Nostrable, delete=False
-) -> NostrEvent:
- merchant = await get_merchant_for_user(user_id)
- assert merchant, "Cannot find merchant!"
-
- event = (
- n.to_nostr_delete_event(merchant.public_key)
- if delete
- else n.to_nostr_event(merchant.public_key)
- )
- event.sig = merchant.sign_hash(bytes.fromhex(event.id))
- await publish_nostr_event(event)
-
- return event
From 6c6cd861ced0bcf1901be559a54fb594c90292d0 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Wed, 8 Mar 2023 14:45:32 +0200
Subject: [PATCH 09/19] refactor: extract `handle_order_paid`
---
services.py | 25 +++++++++++++++++++++++++
tasks.py | 23 +++--------------------
2 files changed, 28 insertions(+), 20 deletions(-)
diff --git a/services.py b/services.py
index 83f064e..ca32d40 100644
--- a/services.py
+++ b/services.py
@@ -1,18 +1,23 @@
+import json
from typing import Optional
+from loguru import logger
from lnbits.core import create_invoice
from .crud import (
+ get_merchant_by_pubkey,
get_merchant_for_user,
get_order,
get_order_by_event_id,
get_products_by_ids,
get_wallet_for_product,
+ update_order_paid_status,
)
from .models import (
Nostrable,
Order,
OrderExtra,
+ OrderStatusUpdate,
PartialOrder,
PaymentOption,
PaymentRequest,
@@ -78,3 +83,23 @@ async def sign_and_send_to_nostr(
await publish_nostr_event(event)
return event
+
+
+async def handle_order_paid(order_id: str, merchant_pubkey: str):
+ try:
+ order = await update_order_paid_status(order_id, True)
+ assert order, f"Paid order cannot be found. Order id: {order_id}"
+ order_status = OrderStatusUpdate(
+ id=order_id, message="Payment received.", paid=True, shipped=order.shipped
+ )
+
+ merchant = await get_merchant_by_pubkey(merchant_pubkey)
+ assert merchant, f"Merchant cannot be found for order {order_id}"
+ dm_content = json.dumps(
+ order_status.dict(), separators=(",", ":"), ensure_ascii=False
+ )
+
+ dm_event = merchant.build_dm_event(dm_content, order.pubkey)
+ await publish_nostr_event(dm_event)
+ except Exception as ex:
+ logger.warning(ex)
\ No newline at end of file
diff --git a/tasks.py b/tasks.py
index 8123a8b..1b961f6 100644
--- a/tasks.py
+++ b/tasks.py
@@ -3,6 +3,7 @@ import json
from asyncio import Queue
import httpx
+
import websocket
from loguru import logger
from websocket import WebSocketApp
@@ -17,13 +18,12 @@ from .crud import (
get_merchant_by_pubkey,
get_public_keys_for_merchants,
get_wallet_for_product,
- update_order_paid_status,
)
from .helpers import order_from_json
-from .models import OrderStatusUpdate, PartialDirectMessage, PartialOrder
+from .models import PartialDirectMessage, PartialOrder
from .nostr.event import NostrEvent
from .nostr.nostr_client import connect_to_nostrclient_ws, publish_nostr_event
-
+from .services import handle_order_paid
async def wait_for_paid_invoices():
invoice_queue = Queue()
@@ -46,24 +46,7 @@ async def on_invoice_paid(payment: Payment) -> None:
await handle_order_paid(order_id, merchant_pubkey)
-async def handle_order_paid(order_id: str, merchant_pubkey: str):
- try:
- order = await update_order_paid_status(order_id, True)
- assert order, f"Paid order cannot be found. Order id: {order_id}"
- order_status = OrderStatusUpdate(
- id=order_id, message="Payment received.", paid=True, shipped=order.shipped
- )
- merchant = await get_merchant_by_pubkey(merchant_pubkey)
- assert merchant, f"Merchant cannot be found for order {order_id}"
- dm_content = json.dumps(
- order_status.dict(), separators=(",", ":"), ensure_ascii=False
- )
-
- dm_event = merchant.build_dm_event(dm_content, order.pubkey)
- await publish_nostr_event(dm_event)
- except Exception as ex:
- logger.warning(ex)
async def subscribe_to_nostr_client(recieve_event_queue: Queue, send_req_queue: Queue):
From 7f3438d07fcb995801cfb121376c2015d894d094 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Wed, 8 Mar 2023 15:17:24 +0200
Subject: [PATCH 10/19] refactor: clean-up `tasks.py`
---
services.py | 122 +++++++++++++++++++++++++++++++++++++++++++++++--
tasks.py | 127 ++-------------------------------------------------
views_api.py | 4 +-
3 files changed, 126 insertions(+), 127 deletions(-)
diff --git a/services.py b/services.py
index ca32d40..b499900 100644
--- a/services.py
+++ b/services.py
@@ -1,10 +1,14 @@
import json
from typing import Optional
+
+import httpx
from loguru import logger
-from lnbits.core import create_invoice
+from lnbits.core import create_invoice, get_wallet, url_for
from .crud import (
+ create_direct_message,
+ create_order,
get_merchant_by_pubkey,
get_merchant_for_user,
get_order,
@@ -13,11 +17,14 @@ from .crud import (
get_wallet_for_product,
update_order_paid_status,
)
+from .helpers import order_from_json
from .models import (
+ Merchant,
Nostrable,
Order,
OrderExtra,
OrderStatusUpdate,
+ PartialDirectMessage,
PartialOrder,
PaymentOption,
PaymentRequest,
@@ -26,7 +33,9 @@ from .nostr.event import NostrEvent
from .nostr.nostr_client import publish_nostr_event
-async def create_order(user_id: str, data: PartialOrder) -> Optional[PaymentRequest]:
+async def create_new_order(
+ user_id: str, data: PartialOrder
+) -> Optional[PaymentRequest]:
if await get_order(user_id, data.id):
return None
if data.event_id and await get_order_by_event_id(user_id, data.event_id):
@@ -102,4 +111,111 @@ async def handle_order_paid(order_id: str, merchant_pubkey: str):
dm_event = merchant.build_dm_event(dm_content, order.pubkey)
await publish_nostr_event(dm_event)
except Exception as ex:
- logger.warning(ex)
\ No newline at end of file
+ logger.warning(ex)
+
+
+async def process_nostr_message(msg: str):
+ try:
+ type, subscription_id, event = json.loads(msg)
+ subscription_name, public_key = subscription_id.split(":")
+ if type.upper() == "EVENT":
+ event = NostrEvent(**event)
+ if event.kind == 4:
+ await _handle_nip04_message(subscription_name, public_key, event)
+
+ except Exception as ex:
+ logger.warning(ex)
+
+
+async def _handle_nip04_message(
+ subscription_name: str, public_key: str, event: NostrEvent
+):
+ merchant = await get_merchant_by_pubkey(public_key)
+ assert merchant, f"Merchant not found for public key '{public_key}'"
+
+ clear_text_msg = merchant.decrypt_message(event.content, event.pubkey)
+ if subscription_name == "direct-messages-in":
+ await _handle_incoming_dms(event, merchant, clear_text_msg)
+ else:
+ await _handle_outgoing_dms(event, merchant, clear_text_msg)
+
+
+async def _handle_incoming_dms(
+ event: NostrEvent, merchant: Merchant, clear_text_msg: str
+):
+ dm_content = await _handle_dirrect_message(
+ merchant.id, event.pubkey, event.id, event.created_at, clear_text_msg
+ )
+ if dm_content:
+ dm_event = merchant.build_dm_event(dm_content, event.pubkey)
+ await publish_nostr_event(dm_event)
+
+
+async def _handle_outgoing_dms(
+ event: NostrEvent, merchant: Merchant, clear_text_msg: str
+):
+ sent_to = event.tag_values("p")
+ if len(sent_to) != 0:
+ dm = PartialDirectMessage(
+ event_id=event.id,
+ event_created_at=event.created_at,
+ message=clear_text_msg, # exclude if json
+ public_key=sent_to[0],
+ incoming=True,
+ )
+ await create_direct_message(merchant.id, dm)
+
+
+async def _handle_dirrect_message(
+ merchant_id: str, from_pubkey: str, event_id: str, event_created_at: int, msg: str
+) -> Optional[str]:
+ order, text_msg = order_from_json(msg)
+ try:
+ if order:
+ order["pubkey"] = from_pubkey
+ order["event_id"] = event_id
+ order["event_created_at"] = event_created_at
+ return await _handle_new_order(PartialOrder(**order))
+ else:
+ print("### text_msg", text_msg)
+ dm = PartialDirectMessage(
+ event_id=event_id,
+ event_created_at=event_created_at,
+ message=text_msg,
+ public_key=from_pubkey,
+ incoming=True,
+ )
+ await create_direct_message(merchant_id, dm)
+ return None
+ except Exception as ex:
+ logger.warning(ex)
+ return None
+
+
+async def _handle_new_order(order: PartialOrder) -> Optional[str]:
+ ### todo: check that event_id not parsed already
+
+ order.validate_order()
+
+ first_product_id = order.items[0].product_id
+ wallet_id = await get_wallet_for_product(first_product_id)
+ assert wallet_id, f"Cannot find wallet id for product id: {first_product_id}"
+
+ wallet = await get_wallet(wallet_id)
+ assert wallet, f"Cannot find wallet for product id: {first_product_id}"
+
+ market_url = url_for(f"/nostrmarket/api/v1/order", external=True)
+ async with httpx.AsyncClient() as client:
+ resp = await client.post(
+ url=market_url,
+ headers={
+ "X-Api-Key": wallet.adminkey,
+ },
+ json=order.dict(),
+ )
+ resp.raise_for_status()
+ data = resp.json()
+ if data:
+ return json.dumps(data, separators=(",", ":"), ensure_ascii=False)
+
+ return None
diff --git a/tasks.py b/tasks.py
index 1b961f6..c1a20ba 100644
--- a/tasks.py
+++ b/tasks.py
@@ -2,28 +2,17 @@ import asyncio
import json
from asyncio import Queue
-import httpx
-
import websocket
from loguru import logger
from websocket import WebSocketApp
-from lnbits.core import get_wallet
from lnbits.core.models import Payment
-from lnbits.helpers import Optional, url_for
from lnbits.tasks import register_invoice_listener
-from .crud import (
- create_direct_message,
- get_merchant_by_pubkey,
- get_public_keys_for_merchants,
- get_wallet_for_product,
-)
-from .helpers import order_from_json
-from .models import PartialDirectMessage, PartialOrder
-from .nostr.event import NostrEvent
-from .nostr.nostr_client import connect_to_nostrclient_ws, publish_nostr_event
-from .services import handle_order_paid
+from .crud import get_public_keys_for_merchants
+from .nostr.nostr_client import connect_to_nostrclient_ws
+from .services import handle_order_paid, process_nostr_message
+
async def wait_for_paid_invoices():
invoice_queue = Queue()
@@ -46,9 +35,6 @@ async def on_invoice_paid(payment: Payment) -> None:
await handle_order_paid(order_id, merchant_pubkey)
-
-
-
async def subscribe_to_nostr_client(recieve_event_queue: Queue, send_req_queue: Queue):
print("### subscribe_nostrclient_ws")
@@ -91,107 +77,4 @@ async def wait_for_nostr_events(recieve_event_queue: Queue, send_req_queue: Queu
while True:
message = await recieve_event_queue.get()
- await handle_message(message)
-
-
-async def handle_message(msg: str):
- try:
- type, subscription_id, event = json.loads(msg)
- subscription_name, public_key = subscription_id.split(":")
- if type.upper() == "EVENT":
- event = NostrEvent(**event)
- if event.kind == 4:
- await handle_nip04_message(subscription_name, public_key, event)
-
- except Exception as ex:
- logger.warning(ex)
-
-
-async def handle_nip04_message(
- subscription_name: str, public_key: str, event: NostrEvent
-):
- merchant = await get_merchant_by_pubkey(public_key)
- assert merchant, f"Merchant not found for public key '{public_key}'"
-
- clear_text_msg = merchant.decrypt_message(event.content, event.pubkey)
- if subscription_name == "direct-messages-in":
- await handle_incoming_dms(event, merchant, clear_text_msg)
- else:
- await handle_outgoing_dms(event, merchant, clear_text_msg)
-
-
-async def handle_incoming_dms(event, merchant, clear_text_msg):
- dm_content = await handle_dirrect_message(
- merchant.id, event.pubkey, event.id, event.created_at, clear_text_msg
- )
- if dm_content:
- dm_event = merchant.build_dm_event(dm_content, event.pubkey)
- await publish_nostr_event(dm_event)
-
-
-async def handle_outgoing_dms(event, merchant, clear_text_msg):
- sent_to = event.tag_values("p")
- if len(sent_to) != 0:
- dm = PartialDirectMessage(
- event_id=event.id,
- event_created_at=event.created_at,
- message=clear_text_msg, # exclude if json
- public_key=sent_to[0],
- incoming=True,
- )
- await create_direct_message(merchant.id, dm)
-
-
-async def handle_dirrect_message(
- merchant_id: str, from_pubkey: str, event_id: str, event_created_at: int, msg: str
-) -> Optional[str]:
- order, text_msg = order_from_json(msg)
- try:
- if order:
- order["pubkey"] = from_pubkey
- order["event_id"] = event_id
- order["event_created_at"] = event_created_at
- return await handle_new_order(PartialOrder(**order))
- else:
- print("### text_msg", text_msg)
- dm = PartialDirectMessage(
- event_id=event_id,
- event_created_at=event_created_at,
- message=text_msg,
- public_key=from_pubkey,
- incoming=True,
- )
- await create_direct_message(merchant_id, dm)
- return None
- except Exception as ex:
- logger.warning(ex)
- return None
-
-
-async def handle_new_order(order: PartialOrder) -> Optional[str]:
- ### todo: check that event_id not parsed already
-
- order.validate_order()
-
- first_product_id = order.items[0].product_id
- wallet_id = await get_wallet_for_product(first_product_id)
- assert wallet_id, f"Cannot find wallet id for product id: {first_product_id}"
-
- wallet = await get_wallet(wallet_id)
- assert wallet, f"Cannot find wallet for product id: {first_product_id}"
-
- market_url = url_for(f"/nostrmarket/api/v1/order", external=True)
- async with httpx.AsyncClient() as client:
- resp = await client.post(
- url=market_url,
- headers={
- "X-Api-Key": wallet.adminkey,
- },
- json=order.dict(),
- )
- resp.raise_for_status()
- data = resp.json()
- if data:
- return json.dumps(data, separators=(",", ":"), ensure_ascii=False)
-
- return None
+ await process_nostr_message(message)
diff --git a/views_api.py b/views_api.py
index f1fd193..05f077f 100644
--- a/views_api.py
+++ b/views_api.py
@@ -58,7 +58,7 @@ from .models import (
Zone,
)
from .nostr.nostr_client import publish_nostr_event
-from .services import create_order
+from .services import create_new_order, sign_and_send_to_nostr
######################################## MERCHANT ########################################
@@ -455,7 +455,7 @@ async def api_create_order(
) -> Optional[PaymentRequest]:
try:
# print("### new order: ", json.dumps(data.dict()))
- return await create_order(wallet.wallet.user, data)
+ return await create_new_order(wallet.wallet.user, data)
except Exception as ex:
logger.warning(ex)
raise HTTPException(
From bf670c35457e2454e06bf2846b10326ceb1fc11b Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Wed, 8 Mar 2023 15:36:50 +0200
Subject: [PATCH 11/19] refactor: do not go over http to create order
---
crud.py | 2 ++
migrations.py | 6 ++++--
services.py | 19 ++++---------------
views_api.py | 19 +------------------
4 files changed, 11 insertions(+), 35 deletions(-)
diff --git a/crud.py b/crud.py
index 2d89175..a4d513f 100644
--- a/crud.py
+++ b/crud.py
@@ -320,6 +320,7 @@ async def create_order(user_id: str, o: Order) -> Order:
f"""
INSERT INTO nostrmarket.orders (user_id, id, event_id, event_created_at, pubkey, address, contact_data, extra_data, order_items, stall_id, invoice_id, total)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ ON CONFLICT(event_id) DO NOTHING
""",
(
user_id,
@@ -421,6 +422,7 @@ async def create_direct_message(
f"""
INSERT INTO nostrmarket.direct_messages (merchant_id, id, event_id, event_created_at, message, public_key, incoming)
VALUES (?, ?, ?, ?, ?, ?, ?)
+ ON CONFLICT(event_id) DO NOTHING
""",
(
merchant_id,
diff --git a/migrations.py b/migrations.py
index 6b3c3c5..c52dea3 100644
--- a/migrations.py
+++ b/migrations.py
@@ -89,7 +89,8 @@ async def m001_initial(db):
invoice_id TEXT NOT NULL,
paid BOOLEAN NOT NULL DEFAULT false,
shipped BOOLEAN NOT NULL DEFAULT false,
- time TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}
+ time TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
+ UNIQUE(event_id)
);
"""
)
@@ -107,7 +108,8 @@ async def m001_initial(db):
message TEXT NOT NULL,
public_key TEXT NOT NULL,
incoming BOOLEAN NOT NULL DEFAULT false,
- time TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}
+ time TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
+ UNIQUE(event_id)
);
"""
)
diff --git a/services.py b/services.py
index b499900..93de86d 100644
--- a/services.py
+++ b/services.py
@@ -1,10 +1,9 @@
import json
from typing import Optional
-import httpx
from loguru import logger
-from lnbits.core import create_invoice, get_wallet, url_for
+from lnbits.core import create_invoice, get_wallet
from .crud import (
create_direct_message,
@@ -204,18 +203,8 @@ async def _handle_new_order(order: PartialOrder) -> Optional[str]:
wallet = await get_wallet(wallet_id)
assert wallet, f"Cannot find wallet for product id: {first_product_id}"
- market_url = url_for(f"/nostrmarket/api/v1/order", external=True)
- async with httpx.AsyncClient() as client:
- resp = await client.post(
- url=market_url,
- headers={
- "X-Api-Key": wallet.adminkey,
- },
- json=order.dict(),
- )
- resp.raise_for_status()
- data = resp.json()
- if data:
- return json.dumps(data, separators=(",", ":"), ensure_ascii=False)
+ new_order = await create_new_order(wallet.user, order)
+ if new_order:
+ return json.dumps(new_order.dict(), separators=(",", ":"), ensure_ascii=False)
return None
diff --git a/views_api.py b/views_api.py
index 05f077f..b654e0c 100644
--- a/views_api.py
+++ b/views_api.py
@@ -48,17 +48,15 @@ from .models import (
OrderStatusUpdate,
PartialDirectMessage,
PartialMerchant,
- PartialOrder,
PartialProduct,
PartialStall,
PartialZone,
- PaymentRequest,
Product,
Stall,
Zone,
)
from .nostr.nostr_client import publish_nostr_event
-from .services import create_new_order, sign_and_send_to_nostr
+from .services import sign_and_send_to_nostr
######################################## MERCHANT ########################################
@@ -449,21 +447,6 @@ async def api_delete_product(
######################################## ORDERS ########################################
-@nostrmarket_ext.post("/api/v1/order")
-async def api_create_order(
- data: PartialOrder, wallet: WalletTypeInfo = Depends(require_admin_key)
-) -> Optional[PaymentRequest]:
- try:
- # print("### new order: ", json.dumps(data.dict()))
- return await create_new_order(wallet.wallet.user, data)
- except Exception as ex:
- logger.warning(ex)
- raise HTTPException(
- status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
- detail="Cannot create order",
- )
-
-
nostrmarket_ext.get("/api/v1/order/{order_id}")
From 69dcbcb002223916cae0314c16c06acbf51c791b Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Wed, 8 Mar 2023 18:33:23 +0200
Subject: [PATCH 12/19] feat: optimize filtering for DMs
---
crud.py | 43 ++++++++++++++++++--
migrations.py | 3 +-
models.py | 3 +-
services.py | 23 ++++++++---
static/components/order-list/order-list.html | 6 +--
tasks.py | 18 ++++++--
views_api.py | 2 +-
7 files changed, 79 insertions(+), 19 deletions(-)
diff --git a/crud.py b/crud.py
index a4d513f..5fbad90 100644
--- a/crud.py
+++ b/crud.py
@@ -318,8 +318,22 @@ async def delete_product(user_id: str, product_id: str) -> None:
async def create_order(user_id: str, o: Order) -> Order:
await db.execute(
f"""
- INSERT INTO nostrmarket.orders (user_id, id, event_id, event_created_at, pubkey, address, contact_data, extra_data, order_items, stall_id, invoice_id, total)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ INSERT INTO nostrmarket.orders (
+ user_id,
+ id,
+ event_id,
+ event_created_at,
+ merchant_public_key,
+ public_key,
+ address,
+ contact_data,
+ extra_data,
+ order_items,
+ stall_id,
+ invoice_id,
+ total
+ )
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(event_id) DO NOTHING
""",
(
@@ -327,7 +341,8 @@ async def create_order(user_id: str, o: Order) -> Order:
o.id,
o.event_id,
o.event_created_at,
- o.pubkey,
+ o.merchant_public_key,
+ o.public_key,
o.address,
json.dumps(o.contact.dict() if o.contact else {}),
json.dumps(o.extra.dict()),
@@ -384,6 +399,17 @@ async def get_orders_for_stall(user_id: str, stall_id: str) -> List[Order]:
return [Order.from_row(row) for row in rows]
+async def get_last_order_time(public_key: str) -> int:
+ row = await db.fetchone(
+ """
+ SELECT event_created_at FROM nostrmarket.orders
+ WHERE merchant_public_key = ? ORDER BY event_created_at DESC LIMIT 1
+ """,
+ (public_key,),
+ )
+ return row[0] if row else 0
+
+
async def update_order_paid_status(order_id: str, paid: bool) -> Optional[Order]:
await db.execute(
f"UPDATE nostrmarket.orders SET paid = ? WHERE id = ?",
@@ -457,3 +483,14 @@ async def get_direct_messages(merchant_id: str, public_key: str) -> List[DirectM
(merchant_id, public_key),
)
return [DirectMessage.from_row(row) for row in rows]
+
+
+async def get_last_direct_messages_time(public_key: str) -> int:
+ row = await db.fetchone(
+ """
+ SELECT event_created_at FROM nostrmarket.direct_messages
+ WHERE public_key = ? ORDER BY event_created_at DESC LIMIT 1
+ """,
+ (public_key),
+ )
+ return row[0] if row else 0
diff --git a/migrations.py b/migrations.py
index c52dea3..522fa7b 100644
--- a/migrations.py
+++ b/migrations.py
@@ -79,7 +79,8 @@ async def m001_initial(db):
id TEXT PRIMARY KEY,
event_id TEXT,
event_created_at INTEGER NOT NULL,
- pubkey TEXT NOT NULL,
+ public_key TEXT NOT NULL,
+ merchant_public_key TEXT NOT NULL,
contact_data TEXT NOT NULL DEFAULT '{empty_object}',
extra_data TEXT NOT NULL DEFAULT '{empty_object}',
order_items TEXT NOT NULL,
diff --git a/models.py b/models.py
index c76d25b..379d3f8 100644
--- a/models.py
+++ b/models.py
@@ -282,7 +282,8 @@ class PartialOrder(BaseModel):
id: str
event_id: Optional[str]
event_created_at: Optional[int]
- pubkey: str
+ public_key: str
+ merchant_public_key: str
items: List[OrderItem]
contact: Optional[OrderContact]
address: Optional[str]
diff --git a/services.py b/services.py
index 93de86d..5f11739 100644
--- a/services.py
+++ b/services.py
@@ -54,7 +54,7 @@ async def create_new_order(
payment_hash, invoice = await create_invoice(
wallet_id=wallet_id,
amount=round(total_amount),
- memo=f"Order '{data.id}' for pubkey '{data.pubkey}'",
+ memo=f"Order '{data.id}' for pubkey '{data.public_key}'",
extra={
"tag": "nostrmarket",
"order_id": data.id,
@@ -107,7 +107,7 @@ async def handle_order_paid(order_id: str, merchant_pubkey: str):
order_status.dict(), separators=(",", ":"), ensure_ascii=False
)
- dm_event = merchant.build_dm_event(dm_content, order.pubkey)
+ dm_event = merchant.build_dm_event(dm_content, order.public_key)
await publish_nostr_event(dm_event)
except Exception as ex:
logger.warning(ex)
@@ -143,7 +143,12 @@ async def _handle_incoming_dms(
event: NostrEvent, merchant: Merchant, clear_text_msg: str
):
dm_content = await _handle_dirrect_message(
- merchant.id, event.pubkey, event.id, event.created_at, clear_text_msg
+ merchant.id,
+ merchant.public_key,
+ event.pubkey,
+ event.id,
+ event.created_at,
+ clear_text_msg,
)
if dm_content:
dm_event = merchant.build_dm_event(dm_content, event.pubkey)
@@ -166,17 +171,23 @@ async def _handle_outgoing_dms(
async def _handle_dirrect_message(
- merchant_id: str, from_pubkey: str, event_id: str, event_created_at: int, msg: str
+ merchant_id: str,
+ merchant_public_key: str,
+ from_pubkey: str,
+ event_id: str,
+ event_created_at: int,
+ msg: str,
) -> Optional[str]:
order, text_msg = order_from_json(msg)
try:
if order:
- order["pubkey"] = from_pubkey
+ order["public_key"] = from_pubkey
+ order["merchant_public_key"] = merchant_public_key
order["event_id"] = event_id
order["event_created_at"] = event_created_at
return await _handle_new_order(PartialOrder(**order))
else:
- print("### text_msg", text_msg)
+ print("### text_msg", text_msg, event_created_at, event_id)
dm = PartialDirectMessage(
event_id=event_id,
event_created_at=event_created_at,
diff --git a/static/components/order-list/order-list.html b/static/components/order-list/order-list.html
index e629b1f..cdac035 100644
--- a/static/components/order-list/order-list.html
+++ b/static/components/order-list/order-list.html
@@ -43,8 +43,8 @@
>
-
- {{toShortId(props.row.pubkey)}}
+
+ {{toShortId(props.row.public_key)}}
{{formatDate(props.row.time)}}
@@ -115,7 +115,7 @@
dense
readonly
disabled
- v-model.trim="props.row.pubkey"
+ v-model.trim="props.row.public_key"
type="text"
>
diff --git a/tasks.py b/tasks.py
index c1a20ba..7d4d9c4 100644
--- a/tasks.py
+++ b/tasks.py
@@ -9,7 +9,11 @@ from websocket import WebSocketApp
from lnbits.core.models import Payment
from lnbits.tasks import register_invoice_listener
-from .crud import get_public_keys_for_merchants
+from .crud import (
+ get_last_direct_messages_time,
+ get_last_order_time,
+ get_public_keys_for_merchants,
+)
from .nostr.nostr_client import connect_to_nostrclient_ws
from .services import handle_order_paid, process_nostr_message
@@ -68,9 +72,15 @@ async def subscribe_to_nostr_client(recieve_event_queue: Queue, send_req_queue:
async def wait_for_nostr_events(recieve_event_queue: Queue, send_req_queue: Queue):
public_keys = await get_public_keys_for_merchants()
for p in public_keys:
- await send_req_queue.put(
- ["REQ", f"direct-messages-in:{p}", {"kind": 4, "#p": [p]}]
- )
+ last_order_time = await get_last_order_time(p)
+ last_dm_time = await get_last_direct_messages_time(p)
+ since = max(last_order_time, last_dm_time)
+
+ in_messages_filter = {"kind": 4, "#p": [p]}
+ if since != 0:
+ in_messages_filter["since"] = since
+ print("### in_messages_filter", in_messages_filter)
+ await send_req_queue.put(["REQ", f"direct-messages-in:{p}", in_messages_filter])
# await send_req_queue.put(
# ["REQ", f"direct-messages-out:{p}", {"kind": 4, "authors": [p]}]
# )
diff --git a/views_api.py b/views_api.py
index b654e0c..1f9aaee 100644
--- a/views_api.py
+++ b/views_api.py
@@ -500,7 +500,7 @@ async def api_update_order_status(
data.paid = order.paid
dm_content = json.dumps(data.dict(), separators=(",", ":"), ensure_ascii=False)
- dm_event = merchant.build_dm_event(dm_content, order.pubkey)
+ dm_event = merchant.build_dm_event(dm_content, order.public_key)
await publish_nostr_event(dm_event)
return order
From 6c5299fe0583423df0a44a3a25b7d7e7df17d3fb Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Tue, 14 Mar 2023 11:19:32 +0200
Subject: [PATCH 13/19] feat: add merchant-details
---
.../merchant-details/merchant-details.html | 37 ++++++++++++++
.../merchant-details/merchant-details.js | 48 +++++++++++++++++++
2 files changed, 85 insertions(+)
create mode 100644 static/components/merchant-details/merchant-details.html
create mode 100644 static/components/merchant-details/merchant-details.js
diff --git a/static/components/merchant-details/merchant-details.html b/static/components/merchant-details/merchant-details.html
new file mode 100644
index 0000000..0d8e1ec
--- /dev/null
+++ b/static/components/merchant-details/merchant-details.html
@@ -0,0 +1,37 @@
+
+
+
+
+
+ Merchant Profile
+ Edit the merchand name, description, etc
+
+
+
+
+ Show Keys
+ Hide Keys
+ Show merchant public and private keys
+
+
+
+
+ Delete Merchant
+ Delete all stalls, products and orders
+
+
+
+
diff --git a/static/components/merchant-details/merchant-details.js b/static/components/merchant-details/merchant-details.js
new file mode 100644
index 0000000..bc53077
--- /dev/null
+++ b/static/components/merchant-details/merchant-details.js
@@ -0,0 +1,48 @@
+async function merchantDetails(path) {
+ const template = await loadTemplateAsync(path)
+ Vue.component('merchant-details', {
+ name: 'merchant-details',
+ props: ['merchant-id', 'adminkey', 'inkey'],
+ template,
+
+ data: function () {
+ return {
+ showKeys: false
+ }
+ },
+ methods: {
+ toggleMerchantKeys: async function () {
+ this.showKeys = !this.showKeys
+ this.$emit('show-keys', this.showKeys)
+ },
+ deleteMerchant: function () {
+ LNbits.utils
+ .confirmDialog(
+ `
+ Stalls, products and orders will be deleted also!
+ Are you sure you want to delete this merchant?
+ `
+ )
+ .onOk(async () => {
+ try {
+ await LNbits.api.request(
+ 'DELETE',
+ '/nostrmarket/api/v1/merchant/' + this.merchantId,
+ this.adminkey
+ )
+ this.$emit('merchant-deleted', this.merchantId)
+ this.$q.notify({
+ type: 'positive',
+ message: 'Merchant Deleted',
+ timeout: 5000
+ })
+ } catch (error) {
+ console.warn(error)
+ LNbits.utils.notifyApiError(error)
+ }
+ })
+ }
+ },
+ created: async function () {}
+ })
+}
From 152fe5baabdc21aebe56b3536935b22a5b588f16 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Tue, 14 Mar 2023 11:19:53 +0200
Subject: [PATCH 14/19] feat: add merchant-details
---
.../shipping-zones/shipping-zones.html | 1 +
static/js/index.js | 6 ++++++
templates/nostrmarket/index.html | 18 ++++++++----------
3 files changed, 15 insertions(+), 10 deletions(-)
diff --git a/static/components/shipping-zones/shipping-zones.html b/static/components/shipping-zones/shipping-zones.html
index cedaca7..04f5650 100644
--- a/static/components/shipping-zones/shipping-zones.html
+++ b/static/components/shipping-zones/shipping-zones.html
@@ -4,6 +4,7 @@
unelevated
color="primary"
icon="public"
+ label="Zones"
@click="openZoneDialog()"
>
diff --git a/static/js/index.js b/static/js/index.js
index cfb214c..435cfd8 100644
--- a/static/js/index.js
+++ b/static/js/index.js
@@ -7,6 +7,9 @@ const merchant = async () => {
await stallList('static/components/stall-list/stall-list.html')
await orderList('static/components/order-list/order-list.html')
await directMessages('static/components/direct-messages/direct-messages.html')
+ await merchantDetails(
+ 'static/components/merchant-details/merchant-details.html'
+ )
const nostr = window.NostrTools
@@ -52,6 +55,9 @@ const merchant = async () => {
showImportKeysDialog: async function () {
this.importKeyDialog.show = true
},
+ toggleMerchantKeys: function (value) {
+ this.showKeys = value
+ },
createMerchant: async function (privateKey) {
try {
const pubkey = nostr.getPublicKey(privateKey)
diff --git a/templates/nostrmarket/index.html b/templates/nostrmarket/index.html
index 89fc385..641f642 100644
--- a/templates/nostrmarket/index.html
+++ b/templates/nostrmarket/index.html
@@ -61,6 +61,13 @@
+
+
+
-
-
- Show Public and Private keys
-
-
@@ -179,6 +176,7 @@
+
{% endblock %}
From 3e0b480f0a573a741001632a6443c66dbaf35eed Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Tue, 14 Mar 2023 12:36:42 +0200
Subject: [PATCH 15/19] feat: user `merchant_id` instead of `user_id`
---
__init__.py | 4 +
crud.py | 174 +++++++++++--------
helpers.py | 2 +
migrations.py | 14 +-
nostr/nostr_client.py | 2 +-
services.py | 45 ++---
static/components/stall-list/stall-list.html | 2 +-
tasks.py | 15 +-
templates/nostrmarket/index.html | 78 ++++-----
views_api.py | 137 +++++++++++----
10 files changed, 286 insertions(+), 187 deletions(-)
diff --git a/__init__.py b/__init__.py
index b4f7af0..4a2d0e7 100644
--- a/__init__.py
+++ b/__init__.py
@@ -42,9 +42,13 @@ from .views_api import * # noqa
def nostrmarket_start():
async def _subscribe_to_nostr_client():
+ # wait for 'nostrclient' extension to initialize
+ await asyncio.sleep(10)
await subscribe_to_nostr_client(recieve_event_queue, send_req_queue)
async def _wait_for_nostr_events():
+ # wait for this extension to initialize
+ await asyncio.sleep(5)
await wait_for_nostr_events(recieve_event_queue, send_req_queue)
loop = asyncio.get_event_loop()
diff --git a/crud.py b/crud.py
index 5fbad90..5e56000 100644
--- a/crud.py
+++ b/crud.py
@@ -76,16 +76,16 @@ async def get_merchant_for_user(user_id: str) -> Optional[Merchant]:
######################################## ZONES ########################################
-async def create_zone(user_id: str, data: PartialZone) -> Zone:
+async def create_zone(merchant_id: str, data: PartialZone) -> Zone:
zone_id = urlsafe_short_hash()
await db.execute(
f"""
- INSERT INTO nostrmarket.zones (id, user_id, name, currency, cost, regions)
+ INSERT INTO nostrmarket.zones (id, merchant_id, name, currency, cost, regions)
VALUES (?, ?, ?, ?, ?, ?)
""",
(
zone_id,
- user_id,
+ merchant_id,
data.name,
data.currency,
data.cost,
@@ -93,55 +93,67 @@ async def create_zone(user_id: str, data: PartialZone) -> Zone:
),
)
- zone = await get_zone(user_id, zone_id)
+ zone = await get_zone(merchant_id, zone_id)
assert zone, "Newly created zone couldn't be retrieved"
return zone
-async def update_zone(user_id: str, z: Zone) -> Optional[Zone]:
+async def update_zone(merchant_id: str, z: Zone) -> Optional[Zone]:
await db.execute(
- f"UPDATE nostrmarket.zones SET name = ?, cost = ?, regions = ? WHERE id = ? AND user_id = ?",
- (z.name, z.cost, json.dumps(z.countries), z.id, user_id),
+ f"UPDATE nostrmarket.zones SET name = ?, cost = ?, regions = ? WHERE id = ? AND merchant_id = ?",
+ (z.name, z.cost, json.dumps(z.countries), z.id, merchant_id),
)
- return await get_zone(user_id, z.id)
+ return await get_zone(merchant_id, z.id)
-async def get_zone(user_id: str, zone_id: str) -> Optional[Zone]:
+async def get_zone(merchant_id: str, zone_id: str) -> Optional[Zone]:
row = await db.fetchone(
- "SELECT * FROM nostrmarket.zones WHERE user_id = ? AND id = ?",
+ "SELECT * FROM nostrmarket.zones WHERE merchant_id = ? AND id = ?",
(
- user_id,
+ merchant_id,
zone_id,
),
)
return Zone.from_row(row) if row else None
-async def get_zones(user_id: str) -> List[Zone]:
+async def get_zones(merchant_id: str) -> List[Zone]:
rows = await db.fetchall(
- "SELECT * FROM nostrmarket.zones WHERE user_id = ?", (user_id,)
+ "SELECT * FROM nostrmarket.zones WHERE merchant_id = ?", (merchant_id,)
)
return [Zone.from_row(row) for row in rows]
-async def delete_zone(zone_id: str) -> None:
- # todo: add user_id
- await db.execute("DELETE FROM nostrmarket.zones WHERE id = ?", (zone_id,))
+async def delete_zone(merchant_id: str, zone_id: str) -> None:
+
+ await db.execute(
+ "DELETE FROM nostrmarket.zones WHERE merchant_id = ? AND id = ?",
+ (
+ merchant_id,
+ zone_id,
+ ),
+ )
+
+
+async def delete_merchant_zones(merchant_id: str) -> None:
+ await db.execute(
+ "DELETE FROM nostrmarket.zones WHERE merchant_id = ?", (merchant_id,)
+ )
######################################## STALL ########################################
-async def create_stall(user_id: str, data: PartialStall) -> Stall:
+async def create_stall(merchant_id: str, data: PartialStall) -> Stall:
stall_id = urlsafe_short_hash()
await db.execute(
f"""
- INSERT INTO nostrmarket.stalls (user_id, id, wallet, name, currency, zones, meta)
+ INSERT INTO nostrmarket.stalls (merchant_id, id, wallet, name, currency, zones, meta)
VALUES (?, ?, ?, ?, ?, ?, ?)
""",
(
- user_id,
+ merchant_id,
stall_id,
data.wallet,
data.name,
@@ -153,35 +165,35 @@ async def create_stall(user_id: str, data: PartialStall) -> Stall:
),
)
- stall = await get_stall(user_id, stall_id)
+ stall = await get_stall(merchant_id, stall_id)
assert stall, "Newly created stall couldn't be retrieved"
return stall
-async def get_stall(user_id: str, stall_id: str) -> Optional[Stall]:
+async def get_stall(merchant_id: str, stall_id: str) -> Optional[Stall]:
row = await db.fetchone(
- "SELECT * FROM nostrmarket.stalls WHERE user_id = ? AND id = ?",
+ "SELECT * FROM nostrmarket.stalls WHERE merchant_id = ? AND id = ?",
(
- user_id,
+ merchant_id,
stall_id,
),
)
return Stall.from_row(row) if row else None
-async def get_stalls(user_id: str) -> List[Stall]:
+async def get_stalls(merchant_id: str) -> List[Stall]:
rows = await db.fetchall(
- "SELECT * FROM nostrmarket.stalls WHERE user_id = ?",
- (user_id,),
+ "SELECT * FROM nostrmarket.stalls WHERE merchant_id = ?",
+ (merchant_id,),
)
return [Stall.from_row(row) for row in rows]
-async def update_stall(user_id: str, stall: Stall) -> Optional[Stall]:
+async def update_stall(merchant_id: str, stall: Stall) -> Optional[Stall]:
await db.execute(
f"""
UPDATE nostrmarket.stalls SET wallet = ?, name = ?, currency = ?, zones = ?, meta = ?
- WHERE user_id = ? AND id = ?
+ WHERE merchant_id = ? AND id = ?
""",
(
stall.wallet,
@@ -191,18 +203,18 @@ async def update_stall(user_id: str, stall: Stall) -> Optional[Stall]:
[z.dict() for z in stall.shipping_zones]
), # todo: cost is float. should be int for sats
json.dumps(stall.config.dict()),
- user_id,
+ merchant_id,
stall.id,
),
)
- return await get_stall(user_id, stall.id)
+ return await get_stall(merchant_id, stall.id)
-async def delete_stall(user_id: str, stall_id: str) -> None:
+async def delete_stall(merchant_id: str, stall_id: str) -> None:
await db.execute(
- "DELETE FROM nostrmarket.stalls WHERE user_id =? AND id = ?",
+ "DELETE FROM nostrmarket.stalls WHERE merchant_id =? AND id = ?",
(
- user_id,
+ merchant_id,
stall_id,
),
)
@@ -211,16 +223,16 @@ async def delete_stall(user_id: str, stall_id: str) -> None:
######################################## PRODUCTS ########################################
-async def create_product(user_id: str, data: PartialProduct) -> Product:
+async def create_product(merchant_id: str, data: PartialProduct) -> Product:
product_id = urlsafe_short_hash()
await db.execute(
f"""
- INSERT INTO nostrmarket.products (user_id, id, stall_id, name, image, price, quantity, category_list, meta)
+ INSERT INTO nostrmarket.products (merchant_id, id, stall_id, name, image, price, quantity, category_list, meta)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
- user_id,
+ merchant_id,
product_id,
data.stall_id,
data.name,
@@ -231,18 +243,18 @@ async def create_product(user_id: str, data: PartialProduct) -> Product:
json.dumps(data.config.dict()),
),
)
- product = await get_product(user_id, product_id)
+ product = await get_product(merchant_id, product_id)
assert product, "Newly created product couldn't be retrieved"
return product
-async def update_product(user_id: str, product: Product) -> Product:
+async def update_product(merchant_id: str, product: Product) -> Product:
await db.execute(
f"""
UPDATE nostrmarket.products set name = ?, image = ?, price = ?, quantity = ?, category_list = ?, meta = ?
- WHERE user_id = ? AND id = ?
+ WHERE merchant_id = ? AND id = ?
""",
(
product.name,
@@ -251,40 +263,42 @@ async def update_product(user_id: str, product: Product) -> Product:
product.quantity,
json.dumps(product.categories),
json.dumps(product.config.dict()),
- user_id,
+ merchant_id,
product.id,
),
)
- updated_product = await get_product(user_id, product.id)
+ updated_product = await get_product(merchant_id, product.id)
assert updated_product, "Updated product couldn't be retrieved"
return updated_product
-async def get_product(user_id: str, product_id: str) -> Optional[Product]:
+async def get_product(merchant_id: str, product_id: str) -> Optional[Product]:
row = await db.fetchone(
- "SELECT * FROM nostrmarket.products WHERE user_id =? AND id = ?",
+ "SELECT * FROM nostrmarket.products WHERE merchant_id =? AND id = ?",
(
- user_id,
+ merchant_id,
product_id,
),
)
return Product.from_row(row) if row else None
-async def get_products(user_id: str, stall_id: str) -> List[Product]:
+async def get_products(merchant_id: str, stall_id: str) -> List[Product]:
rows = await db.fetchall(
- "SELECT * FROM nostrmarket.products WHERE user_id = ? AND stall_id = ?",
- (user_id, stall_id),
+ "SELECT * FROM nostrmarket.products WHERE merchant_id = ? AND stall_id = ?",
+ (merchant_id, stall_id),
)
return [Product.from_row(row) for row in rows]
-async def get_products_by_ids(user_id: str, product_ids: List[str]) -> List[Product]:
+async def get_products_by_ids(
+ merchant_id: str, product_ids: List[str]
+) -> List[Product]:
q = ",".join(["?"] * len(product_ids))
rows = await db.fetchall(
- f"SELECT id, stall_id, name, price, quantity, category_list, meta FROM nostrmarket.products WHERE user_id = ? AND id IN ({q})",
- (user_id, *product_ids),
+ f"SELECT id, stall_id, name, price, quantity, category_list, meta FROM nostrmarket.products WHERE merchant_id = ? AND id IN ({q})",
+ (merchant_id, *product_ids),
)
return [Product.from_row(row) for row in rows]
@@ -302,11 +316,11 @@ async def get_wallet_for_product(product_id: str) -> Optional[str]:
return row[0] if row else None
-async def delete_product(user_id: str, product_id: str) -> None:
+async def delete_product(merchant_id: str, product_id: str) -> None:
await db.execute(
- "DELETE FROM nostrmarket.products WHERE user_id =? AND id = ?",
+ "DELETE FROM nostrmarket.products WHERE merchant_id =? AND id = ?",
(
- user_id,
+ merchant_id,
product_id,
),
)
@@ -315,11 +329,11 @@ async def delete_product(user_id: str, product_id: str) -> None:
######################################## ORDERS ########################################
-async def create_order(user_id: str, o: Order) -> Order:
+async def create_order(merchant_id: str, o: Order) -> Order:
await db.execute(
f"""
INSERT INTO nostrmarket.orders (
- user_id,
+ merchant_id,
id,
event_id,
event_created_at,
@@ -337,7 +351,7 @@ async def create_order(user_id: str, o: Order) -> Order:
ON CONFLICT(event_id) DO NOTHING
""",
(
- user_id,
+ merchant_id,
o.id,
o.event_id,
o.event_created_at,
@@ -352,47 +366,47 @@ async def create_order(user_id: str, o: Order) -> Order:
o.total,
),
)
- order = await get_order(user_id, o.id)
+ order = await get_order(merchant_id, o.id)
assert order, "Newly created order couldn't be retrieved"
return order
-async def get_order(user_id: str, order_id: str) -> Optional[Order]:
+async def get_order(merchant_id: str, order_id: str) -> Optional[Order]:
row = await db.fetchone(
- "SELECT * FROM nostrmarket.orders WHERE user_id =? AND id = ?",
+ "SELECT * FROM nostrmarket.orders WHERE merchant_id =? AND id = ?",
(
- user_id,
+ merchant_id,
order_id,
),
)
return Order.from_row(row) if row else None
-async def get_order_by_event_id(user_id: str, event_id: str) -> Optional[Order]:
+async def get_order_by_event_id(merchant_id: str, event_id: str) -> Optional[Order]:
row = await db.fetchone(
- "SELECT * FROM nostrmarket.orders WHERE user_id =? AND event_id =?",
+ "SELECT * FROM nostrmarket.orders WHERE merchant_id =? AND event_id =?",
(
- user_id,
+ merchant_id,
event_id,
),
)
return Order.from_row(row) if row else None
-async def get_orders(user_id: str) -> List[Order]:
+async def get_orders(merchant_id: str) -> List[Order]:
rows = await db.fetchall(
- "SELECT * FROM nostrmarket.orders WHERE user_id = ? ORDER BY time DESC",
- (user_id,),
+ "SELECT * FROM nostrmarket.orders WHERE merchant_id = ? ORDER BY time DESC",
+ (merchant_id,),
)
return [Order.from_row(row) for row in rows]
-async def get_orders_for_stall(user_id: str, stall_id: str) -> List[Order]:
+async def get_orders_for_stall(merchant_id: str, stall_id: str) -> List[Order]:
rows = await db.fetchall(
- "SELECT * FROM nostrmarket.orders WHERE user_id = ? AND stall_id = ? ORDER BY time DESC",
+ "SELECT * FROM nostrmarket.orders WHERE merchant_id = ? AND stall_id = ? ORDER BY time DESC",
(
- user_id,
+ merchant_id,
stall_id,
),
)
@@ -423,11 +437,11 @@ async def update_order_paid_status(order_id: str, paid: bool) -> Optional[Order]
async def update_order_shipped_status(
- user_id: str, order_id: str, shipped: bool
+ merchant_id: str, order_id: str, shipped: bool
) -> Optional[Order]:
await db.execute(
- f"UPDATE nostrmarket.orders SET shipped = ? WHERE user_id = ? AND id = ?",
- (shipped, user_id, order_id),
+ f"UPDATE nostrmarket.orders SET shipped = ? WHERE merchant_id = ? AND id = ?",
+ (shipped, merchant_id, order_id),
)
row = await db.fetchone(
@@ -460,8 +474,10 @@ async def create_direct_message(
dm.incoming,
),
)
-
- msg = await get_direct_message(merchant_id, dm_id)
+ if dm.event_id:
+ msg = await get_direct_message_by_event_id(merchant_id, dm.event_id)
+ else:
+ msg = await get_direct_message(merchant_id, dm_id)
assert msg, "Newly created dm couldn't be retrieved"
return msg
@@ -476,6 +492,16 @@ async def get_direct_message(merchant_id: str, dm_id: str) -> Optional[DirectMes
)
return DirectMessage.from_row(row) if row else None
+async def get_direct_message_by_event_id(merchant_id: str, event_id: str) -> Optional[DirectMessage]:
+ row = await db.fetchone(
+ "SELECT * FROM nostrmarket.direct_messages WHERE merchant_id = ? AND event_id = ?",
+ (
+ merchant_id,
+ event_id,
+ ),
+ )
+ return DirectMessage.from_row(row) if row else None
+
async def get_direct_messages(merchant_id: str, public_key: str) -> List[DirectMessage]:
rows = await db.fetchall(
diff --git a/helpers.py b/helpers.py
index ee73cd9..d06598d 100644
--- a/helpers.py
+++ b/helpers.py
@@ -16,6 +16,8 @@ def get_shared_secret(privkey: str, pubkey: str):
def decrypt_message(encoded_message: str, encryption_key) -> str:
encoded_data = encoded_message.split("?iv=")
+ if len(encoded_data) == 1:
+ return encoded_data[0]
encoded_content, encoded_iv = encoded_data[0], encoded_data[1]
iv = base64.b64decode(encoded_iv)
diff --git a/migrations.py b/migrations.py
index 522fa7b..530f12e 100644
--- a/migrations.py
+++ b/migrations.py
@@ -18,11 +18,11 @@ async def m001_initial(db):
"""
Initial stalls table.
"""
- # user_id, id, wallet, name, currency, zones, meta
+
await db.execute(
"""
CREATE TABLE nostrmarket.stalls (
- user_id TEXT NOT NULL,
+ merchant_id TEXT NOT NULL,
id TEXT PRIMARY KEY,
wallet TEXT NOT NULL,
name TEXT NOT NULL,
@@ -39,7 +39,7 @@ async def m001_initial(db):
await db.execute(
"""
CREATE TABLE nostrmarket.products (
- user_id TEXT NOT NULL,
+ merchant_id TEXT NOT NULL,
id TEXT PRIMARY KEY,
stall_id TEXT NOT NULL,
name TEXT NOT NULL,
@@ -59,7 +59,7 @@ async def m001_initial(db):
"""
CREATE TABLE nostrmarket.zones (
id TEXT PRIMARY KEY,
- user_id TEXT NOT NULL,
+ merchant_id TEXT NOT NULL,
name TEXT NOT NULL,
currency TEXT NOT NULL,
cost REAL NOT NULL,
@@ -75,7 +75,7 @@ async def m001_initial(db):
await db.execute(
f"""
CREATE TABLE nostrmarket.orders (
- user_id TEXT NOT NULL,
+ merchant_id TEXT NOT NULL,
id TEXT PRIMARY KEY,
event_id TEXT,
event_created_at INTEGER NOT NULL,
@@ -120,8 +120,8 @@ async def m001_initial(db):
Create indexes for message fetching
"""
await db.execute(
- "CREATE INDEX idx_messages_timestamp ON nostrmarket.messages (timestamp DESC)"
+ "CREATE INDEX idx_messages_timestamp ON nostrmarket.direct_messages (time DESC)"
)
await db.execute(
- "CREATE INDEX idx_messages_conversations ON nostrmarket.messages (conversation_id)"
+ "CREATE INDEX idx_event_id ON nostrmarket.direct_messages (event_id)"
)
diff --git a/nostr/nostr_client.py b/nostr/nostr_client.py
index 3e8a47e..a05c4ea 100644
--- a/nostr/nostr_client.py
+++ b/nostr/nostr_client.py
@@ -33,7 +33,7 @@ async def connect_to_nostrclient_ws(
logger.debug(f"Subscribing to websockets for nostrclient extension")
ws = WebSocketApp(
- f"ws://localhost:{settings.port}/nostrclient/api/v1/filters",
+ f"ws://localhost:{settings.port}/nostrclient/api/v1/relay",
on_message=on_message,
on_open=on_open,
on_error=on_error,
diff --git a/services.py b/services.py
index 5f11739..18ff95e 100644
--- a/services.py
+++ b/services.py
@@ -33,17 +33,19 @@ from .nostr.nostr_client import publish_nostr_event
async def create_new_order(
- user_id: str, data: PartialOrder
+ merchant_public_key: str, data: PartialOrder
) -> Optional[PaymentRequest]:
- if await get_order(user_id, data.id):
- return None
- if data.event_id and await get_order_by_event_id(user_id, data.event_id):
- return None
-
- merchant = await get_merchant_for_user(user_id)
+ merchant = await get_merchant_by_pubkey(merchant_public_key)
assert merchant, "Cannot find merchant!"
- products = await get_products_by_ids(user_id, [p.product_id for p in data.items])
+ if await get_order(merchant.id, data.id):
+ return None
+ if data.event_id and await get_order_by_event_id(merchant.id, data.event_id):
+ return None
+
+ products = await get_products_by_ids(
+ merchant.id, [p.product_id for p in data.items]
+ )
data.validate_order_items(products)
total_amount = await data.total_sats(products)
@@ -69,7 +71,7 @@ async def create_new_order(
total=total_amount,
extra=await OrderExtra.from_products(products),
)
- await create_order(user_id, order)
+ await create_order(merchant.id, order)
return PaymentRequest(
id=data.id, payment_options=[PaymentOption(type="ln", link=invoice)]
@@ -77,11 +79,8 @@ async def create_new_order(
async def sign_and_send_to_nostr(
- user_id: str, n: Nostrable, delete=False
+ merchant: Merchant, n: Nostrable, delete=False
) -> NostrEvent:
- merchant = await get_merchant_for_user(user_id)
- assert merchant, "Cannot find merchant!"
-
event = (
n.to_nostr_delete_event(merchant.public_key)
if delete
@@ -115,24 +114,28 @@ async def handle_order_paid(order_id: str, merchant_pubkey: str):
async def process_nostr_message(msg: str):
try:
- type, subscription_id, event = json.loads(msg)
- subscription_name, public_key = subscription_id.split(":")
+ type, *rest= json.loads(msg)
if type.upper() == "EVENT":
+ subscription_id, event = rest
+ subscription_name, merchant_public_key = subscription_id.split(":")
event = NostrEvent(**event)
if event.kind == 4:
- await _handle_nip04_message(subscription_name, public_key, event)
-
+ await _handle_nip04_message(
+ subscription_name, merchant_public_key, event
+ )
+ return
except Exception as ex:
logger.warning(ex)
async def _handle_nip04_message(
- subscription_name: str, public_key: str, event: NostrEvent
+ subscription_name: str, merchant_public_key: str, event: NostrEvent
):
- merchant = await get_merchant_by_pubkey(public_key)
- assert merchant, f"Merchant not found for public key '{public_key}'"
+ merchant = await get_merchant_by_pubkey(merchant_public_key)
+ assert merchant, f"Merchant not found for public key '{merchant_public_key}'"
clear_text_msg = merchant.decrypt_message(event.content, event.pubkey)
+ # print("### clear_text_msg", subscription_name, clear_text_msg)
if subscription_name == "direct-messages-in":
await _handle_incoming_dms(event, merchant, clear_text_msg)
else:
@@ -187,7 +190,7 @@ async def _handle_dirrect_message(
order["event_created_at"] = event_created_at
return await _handle_new_order(PartialOrder(**order))
else:
- print("### text_msg", text_msg, event_created_at, event_id)
+ # print("### text_msg", text_msg, event_created_at, event_id)
dm = PartialDirectMessage(
event_id=event_id,
event_created_at=event_created_at,
diff --git a/static/components/stall-list/stall-list.html b/static/components/stall-list/stall-list.html
index bc6236e..ea738bf 100644
--- a/static/components/stall-list/stall-list.html
+++ b/static/components/stall-list/stall-list.html
@@ -6,7 +6,7 @@
unelevated
color="green"
class="float-left"
- >New StallNew Stall (Store)
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Wellcome to Nostr Market!
In Nostr Market, merchant and customer communicate via NOSTR relays, so
@@ -57,44 +95,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/views_api.py b/views_api.py
index 1f9aaee..4027434 100644
--- a/views_api.py
+++ b/views_api.py
@@ -13,6 +13,7 @@ from lnbits.decorators import (
require_admin_key,
require_invoice_key,
)
+from lnbits.extensions.nostrmarket.helpers import get_shared_secret
from lnbits.utils.exchange_rates import currencies
from . import nostrmarket_ext, scheduled_tasks
@@ -22,6 +23,7 @@ from .crud import (
create_product,
create_stall,
create_zone,
+ delete_merchant_zones,
delete_product,
delete_stall,
delete_zone,
@@ -69,6 +71,7 @@ async def api_create_merchant(
try:
merchant = await create_merchant(wallet.wallet.user, data)
+
return merchant
except Exception as ex:
logger.warning(ex)
@@ -94,13 +97,33 @@ async def api_get_merchant(
)
+@nostrmarket_ext.delete("/api/v1/merchant")
+async def api_delete_merchant(
+ wallet: WalletTypeInfo = Depends(require_admin_key),
+):
+
+ try:
+ merchant = await get_merchant_for_user(wallet.wallet.user)
+ assert merchant, "Merchant cannot be found"
+
+ await delete_merchant_zones(wallet.wallet.user)
+ except Exception as ex:
+ logger.warning(ex)
+ raise HTTPException(
+ status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
+ detail="Cannot get merchant",
+ )
+
+
######################################## ZONES ########################################
@nostrmarket_ext.get("/api/v1/zone")
async def api_get_zones(wallet: WalletTypeInfo = Depends(get_key_type)) -> List[Zone]:
try:
- return await get_zones(wallet.wallet.user)
+ merchant = await get_merchant_for_user(wallet.wallet.user)
+ assert merchant, "Merchant cannot be found"
+ return await get_zones(merchant.id)
except Exception as ex:
logger.warning(ex)
raise HTTPException(
@@ -114,7 +137,9 @@ async def api_create_zone(
data: PartialZone, wallet: WalletTypeInfo = Depends(require_admin_key)
):
try:
- zone = await create_zone(wallet.wallet.user, data)
+ merchant = await get_merchant_for_user(wallet.wallet.user)
+ assert merchant, "Merchant cannot be found"
+ zone = await create_zone(merchant.id, data)
return zone
except Exception as ex:
logger.warning(ex)
@@ -131,7 +156,9 @@ async def api_update_zone(
wallet: WalletTypeInfo = Depends(require_admin_key),
) -> Zone:
try:
- zone = await get_zone(wallet.wallet.user, zone_id)
+ merchant = await get_merchant_for_user(wallet.wallet.user)
+ assert merchant, "Merchant cannot be found"
+ zone = await get_zone(merchant.id, zone_id)
if not zone:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
@@ -153,7 +180,9 @@ async def api_update_zone(
@nostrmarket_ext.delete("/api/v1/zone/{zone_id}")
async def api_delete_zone(zone_id, wallet: WalletTypeInfo = Depends(require_admin_key)):
try:
- zone = await get_zone(wallet.wallet.user, zone_id)
+ merchant = await get_merchant_for_user(wallet.wallet.user)
+ assert merchant, "Merchant cannot be found"
+ zone = await get_zone(merchant.id, zone_id)
if not zone:
raise HTTPException(
@@ -161,7 +190,7 @@ async def api_delete_zone(zone_id, wallet: WalletTypeInfo = Depends(require_admi
detail="Zone does not exist.",
)
- await delete_zone(zone_id)
+ await delete_zone(wallet.wallet.user, zone_id)
except Exception as ex:
logger.warning(ex)
@@ -182,12 +211,14 @@ async def api_create_stall(
try:
data.validate_stall()
- stall = await create_stall(wallet.wallet.user, data=data)
+ merchant = await get_merchant_for_user(wallet.wallet.user)
+ assert merchant, "Merchant cannot be found"
+ stall = await create_stall(merchant.id, data=data)
- event = await sign_and_send_to_nostr(wallet.wallet.user, stall)
+ event = await sign_and_send_to_nostr(merchant, stall)
stall.config.event_id = event.id
- await update_stall(wallet.wallet.user, stall)
+ await update_stall(merchant.id, stall)
return stall
except ValueError as ex:
@@ -211,13 +242,16 @@ async def api_update_stall(
try:
data.validate_stall()
- stall = await update_stall(wallet.wallet.user, data)
+ merchant = await get_merchant_for_user(wallet.wallet.user)
+ assert merchant, "Merchant cannot be found"
+
+ stall = await update_stall(merchant.id, data)
assert stall, "Cannot update stall"
- event = await sign_and_send_to_nostr(wallet.wallet.user, stall)
+ event = await sign_and_send_to_nostr(merchant, stall)
stall.config.event_id = event.id
- await update_stall(wallet.wallet.user, stall)
+ await update_stall(merchant.id, stall)
return stall
except HTTPException as ex:
@@ -238,7 +272,9 @@ async def api_update_stall(
@nostrmarket_ext.get("/api/v1/stall/{stall_id}")
async def api_get_stall(stall_id: str, wallet: WalletTypeInfo = Depends(get_key_type)):
try:
- stall = await get_stall(wallet.wallet.user, stall_id)
+ merchant = await get_merchant_for_user(wallet.wallet.user)
+ assert merchant, "Merchant cannot be found"
+ stall = await get_stall(merchant.id, stall_id)
if not stall:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
@@ -258,7 +294,9 @@ async def api_get_stall(stall_id: str, wallet: WalletTypeInfo = Depends(get_key_
@nostrmarket_ext.get("/api/v1/stall")
async def api_get_stalls(wallet: WalletTypeInfo = Depends(get_key_type)):
try:
- stalls = await get_stalls(wallet.wallet.user)
+ merchant = await get_merchant_for_user(wallet.wallet.user)
+ assert merchant, "Merchant cannot be found"
+ stalls = await get_stalls(merchant.id)
return stalls
except Exception as ex:
logger.warning(ex)
@@ -274,7 +312,9 @@ async def api_get_stall_products(
wallet: WalletTypeInfo = Depends(require_invoice_key),
):
try:
- products = await get_products(wallet.wallet.user, stall_id)
+ merchant = await get_merchant_for_user(wallet.wallet.user)
+ assert merchant, "Merchant cannot be found"
+ products = await get_products(merchant.id, stall_id)
return products
except Exception as ex:
logger.warning(ex)
@@ -290,7 +330,9 @@ async def api_get_stall_orders(
wallet: WalletTypeInfo = Depends(require_invoice_key),
):
try:
- orders = await get_orders_for_stall(wallet.wallet.user, stall_id)
+ 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)
return orders
except Exception as ex:
logger.warning(ex)
@@ -305,19 +347,21 @@ async def api_delete_stall(
stall_id: str, wallet: WalletTypeInfo = Depends(require_admin_key)
):
try:
- stall = await get_stall(wallet.wallet.user, stall_id)
+ merchant = await get_merchant_for_user(wallet.wallet.user)
+ assert merchant, "Merchant cannot be found"
+ stall = await get_stall(merchant.id, stall_id)
if not stall:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail="Stall does not exist.",
)
- await delete_stall(wallet.wallet.user, stall_id)
+ await delete_stall(merchant.id, stall_id)
- event = await sign_and_send_to_nostr(wallet.wallet.user, stall, True)
+ event = await sign_and_send_to_nostr(merchant, stall, True)
stall.config.event_id = event.id
- await update_stall(wallet.wallet.user, stall)
+ await update_stall(merchant.id, stall)
except HTTPException as ex:
raise ex
@@ -339,17 +383,19 @@ async def api_create_product(
) -> Product:
try:
data.validate_product()
+ merchant = await get_merchant_for_user(wallet.wallet.user)
+ assert merchant, "Merchant cannot be found"
- stall = await get_stall(wallet.wallet.user, data.stall_id)
+ stall = await get_stall(merchant.id, data.stall_id)
assert stall, "Stall missing for product"
data.config.currency = stall.currency
- product = await create_product(wallet.wallet.user, data=data)
+ product = await create_product(merchant.id, data=data)
- event = await sign_and_send_to_nostr(wallet.wallet.user, product)
+ event = await sign_and_send_to_nostr(merchant, product)
product.config.event_id = event.id
- await update_product(wallet.wallet.user, product)
+ await update_product(merchant.id, product)
return product
except ValueError as ex:
@@ -376,17 +422,19 @@ async def api_update_product(
raise ValueError("Bad product ID")
product.validate_product()
+ merchant = await get_merchant_for_user(wallet.wallet.user)
+ assert merchant, "Merchant cannot be found"
- stall = await get_stall(wallet.wallet.user, product.stall_id)
+ stall = await get_stall(merchant.id, product.stall_id)
assert stall, "Stall missing for product"
product.config.currency = stall.currency
- product = await update_product(wallet.wallet.user, product)
+ product = await update_product(merchant.id, product)
- event = await sign_and_send_to_nostr(wallet.wallet.user, product)
+ event = await sign_and_send_to_nostr(merchant, product)
product.config.event_id = event.id
- await update_product(wallet.wallet.user, product)
+ await update_product(merchant.id, product)
return product
except ValueError as ex:
@@ -408,7 +456,10 @@ async def api_get_product(
wallet: WalletTypeInfo = Depends(require_invoice_key),
) -> Optional[Product]:
try:
- products = await get_product(wallet.wallet.user, product_id)
+ merchant = await get_merchant_for_user(wallet.wallet.user)
+ assert merchant, "Merchant cannot be found"
+
+ products = await get_product(merchant.id, product_id)
return products
except Exception as ex:
logger.warning(ex)
@@ -424,15 +475,18 @@ async def api_delete_product(
wallet: WalletTypeInfo = Depends(require_admin_key),
):
try:
- product = await get_product(wallet.wallet.user, product_id)
+ merchant = await get_merchant_for_user(wallet.wallet.user)
+ assert merchant, "Merchant cannot be found"
+
+ product = await get_product(merchant.id, product_id)
if not product:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail="Product does not exist.",
)
- await delete_product(wallet.wallet.user, product_id)
- await sign_and_send_to_nostr(wallet.wallet.user, product, True)
+ await delete_product(merchant.id, product_id)
+ await sign_and_send_to_nostr(merchant, product, True)
except HTTPException as ex:
raise ex
@@ -452,7 +506,10 @@ nostrmarket_ext.get("/api/v1/order/{order_id}")
async def api_get_order(order_id: str, wallet: WalletTypeInfo = Depends(get_key_type)):
try:
- order = await get_order(wallet.wallet.user, order_id)
+ merchant = await get_merchant_for_user(wallet.wallet.user)
+ assert merchant, "Merchant cannot be found"
+
+ order = await get_order(merchant.id, order_id)
if not order:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
@@ -472,7 +529,10 @@ 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)):
try:
- orders = await get_orders(wallet.wallet.user)
+ merchant = await get_merchant_for_user(wallet.wallet.user)
+ assert merchant, "Merchant cannot be found"
+
+ orders = await get_orders(merchant.id)
return orders
except Exception as ex:
logger.warning(ex)
@@ -489,12 +549,13 @@ async def api_update_order_status(
) -> Order:
try:
assert data.shipped != None, "Shipped value is required for order"
- order = await update_order_shipped_status(
- wallet.wallet.user, data.id, data.shipped
- )
+ merchant = await get_merchant_for_user(wallet.wallet.user)
+ assert merchant, "Merchant cannot be found"
+
+ order = await update_order_shipped_status(merchant.id, data.id, data.shipped)
assert order, "Cannot find updated order"
- merchant = await get_merchant_for_user(wallet.wallet.user)
+ merchant = await get_merchant_for_user(merchant.id)
assert merchant, f"Merchant cannot be found for order {data.id}"
data.paid = order.paid
@@ -530,7 +591,7 @@ async def api_get_messages(
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
- detail="Cannot get zone",
+ detail="Cannot get direct message",
)
From 90bbc797348a8ec1da39a00c3ab1f2e9b655dbdb Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Tue, 14 Mar 2023 14:22:04 +0200
Subject: [PATCH 16/19] chore: code clean-up
---
services.py | 3 +--
views_api.py | 1 -
2 files changed, 1 insertion(+), 3 deletions(-)
diff --git a/services.py b/services.py
index 18ff95e..1e481ec 100644
--- a/services.py
+++ b/services.py
@@ -9,7 +9,6 @@ from .crud import (
create_direct_message,
create_order,
get_merchant_by_pubkey,
- get_merchant_for_user,
get_order,
get_order_by_event_id,
get_products_by_ids,
@@ -114,7 +113,7 @@ async def handle_order_paid(order_id: str, merchant_pubkey: str):
async def process_nostr_message(msg: str):
try:
- type, *rest= json.loads(msg)
+ type, *rest = json.loads(msg)
if type.upper() == "EVENT":
subscription_id, event = rest
subscription_name, merchant_public_key = subscription_id.split(":")
diff --git a/views_api.py b/views_api.py
index 4027434..09a22d5 100644
--- a/views_api.py
+++ b/views_api.py
@@ -13,7 +13,6 @@ from lnbits.decorators import (
require_admin_key,
require_invoice_key,
)
-from lnbits.extensions.nostrmarket.helpers import get_shared_secret
from lnbits.utils.exchange_rates import currencies
from . import nostrmarket_ext, scheduled_tasks
From f978d9e97f4bf6d04788847434c4cba813519bfa Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Tue, 14 Mar 2023 14:27:13 +0200
Subject: [PATCH 17/19] chore: code format
---
crud.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/crud.py b/crud.py
index 5e56000..747156c 100644
--- a/crud.py
+++ b/crud.py
@@ -492,7 +492,10 @@ async def get_direct_message(merchant_id: str, dm_id: str) -> Optional[DirectMes
)
return DirectMessage.from_row(row) if row else None
-async def get_direct_message_by_event_id(merchant_id: str, event_id: str) -> Optional[DirectMessage]:
+
+async def get_direct_message_by_event_id(
+ merchant_id: str, event_id: str
+) -> Optional[DirectMessage]:
row = await db.fetchone(
"SELECT * FROM nostrmarket.direct_messages WHERE merchant_id = ? AND event_id = ?",
(
From 9931a085660c6b1bf211ecab227edf74fb7a8959 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Tue, 14 Mar 2023 16:00:01 +0200
Subject: [PATCH 18/19] fix: publish events via websocket
---
nostr/nostr_client.py | 36 ++-----------------
.../customer-stall/customer-stall.js | 5 +--
tasks.py | 3 --
3 files changed, 6 insertions(+), 38 deletions(-)
diff --git a/nostr/nostr_client.py b/nostr/nostr_client.py
index a05c4ea..ec0af68 100644
--- a/nostr/nostr_client.py
+++ b/nostr/nostr_client.py
@@ -1,28 +1,18 @@
from threading import Thread
from typing import Callable
-import httpx
from loguru import logger
from websocket import WebSocketApp
from lnbits.app import settings
-from lnbits.helpers import url_for
+from .. import send_req_queue
from .event import NostrEvent
async def publish_nostr_event(e: NostrEvent):
- url = url_for("/nostrclient/api/v1/publish", external=True)
- data = dict(e)
- print("### published", dict(data))
- async with httpx.AsyncClient() as client:
- try:
- await client.post(
- url,
- json=data,
- )
- except Exception as ex:
- logger.warning(ex)
+ print('### publish_nostr_event', e.dict())
+ await send_req_queue.put(["EVENT", e.dict()])
async def connect_to_nostrclient_ws(
@@ -44,23 +34,3 @@ async def connect_to_nostrclient_ws(
wst.start()
return ws
-
-
-# async def handle_event(event, pubkeys):
-# tags = [t[1] for t in event["tags"] if t[0] == "p"]
-# to_merchant = None
-# if tags and len(tags) > 0:
-# to_merchant = tags[0]
-
-# if event["pubkey"] in pubkeys or to_merchant in pubkeys:
-# logger.debug(f"Event sent to {to_merchant}")
-# pubkey = to_merchant if to_merchant in pubkeys else event["pubkey"]
-# # Send event to market extension
-# await send_event_to_market(event=event, pubkey=pubkey)
-
-
-# async def send_event_to_market(event: dict, pubkey: str):
-# # Sends event to market extension, for decrypt and handling
-# market_url = url_for(f"/market/api/v1/nip04/{pubkey}", external=True)
-# async with httpx.AsyncClient() as client:
-# await client.post(url=market_url, json=event)
diff --git a/static/components/customer-stall/customer-stall.js b/static/components/customer-stall/customer-stall.js
index d6a114a..d0b2d32 100644
--- a/static/components/customer-stall/customer-stall.js
+++ b/static/components/customer-stall/customer-stall.js
@@ -345,8 +345,9 @@ async function customerStall(path) {
let json = JSON.parse(text)
if (json.id != this.activeOrder) return
if (json.payment_options) {
- 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/tasks.py b/tasks.py
index 3919c73..57cc36f 100644
--- a/tasks.py
+++ b/tasks.py
@@ -40,8 +40,6 @@ async def on_invoice_paid(payment: Payment) -> None:
async def subscribe_to_nostr_client(recieve_event_queue: Queue, send_req_queue: Queue):
- print("### subscribe_nostrclient_ws")
-
def on_open(_):
logger.info("Connected to 'nostrclient' websocket")
@@ -68,7 +66,6 @@ async def subscribe_to_nostr_client(recieve_event_queue: Queue, send_req_queue:
async def wait_for_nostr_events(recieve_event_queue: Queue, send_req_queue: Queue):
- print("### wait_for_nostr_events")
public_keys = await get_public_keys_for_merchants()
for p in public_keys:
last_order_time = await get_last_order_time(p)
From 92c083399145971d30d4a9adf80a41ef47b3125d Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Tue, 14 Mar 2023 16:26:04 +0200
Subject: [PATCH 19/19] feat: delete merchant from the local DB
---
crud.py | 37 +++++++++++++++++++++++++++++++-
nostr/nostr_client.py | 2 +-
templates/nostrmarket/index.html | 1 +
views_api.py | 16 ++++++++++++--
4 files changed, 52 insertions(+), 4 deletions(-)
diff --git a/crud.py b/crud.py
index 747156c..f3b7fa6 100644
--- a/crud.py
+++ b/crud.py
@@ -73,6 +73,13 @@ async def get_merchant_for_user(user_id: str) -> Optional[Merchant]:
return Merchant.from_row(row) if row else None
+async def delete_merchants(merchant_id: str) -> None:
+ await db.execute(
+ "DELETE FROM nostrmarket.merchants WHERE id = ?",
+ (merchant_id,),
+ )
+
+
######################################## ZONES ########################################
@@ -220,6 +227,13 @@ async def delete_stall(merchant_id: str, stall_id: str) -> None:
)
+async def delete_merchant_stalls(merchant_id: str) -> None:
+ await db.execute(
+ "DELETE FROM nostrmarket.stalls WHERE merchant_id = ?",
+ (merchant_id,),
+ )
+
+
######################################## PRODUCTS ########################################
@@ -326,6 +340,13 @@ async def delete_product(merchant_id: str, product_id: str) -> None:
)
+async def delete_merchant_products(merchant_id: str) -> None:
+ await db.execute(
+ "DELETE FROM nostrmarket.products WHERE merchant_id = ?",
+ (merchant_id,),
+ )
+
+
######################################## ORDERS ########################################
@@ -451,6 +472,13 @@ async def update_order_shipped_status(
return Order.from_row(row) if row else None
+async def delete_merchant_orders(merchant_id: str) -> None:
+ await db.execute(
+ "DELETE FROM nostrmarket.orders WHERE merchant_id = ?",
+ (merchant_id,),
+ )
+
+
######################################## MESSAGES ########################################L
@@ -520,6 +548,13 @@ async def get_last_direct_messages_time(public_key: str) -> int:
SELECT event_created_at FROM nostrmarket.direct_messages
WHERE public_key = ? ORDER BY event_created_at DESC LIMIT 1
""",
- (public_key),
+ (public_key,),
)
return row[0] if row else 0
+
+
+async def delete_merchant_direct_messages(merchant_id: str) -> None:
+ await db.execute(
+ "DELETE FROM nostrmarket.direct_messages WHERE merchant_id = ?",
+ (merchant_id,),
+ )
diff --git a/nostr/nostr_client.py b/nostr/nostr_client.py
index ec0af68..d5bfd7a 100644
--- a/nostr/nostr_client.py
+++ b/nostr/nostr_client.py
@@ -11,7 +11,7 @@ from .event import NostrEvent
async def publish_nostr_event(e: NostrEvent):
- print('### publish_nostr_event', e.dict())
+ print("### publish_nostr_event", e.dict())
await send_req_queue.put(["EVENT", e.dict()])
diff --git a/templates/nostrmarket/index.html b/templates/nostrmarket/index.html
index ecfad1b..32d0ca0 100644
--- a/templates/nostrmarket/index.html
+++ b/templates/nostrmarket/index.html
@@ -8,6 +8,7 @@