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
51
models.py
51
models.py
|
|
@ -1,8 +1,9 @@
|
|||
import json
|
||||
import time
|
||||
from abc import abstractmethod
|
||||
from enum import Enum
|
||||
from sqlite3 import Row
|
||||
from typing import List, Optional, Tuple
|
||||
from typing import Any, List, Optional, Tuple
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
|
@ -120,6 +121,7 @@ class Merchant(PartialMerchant, Nostrable):
|
|||
|
||||
######################################## ZONES ########################################
|
||||
class PartialZone(BaseModel):
|
||||
id: Optional[str]
|
||||
name: Optional[str]
|
||||
currency: str
|
||||
cost: float
|
||||
|
|
@ -140,19 +142,22 @@ class Zone(PartialZone):
|
|||
|
||||
|
||||
class StallConfig(BaseModel):
|
||||
"""Last published nostr event id for this Stall"""
|
||||
|
||||
event_id: Optional[str]
|
||||
image_url: Optional[str]
|
||||
description: Optional[str]
|
||||
|
||||
|
||||
class PartialStall(BaseModel):
|
||||
id: Optional[str]
|
||||
wallet: str
|
||||
name: str
|
||||
currency: str = "sat"
|
||||
shipping_zones: List[Zone] = []
|
||||
config: StallConfig = StallConfig()
|
||||
pending: bool = False
|
||||
|
||||
"""Last published nostr event for this Stall"""
|
||||
event_id: Optional[str]
|
||||
event_created_at: Optional[int]
|
||||
|
||||
def validate_stall(self):
|
||||
for z in self.shipping_zones:
|
||||
|
|
@ -189,7 +194,7 @@ class Stall(PartialStall, Nostrable):
|
|||
pubkey=pubkey,
|
||||
created_at=round(time.time()),
|
||||
kind=5,
|
||||
tags=[["e", self.config.event_id]],
|
||||
tags=[["e", self.event_id]],
|
||||
content=f"Stall '{self.name}' deleted",
|
||||
)
|
||||
delete_event.id = delete_event.event_id
|
||||
|
|
@ -204,24 +209,29 @@ class Stall(PartialStall, Nostrable):
|
|||
return stall
|
||||
|
||||
|
||||
######################################## STALLS ########################################
|
||||
######################################## PRODUCTS ########################################
|
||||
|
||||
|
||||
class ProductConfig(BaseModel):
|
||||
event_id: Optional[str]
|
||||
description: Optional[str]
|
||||
currency: Optional[str]
|
||||
|
||||
|
||||
class PartialProduct(BaseModel):
|
||||
id: Optional[str]
|
||||
stall_id: str
|
||||
name: str
|
||||
categories: List[str] = []
|
||||
images: List[str] = []
|
||||
price: float
|
||||
quantity: int
|
||||
pending: bool = False
|
||||
config: ProductConfig = ProductConfig()
|
||||
|
||||
"""Last published nostr event for this Product"""
|
||||
event_id: Optional[str]
|
||||
event_created_at: Optional[int]
|
||||
|
||||
|
||||
class Product(PartialProduct, Nostrable):
|
||||
id: str
|
||||
|
|
@ -255,7 +265,7 @@ class Product(PartialProduct, Nostrable):
|
|||
pubkey=pubkey,
|
||||
created_at=round(time.time()),
|
||||
kind=5,
|
||||
tags=[["e", self.config.event_id]],
|
||||
tags=[["e", self.event_id]],
|
||||
content=f"Product '{self.name}' deleted",
|
||||
)
|
||||
delete_event.id = delete_event.event_id
|
||||
|
|
@ -300,7 +310,7 @@ class OrderExtra(BaseModel):
|
|||
|
||||
@classmethod
|
||||
async def from_products(cls, products: List[Product]):
|
||||
currency = products[0].config.currency
|
||||
currency = products[0].config.currency if len(products) else "sat"
|
||||
exchange_rate = (
|
||||
(await btc_price(currency)) if currency and currency != "sat" else 1
|
||||
)
|
||||
|
|
@ -401,14 +411,34 @@ class PaymentRequest(BaseModel):
|
|||
######################################## MESSAGE ########################################
|
||||
|
||||
|
||||
|
||||
class DirectMessageType(Enum):
|
||||
"""Various types os direct messages."""
|
||||
PLAIN_TEXT = -1
|
||||
CUSTOMER_ORDER = 0
|
||||
PAYMENT_REQUEST = 1
|
||||
ORDER_PAID_OR_SHIPPED = 2
|
||||
|
||||
class PartialDirectMessage(BaseModel):
|
||||
event_id: Optional[str]
|
||||
event_created_at: Optional[int]
|
||||
message: str
|
||||
public_key: str
|
||||
type: int = DirectMessageType.PLAIN_TEXT.value
|
||||
incoming: bool = False
|
||||
time: Optional[int]
|
||||
|
||||
@classmethod
|
||||
def parse_message(cls, msg) -> Tuple[DirectMessageType, Optional[Any]]:
|
||||
try:
|
||||
msg_json = json.loads(msg)
|
||||
if "type" in msg_json:
|
||||
return DirectMessageType(msg_json["type"]), msg_json
|
||||
|
||||
return DirectMessageType.PLAIN_TEXT, None
|
||||
except Exception:
|
||||
return DirectMessageType.PLAIN_TEXT, None
|
||||
|
||||
|
||||
class DirectMessage(PartialDirectMessage):
|
||||
id: str
|
||||
|
|
@ -419,6 +449,7 @@ class DirectMessage(PartialDirectMessage):
|
|||
return dm
|
||||
|
||||
|
||||
|
||||
######################################## CUSTOMERS ########################################
|
||||
|
||||
|
||||
|
|
@ -437,5 +468,5 @@ class Customer(BaseModel):
|
|||
@classmethod
|
||||
def from_row(cls, row: Row) -> "Customer":
|
||||
customer = cls(**dict(row))
|
||||
customer.profile = CustomerProfile(**json.loads(row["meta"]))
|
||||
customer.profile = CustomerProfile(**json.loads(row["meta"])) if "meta" in row else None
|
||||
return customer
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue