diff --git a/crud.py b/crud.py index c74512f..be080b7 100644 --- a/crud.py +++ b/crud.py @@ -318,8 +318,8 @@ async def delete_product(user_id: str, product_id: str) -> None: async def create_order(user_id: str, o: Order) -> Order: await db.execute( f""" - INSERT INTO nostrmarket.orders (user_id, id, event_id, pubkey, address, contact_data, order_items, invoice_id, total) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + INSERT INTO nostrmarket.orders (user_id, id, event_id, pubkey, address, contact_data, order_items, stall_id, invoice_id, total) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( user_id, @@ -329,6 +329,7 @@ async def create_order(user_id: str, o: Order) -> Order: o.address, json.dumps(o.contact.dict() if o.contact else {}), json.dumps([i.dict() for i in o.items]), + o.stall_id, o.invoice_id, o.total, ), @@ -359,3 +360,22 @@ async def get_order_by_event_id(user_id: str, event_id: str) -> Optional[Order]: ), ) return Order.from_row(row) if row else None + + +async def get_orders(user_id: str) -> List[Order]: + rows = await db.fetchall( + "SELECT * FROM nostrmarket.orders WHERE user_id = ?", + (user_id,), + ) + return [Order.from_row(row) for row in rows] + + +async def get_orders_for_stall(user_id: str, stall_id: str) -> List[Order]: + rows = await db.fetchall( + "SELECT * FROM nostrmarket.orders WHERE user_id = ? AND stall_id = ?", + ( + user_id, + stall_id, + ), + ) + return [Order.from_row(row) for row in rows] diff --git a/migrations.py b/migrations.py index 4ed088b..7c50264 100644 --- a/migrations.py +++ b/migrations.py @@ -78,11 +78,12 @@ async def m001_initial(db): user_id TEXT NOT NULL, id TEXT PRIMARY KEY, event_id TEXT, - pubkey EXT NOT NULL, + pubkey TEXT NOT NULL, contact_data TEXT NOT NULL DEFAULT '{empty_object}', order_items TEXT NOT NULL, address TEXT, total REAL NOT NULL, + stall_id TEXT NOT NULL, invoice_id TEXT NOT NULL, paid BOOLEAN NOT NULL DEFAULT false, shipped BOOLEAN NOT NULL DEFAULT false, diff --git a/models.py b/models.py index 0cfee8f..a8415e1 100644 --- a/models.py +++ b/models.py @@ -269,6 +269,26 @@ class PartialOrder(BaseModel): def validate_order(self): assert len(self.items) != 0, f"Order has no items. Order: '{self.id}'" + def validate_order_items(self, product_list: List[Product]): + assert len(self.items) != 0, f"Order has no items. Order: '{self.id}'" + assert ( + len(product_list) != 0 + ), f"No products found for order. Order: '{self.id}'" + + product_ids = [p.id for p in product_list] + for item in self.items: + if item.product_id not in product_ids: + raise ValueError( + f"Order ({self.id}) item product does not exist: {item.product_id}" + ) + + stall_id = product_list[0].stall_id + for p in product_list: + if p.stall_id != stall_id: + raise ValueError( + f"Order ({self.id}) has products from different stalls" + ) + async def total_sats(self, products: List[Product]) -> float: product_prices = {} for p in products: @@ -286,10 +306,12 @@ class PartialOrder(BaseModel): class Order(PartialOrder): + stall_id: str invoice_id: str total: float paid: bool = False shipped: bool = False + time: int @classmethod def from_row(cls, row: Row) -> "Order": diff --git a/static/components/order-list/order-list.html b/static/components/order-list/order-list.html index 147c21f..99c556a 100644 --- a/static/components/order-list/order-list.html +++ b/static/components/order-list/order-list.html @@ -1,3 +1,44 @@
- xx1 -
\ No newline at end of file + + + + diff --git a/static/components/order-list/order-list.js b/static/components/order-list/order-list.js index 15b1528..9d5e1f7 100644 --- a/static/components/order-list/order-list.js +++ b/static/components/order-list/order-list.js @@ -1,18 +1,95 @@ async function orderList(path) { - const template = await loadTemplateAsync(path) - Vue.component('order-list', { - name: 'order-list', - props: ['adminkey', 'inkey'], - template, - - data: function () { - return { + const template = await loadTemplateAsync(path) + Vue.component('order-list', { + name: 'order-list', + props: ['stall-id', 'adminkey', 'inkey'], + template, + + data: function () { + return { + orders: [], + + filter: '', + ordersTable: { + columns: [ + { + name: '', + align: 'left', + label: '', + field: '' + }, + { + name: 'id', + align: 'left', + label: 'ID', + field: 'id' + }, + { + name: 'total', + align: 'left', + label: 'Total', + field: 'total' + }, + { + name: 'paid', + align: 'left', + label: 'Paid', + field: 'paid' + }, + { + name: 'shipped', + align: 'left', + label: 'Shipped', + field: 'shipped' + }, + { + name: 'pubkey', + align: 'left', + label: 'Customer', + field: 'pubkey' + }, + { + name: 'time', + align: 'left', + label: 'Date', + field: 'time' + } + ], + pagination: { + rowsPerPage: 10 + } } - }, - methods: { - }, - created: async function () { } - }) - } - \ No newline at end of file + }, + methods: { + toShortId: function (value) { + return value.substring(0, 5) + '...' + value.substring(value.length - 5) + }, + formatDate: function (value) { + return Quasar.utils.date.formatDate( + new Date(value * 1000), + 'YYYY-MM-DD HH:mm' + ) + }, + getOrders: async function () { + try { + const ordersPath = this.stallId + ? `/stall/order/${this.stallId}` + : '/order' + const {data} = await LNbits.api.request( + 'GET', + '/nostrmarket/api/v1' + ordersPath, + this.inkey + ) + this.orders = data.map(s => ({...s, expanded: false})) + console.log('### this.orders', this.orders) + } catch (error) { + LNbits.utils.notifyApiError(error) + } + } + }, + created: async function () { + await this.getOrders() + } + }) +} diff --git a/static/components/stall-details/stall-details.html b/static/components/stall-details/stall-details.html index 9ffe49a..4239f68 100644 --- a/static/components/stall-details/stall-details.html +++ b/static/components/stall-details/stall-details.html @@ -187,10 +187,10 @@
+ :adminkey="adminkey" + :inkey="inkey" + :stall-id="stallId" + >
diff --git a/static/components/stall-list/stall-list.html b/static/components/stall-list/stall-list.html index 86ae34e..bc6236e 100644 --- a/static/components/stall-list/stall-list.html +++ b/static/components/stall-list/stall-list.html @@ -50,7 +50,7 @@ {{props.row.name}} - + {{props.row.currency}} {{props.row.config.description}} diff --git a/static/components/stall-list/stall-list.js b/static/components/stall-list/stall-list.js index 5fd8ffd..d41c062 100644 --- a/static/components/stall-list/stall-list.js +++ b/static/components/stall-list/stall-list.js @@ -35,6 +35,12 @@ async function stallList(path) { label: 'Name', field: 'id' }, + { + name: 'currency', + align: 'left', + label: 'Currency', + field: 'currency' + }, { name: 'description', align: 'left', diff --git a/tasks.py b/tasks.py index 9a78344..f4b9477 100644 --- a/tasks.py +++ b/tasks.py @@ -127,9 +127,6 @@ async def handle_new_order(order: PartialOrder): ### check that event_id not parsed already order.validate_order() - assert ( - len(order.items) != 0 - ), f"Order has no items. Order: '{order.id}' ({order.event_id})" first_product_id = order.items[0].product_id wallet_id = await get_wallet_for_product(first_product_id) diff --git a/views_api.py b/views_api.py index bca744d..e21193d 100644 --- a/views_api.py +++ b/views_api.py @@ -1,3 +1,4 @@ +import json from http import HTTPStatus from typing import List, Optional @@ -27,6 +28,8 @@ from .crud import ( get_merchant_for_user, get_order, get_order_by_event_id, + get_orders, + get_orders_for_stall, get_product, get_products, get_products_by_ids, @@ -283,6 +286,22 @@ async def api_get_stall_products( ) +@nostrmarket_ext.get("/api/v1/stall/order/{stall_id}") +async def api_get_stall_orders( + stall_id: str, + wallet: WalletTypeInfo = Depends(require_invoice_key), +): + try: + orders = await get_orders_for_stall(wallet.wallet.user, stall_id) + return orders + except Exception as ex: + logger.warning(ex) + raise HTTPException( + status_code=HTTPStatus.INTERNAL_SERVER_ERROR, + detail="Cannot get stall products", + ) + + @nostrmarket_ext.delete("/api/v1/stall/{stall_id}") async def api_delete_stall( stall_id: str, wallet: WalletTypeInfo = Depends(require_admin_key) @@ -435,6 +454,7 @@ async def api_create_order( data: PartialOrder, wallet: WalletTypeInfo = Depends(require_admin_key) ) -> Optional[PaymentRequest]: try: + # print("### new order: ", json.dumps(data.dict())) if await get_order(wallet.wallet.user, data.id): return None if data.event_id and await get_order_by_event_id( @@ -445,6 +465,8 @@ async def api_create_order( products = await get_products_by_ids( wallet.wallet.user, [p.product_id for p in data.items] ) + data.validate_order_items(products) + total_amount = await data.total_sats(products) wallet_id = await get_wallet_for_product(data.items[0].product_id) @@ -460,7 +482,12 @@ async def api_create_order( }, ) - order = Order(**data.dict(), invoice_id=payment_hash, total=total_amount) + order = Order( + **data.dict(), + stall_id=products[0].stall_id, + invoice_id=payment_hash, + total=total_amount, + ) await create_order(wallet.wallet.user, order) return PaymentRequest( @@ -474,6 +501,41 @@ async def api_create_order( ) +nostrmarket_ext.get("/api/v1/order/{order_id}") + + +async def api_get_order(order_id: str, wallet: WalletTypeInfo = Depends(get_key_type)): + try: + order = await get_order(wallet.wallet.user, order_id) + if not order: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, + detail="Order does not exist.", + ) + return order + except HTTPException as ex: + raise ex + except Exception as ex: + logger.warning(ex) + raise HTTPException( + status_code=HTTPStatus.INTERNAL_SERVER_ERROR, + detail="Cannot get order", + ) + + +@nostrmarket_ext.get("/api/v1/order") +async def api_get_orders(wallet: WalletTypeInfo = Depends(get_key_type)): + try: + orders = await get_orders(wallet.wallet.user) + return orders + except Exception as ex: + logger.warning(ex) + raise HTTPException( + status_code=HTTPStatus.INTERNAL_SERVER_ERROR, + detail="Cannot get orders", + ) + + ######################################## OTHER ########################################