feat: publish event on C_UD for stalls
This commit is contained in:
parent
aba3706a71
commit
5972c44ad1
7 changed files with 305 additions and 40 deletions
47
crud.py
47
crud.py
|
|
@ -1,4 +1,5 @@
|
||||||
import json
|
import json
|
||||||
|
import time
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from lnbits.helpers import urlsafe_short_hash
|
from lnbits.helpers import urlsafe_short_hash
|
||||||
|
|
@ -51,15 +52,7 @@ async def create_zone(user_id: str, data: PartialZone) -> Zone:
|
||||||
zone_id = urlsafe_short_hash()
|
zone_id = urlsafe_short_hash()
|
||||||
await db.execute(
|
await db.execute(
|
||||||
f"""
|
f"""
|
||||||
INSERT INTO nostrmarket.zones (
|
INSERT INTO nostrmarket.zones (id, user_id, name, currency, cost, regions)
|
||||||
id,
|
|
||||||
user_id,
|
|
||||||
name,
|
|
||||||
currency,
|
|
||||||
cost,
|
|
||||||
regions
|
|
||||||
|
|
||||||
)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
|
|
@ -112,6 +105,7 @@ async def delete_zone(zone_id: str) -> None:
|
||||||
|
|
||||||
async def create_stall(user_id: str, data: PartialStall) -> Stall:
|
async def create_stall(user_id: str, data: PartialStall) -> Stall:
|
||||||
stall_id = urlsafe_short_hash()
|
stall_id = urlsafe_short_hash()
|
||||||
|
|
||||||
await db.execute(
|
await db.execute(
|
||||||
f"""
|
f"""
|
||||||
INSERT INTO nostrmarket.stalls (user_id, id, wallet, name, currency, zones, meta)
|
INSERT INTO nostrmarket.stalls (user_id, id, wallet, name, currency, zones, meta)
|
||||||
|
|
@ -123,8 +117,10 @@ async def create_stall(user_id: str, data: PartialStall) -> Stall:
|
||||||
data.wallet,
|
data.wallet,
|
||||||
data.name,
|
data.name,
|
||||||
data.currency,
|
data.currency,
|
||||||
json.dumps(data.shipping_zones),
|
json.dumps(
|
||||||
json.dumps(dict(data.config)),
|
[z.dict() for z in data.shipping_zones]
|
||||||
|
), # todo: cost is float. should be int for sats
|
||||||
|
json.dumps(data.config.dict()),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -152,17 +148,32 @@ async def get_stalls(user_id: str) -> List[Stall]:
|
||||||
return [Stall.from_row(row) for row in rows]
|
return [Stall.from_row(row) for row in rows]
|
||||||
|
|
||||||
|
|
||||||
async def update_stall(user_id: str, stall_id: str, **kwargs) -> Optional[Stall]:
|
async def update_stall(user_id: str, stall: Stall) -> Optional[Stall]:
|
||||||
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
|
|
||||||
await db.execute(
|
await db.execute(
|
||||||
f"UPDATE market.stalls SET {q} WHERE user_id = ? AND id = ?",
|
f"""
|
||||||
(*kwargs.values(), user_id, stall_id),
|
UPDATE nostrmarket.stalls SET wallet = ?, name = ?, currency = ?, zones = ?, meta = ?
|
||||||
|
WHERE user_id = ? AND id = ?
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
stall.wallet,
|
||||||
|
stall.name,
|
||||||
|
stall.currency,
|
||||||
|
json.dumps(
|
||||||
|
[z.dict() for z in stall.shipping_zones]
|
||||||
|
), # todo: cost is float. should be int for sats
|
||||||
|
json.dumps(stall.config.dict()),
|
||||||
|
user_id,
|
||||||
|
stall.id,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
row = await db.fetchone(
|
return await get_stall(user_id, stall.id)
|
||||||
"SELECT * FROM market.stalls WHERE user_id =? AND id = ?",
|
|
||||||
|
|
||||||
|
async def delete_stall(user_id: str, stall_id: str) -> None:
|
||||||
|
await db.execute(
|
||||||
|
"DELETE FROM nostrmarket.stalls WHERE user_id =? AND id = ?",
|
||||||
(
|
(
|
||||||
user_id,
|
user_id,
|
||||||
stall_id,
|
stall_id,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return Stall.from_row(row) if row else None
|
|
||||||
|
|
|
||||||
81
helpers.py
Normal file
81
helpers.py
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
import secrets
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import secp256k1
|
||||||
|
from cffi import FFI
|
||||||
|
from cryptography.hazmat.primitives import padding
|
||||||
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||||
|
|
||||||
|
|
||||||
|
def get_shared_secret(privkey: str, pubkey: str):
|
||||||
|
point = secp256k1.PublicKey(bytes.fromhex("02" + pubkey), True)
|
||||||
|
return point.ecdh(bytes.fromhex(privkey), hashfn=copy_x)
|
||||||
|
|
||||||
|
|
||||||
|
def decrypt_message(encoded_message: str, encryption_key) -> str:
|
||||||
|
encoded_data = encoded_message.split("?iv=")
|
||||||
|
encoded_content, encoded_iv = encoded_data[0], encoded_data[1]
|
||||||
|
|
||||||
|
iv = base64.b64decode(encoded_iv)
|
||||||
|
cipher = Cipher(algorithms.AES(encryption_key), modes.CBC(iv))
|
||||||
|
encrypted_content = base64.b64decode(encoded_content)
|
||||||
|
|
||||||
|
decryptor = cipher.decryptor()
|
||||||
|
decrypted_message = decryptor.update(encrypted_content) + decryptor.finalize()
|
||||||
|
|
||||||
|
unpadder = padding.PKCS7(128).unpadder()
|
||||||
|
unpadded_data = unpadder.update(decrypted_message) + unpadder.finalize()
|
||||||
|
|
||||||
|
return unpadded_data.decode()
|
||||||
|
|
||||||
|
|
||||||
|
def encrypt_message(message: str, encryption_key, iv: Optional[bytes]) -> str:
|
||||||
|
padder = padding.PKCS7(128).padder()
|
||||||
|
padded_data = padder.update(message.encode()) + padder.finalize()
|
||||||
|
|
||||||
|
iv = iv if iv else secrets.token_bytes(16)
|
||||||
|
cipher = Cipher(algorithms.AES(encryption_key), modes.CBC(iv))
|
||||||
|
|
||||||
|
encryptor = cipher.encryptor()
|
||||||
|
encrypted_message = encryptor.update(padded_data) + encryptor.finalize()
|
||||||
|
|
||||||
|
return f"{base64.b64encode(encrypted_message).decode()}?iv={base64.b64encode(iv).decode()}"
|
||||||
|
|
||||||
|
|
||||||
|
def sign_message_hash(private_key: str, hash: bytes) -> str:
|
||||||
|
privkey = secp256k1.PrivateKey(bytes.fromhex(private_key))
|
||||||
|
sig = privkey.schnorr_sign(hash, None, raw=True)
|
||||||
|
return sig.hex()
|
||||||
|
|
||||||
|
|
||||||
|
def test_decrypt_encrypt(encoded_message: str, encryption_key):
|
||||||
|
msg = decrypt_message(encoded_message, encryption_key)
|
||||||
|
|
||||||
|
# ecrypt using the same initialisation vector
|
||||||
|
iv = base64.b64decode(encoded_message.split("?iv=")[1])
|
||||||
|
ecrypted_msg = encrypt_message(msg, encryption_key, iv)
|
||||||
|
assert (
|
||||||
|
encoded_message == ecrypted_msg
|
||||||
|
), f"expected '{encoded_message}', but got '{ecrypted_msg}'"
|
||||||
|
print("### test_decrypt_encrypt", encoded_message == ecrypted_msg)
|
||||||
|
|
||||||
|
|
||||||
|
ffi = FFI()
|
||||||
|
|
||||||
|
|
||||||
|
@ffi.callback(
|
||||||
|
"int (unsigned char *, const unsigned char *, const unsigned char *, void *)"
|
||||||
|
)
|
||||||
|
def copy_x(output, x32, y32, data):
|
||||||
|
ffi.memmove(output, x32, 32)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
def is_json(string: str):
|
||||||
|
try:
|
||||||
|
json.loads(string)
|
||||||
|
except ValueError as e:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
47
models.py
47
models.py
|
|
@ -1,10 +1,13 @@
|
||||||
import json
|
import json
|
||||||
|
import time
|
||||||
from sqlite3 import Row
|
from sqlite3 import Row
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from fastapi import Query
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from .helpers import sign_message_hash
|
||||||
|
from .nostr.event import NostrEvent
|
||||||
|
|
||||||
|
|
||||||
######################################## MERCHANT ########################################
|
######################################## MERCHANT ########################################
|
||||||
class MerchantConfig(BaseModel):
|
class MerchantConfig(BaseModel):
|
||||||
|
|
@ -20,6 +23,9 @@ class PartialMerchant(BaseModel):
|
||||||
class Merchant(PartialMerchant):
|
class Merchant(PartialMerchant):
|
||||||
id: str
|
id: str
|
||||||
|
|
||||||
|
def sign_hash(self, hash: bytes) -> str:
|
||||||
|
return sign_message_hash(self.private_key, hash)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_row(cls, row: Row) -> "Merchant":
|
def from_row(cls, row: Row) -> "Merchant":
|
||||||
merchant = cls(**dict(row))
|
merchant = cls(**dict(row))
|
||||||
|
|
@ -49,24 +55,57 @@ class Zone(PartialZone):
|
||||||
|
|
||||||
|
|
||||||
class StallConfig(BaseModel):
|
class StallConfig(BaseModel):
|
||||||
|
"""Last published nostr event id for this Stall"""
|
||||||
|
|
||||||
|
event_id: Optional[str]
|
||||||
image_url: Optional[str]
|
image_url: Optional[str]
|
||||||
fiat_base_multiplier: int = 1 # todo: reminder wht is this for?
|
description: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
class PartialStall(BaseModel):
|
class PartialStall(BaseModel):
|
||||||
wallet: str
|
wallet: str
|
||||||
name: str
|
name: str
|
||||||
currency: str = "sat"
|
currency: str = "sat"
|
||||||
shipping_zones: List[str] = []
|
shipping_zones: List[PartialZone] = []
|
||||||
config: StallConfig = StallConfig()
|
config: StallConfig = StallConfig()
|
||||||
|
|
||||||
|
|
||||||
class Stall(PartialStall):
|
class Stall(PartialStall):
|
||||||
id: str
|
id: str
|
||||||
|
|
||||||
|
def to_nostr_event(self, pubkey: str) -> NostrEvent:
|
||||||
|
content = {
|
||||||
|
"name": self.name,
|
||||||
|
"description": self.config.description,
|
||||||
|
"currency": self.currency,
|
||||||
|
"shipping": [dict(z) for z in self.shipping_zones],
|
||||||
|
}
|
||||||
|
event = NostrEvent(
|
||||||
|
pubkey=pubkey,
|
||||||
|
created_at=round(time.time()),
|
||||||
|
kind=30005,
|
||||||
|
tags=[["d", self.id]],
|
||||||
|
content=json.dumps(content, separators=(",", ":"), ensure_ascii=False),
|
||||||
|
)
|
||||||
|
event.id = event.event_id
|
||||||
|
|
||||||
|
return event
|
||||||
|
|
||||||
|
def to_nostr_delete_event(self, pubkey: str) -> NostrEvent:
|
||||||
|
delete_event = NostrEvent(
|
||||||
|
pubkey=pubkey,
|
||||||
|
created_at=round(time.time()),
|
||||||
|
kind=5,
|
||||||
|
tags=[["e", self.config.event_id]],
|
||||||
|
content="Stall deleted",
|
||||||
|
)
|
||||||
|
delete_event.id = delete_event.event_id
|
||||||
|
|
||||||
|
return delete_event
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_row(cls, row: Row) -> "Stall":
|
def from_row(cls, row: Row) -> "Stall":
|
||||||
stall = cls(**dict(row))
|
stall = cls(**dict(row))
|
||||||
stall.config = StallConfig(**json.loads(row["meta"]))
|
stall.config = StallConfig(**json.loads(row["meta"]))
|
||||||
stall.shipping_zones = json.loads(row["zones"])
|
stall.shipping_zones = [PartialZone(**z) for z in json.loads(row["zones"])]
|
||||||
return stall
|
return stall
|
||||||
|
|
|
||||||
57
nostr/event.py
Normal file
57
nostr/event.py
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from secp256k1 import PublicKey
|
||||||
|
|
||||||
|
|
||||||
|
class NostrEvent(BaseModel):
|
||||||
|
id: str = ""
|
||||||
|
pubkey: str
|
||||||
|
created_at: int
|
||||||
|
kind: int
|
||||||
|
tags: List[List[str]] = []
|
||||||
|
content: str = ""
|
||||||
|
sig: Optional[str]
|
||||||
|
|
||||||
|
def serialize(self) -> List:
|
||||||
|
return [0, self.pubkey, self.created_at, self.kind, self.tags, self.content]
|
||||||
|
|
||||||
|
def serialize_json(self) -> str:
|
||||||
|
e = self.serialize()
|
||||||
|
return json.dumps(e, separators=(",", ":"), ensure_ascii=False)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def event_id(self) -> str:
|
||||||
|
data = self.serialize_json()
|
||||||
|
id = hashlib.sha256(data.encode()).hexdigest()
|
||||||
|
return id
|
||||||
|
|
||||||
|
def check_signature(self):
|
||||||
|
event_id = self.event_id
|
||||||
|
if self.id != event_id:
|
||||||
|
raise ValueError(
|
||||||
|
f"Invalid event id. Expected: '{event_id}' got '{self.id}'"
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
pub_key = PublicKey(bytes.fromhex("02" + self.pubkey), True)
|
||||||
|
except Exception:
|
||||||
|
raise ValueError(
|
||||||
|
f"Invalid public key: '{self.pubkey}' for event '{self.id}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
valid_signature = pub_key.schnorr_verify(
|
||||||
|
bytes.fromhex(event_id), bytes.fromhex(self.sig), None, raw=True
|
||||||
|
)
|
||||||
|
if not valid_signature:
|
||||||
|
raise ValueError(f"Invalid signature: '{self.sig}' for event '{self.id}'")
|
||||||
|
|
||||||
|
def stringify(self) -> str:
|
||||||
|
return json.dumps(dict(self))
|
||||||
|
|
||||||
|
def tag_values(self, tag_name: str) -> List[str]:
|
||||||
|
return [t[1] for t in self.tags if t[0] == tag_name]
|
||||||
|
|
||||||
|
def has_tag_value(self, tag_name: str, tag_value: str) -> bool:
|
||||||
|
return tag_value in self.tag_values(tag_name)
|
||||||
21
nostr/nostr_client.py
Normal file
21
nostr/nostr_client.py
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import httpx
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
from lnbits.app import settings
|
||||||
|
from lnbits.helpers import url_for
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
@ -30,7 +30,7 @@ async function stallList(path) {
|
||||||
name: this.stallDialog.data.name,
|
name: this.stallDialog.data.name,
|
||||||
wallet: this.stallDialog.data.wallet,
|
wallet: this.stallDialog.data.wallet,
|
||||||
currency: this.stallDialog.data.currency,
|
currency: this.stallDialog.data.currency,
|
||||||
shipping_zones: this.stallDialog.data.shippingZones.map(z => z.id),
|
shipping_zones: this.stallDialog.data.shippingZones,
|
||||||
config: {}
|
config: {}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
@ -71,10 +71,14 @@ async function stallList(path) {
|
||||||
'/nostrmarket/api/v1/zone',
|
'/nostrmarket/api/v1/zone',
|
||||||
this.inkey
|
this.inkey
|
||||||
)
|
)
|
||||||
|
console.log('### zones', data)
|
||||||
this.zoneOptions = data.map(z => ({
|
this.zoneOptions = data.map(z => ({
|
||||||
id: z.id,
|
...z,
|
||||||
label: `${z.name} (${z.countries.join(', ')})`
|
label: z.name
|
||||||
|
? `${z.name} (${z.countries.join(', ')}})`
|
||||||
|
: z.countries.join(', ')
|
||||||
}))
|
}))
|
||||||
|
console.log('### this.zoneOptions', this.zoneOptions)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
LNbits.utils.notifyApiError(error)
|
LNbits.utils.notifyApiError(error)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
82
views_api.py
82
views_api.py
|
|
@ -1,3 +1,4 @@
|
||||||
|
import json
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
|
|
@ -18,6 +19,7 @@ from .crud import (
|
||||||
create_merchant,
|
create_merchant,
|
||||||
create_stall,
|
create_stall,
|
||||||
create_zone,
|
create_zone,
|
||||||
|
delete_stall,
|
||||||
delete_zone,
|
delete_zone,
|
||||||
get_merchant_for_user,
|
get_merchant_for_user,
|
||||||
get_stall,
|
get_stall,
|
||||||
|
|
@ -28,6 +30,7 @@ from .crud import (
|
||||||
update_zone,
|
update_zone,
|
||||||
)
|
)
|
||||||
from .models import Merchant, PartialMerchant, PartialStall, PartialZone, Stall, Zone
|
from .models import Merchant, PartialMerchant, PartialStall, PartialZone, Stall, Zone
|
||||||
|
from .nostr.nostr_client import publish_nostr_event
|
||||||
|
|
||||||
######################################## MERCHANT ########################################
|
######################################## MERCHANT ########################################
|
||||||
|
|
||||||
|
|
@ -148,11 +151,23 @@ async def api_delete_zone(zone_id, wallet: WalletTypeInfo = Depends(require_admi
|
||||||
@nostrmarket_ext.post("/api/v1/stall")
|
@nostrmarket_ext.post("/api/v1/stall")
|
||||||
async def api_create_stall(
|
async def api_create_stall(
|
||||||
data: PartialStall,
|
data: PartialStall,
|
||||||
wallet: WalletTypeInfo = Depends(require_invoice_key),
|
wallet: WalletTypeInfo = Depends(require_admin_key),
|
||||||
):
|
) -> Stall:
|
||||||
try:
|
try:
|
||||||
|
print("### stall", json.dumps(data.dict()))
|
||||||
|
merchant = await get_merchant_for_user(wallet.wallet.user)
|
||||||
|
assert merchant, "Cannot find merchat for stall"
|
||||||
|
|
||||||
stall = await create_stall(wallet.wallet.user, data=data)
|
stall = await create_stall(wallet.wallet.user, data=data)
|
||||||
return stall.dict()
|
|
||||||
|
event = stall.to_nostr_event(merchant.public_key)
|
||||||
|
event.sig = merchant.sign_hash(bytes.fromhex(event.id))
|
||||||
|
await publish_nostr_event(event)
|
||||||
|
|
||||||
|
stall.config.event_id = event.id
|
||||||
|
await update_stall(wallet.wallet.user, stall)
|
||||||
|
|
||||||
|
return stall
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
logger.warning(ex)
|
logger.warning(ex)
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
|
|
@ -164,18 +179,23 @@ async def api_create_stall(
|
||||||
@nostrmarket_ext.put("/api/v1/stall/{stall_id}")
|
@nostrmarket_ext.put("/api/v1/stall/{stall_id}")
|
||||||
async def api_update_stall(
|
async def api_update_stall(
|
||||||
data: Stall,
|
data: Stall,
|
||||||
wallet: WalletTypeInfo = Depends(require_invoice_key),
|
wallet: WalletTypeInfo = Depends(require_admin_key),
|
||||||
):
|
) -> Stall:
|
||||||
try:
|
try:
|
||||||
stall = await get_stall(wallet.wallet.user, data.id)
|
merchant = await get_merchant_for_user(wallet.wallet.user)
|
||||||
if not stall:
|
assert merchant, "Cannot find merchat for stall"
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.NOT_FOUND,
|
event = data.to_nostr_event(merchant.public_key)
|
||||||
detail="Stall does not exist.",
|
event.sig = merchant.sign_hash(bytes.fromhex(event.id))
|
||||||
)
|
|
||||||
stall = await update_stall(wallet.wallet.user, data.id, **data.dict())
|
data.config.event_id = event.id
|
||||||
assert stall, "Cannot fetch updated stall"
|
# data.config.created_at =
|
||||||
return stall.dict()
|
stall = await update_stall(wallet.wallet.user, data)
|
||||||
|
assert stall, "Cannot update stall"
|
||||||
|
|
||||||
|
await publish_nostr_event(event)
|
||||||
|
|
||||||
|
return stall
|
||||||
except HTTPException as ex:
|
except HTTPException as ex:
|
||||||
raise ex
|
raise ex
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
|
|
@ -207,7 +227,7 @@ async def api_get_stall(stall_id: str, wallet: WalletTypeInfo = Depends(get_key_
|
||||||
|
|
||||||
|
|
||||||
@nostrmarket_ext.get("/api/v1/stall")
|
@nostrmarket_ext.get("/api/v1/stall")
|
||||||
async def api_gey_stalls(wallet: WalletTypeInfo = Depends(get_key_type)):
|
async def api_get_stalls(wallet: WalletTypeInfo = Depends(get_key_type)):
|
||||||
try:
|
try:
|
||||||
stalls = await get_stalls(wallet.wallet.user)
|
stalls = await get_stalls(wallet.wallet.user)
|
||||||
return stalls
|
return stalls
|
||||||
|
|
@ -219,6 +239,38 @@ async def api_gey_stalls(wallet: WalletTypeInfo = Depends(get_key_type)):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@nostrmarket_ext.delete("/api/v1/stall/{stall_id}")
|
||||||
|
async def api_delete_stall(
|
||||||
|
stall_id: str, wallet: WalletTypeInfo = Depends(require_admin_key)
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
stall = await get_stall(wallet.wallet.user, stall_id)
|
||||||
|
if not stall:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.NOT_FOUND,
|
||||||
|
detail="Stall does not exist.",
|
||||||
|
)
|
||||||
|
|
||||||
|
merchant = await get_merchant_for_user(wallet.wallet.user)
|
||||||
|
assert merchant, "Cannot find merchat for stall"
|
||||||
|
|
||||||
|
await delete_stall(wallet.wallet.user, stall_id)
|
||||||
|
|
||||||
|
delete_event = stall.to_nostr_delete_event(merchant.public_key)
|
||||||
|
delete_event.sig = merchant.sign_hash(bytes.fromhex(delete_event.id))
|
||||||
|
|
||||||
|
await publish_nostr_event(delete_event)
|
||||||
|
|
||||||
|
except HTTPException as ex:
|
||||||
|
raise ex
|
||||||
|
except Exception as ex:
|
||||||
|
logger.warning(ex)
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
detail="Cannot delte stall",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
######################################## OTHER ########################################
|
######################################## OTHER ########################################
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue