commit
d106879a09
17 changed files with 580 additions and 96 deletions
|
|
@ -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()
|
||||
|
|
|
|||
114
crud.py
114
crud.py
|
|
@ -5,6 +5,8 @@ from lnbits.helpers import urlsafe_short_hash
|
|||
|
||||
from . import db
|
||||
from .models import (
|
||||
Customer,
|
||||
CustomerProfile,
|
||||
DirectMessage,
|
||||
Merchant,
|
||||
MerchantConfig,
|
||||
|
|
@ -392,11 +394,12 @@ async def create_order(merchant_id: str, o: Order) -> Order:
|
|||
contact_data,
|
||||
extra_data,
|
||||
order_items,
|
||||
shipping_id,
|
||||
stall_id,
|
||||
invoice_id,
|
||||
total
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(event_id) DO NOTHING
|
||||
""",
|
||||
(
|
||||
|
|
@ -410,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,
|
||||
|
|
@ -443,33 +447,38 @@ 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]
|
||||
|
||||
|
||||
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(
|
||||
"""
|
||||
|
|
@ -596,9 +605,68 @@ 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),
|
||||
######################################## 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_customers(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),
|
||||
)
|
||||
|
||||
|
||||
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,),
|
||||
)
|
||||
return [row[0] for row in rows]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -125,3 +126,18 @@ 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,
|
||||
unread_messages INTEGER NOT NULL DEFAULT 1,
|
||||
meta TEXT NOT NULL DEFAULT '{}'
|
||||
);
|
||||
"""
|
||||
)
|
||||
|
|
|
|||
40
models.py
40
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):
|
||||
|
|
@ -427,3 +435,25 @@ 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]
|
||||
unread_messages: int = 0
|
||||
|
||||
@classmethod
|
||||
def from_row(cls, row: Row) -> "Customer":
|
||||
customer = cls(**dict(row))
|
||||
customer.profile = CustomerProfile(**json.loads(row["meta"]))
|
||||
return customer
|
||||
|
|
|
|||
|
|
@ -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}"])
|
||||
|
|
|
|||
82
services.py
82
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,9 @@ 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,
|
||||
update_product,
|
||||
update_product_quantity,
|
||||
|
|
@ -23,6 +30,7 @@ from .crud import (
|
|||
)
|
||||
from .helpers import order_from_json
|
||||
from .models import (
|
||||
Customer,
|
||||
Merchant,
|
||||
Nostrable,
|
||||
Order,
|
||||
|
|
@ -53,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}`"
|
||||
|
|
@ -68,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",
|
||||
|
|
@ -77,14 +89,29 @@ 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(
|
||||
merchant.id,
|
||||
json.dumps(
|
||||
{
|
||||
"type": "new-order",
|
||||
"stallId": products[0].stall_id,
|
||||
"customerPubkey": data.public_key,
|
||||
"orderId": order.id,
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
return PaymentRequest(
|
||||
id=data.id, payment_options=[PaymentOption(type="ln", link=invoice)]
|
||||
|
|
@ -206,9 +233,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 +264,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
|
||||
):
|
||||
dm_content = await _handle_dirrect_message(
|
||||
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,
|
||||
merchant.public_key,
|
||||
event.pubkey,
|
||||
|
|
@ -243,8 +278,8 @@ 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)
|
||||
|
||||
|
||||
|
|
@ -287,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)
|
||||
|
|
@ -308,3 +348,29 @@ async def _handle_new_order(order: PartialOrder) -> Optional[str]:
|
|||
return json.dumps(new_order.dict(), separators=(",", ":"), ensure_ascii=False)
|
||||
|
||||
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)
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,7 +1,26 @@
|
|||
<div>
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<h6 class="text-subtitle1 q-my-none">Messages</h6>
|
||||
<div class="row">
|
||||
<div class="col-2">
|
||||
<h6 class="text-subtitle1 q-my-none">Messages</h6>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<q-badge v-if="unreadMessages" color="green"
|
||||
><span v-text="unreadMessages"></span> new</q-badge
|
||||
>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<q-btn
|
||||
v-if="activePublicKey"
|
||||
@click="showClientOrders"
|
||||
unelevated
|
||||
outline
|
||||
class="float-right"
|
||||
>Client Orders</q-btn
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-card-section class="q-pa-none">
|
||||
<q-separator></q-separator>
|
||||
|
|
@ -9,7 +28,7 @@
|
|||
<q-card-section>
|
||||
<q-select
|
||||
v-model="activePublicKey"
|
||||
:options="customersPublicKeys.map(k => ({label: `${k.slice(0, 16)}...${k.slice(k.length - 16)}`, value: k}))"
|
||||
:options="customers.map(c => ({label: buildCustomerLabel(c), value: c.public_key}))"
|
||||
label="Select Customer"
|
||||
emit-value
|
||||
@input="selectActiveCustomer()"
|
||||
|
|
|
|||
|
|
@ -2,23 +2,38 @@ async function directMessages(path) {
|
|||
const template = await loadTemplateAsync(path)
|
||||
Vue.component('direct-messages', {
|
||||
name: 'direct-messages',
|
||||
props: ['active-public-key', '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)
|
||||
}
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
customersPublicKeys: [],
|
||||
customers: [],
|
||||
unreadMessages: 0,
|
||||
activePublicKey: null,
|
||||
messages: [],
|
||||
newMessage: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
sendMessage: async function () {},
|
||||
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
|
||||
},
|
||||
getDirectMessages: async function (pubkey) {
|
||||
if (!pubkey) {
|
||||
this.messages = []
|
||||
|
|
@ -31,23 +46,21 @@ 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)
|
||||
}
|
||||
},
|
||||
getCustomersPublicKeys: async function () {
|
||||
getCustomers: async function () {
|
||||
try {
|
||||
const {data} = await LNbits.api.request(
|
||||
'GET',
|
||||
'/nostrmarket/api/v1/customers',
|
||||
this.inkey
|
||||
)
|
||||
this.customersPublicKeys = data
|
||||
this.customers = data
|
||||
this.unreadMessages = data.filter(c => c.unread_messages).length
|
||||
} catch (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
|
|
@ -70,8 +83,19 @@ 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)
|
||||
},
|
||||
selectActiveCustomer: async function () {
|
||||
await this.getDirectMessages(this.activePublicKey)
|
||||
await this.getCustomers()
|
||||
},
|
||||
focusOnChatBox: function (index) {
|
||||
setTimeout(() => {
|
||||
|
|
@ -85,7 +109,7 @@ async function directMessages(path) {
|
|||
}
|
||||
},
|
||||
created: async function () {
|
||||
await this.getCustomersPublicKeys()
|
||||
await this.getCustomers()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,45 @@
|
|||
<div>
|
||||
<div class="row q-mb-md">
|
||||
<div class="col">
|
||||
<div class="col-3 q-pr-lg">
|
||||
<q-select
|
||||
v-model="search.publicKey"
|
||||
:options="customers.map(c => ({label: buildCustomerLabel(c), value: c.public_key}))"
|
||||
label="Customer"
|
||||
emit-value
|
||||
class="text-wrap"
|
||||
>
|
||||
</q-select>
|
||||
</div>
|
||||
<div class="col-3 q-pr-lg">
|
||||
<q-select
|
||||
v-model="search.isPaid"
|
||||
:options="ternaryOptions"
|
||||
label="Paid"
|
||||
emit-value
|
||||
>
|
||||
</q-select>
|
||||
</div>
|
||||
<div class="col-3 q-pr-lg">
|
||||
<q-select
|
||||
v-model="search.isShipped"
|
||||
:options="ternaryOptions"
|
||||
label="Shipped"
|
||||
emit-value
|
||||
>
|
||||
</q-select>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<q-btn
|
||||
unelevated
|
||||
color="secondary"
|
||||
outline
|
||||
icon="refresh"
|
||||
icon="search"
|
||||
@click="getOrders()"
|
||||
class="float-left"
|
||||
>Refresh Orders</q-btn
|
||||
class="float-right"
|
||||
>Search Orders</q-btn
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="row q-mt-md">
|
||||
<div class="col">
|
||||
<q-table
|
||||
flat
|
||||
|
|
@ -36,9 +63,18 @@
|
|||
/>
|
||||
</q-td>
|
||||
|
||||
<q-td key="id" :props="props"> {{toShortId(props.row.id)}} </q-td>
|
||||
<q-td key="total" :props="props"> {{props.row.total}} </q-td>
|
||||
<!-- todo: currency per order -->
|
||||
<q-td key="id" :props="props">
|
||||
{{toShortId(props.row.id)}}
|
||||
<q-badge v-if="props.row.isNew" color="orange">new</q-badge></q-td
|
||||
>
|
||||
<q-td key="total" :props="props">
|
||||
{{satBtc(props.row.total)}}
|
||||
</q-td>
|
||||
<q-td key="fiat" :props="props">
|
||||
<span v-if="props.row.extra.currency !== 'sat'">
|
||||
{{orderTotal(props.row)}} {{props.row.extra.currency}}
|
||||
</span>
|
||||
</q-td>
|
||||
|
||||
<q-td key="paid" :props="props">
|
||||
<q-checkbox
|
||||
|
|
@ -78,7 +114,9 @@
|
|||
<div class="row items-center no-wrap q-mb-md">
|
||||
<div class="col-1">Quantity</div>
|
||||
<div class="col-1"></div>
|
||||
<div class="col-10">Name</div>
|
||||
<div class="col-4">Name</div>
|
||||
<div class="col-2">Price</div>
|
||||
<div class="col-4"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-1"></div>
|
||||
|
|
@ -92,13 +130,34 @@
|
|||
>
|
||||
<div class="col-1">{{item.quantity}}</div>
|
||||
<div class="col-1">x</div>
|
||||
<div class="col-10">
|
||||
{{productOverview(props.row, item.product_id)}}
|
||||
<div class="col-4">
|
||||
{{productName(props.row, item.product_id)}}
|
||||
</div>
|
||||
<div class="col-2">
|
||||
{{productPrice(props.row, item.product_id)}}
|
||||
</div>
|
||||
<div class="col-4"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-1"></div>
|
||||
</div>
|
||||
<div
|
||||
v-if="props.row.extra.currency !== 'sat'"
|
||||
class="row items-center no-wrap q-mb-md q-mt-md"
|
||||
>
|
||||
<div class="col-3 q-pr-lg">Exchange Rate (1 BTC):</div>
|
||||
<div class="col-6 col-sm-8 q-pr-lg">
|
||||
<q-input
|
||||
filled
|
||||
dense
|
||||
readonly
|
||||
disabled
|
||||
:value="formatFiat(props.row.extra.btc_price, props.row.extra.currency)"
|
||||
type="text"
|
||||
></q-input>
|
||||
</div>
|
||||
<div class="col-3 col-sm-1"></div>
|
||||
</div>
|
||||
<div class="row items-center no-wrap q-mb-md q-mt-md">
|
||||
<div class="col-3 q-pr-lg">Order ID:</div>
|
||||
<div class="col-6 col-sm-8 q-pr-lg">
|
||||
|
|
|
|||
|
|
@ -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: [],
|
||||
|
|
@ -12,6 +21,32 @@ async function orderList(path) {
|
|||
shippingMessage: '',
|
||||
showShipDialog: false,
|
||||
filter: '',
|
||||
search: {
|
||||
publicKey: '',
|
||||
isPaid: {
|
||||
label: 'All',
|
||||
id: null
|
||||
},
|
||||
isShipped: {
|
||||
label: 'All',
|
||||
id: null
|
||||
}
|
||||
},
|
||||
customers: [],
|
||||
ternaryOptions: [
|
||||
{
|
||||
label: 'All',
|
||||
id: null
|
||||
},
|
||||
{
|
||||
label: 'Yes',
|
||||
id: 'true'
|
||||
},
|
||||
{
|
||||
label: 'No',
|
||||
id: 'false'
|
||||
}
|
||||
],
|
||||
ordersTable: {
|
||||
columns: [
|
||||
{
|
||||
|
|
@ -23,15 +58,21 @@ async function orderList(path) {
|
|||
{
|
||||
name: 'id',
|
||||
align: 'left',
|
||||
label: 'ID',
|
||||
label: 'Order ID',
|
||||
field: 'id'
|
||||
},
|
||||
{
|
||||
name: 'total',
|
||||
align: 'left',
|
||||
label: 'Total',
|
||||
label: 'Total Sats',
|
||||
field: 'total'
|
||||
},
|
||||
{
|
||||
name: 'fiat',
|
||||
align: 'left',
|
||||
label: 'Total Fiat',
|
||||
field: 'fiat'
|
||||
},
|
||||
{
|
||||
name: 'paid',
|
||||
align: 'left',
|
||||
|
|
@ -73,21 +114,51 @@ 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) => {
|
||||
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
|
||||
? `/stall/order/${this.stallId}`
|
||||
: '/order'
|
||||
? `stall/order/${this.stallId}`
|
||||
: 'order'
|
||||
|
||||
const query = []
|
||||
if (this.search.publicKey) {
|
||||
query.push(`pubkey=${this.search.publicKey}`)
|
||||
}
|
||||
if (this.search.isPaid.id) {
|
||||
query.push(`paid=${this.search.isPaid.id}`)
|
||||
}
|
||||
if (this.search.isShipped.id) {
|
||||
query.push(`shipped=${this.search.isShipped.id}`)
|
||||
}
|
||||
const {data} = await LNbits.api.request(
|
||||
'GET',
|
||||
'/nostrmarket/api/v1' + ordersPath,
|
||||
`/nostrmarket/api/v1/${ordersPath}?${query.join('&')}`,
|
||||
this.inkey
|
||||
)
|
||||
this.orders = data.map(s => ({...s, expanded: false}))
|
||||
|
|
@ -95,6 +166,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, isNew: true}
|
||||
} catch (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
},
|
||||
updateOrderShipped: async function () {
|
||||
this.selectedOrder.shipped = !this.selectedOrder.shipped
|
||||
try {
|
||||
|
|
@ -117,6 +200,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
|
||||
|
|
@ -129,10 +222,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()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ const merchant = async () => {
|
|||
merchant: {},
|
||||
shippingZones: [],
|
||||
activeChatCustomer: '',
|
||||
orderPubkey: null,
|
||||
showKeys: false,
|
||||
importKeyDialog: {
|
||||
show: false,
|
||||
|
|
@ -102,10 +103,43 @@ const merchant = async () => {
|
|||
},
|
||||
customerSelectedForOrder: function (customerPubkey) {
|
||||
this.activeChatCustomer = customerPubkey
|
||||
},
|
||||
filterOrdersForCustomer: function (customerPubkey) {
|
||||
this.orderPubkey = customerPubkey
|
||||
},
|
||||
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)
|
||||
wsConnection.onmessage = async e => {
|
||||
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') {
|
||||
await this.$refs.directMessagesRef.handleNewMessage(data)
|
||||
}
|
||||
}
|
||||
} 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()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
5
tasks.py
5
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)
|
||||
|
|
|
|||
|
|
@ -59,6 +59,21 @@
|
|||
></stall-list>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
<q-card class="q-mt-lg">
|
||||
<q-card-section>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<order-list
|
||||
ref="orderListRef"
|
||||
:adminkey="g.user.wallets[0].adminkey"
|
||||
:inkey="g.user.wallets[0].inkey"
|
||||
:customer-pubkey-filter="orderPubkey"
|
||||
@customer-selected="customerSelectedForOrder"
|
||||
></order-list>
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</div>
|
||||
<q-card v-else>
|
||||
<q-card-section>
|
||||
|
|
@ -133,9 +148,11 @@
|
|||
</div>
|
||||
<div v-if="merchant && merchant.id" class="col-12">
|
||||
<direct-messages
|
||||
:active-public-key="activeChatCustomer"
|
||||
ref="directMessagesRef"
|
||||
:inkey="g.user.wallets[0].inkey"
|
||||
:adminkey="g.user.wallets[0].adminkey"
|
||||
:active-chat-customer="activeChatCustomer"
|
||||
@customer-selected="filterOrdersForCustomer"
|
||||
>
|
||||
</direct-messages>
|
||||
</div>
|
||||
|
|
|
|||
44
views_api.py
44
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
|
||||
|
|
@ -31,6 +31,7 @@ from .crud import (
|
|||
delete_product,
|
||||
delete_stall,
|
||||
delete_zone,
|
||||
get_customers,
|
||||
get_direct_messages,
|
||||
get_merchant_by_pubkey,
|
||||
get_merchant_for_user,
|
||||
|
|
@ -39,12 +40,11 @@ from .crud import (
|
|||
get_orders_for_stall,
|
||||
get_product,
|
||||
get_products,
|
||||
get_public_keys_for_direct_messages,
|
||||
get_public_keys_for_orders,
|
||||
get_stall,
|
||||
get_stalls,
|
||||
get_zone,
|
||||
get_zones,
|
||||
update_customer_no_unread_messages,
|
||||
update_merchant,
|
||||
update_order_shipped_status,
|
||||
update_product,
|
||||
|
|
@ -52,6 +52,7 @@ from .crud import (
|
|||
update_zone,
|
||||
)
|
||||
from .models import (
|
||||
Customer,
|
||||
DirectMessage,
|
||||
Merchant,
|
||||
Order,
|
||||
|
|
@ -447,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(
|
||||
|
|
@ -641,10 +647,10 @@ async def api_delete_product(
|
|||
######################################## ORDERS ########################################
|
||||
|
||||
|
||||
nostrmarket_ext.get("/api/v1/order/{order_id}")
|
||||
|
||||
|
||||
async def api_get_order(order_id: str, wallet: WalletTypeInfo = Depends(get_key_type)):
|
||||
@nostrmarket_ext.get("/api/v1/order/{order_id}")
|
||||
async def api_get_order(
|
||||
order_id: str, wallet: WalletTypeInfo = Depends(require_invoice_key)
|
||||
):
|
||||
try:
|
||||
merchant = await get_merchant_for_user(wallet.wallet.user)
|
||||
assert merchant, "Merchant cannot be found"
|
||||
|
|
@ -672,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(
|
||||
|
|
@ -738,6 +751,7 @@ async def api_get_messages(
|
|||
assert merchant, f"Merchant cannot be found"
|
||||
|
||||
messages = await get_direct_messages(merchant.id, public_key)
|
||||
await update_customer_no_unread_messages(public_key)
|
||||
return messages
|
||||
except AssertionError as ex:
|
||||
raise HTTPException(
|
||||
|
|
@ -785,17 +799,13 @@ async def api_create_message(
|
|||
|
||||
|
||||
@nostrmarket_ext.get("/api/v1/customers")
|
||||
async def api_create_message(
|
||||
async def api_get_customers(
|
||||
wallet: WalletTypeInfo = Depends(get_key_type),
|
||||
) -> 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(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue