From 6b2f3d05d51d7aabe8a00dac10f9dfc3d96134a9 Mon Sep 17 00:00:00 2001 From: benarc Date: Thu, 27 Jan 2022 12:24:38 +0000 Subject: [PATCH 01/30] initial --- lnbits/extensions/diagonalley/README.md | 9 + lnbits/extensions/diagonalley/__init__.py | 16 + lnbits/extensions/diagonalley/config.json | 6 + lnbits/extensions/diagonalley/crud.py | 395 +++++++++ lnbits/extensions/diagonalley/migrations.py | 69 ++ lnbits/extensions/diagonalley/models.py | 57 ++ .../extensions/diagonalley/static/js/index.js | 824 ++++++++++++++++++ lnbits/extensions/diagonalley/tasks.py | 29 + .../templates/diagonalley/_api_docs.html | 129 +++ .../templates/diagonalley/index.html | 634 ++++++++++++++ .../templates/diagonalley/stall.html | 9 + lnbits/extensions/diagonalley/views.py | 44 + lnbits/extensions/diagonalley/views_api.py | 348 ++++++++ 13 files changed, 2569 insertions(+) create mode 100644 lnbits/extensions/diagonalley/README.md create mode 100644 lnbits/extensions/diagonalley/__init__.py create mode 100644 lnbits/extensions/diagonalley/config.json create mode 100644 lnbits/extensions/diagonalley/crud.py create mode 100644 lnbits/extensions/diagonalley/migrations.py create mode 100644 lnbits/extensions/diagonalley/models.py create mode 100644 lnbits/extensions/diagonalley/static/js/index.js create mode 100644 lnbits/extensions/diagonalley/tasks.py create mode 100644 lnbits/extensions/diagonalley/templates/diagonalley/_api_docs.html create mode 100644 lnbits/extensions/diagonalley/templates/diagonalley/index.html create mode 100644 lnbits/extensions/diagonalley/templates/diagonalley/stall.html create mode 100644 lnbits/extensions/diagonalley/views.py create mode 100644 lnbits/extensions/diagonalley/views_api.py diff --git a/lnbits/extensions/diagonalley/README.md b/lnbits/extensions/diagonalley/README.md new file mode 100644 index 00000000..e8035b74 --- /dev/null +++ b/lnbits/extensions/diagonalley/README.md @@ -0,0 +1,9 @@ +

Diagon Alley

+

A movable market stand

+Make a list of products to sell, point the list to an relay (or many), stack sats. +Diagon Alley is a movable market stand, for anon transactions. You then give permission for an relay to list those products. Delivery addresses are sent through the Lightning Network. + + +

API endpoints

+ +curl -X GET http://YOUR-TOR-ADDRESS diff --git a/lnbits/extensions/diagonalley/__init__.py b/lnbits/extensions/diagonalley/__init__.py new file mode 100644 index 00000000..720c55c8 --- /dev/null +++ b/lnbits/extensions/diagonalley/__init__.py @@ -0,0 +1,16 @@ +from quart import Blueprint +from lnbits.db import Database + +db = Database("ext_diagonalley") + +diagonalley_ext: Blueprint = Blueprint( + "diagonalley", __name__, static_folder="static", template_folder="templates" +) + +from .views_api import * # noqa +from .views import * # noqa + +from .tasks import register_listeners +from lnbits.tasks import record_async + +diagonalley_ext.record(record_async(register_listeners)) diff --git a/lnbits/extensions/diagonalley/config.json b/lnbits/extensions/diagonalley/config.json new file mode 100644 index 00000000..99e92e9b --- /dev/null +++ b/lnbits/extensions/diagonalley/config.json @@ -0,0 +1,6 @@ +{ + "name": "Diagon Alley", + "short_description": "Movable anonymous market stand", + "icon": "add_shopping_cart", + "contributors": ["benarc","DeanH"] +} diff --git a/lnbits/extensions/diagonalley/crud.py b/lnbits/extensions/diagonalley/crud.py new file mode 100644 index 00000000..c6ce8222 --- /dev/null +++ b/lnbits/extensions/diagonalley/crud.py @@ -0,0 +1,395 @@ +from base64 import urlsafe_b64encode +from uuid import uuid4 +from typing import List, Optional, Union + +from lnbits.settings import WALLET + +# from lnbits.db import open_ext_db +from lnbits.db import SQLITE +from . import db +from .models import Products, Orders, Stalls, Zones + +import httpx +from lnbits.helpers import urlsafe_short_hash +import re + +regex = re.compile( + r"^(?:http|ftp)s?://" # http:// or https:// + r"(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|" + r"localhost|" + r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})" + r"(?::\d+)?" + r"(?:/?|[/?]\S+)$", + re.IGNORECASE, +) + + +###Products + + +async def create_diagonalley_product( + *, + stall_id: str, + product: str, + categories: str, + description: str, + image: Optional[str] = None, + price: int, + quantity: int, + shippingzones: str, +) -> Products: + returning = "" if db.type == SQLITE else "RETURNING ID" + method = db.execute if db.type == SQLITE else db.fetchone + product_id = urlsafe_short_hash() + # with open_ext_db("diagonalley") as db: + result = await (method)( + f""" + INSERT INTO diagonalley.products (id, stall, product, categories, description, image, price, quantity, shippingzones) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + {returning} + """, + ( + product_id, + stall_id, + product, + categories, + description, + image, + price, + quantity, + ), + ) + product = await get_diagonalley_product(product_id) + assert product, "Newly created product couldn't be retrieved" + return product + + +async def update_diagonalley_product(product_id: str, **kwargs) -> Optional[Stalls]: + q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) + + # with open_ext_db("diagonalley") as db: + await db.execute( + f"UPDATE diagonalley.products SET {q} WHERE id = ?", + (*kwargs.values(), product_id), + ) + row = await db.fetchone( + "SELECT * FROM diagonalley.products WHERE id = ?", (product_id,) + ) + + return get_diagonalley_stall(product_id) + + +async def get_diagonalley_product(product_id: str) -> Optional[Products]: + row = await db.fetchone( + "SELECT * FROM diagonalley.products WHERE id = ?", (product_id,) + ) + return Products.from_row(row) if row else None + + +async def get_diagonalley_products(wallet_ids: Union[str, List[str]]) -> List[Products]: + if isinstance(wallet_ids, str): + wallet_ids = [wallet_ids] + + # with open_ext_db("diagonalley") as db: + q = ",".join(["?"] * len(wallet_ids)) + rows = await db.fetchall( + f""" + SELECT * FROM diagonalley.products WHERE stall IN ({q}) + """, + (*wallet_ids,), + ) + return [Products.from_row(row) for row in rows] + + +async def delete_diagonalley_product(product_id: str) -> None: + await db.execute("DELETE FROM diagonalley.products WHERE id = ?", (product_id,)) + + +###zones + + +async def create_diagonalley_zone( + *, + wallet: Optional[str] = None, + cost: Optional[int] = 0, + countries: Optional[str] = None, +) -> Zones: + + returning = "" if db.type == SQLITE else "RETURNING ID" + method = db.execute if db.type == SQLITE else db.fetchone + + zone_id = urlsafe_short_hash() + result = await (method)( + f""" + INSERT INTO diagonalley.zones ( + id, + wallet, + cost, + countries + + ) + VALUES (?, ?, ?, ?) + {returning} + """, + (zone_id, wallet, cost, countries), + ) + + zone = await get_diagonalley_zone(zone_id) + assert zone, "Newly created zone couldn't be retrieved" + return zone + + +async def update_diagonalley_zone(zone_id: str, **kwargs) -> Optional[Zones]: + q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) + await db.execute( + f"UPDATE diagonalley.zones SET {q} WHERE id = ?", + (*kwargs.values(), zone_id), + ) + row = await db.fetchone("SELECT * FROM diagonalley.zones WHERE id = ?", (zone_id,)) + return Zones.from_row(row) if row else None + + +async def get_diagonalley_zone(zone_id: str) -> Optional[Zones]: + row = await db.fetchone("SELECT * FROM diagonalley.zones WHERE id = ?", (zone_id,)) + return Zones.from_row(row) if row else None + + +async def get_diagonalley_zones(wallet_ids: Union[str, List[str]]) -> List[Zones]: + if isinstance(wallet_ids, str): + wallet_ids = [wallet_ids] + print(wallet_ids) + + q = ",".join(["?"] * len(wallet_ids)) + rows = await db.fetchall( + f"SELECT * FROM diagonalley.zones WHERE wallet IN ({q})", (*wallet_ids,) + ) + + for r in rows: + try: + x = httpx.get(r["zoneaddress"] + "/" + r["ratingkey"]) + if x.status_code == 200: + await db.execute( + "UPDATE diagonalley.zones SET online = ? WHERE id = ?", + ( + True, + r["id"], + ), + ) + else: + await db.execute( + "UPDATE diagonalley.zones SET online = ? WHERE id = ?", + ( + False, + r["id"], + ), + ) + except: + print("An exception occurred") + q = ",".join(["?"] * len(wallet_ids)) + rows = await db.fetchall( + f"SELECT * FROM diagonalley.zones WHERE wallet IN ({q})", (*wallet_ids,) + ) + return [Zones.from_row(row) for row in rows] + + +async def delete_diagonalley_zone(zone_id: str) -> None: + await db.execute("DELETE FROM diagonalley.zones WHERE id = ?", (zone_id,)) + + +###Stalls + + +async def create_diagonalley_stall( + *, + wallet: str, + name: str, + publickey: str, + privatekey: str, + relays: str, + shippingzones: str, +) -> Stalls: + + returning = "" if db.type == SQLITE else "RETURNING ID" + method = db.execute if db.type == SQLITE else db.fetchone + + stall_id = urlsafe_short_hash() + result = await (method)( + f""" + INSERT INTO diagonalley.stalls ( + id, + wallet, + name, + publickey, + privatekey, + relays, + shippingzones + ) + VALUES (?, ?, ?, ?, ?, ?, ?) + {returning} + """, + (stall_id, wallet, name, publickey, privatekey, relays, shippingzones), + ) + + stall = await get_diagonalley_stall(stall_id) + assert stall, "Newly created stall couldn't be retrieved" + return stall + + +async def update_diagonalley_stall(stall_id: str, **kwargs) -> Optional[Stalls]: + q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) + await db.execute( + f"UPDATE diagonalley.stalls SET {q} WHERE id = ?", + (*kwargs.values(), stall_id), + ) + row = await db.fetchone( + "SELECT * FROM diagonalley.stalls WHERE id = ?", (stall_id,) + ) + return Stalls.from_row(row) if row else None + + +async def get_diagonalley_stall(stall_id: str) -> Optional[Stalls]: + roww = await db.fetchone( + "SELECT * FROM diagonalley.stalls WHERE id = ?", (stall_id,) + ) + + try: + x = httpx.get(roww["stalladdress"] + "/" + roww["ratingkey"]) + if x.status_code == 200: + await db.execute( + "UPDATE diagonalley.stalls SET online = ? WHERE id = ?", + ( + True, + stall_id, + ), + ) + else: + await db.execute( + "UPDATE diagonalley.stalls SET online = ? WHERE id = ?", + ( + False, + stall_id, + ), + ) + except: + print("An exception occurred") + + # with open_ext_db("diagonalley") as db: + row = await db.fetchone( + "SELECT * FROM diagonalley.stalls WHERE id = ?", (stall_id,) + ) + return Stalls.from_row(row) if row else None + + +async def get_diagonalley_stalls(wallet_ids: Union[str, List[str]]) -> List[Stalls]: + if isinstance(wallet_ids, str): + wallet_ids = [wallet_ids] + + q = ",".join(["?"] * len(wallet_ids)) + rows = await db.fetchall( + f"SELECT * FROM diagonalley.stalls WHERE wallet IN ({q})", (*wallet_ids,) + ) + + for r in rows: + try: + x = httpx.get(r["stalladdress"] + "/" + r["ratingkey"]) + if x.status_code == 200: + await db.execute( + "UPDATE diagonalley.stalls SET online = ? WHERE id = ?", + ( + True, + r["id"], + ), + ) + else: + await db.execute( + "UPDATE diagonalley.stalls SET online = ? WHERE id = ?", + ( + False, + r["id"], + ), + ) + except: + print("An exception occurred") + q = ",".join(["?"] * len(wallet_ids)) + rows = await db.fetchall( + f"SELECT * FROM diagonalley.stalls WHERE wallet IN ({q})", (*wallet_ids,) + ) + return [Stalls.from_row(row) for row in rows] + + +async def delete_diagonalley_stall(stall_id: str) -> None: + await db.execute("DELETE FROM diagonalley.stalls WHERE id = ?", (stall_id,)) + + +###Orders + + +async def create_diagonalley_order( + *, + productid: str, + wallet: str, + product: str, + quantity: int, + shippingzone: str, + address: str, + email: str, + invoiceid: str, + paid: bool, + shipped: bool, +) -> Orders: + returning = "" if db.type == SQLITE else "RETURNING ID" + method = db.execute if db.type == SQLITE else db.fetchone + + order_id = urlsafe_short_hash() + result = await (method)( + f""" + INSERT INTO diagonalley.orders (id, productid, wallet, product, + quantity, shippingzone, address, email, invoiceid, paid, shipped) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + {returning} + """, + ( + order_id, + productid, + wallet, + product, + quantity, + shippingzone, + address, + email, + invoiceid, + False, + False, + ), + ) + if db.type == SQLITE: + order_id = result._result_proxy.lastrowid + else: + order_id = result[0] + + link = await get_diagonalley_order(order_id) + assert link, "Newly created link couldn't be retrieved" + return link + + +async def get_diagonalley_order(order_id: str) -> Optional[Orders]: + row = await db.fetchone( + "SELECT * FROM diagonalley.orders WHERE id = ?", (order_id,) + ) + return Orders.from_row(row) if row else None + + +async def get_diagonalley_orders(wallet_ids: Union[str, List[str]]) -> List[Orders]: + if isinstance(wallet_ids, str): + wallet_ids = [wallet_ids] + + q = ",".join(["?"] * len(wallet_ids)) + rows = await db.fetchall( + f"SELECT * FROM diagonalley.orders WHERE wallet IN ({q})", (*wallet_ids,) + ) + # + return [Orders.from_row(row) for row in rows] + + +async def delete_diagonalley_order(order_id: str) -> None: + await db.execute("DELETE FROM diagonalley.orders WHERE id = ?", (order_id,)) diff --git a/lnbits/extensions/diagonalley/migrations.py b/lnbits/extensions/diagonalley/migrations.py new file mode 100644 index 00000000..1523f398 --- /dev/null +++ b/lnbits/extensions/diagonalley/migrations.py @@ -0,0 +1,69 @@ +async def m001_initial(db): + """ + Initial products table. + """ + await db.execute( + """ + CREATE TABLE diagonalley.products ( + id TEXT PRIMARY KEY, + stall TEXT NOT NULL, + product TEXT NOT NULL, + categories TEXT NOT NULL, + description TEXT NOT NULL, + image TEXT NOT NULL, + price INTEGER NOT NULL, + quantity INTEGER NOT NULL + ); + """ + ) + + """ + Initial stalls table. + """ + await db.execute( + """ + CREATE TABLE diagonalley.stalls ( + id TEXT PRIMARY KEY, + wallet TEXT NOT NULL, + name TEXT NOT NULL, + publickey TEXT NOT NULL, + privatekey TEXT NOT NULL, + relays TEXT NOT NULL + ); + """ + ) + + """ + Initial zones table. + """ + await db.execute( + """ + CREATE TABLE diagonalley.zones ( + id TEXT PRIMARY KEY, + wallet TEXT NOT NULL, + cost TEXT NOT NULL, + countries TEXT NOT NULL + ); + """ + ) + + """ + Initial orders table. + """ + await db.execute( + """ + CREATE TABLE diagonalley.orders ( + id TEXT PRIMARY KEY, + productid TEXT NOT NULL, + wallet TEXT NOT NULL, + product TEXT NOT NULL, + quantity INTEGER NOT NULL, + shippingzone INTEGER NOT NULL, + address TEXT NOT NULL, + email TEXT NOT NULL, + invoiceid TEXT NOT NULL, + paid BOOLEAN NOT NULL, + shipped BOOLEAN NOT NULL + ); + """ + ) diff --git a/lnbits/extensions/diagonalley/models.py b/lnbits/extensions/diagonalley/models.py new file mode 100644 index 00000000..0f2a1d78 --- /dev/null +++ b/lnbits/extensions/diagonalley/models.py @@ -0,0 +1,57 @@ +from urllib.parse import urlparse, urlunparse, parse_qs, urlencode, ParseResult +from starlette.requests import Request +from fastapi.param_functions import Query +from typing import Optional, Dict +from lnbits.lnurl import encode as lnurl_encode # type: ignore +from lnurl.types import LnurlPayMetadata # type: ignore +from pydantic import BaseModel +import json +from sqlite3 import Row + + +class Stalls(BaseModel): + id: str = Query(None) + wallet: str = Query(None) + name: str = Query(None) + publickey: str = Query(None) + privatekey: str = Query(None) + relays: str = Query(None) + +class createStalls(BaseModel): + wallet: str = Query(None) + name: str = Query(None) + publickey: str = Query(None) + privatekey: str = Query(None) + relays: str = Query(None) + shippingzones: str = Query(None) + +class Products(BaseModel): + id: str = Query(None) + stall: str = Query(None) + product: str = Query(None) + categories: str = Query(None) + description: str = Query(None) + image: str = Query(None) + price: int = Query(0) + quantity: int = Query(0) + + +class Zones(BaseModel): + id: str = Query(None) + wallet: str = Query(None) + cost: str = Query(None) + countries: str = Query(None) + + +class Orders(BaseModel): + id: str = Query(None) + productid: str = Query(None) + stall: str = Query(None) + product: str = Query(None) + quantity: int = Query(0) + shippingzone: int = Query(0) + address: str = Query(None) + email: str = Query(None) + invoiceid: str = Query(None) + paid: bool + shipped: bool diff --git a/lnbits/extensions/diagonalley/static/js/index.js b/lnbits/extensions/diagonalley/static/js/index.js new file mode 100644 index 00000000..1a25edaa --- /dev/null +++ b/lnbits/extensions/diagonalley/static/js/index.js @@ -0,0 +1,824 @@ +/* globals Quasar, Vue, _, VueQrcode, windowMixin, LNbits, LOCALE */ + +Vue.component(VueQrcode.name, VueQrcode) + +const pica = window.pica() + +new Vue({ + el: '#vue', + mixins: [windowMixin], + data: function () { + return { + products: [], + orders: [], + stalls: [], + zones: [], + shippedModel: false, + shippingZoneOptions: [ + 'Australia', + 'Austria', + 'Belgium', + 'Brazil', + 'Canada', + 'Denmark', + 'Finland', + 'France*', + 'Germany', + 'Greece', + 'Hong Kong', + 'Hungary', + 'Ireland', + 'Indonesia', + 'Israel', + 'Italy', + 'Japan', + 'Kazakhstan', + 'Korea', + 'Luxembourg', + 'Malaysia', + 'Mexico', + 'Netherlands', + 'New Zealand', + 'Norway', + 'Poland', + 'Portugal', + 'Russia', + 'Saudi Arabia', + 'Singapore', + 'Spain', + 'Sweden', + 'Switzerland', + 'Thailand', + 'Turkey', + 'Ukraine', + 'United Kingdom**', + 'United States***', + 'Vietnam', + 'China' + ], + categories: [ + 'Fashion (clothing and accessories)', + 'Health (and beauty)', + 'Toys (and baby equipment)', + 'Media (Books and CDs)', + 'Groceries (Food and Drink)', + 'Technology (Phones and Computers)', + 'Home (furniture and accessories)', + 'Gifts (flowers, cards, etc)' + ], + relayOptions: [ + 'wss://nostr-relay.herokuapp.com/ws', + 'wss://nostr-relay.bigsun.xyz/ws', + 'wss://freedom-relay.herokuapp.com/ws' + ], + label: '', + ordersTable: { + columns: [ + { + name: 'product', + align: 'left', + label: 'Product', + field: 'product' + }, + { + name: 'quantity', + align: 'left', + label: 'Quantity', + field: 'quantity' + }, + { + name: 'address', + align: 'left', + label: 'Address', + field: 'address' + }, + { + name: 'invoiceid', + align: 'left', + label: 'InvoiceID', + field: 'invoiceid' + }, + {name: 'paid', align: 'left', label: 'Paid', field: 'paid'}, + {name: 'shipped', align: 'left', label: 'Shipped', field: 'shipped'} + ], + pagination: { + rowsPerPage: 10 + } + }, + productsTable: { + columns: [ + { + name: 'stall', + align: 'left', + label: 'Stall', + field: 'stall' + }, + { + name: 'product', + align: 'left', + label: 'Product', + field: 'product' + }, + { + name: 'description', + align: 'left', + label: 'Description', + field: 'description' + }, + { + name: 'categories', + align: 'left', + label: 'Categories', + field: 'categories' + }, + {name: 'price', align: 'left', label: 'Price', field: 'price'}, + { + name: 'quantity', + align: 'left', + label: 'Quantity', + field: 'quantity' + }, + {name: 'id', align: 'left', label: 'ID', field: 'id'} + ], + pagination: { + rowsPerPage: 10 + } + }, + stallTable: { + columns: [ + { + name: 'id', + align: 'left', + label: 'ID', + field: 'id' + }, + { + name: 'name', + align: 'left', + label: 'Name', + field: 'name' + }, + { + name: 'wallet', + align: 'left', + label: 'Wallet', + field: 'wallet' + }, + { + name: 'publickey', + align: 'left', + label: 'Public key', + field: 'publickey' + }, + { + name: 'privatekey', + align: 'left', + label: 'Private key', + field: 'privatekey' + } + ], + pagination: { + rowsPerPage: 10 + } + }, + zonesTable: { + columns: [ + { + name: 'id', + align: 'left', + label: 'ID', + field: 'id' + }, + { + name: 'countries', + align: 'left', + label: 'Countries', + field: 'countries' + }, + { + name: 'cost', + align: 'left', + label: 'Cost', + field: 'cost' + } + ], + pagination: { + rowsPerPage: 10 + } + }, + productDialog: { + show: false, + data: {} + }, + stallDialog: { + show: false, + data: {} + }, + zoneDialog: { + show: false, + data: {} + }, + shopDialog: { + show: false, + data: {activate: false} + }, + orderDialog: { + show: false, + data: {} + }, + relayDialog: { + show: false, + data: {} + } + } + }, + computed: { + categoryOther: function () { + cats = trim(this.productDialog.data.categories.split(',')) + for (let i = 0; i < cats.length; i++) { + if (cats[i] == 'Others') { + return true + } + } + return false + } + }, + methods: { + //////////////////////////////////////// + ////////////////STALLS////////////////// + //////////////////////////////////////// + getStalls: function () { + var self = this + LNbits.api + .request( + 'GET', + '/diagonalley/api/v1/stalls?all_wallets', + this.g.user.wallets[0].inkey + ) + .then(function (response) { + self.stalls = response.data.map(function (obj) { + console.log(obj) + return mapDiagonAlley(obj) + }) + }) + }, + openStallUpdateDialog: function (linkId) { + var self = this + var link = _.findWhere(self.stalls, {id: linkId}) + + this.stallDialog.data = _.clone(link._data) + this.stallDialog.show = true + }, + sendStallFormData: function () { + if (this.stallDialog.data.id) { + } else { + var data = { + name: this.stallDialog.data.name, + wallet: this.stallDialog.data.wallet, + publickey: this.stallDialog.data.publickey, + privatekey: this.stallDialog.data.privatekey, + relays: this.stallDialog.data.relays + } + } + + if (this.stallDialog.data.id) { + this.updateStall(this.stallDialog.data) + } else { + this.createStall(data) + } + }, + updateStall: function (data) { + var self = this + LNbits.api + .request( + 'PUT', + '/diagonalley/api/v1/stalls' + data.id, + _.findWhere(self.g.user.wallets, { + id: self.stallDialog.data.wallet + }).inkey, + _.pick(data, 'name', 'wallet', 'publickey', 'privatekey') + ) + .then(function (response) { + self.stalls = _.reject(self.stalls, function (obj) { + return obj.id == data.id + }) + self.stalls.push(mapDiagonAlley(response.data)) + self.stallDialog.show = false + self.stallDialog.data = {} + data = {} + }) + .catch(function (error) { + LNbits.utils.notifyApiError(error) + }) + }, + createStall: function (data) { + var self = this + LNbits.api + .request( + 'POST', + '/diagonalley/api/v1/stalls', + _.findWhere(self.g.user.wallets, { + id: self.stallDialog.data.wallet + }).inkey, + data + ) + .then(function (response) { + self.stalls.push(mapDiagonAlley(response.data)) + self.stallDialog.show = false + self.stallDialog.data = {} + data = {} + }) + .catch(function (error) { + LNbits.utils.notifyApiError(error) + }) + }, + deleteStall: function (stallId) { + var self = this + var stall = _.findWhere(self.stalls, {id: stallId}) + + LNbits.utils + .confirmDialog('Are you sure you want to delete this Stall link?') + .onOk(function () { + LNbits.api + .request( + 'DELETE', + '/diagonalley/api/v1/stalls/' + stallId, + _.findWhere(self.g.user.wallets, {id: stall.wallet}).inkey + ) + .then(function (response) { + self.stalls = _.reject(self.stalls, function (obj) { + return obj.id == stallId + }) + }) + .catch(function (error) { + LNbits.utils.notifyApiError(error) + }) + }) + }, + exportStallsCSV: function () { + LNbits.utils.exportCSV(this.stallsTable.columns, this.stalls) + }, + //////////////////////////////////////// + ///////////////PRODUCTS///////////////// + //////////////////////////////////////// + getProducts: function () { + var self = this + + LNbits.api + .request( + 'GET', + '/diagonalley/api/v1/products?all_stalls', + this.g.user.wallets[0].inkey + ) + .then(function (response) { + self.products = response.data.map(function (obj) { + return mapDiagonAlley(obj) + }) + }) + }, + openProductUpdateDialog: function (linkId) { + var self = this + var link = _.findWhere(self.products, {id: linkId}) + + self.productDialog.data = _.clone(link._data) + self.productDialog.show = true + }, + sendProductFormData: function () { + if (this.productDialog.data.id) { + } else { + var data = { + product: this.productDialog.data.product, + categories: + this.productDialog.data.categories + + this.productDialog.categoriesextra, + description: this.productDialog.data.description, + image: this.productDialog.data.image, + price: this.productDialog.data.price, + quantity: this.productDialog.data.quantity + } + } + if (this.productDialog.data.id) { + this.updateProduct(this.productDialog.data) + } else { + this.createProduct(data) + } + }, + imageAdded(file) { + let blobURL = URL.createObjectURL(file) + let image = new Image() + image.src = blobURL + image.onload = async () => { + let canvas = document.createElement('canvas') + canvas.setAttribute('width', 100) + canvas.setAttribute('height', 100) + await pica.resize(image, canvas, { + quality: 0, + alpha: true, + unsharpAmount: 95, + unsharpRadius: 0.9, + unsharpThreshold: 70 + }) + this.productDialog.data.image = canvas.toDataURL() + this.productDialog = {...this.productDialog} + } + }, + imageCleared() { + this.productDialog.data.image = null + this.productDialog = {...this.productDialog} + }, + updateProduct: function (data) { + var self = this + LNbits.api + .request( + 'PUT', + '/diagonalley/api/v1/products' + data.id, + _.findWhere(self.g.user.wallets, { + id: self.productDialog.data.wallet + }).inkey, + _.pick( + data, + 'shopname', + 'relayaddress', + 'shippingzone1', + 'zone1cost', + 'shippingzone2', + 'zone2cost', + 'email' + ) + ) + .then(function (response) { + self.products = _.reject(self.products, function (obj) { + return obj.id == data.id + }) + self.products.push(mapDiagonAlley(response.data)) + self.productDialog.show = false + self.productDialog.data = {} + }) + .catch(function (error) { + LNbits.utils.notifyApiError(error) + }) + }, + createProduct: function (data) { + var self = this + LNbits.api + .request( + 'POST', + '/diagonalley/api/v1/products', + _.findWhere(self.g.user.wallets, { + id: self.productDialog.data.wallet + }).inkey, + data + ) + .then(function (response) { + self.products.push(mapDiagonAlley(response.data)) + self.productDialog.show = false + self.productDialog.data = {} + }) + .catch(function (error) { + LNbits.utils.notifyApiError(error) + }) + }, + deleteProduct: function (productId) { + var self = this + var product = _.findWhere(this.products, {id: productId}) + + LNbits.utils + .confirmDialog('Are you sure you want to delete this products link?') + .onOk(function () { + LNbits.api + .request( + 'DELETE', + '/diagonalley/api/v1/products/' + productId, + _.findWhere(self.g.user.wallets, {id: product.wallet}).inkey + ) + .then(function (response) { + self.products = _.reject(self.products, function (obj) { + return obj.id == productId + }) + }) + .catch(function (error) { + LNbits.utils.notifyApiError(error) + }) + }) + }, + exportProductsCSV: function () { + LNbits.utils.exportCSV(this.productsTable.columns, this.products) + }, + //////////////////////////////////////// + //////////////////ZONE////////////////// + //////////////////////////////////////// + getZones: function () { + var self = this + + LNbits.api + .request( + 'GET', + '/diagonalley/api/v1/zones?all_wallets', + this.g.user.wallets[0].inkey + ) + .then(function (response) { + self.zones = response.data.map(function (obj) { + return mapDiagonAlley(obj) + }) + }) + }, + openZoneUpdateDialog: function (linkId) { + var self = this + var link = _.findWhere(self.zones, {id: linkId}) + + this.zoneDialog.data = _.clone(link._data) + this.zoneDialog.show = true + }, + sendZoneFormData: function () { + if (this.zoneDialog.data.id) { + } else { + var data = { + countries: toString(this.zoneDialog.data.countries), + cost: parseInt(this.zoneDialog.data.cost) + } + } + + if (this.zoneDialog.data.id) { + this.updateZone(this.zoneDialog.data) + } else { + this.createZone(data) + } + }, + updateZone: function (data) { + var self = this + LNbits.api + .request( + 'PUT', + '/diagonalley/api/v1/zones' + data.id, + _.findWhere(self.g.user.wallets, { + id: self.zoneDialog.data.wallet + }).inkey, + _.pick(data, 'countries', 'cost') + ) + .then(function (response) { + self.zones = _.reject(self.zones, function (obj) { + return obj.id == data.id + }) + self.zones.push(mapDiagonAlley(response.data)) + self.zoneDialog.show = false + self.zoneDialog.data = {} + data = {} + }) + .catch(function (error) { + LNbits.utils.notifyApiError(error) + }) + }, + createZone: function (data) { + var self = this + console.log(self.g.user.wallets[0]) + console.log(data) + LNbits.api + .request( + 'POST', + '/diagonalley/api/v1/zones', + self.g.user.wallets[0].inkey, + data + ) + .then(function (response) { + self.zones.push(mapDiagonAlley(response.data)) + self.zoneDialog.show = false + self.zoneDialog.data = {} + data = {} + }) + .catch(function (error) { + LNbits.utils.notifyApiError(error) + }) + }, + deleteZone: function (zoneId) { + var self = this + var zone = _.findWhere(self.zones, {id: zoneId}) + + LNbits.utils + .confirmDialog('Are you sure you want to delete this Zone link?') + .onOk(function () { + LNbits.api + .request( + 'DELETE', + '/diagonalley/api/v1/zones/' + zoneId, + _.findWhere(self.g.user.wallets, {id: zone.wallet}).inkey + ) + .then(function (response) { + self.zones = _.reject(self.zones, function (obj) { + return obj.id == zoneId + }) + }) + .catch(function (error) { + LNbits.utils.notifyApiError(error) + }) + }) + }, + exportZonesCSV: function () { + LNbits.utils.exportCSV(this.zonesTable.columns, this.zones) + }, + //////////////////////////////////////// + //////////////////SHOP////////////////// + //////////////////////////////////////// + getShops: function () { + var self = this + + LNbits.api + .request( + 'GET', + '/diagonalley/api/v1/shops?all_wallets', + this.g.user.wallets[0].inkey + ) + .then(function (response) { + self.shops = response.data.map(function (obj) { + return mapDiagonAlley(obj) + }) + }) + }, + openShopUpdateDialog: function (linkId) { + var self = this + var link = _.findWhere(self.shops, {id: linkId}) + + this.shopDialog.data = _.clone(link._data) + this.shopDialog.show = true + }, + sendShopFormData: function () { + if (this.shopDialog.data.id) { + } else { + var data = { + countries: this.shopDialog.data.countries, + cost: this.shopDialog.data.cost + } + } + + if (this.shopDialog.data.id) { + this.updateZone(this.shopDialog.data) + } else { + this.createZone(data) + } + }, + updateShop: function (data) { + var self = this + LNbits.api + .request( + 'PUT', + '/diagonalley/api/v1/shops' + data.id, + _.findWhere(self.g.user.wallets, { + id: self.shopDialog.data.wallet + }).inkey, + _.pick(data, 'countries', 'cost') + ) + .then(function (response) { + self.shops = _.reject(self.shops, function (obj) { + return obj.id == data.id + }) + self.shops.push(mapDiagonAlley(response.data)) + self.shopDialog.show = false + self.shopDialog.data = {} + data = {} + }) + .catch(function (error) { + LNbits.utils.notifyApiError(error) + }) + }, + createShop: function (data) { + var self = this + console.log('cuntywoo') + LNbits.api + .request( + 'POST', + '/diagonalley/api/v1/shops', + _.findWhere(self.g.user.wallets, { + id: self.shopDialog.data.wallet + }).inkey, + data + ) + .then(function (response) { + self.shops.push(mapDiagonAlley(response.data)) + self.shopDialog.show = false + self.shopDialog.data = {} + data = {} + }) + .catch(function (error) { + LNbits.utils.notifyApiError(error) + }) + }, + deleteShop: function (shopId) { + var self = this + var shop = _.findWhere(self.shops, {id: shopId}) + + LNbits.utils + .confirmDialog('Are you sure you want to delete this Shop link?') + .onOk(function () { + LNbits.api + .request( + 'DELETE', + '/diagonalley/api/v1/shops/' + shopId, + _.findWhere(self.g.user.wallets, {id: shop.wallet}).inkey + ) + .then(function (response) { + self.shops = _.reject(self.shops, function (obj) { + return obj.id == shopId + }) + }) + .catch(function (error) { + LNbits.utils.notifyApiError(error) + }) + }) + }, + exportShopsCSV: function () { + LNbits.utils.exportCSV(this.shopsTable.columns, this.shops) + }, + //////////////////////////////////////// + ////////////////ORDERS////////////////// + //////////////////////////////////////// + getOrders: function () { + var self = this + + LNbits.api + .request( + 'GET', + '/diagonalley/api/v1/orders?all_wallets', + this.g.user.wallets[0].inkey + ) + .then(function (response) { + self.orders = response.data.map(function (obj) { + return mapDiagonAlley(obj) + }) + }) + }, + createOrder: function () { + var data = { + address: this.orderDialog.data.address, + email: this.orderDialog.data.email, + quantity: this.orderDialog.data.quantity, + shippingzone: this.orderDialog.data.shippingzone + } + var self = this + + LNbits.api + .request( + 'POST', + '/diagonalley/api/v1/orders', + _.findWhere(self.g.user.wallets, {id: self.orderDialog.data.wallet}) + .inkey, + data + ) + .then(function (response) { + self.orders.push(mapDiagonAlley(response.data)) + self.orderDialog.show = false + self.orderDialog.data = {} + }) + .catch(function (error) { + LNbits.utils.notifyApiError(error) + }) + }, + deleteOrder: function (orderId) { + var self = this + var order = _.findWhere(self.orders, {id: orderId}) + + LNbits.utils + .confirmDialog('Are you sure you want to delete this order link?') + .onOk(function () { + LNbits.api + .request( + 'DELETE', + '/diagonalley/api/v1/orders/' + orderId, + _.findWhere(self.g.user.wallets, {id: order.wallet}).inkey + ) + .then(function (response) { + self.orders = _.reject(self.orders, function (obj) { + return obj.id == orderId + }) + }) + .catch(function (error) { + LNbits.utils.notifyApiError(error) + }) + }) + }, + shipOrder: function (order_id) { + var self = this + + LNbits.api + .request( + 'GET', + '/diagonalley/api/v1/orders/shipped/' + order_id, + this.g.user.wallets[0].inkey + ) + .then(function (response) { + self.orders = response.data.map(function (obj) { + return mapDiagonAlley(obj) + }) + }) + }, + exportOrdersCSV: function () { + LNbits.utils.exportCSV(this.ordersTable.columns, this.orders) + } + }, + created: function () { + if (this.g.user.wallets.length) { + this.getStalls() + this.getProducts() + this.getZones() + this.getOrders() + } + } +}) diff --git a/lnbits/extensions/diagonalley/tasks.py b/lnbits/extensions/diagonalley/tasks.py new file mode 100644 index 00000000..3fee63d9 --- /dev/null +++ b/lnbits/extensions/diagonalley/tasks.py @@ -0,0 +1,29 @@ +import asyncio + +from lnbits.core.models import Payment +from lnbits.tasks import register_invoice_listener + +from .crud import get_ticket, set_ticket_paid + + +async def wait_for_paid_invoices(): + invoice_queue = asyncio.Queue() + register_invoice_listener(invoice_queue) + + while True: + payment = await invoice_queue.get() + await on_invoice_paid(payment) + + +async def on_invoice_paid(payment: Payment) -> None: + if "lnticket" != payment.extra.get("tag"): + # not a lnticket invoice + return + + ticket = await get_ticket(payment.checking_id) + if not ticket: + print("this should never happen", payment) + return + + await payment.set_pending(False) + await set_ticket_paid(payment.payment_hash) \ No newline at end of file diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/_api_docs.html b/lnbits/extensions/diagonalley/templates/diagonalley/_api_docs.html new file mode 100644 index 00000000..530c89e8 --- /dev/null +++ b/lnbits/extensions/diagonalley/templates/diagonalley/_api_docs.html @@ -0,0 +1,129 @@ + + + +
+ Diagon Alley: Decentralised Market-Stalls +
+

+ Each Stall has its own keys!
+

    +
  1. Create Shipping Zones you're willing to ship to
  2. +
  3. Create a Stall to list yiur products on
  4. +
  5. Create products to put on the Stall
  6. +
  7. List stalls on a simple frontend shop page, or point at Nostr shop client key
  8. +
+ Make a list of products to sell, point your list of products at a public + relay. Buyers browse your products on the relay, and pay you directly. + Ratings are managed by the relay. Your stall can be listed in multiple + relays, even over TOR, if you wish to be anonymous.
+ More information on the + Diagon Alley Protocol
+ + Created by, Ben Arc +

+
+
+
+ + + + + + GET + /diagonalley/api/v1/stall/products/<relay_id> +
Body (application/json)
+
+ Returns 201 CREATED (application/json) +
+ Product JSON list +
Curl example
+ curl -X GET {{ request.url_root + }}api/v1/stall/products/<relay_id> +
+
+
+ + + + POST + /diagonalley/api/v1/stall/order/<relay_id> +
Body (application/json)
+ {"id": <string>, "address": <string>, "shippingzone": + <integer>, "email": <string>, "quantity": + <integer>} +
+ Returns 201 CREATED (application/json) +
+ {"checking_id": <string>,"payment_request": + <string>} +
Curl example
+ curl -X POST {{ request.url_root + }}api/v1/stall/order/<relay_id> -d '{"id": <product_id&>, + "email": <customer_email>, "address": <customer_address>, + "quantity": 2, "shippingzone": 1}' -H "Content-type: application/json" + +
+
+
+ + + + GET + /diagonalley/api/v1/stall/checkshipped/<checking_id> +
Headers
+
+ Returns 200 OK (application/json) +
+ {"shipped": <boolean>} +
Curl example
+ curl -X GET {{ request.url_root + }}api/v1/stall/checkshipped/<checking_id> -H "Content-type: + application/json" +
+
+
+
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/index.html b/lnbits/extensions/diagonalley/templates/diagonalley/index.html new file mode 100644 index 00000000..98405f6d --- /dev/null +++ b/lnbits/extensions/diagonalley/templates/diagonalley/index.html @@ -0,0 +1,634 @@ +{% extends "base.html" %} {% from "macros.jinja" import window_vars with context +%} {% block page %} +
+ + + + + + + + +
+
+ +
+
+ +
+
+ + + + + + + + +
+ Update Product + + Create Product + + Cancel +
+
+
+
+ + + + + + +
+ Update Shipping Zone + Create Shipping Zone + + Cancel +
+
+
+
+ + + + + + + +
+ Update Relay + Launch + + Cancel +
+
+
+
+ + + + + + + +
+
+ Generate keys +
+
+ Restore keys +
+
+ + + + + + + +
+ Update Stall + Create Stall + Cancel +
+
+
+
+ +
+ + + + Product List a product + + Shipping Zone Create a shipping zone + + Stall + Create a stall to list products on + Launch frontend shop (not Nostr) + Makes a simple frontend shop for your stalls + + + + + +
+
+
Orders
+
+
+ Export to CSV +
+
+ + {% raw %} + + + {% endraw %} + +
+
+ + + +
+
+
Products
+
+
+ Export to CSV +
+
+ + {% raw %} + + + {% endraw %} + +
+
+ + + +
+
+
Stalls
+
+
+ Export to CSV +
+
+ + {% raw %} + + + {% endraw %} + +
+
+ + + +
+
+
Shipping Zones
+
+
+ Export to CSV +
+
+ + {% raw %} + + + {% endraw %} + +
+
+
+ +
+ + +
+ LNbits Diagon Alley Extension, powered by Nostr +
+
+ + + {% include "diagonalley/_api_docs.html" %} + +
+ + +
Messages (example)
+
+ + + +
+
+ OrderID:87h87h
KJBIBYBUYBUF90898....
+ OrderID:NIUHB7
79867KJGJHGVFYFV....
+
+
+
+ + +
+ + +
+ + +
+
+
+{% endblock %} {% block scripts %} {{ window_vars(user) }} + + +{% endblock %} diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/stall.html b/lnbits/extensions/diagonalley/templates/diagonalley/stall.html new file mode 100644 index 00000000..768bedfe --- /dev/null +++ b/lnbits/extensions/diagonalley/templates/diagonalley/stall.html @@ -0,0 +1,9 @@ +

+
+
diff --git a/lnbits/extensions/diagonalley/views.py b/lnbits/extensions/diagonalley/views.py
new file mode 100644
index 00000000..2deed72b
--- /dev/null
+++ b/lnbits/extensions/diagonalley/views.py
@@ -0,0 +1,44 @@
+
+from typing import List
+
+from fastapi import Request, WebSocket, WebSocketDisconnect
+from fastapi.params import Depends
+from fastapi.templating import Jinja2Templates
+from starlette.responses import HTMLResponse  # type: ignore
+
+from http import HTTPStatus
+import json
+from lnbits.decorators import check_user_exists, validate_uuids
+from lnbits.extensions.diagonalley import diagonalley_ext
+
+from .crud import (
+    create_diagonalley_product,
+    get_diagonalley_product,
+    get_diagonalley_products,
+    delete_diagonalley_product,
+    create_diagonalley_order,
+    get_diagonalley_order,
+    get_diagonalley_orders,
+    update_diagonalley_product,
+)
+
+
+@diagonalley_ext.get("/", response_class=HTMLResponse)
+@validate_uuids(["usr"], required=True)
+@check_user_exists(request: Request)
+async def index():
+    return await render_template("diagonalley/index.html", user=g.user)
+
+
+@diagonalley_ext.get("/", response_class=HTMLResponse)
+async def display(request: Request, stall_id):
+    product = await get_diagonalley_products(stall_id)
+    if not product:
+        abort(HTTPStatus.NOT_FOUND, "Stall does not exist.")
+
+    return await render_template(
+        "diagonalley/stall.html",
+        stall=json.dumps(
+            [product._asdict() for product in await get_diagonalley_products(stall_id)]
+        ),
+    )
diff --git a/lnbits/extensions/diagonalley/views_api.py b/lnbits/extensions/diagonalley/views_api.py
new file mode 100644
index 00000000..4de2799e
--- /dev/null
+++ b/lnbits/extensions/diagonalley/views_api.py
@@ -0,0 +1,348 @@
+from http import HTTPStatus
+
+from fastapi import Request
+from fastapi.param_functions import Query
+from fastapi.params import Depends
+from starlette.exceptions import HTTPException
+
+from lnbits.core.crud import get_user
+from lnbits.decorators import api_check_wallet_key, api_validate_post_request
+
+from . import diagonalley_ext
+from .crud import (
+    create_diagonalley_product,
+    get_diagonalley_product,
+    get_diagonalley_products,
+    delete_diagonalley_product,
+    create_diagonalley_zone,
+    update_diagonalley_zone,
+    get_diagonalley_zone,
+    get_diagonalley_zones,
+    delete_diagonalley_zone,
+    create_diagonalley_stall,
+    update_diagonalley_stall,
+    get_diagonalley_stall,
+    get_diagonalley_stalls,
+    delete_diagonalley_stall,
+    create_diagonalley_order,
+    get_diagonalley_order,
+    get_diagonalley_orders,
+    update_diagonalley_product,
+    delete_diagonalley_order,
+)
+from lnbits.core.services import create_invoice
+from base64 import urlsafe_b64encode
+from uuid import uuid4
+
+# from lnbits.db import open_ext_db
+
+from . import db
+from .models import Products, Orders, Stalls
+
+### Products
+
+@copilot_ext.get("/api/v1/copilot/{copilot_id}")
+async def api_copilot_retrieve(
+    req: Request,
+    copilot_id: str = Query(None),
+    wallet: WalletTypeInfo = Depends(get_key_type),
+):
+    copilot = await get_copilot(copilot_id)
+    if not copilot:
+        raise HTTPException(
+            status_code=HTTPStatus.NOT_FOUND, detail="Copilot not found"
+        )
+    if not copilot.lnurl_toggle:
+        return copilot.dict()
+    return {**copilot.dict(), **{"lnurl": copilot.lnurl(req)}}
+
+
+@diagonalley_ext.get("/api/v1/products")
+async def api_diagonalley_products(
+    req: Request,
+    wallet: WalletTypeInfo = Depends(get_key_type),
+):
+    wallet_ids = [wallet.wallet.id]
+
+    if "all_stalls" in request.args:
+        wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
+
+    return ([product._asdict() for product in await get_diagonalley_products(wallet_ids)])
+
+
+@diagonalley_ext.post("/api/v1/products")
+@diagonalley_ext.put("/api/v1/products/{product_id}")
+async def api_diagonalley_product_create(
+    data: Products, 
+    product_id=: str = Query(None), 
+    wallet: WalletTypeInfo = Depends(get_key_type)
+    ):
+
+    if product_id:
+        product = await get_diagonalley_product(product_id)
+
+        if not product:
+            return ({"message": "Withdraw product does not exist."}))
+
+        if product.wallet != wallet.wallet.id:
+            return ({"message": "Not your withdraw product."}))
+
+        product = await update_diagonalley_product(product_id, data)
+    else:
+        product = await create_diagonalley_product(wallet_id=wallet.wallet.id, data)
+
+    return ({**product._asdict()}))
+
+
+@diagonalley_ext.route("/api/v1/products/{product_id}")
+async def api_diagonalley_products_delete(product_id, wallet: WalletTypeInfo = Depends(require_admin_key)):
+    product = await get_diagonalley_product(product_id)
+
+    if not product:
+        return ({"message": "Product does not exist."})
+
+    if product.wallet != wallet.wallet.id:
+        return ({"message": "Not your Diagon Alley."})
+
+    await delete_diagonalley_product(product_id)
+
+    raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
+
+
+# # # Shippingzones
+
+
+@diagonalley_ext.get("/api/v1/zones")
+async def api_diagonalley_zones(wallet: WalletTypeInfo = Depends(get_key_type)):
+    wallet_ids = [wallet.wallet.id]
+
+    if "all_wallets" in request.args:
+        wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
+
+    return ([zone._asdict() for zone in await get_diagonalley_zones(wallet_ids)]))
+
+
+@diagonalley_ext.post("/api/v1/zones")
+@diagonalley_ext.put("/api/v1/zones/{zone_id}")
+async def api_diagonalley_zone_create(
+    data: Zones, 
+    zone_id: str = Query(None),  
+    wallet: WalletTypeInfo = Depends(get_key_type)
+    ):
+    if zone_id:
+        zone = await get_diagonalley_zone(zone_id)
+
+        if not zone:
+            return ({"message": "Zone does not exist."}))
+
+        if zone.wallet != walley.wallet.id:
+            return ({"message": "Not your record."}))
+
+        zone = await update_diagonalley_zone(zone_id, data)
+    else:
+        zone = await create_diagonalley_zone(wallet=wallet.wallet.id, data)
+
+    return ({**zone._asdict()}))
+
+
+@diagonalley_ext.delete("/api/v1/zones/{zone_id}")
+async def api_diagonalley_zone_delete(zone_id: str = Query(None),  wallet: WalletTypeInfo = Depends(require_admin_key)):
+    zone = await get_diagonalley_zone(zone_id)
+
+    if not zone:
+        return ({"message": "zone does not exist."})
+
+    if zone.wallet != wallet.wallet.id:
+        return ({"message": "Not your zone."})
+
+    await delete_diagonalley_zone(zone_id)
+
+    raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
+
+
+# # # Stalls
+
+
+@diagonalley_ext.get("/api/v1/stalls")
+async def api_diagonalley_stalls(wallet: WalletTypeInfo = Depends(get_key_type)):
+    wallet_ids = [wallet.wallet.id]
+
+    if "all_wallets" in request.args:
+        wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
+
+    return ([stall._asdict() for stall in await get_diagonalley_stalls(wallet_ids)])
+
+
+@diagonalley_ext.post("/api/v1/stalls")
+@diagonalley_ext.put("/api/v1/stalls/{stall_id}")
+async def api_diagonalley_stall_create(data: createStalls, stall_id: str = Query(None), wallet: WalletTypeInfo = Depends(get_key_type)):
+
+    if stall_id:
+        stall = await get_diagonalley_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_diagonalley_stall(stall_id, data)
+    else:
+        stall = await create_diagonalley_stall(wallet_id=wallet.wallet.id, data)
+
+    return ({**stall._asdict()}))
+
+
+@diagonalley_ext.delete("/api/v1/stalls/{stall_id}")
+async def api_diagonalley_stall_delete(stall_id: str = Query(None), wallet: WalletTypeInfo = Depends(require_admin_key)):
+    stall = await get_diagonalley_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_diagonalley_stall(stall_id)
+
+    raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
+
+
+###Orders
+
+
+@diagonalley_ext.get("/api/v1/orders")
+async def api_diagonalley_orders(wallet: WalletTypeInfo = Depends(get_key_type)):
+    wallet_ids = [wallet.wallet.id]
+
+    if "all_wallets" in request.args:
+        wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
+
+    try:
+        return ([order._asdict() for order in await get_diagonalley_orders(wallet_ids)])
+    except:
+        return ({"message": "We could not retrieve the orders."}))
+
+
+@diagonalley_ext.post("/api/v1/orders")
+
+async def api_diagonalley_order_create(data: createOrders, wallet: WalletTypeInfo = Depends(get_key_type)):
+    order = await create_diagonalley_order(wallet_id=wallet.wallet.id, data)
+    return ({**order._asdict()})
+
+
+@diagonalley_ext.delete("/api/v1/orders/{order_id}")
+async def api_diagonalley_order_delete(order_id: str = Query(None), wallet: WalletTypeInfo = Depends(get_key_type)):
+    order = await get_diagonalley_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_diagonalley_order(order_id)
+
+    raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
+
+
+@diagonalley_ext.get("/api/v1/orders/paid/{order_id}")
+async def api_diagonalley_order_paid(order_id: str = Query(None), wallet: WalletTypeInfo = Depends(require_admin_key)):
+    await db.execute(
+        "UPDATE diagonalley.orders SET paid = ? WHERE id = ?",
+        (
+            True,
+            order_id,
+        ),
+    )
+    return "", HTTPStatus.OK
+
+
+@diagonalley_ext.get("/api/v1/orders/shipped/{order_id}")
+async def api_diagonalley_order_shipped(order_id: str = Query(None), wallet: WalletTypeInfo = Depends(get_key_type)):
+    await db.execute(
+        "UPDATE diagonalley.orders SET shipped = ? WHERE id = ?",
+        (
+            True,
+            order_id,
+        ),
+    )
+    order = await db.fetchone(
+        "SELECT * FROM diagonalley.orders WHERE id = ?", (order_id,)
+    )
+
+    return ([order._asdict() for order in get_diagonalley_orders(order["wallet"])]))
+
+
+###List products based on stall id
+
+
+@diagonalley_ext.get("/api/v1/stall/products/{stall_id}")
+async def api_diagonalley_stall_products(stall_id: str = Query(None), wallet: WalletTypeInfo = Depends(get_key_type)):
+
+    rows = await db.fetchone(
+        "SELECT * FROM diagonalley.stalls WHERE id = ?", (stall_id,)
+    )
+    print(rows[1])
+    if not rows:
+        return ({"message": "Stall does not exist."})
+
+    products = db.fetchone(
+        "SELECT * FROM diagonalley.products WHERE wallet = ?", (rows[1],)
+    )
+    if not products:
+        return ({"message": "No products"})
+
+    return ([products._asdict() for products in await get_diagonalley_products(rows[1])])
+
+
+###Check a product has been shipped
+
+
+@diagonalley_ext.get("/api/v1/stall/checkshipped/{checking_id}")
+async def api_diagonalley_stall_checkshipped(checking_id: str = Query(None), wallet: WalletTypeInfo = Depends(get_key_type)):
+    rows = await db.fetchone(
+        "SELECT * FROM diagonalley.orders WHERE invoiceid = ?", (checking_id,)
+    )
+    return ({"shipped": rows["shipped"]})
+
+
+###Place order
+
+
+@diagonalley_ext.post("/api/v1/stall/order/{stall_id}")
+async def api_diagonalley_stall_order(data:createOrders, wallet: WalletTypeInfo = Depends(get_key_type)):
+    product = await get_diagonalley_product(data.id)
+    shipping = await get_diagonalley_stall(stall_id)
+
+    if data.shippingzone == 1:
+        shippingcost = shipping.zone1cost
+    else:
+        shippingcost = shipping.zone2cost
+
+    checking_id, payment_request = await create_invoice(
+        wallet_id=product.wallet,
+        amount=shippingcost + (data.quantity * product.price),
+        memo=data.id,
+    )
+    selling_id = urlsafe_b64encode(uuid4().bytes_le).decode("utf-8")
+    await db.execute(
+        """
+            INSERT INTO diagonalley.orders (id, productid, wallet, product, quantity, shippingzone, address, email, invoiceid, paid, shipped)
+            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+            """,
+        (
+            selling_id,
+            data.id,
+            product.wallet,
+            product.product,
+            data.quantity,
+            data.shippingzone,
+            data.address,
+            data.email,
+            checking_id,
+            False,
+            False,
+        ),
+    )
+    return ({"checking_id": checking_id, "payment_request": payment_request}))

From 903e0d5b0b2f2f4ead59a3a769585c91bf8354c2 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos 
Date: Thu, 27 Jan 2022 14:58:58 +0000
Subject: [PATCH 02/30] products

---
 lnbits/extensions/diagonalley/views_api.py | 75 ++++++++++++----------
 1 file changed, 40 insertions(+), 35 deletions(-)

diff --git a/lnbits/extensions/diagonalley/views_api.py b/lnbits/extensions/diagonalley/views_api.py
index 4de2799e..9df2c1d8 100644
--- a/lnbits/extensions/diagonalley/views_api.py
+++ b/lnbits/extensions/diagonalley/views_api.py
@@ -1,4 +1,6 @@
+from base64 import urlsafe_b64encode
 from http import HTTPStatus
+from uuid import uuid4
 
 from fastapi import Request
 from fastapi.param_functions import Query
@@ -6,41 +8,44 @@ from fastapi.params import Depends
 from starlette.exceptions import HTTPException
 
 from lnbits.core.crud import get_user
-from lnbits.decorators import api_check_wallet_key, api_validate_post_request
+from lnbits.core.services import create_invoice
+from lnbits.decorators import (
+    WalletTypeInfo,
+    api_check_wallet_key,
+    api_validate_post_request,
+    get_key_type,
+    require_admin_key,
+)
 
-from . import diagonalley_ext
+from . import db, diagonalley_ext
 from .crud import (
-    create_diagonalley_product,
-    get_diagonalley_product,
-    get_diagonalley_products,
-    delete_diagonalley_product,
-    create_diagonalley_zone,
-    update_diagonalley_zone,
-    get_diagonalley_zone,
-    get_diagonalley_zones,
-    delete_diagonalley_zone,
-    create_diagonalley_stall,
-    update_diagonalley_stall,
-    get_diagonalley_stall,
-    get_diagonalley_stalls,
-    delete_diagonalley_stall,
     create_diagonalley_order,
+    create_diagonalley_product,
+    create_diagonalley_stall,
+    create_diagonalley_zone,
+    delete_diagonalley_order,
+    delete_diagonalley_product,
+    delete_diagonalley_stall,
+    delete_diagonalley_zone,
     get_diagonalley_order,
     get_diagonalley_orders,
+    get_diagonalley_product,
+    get_diagonalley_products,
+    get_diagonalley_stall,
+    get_diagonalley_stalls,
+    get_diagonalley_zone,
+    get_diagonalley_zones,
     update_diagonalley_product,
-    delete_diagonalley_order,
+    update_diagonalley_stall,
+    update_diagonalley_zone,
 )
-from lnbits.core.services import create_invoice
-from base64 import urlsafe_b64encode
-from uuid import uuid4
+from .models import Orders, Products, Stalls
 
 # from lnbits.db import open_ext_db
 
-from . import db
-from .models import Products, Orders, Stalls
 
 ### Products
-
+"""
 @copilot_ext.get("/api/v1/copilot/{copilot_id}")
 async def api_copilot_retrieve(
     req: Request,
@@ -55,26 +60,27 @@ async def api_copilot_retrieve(
     if not copilot.lnurl_toggle:
         return copilot.dict()
     return {**copilot.dict(), **{"lnurl": copilot.lnurl(req)}}
-
+"""
 
 @diagonalley_ext.get("/api/v1/products")
 async def api_diagonalley_products(
     req: Request,
     wallet: WalletTypeInfo = Depends(get_key_type),
+    all_stalls: bool = Query(False)
 ):
     wallet_ids = [wallet.wallet.id]
 
-    if "all_stalls" in request.args:
+    if all_stalls:
         wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
 
-    return ([product._asdict() for product in await get_diagonalley_products(wallet_ids)])
+    return ([product.dict() for product in await get_diagonalley_products(wallet_ids)])
 
 
 @diagonalley_ext.post("/api/v1/products")
 @diagonalley_ext.put("/api/v1/products/{product_id}")
 async def api_diagonalley_product_create(
     data: Products, 
-    product_id=: str = Query(None), 
+    product_id: str = Query(None), 
     wallet: WalletTypeInfo = Depends(get_key_type)
     ):
 
@@ -82,19 +88,19 @@ async def api_diagonalley_product_create(
         product = await get_diagonalley_product(product_id)
 
         if not product:
-            return ({"message": "Withdraw product does not exist."}))
+            return ({"message": "Withdraw product does not exist."})
 
         if product.wallet != wallet.wallet.id:
-            return ({"message": "Not your withdraw product."}))
+            return ({"message": "Not your withdraw product."})
 
         product = await update_diagonalley_product(product_id, data)
     else:
         product = await create_diagonalley_product(wallet_id=wallet.wallet.id, data)
 
-    return ({**product._asdict()}))
+    return product.dict()
 
 
-@diagonalley_ext.route("/api/v1/products/{product_id}")
+@diagonalley_ext.delete("/api/v1/products/{product_id}")
 async def api_diagonalley_products_delete(product_id, wallet: WalletTypeInfo = Depends(require_admin_key)):
     product = await get_diagonalley_product(product_id)
 
@@ -105,7 +111,6 @@ async def api_diagonalley_products_delete(product_id, wallet: WalletTypeInfo = D
         return ({"message": "Not your Diagon Alley."})
 
     await delete_diagonalley_product(product_id)
-
     raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
 
 
@@ -113,13 +118,13 @@ async def api_diagonalley_products_delete(product_id, wallet: WalletTypeInfo = D
 
 
 @diagonalley_ext.get("/api/v1/zones")
-async def api_diagonalley_zones(wallet: WalletTypeInfo = Depends(get_key_type)):
+async def api_diagonalley_zones(wallet: WalletTypeInfo = Depends(get_key_type), all_wallets: bool = Query(False)):
     wallet_ids = [wallet.wallet.id]
 
-    if "all_wallets" in request.args:
+    if all_wallets:
         wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
 
-    return ([zone._asdict() for zone in await get_diagonalley_zones(wallet_ids)]))
+    return ([zone.dict() for zone in await get_diagonalley_zones(wallet_ids)])
 
 
 @diagonalley_ext.post("/api/v1/zones")

From 8db457bd423b000d123e91f0c031b02bea0e8d69 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos 
Date: Thu, 27 Jan 2022 15:26:55 +0000
Subject: [PATCH 03/30] zones

---
 lnbits/extensions/diagonalley/crud.py      | 63 +++++++++-------------
 lnbits/extensions/diagonalley/models.py    | 41 +++++++++-----
 lnbits/extensions/diagonalley/views_api.py | 26 +++++----
 3 files changed, 64 insertions(+), 66 deletions(-)

diff --git a/lnbits/extensions/diagonalley/crud.py b/lnbits/extensions/diagonalley/crud.py
index c6ce8222..7dc02cd4 100644
--- a/lnbits/extensions/diagonalley/crud.py
+++ b/lnbits/extensions/diagonalley/crud.py
@@ -1,17 +1,17 @@
+import re
 from base64 import urlsafe_b64encode
-from uuid import uuid4
 from typing import List, Optional, Union
+from uuid import uuid4
 
-from lnbits.settings import WALLET
+import httpx
 
 # from lnbits.db import open_ext_db
 from lnbits.db import SQLITE
-from . import db
-from .models import Products, Orders, Stalls, Zones
-
-import httpx
 from lnbits.helpers import urlsafe_short_hash
-import re
+from lnbits.settings import WALLET
+
+from . import db
+from .models import Orders, Products, Stalls, Zones, createProduct, createZones
 
 regex = re.compile(
     r"^(?:http|ftp)s?://"  # http:// or https://
@@ -28,35 +28,27 @@ regex = re.compile(
 
 
 async def create_diagonalley_product(
-    *,
-    stall_id: str,
-    product: str,
-    categories: str,
-    description: str,
-    image: Optional[str] = None,
-    price: int,
-    quantity: int,
-    shippingzones: str,
+    data: createProduct
 ) -> Products:
-    returning = "" if db.type == SQLITE else "RETURNING ID"
-    method = db.execute if db.type == SQLITE else db.fetchone
+    # returning = "" if db.type == SQLITE else "RETURNING ID"
+    # method = db.execute if db.type == SQLITE else db.fetchone
     product_id = urlsafe_short_hash()
     # with open_ext_db("diagonalley") as db:
-    result = await (method)(
+    # result = await (method)(
+    await db.execute(
         f"""
-        INSERT INTO diagonalley.products (id, stall, product, categories, description, image, price, quantity, shippingzones)
+        INSERT INTO diagonalley.products (id, stall, product, categories, description, image, price, quantity)
         VALUES (?, ?, ?, ?, ?, ?, ?, ?)
-        {returning}
         """,
         (
             product_id,
-            stall_id,
-            product,
-            categories,
-            description,
-            image,
-            price,
-            quantity,
+            data.stall,
+            data.product,
+            data.categories,
+            data.description,
+            data.image,
+            data.price,
+            data.quantity,
         ),
     )
     product = await get_diagonalley_product(product_id)
@@ -109,17 +101,11 @@ async def delete_diagonalley_product(product_id: str) -> None:
 
 
 async def create_diagonalley_zone(
-    *,
-    wallet: Optional[str] = None,
-    cost: Optional[int] = 0,
-    countries: Optional[str] = None,
+    wallet,
+    data: createZones
 ) -> Zones:
-
-    returning = "" if db.type == SQLITE else "RETURNING ID"
-    method = db.execute if db.type == SQLITE else db.fetchone
-
     zone_id = urlsafe_short_hash()
-    result = await (method)(
+    await db.execute(
         f"""
         INSERT INTO diagonalley.zones (
             id,
@@ -129,9 +115,8 @@ async def create_diagonalley_zone(
 
         )
         VALUES (?, ?, ?, ?)
-        {returning}
         """,
-        (zone_id, wallet, cost, countries),
+        (zone_id, wallet, data.cost, data.countries),
     )
 
     zone = await get_diagonalley_zone(zone_id)
diff --git a/lnbits/extensions/diagonalley/models.py b/lnbits/extensions/diagonalley/models.py
index 0f2a1d78..bd667a2f 100644
--- a/lnbits/extensions/diagonalley/models.py
+++ b/lnbits/extensions/diagonalley/models.py
@@ -1,12 +1,15 @@
-from urllib.parse import urlparse, urlunparse, parse_qs, urlencode, ParseResult
-from starlette.requests import Request
+import json
+from lib2to3.pytree import Base
+from sqlite3 import Row
+from typing import Dict, Optional
+from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse
+
 from fastapi.param_functions import Query
-from typing import Optional, Dict
-from lnbits.lnurl import encode as lnurl_encode  # type: ignore
 from lnurl.types import LnurlPayMetadata  # type: ignore
 from pydantic import BaseModel
-import json
-from sqlite3 import Row
+from starlette.requests import Request
+
+from lnbits.lnurl import encode as lnurl_encode  # type: ignore
 
 
 class Stalls(BaseModel):
@@ -25,23 +28,35 @@ class createStalls(BaseModel):
     relays: str = Query(None)
     shippingzones: str = Query(None)
 
-class Products(BaseModel):
-    id: str = Query(None)
+class createProduct(BaseModel):
     stall: str = Query(None)
     product: str = Query(None)
     categories: str = Query(None)
     description: str = Query(None)
     image: str = Query(None)
-    price: int = Query(0)
-    quantity: int = Query(0)
+    price: int = Query(0, ge=0)
+    quantity: int = Query(0, ge=0)
 
+class Products(BaseModel):
+    id: str
+    stall: str
+    product: str
+    categories: str
+    description: str
+    image: str
+    price: int
+    quantity: int
 
-class Zones(BaseModel):
-    id: str = Query(None)
-    wallet: str = Query(None)
+class createZones(BaseModel):
     cost: str = Query(None)
     countries: str = Query(None)
 
+class Zones(BaseModel):
+    id: str
+    wallet: str
+    cost: str
+    countries: str
+
 
 class Orders(BaseModel):
     id: str = Query(None)
diff --git a/lnbits/extensions/diagonalley/views_api.py b/lnbits/extensions/diagonalley/views_api.py
index 9df2c1d8..1ad83936 100644
--- a/lnbits/extensions/diagonalley/views_api.py
+++ b/lnbits/extensions/diagonalley/views_api.py
@@ -39,7 +39,7 @@ from .crud import (
     update_diagonalley_stall,
     update_diagonalley_zone,
 )
-from .models import Orders, Products, Stalls
+from .models import Orders, Products, Stalls, Zones, createProduct, createZones
 
 # from lnbits.db import open_ext_db
 
@@ -79,7 +79,7 @@ async def api_diagonalley_products(
 @diagonalley_ext.post("/api/v1/products")
 @diagonalley_ext.put("/api/v1/products/{product_id}")
 async def api_diagonalley_product_create(
-    data: Products, 
+    data: createProduct, 
     product_id: str = Query(None), 
     wallet: WalletTypeInfo = Depends(get_key_type)
     ):
@@ -93,9 +93,9 @@ async def api_diagonalley_product_create(
         if product.wallet != wallet.wallet.id:
             return ({"message": "Not your withdraw product."})
 
-        product = await update_diagonalley_product(product_id, data)
+        product = await update_diagonalley_product(product_id, **data.dict())
     else:
-        product = await create_diagonalley_product(wallet_id=wallet.wallet.id, data)
+        product = await create_diagonalley_product(data=data)
 
     return product.dict()
 
@@ -126,11 +126,10 @@ async def api_diagonalley_zones(wallet: WalletTypeInfo = Depends(get_key_type),
 
     return ([zone.dict() for zone in await get_diagonalley_zones(wallet_ids)])
 
-
 @diagonalley_ext.post("/api/v1/zones")
 @diagonalley_ext.put("/api/v1/zones/{zone_id}")
 async def api_diagonalley_zone_create(
-    data: Zones, 
+    data: createZones, 
     zone_id: str = Query(None),  
     wallet: WalletTypeInfo = Depends(get_key_type)
     ):
@@ -138,20 +137,20 @@ async def api_diagonalley_zone_create(
         zone = await get_diagonalley_zone(zone_id)
 
         if not zone:
-            return ({"message": "Zone does not exist."}))
+            return ({"message": "Zone does not exist."})
 
-        if zone.wallet != walley.wallet.id:
-            return ({"message": "Not your record."}))
+        if zone.wallet != wallet.wallet.id:
+            return ({"message": "Not your record."})
 
-        zone = await update_diagonalley_zone(zone_id, data)
+        zone = await update_diagonalley_zone(zone_id, **data.dict())
     else:
-        zone = await create_diagonalley_zone(wallet=wallet.wallet.id, data)
+        zone = await create_diagonalley_zone(wallet=wallet.wallet.id, data=data)
 
-    return ({**zone._asdict()}))
+    return zone.dict()
 
 
 @diagonalley_ext.delete("/api/v1/zones/{zone_id}")
-async def api_diagonalley_zone_delete(zone_id: str = Query(None),  wallet: WalletTypeInfo = Depends(require_admin_key)):
+async def api_diagonalley_zone_delete(zone_id, wallet: WalletTypeInfo = Depends(require_admin_key)):
     zone = await get_diagonalley_zone(zone_id)
 
     if not zone:
@@ -161,7 +160,6 @@ async def api_diagonalley_zone_delete(zone_id: str = Query(None),  wallet: Walle
         return ({"message": "Not your zone."})
 
     await delete_diagonalley_zone(zone_id)
-
     raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
 
 

From d6d33c6a22c1e5ed9c336ec1337018407eb1562e Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos 
Date: Thu, 27 Jan 2022 16:18:12 +0000
Subject: [PATCH 04/30] stalls

---
 lnbits/extensions/diagonalley/crud.py      | 34 ++++++++++++----------
 lnbits/extensions/diagonalley/models.py    | 25 ++++++++--------
 lnbits/extensions/diagonalley/views_api.py | 29 +++++++++++-------
 3 files changed, 50 insertions(+), 38 deletions(-)

diff --git a/lnbits/extensions/diagonalley/crud.py b/lnbits/extensions/diagonalley/crud.py
index 7dc02cd4..3b58b129 100644
--- a/lnbits/extensions/diagonalley/crud.py
+++ b/lnbits/extensions/diagonalley/crud.py
@@ -11,7 +11,15 @@ from lnbits.helpers import urlsafe_short_hash
 from lnbits.settings import WALLET
 
 from . import db
-from .models import Orders, Products, Stalls, Zones, createProduct, createZones
+from .models import (
+    Orders,
+    Products,
+    Stalls,
+    Zones,
+    createProduct,
+    createStalls,
+    createZones,
+)
 
 regex = re.compile(
     r"^(?:http|ftp)s?://"  # http:// or https://
@@ -185,20 +193,10 @@ async def delete_diagonalley_zone(zone_id: str) -> None:
 
 
 async def create_diagonalley_stall(
-    *,
-    wallet: str,
-    name: str,
-    publickey: str,
-    privatekey: str,
-    relays: str,
-    shippingzones: str,
+    data: createStalls
 ) -> Stalls:
-
-    returning = "" if db.type == SQLITE else "RETURNING ID"
-    method = db.execute if db.type == SQLITE else db.fetchone
-
     stall_id = urlsafe_short_hash()
-    result = await (method)(
+    await db.execute(
         f"""
         INSERT INTO diagonalley.stalls (
             id,
@@ -210,9 +208,15 @@ async def create_diagonalley_stall(
             shippingzones
         )
         VALUES (?, ?, ?, ?, ?, ?, ?)
-        {returning}
         """,
-        (stall_id, wallet, name, publickey, privatekey, relays, shippingzones),
+        (
+            stall_id,
+            data.wallet,
+            data.name,
+            data.publickey,
+            data.privatekey,
+            data.relays,
+            data.shippingzones),
     )
 
     stall = await get_diagonalley_stall(stall_id)
diff --git a/lnbits/extensions/diagonalley/models.py b/lnbits/extensions/diagonalley/models.py
index bd667a2f..4c674c8a 100644
--- a/lnbits/extensions/diagonalley/models.py
+++ b/lnbits/extensions/diagonalley/models.py
@@ -13,20 +13,21 @@ from lnbits.lnurl import encode as lnurl_encode  # type: ignore
 
 
 class Stalls(BaseModel):
-    id: str = Query(None)
-    wallet: str = Query(None)
-    name: str = Query(None)
-    publickey: str = Query(None)
-    privatekey: str = Query(None)
-    relays: str = Query(None)
+    id: str
+    wallet: str
+    name: str
+    publickey: str
+    privatekey: str
+    relays: str
+    shippingzones: str
 
 class createStalls(BaseModel):
-    wallet: str = Query(None)
-    name: str = Query(None)
-    publickey: str = Query(None)
-    privatekey: str = Query(None)
-    relays: str = Query(None)
-    shippingzones: str = Query(None)
+    wallet: str = Query(...)
+    name: str = Query(...)
+    publickey: str = Query(...)
+    privatekey: str = Query(...)
+    relays: str = Query(...)
+    shippingzones: str = Query(...)
 
 class createProduct(BaseModel):
     stall: str = Query(None)
diff --git a/lnbits/extensions/diagonalley/views_api.py b/lnbits/extensions/diagonalley/views_api.py
index 1ad83936..ccac9b24 100644
--- a/lnbits/extensions/diagonalley/views_api.py
+++ b/lnbits/extensions/diagonalley/views_api.py
@@ -39,7 +39,15 @@ from .crud import (
     update_diagonalley_stall,
     update_diagonalley_zone,
 )
-from .models import Orders, Products, Stalls, Zones, createProduct, createZones
+from .models import (
+    Orders,
+    Products,
+    Stalls,
+    Zones,
+    createProduct,
+    createStalls,
+    createZones,
+)
 
 # from lnbits.db import open_ext_db
 
@@ -167,33 +175,33 @@ async def api_diagonalley_zone_delete(zone_id, wallet: WalletTypeInfo = Depends(
 
 
 @diagonalley_ext.get("/api/v1/stalls")
-async def api_diagonalley_stalls(wallet: WalletTypeInfo = Depends(get_key_type)):
+async def api_diagonalley_stalls(wallet: WalletTypeInfo = Depends(get_key_type), all_wallets: bool = Query(False)):
     wallet_ids = [wallet.wallet.id]
 
-    if "all_wallets" in request.args:
+    if all_wallets:
         wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
 
-    return ([stall._asdict() for stall in await get_diagonalley_stalls(wallet_ids)])
+    return ([stall.dict() for stall in await get_diagonalley_stalls(wallet_ids)])
 
 
 @diagonalley_ext.post("/api/v1/stalls")
 @diagonalley_ext.put("/api/v1/stalls/{stall_id}")
-async def api_diagonalley_stall_create(data: createStalls, stall_id: str = Query(None), wallet: WalletTypeInfo = Depends(get_key_type)):
+async def api_diagonalley_stall_create(data: createStalls, stall_id = None, wallet: WalletTypeInfo = Depends(get_key_type)):
 
     if stall_id:
         stall = await get_diagonalley_stall(stall_id)
 
         if not stall:
-            return ({"message": "Withdraw stall does not exist."}))
+            return ({"message": "Withdraw stall does not exist."})
 
         if stall.wallet != wallet.wallet.id:
-            return ({"message": "Not your withdraw stall."}))
+            return ({"message": "Not your withdraw stall."})
 
-        stall = await update_diagonalley_stall(stall_id, data)
+        stall = await update_diagonalley_stall(stall_id, **data.dict())
     else:
-        stall = await create_diagonalley_stall(wallet_id=wallet.wallet.id, data)
+        stall = await create_diagonalley_stall(data=data)
 
-    return ({**stall._asdict()}))
+    return stall.dict()
 
 
 @diagonalley_ext.delete("/api/v1/stalls/{stall_id}")
@@ -207,7 +215,6 @@ async def api_diagonalley_stall_delete(stall_id: str = Query(None), wallet: Wall
         return ({"message": "Not your Stall."})
 
     await delete_diagonalley_stall(stall_id)
-
     raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
 
 

From cad896f909645e2dedfd31e45f5494648aebb7e3 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos 
Date: Fri, 28 Jan 2022 15:11:31 +0000
Subject: [PATCH 05/30] orders

---
 lnbits/extensions/diagonalley/crud.py      | 62 +++++++++-------------
 lnbits/extensions/diagonalley/models.py    | 28 ++++++----
 lnbits/extensions/diagonalley/views_api.py | 42 +++++++--------
 3 files changed, 65 insertions(+), 67 deletions(-)

diff --git a/lnbits/extensions/diagonalley/crud.py b/lnbits/extensions/diagonalley/crud.py
index 3b58b129..4cf14014 100644
--- a/lnbits/extensions/diagonalley/crud.py
+++ b/lnbits/extensions/diagonalley/crud.py
@@ -16,6 +16,7 @@ from .models import (
     Products,
     Stalls,
     Zones,
+    createOrder,
     createProduct,
     createStalls,
     createZones,
@@ -83,7 +84,7 @@ async def get_diagonalley_product(product_id: str) -> Optional[Products]:
     row = await db.fetchone(
         "SELECT * FROM diagonalley.products WHERE id = ?", (product_id,)
     )
-    return Products.from_row(row) if row else None
+    return Products(**row) if row else None
 
 
 async def get_diagonalley_products(wallet_ids: Union[str, List[str]]) -> List[Products]:
@@ -98,7 +99,7 @@ async def get_diagonalley_products(wallet_ids: Union[str, List[str]]) -> List[Pr
         """,
         (*wallet_ids,),
     )
-    return [Products.from_row(row) for row in rows]
+    return [Products(**row) for row in rows]
 
 
 async def delete_diagonalley_product(product_id: str) -> None:
@@ -139,12 +140,12 @@ async def update_diagonalley_zone(zone_id: str, **kwargs) -> Optional[Zones]:
         (*kwargs.values(), zone_id),
     )
     row = await db.fetchone("SELECT * FROM diagonalley.zones WHERE id = ?", (zone_id,))
-    return Zones.from_row(row) if row else None
+    return Zones(**row) if row else None
 
 
 async def get_diagonalley_zone(zone_id: str) -> Optional[Zones]:
     row = await db.fetchone("SELECT * FROM diagonalley.zones WHERE id = ?", (zone_id,))
-    return Zones.from_row(row) if row else None
+    return Zones(**row) if row else None
 
 
 async def get_diagonalley_zones(wallet_ids: Union[str, List[str]]) -> List[Zones]:
@@ -182,7 +183,7 @@ async def get_diagonalley_zones(wallet_ids: Union[str, List[str]]) -> List[Zones
     rows = await db.fetchall(
         f"SELECT * FROM diagonalley.zones WHERE wallet IN ({q})", (*wallet_ids,)
     )
-    return [Zones.from_row(row) for row in rows]
+    return [Zones(**row) for row in rows]
 
 
 async def delete_diagonalley_zone(zone_id: str) -> None:
@@ -233,7 +234,7 @@ async def update_diagonalley_stall(stall_id: str, **kwargs) -> Optional[Stalls]:
     row = await db.fetchone(
         "SELECT * FROM diagonalley.stalls WHERE id = ?", (stall_id,)
     )
-    return Stalls.from_row(row) if row else None
+    return Stalls(**row) if row else None
 
 
 async def get_diagonalley_stall(stall_id: str) -> Optional[Stalls]:
@@ -266,7 +267,7 @@ async def get_diagonalley_stall(stall_id: str) -> Optional[Stalls]:
     row = await db.fetchone(
         "SELECT * FROM diagonalley.stalls WHERE id = ?", (stall_id,)
     )
-    return Stalls.from_row(row) if row else None
+    return Stalls(**row) if row else None
 
 
 async def get_diagonalley_stalls(wallet_ids: Union[str, List[str]]) -> List[Stalls]:
@@ -303,7 +304,7 @@ async def get_diagonalley_stalls(wallet_ids: Union[str, List[str]]) -> List[Stal
     rows = await db.fetchall(
         f"SELECT * FROM diagonalley.stalls WHERE wallet IN ({q})", (*wallet_ids,)
     )
-    return [Stalls.from_row(row) for row in rows]
+    return [Stalls(**row) for row in rows]
 
 
 async def delete_diagonalley_stall(stall_id: str) -> None:
@@ -314,47 +315,34 @@ async def delete_diagonalley_stall(stall_id: str) -> None:
 
 
 async def create_diagonalley_order(
-    *,
-    productid: str,
-    wallet: str,
-    product: str,
-    quantity: int,
-    shippingzone: str,
-    address: str,
-    email: str,
-    invoiceid: str,
-    paid: bool,
-    shipped: bool,
+    data: createOrder
 ) -> Orders:
-    returning = "" if db.type == SQLITE else "RETURNING ID"
-    method = db.execute if db.type == SQLITE else db.fetchone
 
     order_id = urlsafe_short_hash()
-    result = await (method)(
+    await db.execute(
         f"""
             INSERT INTO diagonalley.orders (id, productid, wallet, product,
             quantity, shippingzone, address, email, invoiceid, paid, shipped)
             VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
-            {returning}
             """,
         (
             order_id,
-            productid,
-            wallet,
-            product,
-            quantity,
-            shippingzone,
-            address,
-            email,
-            invoiceid,
+            data.productid,
+            data.wallet,
+            data.product,
+            data.quantity,
+            data.shippingzone,
+            data.address,
+            data.email,
+            data.invoiceid,
             False,
             False,
         ),
     )
-    if db.type == SQLITE:
-        order_id = result._result_proxy.lastrowid
-    else:
-        order_id = result[0]
+    # if db.type == SQLITE:
+    #     order_id = result._result_proxy.lastrowid
+    # else:
+    #     order_id = result[0]
 
     link = await get_diagonalley_order(order_id)
     assert link, "Newly created link couldn't be retrieved"
@@ -365,7 +353,7 @@ async def get_diagonalley_order(order_id: str) -> Optional[Orders]:
     row = await db.fetchone(
         "SELECT * FROM diagonalley.orders WHERE id = ?", (order_id,)
     )
-    return Orders.from_row(row) if row else None
+    return Orders(**row) if row else None
 
 
 async def get_diagonalley_orders(wallet_ids: Union[str, List[str]]) -> List[Orders]:
@@ -377,7 +365,7 @@ async def get_diagonalley_orders(wallet_ids: Union[str, List[str]]) -> List[Orde
         f"SELECT * FROM diagonalley.orders WHERE wallet IN ({q})", (*wallet_ids,)
     )
     #
-    return [Orders.from_row(row) for row in rows]
+    return [Orders(**row) for row in rows]
 
 
 async def delete_diagonalley_order(order_id: str) -> None:
diff --git a/lnbits/extensions/diagonalley/models.py b/lnbits/extensions/diagonalley/models.py
index 4c674c8a..1a975e10 100644
--- a/lnbits/extensions/diagonalley/models.py
+++ b/lnbits/extensions/diagonalley/models.py
@@ -59,15 +59,25 @@ class Zones(BaseModel):
     countries: str
 
 
+class createOrder(BaseModel):
+    productid: str = Query(...)
+    stall: str = Query(...)
+    product: str = Query(...)
+    quantity: int = Query(..., ge=1)
+    shippingzone: int = Query(...)
+    address: str = Query(...)
+    email: str = Query(...)
+    invoiceid: str = Query(...)
+
 class Orders(BaseModel):
-    id: str = Query(None)
-    productid: str = Query(None)
-    stall: str = Query(None)
-    product: str = Query(None)
-    quantity: int = Query(0)
-    shippingzone: int = Query(0)
-    address: str = Query(None)
-    email: str = Query(None)
-    invoiceid: str = Query(None)
+    id: str
+    productid: str
+    stall: str
+    product: str
+    quantity: int
+    shippingzone: int
+    address: str
+    email: str
+    invoiceid: str
     paid: bool
     shipped: bool
diff --git a/lnbits/extensions/diagonalley/views_api.py b/lnbits/extensions/diagonalley/views_api.py
index ccac9b24..165d2a0c 100644
--- a/lnbits/extensions/diagonalley/views_api.py
+++ b/lnbits/extensions/diagonalley/views_api.py
@@ -44,6 +44,7 @@ from .models import (
     Products,
     Stalls,
     Zones,
+    createOrder,
     createProduct,
     createStalls,
     createZones,
@@ -87,8 +88,8 @@ async def api_diagonalley_products(
 @diagonalley_ext.post("/api/v1/products")
 @diagonalley_ext.put("/api/v1/products/{product_id}")
 async def api_diagonalley_product_create(
+    product_id, 
     data: createProduct, 
-    product_id: str = Query(None), 
     wallet: WalletTypeInfo = Depends(get_key_type)
     ):
 
@@ -186,7 +187,7 @@ async def api_diagonalley_stalls(wallet: WalletTypeInfo = Depends(get_key_type),
 
 @diagonalley_ext.post("/api/v1/stalls")
 @diagonalley_ext.put("/api/v1/stalls/{stall_id}")
-async def api_diagonalley_stall_create(data: createStalls, stall_id = None, wallet: WalletTypeInfo = Depends(get_key_type)):
+async def api_diagonalley_stall_create(data: createStalls, stall_id: str = Query(None), wallet: WalletTypeInfo = Depends(get_key_type)):
 
     if stall_id:
         stall = await get_diagonalley_stall(stall_id)
@@ -222,23 +223,22 @@ async def api_diagonalley_stall_delete(stall_id: str = Query(None), wallet: Wall
 
 
 @diagonalley_ext.get("/api/v1/orders")
-async def api_diagonalley_orders(wallet: WalletTypeInfo = Depends(get_key_type)):
+async def api_diagonalley_orders(wallet: WalletTypeInfo = Depends(get_key_type), all_wallets: bool = Query(False)):
     wallet_ids = [wallet.wallet.id]
 
-    if "all_wallets" in request.args:
+    if all_wallets:
         wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
 
     try:
-        return ([order._asdict() for order in await get_diagonalley_orders(wallet_ids)])
+        return ([order.dict() for order in await get_diagonalley_orders(wallet_ids)])
     except:
-        return ({"message": "We could not retrieve the orders."}))
+        return ({"message": "We could not retrieve the orders."})
 
 
 @diagonalley_ext.post("/api/v1/orders")
-
-async def api_diagonalley_order_create(data: createOrders, wallet: WalletTypeInfo = Depends(get_key_type)):
-    order = await create_diagonalley_order(wallet_id=wallet.wallet.id, data)
-    return ({**order._asdict()})
+async def api_diagonalley_order_create(data: createOrder, wallet: WalletTypeInfo = Depends(get_key_type)):
+    order = await create_diagonalley_order(wallet_id=wallet.wallet.id, data=data)
+    return order.dict()
 
 
 @diagonalley_ext.delete("/api/v1/orders/{order_id}")
@@ -281,7 +281,7 @@ async def api_diagonalley_order_shipped(order_id: str = Query(None), wallet: Wal
         "SELECT * FROM diagonalley.orders WHERE id = ?", (order_id,)
     )
 
-    return ([order._asdict() for order in get_diagonalley_orders(order["wallet"])]))
+    return ([order.dict() for order in get_diagonalley_orders(order["wallet"])])
 
 
 ###List products based on stall id
@@ -303,14 +303,14 @@ async def api_diagonalley_stall_products(stall_id: str = Query(None), wallet: Wa
     if not products:
         return ({"message": "No products"})
 
-    return ([products._asdict() for products in await get_diagonalley_products(rows[1])])
+    return ([products.dict() for products in await get_diagonalley_products(rows[1])])
 
 
 ###Check a product has been shipped
 
 
 @diagonalley_ext.get("/api/v1/stall/checkshipped/{checking_id}")
-async def api_diagonalley_stall_checkshipped(checking_id: str = Query(None), wallet: WalletTypeInfo = Depends(get_key_type)):
+async def api_diagonalley_stall_checkshipped(checking_id, wallet: WalletTypeInfo = Depends(get_key_type)):
     rows = await db.fetchone(
         "SELECT * FROM diagonalley.orders WHERE invoiceid = ?", (checking_id,)
     )
@@ -321,19 +321,19 @@ async def api_diagonalley_stall_checkshipped(checking_id: str = Query(None), wal
 
 
 @diagonalley_ext.post("/api/v1/stall/order/{stall_id}")
-async def api_diagonalley_stall_order(data:createOrders, wallet: WalletTypeInfo = Depends(get_key_type)):
-    product = await get_diagonalley_product(data.id)
+async def api_diagonalley_stall_order(stall_id, data: createOrder, wallet: WalletTypeInfo = Depends(get_key_type)):
+    product = await get_diagonalley_product(data.productid)
     shipping = await get_diagonalley_stall(stall_id)
 
     if data.shippingzone == 1:
-        shippingcost = shipping.zone1cost
+        shippingcost = shipping.zone1cost #missing in model
     else:
-        shippingcost = shipping.zone2cost
+        shippingcost = shipping.zone2cost #missing in model
 
     checking_id, payment_request = await create_invoice(
         wallet_id=product.wallet,
         amount=shippingcost + (data.quantity * product.price),
-        memo=data.id,
+        memo=shipping.wallet,
     )
     selling_id = urlsafe_b64encode(uuid4().bytes_le).decode("utf-8")
     await db.execute(
@@ -343,8 +343,8 @@ async def api_diagonalley_stall_order(data:createOrders, wallet: WalletTypeInfo
             """,
         (
             selling_id,
-            data.id,
-            product.wallet,
+            data.productid,
+            product.wallet, #doesn't exist in model
             product.product,
             data.quantity,
             data.shippingzone,
@@ -355,4 +355,4 @@ async def api_diagonalley_stall_order(data:createOrders, wallet: WalletTypeInfo
             False,
         ),
     )
-    return ({"checking_id": checking_id, "payment_request": payment_request}))
+    return ({"checking_id": checking_id, "payment_request": payment_request})

From 5048080ff671799a363e19c055ffa443c23c8f03 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos 
Date: Fri, 28 Jan 2022 16:22:54 +0000
Subject: [PATCH 06/30] UI fires up

---
 lnbits/extensions/diagonalley/__init__.py  | 35 +++++++++++---
 lnbits/extensions/diagonalley/tasks.py     |  6 +--
 lnbits/extensions/diagonalley/views.py     | 55 +++++++++-------------
 lnbits/extensions/diagonalley/views_api.py |  8 +---
 4 files changed, 55 insertions(+), 49 deletions(-)

diff --git a/lnbits/extensions/diagonalley/__init__.py b/lnbits/extensions/diagonalley/__init__.py
index 720c55c8..cab65685 100644
--- a/lnbits/extensions/diagonalley/__init__.py
+++ b/lnbits/extensions/diagonalley/__init__.py
@@ -1,16 +1,37 @@
-from quart import Blueprint
+import asyncio
+
+from fastapi import APIRouter
+from fastapi.staticfiles import StaticFiles
+
 from lnbits.db import Database
+from lnbits.helpers import template_renderer
+from lnbits.tasks import catch_everything_and_restart
 
 db = Database("ext_diagonalley")
 
-diagonalley_ext: Blueprint = Blueprint(
-    "diagonalley", __name__, static_folder="static", template_folder="templates"
+diagonalley_static_files = [
+    {
+        "path": "/diagonalley/static",
+        "app": StaticFiles(directory="lnbits/extensions/diagonalley/static"),
+        "name": "diagonalley_static",
+    }
+]
+
+diagonalley_ext: APIRouter = APIRouter(
+    prefix="/diagonalley", tags=["diagonalley"]
+    # "diagonalley", __name__, static_folder="static", template_folder="templates"
 )
 
-from .views_api import *  # noqa
+def diagonalley_renderer():
+    return template_renderer(["lnbits/extensions/diagonalley/templates"])
+
+
+from .tasks import wait_for_paid_invoices
 from .views import *  # noqa
+from .views_api import *  # noqa
 
-from .tasks import register_listeners
-from lnbits.tasks import record_async
 
-diagonalley_ext.record(record_async(register_listeners))
+def diagonalley_start():
+    loop = asyncio.get_event_loop()
+    loop.create_task(catch_everything_and_restart(wait_for_paid_invoices))
+
diff --git a/lnbits/extensions/diagonalley/tasks.py b/lnbits/extensions/diagonalley/tasks.py
index 3fee63d9..bcbb7025 100644
--- a/lnbits/extensions/diagonalley/tasks.py
+++ b/lnbits/extensions/diagonalley/tasks.py
@@ -3,8 +3,6 @@ import asyncio
 from lnbits.core.models import Payment
 from lnbits.tasks import register_invoice_listener
 
-from .crud import get_ticket, set_ticket_paid
-
 
 async def wait_for_paid_invoices():
     invoice_queue = asyncio.Queue()
@@ -16,6 +14,7 @@ async def wait_for_paid_invoices():
 
 
 async def on_invoice_paid(payment: Payment) -> None:
+    """
     if "lnticket" != payment.extra.get("tag"):
         # not a lnticket invoice
         return
@@ -26,4 +25,5 @@ async def on_invoice_paid(payment: Payment) -> None:
         return
 
     await payment.set_pending(False)
-    await set_ticket_paid(payment.payment_hash)
\ No newline at end of file
+    await set_ticket_paid(payment.payment_hash)
+    """
diff --git a/lnbits/extensions/diagonalley/views.py b/lnbits/extensions/diagonalley/views.py
index 2deed72b..ae0899ca 100644
--- a/lnbits/extensions/diagonalley/views.py
+++ b/lnbits/extensions/diagonalley/views.py
@@ -1,44 +1,35 @@
 
-from typing import List
-
-from fastapi import Request, WebSocket, WebSocketDisconnect
-from fastapi.params import Depends
-from fastapi.templating import Jinja2Templates
-from starlette.responses import HTMLResponse  # type: ignore
-
 from http import HTTPStatus
-import json
-from lnbits.decorators import check_user_exists, validate_uuids
-from lnbits.extensions.diagonalley import diagonalley_ext
 
-from .crud import (
-    create_diagonalley_product,
-    get_diagonalley_product,
-    get_diagonalley_products,
-    delete_diagonalley_product,
-    create_diagonalley_order,
-    get_diagonalley_order,
-    get_diagonalley_orders,
-    update_diagonalley_product,
-)
+from fastapi import Request
+from fastapi.params import Depends
+from fastapi.templating import Jinja2Templates
+from starlette.exceptions import HTTPException
+from starlette.responses import HTMLResponse
 
+from lnbits.core.models import User
+from lnbits.decorators import check_user_exists  # type: ignore
+from lnbits.extensions.diagonalley import diagonalley_ext, diagonalley_renderer
+
+from .crud import get_diagonalley_products
+
+templates = Jinja2Templates(directory="templates")
 
 @diagonalley_ext.get("/", response_class=HTMLResponse)
-@validate_uuids(["usr"], required=True)
-@check_user_exists(request: Request)
-async def index():
-    return await render_template("diagonalley/index.html", user=g.user)
+async def index(request: Request, user: User = Depends(check_user_exists)):
+    return diagonalley_renderer().TemplateResponse(
+        "diagonalley/index.html", {"request": request, "user": user.dict()}
+    )
 
-
-@diagonalley_ext.get("/", response_class=HTMLResponse)
+@diagonalley_ext.get("/{stall_id}", response_class=HTMLResponse)
 async def display(request: Request, stall_id):
     product = await get_diagonalley_products(stall_id)
+    
     if not product:
-        abort(HTTPStatus.NOT_FOUND, "Stall does not exist.")
-
-    return await render_template(
+        raise HTTPException(
+            status_code=HTTPStatus.NOT_FOUND, detail="Stall does not exist."
+        )
+    return diagonalley_renderer().TemplateResponse(
         "diagonalley/stall.html",
-        stall=json.dumps(
-            [product._asdict() for product in await get_diagonalley_products(stall_id)]
-        ),
+        {"stall": [product.dict() for product in await get_diagonalley_products(stall_id)]}
     )
diff --git a/lnbits/extensions/diagonalley/views_api.py b/lnbits/extensions/diagonalley/views_api.py
index 165d2a0c..43232841 100644
--- a/lnbits/extensions/diagonalley/views_api.py
+++ b/lnbits/extensions/diagonalley/views_api.py
@@ -9,13 +9,7 @@ from starlette.exceptions import HTTPException
 
 from lnbits.core.crud import get_user
 from lnbits.core.services import create_invoice
-from lnbits.decorators import (
-    WalletTypeInfo,
-    api_check_wallet_key,
-    api_validate_post_request,
-    get_key_type,
-    require_admin_key,
-)
+from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
 
 from . import db, diagonalley_ext
 from .crud import (

From 301135d0064d378843ad997124fcfbb169520e0c Mon Sep 17 00:00:00 2001
From: benarc 
Date: Thu, 3 Feb 2022 21:19:24 +0000
Subject: [PATCH 07/30] Message ui working

---
 .../extensions/diagonalley/static/js/index.js | 83 ++++++++++++-------
 .../templates/diagonalley/index.html          | 47 ++++++-----
 2 files changed, 78 insertions(+), 52 deletions(-)

diff --git a/lnbits/extensions/diagonalley/static/js/index.js b/lnbits/extensions/diagonalley/static/js/index.js
index 1a25edaa..af81db2a 100644
--- a/lnbits/extensions/diagonalley/static/js/index.js
+++ b/lnbits/extensions/diagonalley/static/js/index.js
@@ -2,7 +2,24 @@
 
 Vue.component(VueQrcode.name, VueQrcode)
 
-const pica = window.pica()
+//const pica = window.pica()
+
+var mapStalls = obj => {
+  obj._data = _.clone(obj)
+  return obj
+}
+var mapProducts = obj => {
+  obj._data = _.clone(obj)
+  return obj
+}
+var mapZone = obj => {
+  obj._data = _.clone(obj)
+  return obj
+}
+var mapOrders = obj => {
+  obj._data = _.clone(obj)
+  return obj
+}
 
 new Vue({
   el: '#vue',
@@ -13,6 +30,9 @@ new Vue({
       orders: [],
       stalls: [],
       zones: [],
+      customerKeys: [],
+      customerKey: '',
+      customerMessages: {},
       shippedModel: false,
       shippingZoneOptions: [
         'Australia',
@@ -64,7 +84,8 @@ new Vue({
         'Groceries (Food and Drink)',
         'Technology (Phones and Computers)',
         'Home (furniture and accessories)',
-        'Gifts (flowers, cards, etc)'
+        'Gifts (flowers, cards, etc)',
+        'Adult'
       ],
       relayOptions: [
         'wss://nostr-relay.herokuapp.com/ws',
@@ -244,6 +265,17 @@ new Vue({
     }
   },
   methods: {
+    ////////////////////////////////////////
+    ///////////SUPPORT MESSAGES/////////////
+    ////////////////////////////////////////
+    getMessages: function (customerKey) {
+      var self = this
+      console.log('fuck')
+      messages = []
+      messages.push(['in', 'blah blah'])
+      messages.push(['out', 'blah blah'])
+      self.customerMessages = messages
+    },
     ////////////////////////////////////////
     ////////////////STALLS//////////////////
     ////////////////////////////////////////
@@ -256,10 +288,7 @@ new Vue({
           this.g.user.wallets[0].inkey
         )
         .then(function (response) {
-          self.stalls = response.data.map(function (obj) {
-            console.log(obj)
-            return mapDiagonAlley(obj)
-          })
+          self.stalls.push(mapStalls(response.data))
         })
     },
     openStallUpdateDialog: function (linkId) {
@@ -302,7 +331,7 @@ new Vue({
           self.stalls = _.reject(self.stalls, function (obj) {
             return obj.id == data.id
           })
-          self.stalls.push(mapDiagonAlley(response.data))
+          self.stalls.push(mapStalls(response.data))
           self.stallDialog.show = false
           self.stallDialog.data = {}
           data = {}
@@ -323,7 +352,7 @@ new Vue({
           data
         )
         .then(function (response) {
-          self.stalls.push(mapDiagonAlley(response.data))
+          self.stalls.push(mapStalls(response.data))
           self.stallDialog.show = false
           self.stallDialog.data = {}
           data = {}
@@ -371,9 +400,7 @@ new Vue({
           this.g.user.wallets[0].inkey
         )
         .then(function (response) {
-          self.products = response.data.map(function (obj) {
-            return mapDiagonAlley(obj)
-          })
+          self.products.push(mapProducts(response.data))
         })
     },
     openProductUpdateDialog: function (linkId) {
@@ -450,7 +477,7 @@ new Vue({
           self.products = _.reject(self.products, function (obj) {
             return obj.id == data.id
           })
-          self.products.push(mapDiagonAlley(response.data))
+          self.products.push(mapProducts(response.data))
           self.productDialog.show = false
           self.productDialog.data = {}
         })
@@ -470,7 +497,7 @@ new Vue({
           data
         )
         .then(function (response) {
-          self.products.push(mapDiagonAlley(response.data))
+          self.products.push(mapProducts(response.data))
           self.productDialog.show = false
           self.productDialog.data = {}
         })
@@ -517,9 +544,7 @@ new Vue({
           this.g.user.wallets[0].inkey
         )
         .then(function (response) {
-          self.zones = response.data.map(function (obj) {
-            return mapDiagonAlley(obj)
-          })
+          self.zones.push(mapZone(response.data))
         })
     },
     openZoneUpdateDialog: function (linkId) {
@@ -559,7 +584,7 @@ new Vue({
           self.zones = _.reject(self.zones, function (obj) {
             return obj.id == data.id
           })
-          self.zones.push(mapDiagonAlley(response.data))
+          self.zones.push(mapZone(response.data))
           self.zoneDialog.show = false
           self.zoneDialog.data = {}
           data = {}
@@ -580,7 +605,7 @@ new Vue({
           data
         )
         .then(function (response) {
-          self.zones.push(mapDiagonAlley(response.data))
+          self.zones.push(mapZone(response.data))
           self.zoneDialog.show = false
           self.zoneDialog.data = {}
           data = {}
@@ -628,9 +653,7 @@ new Vue({
           this.g.user.wallets[0].inkey
         )
         .then(function (response) {
-          self.shops = response.data.map(function (obj) {
-            return mapDiagonAlley(obj)
-          })
+          self.shops.push(mapShops(response.data))
         })
     },
     openShopUpdateDialog: function (linkId) {
@@ -670,7 +693,7 @@ new Vue({
           self.shops = _.reject(self.shops, function (obj) {
             return obj.id == data.id
           })
-          self.shops.push(mapDiagonAlley(response.data))
+          self.shops.push(mapShops(response.data))
           self.shopDialog.show = false
           self.shopDialog.data = {}
           data = {}
@@ -692,7 +715,7 @@ new Vue({
           data
         )
         .then(function (response) {
-          self.shops.push(mapDiagonAlley(response.data))
+          self.shops.push(mapShops(response.data))
           self.shopDialog.show = false
           self.shopDialog.data = {}
           data = {}
@@ -740,9 +763,7 @@ new Vue({
           this.g.user.wallets[0].inkey
         )
         .then(function (response) {
-          self.orders = response.data.map(function (obj) {
-            return mapDiagonAlley(obj)
-          })
+          self.orders.push(mapOrders(response.data))
         })
     },
     createOrder: function () {
@@ -763,7 +784,7 @@ new Vue({
           data
         )
         .then(function (response) {
-          self.orders.push(mapDiagonAlley(response.data))
+          self.orders.push(mapOrders(response.data))
           self.orderDialog.show = false
           self.orderDialog.data = {}
         })
@@ -804,9 +825,7 @@ new Vue({
           this.g.user.wallets[0].inkey
         )
         .then(function (response) {
-          self.orders = response.data.map(function (obj) {
-            return mapDiagonAlley(obj)
-          })
+          self.orders.push(mapOrders(response.data))
         })
     },
     exportOrdersCSV: function () {
@@ -819,6 +838,10 @@ new Vue({
       this.getProducts()
       this.getZones()
       this.getOrders()
+      this.customerKeys = [
+        'cb4c0164fe03fcdadcbfb4f76611c71620790944c24f21a1cd119395cdedfe1b',
+        'a9c17358a6dc4ceb3bb4d883eb87967a66b3453a0f3199f0b1c8eef8070c6a07'
+      ]
     }
   }
 })
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/index.html b/lnbits/extensions/diagonalley/templates/diagonalley/index.html
index 98405f6d..a89c8b5e 100644
--- a/lnbits/extensions/diagonalley/templates/diagonalley/index.html
+++ b/lnbits/extensions/diagonalley/templates/diagonalley/index.html
@@ -594,34 +594,37 @@
     
     
       
-        
Messages (example)
+
Messages
-
-
- OrderID:87h87h
KJBIBYBUYBUF90898....
- OrderID:NIUHB7
79867KJGJHGVFYFV....
+
+
+
-
-
- - +
+
+ + +
+
+
+ + +
- -
From ecaea51a1ce5f98c585b58caf2b0c16f2eb8a75b Mon Sep 17 00:00:00 2001 From: benarc Date: Thu, 3 Feb 2022 22:30:53 +0000 Subject: [PATCH 08/30] Added error messages --- lnbits/extensions/diagonalley/static/js/index.js | 6 ++++++ .../diagonalley/templates/diagonalley/index.html | 13 ++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/lnbits/extensions/diagonalley/static/js/index.js b/lnbits/extensions/diagonalley/static/js/index.js index af81db2a..d101bfcf 100644 --- a/lnbits/extensions/diagonalley/static/js/index.js +++ b/lnbits/extensions/diagonalley/static/js/index.js @@ -265,6 +265,12 @@ new Vue({ } }, methods: { + errorMessage: function (error) { + this.$q.notify({ + color: 'primary', + message: error + }) + }, //////////////////////////////////////// ///////////SUPPORT MESSAGES///////////// //////////////////////////////////////// diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/index.html b/lnbits/extensions/diagonalley/templates/diagonalley/index.html index a89c8b5e..d14d7cee 100644 --- a/lnbits/extensions/diagonalley/templates/diagonalley/index.html +++ b/lnbits/extensions/diagonalley/templates/diagonalley/index.html @@ -300,13 +300,20 @@
- + Product List a product + + Product List a product + Shipping Zone Create a shipping zone - + Stall + Create a stall to list products on + + Stall Create a stall to list products on @@ -618,7 +625,7 @@ >
-
+
@@ -143,7 +145,7 @@
{{SITE_TITLE}} Nostr Extension

Only Admin users can manage this extension

- Okay +
@@ -153,7 +155,7 @@