nostrmarket/views_api.py
padreug e8ddc4b697
Some checks failed
CI / lint (push) Has been cancelled
/ release (push) Has been cancelled
/ pullrequest (push) Has been cancelled
Refactors type hints for clarity
Updates type hints in `crud.py`, `helpers.py`, and `models.py` for improved readability and maintainability.

Replaces `Merchant | None` with `Optional[Merchant]` and `list[X]` with `List[X]` for consistency with standard Python typing practices.
2025-11-04 00:59:55 +01:00

1147 lines
35 KiB
Python

import json
from http import HTTPStatus
from typing import List, Optional
from fastapi import Depends
from fastapi.exceptions import HTTPException
from lnbits.core.crud import get_account, update_account
from lnbits.core.services import websocket_updater
from lnbits.decorators import (
WalletTypeInfo,
require_admin_key,
require_invoice_key,
)
from lnbits.utils.exchange_rates import currencies
from lnbits.utils.nostr import generate_keypair
from loguru import logger
from . import nostr_client, nostrmarket_ext
from .crud import (
create_customer,
create_direct_message,
create_merchant,
create_product,
create_stall,
create_zone,
delete_merchant,
delete_merchant_direct_messages,
delete_merchant_orders,
delete_merchant_products,
delete_merchant_stalls,
delete_merchant_zones,
delete_product,
delete_stall,
delete_zone,
get_customer,
get_customers,
get_direct_message_by_event_id,
get_direct_messages,
get_last_direct_messages_time,
get_merchant_by_pubkey,
get_merchant_for_user,
get_order,
get_order_by_event_id,
get_orders,
get_orders_for_stall,
get_orders_from_direct_messages,
get_product,
get_products,
get_stall,
get_stalls,
get_zone,
get_zones,
touch_merchant,
update_customer_no_unread_messages,
update_merchant,
update_order,
update_order_shipped_status,
update_product,
update_stall,
update_zone,
)
from .helpers import normalize_public_key
from .models import (
CreateMerchantRequest,
Customer,
DirectMessage,
DirectMessageType,
Merchant,
Order,
OrderReissue,
OrderStatusUpdate,
PartialDirectMessage,
PartialMerchant,
PartialOrder,
PaymentOption,
PaymentRequest,
Product,
Stall,
Zone,
)
from .services import (
build_order_with_payment,
create_or_update_order_from_dm,
reply_to_structured_dm,
resubscribe_to_all_merchants,
sign_and_send_to_nostr,
subscribe_to_all_merchants,
update_merchant_to_nostr,
)
######################################## MERCHANT ######################################
@nostrmarket_ext.post("/api/v1/merchant")
async def api_create_merchant(
data: CreateMerchantRequest,
wallet: WalletTypeInfo = Depends(require_admin_key),
) -> Merchant:
try:
# Check if merchant already exists for this user
merchant = await get_merchant_for_user(wallet.wallet.user)
assert merchant is None, "A merchant already exists for this user"
# Get user's account to access their Nostr keypairs
account = await get_account(wallet.wallet.user)
if not account:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail="User account not found",
)
# Check if user has Nostr keypairs, generate them if not
if not account.pubkey or not account.prvkey:
# Generate new keypair for user
private_key, public_key = generate_keypair()
# Update user account with new keypairs
account.pubkey = public_key
account.prvkey = private_key
await update_account(account)
else:
public_key = account.pubkey
private_key = account.prvkey
# Check if another merchant is already using this public key
existing_merchant = await get_merchant_by_pubkey(public_key)
assert existing_merchant is None, "A merchant already uses this public key"
# Create PartialMerchant with user's keypairs
partial_merchant = PartialMerchant(
private_key=private_key,
public_key=public_key,
config=data.config
)
merchant = await create_merchant(wallet.wallet.user, partial_merchant)
await create_zone(
merchant.id,
Zone(
id=f"online-{merchant.public_key}",
name="Online",
currency="sat",
cost=0,
countries=["Free (digital)"],
),
)
await resubscribe_to_all_merchants()
await nostr_client.merchant_temp_subscription(public_key)
return merchant
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
) from ex
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot create merchant",
) from ex
@nostrmarket_ext.get("/api/v1/merchant")
async def api_get_merchant(
wallet: WalletTypeInfo = Depends(require_invoice_key),
) -> Optional[Merchant]:
try:
merchant = await get_merchant_for_user(wallet.wallet.user)
if not merchant:
return None
merchant = await touch_merchant(wallet.wallet.user, merchant.id)
assert merchant
last_dm_time = await get_last_direct_messages_time(merchant.id)
assert merchant.time
merchant.config.restore_in_progress = (merchant.time - last_dm_time) < 30
return merchant
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot get merchant",
) from ex
@nostrmarket_ext.delete("/api/v1/merchant/{merchant_id}")
async def api_delete_merchant(
merchant_id: str,
wallet: WalletTypeInfo = Depends(require_admin_key),
):
try:
merchant = await get_merchant_for_user(wallet.wallet.user)
assert merchant, "Merchant cannot be found"
assert merchant.id == merchant_id, "Wrong merchant ID"
await nostr_client.unsubscribe_merchants()
await delete_merchant_orders(merchant.id)
await delete_merchant_products(merchant.id)
await delete_merchant_stalls(merchant.id)
await delete_merchant_direct_messages(merchant.id)
await delete_merchant_zones(merchant.id)
await delete_merchant(merchant.id)
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
) from ex
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot get merchant",
) from ex
finally:
await subscribe_to_all_merchants()
@nostrmarket_ext.put("/api/v1/merchant/{merchant_id}/nostr")
async def api_republish_merchant(
merchant_id: str,
wallet: WalletTypeInfo = Depends(require_admin_key),
):
try:
merchant = await get_merchant_for_user(wallet.wallet.user)
assert merchant, "Merchant cannot be found"
assert merchant.id == merchant_id, "Wrong merchant ID"
merchant = await update_merchant_to_nostr(merchant)
await update_merchant(wallet.wallet.user, merchant.id, merchant.config)
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
) from ex
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot republish to nostr",
) from ex
@nostrmarket_ext.get("/api/v1/merchant/{merchant_id}/nostr")
async def api_refresh_merchant(
merchant_id: str,
wallet: WalletTypeInfo = Depends(require_admin_key),
):
try:
merchant = await get_merchant_for_user(wallet.wallet.user)
assert merchant, "Merchant cannot be found"
assert merchant.id == merchant_id, "Wrong merchant ID"
await nostr_client.merchant_temp_subscription(merchant.public_key)
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
) from ex
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot refresh from nostr",
) from ex
@nostrmarket_ext.put("/api/v1/merchant/{merchant_id}/toggle")
async def api_toggle_merchant(
merchant_id: str,
wallet: WalletTypeInfo = Depends(require_admin_key),
) -> Merchant:
try:
merchant = await get_merchant_for_user(wallet.wallet.user)
assert merchant, "Merchant cannot be found"
assert merchant.id == merchant_id, "Wrong merchant ID"
merchant.config.active = not merchant.config.active
await update_merchant(wallet.wallet.user, merchant.id, merchant.config)
return merchant
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
) from ex
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot get merchant",
) from ex
@nostrmarket_ext.delete("/api/v1/merchant/{merchant_id}/nostr")
async def api_delete_merchant_on_nostr(
merchant_id: str,
wallet: WalletTypeInfo = Depends(require_admin_key),
):
try:
merchant = await get_merchant_for_user(wallet.wallet.user)
assert merchant, "Merchant cannot be found"
assert merchant.id == merchant_id, "Wrong merchant ID"
merchant = await update_merchant_to_nostr(merchant, True)
await update_merchant(wallet.wallet.user, merchant.id, merchant.config)
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
) from ex
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot get merchant",
) from ex
######################################## ZONES ########################################
@nostrmarket_ext.get("/api/v1/zone")
async def api_get_zones(
wallet: WalletTypeInfo = Depends(require_invoice_key),
) -> List[Zone]:
try:
merchant = await get_merchant_for_user(wallet.wallet.user)
assert merchant, "Merchant cannot be found"
return await get_zones(merchant.id)
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
) from ex
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot get zone",
) from ex
@nostrmarket_ext.post("/api/v1/zone")
async def api_create_zone(
data: Zone, wallet: WalletTypeInfo = Depends(require_admin_key)
):
try:
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 AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
) from ex
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot create zone",
) from ex
@nostrmarket_ext.patch("/api/v1/zone/{zone_id}")
async def api_update_zone(
data: Zone,
zone_id: str,
wallet: WalletTypeInfo = Depends(require_admin_key),
) -> Zone:
try:
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,
detail="Zone does not exist.",
)
zone = await update_zone(merchant.id, data)
assert zone, "Cannot find updated zone"
return zone
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
) from ex
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot update zone",
) from ex
@nostrmarket_ext.delete("/api/v1/zone/{zone_id}")
async def api_delete_zone(zone_id, wallet: WalletTypeInfo = Depends(require_admin_key)):
try:
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,
detail="Zone does not exist.",
)
await delete_zone(merchant.id, zone_id)
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
) from ex
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot delete zone",
) from ex
######################################## STALLS ########################################
@nostrmarket_ext.post("/api/v1/stall")
async def api_create_stall(
data: Stall,
wallet: WalletTypeInfo = Depends(require_admin_key),
) -> Stall:
try:
# shipping_zones = await
data.validate_stall()
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(merchant, stall)
stall.event_id = event.id
await update_stall(merchant.id, stall)
return stall
except (ValueError, AssertionError) as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
) from ex
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot create stall",
) from ex
@nostrmarket_ext.put("/api/v1/stall/{stall_id}")
async def api_update_stall(
data: Stall,
wallet: WalletTypeInfo = Depends(require_admin_key),
) -> Stall:
try:
data.validate_stall()
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(merchant, stall)
stall.event_id = event.id
await update_stall(merchant.id, stall)
return stall
except (ValueError, AssertionError) as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
) from ex
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot update stall",
) from ex
@nostrmarket_ext.get("/api/v1/stall/{stall_id}")
async def api_get_stall(
stall_id: str, wallet: WalletTypeInfo = Depends(require_invoice_key)
):
try:
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.",
)
return stall
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
) from ex
except HTTPException as ex:
raise ex
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot get stall",
) from ex
@nostrmarket_ext.get("/api/v1/stall")
async def api_get_stalls(
pending: Optional[bool] = False,
wallet: WalletTypeInfo = Depends(require_invoice_key),
):
try:
merchant = await get_merchant_for_user(wallet.wallet.user)
assert merchant, "Merchant cannot be found"
stalls = await get_stalls(merchant.id, pending)
return stalls
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
) from ex
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot get stalls",
) from ex
@nostrmarket_ext.get("/api/v1/stall/product/{stall_id}")
async def api_get_stall_products(
stall_id: str,
pending: Optional[bool] = False,
wallet: WalletTypeInfo = Depends(require_invoice_key),
):
try:
merchant = await get_merchant_for_user(wallet.wallet.user)
assert merchant, "Merchant cannot be found"
products = await get_products(merchant.id, stall_id, pending)
return products
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
) from ex
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot get stall products",
) from ex
@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, paid=paid, shipped=shipped, public_key=pubkey
)
return orders
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
) from ex
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot get stall products",
) from ex
@nostrmarket_ext.delete("/api/v1/stall/{stall_id}")
async def api_delete_stall(
stall_id: str, wallet: WalletTypeInfo = Depends(require_admin_key)
):
try:
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(merchant.id, stall_id)
event = await sign_and_send_to_nostr(merchant, stall, True)
stall.event_id = event.id
await update_stall(merchant.id, stall)
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
) from ex
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot delete stall",
) from ex
######################################## PRODUCTS ######################################
@nostrmarket_ext.post("/api/v1/product")
async def api_create_product(
data: Product,
wallet: WalletTypeInfo = Depends(require_admin_key),
) -> Product:
try:
merchant = await get_merchant_for_user(wallet.wallet.user)
assert merchant, "Merchant cannot be found"
stall = await get_stall(merchant.id, data.stall_id)
assert stall, "Stall missing for product"
data.config.currency = stall.currency
product = await create_product(merchant.id, data=data)
event = await sign_and_send_to_nostr(merchant, product)
product.event_id = event.id
await update_product(merchant.id, product)
return product
except (ValueError, AssertionError) as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
) from ex
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot create product",
) from ex
@nostrmarket_ext.patch("/api/v1/product/{product_id}")
async def api_update_product(
product_id: str,
product: Product,
wallet: WalletTypeInfo = Depends(require_admin_key),
) -> Product:
try:
if product_id != product.id:
raise ValueError("Bad product ID")
merchant = await get_merchant_for_user(wallet.wallet.user)
assert merchant, "Merchant cannot be found"
stall = await get_stall(merchant.id, product.stall_id)
assert stall, "Stall missing for product"
product.config.currency = stall.currency
product = await update_product(merchant.id, product)
event = await sign_and_send_to_nostr(merchant, product)
product.event_id = event.id
await update_product(merchant.id, product)
return product
except (ValueError, AssertionError) as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
) from ex
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot update product",
) from ex
@nostrmarket_ext.get("/api/v1/product/{product_id}")
async def api_get_product(
product_id: str,
wallet: WalletTypeInfo = Depends(require_invoice_key),
) -> Optional[Product]:
try:
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 AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
) from ex
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot get product",
) from ex
@nostrmarket_ext.delete("/api/v1/product/{product_id}")
async def api_delete_product(
product_id: str,
wallet: WalletTypeInfo = Depends(require_admin_key),
):
try:
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(merchant.id, product_id)
await sign_and_send_to_nostr(merchant, product, True)
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
) from ex
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot delete product",
) from ex
######################################## ORDERS ########################################
@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"
order = await get_order(merchant.id, order_id)
if not order:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
detail="Order does not exist.",
)
return order
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
) from ex
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot get order",
) from ex
@nostrmarket_ext.get("/api/v1/order")
async def api_get_orders(
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(
merchant_id=merchant.id, paid=paid, shipped=shipped, public_key=pubkey
)
return orders
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
) from ex
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot get orders",
) from ex
@nostrmarket_ext.patch("/api/v1/order/{order_id}")
async def api_update_order_status(
data: OrderStatusUpdate,
wallet: WalletTypeInfo = Depends(require_admin_key),
) -> Order:
try:
assert data.shipped is not None, "Shipped value is required for order"
merchant = await get_merchant_for_user(wallet.wallet.user)
assert merchant, "Merchant cannot be found for order {data.id}"
order = await update_order_shipped_status(merchant.id, data.id, data.shipped)
assert order, "Cannot find updated order"
data.paid = order.paid
dm_content = json.dumps(
{"type": DirectMessageType.ORDER_PAID_OR_SHIPPED.value, **data.dict()},
separators=(",", ":"),
ensure_ascii=False,
)
dm_event = merchant.build_dm_event(dm_content, order.public_key)
dm = PartialDirectMessage(
event_id=dm_event.id,
event_created_at=dm_event.created_at,
message=dm_content,
public_key=order.public_key,
type=DirectMessageType.ORDER_PAID_OR_SHIPPED.value,
)
await create_direct_message(merchant.id, dm)
await nostr_client.publish_nostr_event(dm_event)
await websocket_updater(
merchant.id,
json.dumps(
{
"type": f"dm:{dm.type}",
"customerPubkey": order.public_key,
"dm": dm.dict(),
}
),
)
return order
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
) from ex
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot update order",
) from ex
@nostrmarket_ext.put("/api/v1/order/restore/{event_id}")
async def api_restore_order(
event_id: str,
wallet: WalletTypeInfo = Depends(require_admin_key),
) -> Optional[Order]:
try:
merchant = await get_merchant_for_user(wallet.wallet.user)
assert merchant, "Merchant cannot be found"
dm = await get_direct_message_by_event_id(merchant.id, event_id)
assert dm, "Canot find direct message"
await create_or_update_order_from_dm(merchant.id, merchant.public_key, dm)
return await get_order_by_event_id(merchant.id, event_id)
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
) from ex
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot restore order",
) from ex
@nostrmarket_ext.put("/api/v1/orders/restore")
async def api_restore_orders(
wallet: WalletTypeInfo = Depends(require_admin_key),
) -> None:
try:
merchant = await get_merchant_for_user(wallet.wallet.user)
assert merchant, "Merchant cannot be found"
dms = await get_orders_from_direct_messages(merchant.id)
for dm in dms:
try:
await create_or_update_order_from_dm(
merchant.id, merchant.public_key, dm
)
except Exception as e:
logger.debug(
f"Failed to restore order from event '{dm.event_id}': '{e!s}'."
)
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
) from ex
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot restore orders",
) from ex
@nostrmarket_ext.put("/api/v1/order/reissue")
async def api_reissue_order_invoice(
reissue_data: OrderReissue,
wallet: WalletTypeInfo = Depends(require_admin_key),
) -> Order:
try:
merchant = await get_merchant_for_user(wallet.wallet.user)
assert merchant, "Merchant cannot be found"
data = await get_order(merchant.id, reissue_data.id)
assert data, "Order cannot be found"
if reissue_data.shipping_id:
data.shipping_id = reissue_data.shipping_id
order, invoice, receipt = await build_order_with_payment(
merchant.id, merchant.public_key, PartialOrder(**data.dict())
)
order_update = {
"stall_id": order.stall_id,
"total": order.total,
"invoice_id": order.invoice_id,
"shipping_id": order.shipping_id,
"extra_data": json.dumps(order.extra.dict()),
}
await update_order(
merchant.id,
order.id,
**order_update,
)
payment_req = PaymentRequest(
id=data.id,
payment_options=[PaymentOption(type="ln", link=invoice)],
message=receipt,
)
response = {
"type": DirectMessageType.PAYMENT_REQUEST.value,
**payment_req.dict(),
}
dm_reply = json.dumps(response, separators=(",", ":"), ensure_ascii=False)
await reply_to_structured_dm(
merchant,
order.public_key,
DirectMessageType.PAYMENT_REQUEST.value,
dm_reply,
)
return order
except (AssertionError, ValueError) as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
) from ex
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot reissue order invoice",
) from ex
######################################## DIRECT MESSAGES ###############################
@nostrmarket_ext.get("/api/v1/message/{public_key}")
async def api_get_messages(
public_key: str, wallet: WalletTypeInfo = Depends(require_invoice_key)
) -> List[DirectMessage]:
try:
merchant = await get_merchant_for_user(wallet.wallet.user)
assert merchant, "Merchant cannot be found"
messages = await get_direct_messages(merchant.id, public_key)
await update_customer_no_unread_messages(merchant.id, public_key)
return messages
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
) from ex
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot get direct message",
) from ex
@nostrmarket_ext.post("/api/v1/message")
async def api_create_message(
data: PartialDirectMessage, wallet: WalletTypeInfo = Depends(require_admin_key)
) -> DirectMessage:
try:
merchant = await get_merchant_for_user(wallet.wallet.user)
assert merchant, "Merchant cannot be found"
dm_event = merchant.build_dm_event(data.message, data.public_key)
data.event_id = dm_event.id
data.event_created_at = dm_event.created_at
dm = await create_direct_message(merchant.id, data)
await nostr_client.publish_nostr_event(dm_event)
return dm
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
) from ex
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot create message",
) from ex
######################################## CUSTOMERS #####################################
@nostrmarket_ext.get("/api/v1/customer")
async def api_get_customers(
wallet: WalletTypeInfo = Depends(require_invoice_key),
) -> List[Customer]:
try:
merchant = await get_merchant_for_user(wallet.wallet.user)
assert merchant, "Merchant cannot be found"
return await get_customers(merchant.id)
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
) from ex
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot create message",
) from ex
@nostrmarket_ext.post("/api/v1/customer")
async def api_create_customer(
data: Customer,
wallet: WalletTypeInfo = Depends(require_admin_key),
) -> Customer:
try:
pubkey = normalize_public_key(data.public_key)
merchant = await get_merchant_for_user(wallet.wallet.user)
assert merchant, "A merchant does not exists for this user"
assert merchant.id == data.merchant_id, "Invalid merchant id for user"
existing_customer = await get_customer(merchant.id, pubkey)
assert existing_customer is None, "This public key already exists"
customer = await create_customer(
merchant.id, Customer(merchant_id=merchant.id, public_key=pubkey)
)
await nostr_client.user_profile_temp_subscribe(pubkey)
return customer
except (ValueError, AssertionError) as ex:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(ex),
) from ex
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot create customer",
) from ex
######################################## OTHER ########################################
@nostrmarket_ext.get("/api/v1/currencies")
async def api_list_currencies_available():
return list(currencies.keys())
@nostrmarket_ext.put("/api/v1/restart")
async def restart_nostr_client(wallet: WalletTypeInfo = Depends(require_admin_key)):
try:
await nostr_client.restart()
except Exception as ex:
logger.warning(ex)