from base64 import urlsafe_b64encode from http import HTTPStatus from typing import List, Union from uuid import uuid4 from fastapi import Body, Depends, Query, Request from loguru import logger from starlette.exceptions import HTTPException from lnbits.core.crud import get_user from lnbits.core.services import create_invoice from lnbits.core.views.api import api_payment from lnbits.decorators import ( WalletTypeInfo, get_key_type, require_admin_key, require_invoice_key, ) from lnbits.helpers import urlsafe_short_hash from lnbits.utils.exchange_rates import currencies, get_fiat_rate_satoshis from . import db, shop_ext from .crud import ( create_shop_market, create_shop_market_stalls, create_shop_order, create_shop_order_details, create_shop_product, create_shop_settings, create_shop_stall, create_shop_zone, delete_shop_order, delete_shop_product, delete_shop_stall, delete_shop_zone, get_shop_chat_by_merchant, get_shop_chat_messages, get_shop_latest_chat_messages, get_shop_market, get_shop_market_stalls, get_shop_markets, get_shop_order, get_shop_order_details, get_shop_order_invoiceid, get_shop_orders, get_shop_product, get_shop_products, get_shop_settings, get_shop_stall, get_shop_stalls, get_shop_stalls_by_ids, get_shop_zone, get_shop_zones, set_shop_order_pubkey, set_shop_settings, update_shop_market, update_shop_product, update_shop_stall, update_shop_zone, ) from .models import ( CreateMarket, CreateMarketStalls, Orders, Products, SetSettings, Stalls, Zones, createOrder, createProduct, createStalls, createZones, ) # from lnbits.db import open_ext_db ### Products @shop_ext.get("/api/v1/products") async def api_shop_products( wallet: WalletTypeInfo = Depends(require_invoice_key), all_stalls: bool = Query(False), ): wallet_ids = [wallet.wallet.id] if all_stalls: user = await get_user(wallet.wallet.user) wallet_ids = user.wallet_ids if user else [] stalls = [stall.id for stall in await get_shop_stalls(wallet_ids)] if not stalls: return return [product.dict() for product in await get_shop_products(stalls)] @shop_ext.post("/api/v1/products") @shop_ext.put("/api/v1/products/{product_id}") async def api_shop_product_create( data: createProduct, product_id=None, wallet: WalletTypeInfo = Depends(require_invoice_key), ): # For fiat currencies, # we multiply by data.fiat_base_multiplier (usually 100) to save the value in cents. settings = await get_shop_settings(user=wallet.wallet.user) assert settings stall = await get_shop_stall(stall_id=data.stall) assert stall if stall.currency != "sat": data.price *= settings.fiat_base_multiplier if product_id: product = await get_shop_product(product_id) if not product: return {"message": "Product does not exist."} # stall = await get_shop_stall(stall_id=product.stall) if stall.wallet != wallet.wallet.id: return {"message": "Not your product."} product = await update_shop_product(product_id, **data.dict()) else: product = await create_shop_product(data=data) assert product return product.dict() @shop_ext.delete("/api/v1/products/{product_id}") async def api_shop_products_delete( product_id, wallet: WalletTypeInfo = Depends(require_admin_key) ): product = await get_shop_product(product_id) if not product: return {"message": "Product does not exist."} stall = await get_shop_stall(product.stall) assert stall if stall.wallet != wallet.wallet.id: return {"message": "Not your Shop."} await delete_shop_product(product_id) raise HTTPException(status_code=HTTPStatus.NO_CONTENT) # # # Shippingzones @shop_ext.get("/api/v1/zones") async def api_shop_zones(wallet: WalletTypeInfo = Depends(get_key_type)): return await get_shop_zones(wallet.wallet.user) @shop_ext.post("/api/v1/zones") async def api_shop_zone_create( data: createZones, wallet: WalletTypeInfo = Depends(get_key_type) ): zone = await create_shop_zone(user=wallet.wallet.user, data=data) return zone.dict() @shop_ext.post("/api/v1/zones/{zone_id}") async def api_shop_zone_update( data: createZones, zone_id: str, wallet: WalletTypeInfo = Depends(require_admin_key), ): zone = await get_shop_zone(zone_id) if not zone: return {"message": "Zone does not exist."} if zone.user != wallet.wallet.user: return {"message": "Not your record."} zone = await update_shop_zone(zone_id, **data.dict()) return zone @shop_ext.delete("/api/v1/zones/{zone_id}") async def api_shop_zone_delete( zone_id, wallet: WalletTypeInfo = Depends(require_admin_key) ): zone = await get_shop_zone(zone_id) if not zone: return {"message": "zone does not exist."} if zone.user != wallet.wallet.user: return {"message": "Not your zone."} await delete_shop_zone(zone_id) raise HTTPException(status_code=HTTPStatus.NO_CONTENT) # # # Stalls @shop_ext.get("/api/v1/stalls") async def api_shop_stalls( wallet: WalletTypeInfo = Depends(get_key_type), all_wallets: bool = Query(False) ): wallet_ids = [wallet.wallet.id] if all_wallets: user = await get_user(wallet.wallet.user) wallet_ids = user.wallet_ids if user else [] return [stall.dict() for stall in await get_shop_stalls(wallet_ids)] @shop_ext.post("/api/v1/stalls") @shop_ext.put("/api/v1/stalls/{stall_id}") async def api_shop_stall_create( data: createStalls, stall_id: str = None, wallet: WalletTypeInfo = Depends(require_invoice_key), ): if stall_id: stall = await get_shop_stall(stall_id) if not stall: return {"message": "Withdraw stall does not exist."} if stall.wallet != wallet.wallet.id: return {"message": "Not your withdraw stall."} stall = await update_shop_stall(stall_id, **data.dict()) else: stall = await create_shop_stall(data=data) assert stall return stall.dict() @shop_ext.delete("/api/v1/stalls/{stall_id}") async def api_shop_stall_delete( stall_id: str, wallet: WalletTypeInfo = Depends(require_admin_key) ): stall = await get_shop_stall(stall_id) if not stall: return {"message": "Stall does not exist."} if stall.wallet != wallet.wallet.id: return {"message": "Not your Stall."} await delete_shop_stall(stall_id) raise HTTPException(status_code=HTTPStatus.NO_CONTENT) ###Orders @shop_ext.get("/api/v1/orders") async def api_shop_orders( wallet: WalletTypeInfo = Depends(get_key_type), all_wallets: bool = Query(False) ): wallet_ids = [wallet.wallet.id] if all_wallets: user = await get_user(wallet.wallet.user) wallet_ids = user.wallet_ids if user else [] orders = await get_shop_orders(wallet_ids) if not orders: return orders_with_details = [] for order in orders: _order = order.dict() _order["details"] = await get_shop_order_details(_order["id"]) orders_with_details.append(_order) try: return orders_with_details # [order for order in orders] # return [order.dict() for order in await get_shop_orders(wallet_ids)] except: return {"message": "We could not retrieve the orders."} @shop_ext.get("/api/v1/orders/{order_id}") async def api_shop_order_by_id(order_id: str): order = await get_shop_order(order_id) assert order _order = order.dict() _order["details"] = await get_shop_order_details(order_id) return _order @shop_ext.post("/api/v1/orders") async def api_shop_order_create(data: createOrder): ref = urlsafe_short_hash() payment_hash, payment_request = await create_invoice( wallet_id=data.wallet, amount=data.total, memo=f"New order on Diagon alley", extra={ "tag": "shop", "reference": ref, }, ) order_id = await create_shop_order(invoiceid=payment_hash, data=data) logger.debug(f"ORDER ID {order_id}") logger.debug(f"PRODUCTS {data.products}") await create_shop_order_details(order_id=order_id, data=data.products) return { "payment_hash": payment_hash, "payment_request": payment_request, "order_reference": ref, } @shop_ext.get("/api/v1/orders/payments/{payment_hash}") async def api_shop_check_payment(payment_hash: str): order = await get_shop_order_invoiceid(payment_hash) if not order: raise HTTPException( status_code=HTTPStatus.NOT_FOUND, detail="Order does not exist." ) try: status = await api_payment(payment_hash) except Exception as exc: logger.error(exc) return {"paid": False} return status @shop_ext.delete("/api/v1/orders/{order_id}") async def api_shop_order_delete( order_id: str, wallet: WalletTypeInfo = Depends(require_admin_key) ): order = await get_shop_order(order_id) if not order: return {"message": "Order does not exist."} if order.wallet != wallet.wallet.id: return {"message": "Not your Order."} await delete_shop_order(order_id) raise HTTPException(status_code=HTTPStatus.NO_CONTENT) # @shop_ext.get("/api/v1/orders/paid/{order_id}") # async def api_shop_order_paid( # order_id, wallet: WalletTypeInfo = Depends(require_admin_key) # ): # await db.execute( # "UPDATE shop.orders SET paid = ? WHERE id = ?", # ( # True, # order_id, # ), # ) # return "", HTTPStatus.OK @shop_ext.get("/api/v1/order/pubkey/{payment_hash}/{pubkey}") async def api_shop_order_pubkey(payment_hash: str, pubkey: str): await set_shop_order_pubkey(payment_hash, pubkey) return "", HTTPStatus.OK @shop_ext.get("/api/v1/orders/shipped/{order_id}") async def api_shop_order_shipped( order_id, shipped: bool = Query(...), wallet: WalletTypeInfo = Depends(get_key_type) ): await db.execute( "UPDATE shop.orders SET shipped = ? WHERE id = ?", ( shipped, order_id, ), ) order = await db.fetchone("SELECT * FROM shop.orders WHERE id = ?", (order_id,)) return order ###List products based on stall id # @shop_ext.get("/api/v1/stall/products/{stall_id}") # async def api_shop_stall_products( # stall_id, wallet: WalletTypeInfo = Depends(get_key_type) # ): # rows = await db.fetchone("SELECT * FROM shop.stalls WHERE id = ?", (stall_id,)) # if not rows: # return {"message": "Stall does not exist."} # products = db.fetchone("SELECT * FROM shop.products WHERE wallet = ?", (rows[1],)) # if not products: # return {"message": "No products"} # return [products.dict() for products in await get_shop_products(rows[1])] ###Check a product has been shipped # @shop_ext.get("/api/v1/stall/checkshipped/{checking_id}") # async def api_shop_stall_checkshipped( # checking_id, wallet: WalletTypeInfo = Depends(get_key_type) # ): # rows = await db.fetchone( # "SELECT * FROM shop.orders WHERE invoiceid = ?", (checking_id,) # ) # return {"shipped": rows["shipped"]} ## # MARKETS ## @shop_ext.get("/api/v1/markets") async def api_shop_markets(wallet: WalletTypeInfo = Depends(get_key_type)): # await get_shop_market_stalls(market_id="FzpWnMyHQMcRppiGVua4eY") try: return [market.dict() for market in await get_shop_markets(wallet.wallet.user)] except: return {"message": "We could not retrieve the markets."} @shop_ext.get("/api/v1/markets/{market_id}/stalls") async def api_shop_market_stalls(market_id: str): stall_ids = await get_shop_market_stalls(market_id) return stall_ids @shop_ext.post("/api/v1/markets") @shop_ext.put("/api/v1/markets/{market_id}") async def api_shop_market_create( data: CreateMarket, market_id: str = None, wallet: WalletTypeInfo = Depends(require_invoice_key), ): if market_id: market = await get_shop_market(market_id) if not market: return {"message": "Market does not exist."} if market.usr != wallet.wallet.user: return {"message": "Not your market."} market = await update_shop_market(market_id, data.name) else: market = await create_shop_market(data=data) assert market await create_shop_market_stalls(market_id=market.id, data=data.stalls) return market.dict() ## MESSAGES/CHAT @shop_ext.get("/api/v1/chat/messages/merchant") async def api_get_merchant_messages( orders: str = Query(...), wallet: WalletTypeInfo = Depends(require_admin_key) ): return [msg.dict() for msg in await get_shop_chat_by_merchant(orders.split(","))] @shop_ext.get("/api/v1/chat/messages/{room_name}") async def api_get_latest_chat_msg(room_name: str, all_messages: bool = Query(False)): if all_messages: messages = await get_shop_chat_messages(room_name) else: messages = await get_shop_latest_chat_messages(room_name) return messages @shop_ext.get("/api/v1/currencies") async def api_list_currencies_available(): return list(currencies.keys()) @shop_ext.get("/api/v1/settings") async def api_get_settings(wallet: WalletTypeInfo = Depends(require_admin_key)): user = wallet.wallet.user settings = await get_shop_settings(user) return settings @shop_ext.post("/api/v1/settings") @shop_ext.put("/api/v1/settings/{usr}") async def api_set_settings( data: SetSettings, usr: str = None, wallet: WalletTypeInfo = Depends(require_admin_key), ): if usr: if usr != wallet.wallet.user: return {"message": "Not your Shop."} settings = await get_shop_settings(user=usr) assert settings if settings.user != wallet.wallet.user: return {"message": "Not your Shop."} return await set_shop_settings(usr, data) user = wallet.wallet.user return await create_shop_settings(user, data)