Product delete (#64)
* feat: restore stalls from `nostr` as pending * feat: stall and prod last update time * feat: restore products and stalls as `pending` * feat: show pending stalls * feat: restore stall * feat: restore a stall from nostr * feat: add blank `Restore Product` button * fix: handle no talls to restore case * feat: show restore dialog * feat: allow query for pending products * feat: restore products * chore: code clean-up * fix: last dm and last order query * chore: code clean-up * fix: subscribe for stalls and products on merchant create/restore * feat: add message type to orders * feat: simplify messages; code format * feat: add type to DMs; restore DMs from nostr * fix: parsing ints * fix: hide copy button if invoice not present * fix: do not generate invoice if product not found * feat: order restore: first version * refactor: move some logic into `services` * feat: improve restore UX * fix: too many calls to customer DMs * fix: allow `All` customers filter * fix: ws reconnect on server restart * fix: query for customer profiles only one * fix: unread messages per customer per merchant * fix: disable `user-profile-events` * fix: customer profile is optional * fix: get customers after new message debounced * chore: code clean-up * feat: auto-create zone * feat: fixed ID for default zone * feat: notify order paid
This commit is contained in:
parent
1cb8fe86b1
commit
51c4147e65
17 changed files with 934 additions and 610 deletions
144
crud.py
144
crud.py
|
|
@ -72,12 +72,12 @@ async def get_merchant_by_pubkey(public_key: str) -> Optional[Merchant]:
|
|||
return Merchant.from_row(row) if row else None
|
||||
|
||||
|
||||
async def get_public_keys_for_merchants() -> List[str]:
|
||||
async def get_merchants_ids_with_pubkeys() -> List[str]:
|
||||
rows = await db.fetchall(
|
||||
"""SELECT public_key FROM nostrmarket.merchants""",
|
||||
"""SELECT id, public_key FROM nostrmarket.merchants""",
|
||||
)
|
||||
|
||||
return [row[0] for row in rows]
|
||||
return [(row[0], row[1]) for row in rows]
|
||||
|
||||
|
||||
async def get_merchant_for_user(user_id: str) -> Optional[Merchant]:
|
||||
|
|
@ -100,7 +100,7 @@ async def delete_merchant(merchant_id: str) -> None:
|
|||
|
||||
|
||||
async def create_zone(merchant_id: str, data: PartialZone) -> Zone:
|
||||
zone_id = urlsafe_short_hash()
|
||||
zone_id = data.id or urlsafe_short_hash()
|
||||
await db.execute(
|
||||
f"""
|
||||
INSERT INTO nostrmarket.zones (id, merchant_id, name, currency, cost, regions)
|
||||
|
|
@ -168,12 +168,14 @@ async def delete_merchant_zones(merchant_id: str) -> None:
|
|||
|
||||
|
||||
async def create_stall(merchant_id: str, data: PartialStall) -> Stall:
|
||||
stall_id = urlsafe_short_hash()
|
||||
stall_id = data.id or urlsafe_short_hash()
|
||||
|
||||
await db.execute(
|
||||
f"""
|
||||
INSERT INTO nostrmarket.stalls (merchant_id, id, wallet, name, currency, zones, meta)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
INSERT INTO nostrmarket.stalls
|
||||
(merchant_id, id, wallet, name, currency, pending, event_id, event_created_at, zones, meta)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(id) DO NOTHING
|
||||
""",
|
||||
(
|
||||
merchant_id,
|
||||
|
|
@ -181,6 +183,9 @@ async def create_stall(merchant_id: str, data: PartialStall) -> Stall:
|
|||
data.wallet,
|
||||
data.name,
|
||||
data.currency,
|
||||
data.pending,
|
||||
data.event_id,
|
||||
data.event_created_at,
|
||||
json.dumps(
|
||||
[z.dict() for z in data.shipping_zones]
|
||||
), # todo: cost is float. should be int for sats
|
||||
|
|
@ -204,30 +209,42 @@ async def get_stall(merchant_id: str, stall_id: str) -> Optional[Stall]:
|
|||
return Stall.from_row(row) if row else None
|
||||
|
||||
|
||||
async def get_stalls(merchant_id: str) -> List[Stall]:
|
||||
async def get_stalls(merchant_id: str, pending: Optional[bool] = False) -> List[Stall]:
|
||||
rows = await db.fetchall(
|
||||
"SELECT * FROM nostrmarket.stalls WHERE merchant_id = ?",
|
||||
(merchant_id,),
|
||||
"SELECT * FROM nostrmarket.stalls WHERE merchant_id = ? AND pending = ?",
|
||||
(merchant_id, pending,),
|
||||
)
|
||||
return [Stall.from_row(row) for row in rows]
|
||||
|
||||
async def get_last_stall_update_time(merchant_id: str) -> int:
|
||||
row = await db.fetchone(
|
||||
"""
|
||||
SELECT event_created_at FROM nostrmarket.stalls
|
||||
WHERE merchant_id = ? ORDER BY event_created_at DESC LIMIT 1
|
||||
""",
|
||||
(merchant_id,),
|
||||
)
|
||||
return row[0] or 0 if row else 0
|
||||
|
||||
async def update_stall(merchant_id: str, stall: Stall) -> Optional[Stall]:
|
||||
await db.execute(
|
||||
f"""
|
||||
UPDATE nostrmarket.stalls SET wallet = ?, name = ?, currency = ?, zones = ?, meta = ?
|
||||
UPDATE nostrmarket.stalls SET wallet = ?, name = ?, currency = ?, pending = ?, event_id = ?, event_created_at = ?, zones = ?, meta = ?
|
||||
WHERE merchant_id = ? AND id = ?
|
||||
""",
|
||||
(
|
||||
stall.wallet,
|
||||
stall.name,
|
||||
stall.currency,
|
||||
stall.pending,
|
||||
stall.event_id,
|
||||
stall.event_created_at,
|
||||
json.dumps(
|
||||
[z.dict() for z in stall.shipping_zones]
|
||||
), # todo: cost is float. should be int for sats
|
||||
json.dumps(stall.config.dict()),
|
||||
merchant_id,
|
||||
stall.id,
|
||||
stall.id
|
||||
),
|
||||
)
|
||||
return await get_stall(merchant_id, stall.id)
|
||||
|
|
@ -254,12 +271,14 @@ async def delete_merchant_stalls(merchant_id: str) -> None:
|
|||
|
||||
|
||||
async def create_product(merchant_id: str, data: PartialProduct) -> Product:
|
||||
product_id = urlsafe_short_hash()
|
||||
product_id = data.id or urlsafe_short_hash()
|
||||
|
||||
await db.execute(
|
||||
f"""
|
||||
INSERT INTO nostrmarket.products (merchant_id, id, stall_id, name, price, quantity, image_urls, category_list, meta)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
INSERT INTO nostrmarket.products
|
||||
(merchant_id, id, stall_id, name, price, quantity, pending, event_id, event_created_at, image_urls, category_list, meta)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(id) DO NOTHING
|
||||
""",
|
||||
(
|
||||
merchant_id,
|
||||
|
|
@ -268,6 +287,9 @@ async def create_product(merchant_id: str, data: PartialProduct) -> Product:
|
|||
data.name,
|
||||
data.price,
|
||||
data.quantity,
|
||||
data.pending,
|
||||
data.event_id,
|
||||
data.event_created_at,
|
||||
json.dumps(data.images),
|
||||
json.dumps(data.categories),
|
||||
json.dumps(data.config.dict()),
|
||||
|
|
@ -283,13 +305,16 @@ async def update_product(merchant_id: str, product: Product) -> Product:
|
|||
|
||||
await db.execute(
|
||||
f"""
|
||||
UPDATE nostrmarket.products set name = ?, price = ?, quantity = ?, image_urls = ?, category_list = ?, meta = ?
|
||||
UPDATE nostrmarket.products set name = ?, price = ?, quantity = ?, pending = ?, event_id =?, event_created_at = ?, image_urls = ?, category_list = ?, meta = ?
|
||||
WHERE merchant_id = ? AND id = ?
|
||||
""",
|
||||
(
|
||||
product.name,
|
||||
product.price,
|
||||
product.quantity,
|
||||
product.pending,
|
||||
product.event_id,
|
||||
product.event_created_at,
|
||||
json.dumps(product.images),
|
||||
json.dumps(product.categories),
|
||||
json.dumps(product.config.dict()),
|
||||
|
|
@ -328,10 +353,10 @@ async def get_product(merchant_id: str, product_id: str) -> Optional[Product]:
|
|||
return Product.from_row(row) if row else None
|
||||
|
||||
|
||||
async def get_products(merchant_id: str, stall_id: str) -> List[Product]:
|
||||
async def get_products(merchant_id: str, stall_id: str, pending: Optional[bool] = False) -> List[Product]:
|
||||
rows = await db.fetchall(
|
||||
"SELECT * FROM nostrmarket.products WHERE merchant_id = ? AND stall_id = ?",
|
||||
(merchant_id, stall_id),
|
||||
"SELECT * FROM nostrmarket.products WHERE merchant_id = ? AND stall_id = ? AND pending = ?",
|
||||
(merchant_id, stall_id, pending),
|
||||
)
|
||||
return [Product.from_row(row) for row in rows]
|
||||
|
||||
|
|
@ -341,7 +366,11 @@ async def get_products_by_ids(
|
|||
) -> 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 merchant_id = ? AND id IN ({q})",
|
||||
f"""
|
||||
SELECT id, stall_id, name, price, quantity, category_list, meta
|
||||
FROM nostrmarket.products
|
||||
WHERE merchant_id = ? AND pending = false AND id IN ({q})
|
||||
""",
|
||||
(merchant_id, *product_ids),
|
||||
)
|
||||
return [Product.from_row(row) for row in rows]
|
||||
|
|
@ -353,12 +382,21 @@ async def get_wallet_for_product(product_id: str) -> Optional[str]:
|
|||
SELECT s.wallet FROM nostrmarket.products p
|
||||
INNER JOIN nostrmarket.stalls s
|
||||
ON p.stall_id = s.id
|
||||
WHERE p.id=?
|
||||
WHERE p.id = ? AND p.pending = false AND s.pending = false
|
||||
""",
|
||||
(product_id,),
|
||||
)
|
||||
return row[0] if row else None
|
||||
|
||||
async def get_last_product_update_time(merchant_id: str) -> int:
|
||||
row = await db.fetchone(
|
||||
"""
|
||||
SELECT event_created_at FROM nostrmarket.products
|
||||
WHERE merchant_id = ? ORDER BY event_created_at DESC LIMIT 1
|
||||
""",
|
||||
(merchant_id,),
|
||||
)
|
||||
return row[0] or 0 if row else 0
|
||||
|
||||
async def delete_product(merchant_id: str, product_id: str) -> None:
|
||||
await db.execute(
|
||||
|
|
@ -456,7 +494,7 @@ async def get_orders(merchant_id: str, **kwargs) -> List[Order]:
|
|||
q = f"AND {q}"
|
||||
values = (v for v in kwargs.values() if v != None)
|
||||
rows = await db.fetchall(
|
||||
f"SELECT * FROM nostrmarket.orders WHERE merchant_id = ? {q} ORDER BY time DESC",
|
||||
f"SELECT * FROM nostrmarket.orders WHERE merchant_id = ? {q} ORDER BY event_created_at DESC",
|
||||
(merchant_id, *values),
|
||||
)
|
||||
return [Order.from_row(row) for row in rows]
|
||||
|
|
@ -479,17 +517,29 @@ async def get_orders_for_stall(
|
|||
return [Order.from_row(row) for row in rows]
|
||||
|
||||
|
||||
async def get_last_order_time(public_key: str) -> int:
|
||||
async def get_last_order_time(merchant_id: 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
|
||||
WHERE merchant_id = ? ORDER BY event_created_at DESC LIMIT 1
|
||||
""",
|
||||
(public_key,),
|
||||
(merchant_id,),
|
||||
)
|
||||
return row[0] if row else 0
|
||||
|
||||
|
||||
async def update_order(merchant_id: str, order_id: str, **kwargs) -> Optional[Order]:
|
||||
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
|
||||
await db.execute(
|
||||
f"""
|
||||
UPDATE nostrmarket.orders SET {q} WHERE merchant_id = ? and id = ?
|
||||
""",
|
||||
(*kwargs.values(), merchant_id, order_id)
|
||||
)
|
||||
|
||||
return await get_order(merchant_id, order_id)
|
||||
|
||||
|
||||
async def update_order_paid_status(order_id: str, paid: bool) -> Optional[Order]:
|
||||
await db.execute(
|
||||
f"UPDATE nostrmarket.orders SET paid = ? WHERE id = ?",
|
||||
|
|
@ -533,8 +583,8 @@ async def create_direct_message(
|
|||
dm_id = urlsafe_short_hash()
|
||||
await db.execute(
|
||||
f"""
|
||||
INSERT INTO nostrmarket.direct_messages (merchant_id, id, event_id, event_created_at, message, public_key, incoming)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
INSERT INTO nostrmarket.direct_messages (merchant_id, id, event_id, event_created_at, message, public_key, type, incoming)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(event_id) DO NOTHING
|
||||
""",
|
||||
(
|
||||
|
|
@ -544,6 +594,7 @@ async def create_direct_message(
|
|||
dm.event_created_at,
|
||||
dm.message,
|
||||
dm.public_key,
|
||||
dm.type,
|
||||
dm.incoming,
|
||||
),
|
||||
)
|
||||
|
|
@ -586,14 +637,24 @@ async def get_direct_messages(merchant_id: str, public_key: str) -> List[DirectM
|
|||
)
|
||||
return [DirectMessage.from_row(row) for row in rows]
|
||||
|
||||
async def get_orders_from_direct_messages(merchant_id: str) -> List[DirectMessage]:
|
||||
rows = await db.fetchall(
|
||||
"SELECT * FROM nostrmarket.direct_messages WHERE merchant_id = ? AND type >= 0 ORDER BY event_created_at, type",
|
||||
(merchant_id),
|
||||
)
|
||||
return [DirectMessage.from_row(row) for row in rows]
|
||||
|
||||
async def get_last_direct_messages_time(public_key: str) -> int:
|
||||
|
||||
|
||||
|
||||
|
||||
async def get_last_direct_messages_time(merchant_id: 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
|
||||
WHERE merchant_id = ? ORDER BY event_created_at DESC LIMIT 1
|
||||
""",
|
||||
(public_key,),
|
||||
(merchant_id,),
|
||||
)
|
||||
return row[0] if row else 0
|
||||
|
||||
|
|
@ -644,8 +705,13 @@ async def get_customers(merchant_id: str) -> List[Customer]:
|
|||
return [Customer.from_row(row) for row in rows]
|
||||
|
||||
|
||||
async def get_all_customers() -> List[Customer]:
|
||||
rows = await db.fetchall("SELECT * FROM nostrmarket.customers")
|
||||
async def get_all_unique_customers() -> List[Customer]:
|
||||
q = """
|
||||
SELECT public_key, MAX(merchant_id) as merchant_id, MAX(event_created_at)
|
||||
FROM nostrmarket.customers
|
||||
GROUP BY public_key
|
||||
"""
|
||||
rows = await db.fetchall(q)
|
||||
return [Customer.from_row(row) for row in rows]
|
||||
|
||||
|
||||
|
|
@ -658,15 +724,15 @@ async def update_customer_profile(
|
|||
)
|
||||
|
||||
|
||||
async def increment_customer_unread_messages(public_key: str):
|
||||
async def increment_customer_unread_messages(merchant_id: str, public_key: str):
|
||||
await db.execute(
|
||||
f"UPDATE nostrmarket.customers SET unread_messages = unread_messages + 1 WHERE public_key = ?",
|
||||
(public_key,),
|
||||
f"UPDATE nostrmarket.customers SET unread_messages = unread_messages + 1 WHERE merchant_id = ? AND public_key = ?",
|
||||
(merchant_id, public_key,),
|
||||
)
|
||||
|
||||
|
||||
async def update_customer_no_unread_messages(public_key: str):
|
||||
#??? two merchants
|
||||
async def update_customer_no_unread_messages(merchant_id: str, public_key: str):
|
||||
await db.execute(
|
||||
f"UPDATE nostrmarket.customers SET unread_messages = 0 WHERE public_key = ?",
|
||||
(public_key,),
|
||||
f"UPDATE nostrmarket.customers SET unread_messages = 0 WHERE merchant_id =? AND public_key = ?",
|
||||
(merchant_id, public_key,),
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue