From 6b2f3d05d51d7aabe8a00dac10f9dfc3d96134a9 Mon Sep 17 00:00:00 2001
From: benarc
Date: Thu, 27 Jan 2022 12:24:38 +0000
Subject: [PATCH 001/226] 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!
+
+ Create Shipping Zones you're willing to ship to
+ Create a Stall to list yiur products on
+ Create products to put on the Stall
+ List stalls on a simple frontend shop page, or point at Nostr shop client key
+
+ 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 %}
+
+
+
+ {{ col.label }}
+
+
+
+
+
+
+
+ {{ col.value }}
+
+
+
+
+ Product shipped?
+
+
+
+
+
+
+
+ {% endraw %}
+
+
+
+
+
+
+
+
+
Products
+
+
+ Export to CSV
+
+
+
+ {% raw %}
+
+
+
+
+ {{ col.label }}
+
+
+
+
+
+
+
+
+ Link to pass to stall relay
+
+
+ {{ col.value }}
+
+
+
+
+
+
+
+
+
+
+ {% endraw %}
+
+
+
+
+
+
+
+
+
Stalls
+
+
+ Export to CSV
+
+
+
+ {% raw %}
+
+
+
+ {{ col.label }}
+
+
+
+
+
+
+
+ {{ col.value }}
+
+
+
+
+
+
+
+ {% endraw %}
+
+
+
+
+
+
+
+
+
Shipping Zones
+
+
+ Export to CSV
+
+
+
+ {% raw %}
+
+
+
+ {{ col.label }}
+
+
+
+
+
+
+
+ {{ col.value }}
+
+
+
+
+
+
+
+ {% 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 002/226] 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 003/226] 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 004/226] 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 005/226] 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 006/226] 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 007/226] 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 008/226] 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 @@
>
-
+
From 44f7fae2d4035f9757d9f6836e59be68b09c987c Mon Sep 17 00:00:00 2001
From: benarc
Date: Fri, 4 Feb 2022 13:05:48 +0000
Subject: [PATCH 009/226] Zones are saving/updating
---
lnbits/extensions/diagonalley/__init__.py | 11 +-
lnbits/extensions/diagonalley/crud.py | 116 +--
lnbits/extensions/diagonalley/migrations.py | 8 +-
lnbits/extensions/diagonalley/models.py | 7 +-
.../extensions/diagonalley/static/js/index.js | 853 -----------------
.../templates/diagonalley/index.html | 895 +++++++++++++++++-
lnbits/extensions/diagonalley/views_api.py | 40 +-
7 files changed, 924 insertions(+), 1006 deletions(-)
delete mode 100644 lnbits/extensions/diagonalley/static/js/index.js
diff --git a/lnbits/extensions/diagonalley/__init__.py b/lnbits/extensions/diagonalley/__init__.py
index cab65685..388c08db 100644
--- a/lnbits/extensions/diagonalley/__init__.py
+++ b/lnbits/extensions/diagonalley/__init__.py
@@ -9,17 +9,9 @@ from lnbits.tasks import catch_everything_and_restart
db = Database("ext_diagonalley")
-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"
)
def diagonalley_renderer():
@@ -33,5 +25,4 @@ from .views_api import * # noqa
def diagonalley_start():
loop = asyncio.get_event_loop()
- loop.create_task(catch_everything_and_restart(wait_for_paid_invoices))
-
+ loop.create_task(catch_everything_and_restart(wait_for_paid_invoices))
\ No newline at end of file
diff --git a/lnbits/extensions/diagonalley/crud.py b/lnbits/extensions/diagonalley/crud.py
index 4cf14014..d2df427c 100644
--- a/lnbits/extensions/diagonalley/crud.py
+++ b/lnbits/extensions/diagonalley/crud.py
@@ -22,28 +22,12 @@ from .models import (
createZones,
)
-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(
data: createProduct
) -> 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)(
await db.execute(
f"""
INSERT INTO diagonalley.products (id, stall, product, categories, description, image, price, quantity)
@@ -110,7 +94,7 @@ async def delete_diagonalley_product(product_id: str) -> None:
async def create_diagonalley_zone(
- wallet,
+ user,
data: createZones
) -> Zones:
zone_id = urlsafe_short_hash()
@@ -118,14 +102,14 @@ async def create_diagonalley_zone(
f"""
INSERT INTO diagonalley.zones (
id,
- wallet,
+ user,
cost,
countries
)
VALUES (?, ?, ?, ?)
""",
- (zone_id, wallet, data.cost, data.countries),
+ (zone_id, user, data.cost, data.countries.lower()),
)
zone = await get_diagonalley_zone(zone_id)
@@ -148,41 +132,8 @@ async def get_diagonalley_zone(zone_id: str) -> Optional[Zones]:
return Zones(**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,)
- )
+async def get_diagonalley_zones(user: str) -> List[Zones]:
+ rows = await db.fetchall("SELECT * FROM diagonalley.zones WHERE user = ?", (user,))
return [Zones(**row) for row in rows]
@@ -217,7 +168,7 @@ async def create_diagonalley_stall(
data.publickey,
data.privatekey,
data.relays,
- data.shippingzones),
+ repr(data.shippingzones)),
)
stall = await get_diagonalley_stall(stall_id)
@@ -238,32 +189,6 @@ async def update_diagonalley_stall(stall_id: str, **kwargs) -> Optional[Stalls]:
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,)
)
@@ -271,35 +196,6 @@ async def get_diagonalley_stall(stall_id: str) -> Optional[Stalls]:
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,)
diff --git a/lnbits/extensions/diagonalley/migrations.py b/lnbits/extensions/diagonalley/migrations.py
index 1523f398..29a84419 100644
--- a/lnbits/extensions/diagonalley/migrations.py
+++ b/lnbits/extensions/diagonalley/migrations.py
@@ -28,7 +28,8 @@ async def m001_initial(db):
name TEXT NOT NULL,
publickey TEXT NOT NULL,
privatekey TEXT NOT NULL,
- relays TEXT NOT NULL
+ relays TEXT NOT NULL,
+ shippingzones TEXT NOT NULL
);
"""
)
@@ -40,7 +41,7 @@ async def m001_initial(db):
"""
CREATE TABLE diagonalley.zones (
id TEXT PRIMARY KEY,
- wallet TEXT NOT NULL,
+ user TEXT NOT NULL,
cost TEXT NOT NULL,
countries TEXT NOT NULL
);
@@ -55,7 +56,8 @@ async def m001_initial(db):
CREATE TABLE diagonalley.orders (
id TEXT PRIMARY KEY,
productid TEXT NOT NULL,
- wallet TEXT NOT NULL,
+ usr TEXT NOT NULL,
+ pubkey TEXT NOT NULL,
product TEXT NOT NULL,
quantity INTEGER NOT NULL,
shippingzone INTEGER NOT NULL,
diff --git a/lnbits/extensions/diagonalley/models.py b/lnbits/extensions/diagonalley/models.py
index 1a975e10..743c2068 100644
--- a/lnbits/extensions/diagonalley/models.py
+++ b/lnbits/extensions/diagonalley/models.py
@@ -49,13 +49,13 @@ class Products(BaseModel):
quantity: int
class createZones(BaseModel):
- cost: str = Query(None)
+ cost: int = Query(0, ge=0)
countries: str = Query(None)
class Zones(BaseModel):
id: str
- wallet: str
- cost: str
+ user: str
+ cost: int
countries: str
@@ -73,6 +73,7 @@ class Orders(BaseModel):
id: str
productid: str
stall: str
+ pubkey: str
product: str
quantity: int
shippingzone: int
diff --git a/lnbits/extensions/diagonalley/static/js/index.js b/lnbits/extensions/diagonalley/static/js/index.js
deleted file mode 100644
index d101bfcf..00000000
--- a/lnbits/extensions/diagonalley/static/js/index.js
+++ /dev/null
@@ -1,853 +0,0 @@
-/* globals Quasar, Vue, _, VueQrcode, windowMixin, LNbits, LOCALE */
-
-Vue.component(VueQrcode.name, VueQrcode)
-
-//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',
- mixins: [windowMixin],
- data: function () {
- return {
- products: [],
- orders: [],
- stalls: [],
- zones: [],
- customerKeys: [],
- customerKey: '',
- customerMessages: {},
- 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)',
- 'Adult'
- ],
- 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: {
- errorMessage: function (error) {
- this.$q.notify({
- color: 'primary',
- message: error
- })
- },
- ////////////////////////////////////////
- ///////////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//////////////////
- ////////////////////////////////////////
- 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.push(mapStalls(response.data))
- })
- },
- 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(mapStalls(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(mapStalls(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.push(mapProducts(response.data))
- })
- },
- 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(mapProducts(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(mapProducts(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.push(mapZone(response.data))
- })
- },
- 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(mapZone(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(mapZone(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.push(mapShops(response.data))
- })
- },
- 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(mapShops(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(mapShops(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.push(mapOrders(response.data))
- })
- },
- 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(mapOrders(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.push(mapOrders(response.data))
- })
- },
- 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()
- this.customerKeys = [
- 'cb4c0164fe03fcdadcbfb4f76611c71620790944c24f21a1cd119395cdedfe1b',
- 'a9c17358a6dc4ceb3bb4d883eb87967a66b3453a0f3199f0b1c8eef8070c6a07'
- ]
- }
- }
-})
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/index.html b/lnbits/extensions/diagonalley/templates/diagonalley/index.html
index d14d7cee..7b8f8e6a 100644
--- a/lnbits/extensions/diagonalley/templates/diagonalley/index.html
+++ b/lnbits/extensions/diagonalley/templates/diagonalley/index.html
@@ -300,8 +300,9 @@
- + Product List a product + Product List a produc
+ Product List a product + Shipping Zone Create a shipping zone
- + Stall
Create a stall to list products on
@@ -321,6 +322,7 @@
>Launch frontend shop (not Nostr)
Makes a simple frontend shop for your stalls
+
@@ -638,7 +640,890 @@
+
{% endblock %} {% block scripts %} {{ window_vars(user) }}
-
-
+
+
{% endblock %}
+
diff --git a/lnbits/extensions/diagonalley/views_api.py b/lnbits/extensions/diagonalley/views_api.py
index 43232841..6363dc97 100644
--- a/lnbits/extensions/diagonalley/views_api.py
+++ b/lnbits/extensions/diagonalley/views_api.py
@@ -121,36 +121,32 @@ 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), all_wallets: bool = Query(False)):
- wallet_ids = [wallet.wallet.id]
+async def api_diagonalley_zones(wallet: WalletTypeInfo = Depends(get_key_type)):
- if all_wallets:
- wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
-
- return ([zone.dict() for zone in await get_diagonalley_zones(wallet_ids)])
+ return await get_diagonalley_zones(wallet.wallet.user)
@diagonalley_ext.post("/api/v1/zones")
-@diagonalley_ext.put("/api/v1/zones/{zone_id}")
async def api_diagonalley_zone_create(
data: createZones,
- 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 != wallet.wallet.id:
- return ({"message": "Not your record."})
-
- zone = await update_diagonalley_zone(zone_id, **data.dict())
- else:
- zone = await create_diagonalley_zone(wallet=wallet.wallet.id, data=data)
-
+ zone = await create_diagonalley_zone(user=wallet.wallet.user, data=data)
return zone.dict()
+@diagonalley_ext.post("/api/v1/zones/{zone_id}")
+async def api_diagonalley_zone_update(
+ data: createZones,
+ 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.user != wallet.wallet.user:
+ return ({"message": "Not your record."})
+ zone = await update_diagonalley_zone(zone_id, **data.dict())
+ return zone
+
@diagonalley_ext.delete("/api/v1/zones/{zone_id}")
async def api_diagonalley_zone_delete(zone_id, wallet: WalletTypeInfo = Depends(require_admin_key)):
@@ -159,7 +155,7 @@ async def api_diagonalley_zone_delete(zone_id, wallet: WalletTypeInfo = Depends(
if not zone:
return ({"message": "zone does not exist."})
- if zone.wallet != wallet.wallet.id:
+ if zone.user != wallet.wallet.user:
return ({"message": "Not your zone."})
await delete_diagonalley_zone(zone_id)
From 80b73e7aba1b1dff80693265dadd96946b27375e Mon Sep 17 00:00:00 2001
From: benarc
Date: Sun, 6 Feb 2022 22:40:51 +0000
Subject: [PATCH 010/226] small fix
---
.../templates/diagonalley/index.html | 25 +++++++++++++------
1 file changed, 18 insertions(+), 7 deletions(-)
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/index.html b/lnbits/extensions/diagonalley/templates/diagonalley/index.html
index 7b8f8e6a..6d6ea872 100644
--- a/lnbits/extensions/diagonalley/templates/diagonalley/index.html
+++ b/lnbits/extensions/diagonalley/templates/diagonalley/index.html
@@ -244,7 +244,7 @@
label="Private Key"
>
@@ -270,7 +270,7 @@
filled
dense
v-model.trim="stallDialog.data.nostrShops"
- label="Stall public keys (seperate by comma)"
+ label="Nostr shop public keys (seperate by comma)"
>
- + Product List a produc 1" color="primary" @click="productDialog.show = true"
+ >+ Product List a product
+ Product List a product + Shipping Zone
Create a shipping zone
-
+ Stall
Create a stall to list products on
@@ -675,6 +675,7 @@ new Vue({
orders: [],
stalls: [],
zones: [],
+ zoneOptions: [],
customerKeys: [],
customerKey: '',
customerMessages: {},
@@ -949,10 +950,19 @@ new Vue({
LNbits.utils.notifyApiError(error)
})
},
+ openStallDialog: function () {
+ console.log(this.zones[0]['id'])
+ for(let i = 0; i < this.zones.length; i ++){
+ this.zoneOptions.push(this.zones[i]['id'])
+ }
+ this.stallDialog.show = true
+ },
openStallUpdateDialog: function (linkId) {
var self = this
var link = _.findWhere(self.stalls, {id: linkId})
-
+ for(let i = 0; i < this.zones.length; i ++){
+ this.zoneOptions.push(this.stalls[i][0])
+ }
this.stallDialog.data = _.clone(link._data)
this.stallDialog.show = true
},
@@ -1210,6 +1220,7 @@ new Vue({
if (response.data) {
console.log(response)
self.zones = response.data.map(mapZone)
+
}
})
.catch(function (error) {
From f38492a9209f27cf587946c0359c5ae047d9463c Mon Sep 17 00:00:00 2001
From: benarc
Date: Mon, 7 Feb 2022 13:22:43 +0000
Subject: [PATCH 011/226] Added nostr ext
---
lnbits/extensions/nostr/README.md | 3 +
lnbits/extensions/nostr/__init__.py | 17 +
lnbits/extensions/nostr/config.json | 6 +
lnbits/extensions/nostr/crud.py | 134 +++++
lnbits/extensions/nostr/migrations.py | 47 ++
lnbits/extensions/nostr/models.py | 34 ++
.../templates/lnurldevice/_api_docs.html | 169 ++++++
.../nostr/templates/lnurldevice/error.html | 34 ++
.../nostr/templates/lnurldevice/index.html | 534 ++++++++++++++++++
.../nostr/templates/lnurldevice/paid.html | 27 +
lnbits/extensions/nostr/views.py | 24 +
lnbits/extensions/nostr/views_api.py | 48 ++
12 files changed, 1077 insertions(+)
create mode 100644 lnbits/extensions/nostr/README.md
create mode 100644 lnbits/extensions/nostr/__init__.py
create mode 100644 lnbits/extensions/nostr/config.json
create mode 100644 lnbits/extensions/nostr/crud.py
create mode 100644 lnbits/extensions/nostr/migrations.py
create mode 100644 lnbits/extensions/nostr/models.py
create mode 100644 lnbits/extensions/nostr/templates/lnurldevice/_api_docs.html
create mode 100644 lnbits/extensions/nostr/templates/lnurldevice/error.html
create mode 100644 lnbits/extensions/nostr/templates/lnurldevice/index.html
create mode 100644 lnbits/extensions/nostr/templates/lnurldevice/paid.html
create mode 100644 lnbits/extensions/nostr/views.py
create mode 100644 lnbits/extensions/nostr/views_api.py
diff --git a/lnbits/extensions/nostr/README.md b/lnbits/extensions/nostr/README.md
new file mode 100644
index 00000000..596cce9d
--- /dev/null
+++ b/lnbits/extensions/nostr/README.md
@@ -0,0 +1,3 @@
+# Nostr
+
+Opens a Nostr daemon
diff --git a/lnbits/extensions/nostr/__init__.py b/lnbits/extensions/nostr/__init__.py
new file mode 100644
index 00000000..775960e3
--- /dev/null
+++ b/lnbits/extensions/nostr/__init__.py
@@ -0,0 +1,17 @@
+from fastapi import APIRouter
+
+from lnbits.db import Database
+from lnbits.helpers import template_renderer
+
+db = Database("ext_nostr")
+
+nostr_ext: APIRouter = APIRouter(prefix="/nostr", tags=["nostr"])
+
+
+def nostr_renderer():
+ return template_renderer(["lnbits/extensions/nostr/templates"])
+
+
+from .lnurl import * # noqa
+from .views import * # noqa
+from .views_api import * # noqa
diff --git a/lnbits/extensions/nostr/config.json b/lnbits/extensions/nostr/config.json
new file mode 100644
index 00000000..a32e39a1
--- /dev/null
+++ b/lnbits/extensions/nostr/config.json
@@ -0,0 +1,6 @@
+{
+ "name": "Nostr",
+ "short_description": "Daemon for Nostr",
+ "icon": "swap_horizontal_circle",
+ "contributors": ["arcbtc"]
+}
diff --git a/lnbits/extensions/nostr/crud.py b/lnbits/extensions/nostr/crud.py
new file mode 100644
index 00000000..55e99ec6
--- /dev/null
+++ b/lnbits/extensions/nostr/crud.py
@@ -0,0 +1,134 @@
+from typing import List, Optional, Union
+
+from lnbits.helpers import urlsafe_short_hash
+
+from . import db
+from .models import nostrKeys, nostrNotes, nostrRelays, nostrConnections
+
+###############KEYS##################
+
+async def create_nostrkeys(
+ data: nostrKeys
+) -> nostrKeys:
+ nostrkey_id = urlsafe_short_hash()
+ await db.execute(
+ """
+ INSERT INTO nostr.keys (
+ id,
+ pubkey,
+ privkey
+ )
+ VALUES (?, ?, ?)
+ """,
+ (nostrkey_id, data.pubkey, data.privkey),
+ )
+ return await get_nostrkeys(nostrkey_id)
+
+async def get_nostrkeys(nostrkey_id: str) -> nostrKeys:
+ row = await db.fetchone(
+ "SELECT * FROM nostr.keys WHERE id = ?",
+ (lnurldevicepayment_id,),
+ )
+ return nostrKeys(**row) if row else None
+
+
+###############NOTES##################
+
+async def create_nostrnotes(
+ data: nostrNotes
+) -> nostrNotes:
+ await db.execute(
+ """
+ INSERT INTO nostr.notes (
+ id,
+ pubkey,
+ created_at,
+ kind,
+ tags,
+ content,
+ sig
+ )
+ VALUES (?, ?, ?, ?, ?, ?, ?)
+ """,
+ (data.id, data.pubkey, data.created_at, data.kind, data.tags, data.content, data.sig),
+ )
+ return await get_nostrnotes(data.id)
+
+async def get_nostrnotes(nostrnote_id: str) -> nostrNotes:
+ row = await db.fetchone(
+ "SELECT * FROM nostr.notes WHERE id = ?",
+ (nostrnote_id,),
+ )
+ return nostrNotes(**row) if row else None
+
+###############RELAYS##################
+
+async def create_nostrrelays(
+ relay: str
+) -> nostrRelays:
+ nostrrelay_id = urlsafe_short_hash()
+ await db.execute(
+ """
+ INSERT INTO nostr.relays (
+ id,
+ relay
+ )
+ VALUES (?, ?)
+ """,
+ (nostrrelay_id, relay),
+ )
+ return await get_nostrnotes(nostrrelay_id)
+
+async def get_nostrrelays(nostrrelay_id: str) -> nostrRelays:
+ row = await db.fetchone(
+ "SELECT * FROM nostr.relays WHERE id = ?",
+ (nostrnote_id,),
+ )
+ return nostrRelays(**row) if row else None
+
+
+###############CONNECTIONS##################
+
+async def create_nostrconnections(
+ data: nostrNotes
+) -> nostrNotes:
+ nostrkey_id = urlsafe_short_hash()
+ await db.execute(
+ """
+ INSERT INTO nostr.notes (
+ id,
+ pubkey,
+ created_at,
+ kind,
+ tags,
+ content,
+ sig
+ )
+ VALUES (?, ?, ?, ?, ?, ?, ?)
+ """,
+ (data.id, data.pubkey, data.created_at, data.kind, data.tags, data.content, data.sig),
+ )
+ return await get_nostrnotes(data.id)
+
+async def get_nostrnotes(nostrnote_id: str) -> nostrNotes:
+ row = await db.fetchone(
+ "SELECT * FROM nostr.notes WHERE id = ?",
+ (nostrnote_id,),
+ )
+ return nostrNotes(**row) if row else None
+
+
+
+async def update_lnurldevicepayment(
+ lnurldevicepayment_id: str, **kwargs
+) -> Optional[lnurldevicepayment]:
+ q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
+ await db.execute(
+ f"UPDATE lnurldevice.lnurldevicepayment SET {q} WHERE id = ?",
+ (*kwargs.values(), lnurldevicepayment_id),
+ )
+ row = await db.fetchone(
+ "SELECT * FROM lnurldevice.lnurldevicepayment WHERE id = ?",
+ (lnurldevicepayment_id,),
+ )
+ return lnurldevicepayment(**row) if row else None
\ No newline at end of file
diff --git a/lnbits/extensions/nostr/migrations.py b/lnbits/extensions/nostr/migrations.py
new file mode 100644
index 00000000..de32a578
--- /dev/null
+++ b/lnbits/extensions/nostr/migrations.py
@@ -0,0 +1,47 @@
+from lnbits.db import Database
+
+db2 = Database("ext_nostr")
+
+
+async def m001_initial(db):
+ """
+ Initial nostr table.
+ """
+ await db.execute(
+ f"""
+ CREATE TABLE nostr.keys (
+ pubkey TEXT NOT NULL PRIMARY KEY,
+ privkey TEXT NOT NULL
+ );
+ """
+ )
+ await db.execute(
+ f"""
+ CREATE TABLE nostr.notes (
+ id TEXT NOT NULL PRIMARY KEY,
+ pubkey TEXT NOT NULL,
+ created_at TEXT NOT NULL,
+ kind INT NOT NULL,
+ tags TEXT NOT NULL,
+ content TEXT NOT NULL,
+ sig TEXT NOT NULL,
+ );
+ """
+ )
+ await db.execute(
+ f"""
+ CREATE TABLE nostr.relays (
+ id TEXT NOT NULL PRIMARY KEY,
+ relay TEXT NOT NULL
+ );
+ """
+ )
+ await db.execute(
+ f"""
+ CREATE TABLE nostr.connections (
+ id TEXT NOT NULL PRIMARY KEY,
+ publickey TEXT NOT NULL,
+ relayid TEXT NOT NULL
+ );
+ """
+ )
\ No newline at end of file
diff --git a/lnbits/extensions/nostr/models.py b/lnbits/extensions/nostr/models.py
new file mode 100644
index 00000000..ba0c87a5
--- /dev/null
+++ b/lnbits/extensions/nostr/models.py
@@ -0,0 +1,34 @@
+import json
+from sqlite3 import Row
+from typing import Optional
+
+from fastapi import Request
+from lnurl import Lnurl
+from lnurl import encode as lnurl_encode # type: ignore
+from lnurl.models import LnurlPaySuccessAction, UrlAction # type: ignore
+from lnurl.types import LnurlPayMetadata # type: ignore
+from pydantic import BaseModel
+from pydantic.main import BaseModel
+
+class nostrKeys(BaseModel):
+ id: str
+ pubkey: str
+ privkey: str
+
+class nostrNotes(BaseModel):
+ id: str
+ pubkey: str
+ created_at: str
+ kind: int
+ tags: str
+ content: str
+ sig: str
+
+class nostrRelays(BaseModel):
+ id: str
+ relay: str
+
+class nostrConnections(BaseModel):
+ id: str
+ pubkey: str
+ relayid: str
\ No newline at end of file
diff --git a/lnbits/extensions/nostr/templates/lnurldevice/_api_docs.html b/lnbits/extensions/nostr/templates/lnurldevice/_api_docs.html
new file mode 100644
index 00000000..af69b76e
--- /dev/null
+++ b/lnbits/extensions/nostr/templates/lnurldevice/_api_docs.html
@@ -0,0 +1,169 @@
+
+
+
+ Register LNURLDevice devices to receive payments in your LNbits wallet.
+ Build your own here
+ https://github.com/arcbtc/bitcoinpos
+
+ Created by, Ben Arc
+
+
+
+
+
+
+ /lnurldevice/api/v1/lnurlpos
+ Headers
+ {"X-Api-Key": <admin_key>}
+
+ Body (application/json)
+
+
+ Returns 200 OK (application/json)
+
+ [<lnurldevice_object>, ...]
+ Curl example
+ curl -X POST {{ request.base_url }}api/v1/lnurldevice -d '{"title":
+ <string>, "message":<string>, "currency":
+ <integer>}' -H "Content-type: application/json" -H "X-Api-Key:
+ {{user.wallets[0].adminkey }}"
+
+
+
+
+
+
+
+ PUT
+ /lnurldevice/api/v1/lnurlpos/<lnurldevice_id>
+ Headers
+ {"X-Api-Key": <admin_key>}
+
+ Body (application/json)
+
+
+ Returns 200 OK (application/json)
+
+ [<lnurldevice_object>, ...]
+ Curl example
+ curl -X POST {{ request.base_url
+ }}api/v1/lnurlpos/<lnurldevice_id> -d ''{"title":
+ <string>, "message":<string>, "currency":
+ <integer>} -H "Content-type: application/json" -H "X-Api-Key:
+ {{user.wallets[0].adminkey }}"
+
+
+
+
+
+
+
+
+ GET
+ /lnurldevice/api/v1/lnurlpos/<lnurldevice_id>
+ Headers
+ {"X-Api-Key": <invoice_key>}
+
+ Body (application/json)
+
+
+ Returns 200 OK (application/json)
+
+ [<lnurldevice_object>, ...]
+ Curl example
+ curl -X GET {{ request.base_url
+ }}api/v1/lnurlpos/<lnurldevice_id> -H "X-Api-Key: {{
+ user.wallets[0].inkey }}"
+
+
+
+
+
+
+
+ GET
+ /lnurldevice/api/v1/lnurlposs
+ Headers
+ {"X-Api-Key": <invoice_key>}
+
+ Body (application/json)
+
+
+ Returns 200 OK (application/json)
+
+ [<lnurldevice_object>, ...]
+ Curl example
+ curl -X GET {{ request.base_url }}api/v1/lnurldevices -H
+ "X-Api-Key: {{ user.wallets[0].inkey }}"
+
+
+
+
+
+
+
+ DELETE
+ /lnurldevice/api/v1/lnurlpos/<lnurldevice_id>
+ Headers
+ {"X-Api-Key": <admin_key>}
+ Returns 204 NO CONTENT
+
+ Curl example
+ curl -X DELETE {{ request.base_url
+ }}api/v1/lnurlpos/<lnurldevice_id> -H "X-Api-Key: {{
+ user.wallets[0].adminkey }}"
+
+
+
+
+
+
diff --git a/lnbits/extensions/nostr/templates/lnurldevice/error.html b/lnbits/extensions/nostr/templates/lnurldevice/error.html
new file mode 100644
index 00000000..d8e41832
--- /dev/null
+++ b/lnbits/extensions/nostr/templates/lnurldevice/error.html
@@ -0,0 +1,34 @@
+{% extends "public.html" %} {% block page %}
+
+
+
+
+
+ LNURL-pay not paid
+
+
+
+
+
+
+
+
+
+ {% endblock %} {% block scripts %}
+
+
+
+ {% endblock %}
+
diff --git a/lnbits/extensions/nostr/templates/lnurldevice/index.html b/lnbits/extensions/nostr/templates/lnurldevice/index.html
new file mode 100644
index 00000000..b51e2556
--- /dev/null
+++ b/lnbits/extensions/nostr/templates/lnurldevice/index.html
@@ -0,0 +1,534 @@
+{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
+%} {% block page %}
+
+
+
+
+ {% raw %}
+ New LNURLDevice instance
+
+
+
+
+
+
+
+
+
lNURLdevice
+
+
+
+
+
+
+
+
+ Export to CSV
+
+
+
+
+
+
+
+
+
+
+ {{ col.label }}
+
+
+
+
+
+
+
+
+
+ Delete LNURLDevice
+
+
+
+
+ LNURLDevice Settings
+
+
+
+
+ {{ col.value }}
+
+
+
+ {% endraw %}
+
+
+
+
+
+
+
+
+
+ {{SITE_TITLE}} LNURLDevice Extension
+
+
+
+
+ {% include "lnurldevice/_api_docs.html" %}
+
+
+
+
+
+
+ LNURLDevice device string
+ {% raw
+ %}{{location}}/lnurldevice/api/v1/lnurl/{{settingsDialog.data.id}},
+ {{settingsDialog.data.key}}, {{settingsDialog.data.currency}}{% endraw
+ %} Click to copy URL
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Update lnurldevice
+ Create lnurldevice
+ Cancel
+
+
+
+
+
+{% endblock %} {% block scripts %} {{ window_vars(user) }}
+
+
+{% endblock %}
diff --git a/lnbits/extensions/nostr/templates/lnurldevice/paid.html b/lnbits/extensions/nostr/templates/lnurldevice/paid.html
new file mode 100644
index 00000000..c185ecce
--- /dev/null
+++ b/lnbits/extensions/nostr/templates/lnurldevice/paid.html
@@ -0,0 +1,27 @@
+{% extends "public.html" %} {% block page %}
+
+
+
+
+
+ {{ pin }}
+
+
+
+
+
+
+ {% endblock %} {% block scripts %}
+
+
+
+ {% endblock %}
+
diff --git a/lnbits/extensions/nostr/views.py b/lnbits/extensions/nostr/views.py
new file mode 100644
index 00000000..1dfc07da
--- /dev/null
+++ b/lnbits/extensions/nostr/views.py
@@ -0,0 +1,24 @@
+from http import HTTPStatus
+
+from fastapi import Request
+from fastapi.param_functions import Query
+from fastapi.params import Depends
+from fastapi.templating import Jinja2Templates
+from starlette.exceptions import HTTPException
+from starlette.responses import HTMLResponse
+
+from lnbits.core.crud import update_payment_status
+from lnbits.core.models import User
+from lnbits.core.views.api import api_payment
+from lnbits.decorators import check_user_exists
+
+from . import nostr_ext, nostr_renderer
+
+templates = Jinja2Templates(directory="templates")
+
+
+@nostr_ext.get("/", response_class=HTMLResponse)
+async def index(request: Request, user: User = Depends(check_user_exists)):
+ return nostr_renderer().TemplateResponse(
+ "nostr/index.html", {"request": request, "user": user.dict()}
+ )
diff --git a/lnbits/extensions/nostr/views_api.py b/lnbits/extensions/nostr/views_api.py
new file mode 100644
index 00000000..a479cad8
--- /dev/null
+++ b/lnbits/extensions/nostr/views_api.py
@@ -0,0 +1,48 @@
+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 WalletTypeInfo, get_key_type, require_admin_key
+from lnbits.extensions.lnurldevice import lnurldevice_ext
+from lnbits.utils.exchange_rates import currencies
+
+from . import lnurldevice_ext
+from .crud import (
+ create_lnurldevice,
+ delete_lnurldevice,
+ get_lnurldevice,
+ get_lnurldevices,
+ update_lnurldevice,
+)
+from .models import createLnurldevice
+
+
+@nostr_ext.get("/api/v1/lnurlpos")
+async def api_check_daemon(wallet: WalletTypeInfo = Depends(get_key_type)):
+ wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
+ try:
+ return [
+ {**lnurldevice.dict()} for lnurldevice in await get_lnurldevices(wallet_ids)
+ ]
+ except:
+ return ""
+
+@nostr_ext.delete("/api/v1/lnurlpos/{lnurldevice_id}")
+async def api_lnurldevice_delete(
+ wallet: WalletTypeInfo = Depends(require_admin_key),
+ lnurldevice_id: str = Query(None),
+):
+ lnurldevice = await get_lnurldevice(lnurldevice_id)
+
+ if not lnurldevice:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND, detail="Wallet link does not exist."
+ )
+
+ await delete_lnurldevice(lnurldevice_id)
+
+ return "", HTTPStatus.NO_CONTENT
\ No newline at end of file
From da1025a625b75dc842efb2baf7582cfb5682a191 Mon Sep 17 00:00:00 2001
From: benarc
Date: Mon, 7 Feb 2022 20:01:01 +0000
Subject: [PATCH 012/226] extension loading
---
lnbits/extensions/nostr/__init__.py | 2 -
lnbits/extensions/nostr/crud.py | 71 +--
lnbits/extensions/nostr/migrations.py | 2 +-
lnbits/extensions/nostr/models.py | 12 +-
.../templates/lnurldevice/_api_docs.html | 169 ------
.../nostr/templates/lnurldevice/error.html | 34 --
.../nostr/templates/lnurldevice/index.html | 534 ------------------
.../nostr/templates/lnurldevice/paid.html | 27 -
.../nostr/templates/nostr/index.html | 159 ++++++
lnbits/extensions/nostr/views_api.py | 46 +-
10 files changed, 204 insertions(+), 852 deletions(-)
delete mode 100644 lnbits/extensions/nostr/templates/lnurldevice/_api_docs.html
delete mode 100644 lnbits/extensions/nostr/templates/lnurldevice/error.html
delete mode 100644 lnbits/extensions/nostr/templates/lnurldevice/index.html
delete mode 100644 lnbits/extensions/nostr/templates/lnurldevice/paid.html
create mode 100644 lnbits/extensions/nostr/templates/nostr/index.html
diff --git a/lnbits/extensions/nostr/__init__.py b/lnbits/extensions/nostr/__init__.py
index 775960e3..9774a279 100644
--- a/lnbits/extensions/nostr/__init__.py
+++ b/lnbits/extensions/nostr/__init__.py
@@ -11,7 +11,5 @@ nostr_ext: APIRouter = APIRouter(prefix="/nostr", tags=["nostr"])
def nostr_renderer():
return template_renderer(["lnbits/extensions/nostr/templates"])
-
-from .lnurl import * # noqa
from .views import * # noqa
from .views_api import * # noqa
diff --git a/lnbits/extensions/nostr/crud.py b/lnbits/extensions/nostr/crud.py
index 55e99ec6..0b30bc9a 100644
--- a/lnbits/extensions/nostr/crud.py
+++ b/lnbits/extensions/nostr/crud.py
@@ -1,33 +1,31 @@
from typing import List, Optional, Union
from lnbits.helpers import urlsafe_short_hash
-
+import shortuuid
from . import db
-from .models import nostrKeys, nostrNotes, nostrRelays, nostrConnections
+from .models import nostrKeys, nostrNotes, nostrCreateRelays, nostrRelays, nostrConnections, nostrCreateConnections
###############KEYS##################
async def create_nostrkeys(
data: nostrKeys
) -> nostrKeys:
- nostrkey_id = urlsafe_short_hash()
await db.execute(
"""
INSERT INTO nostr.keys (
- id,
pubkey,
privkey
)
- VALUES (?, ?, ?)
+ VALUES (?, ?)
""",
- (nostrkey_id, data.pubkey, data.privkey),
+ (data.pubkey, data.privkey),
)
return await get_nostrkeys(nostrkey_id)
-async def get_nostrkeys(nostrkey_id: str) -> nostrKeys:
+async def get_nostrkeys(pubkey: str) -> nostrKeys:
row = await db.fetchone(
- "SELECT * FROM nostr.keys WHERE id = ?",
- (lnurldevicepayment_id,),
+ "SELECT * FROM nostr.keys WHERE pubkey = ?",
+ (pubkey,),
)
return nostrKeys(**row) if row else None
@@ -64,9 +62,12 @@ async def get_nostrnotes(nostrnote_id: str) -> nostrNotes:
###############RELAYS##################
async def create_nostrrelays(
- relay: str
-) -> nostrRelays:
- nostrrelay_id = urlsafe_short_hash()
+ data: nostrCreateRelays
+) -> nostrCreateRelays:
+ nostrrelay_id = shortuuid.uuid(name=relay)
+
+ if await get_nostrrelays(nostrrelay_id):
+ return "error"
await db.execute(
"""
INSERT INTO nostr.relays (
@@ -75,7 +76,7 @@ async def create_nostrrelays(
)
VALUES (?, ?)
""",
- (nostrrelay_id, relay),
+ (nostrrelay_id, data.relay),
)
return await get_nostrnotes(nostrrelay_id)
@@ -90,45 +91,25 @@ async def get_nostrrelays(nostrrelay_id: str) -> nostrRelays:
###############CONNECTIONS##################
async def create_nostrconnections(
- data: nostrNotes
-) -> nostrNotes:
- nostrkey_id = urlsafe_short_hash()
+ data: nostrCreateConnections
+) -> nostrCreateConnections:
+ nostrrelay_id = shortuuid.uuid(name=data.relayid + data.pubkey)
await db.execute(
"""
- INSERT INTO nostr.notes (
+ INSERT INTO nostr.connections (
id,
pubkey,
- created_at,
- kind,
- tags,
- content,
- sig
+ relayid
)
- VALUES (?, ?, ?, ?, ?, ?, ?)
+ VALUES (?, ?, ?)
""",
- (data.id, data.pubkey, data.created_at, data.kind, data.tags, data.content, data.sig),
+ (data.id, data.pubkey, data.relayid),
)
- return await get_nostrnotes(data.id)
+ return await get_nostrconnections(data.id)
-async def get_nostrnotes(nostrnote_id: str) -> nostrNotes:
+async def get_nostrconnections(nostrconnections_id: str) -> nostrConnections:
row = await db.fetchone(
- "SELECT * FROM nostr.notes WHERE id = ?",
- (nostrnote_id,),
+ "SELECT * FROM nostr.connections WHERE id = ?",
+ (nostrconnections_id,),
)
- return nostrNotes(**row) if row else None
-
-
-
-async def update_lnurldevicepayment(
- lnurldevicepayment_id: str, **kwargs
-) -> Optional[lnurldevicepayment]:
- q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
- await db.execute(
- f"UPDATE lnurldevice.lnurldevicepayment SET {q} WHERE id = ?",
- (*kwargs.values(), lnurldevicepayment_id),
- )
- row = await db.fetchone(
- "SELECT * FROM lnurldevice.lnurldevicepayment WHERE id = ?",
- (lnurldevicepayment_id,),
- )
- return lnurldevicepayment(**row) if row else None
\ No newline at end of file
+ return nostrConnections(**row) if row else None
\ No newline at end of file
diff --git a/lnbits/extensions/nostr/migrations.py b/lnbits/extensions/nostr/migrations.py
index de32a578..0c616110 100644
--- a/lnbits/extensions/nostr/migrations.py
+++ b/lnbits/extensions/nostr/migrations.py
@@ -24,7 +24,7 @@ async def m001_initial(db):
kind INT NOT NULL,
tags TEXT NOT NULL,
content TEXT NOT NULL,
- sig TEXT NOT NULL,
+ sig TEXT NOT NULL
);
"""
)
diff --git a/lnbits/extensions/nostr/models.py b/lnbits/extensions/nostr/models.py
index ba0c87a5..e21e5d76 100644
--- a/lnbits/extensions/nostr/models.py
+++ b/lnbits/extensions/nostr/models.py
@@ -3,15 +3,10 @@ from sqlite3 import Row
from typing import Optional
from fastapi import Request
-from lnurl import Lnurl
-from lnurl import encode as lnurl_encode # type: ignore
-from lnurl.models import LnurlPaySuccessAction, UrlAction # type: ignore
-from lnurl.types import LnurlPayMetadata # type: ignore
from pydantic import BaseModel
from pydantic.main import BaseModel
class nostrKeys(BaseModel):
- id: str
pubkey: str
privkey: str
@@ -24,6 +19,13 @@ class nostrNotes(BaseModel):
content: str
sig: str
+class nostrCreateRelays(BaseModel):
+ relay: str
+
+class nostrCreateConnections(BaseModel):
+ pubkey: str
+ relayid: str
+
class nostrRelays(BaseModel):
id: str
relay: str
diff --git a/lnbits/extensions/nostr/templates/lnurldevice/_api_docs.html b/lnbits/extensions/nostr/templates/lnurldevice/_api_docs.html
deleted file mode 100644
index af69b76e..00000000
--- a/lnbits/extensions/nostr/templates/lnurldevice/_api_docs.html
+++ /dev/null
@@ -1,169 +0,0 @@
-
-
-
- Register LNURLDevice devices to receive payments in your LNbits wallet.
- Build your own here
- https://github.com/arcbtc/bitcoinpos
-
- Created by, Ben Arc
-
-
-
-
-
-
- /lnurldevice/api/v1/lnurlpos
- Headers
- {"X-Api-Key": <admin_key>}
-
- Body (application/json)
-
-
- Returns 200 OK (application/json)
-
- [<lnurldevice_object>, ...]
- Curl example
- curl -X POST {{ request.base_url }}api/v1/lnurldevice -d '{"title":
- <string>, "message":<string>, "currency":
- <integer>}' -H "Content-type: application/json" -H "X-Api-Key:
- {{user.wallets[0].adminkey }}"
-
-
-
-
-
-
-
- PUT
- /lnurldevice/api/v1/lnurlpos/<lnurldevice_id>
- Headers
- {"X-Api-Key": <admin_key>}
-
- Body (application/json)
-
-
- Returns 200 OK (application/json)
-
- [<lnurldevice_object>, ...]
- Curl example
- curl -X POST {{ request.base_url
- }}api/v1/lnurlpos/<lnurldevice_id> -d ''{"title":
- <string>, "message":<string>, "currency":
- <integer>} -H "Content-type: application/json" -H "X-Api-Key:
- {{user.wallets[0].adminkey }}"
-
-
-
-
-
-
-
-
- GET
- /lnurldevice/api/v1/lnurlpos/<lnurldevice_id>
- Headers
- {"X-Api-Key": <invoice_key>}
-
- Body (application/json)
-
-
- Returns 200 OK (application/json)
-
- [<lnurldevice_object>, ...]
- Curl example
- curl -X GET {{ request.base_url
- }}api/v1/lnurlpos/<lnurldevice_id> -H "X-Api-Key: {{
- user.wallets[0].inkey }}"
-
-
-
-
-
-
-
- GET
- /lnurldevice/api/v1/lnurlposs
- Headers
- {"X-Api-Key": <invoice_key>}
-
- Body (application/json)
-
-
- Returns 200 OK (application/json)
-
- [<lnurldevice_object>, ...]
- Curl example
- curl -X GET {{ request.base_url }}api/v1/lnurldevices -H
- "X-Api-Key: {{ user.wallets[0].inkey }}"
-
-
-
-
-
-
-
- DELETE
- /lnurldevice/api/v1/lnurlpos/<lnurldevice_id>
- Headers
- {"X-Api-Key": <admin_key>}
- Returns 204 NO CONTENT
-
- Curl example
- curl -X DELETE {{ request.base_url
- }}api/v1/lnurlpos/<lnurldevice_id> -H "X-Api-Key: {{
- user.wallets[0].adminkey }}"
-
-
-
-
-
-
diff --git a/lnbits/extensions/nostr/templates/lnurldevice/error.html b/lnbits/extensions/nostr/templates/lnurldevice/error.html
deleted file mode 100644
index d8e41832..00000000
--- a/lnbits/extensions/nostr/templates/lnurldevice/error.html
+++ /dev/null
@@ -1,34 +0,0 @@
-{% extends "public.html" %} {% block page %}
-
-
-
-
-
- LNURL-pay not paid
-
-
-
-
-
-
-
-
-
- {% endblock %} {% block scripts %}
-
-
-
- {% endblock %}
-
diff --git a/lnbits/extensions/nostr/templates/lnurldevice/index.html b/lnbits/extensions/nostr/templates/lnurldevice/index.html
deleted file mode 100644
index b51e2556..00000000
--- a/lnbits/extensions/nostr/templates/lnurldevice/index.html
+++ /dev/null
@@ -1,534 +0,0 @@
-{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
-%} {% block page %}
-
-
-
-
- {% raw %}
- New LNURLDevice instance
-
-
-
-
-
-
-
-
-
lNURLdevice
-
-
-
-
-
-
-
-
- Export to CSV
-
-
-
-
-
-
-
-
-
-
- {{ col.label }}
-
-
-
-
-
-
-
-
-
- Delete LNURLDevice
-
-
-
-
- LNURLDevice Settings
-
-
-
-
- {{ col.value }}
-
-
-
- {% endraw %}
-
-
-
-
-
-
-
-
-
- {{SITE_TITLE}} LNURLDevice Extension
-
-
-
-
- {% include "lnurldevice/_api_docs.html" %}
-
-
-
-
-
-
- LNURLDevice device string
- {% raw
- %}{{location}}/lnurldevice/api/v1/lnurl/{{settingsDialog.data.id}},
- {{settingsDialog.data.key}}, {{settingsDialog.data.currency}}{% endraw
- %} Click to copy URL
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Update lnurldevice
- Create lnurldevice
- Cancel
-
-
-
-
-
-{% endblock %} {% block scripts %} {{ window_vars(user) }}
-
-
-{% endblock %}
diff --git a/lnbits/extensions/nostr/templates/lnurldevice/paid.html b/lnbits/extensions/nostr/templates/lnurldevice/paid.html
deleted file mode 100644
index c185ecce..00000000
--- a/lnbits/extensions/nostr/templates/lnurldevice/paid.html
+++ /dev/null
@@ -1,27 +0,0 @@
-{% extends "public.html" %} {% block page %}
-
-
-
-
-
- {{ pin }}
-
-
-
-
-
-
- {% endblock %} {% block scripts %}
-
-
-
- {% endblock %}
-
diff --git a/lnbits/extensions/nostr/templates/nostr/index.html b/lnbits/extensions/nostr/templates/nostr/index.html
new file mode 100644
index 00000000..f17d0243
--- /dev/null
+++ b/lnbits/extensions/nostr/templates/nostr/index.html
@@ -0,0 +1,159 @@
+{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
+%} {% block page %}
+
+
+
+
+
+
+
Nostr
+
+
+
+
+
+
+
+
+ Export to CSV
+
+
+
+ {% raw %}
+
+
+
+
+
+
+
+ {{ col.label }}
+
+
+
+
+
+
+
+
+
+ {{ col.value }}
+
+
+
+ {% endraw %}
+
+
+
+
+
+
+
+
+ {{SITE_TITLE}} Nostr Extension
+
+ Okay
+
+
+
+
+{% endblock %} {% block scripts %} {{ window_vars(user) }}
+
+
+{% endblock %}
diff --git a/lnbits/extensions/nostr/views_api.py b/lnbits/extensions/nostr/views_api.py
index a479cad8..9e7ccfff 100644
--- a/lnbits/extensions/nostr/views_api.py
+++ b/lnbits/extensions/nostr/views_api.py
@@ -7,42 +7,18 @@ from starlette.exceptions import HTTPException
from lnbits.core.crud import get_user
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
-from lnbits.extensions.lnurldevice import lnurldevice_ext
+from lnbits.extensions.nostr import nostr_ext
from lnbits.utils.exchange_rates import currencies
-from . import lnurldevice_ext
+from . import nostr_ext
from .crud import (
- create_lnurldevice,
- delete_lnurldevice,
- get_lnurldevice,
- get_lnurldevices,
- update_lnurldevice,
+ create_nostrkeys,
+ get_nostrkeys,
+ create_nostrnotes,
+ get_nostrnotes,
+ create_nostrrelays,
+ get_nostrrelays,
+ create_nostrconnections,
+ get_nostrconnections,
)
-from .models import createLnurldevice
-
-
-@nostr_ext.get("/api/v1/lnurlpos")
-async def api_check_daemon(wallet: WalletTypeInfo = Depends(get_key_type)):
- wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
- try:
- return [
- {**lnurldevice.dict()} for lnurldevice in await get_lnurldevices(wallet_ids)
- ]
- except:
- return ""
-
-@nostr_ext.delete("/api/v1/lnurlpos/{lnurldevice_id}")
-async def api_lnurldevice_delete(
- wallet: WalletTypeInfo = Depends(require_admin_key),
- lnurldevice_id: str = Query(None),
-):
- lnurldevice = await get_lnurldevice(lnurldevice_id)
-
- if not lnurldevice:
- raise HTTPException(
- status_code=HTTPStatus.NOT_FOUND, detail="Wallet link does not exist."
- )
-
- await delete_lnurldevice(lnurldevice_id)
-
- return "", HTTPStatus.NO_CONTENT
\ No newline at end of file
+from .models import nostrKeys
\ No newline at end of file
From 29bd8d9ce963b4a9876120fbe4354121680ca32e Mon Sep 17 00:00:00 2001
From: benarc
Date: Tue, 8 Feb 2022 10:35:20 +0000
Subject: [PATCH 013/226] Chnaged nostr to nostradmin
---
.env.example | 2 +-
lnbits/extensions/nostr/config.json | 6 -
lnbits/extensions/nostr/crud.py | 115 -------------
lnbits/extensions/nostr/views_api.py | 24 ---
.../{nostr => nostradmin}/README.md | 0
.../{nostr => nostradmin}/__init__.py | 7 +-
lnbits/extensions/nostradmin/config.json | 6 +
lnbits/extensions/nostradmin/crud.py | 156 ++++++++++++++++++
.../{nostr => nostradmin}/migrations.py | 32 +++-
.../{nostr => nostradmin}/models.py | 14 +-
.../templates/nostradmin}/index.html | 130 ++++++++++++++-
.../extensions/{nostr => nostradmin}/views.py | 2 +-
lnbits/extensions/nostradmin/views_api.py | 87 ++++++++++
13 files changed, 421 insertions(+), 160 deletions(-)
delete mode 100644 lnbits/extensions/nostr/config.json
delete mode 100644 lnbits/extensions/nostr/crud.py
delete mode 100644 lnbits/extensions/nostr/views_api.py
rename lnbits/extensions/{nostr => nostradmin}/README.md (100%)
rename lnbits/extensions/{nostr => nostradmin}/__init__.py (51%)
create mode 100644 lnbits/extensions/nostradmin/config.json
create mode 100644 lnbits/extensions/nostradmin/crud.py
rename lnbits/extensions/{nostr => nostradmin}/migrations.py (54%)
rename lnbits/extensions/{nostr => nostradmin}/models.py (73%)
rename lnbits/extensions/{nostr/templates/nostr => nostradmin/templates/nostradmin}/index.html (50%)
rename lnbits/extensions/{nostr => nostradmin}/views.py (90%)
create mode 100644 lnbits/extensions/nostradmin/views_api.py
diff --git a/.env.example b/.env.example
index 060748a9..0d5b497e 100644
--- a/.env.example
+++ b/.env.example
@@ -8,7 +8,7 @@ PORT=5000
LNBITS_ALLOWED_USERS=""
LNBITS_ADMIN_USERS=""
# Extensions only admin can access
-LNBITS_ADMIN_EXTENSIONS="ngrok"
+LNBITS_ADMIN_EXTENSIONS="nostradmin"
LNBITS_DEFAULT_WALLET_NAME="LNbits wallet"
# Disable extensions for all users, use "all" to disable all extensions
diff --git a/lnbits/extensions/nostr/config.json b/lnbits/extensions/nostr/config.json
deleted file mode 100644
index a32e39a1..00000000
--- a/lnbits/extensions/nostr/config.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "name": "Nostr",
- "short_description": "Daemon for Nostr",
- "icon": "swap_horizontal_circle",
- "contributors": ["arcbtc"]
-}
diff --git a/lnbits/extensions/nostr/crud.py b/lnbits/extensions/nostr/crud.py
deleted file mode 100644
index 0b30bc9a..00000000
--- a/lnbits/extensions/nostr/crud.py
+++ /dev/null
@@ -1,115 +0,0 @@
-from typing import List, Optional, Union
-
-from lnbits.helpers import urlsafe_short_hash
-import shortuuid
-from . import db
-from .models import nostrKeys, nostrNotes, nostrCreateRelays, nostrRelays, nostrConnections, nostrCreateConnections
-
-###############KEYS##################
-
-async def create_nostrkeys(
- data: nostrKeys
-) -> nostrKeys:
- await db.execute(
- """
- INSERT INTO nostr.keys (
- pubkey,
- privkey
- )
- VALUES (?, ?)
- """,
- (data.pubkey, data.privkey),
- )
- return await get_nostrkeys(nostrkey_id)
-
-async def get_nostrkeys(pubkey: str) -> nostrKeys:
- row = await db.fetchone(
- "SELECT * FROM nostr.keys WHERE pubkey = ?",
- (pubkey,),
- )
- return nostrKeys(**row) if row else None
-
-
-###############NOTES##################
-
-async def create_nostrnotes(
- data: nostrNotes
-) -> nostrNotes:
- await db.execute(
- """
- INSERT INTO nostr.notes (
- id,
- pubkey,
- created_at,
- kind,
- tags,
- content,
- sig
- )
- VALUES (?, ?, ?, ?, ?, ?, ?)
- """,
- (data.id, data.pubkey, data.created_at, data.kind, data.tags, data.content, data.sig),
- )
- return await get_nostrnotes(data.id)
-
-async def get_nostrnotes(nostrnote_id: str) -> nostrNotes:
- row = await db.fetchone(
- "SELECT * FROM nostr.notes WHERE id = ?",
- (nostrnote_id,),
- )
- return nostrNotes(**row) if row else None
-
-###############RELAYS##################
-
-async def create_nostrrelays(
- data: nostrCreateRelays
-) -> nostrCreateRelays:
- nostrrelay_id = shortuuid.uuid(name=relay)
-
- if await get_nostrrelays(nostrrelay_id):
- return "error"
- await db.execute(
- """
- INSERT INTO nostr.relays (
- id,
- relay
- )
- VALUES (?, ?)
- """,
- (nostrrelay_id, data.relay),
- )
- return await get_nostrnotes(nostrrelay_id)
-
-async def get_nostrrelays(nostrrelay_id: str) -> nostrRelays:
- row = await db.fetchone(
- "SELECT * FROM nostr.relays WHERE id = ?",
- (nostrnote_id,),
- )
- return nostrRelays(**row) if row else None
-
-
-###############CONNECTIONS##################
-
-async def create_nostrconnections(
- data: nostrCreateConnections
-) -> nostrCreateConnections:
- nostrrelay_id = shortuuid.uuid(name=data.relayid + data.pubkey)
- await db.execute(
- """
- INSERT INTO nostr.connections (
- id,
- pubkey,
- relayid
- )
- VALUES (?, ?, ?)
- """,
- (data.id, data.pubkey, data.relayid),
- )
- return await get_nostrconnections(data.id)
-
-async def get_nostrconnections(nostrconnections_id: str) -> nostrConnections:
- row = await db.fetchone(
- "SELECT * FROM nostr.connections WHERE id = ?",
- (nostrconnections_id,),
- )
- return nostrConnections(**row) if row else None
\ No newline at end of file
diff --git a/lnbits/extensions/nostr/views_api.py b/lnbits/extensions/nostr/views_api.py
deleted file mode 100644
index 9e7ccfff..00000000
--- a/lnbits/extensions/nostr/views_api.py
+++ /dev/null
@@ -1,24 +0,0 @@
-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 WalletTypeInfo, get_key_type, require_admin_key
-from lnbits.extensions.nostr import nostr_ext
-from lnbits.utils.exchange_rates import currencies
-
-from . import nostr_ext
-from .crud import (
- create_nostrkeys,
- get_nostrkeys,
- create_nostrnotes,
- get_nostrnotes,
- create_nostrrelays,
- get_nostrrelays,
- create_nostrconnections,
- get_nostrconnections,
-)
-from .models import nostrKeys
\ No newline at end of file
diff --git a/lnbits/extensions/nostr/README.md b/lnbits/extensions/nostradmin/README.md
similarity index 100%
rename from lnbits/extensions/nostr/README.md
rename to lnbits/extensions/nostradmin/README.md
diff --git a/lnbits/extensions/nostr/__init__.py b/lnbits/extensions/nostradmin/__init__.py
similarity index 51%
rename from lnbits/extensions/nostr/__init__.py
rename to lnbits/extensions/nostradmin/__init__.py
index 9774a279..7034ca46 100644
--- a/lnbits/extensions/nostr/__init__.py
+++ b/lnbits/extensions/nostradmin/__init__.py
@@ -3,13 +3,14 @@ from fastapi import APIRouter
from lnbits.db import Database
from lnbits.helpers import template_renderer
-db = Database("ext_nostr")
+db = Database("ext_nostradmin")
-nostr_ext: APIRouter = APIRouter(prefix="/nostr", tags=["nostr"])
+nostr_ext: APIRouter = APIRouter(prefix="/nostradmin", tags=["nostradmin"])
def nostr_renderer():
- return template_renderer(["lnbits/extensions/nostr/templates"])
+ return template_renderer(["lnbits/extensions/nostradmin/templates"])
+
from .views import * # noqa
from .views_api import * # noqa
diff --git a/lnbits/extensions/nostradmin/config.json b/lnbits/extensions/nostradmin/config.json
new file mode 100644
index 00000000..2c4f76d3
--- /dev/null
+++ b/lnbits/extensions/nostradmin/config.json
@@ -0,0 +1,6 @@
+{
+ "name": "NostrAdmin",
+ "short_description": "Admin daemon for Nostr",
+ "icon": "swap_horizontal_circle",
+ "contributors": ["arcbtc"]
+}
diff --git a/lnbits/extensions/nostradmin/crud.py b/lnbits/extensions/nostradmin/crud.py
new file mode 100644
index 00000000..08908e85
--- /dev/null
+++ b/lnbits/extensions/nostradmin/crud.py
@@ -0,0 +1,156 @@
+from typing import List, Optional, Union
+
+from lnbits.helpers import urlsafe_short_hash
+import shortuuid
+from . import db
+from .models import (
+ nostrKeys,
+ nostrNotes,
+ nostrCreateRelays,
+ nostrRelays,
+ nostrConnections,
+ nostrCreateConnections,
+ nostrRelayList,
+)
+
+###############KEYS##################
+
+
+async def create_nostrkeys(data: nostrKeys) -> nostrKeys:
+ await db.execute(
+ """
+ INSERT INTO nostradmin.keys (
+ pubkey,
+ privkey
+ )
+ VALUES (?, ?)
+ """,
+ (data.pubkey, data.privkey),
+ )
+ return await get_nostrkeys(nostrkey_id)
+
+
+async def get_nostrkeys(pubkey: str) -> nostrKeys:
+ row = await db.fetchone("SELECT * FROM nostradmin.keys WHERE pubkey = ?", (pubkey,))
+ return nostrKeys(**row) if row else None
+
+
+###############NOTES##################
+
+
+async def create_nostrnotes(data: nostrNotes) -> nostrNotes:
+ await db.execute(
+ """
+ INSERT INTO nostradmin.notes (
+ id,
+ pubkey,
+ created_at,
+ kind,
+ tags,
+ content,
+ sig
+ )
+ VALUES (?, ?, ?, ?, ?, ?, ?)
+ """,
+ (
+ data.id,
+ data.pubkey,
+ data.created_at,
+ data.kind,
+ data.tags,
+ data.content,
+ data.sig,
+ ),
+ )
+ return await get_nostrnotes(data.id)
+
+
+async def get_nostrnotes(nostrnote_id: str) -> nostrNotes:
+ row = await db.fetchone("SELECT * FROM nostradmin.notes WHERE id = ?", (nostrnote_id,))
+ return nostrNotes(**row) if row else None
+
+
+###############RELAYS##################
+
+
+async def create_nostrrelays(data: nostrCreateRelays) -> nostrCreateRelays:
+ nostrrelay_id = shortuuid.uuid(name=data.relay)
+
+ if await get_nostrrelay(nostrrelay_id):
+ return "error"
+ await db.execute(
+ """
+ INSERT INTO nostradmin.relays (
+ id,
+ relay
+ )
+ VALUES (?, ?)
+ """,
+ (nostrrelay_id, data.relay),
+ )
+ return await get_nostrrelay(nostrrelay_id)
+
+
+async def get_nostrrelays() -> nostrRelays:
+ rows = await db.fetchall("SELECT * FROM nostradmin.relays")
+ return [nostrRelays(**row) for row in rows]
+
+
+async def get_nostrrelay(nostrrelay_id: str) -> nostrRelays:
+ row = await db.fetchone("SELECT * FROM nostradmin.relays WHERE id = ?", (nostrrelay_id,))
+ return nostrRelays(**row) if row else None
+
+
+async def update_nostrrelayallowlist(allowlist: str) -> nostrRelayList:
+ await db.execute(
+ """
+ UPDATE nostradmin.relaylist SET
+ allowlist = ?
+ WHERE id = ?
+ """,
+ (allowlist, 1),
+ )
+ return await get_nostrrelaylist()
+
+async def update_nostrrelaydenylist(denylist: str) -> nostrRelayList:
+ await db.execute(
+ """
+ UPDATE nostradmin.relaylist SET
+ denylist = ?
+ WHERE id = ?
+ """,
+ (denylist, 1),
+ )
+ return await get_nostrrelaylist()
+
+async def get_nostrrelaylist() -> nostrRelayList:
+ row = await db.fetchone("SELECT * FROM nostradmin.relaylist WHERE id = ?", (1,))
+ return nostrRelayList(**row) if row else None
+
+
+###############CONNECTIONS##################
+
+
+async def create_nostrconnections(
+ data: nostrCreateConnections
+) -> nostrCreateConnections:
+ nostrrelay_id = shortuuid.uuid(name=data.relayid + data.pubkey)
+ await db.execute(
+ """
+ INSERT INTO nostradmin.connections (
+ id,
+ pubkey,
+ relayid
+ )
+ VALUES (?, ?, ?)
+ """,
+ (data.id, data.pubkey, data.relayid),
+ )
+ return await get_nostrconnections(data.id)
+
+
+async def get_nostrconnections(nostrconnections_id: str) -> nostrConnections:
+ row = await db.fetchone(
+ "SELECT * FROM nostradmin.connections WHERE id = ?", (nostrconnections_id,)
+ )
+ return nostrConnections(**row) if row else None
diff --git a/lnbits/extensions/nostr/migrations.py b/lnbits/extensions/nostradmin/migrations.py
similarity index 54%
rename from lnbits/extensions/nostr/migrations.py
rename to lnbits/extensions/nostradmin/migrations.py
index 0c616110..09b28117 100644
--- a/lnbits/extensions/nostr/migrations.py
+++ b/lnbits/extensions/nostradmin/migrations.py
@@ -9,7 +9,7 @@ async def m001_initial(db):
"""
await db.execute(
f"""
- CREATE TABLE nostr.keys (
+ CREATE TABLE nostradmin.keys (
pubkey TEXT NOT NULL PRIMARY KEY,
privkey TEXT NOT NULL
);
@@ -17,7 +17,7 @@ async def m001_initial(db):
)
await db.execute(
f"""
- CREATE TABLE nostr.notes (
+ CREATE TABLE nostradmin.notes (
id TEXT NOT NULL PRIMARY KEY,
pubkey TEXT NOT NULL,
created_at TEXT NOT NULL,
@@ -30,7 +30,7 @@ async def m001_initial(db):
)
await db.execute(
f"""
- CREATE TABLE nostr.relays (
+ CREATE TABLE nostradmin.relays (
id TEXT NOT NULL PRIMARY KEY,
relay TEXT NOT NULL
);
@@ -38,10 +38,32 @@ async def m001_initial(db):
)
await db.execute(
f"""
- CREATE TABLE nostr.connections (
+ CREATE TABLE nostradmin.relaylists (
+ id TEXT NOT NULL PRIMARY KEY DEFAULT 1,
+ allowlist TEXT NOT NULL,
+ denylist TEXT NOT NULL
+ );
+ """
+ )
+ try:
+ await db.execute(
+ """
+ INSERT INTO nostradmin.relaylist (
+ id,
+ denylist
+ )
+ VALUES (?, ?,)
+ """,
+ (1, "\n".join(["wss://zucks-meta-relay.com", "wss://nostradmin.cia.gov"])),
+ )
+ except:
+ return
+ await db.execute(
+ f"""
+ CREATE TABLE nostradmin.connections (
id TEXT NOT NULL PRIMARY KEY,
publickey TEXT NOT NULL,
relayid TEXT NOT NULL
);
"""
- )
\ No newline at end of file
+ )
diff --git a/lnbits/extensions/nostr/models.py b/lnbits/extensions/nostradmin/models.py
similarity index 73%
rename from lnbits/extensions/nostr/models.py
rename to lnbits/extensions/nostradmin/models.py
index e21e5d76..fd89f515 100644
--- a/lnbits/extensions/nostr/models.py
+++ b/lnbits/extensions/nostradmin/models.py
@@ -6,6 +6,7 @@ from fastapi import Request
from pydantic import BaseModel
from pydantic.main import BaseModel
+
class nostrKeys(BaseModel):
pubkey: str
privkey: str
@@ -30,7 +31,18 @@ class nostrRelays(BaseModel):
id: str
relay: str
+class nostrRelayList(BaseModel):
+ id: str
+ allowlist: str
+ denylist: str
+
+class nostrRelayDenyList(BaseModel):
+ denylist: str
+
+class nostrRelayAllowList(BaseModel):
+ allowlist: str
+
class nostrConnections(BaseModel):
id: str
pubkey: str
- relayid: str
\ No newline at end of file
+ relayid: str
diff --git a/lnbits/extensions/nostr/templates/nostr/index.html b/lnbits/extensions/nostradmin/templates/nostradmin/index.html
similarity index 50%
rename from lnbits/extensions/nostr/templates/nostr/index.html
rename to lnbits/extensions/nostradmin/templates/nostradmin/index.html
index f17d0243..adab98e2 100644
--- a/lnbits/extensions/nostr/templates/nostr/index.html
+++ b/lnbits/extensions/nostradmin/templates/nostradmin/index.html
@@ -71,12 +71,77 @@
+
+
+
+
+
+
+
+
+
+
+
+ Deny List (denys use of relays in this list)
+
+
+
+
+
+
+
+
+ Update Deny List
+
+ Reset
+
+
+
+
+
+
+
+ Allow List (denys any relays not in this list)
+
+
+
+
+
+
+
+
+ Update Allow List
+
+ Reset
+
+
+
+
+
+
{{SITE_TITLE}} Nostr Extension
+ Only Admin users can manage this extension
Okay
@@ -109,6 +174,9 @@
mixins: [windowMixin],
data: function () {
return {
+ listSelection: 'denylist',
+ allowList: [],
+ denyList: [],
nostrTable: {
columns: [
{
@@ -136,12 +204,66 @@
LNbits.api
.request(
'GET',
- '/nostr/api/v1/relays',
+ '/nostradmin/api/v1/relays',
self.g.user.wallets[0].adminkey
)
.then(function (response) {
if (response.data) {
- self.lnurldeviceLinks = response.data.map(maplnurldevice)
+ self.nostrrelayLinks = response.data.map(maprelays)
+ }
+ })
+ .catch(function (error) {
+ LNbits.utils.notifyApiError(error)
+ })
+ },
+ setAllowList: function () {
+ var self = this
+ LNbits.api
+ .request(
+ 'POST',
+ '/nostradmin/api/v1/allowlist',
+ self.g.user.wallets[0].adminkey,
+ self.allowList
+ )
+ .then(function (response) {
+ if (response.data) {
+ self.allowList = response.data
+ }
+ })
+ .catch(function (error) {
+ LNbits.utils.notifyApiError(error)
+ })
+ },
+ setDenyList: function () {
+ var self = this
+ LNbits.api
+ .request(
+ 'POST',
+ '/nostradmin/api/v1/denylist',
+ self.g.user.wallets[0].adminkey,
+ self.allowList
+ )
+ .then(function (response) {
+ if (response.data) {
+ self.denyList = response.data
+ }
+ })
+ .catch(function (error) {
+ LNbits.utils.notifyApiError(error)
+ })
+ },
+ getLists: function () {
+ var self = this
+ LNbits.api
+ .request(
+ 'GET',
+ '/nostradmin/api/v1/relaylist',
+ self.g.user.wallets[0].adminkey
+ )
+ .then(function (response) {
+ if (response.data) {
+ self.denyList = response.data.denylist
+ self.allowList = response.data.allowlist
}
})
.catch(function (error) {
@@ -151,8 +273,8 @@
},
created: function () {
var self = this
- var getrelays = this.getrelays
- getrelays()
+ this.getrelays()
+ this.getLists()
}
})
diff --git a/lnbits/extensions/nostr/views.py b/lnbits/extensions/nostradmin/views.py
similarity index 90%
rename from lnbits/extensions/nostr/views.py
rename to lnbits/extensions/nostradmin/views.py
index 1dfc07da..5609c218 100644
--- a/lnbits/extensions/nostr/views.py
+++ b/lnbits/extensions/nostradmin/views.py
@@ -20,5 +20,5 @@ templates = Jinja2Templates(directory="templates")
@nostr_ext.get("/", response_class=HTMLResponse)
async def index(request: Request, user: User = Depends(check_user_exists)):
return nostr_renderer().TemplateResponse(
- "nostr/index.html", {"request": request, "user": user.dict()}
+ "nostradmin/index.html", {"request": request, "user": user.dict()}
)
diff --git a/lnbits/extensions/nostradmin/views_api.py b/lnbits/extensions/nostradmin/views_api.py
new file mode 100644
index 00000000..da6e140b
--- /dev/null
+++ b/lnbits/extensions/nostradmin/views_api.py
@@ -0,0 +1,87 @@
+from http import HTTPStatus
+import asyncio
+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 WalletTypeInfo, get_key_type, require_admin_key
+from lnbits.extensions.nostr import nostr_ext
+from lnbits.utils.exchange_rates import currencies
+
+from . import nostr_ext
+from lnbits.settings import LNBITS_ADMIN_USERS
+from .crud import (
+ create_nostrkeys,
+ get_nostrkeys,
+ create_nostrnotes,
+ get_nostrnotes,
+ create_nostrrelays,
+ get_nostrrelays,
+ get_nostrrelaylist,
+ update_nostrrelayallowlist,
+ update_nostrrelaydenylist,
+ create_nostrconnections,
+ get_nostrconnections,
+)
+from .models import nostrKeys, nostrCreateRelays, nostrRelayAllowList, nostrRelayDenyList
+
+
+# while True:
+async def nostr_subscribe():
+ return
+ # for the relays:
+ # async with websockets.connect("ws://localhost:8765") as websocket:
+ # for the public keys:
+ # await websocket.send("subscribe to events")
+ # await websocket.recv()
+
+
+websocket_queue = asyncio.Queue(1000)
+
+
+async def internal_invoice_listener():
+ while True:
+ checking_id = await internal_invoice_queue.get()
+ asyncio.create_task(invoice_callback_dispatcher(checking_id))
+
+
+@nostr_ext.get("/api/v1/relays")
+async def api_relays_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)):
+
+ relays = await get_nostrrelays()
+ if not relays:
+ await create_nostrrelays(nostrCreateRelays(relay="wss://relayer.fiatjaf.com"))
+ await create_nostrrelays(
+ nostrCreateRelays(relay="wss://nostr-pub.wellorder.net")
+ )
+ relays = await get_nostrrelays()
+ try:
+ return [{**relays.dict()} for relays in await relays]
+ except:
+ None
+
+@nostr_ext.get("/api/v1/relaylist")
+async def api_relaylist(wallet: WalletTypeInfo = Depends(get_key_type)):
+ if wallet.wallet.user not in LNBITS_ADMIN_USERS:
+ raise HTTPException(
+ status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
+ )
+ return await get_nostrrelaylist()
+
+@nostr_ext.post("/api/v1/allowlist")
+async def api_relaysallowed(data: nostrRelayAllowList, wallet: WalletTypeInfo = Depends(get_key_type)):
+ if wallet.wallet.user not in LNBITS_ADMIN_USERS:
+ raise HTTPException(
+ status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
+ )
+ return await update_nostrrelayallowlist(data)
+
+@nostr_ext.post("/api/v1/denylist")
+async def api_relaysdenyed(data: nostrRelayDenyList, wallet: WalletTypeInfo = Depends(get_key_type)):
+ if wallet.wallet.user not in LNBITS_ADMIN_USERS:
+ raise HTTPException(
+ status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
+ )
+ return await update_nostrrelaydenylist(data)
\ No newline at end of file
From 2083ecbc27b00ad4aca605425a183b8122e7d995 Mon Sep 17 00:00:00 2001
From: benarc
Date: Tue, 8 Feb 2022 13:13:36 +0000
Subject: [PATCH 014/226] models decided
---
lnbits/extensions/nostradmin/__init__.py | 2 +-
lnbits/extensions/nostradmin/crud.py | 22 ++----
lnbits/extensions/nostradmin/migrations.py | 41 ++++++-----
lnbits/extensions/nostradmin/models.py | 33 +++++----
.../templates/nostradmin/index.html | 71 +++++++++++--------
lnbits/extensions/nostradmin/views.py | 5 +-
lnbits/extensions/nostradmin/views_api.py | 33 +++------
7 files changed, 100 insertions(+), 107 deletions(-)
diff --git a/lnbits/extensions/nostradmin/__init__.py b/lnbits/extensions/nostradmin/__init__.py
index 7034ca46..797542c4 100644
--- a/lnbits/extensions/nostradmin/__init__.py
+++ b/lnbits/extensions/nostradmin/__init__.py
@@ -5,7 +5,7 @@ from lnbits.helpers import template_renderer
db = Database("ext_nostradmin")
-nostr_ext: APIRouter = APIRouter(prefix="/nostradmin", tags=["nostradmin"])
+nostradmin_ext: APIRouter = APIRouter(prefix="/nostradmin", tags=["nostradmin"])
def nostr_renderer():
diff --git a/lnbits/extensions/nostradmin/crud.py b/lnbits/extensions/nostradmin/crud.py
index 08908e85..1e1bf810 100644
--- a/lnbits/extensions/nostradmin/crud.py
+++ b/lnbits/extensions/nostradmin/crud.py
@@ -12,6 +12,7 @@ from .models import (
nostrCreateConnections,
nostrRelayList,
)
+from .models import nostrKeys, nostrCreateRelays, nostrRelaySetList
###############KEYS##################
@@ -100,31 +101,20 @@ async def get_nostrrelay(nostrrelay_id: str) -> nostrRelays:
row = await db.fetchone("SELECT * FROM nostradmin.relays WHERE id = ?", (nostrrelay_id,))
return nostrRelays(**row) if row else None
-
-async def update_nostrrelayallowlist(allowlist: str) -> nostrRelayList:
+async def update_nostrrelaysetlist(data: nostrRelaySetList) -> nostrRelayList:
await db.execute(
"""
- UPDATE nostradmin.relaylist SET
+ UPDATE nostradmin.relaylists SET
+ denylist = ?,
allowlist = ?
WHERE id = ?
""",
- (allowlist, 1),
- )
- return await get_nostrrelaylist()
-
-async def update_nostrrelaydenylist(denylist: str) -> nostrRelayList:
- await db.execute(
- """
- UPDATE nostradmin.relaylist SET
- denylist = ?
- WHERE id = ?
- """,
- (denylist, 1),
+ (data.denylist, data.allowlist, 1),
)
return await get_nostrrelaylist()
async def get_nostrrelaylist() -> nostrRelayList:
- row = await db.fetchone("SELECT * FROM nostradmin.relaylist WHERE id = ?", (1,))
+ row = await db.fetchone("SELECT * FROM nostradmin.relaylists WHERE id = ?", (1,))
return nostrRelayList(**row) if row else None
diff --git a/lnbits/extensions/nostradmin/migrations.py b/lnbits/extensions/nostradmin/migrations.py
index 09b28117..590f72ea 100644
--- a/lnbits/extensions/nostradmin/migrations.py
+++ b/lnbits/extensions/nostradmin/migrations.py
@@ -1,11 +1,8 @@
from lnbits.db import Database
-db2 = Database("ext_nostr")
-
-
async def m001_initial(db):
"""
- Initial nostr table.
+ Initial nostradmin table.
"""
await db.execute(
f"""
@@ -40,24 +37,23 @@ async def m001_initial(db):
f"""
CREATE TABLE nostradmin.relaylists (
id TEXT NOT NULL PRIMARY KEY DEFAULT 1,
- allowlist TEXT NOT NULL,
- denylist TEXT NOT NULL
+ allowlist TEXT,
+ denylist TEXT
);
"""
)
- try:
- await db.execute(
- """
- INSERT INTO nostradmin.relaylist (
- id,
- denylist
- )
- VALUES (?, ?,)
- """,
- (1, "\n".join(["wss://zucks-meta-relay.com", "wss://nostradmin.cia.gov"])),
+
+ await db.execute(
+ """
+ INSERT INTO nostradmin.relaylists (
+ id,
+ denylist
)
- except:
- return
+ VALUES (?, ?)
+ """,
+ ("1", "wss://zucks-meta-relay.com\nwss://nostr.cia.gov",),
+ )
+
await db.execute(
f"""
CREATE TABLE nostradmin.connections (
@@ -67,3 +63,12 @@ async def m001_initial(db):
);
"""
)
+ await db.execute(
+ f"""
+ CREATE TABLE nostradmin.subscribed (
+ id TEXT NOT NULL PRIMARY KEY,
+ userPubkey TEXT NOT NULL,
+ subscribedPubkey TEXT NOT NULL
+ );
+ """
+ )
\ No newline at end of file
diff --git a/lnbits/extensions/nostradmin/models.py b/lnbits/extensions/nostradmin/models.py
index fd89f515..1968567f 100644
--- a/lnbits/extensions/nostradmin/models.py
+++ b/lnbits/extensions/nostradmin/models.py
@@ -5,7 +5,7 @@ from typing import Optional
from fastapi import Request
from pydantic import BaseModel
from pydantic.main import BaseModel
-
+from fastapi.param_functions import Query
class nostrKeys(BaseModel):
pubkey: str
@@ -21,28 +21,31 @@ class nostrNotes(BaseModel):
sig: str
class nostrCreateRelays(BaseModel):
- relay: str
+ relay: str = Query(None)
class nostrCreateConnections(BaseModel):
- pubkey: str
- relayid: str
+ pubkey: str = Query(None)
+ relayid: str = Query(None)
class nostrRelays(BaseModel):
- id: str
- relay: str
+ id: Optional[str]
+ relay: Optional[str]
class nostrRelayList(BaseModel):
id: str
- allowlist: str
- denylist: str
+ allowlist: Optional[str]
+ denylist: Optional[str]
-class nostrRelayDenyList(BaseModel):
- denylist: str
-
-class nostrRelayAllowList(BaseModel):
- allowlist: str
+class nostrRelaySetList(BaseModel):
+ allowlist: Optional[str]
+ denylist: Optional[str]
class nostrConnections(BaseModel):
id: str
- pubkey: str
- relayid: str
+ pubkey: Optional[str]
+ relayid: Optional[str]
+
+class nostrSubscriptions(BaseModel):
+ id: str
+ userPubkey: Optional[str]
+ subscribedPubkey: Optional[str]
\ No newline at end of file
diff --git a/lnbits/extensions/nostradmin/templates/nostradmin/index.html b/lnbits/extensions/nostradmin/templates/nostradmin/index.html
index adab98e2..27decdc8 100644
--- a/lnbits/extensions/nostradmin/templates/nostradmin/index.html
+++ b/lnbits/extensions/nostradmin/templates/nostradmin/index.html
@@ -6,7 +6,7 @@
-
Nostr
+ NOSTR RELAYS ONLINE
@@ -29,7 +29,7 @@
-
-
+
+
-
+
-
+
-
- Deny List (denys use of relays in this list)
-
-
+ Relays in this list will NOT be used
+
-
+
Update Deny List
- Reset
-
- Allow List (denys any relays not in this list)
-
-
+ ONLY relays in this list will be used
+
-
+
Update Allow List
- Reset
@@ -175,8 +175,12 @@
data: function () {
return {
listSelection: 'denylist',
- allowList: [],
- denyList: [],
+ setList: {
+ allowlist: '',
+ denylist: ''
+ },
+ nostrLinks: [],
+ filter: '',
nostrTable: {
columns: [
{
@@ -234,18 +238,20 @@
LNbits.utils.notifyApiError(error)
})
},
- setDenyList: function () {
+ setRelayList: function () {
var self = this
+ console.log(self.setList)
LNbits.api
.request(
'POST',
- '/nostradmin/api/v1/denylist',
+ '/nostradmin/api/v1/setlist',
self.g.user.wallets[0].adminkey,
- self.allowList
+ self.setList
)
.then(function (response) {
if (response.data) {
- self.denyList = response.data
+ console.log(response.data)
+ // self.denyList = response.data
}
})
.catch(function (error) {
@@ -262,13 +268,18 @@
)
.then(function (response) {
if (response.data) {
- self.denyList = response.data.denylist
- self.allowList = response.data.allowlist
+ console.log(response.data)
+ self.setList.denylist = response.data.denylist
+ self.setList.allowlist = response.data.allowlist
}
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
+ },
+ exportlnurldeviceCSV: function () {
+ var self = this
+ LNbits.utils.exportCSV(self.nostrTable.columns, this.nostrLinks)
}
},
created: function () {
diff --git a/lnbits/extensions/nostradmin/views.py b/lnbits/extensions/nostradmin/views.py
index 5609c218..51297320 100644
--- a/lnbits/extensions/nostradmin/views.py
+++ b/lnbits/extensions/nostradmin/views.py
@@ -6,18 +6,19 @@ from fastapi.params import Depends
from fastapi.templating import Jinja2Templates
from starlette.exceptions import HTTPException
from starlette.responses import HTMLResponse
+from . import nostradmin_ext, nostr_renderer
from lnbits.core.crud import update_payment_status
from lnbits.core.models import User
from lnbits.core.views.api import api_payment
from lnbits.decorators import check_user_exists
-from . import nostr_ext, nostr_renderer
+
templates = Jinja2Templates(directory="templates")
-@nostr_ext.get("/", response_class=HTMLResponse)
+@nostradmin_ext.get("/", response_class=HTMLResponse)
async def index(request: Request, user: User = Depends(check_user_exists)):
return nostr_renderer().TemplateResponse(
"nostradmin/index.html", {"request": request, "user": user.dict()}
diff --git a/lnbits/extensions/nostradmin/views_api.py b/lnbits/extensions/nostradmin/views_api.py
index da6e140b..d79315ac 100644
--- a/lnbits/extensions/nostradmin/views_api.py
+++ b/lnbits/extensions/nostradmin/views_api.py
@@ -7,11 +7,10 @@ from starlette.exceptions import HTTPException
from lnbits.core.crud import get_user
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
-from lnbits.extensions.nostr import nostr_ext
from lnbits.utils.exchange_rates import currencies
-from . import nostr_ext
from lnbits.settings import LNBITS_ADMIN_USERS
+from . import nostradmin_ext
from .crud import (
create_nostrkeys,
get_nostrkeys,
@@ -20,13 +19,11 @@ from .crud import (
create_nostrrelays,
get_nostrrelays,
get_nostrrelaylist,
- update_nostrrelayallowlist,
- update_nostrrelaydenylist,
+ update_nostrrelaysetlist,
create_nostrconnections,
get_nostrconnections,
)
-from .models import nostrKeys, nostrCreateRelays, nostrRelayAllowList, nostrRelayDenyList
-
+from .models import nostrKeys, nostrCreateRelays, nostrRelaySetList
# while True:
async def nostr_subscribe():
@@ -41,13 +38,7 @@ async def nostr_subscribe():
websocket_queue = asyncio.Queue(1000)
-async def internal_invoice_listener():
- while True:
- checking_id = await internal_invoice_queue.get()
- asyncio.create_task(invoice_callback_dispatcher(checking_id))
-
-
-@nostr_ext.get("/api/v1/relays")
+@nostradmin_ext.get("/api/v1/relays")
async def api_relays_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)):
relays = await get_nostrrelays()
@@ -62,7 +53,7 @@ async def api_relays_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)):
except:
None
-@nostr_ext.get("/api/v1/relaylist")
+@nostradmin_ext.get("/api/v1/relaylist")
async def api_relaylist(wallet: WalletTypeInfo = Depends(get_key_type)):
if wallet.wallet.user not in LNBITS_ADMIN_USERS:
raise HTTPException(
@@ -70,18 +61,10 @@ async def api_relaylist(wallet: WalletTypeInfo = Depends(get_key_type)):
)
return await get_nostrrelaylist()
-@nostr_ext.post("/api/v1/allowlist")
-async def api_relaysallowed(data: nostrRelayAllowList, wallet: WalletTypeInfo = Depends(get_key_type)):
+@nostradmin_ext.post("/api/v1/setlist")
+async def api_relayssetlist(data: nostrRelaySetList, wallet: WalletTypeInfo = Depends(get_key_type)):
if wallet.wallet.user not in LNBITS_ADMIN_USERS:
raise HTTPException(
status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
)
- return await update_nostrrelayallowlist(data)
-
-@nostr_ext.post("/api/v1/denylist")
-async def api_relaysdenyed(data: nostrRelayDenyList, wallet: WalletTypeInfo = Depends(get_key_type)):
- if wallet.wallet.user not in LNBITS_ADMIN_USERS:
- raise HTTPException(
- status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
- )
- return await update_nostrrelaydenylist(data)
\ No newline at end of file
+ return await update_nostrrelaysetlist(data)
\ No newline at end of file
From 21c316293b4c8986854ade1421e4119fc6e4e5bb Mon Sep 17 00:00:00 2001
From: benarc
Date: Tue, 8 Feb 2022 13:52:19 +0000
Subject: [PATCH 015/226] Started planning websocket daemon
---
lnbits/extensions/nostradmin/views.py | 70 ++++++++++++++++++++++-
lnbits/extensions/nostradmin/views_api.py | 12 ----
2 files changed, 68 insertions(+), 14 deletions(-)
diff --git a/lnbits/extensions/nostradmin/views.py b/lnbits/extensions/nostradmin/views.py
index 51297320..235ec7e9 100644
--- a/lnbits/extensions/nostradmin/views.py
+++ b/lnbits/extensions/nostradmin/views.py
@@ -1,5 +1,6 @@
from http import HTTPStatus
-
+import asyncio
+import asyncio
from fastapi import Request
from fastapi.param_functions import Query
from fastapi.params import Depends
@@ -7,13 +8,14 @@ from fastapi.templating import Jinja2Templates
from starlette.exceptions import HTTPException
from starlette.responses import HTMLResponse
from . import nostradmin_ext, nostr_renderer
+from fastapi import Request, WebSocket, WebSocketDisconnect
from lnbits.core.crud import update_payment_status
from lnbits.core.models import User
from lnbits.core.views.api import api_payment
from lnbits.decorators import check_user_exists
-
+from .crud import get_nostrkeys
templates = Jinja2Templates(directory="templates")
@@ -23,3 +25,67 @@ async def index(request: Request, user: User = Depends(check_user_exists)):
return nostr_renderer().TemplateResponse(
"nostradmin/index.html", {"request": request, "user": user.dict()}
)
+
+#####################################################################
+#################### NOSTR WEBSOCKET THREAD #########################
+##### THE QUEUE LOOP THREAD THING THAT LISTENS TO BUNCH OF ##########
+### WEBSOCKET CONNECTIONS, STORING DATA IN DB/PUSHING TO FRONTEND ###
+################### VIA updater() FUNCTION ##########################
+#####################################################################
+
+websocket_queue = asyncio.Queue(1000)
+
+# while True:
+async def nostr_subscribe():
+ return
+ # for the relays:
+ # async with websockets.connect("ws://localhost:8765") as websocket:
+ # for the public keys:
+ # await websocket.send("subscribe to events")
+ # await websocket.recv()
+
+#####################################################################
+################### LNBITS WEBSOCKET ROUTES #########################
+#### HERE IS WHERE LNBITS FRONTEND CAN RECEIVE AND SEND MESSAGES ####
+#####################################################################
+
+class ConnectionManager:
+ def __init__(self):
+ self.active_connections: List[WebSocket] = []
+
+ async def connect(self, websocket: WebSocket, nostr_id: str):
+ await websocket.accept()
+ websocket.id = nostr_id
+ self.active_connections.append(websocket)
+
+ def disconnect(self, websocket: WebSocket):
+ self.active_connections.remove(websocket)
+
+ async def send_personal_message(self, message: str, nostr_id: str):
+ for connection in self.active_connections:
+ if connection.id == nostr_id:
+ await connection.send_text(message)
+
+ async def broadcast(self, message: str):
+ for connection in self.active_connections:
+ await connection.send_text(message)
+
+
+manager = ConnectionManager()
+
+
+@nostradmin_ext.websocket("/nostradmin/ws/{nostr_id}", name="copilot.websocket_by_id")
+async def websocket_endpoint(websocket: WebSocket, copilot_id: str):
+ await manager.connect(websocket, nostr_id)
+ try:
+ while True:
+ data = await websocket.receive_text()
+ except WebSocketDisconnect:
+ manager.disconnect(websocket)
+
+
+async def updater(nostr_id, message):
+ copilot = await get_copilot(nostr_id)
+ if not copilot:
+ return
+ await manager.send_personal_message(f"{message}", nostr_id)
\ No newline at end of file
diff --git a/lnbits/extensions/nostradmin/views_api.py b/lnbits/extensions/nostradmin/views_api.py
index d79315ac..ad8bcf17 100644
--- a/lnbits/extensions/nostradmin/views_api.py
+++ b/lnbits/extensions/nostradmin/views_api.py
@@ -25,18 +25,6 @@ from .crud import (
)
from .models import nostrKeys, nostrCreateRelays, nostrRelaySetList
-# while True:
-async def nostr_subscribe():
- return
- # for the relays:
- # async with websockets.connect("ws://localhost:8765") as websocket:
- # for the public keys:
- # await websocket.send("subscribe to events")
- # await websocket.recv()
-
-
-websocket_queue = asyncio.Queue(1000)
-
@nostradmin_ext.get("/api/v1/relays")
async def api_relays_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)):
From 0a5495f185a87a2aee8a2efd78b7dff0ad5d2384 Mon Sep 17 00:00:00 2001
From: benarc
Date: Tue, 8 Feb 2022 14:02:50 +0000
Subject: [PATCH 016/226] typo
---
lnbits/extensions/nostradmin/views.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/nostradmin/views.py b/lnbits/extensions/nostradmin/views.py
index 235ec7e9..f00c43a3 100644
--- a/lnbits/extensions/nostradmin/views.py
+++ b/lnbits/extensions/nostradmin/views.py
@@ -74,7 +74,7 @@ class ConnectionManager:
manager = ConnectionManager()
-@nostradmin_ext.websocket("/nostradmin/ws/{nostr_id}", name="copilot.websocket_by_id")
+@nostradmin_ext.websocket("/nostradmin/ws/{nostr_id}", name="nostr_id.websocket_by_id")
async def websocket_endpoint(websocket: WebSocket, copilot_id: str):
await manager.connect(websocket, nostr_id)
try:
From 6917813e2af357138bebb2df31d00be6148b7553 Mon Sep 17 00:00:00 2001
From: benarc
Date: Thu, 10 Feb 2022 09:58:50 +0000
Subject: [PATCH 017/226] Working, looking good
---
lnbits/extensions/nostradmin/models.py | 1 +
.../templates/nostradmin/index.html | 39 ++++++++++++-------
lnbits/extensions/nostradmin/views.py | 21 +++++++---
lnbits/extensions/nostradmin/views_api.py | 17 +++++---
4 files changed, 53 insertions(+), 25 deletions(-)
diff --git a/lnbits/extensions/nostradmin/models.py b/lnbits/extensions/nostradmin/models.py
index 1968567f..dc99b083 100644
--- a/lnbits/extensions/nostradmin/models.py
+++ b/lnbits/extensions/nostradmin/models.py
@@ -30,6 +30,7 @@ class nostrCreateConnections(BaseModel):
class nostrRelays(BaseModel):
id: Optional[str]
relay: Optional[str]
+ status: Optional[bool] = False
class nostrRelayList(BaseModel):
id: str
diff --git a/lnbits/extensions/nostradmin/templates/nostradmin/index.html b/lnbits/extensions/nostradmin/templates/nostradmin/index.html
index 27decdc8..57552bf2 100644
--- a/lnbits/extensions/nostradmin/templates/nostradmin/index.html
+++ b/lnbits/extensions/nostradmin/templates/nostradmin/index.html
@@ -29,7 +29,7 @@
-
-
-
- {{ col.value }}
+
+
+ {{ col.value }}
+
+
{{ col.value }}
+
@@ -143,7 +145,7 @@
{{SITE_TITLE}} Nostr Extension
Only Admin users can manage this extension
- Okay
+
@@ -153,7 +155,7 @@
+
+{% 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 5cc39930e5c0e0957596362dbbe95f16f475d3b4 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Thu, 27 Jan 2022 14:58:58 +0000
Subject: [PATCH 032/226] 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 ebaf0c990f32cd545c40e1ae1c645b53a9cdd0bc Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Thu, 27 Jan 2022 15:26:55 +0000
Subject: [PATCH 033/226] 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 8b373021641674d7875afb6b002e7b26c7afae55 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Thu, 27 Jan 2022 16:18:12 +0000
Subject: [PATCH 034/226] 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 c41f6033be1066bae9f13f66d414a6bc07c3734b Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Fri, 28 Jan 2022 15:11:31 +0000
Subject: [PATCH 035/226] 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 a55dd18528ef5a91782352972d6c59d02d5c46c9 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Fri, 28 Jan 2022 16:22:54 +0000
Subject: [PATCH 036/226] 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 80a5d26dce583e1b10d104dde9554576dadb85a5 Mon Sep 17 00:00:00 2001
From: benarc
Date: Thu, 3 Feb 2022 21:19:24 +0000
Subject: [PATCH 037/226] 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 1988a9d5d769ed1d2fc72471cb790ec0b4f2fefd Mon Sep 17 00:00:00 2001
From: benarc
Date: Thu, 3 Feb 2022 22:30:53 +0000
Subject: [PATCH 038/226] 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 @@
>
-
+
From 40fd894cced11135332b187939a52f787bcd1fab Mon Sep 17 00:00:00 2001
From: benarc
Date: Fri, 4 Feb 2022 13:05:48 +0000
Subject: [PATCH 039/226] Zones are saving/updating
---
lnbits/extensions/diagonalley/__init__.py | 11 +-
lnbits/extensions/diagonalley/crud.py | 116 +--
lnbits/extensions/diagonalley/migrations.py | 8 +-
lnbits/extensions/diagonalley/models.py | 7 +-
.../extensions/diagonalley/static/js/index.js | 853 -----------------
.../templates/diagonalley/index.html | 895 +++++++++++++++++-
lnbits/extensions/diagonalley/views_api.py | 40 +-
7 files changed, 924 insertions(+), 1006 deletions(-)
delete mode 100644 lnbits/extensions/diagonalley/static/js/index.js
diff --git a/lnbits/extensions/diagonalley/__init__.py b/lnbits/extensions/diagonalley/__init__.py
index cab65685..388c08db 100644
--- a/lnbits/extensions/diagonalley/__init__.py
+++ b/lnbits/extensions/diagonalley/__init__.py
@@ -9,17 +9,9 @@ from lnbits.tasks import catch_everything_and_restart
db = Database("ext_diagonalley")
-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"
)
def diagonalley_renderer():
@@ -33,5 +25,4 @@ from .views_api import * # noqa
def diagonalley_start():
loop = asyncio.get_event_loop()
- loop.create_task(catch_everything_and_restart(wait_for_paid_invoices))
-
+ loop.create_task(catch_everything_and_restart(wait_for_paid_invoices))
\ No newline at end of file
diff --git a/lnbits/extensions/diagonalley/crud.py b/lnbits/extensions/diagonalley/crud.py
index 4cf14014..d2df427c 100644
--- a/lnbits/extensions/diagonalley/crud.py
+++ b/lnbits/extensions/diagonalley/crud.py
@@ -22,28 +22,12 @@ from .models import (
createZones,
)
-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(
data: createProduct
) -> 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)(
await db.execute(
f"""
INSERT INTO diagonalley.products (id, stall, product, categories, description, image, price, quantity)
@@ -110,7 +94,7 @@ async def delete_diagonalley_product(product_id: str) -> None:
async def create_diagonalley_zone(
- wallet,
+ user,
data: createZones
) -> Zones:
zone_id = urlsafe_short_hash()
@@ -118,14 +102,14 @@ async def create_diagonalley_zone(
f"""
INSERT INTO diagonalley.zones (
id,
- wallet,
+ user,
cost,
countries
)
VALUES (?, ?, ?, ?)
""",
- (zone_id, wallet, data.cost, data.countries),
+ (zone_id, user, data.cost, data.countries.lower()),
)
zone = await get_diagonalley_zone(zone_id)
@@ -148,41 +132,8 @@ async def get_diagonalley_zone(zone_id: str) -> Optional[Zones]:
return Zones(**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,)
- )
+async def get_diagonalley_zones(user: str) -> List[Zones]:
+ rows = await db.fetchall("SELECT * FROM diagonalley.zones WHERE user = ?", (user,))
return [Zones(**row) for row in rows]
@@ -217,7 +168,7 @@ async def create_diagonalley_stall(
data.publickey,
data.privatekey,
data.relays,
- data.shippingzones),
+ repr(data.shippingzones)),
)
stall = await get_diagonalley_stall(stall_id)
@@ -238,32 +189,6 @@ async def update_diagonalley_stall(stall_id: str, **kwargs) -> Optional[Stalls]:
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,)
)
@@ -271,35 +196,6 @@ async def get_diagonalley_stall(stall_id: str) -> Optional[Stalls]:
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,)
diff --git a/lnbits/extensions/diagonalley/migrations.py b/lnbits/extensions/diagonalley/migrations.py
index 1523f398..29a84419 100644
--- a/lnbits/extensions/diagonalley/migrations.py
+++ b/lnbits/extensions/diagonalley/migrations.py
@@ -28,7 +28,8 @@ async def m001_initial(db):
name TEXT NOT NULL,
publickey TEXT NOT NULL,
privatekey TEXT NOT NULL,
- relays TEXT NOT NULL
+ relays TEXT NOT NULL,
+ shippingzones TEXT NOT NULL
);
"""
)
@@ -40,7 +41,7 @@ async def m001_initial(db):
"""
CREATE TABLE diagonalley.zones (
id TEXT PRIMARY KEY,
- wallet TEXT NOT NULL,
+ user TEXT NOT NULL,
cost TEXT NOT NULL,
countries TEXT NOT NULL
);
@@ -55,7 +56,8 @@ async def m001_initial(db):
CREATE TABLE diagonalley.orders (
id TEXT PRIMARY KEY,
productid TEXT NOT NULL,
- wallet TEXT NOT NULL,
+ usr TEXT NOT NULL,
+ pubkey TEXT NOT NULL,
product TEXT NOT NULL,
quantity INTEGER NOT NULL,
shippingzone INTEGER NOT NULL,
diff --git a/lnbits/extensions/diagonalley/models.py b/lnbits/extensions/diagonalley/models.py
index 1a975e10..743c2068 100644
--- a/lnbits/extensions/diagonalley/models.py
+++ b/lnbits/extensions/diagonalley/models.py
@@ -49,13 +49,13 @@ class Products(BaseModel):
quantity: int
class createZones(BaseModel):
- cost: str = Query(None)
+ cost: int = Query(0, ge=0)
countries: str = Query(None)
class Zones(BaseModel):
id: str
- wallet: str
- cost: str
+ user: str
+ cost: int
countries: str
@@ -73,6 +73,7 @@ class Orders(BaseModel):
id: str
productid: str
stall: str
+ pubkey: str
product: str
quantity: int
shippingzone: int
diff --git a/lnbits/extensions/diagonalley/static/js/index.js b/lnbits/extensions/diagonalley/static/js/index.js
deleted file mode 100644
index d101bfcf..00000000
--- a/lnbits/extensions/diagonalley/static/js/index.js
+++ /dev/null
@@ -1,853 +0,0 @@
-/* globals Quasar, Vue, _, VueQrcode, windowMixin, LNbits, LOCALE */
-
-Vue.component(VueQrcode.name, VueQrcode)
-
-//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',
- mixins: [windowMixin],
- data: function () {
- return {
- products: [],
- orders: [],
- stalls: [],
- zones: [],
- customerKeys: [],
- customerKey: '',
- customerMessages: {},
- 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)',
- 'Adult'
- ],
- 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: {
- errorMessage: function (error) {
- this.$q.notify({
- color: 'primary',
- message: error
- })
- },
- ////////////////////////////////////////
- ///////////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//////////////////
- ////////////////////////////////////////
- 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.push(mapStalls(response.data))
- })
- },
- 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(mapStalls(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(mapStalls(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.push(mapProducts(response.data))
- })
- },
- 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(mapProducts(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(mapProducts(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.push(mapZone(response.data))
- })
- },
- 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(mapZone(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(mapZone(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.push(mapShops(response.data))
- })
- },
- 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(mapShops(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(mapShops(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.push(mapOrders(response.data))
- })
- },
- 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(mapOrders(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.push(mapOrders(response.data))
- })
- },
- 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()
- this.customerKeys = [
- 'cb4c0164fe03fcdadcbfb4f76611c71620790944c24f21a1cd119395cdedfe1b',
- 'a9c17358a6dc4ceb3bb4d883eb87967a66b3453a0f3199f0b1c8eef8070c6a07'
- ]
- }
- }
-})
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/index.html b/lnbits/extensions/diagonalley/templates/diagonalley/index.html
index d14d7cee..7b8f8e6a 100644
--- a/lnbits/extensions/diagonalley/templates/diagonalley/index.html
+++ b/lnbits/extensions/diagonalley/templates/diagonalley/index.html
@@ -300,8 +300,9 @@
- + Product List a product + Product List a produc
+ Product List a product + Shipping Zone Create a shipping zone
- + Stall
Create a stall to list products on
@@ -321,6 +322,7 @@
>Launch frontend shop (not Nostr)
Makes a simple frontend shop for your stalls
+
@@ -638,7 +640,890 @@
+
{% endblock %} {% block scripts %} {{ window_vars(user) }}
-
-
+
+
{% endblock %}
+
diff --git a/lnbits/extensions/diagonalley/views_api.py b/lnbits/extensions/diagonalley/views_api.py
index 43232841..6363dc97 100644
--- a/lnbits/extensions/diagonalley/views_api.py
+++ b/lnbits/extensions/diagonalley/views_api.py
@@ -121,36 +121,32 @@ 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), all_wallets: bool = Query(False)):
- wallet_ids = [wallet.wallet.id]
+async def api_diagonalley_zones(wallet: WalletTypeInfo = Depends(get_key_type)):
- if all_wallets:
- wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
-
- return ([zone.dict() for zone in await get_diagonalley_zones(wallet_ids)])
+ return await get_diagonalley_zones(wallet.wallet.user)
@diagonalley_ext.post("/api/v1/zones")
-@diagonalley_ext.put("/api/v1/zones/{zone_id}")
async def api_diagonalley_zone_create(
data: createZones,
- 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 != wallet.wallet.id:
- return ({"message": "Not your record."})
-
- zone = await update_diagonalley_zone(zone_id, **data.dict())
- else:
- zone = await create_diagonalley_zone(wallet=wallet.wallet.id, data=data)
-
+ zone = await create_diagonalley_zone(user=wallet.wallet.user, data=data)
return zone.dict()
+@diagonalley_ext.post("/api/v1/zones/{zone_id}")
+async def api_diagonalley_zone_update(
+ data: createZones,
+ 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.user != wallet.wallet.user:
+ return ({"message": "Not your record."})
+ zone = await update_diagonalley_zone(zone_id, **data.dict())
+ return zone
+
@diagonalley_ext.delete("/api/v1/zones/{zone_id}")
async def api_diagonalley_zone_delete(zone_id, wallet: WalletTypeInfo = Depends(require_admin_key)):
@@ -159,7 +155,7 @@ async def api_diagonalley_zone_delete(zone_id, wallet: WalletTypeInfo = Depends(
if not zone:
return ({"message": "zone does not exist."})
- if zone.wallet != wallet.wallet.id:
+ if zone.user != wallet.wallet.user:
return ({"message": "Not your zone."})
await delete_diagonalley_zone(zone_id)
From 671ac08a046052615fa0366578d9e3365f9de5f1 Mon Sep 17 00:00:00 2001
From: benarc
Date: Sun, 6 Feb 2022 22:40:51 +0000
Subject: [PATCH 040/226] small fix
---
.../templates/diagonalley/index.html | 25 +++++++++++++------
1 file changed, 18 insertions(+), 7 deletions(-)
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/index.html b/lnbits/extensions/diagonalley/templates/diagonalley/index.html
index 7b8f8e6a..6d6ea872 100644
--- a/lnbits/extensions/diagonalley/templates/diagonalley/index.html
+++ b/lnbits/extensions/diagonalley/templates/diagonalley/index.html
@@ -244,7 +244,7 @@
label="Private Key"
>
@@ -270,7 +270,7 @@
filled
dense
v-model.trim="stallDialog.data.nostrShops"
- label="Stall public keys (seperate by comma)"
+ label="Nostr shop public keys (seperate by comma)"
>
- + Product List a produc 1" color="primary" @click="productDialog.show = true"
+ >+ Product List a product
+ Product List a product + Shipping Zone
Create a shipping zone
-
+ Stall
Create a stall to list products on
@@ -675,6 +675,7 @@ new Vue({
orders: [],
stalls: [],
zones: [],
+ zoneOptions: [],
customerKeys: [],
customerKey: '',
customerMessages: {},
@@ -949,10 +950,19 @@ new Vue({
LNbits.utils.notifyApiError(error)
})
},
+ openStallDialog: function () {
+ console.log(this.zones[0]['id'])
+ for(let i = 0; i < this.zones.length; i ++){
+ this.zoneOptions.push(this.zones[i]['id'])
+ }
+ this.stallDialog.show = true
+ },
openStallUpdateDialog: function (linkId) {
var self = this
var link = _.findWhere(self.stalls, {id: linkId})
-
+ for(let i = 0; i < this.zones.length; i ++){
+ this.zoneOptions.push(this.stalls[i][0])
+ }
this.stallDialog.data = _.clone(link._data)
this.stallDialog.show = true
},
@@ -1210,6 +1220,7 @@ new Vue({
if (response.data) {
console.log(response)
self.zones = response.data.map(mapZone)
+
}
})
.catch(function (error) {
From 61cf79de691221ed631c58f77c25c76f522e9c85 Mon Sep 17 00:00:00 2001
From: benarc
Date: Mon, 7 Feb 2022 13:22:43 +0000
Subject: [PATCH 041/226] Added nostr ext
---
lnbits/extensions/nostr/README.md | 3 +
lnbits/extensions/nostr/__init__.py | 17 +
lnbits/extensions/nostr/config.json | 6 +
lnbits/extensions/nostr/crud.py | 134 +++++
lnbits/extensions/nostr/migrations.py | 47 ++
lnbits/extensions/nostr/models.py | 34 ++
.../templates/lnurldevice/_api_docs.html | 169 ++++++
.../nostr/templates/lnurldevice/error.html | 34 ++
.../nostr/templates/lnurldevice/index.html | 534 ++++++++++++++++++
.../nostr/templates/lnurldevice/paid.html | 27 +
lnbits/extensions/nostr/views.py | 24 +
lnbits/extensions/nostr/views_api.py | 48 ++
12 files changed, 1077 insertions(+)
create mode 100644 lnbits/extensions/nostr/README.md
create mode 100644 lnbits/extensions/nostr/__init__.py
create mode 100644 lnbits/extensions/nostr/config.json
create mode 100644 lnbits/extensions/nostr/crud.py
create mode 100644 lnbits/extensions/nostr/migrations.py
create mode 100644 lnbits/extensions/nostr/models.py
create mode 100644 lnbits/extensions/nostr/templates/lnurldevice/_api_docs.html
create mode 100644 lnbits/extensions/nostr/templates/lnurldevice/error.html
create mode 100644 lnbits/extensions/nostr/templates/lnurldevice/index.html
create mode 100644 lnbits/extensions/nostr/templates/lnurldevice/paid.html
create mode 100644 lnbits/extensions/nostr/views.py
create mode 100644 lnbits/extensions/nostr/views_api.py
diff --git a/lnbits/extensions/nostr/README.md b/lnbits/extensions/nostr/README.md
new file mode 100644
index 00000000..596cce9d
--- /dev/null
+++ b/lnbits/extensions/nostr/README.md
@@ -0,0 +1,3 @@
+# Nostr
+
+Opens a Nostr daemon
diff --git a/lnbits/extensions/nostr/__init__.py b/lnbits/extensions/nostr/__init__.py
new file mode 100644
index 00000000..775960e3
--- /dev/null
+++ b/lnbits/extensions/nostr/__init__.py
@@ -0,0 +1,17 @@
+from fastapi import APIRouter
+
+from lnbits.db import Database
+from lnbits.helpers import template_renderer
+
+db = Database("ext_nostr")
+
+nostr_ext: APIRouter = APIRouter(prefix="/nostr", tags=["nostr"])
+
+
+def nostr_renderer():
+ return template_renderer(["lnbits/extensions/nostr/templates"])
+
+
+from .lnurl import * # noqa
+from .views import * # noqa
+from .views_api import * # noqa
diff --git a/lnbits/extensions/nostr/config.json b/lnbits/extensions/nostr/config.json
new file mode 100644
index 00000000..a32e39a1
--- /dev/null
+++ b/lnbits/extensions/nostr/config.json
@@ -0,0 +1,6 @@
+{
+ "name": "Nostr",
+ "short_description": "Daemon for Nostr",
+ "icon": "swap_horizontal_circle",
+ "contributors": ["arcbtc"]
+}
diff --git a/lnbits/extensions/nostr/crud.py b/lnbits/extensions/nostr/crud.py
new file mode 100644
index 00000000..55e99ec6
--- /dev/null
+++ b/lnbits/extensions/nostr/crud.py
@@ -0,0 +1,134 @@
+from typing import List, Optional, Union
+
+from lnbits.helpers import urlsafe_short_hash
+
+from . import db
+from .models import nostrKeys, nostrNotes, nostrRelays, nostrConnections
+
+###############KEYS##################
+
+async def create_nostrkeys(
+ data: nostrKeys
+) -> nostrKeys:
+ nostrkey_id = urlsafe_short_hash()
+ await db.execute(
+ """
+ INSERT INTO nostr.keys (
+ id,
+ pubkey,
+ privkey
+ )
+ VALUES (?, ?, ?)
+ """,
+ (nostrkey_id, data.pubkey, data.privkey),
+ )
+ return await get_nostrkeys(nostrkey_id)
+
+async def get_nostrkeys(nostrkey_id: str) -> nostrKeys:
+ row = await db.fetchone(
+ "SELECT * FROM nostr.keys WHERE id = ?",
+ (lnurldevicepayment_id,),
+ )
+ return nostrKeys(**row) if row else None
+
+
+###############NOTES##################
+
+async def create_nostrnotes(
+ data: nostrNotes
+) -> nostrNotes:
+ await db.execute(
+ """
+ INSERT INTO nostr.notes (
+ id,
+ pubkey,
+ created_at,
+ kind,
+ tags,
+ content,
+ sig
+ )
+ VALUES (?, ?, ?, ?, ?, ?, ?)
+ """,
+ (data.id, data.pubkey, data.created_at, data.kind, data.tags, data.content, data.sig),
+ )
+ return await get_nostrnotes(data.id)
+
+async def get_nostrnotes(nostrnote_id: str) -> nostrNotes:
+ row = await db.fetchone(
+ "SELECT * FROM nostr.notes WHERE id = ?",
+ (nostrnote_id,),
+ )
+ return nostrNotes(**row) if row else None
+
+###############RELAYS##################
+
+async def create_nostrrelays(
+ relay: str
+) -> nostrRelays:
+ nostrrelay_id = urlsafe_short_hash()
+ await db.execute(
+ """
+ INSERT INTO nostr.relays (
+ id,
+ relay
+ )
+ VALUES (?, ?)
+ """,
+ (nostrrelay_id, relay),
+ )
+ return await get_nostrnotes(nostrrelay_id)
+
+async def get_nostrrelays(nostrrelay_id: str) -> nostrRelays:
+ row = await db.fetchone(
+ "SELECT * FROM nostr.relays WHERE id = ?",
+ (nostrnote_id,),
+ )
+ return nostrRelays(**row) if row else None
+
+
+###############CONNECTIONS##################
+
+async def create_nostrconnections(
+ data: nostrNotes
+) -> nostrNotes:
+ nostrkey_id = urlsafe_short_hash()
+ await db.execute(
+ """
+ INSERT INTO nostr.notes (
+ id,
+ pubkey,
+ created_at,
+ kind,
+ tags,
+ content,
+ sig
+ )
+ VALUES (?, ?, ?, ?, ?, ?, ?)
+ """,
+ (data.id, data.pubkey, data.created_at, data.kind, data.tags, data.content, data.sig),
+ )
+ return await get_nostrnotes(data.id)
+
+async def get_nostrnotes(nostrnote_id: str) -> nostrNotes:
+ row = await db.fetchone(
+ "SELECT * FROM nostr.notes WHERE id = ?",
+ (nostrnote_id,),
+ )
+ return nostrNotes(**row) if row else None
+
+
+
+async def update_lnurldevicepayment(
+ lnurldevicepayment_id: str, **kwargs
+) -> Optional[lnurldevicepayment]:
+ q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
+ await db.execute(
+ f"UPDATE lnurldevice.lnurldevicepayment SET {q} WHERE id = ?",
+ (*kwargs.values(), lnurldevicepayment_id),
+ )
+ row = await db.fetchone(
+ "SELECT * FROM lnurldevice.lnurldevicepayment WHERE id = ?",
+ (lnurldevicepayment_id,),
+ )
+ return lnurldevicepayment(**row) if row else None
\ No newline at end of file
diff --git a/lnbits/extensions/nostr/migrations.py b/lnbits/extensions/nostr/migrations.py
new file mode 100644
index 00000000..de32a578
--- /dev/null
+++ b/lnbits/extensions/nostr/migrations.py
@@ -0,0 +1,47 @@
+from lnbits.db import Database
+
+db2 = Database("ext_nostr")
+
+
+async def m001_initial(db):
+ """
+ Initial nostr table.
+ """
+ await db.execute(
+ f"""
+ CREATE TABLE nostr.keys (
+ pubkey TEXT NOT NULL PRIMARY KEY,
+ privkey TEXT NOT NULL
+ );
+ """
+ )
+ await db.execute(
+ f"""
+ CREATE TABLE nostr.notes (
+ id TEXT NOT NULL PRIMARY KEY,
+ pubkey TEXT NOT NULL,
+ created_at TEXT NOT NULL,
+ kind INT NOT NULL,
+ tags TEXT NOT NULL,
+ content TEXT NOT NULL,
+ sig TEXT NOT NULL,
+ );
+ """
+ )
+ await db.execute(
+ f"""
+ CREATE TABLE nostr.relays (
+ id TEXT NOT NULL PRIMARY KEY,
+ relay TEXT NOT NULL
+ );
+ """
+ )
+ await db.execute(
+ f"""
+ CREATE TABLE nostr.connections (
+ id TEXT NOT NULL PRIMARY KEY,
+ publickey TEXT NOT NULL,
+ relayid TEXT NOT NULL
+ );
+ """
+ )
\ No newline at end of file
diff --git a/lnbits/extensions/nostr/models.py b/lnbits/extensions/nostr/models.py
new file mode 100644
index 00000000..ba0c87a5
--- /dev/null
+++ b/lnbits/extensions/nostr/models.py
@@ -0,0 +1,34 @@
+import json
+from sqlite3 import Row
+from typing import Optional
+
+from fastapi import Request
+from lnurl import Lnurl
+from lnurl import encode as lnurl_encode # type: ignore
+from lnurl.models import LnurlPaySuccessAction, UrlAction # type: ignore
+from lnurl.types import LnurlPayMetadata # type: ignore
+from pydantic import BaseModel
+from pydantic.main import BaseModel
+
+class nostrKeys(BaseModel):
+ id: str
+ pubkey: str
+ privkey: str
+
+class nostrNotes(BaseModel):
+ id: str
+ pubkey: str
+ created_at: str
+ kind: int
+ tags: str
+ content: str
+ sig: str
+
+class nostrRelays(BaseModel):
+ id: str
+ relay: str
+
+class nostrConnections(BaseModel):
+ id: str
+ pubkey: str
+ relayid: str
\ No newline at end of file
diff --git a/lnbits/extensions/nostr/templates/lnurldevice/_api_docs.html b/lnbits/extensions/nostr/templates/lnurldevice/_api_docs.html
new file mode 100644
index 00000000..af69b76e
--- /dev/null
+++ b/lnbits/extensions/nostr/templates/lnurldevice/_api_docs.html
@@ -0,0 +1,169 @@
+
+
+
+ Register LNURLDevice devices to receive payments in your LNbits wallet.
+ Build your own here
+ https://github.com/arcbtc/bitcoinpos
+
+ Created by, Ben Arc
+
+
+
+
+
+
+ /lnurldevice/api/v1/lnurlpos
+ Headers
+ {"X-Api-Key": <admin_key>}
+
+ Body (application/json)
+
+
+ Returns 200 OK (application/json)
+
+ [<lnurldevice_object>, ...]
+ Curl example
+ curl -X POST {{ request.base_url }}api/v1/lnurldevice -d '{"title":
+ <string>, "message":<string>, "currency":
+ <integer>}' -H "Content-type: application/json" -H "X-Api-Key:
+ {{user.wallets[0].adminkey }}"
+
+
+
+
+
+
+
+ PUT
+ /lnurldevice/api/v1/lnurlpos/<lnurldevice_id>
+ Headers
+ {"X-Api-Key": <admin_key>}
+
+ Body (application/json)
+
+
+ Returns 200 OK (application/json)
+
+ [<lnurldevice_object>, ...]
+ Curl example
+ curl -X POST {{ request.base_url
+ }}api/v1/lnurlpos/<lnurldevice_id> -d ''{"title":
+ <string>, "message":<string>, "currency":
+ <integer>} -H "Content-type: application/json" -H "X-Api-Key:
+ {{user.wallets[0].adminkey }}"
+
+
+
+
+
+
+
+
+ GET
+ /lnurldevice/api/v1/lnurlpos/<lnurldevice_id>
+ Headers
+ {"X-Api-Key": <invoice_key>}
+
+ Body (application/json)
+
+
+ Returns 200 OK (application/json)
+
+ [<lnurldevice_object>, ...]
+ Curl example
+ curl -X GET {{ request.base_url
+ }}api/v1/lnurlpos/<lnurldevice_id> -H "X-Api-Key: {{
+ user.wallets[0].inkey }}"
+
+
+
+
+
+
+
+ GET
+ /lnurldevice/api/v1/lnurlposs
+ Headers
+ {"X-Api-Key": <invoice_key>}
+
+ Body (application/json)
+
+
+ Returns 200 OK (application/json)
+
+ [<lnurldevice_object>, ...]
+ Curl example
+ curl -X GET {{ request.base_url }}api/v1/lnurldevices -H
+ "X-Api-Key: {{ user.wallets[0].inkey }}"
+
+
+
+
+
+
+
+ DELETE
+ /lnurldevice/api/v1/lnurlpos/<lnurldevice_id>
+ Headers
+ {"X-Api-Key": <admin_key>}
+ Returns 204 NO CONTENT
+
+ Curl example
+ curl -X DELETE {{ request.base_url
+ }}api/v1/lnurlpos/<lnurldevice_id> -H "X-Api-Key: {{
+ user.wallets[0].adminkey }}"
+
+
+
+
+
+
diff --git a/lnbits/extensions/nostr/templates/lnurldevice/error.html b/lnbits/extensions/nostr/templates/lnurldevice/error.html
new file mode 100644
index 00000000..d8e41832
--- /dev/null
+++ b/lnbits/extensions/nostr/templates/lnurldevice/error.html
@@ -0,0 +1,34 @@
+{% extends "public.html" %} {% block page %}
+
+
+
+
+
+ LNURL-pay not paid
+
+
+
+
+
+
+
+
+
+ {% endblock %} {% block scripts %}
+
+
+
+ {% endblock %}
+
diff --git a/lnbits/extensions/nostr/templates/lnurldevice/index.html b/lnbits/extensions/nostr/templates/lnurldevice/index.html
new file mode 100644
index 00000000..b51e2556
--- /dev/null
+++ b/lnbits/extensions/nostr/templates/lnurldevice/index.html
@@ -0,0 +1,534 @@
+{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
+%} {% block page %}
+
+
+
+
+ {% raw %}
+ New LNURLDevice instance
+
+
+
+
+
+
+
+
+
lNURLdevice
+
+
+
+
+
+
+
+
+ Export to CSV
+
+
+
+
+
+
+
+
+
+
+ {{ col.label }}
+
+
+
+
+
+
+
+
+
+ Delete LNURLDevice
+
+
+
+
+ LNURLDevice Settings
+
+
+
+
+ {{ col.value }}
+
+
+
+ {% endraw %}
+
+
+
+
+
+
+
+
+
+ {{SITE_TITLE}} LNURLDevice Extension
+
+
+
+
+ {% include "lnurldevice/_api_docs.html" %}
+
+
+
+
+
+
+ LNURLDevice device string
+ {% raw
+ %}{{location}}/lnurldevice/api/v1/lnurl/{{settingsDialog.data.id}},
+ {{settingsDialog.data.key}}, {{settingsDialog.data.currency}}{% endraw
+ %} Click to copy URL
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Update lnurldevice
+ Create lnurldevice
+ Cancel
+
+
+
+
+
+{% endblock %} {% block scripts %} {{ window_vars(user) }}
+
+
+{% endblock %}
diff --git a/lnbits/extensions/nostr/templates/lnurldevice/paid.html b/lnbits/extensions/nostr/templates/lnurldevice/paid.html
new file mode 100644
index 00000000..c185ecce
--- /dev/null
+++ b/lnbits/extensions/nostr/templates/lnurldevice/paid.html
@@ -0,0 +1,27 @@
+{% extends "public.html" %} {% block page %}
+
+
+
+
+
+ {{ pin }}
+
+
+
+
+
+
+ {% endblock %} {% block scripts %}
+
+
+
+ {% endblock %}
+
diff --git a/lnbits/extensions/nostr/views.py b/lnbits/extensions/nostr/views.py
new file mode 100644
index 00000000..1dfc07da
--- /dev/null
+++ b/lnbits/extensions/nostr/views.py
@@ -0,0 +1,24 @@
+from http import HTTPStatus
+
+from fastapi import Request
+from fastapi.param_functions import Query
+from fastapi.params import Depends
+from fastapi.templating import Jinja2Templates
+from starlette.exceptions import HTTPException
+from starlette.responses import HTMLResponse
+
+from lnbits.core.crud import update_payment_status
+from lnbits.core.models import User
+from lnbits.core.views.api import api_payment
+from lnbits.decorators import check_user_exists
+
+from . import nostr_ext, nostr_renderer
+
+templates = Jinja2Templates(directory="templates")
+
+
+@nostr_ext.get("/", response_class=HTMLResponse)
+async def index(request: Request, user: User = Depends(check_user_exists)):
+ return nostr_renderer().TemplateResponse(
+ "nostr/index.html", {"request": request, "user": user.dict()}
+ )
diff --git a/lnbits/extensions/nostr/views_api.py b/lnbits/extensions/nostr/views_api.py
new file mode 100644
index 00000000..a479cad8
--- /dev/null
+++ b/lnbits/extensions/nostr/views_api.py
@@ -0,0 +1,48 @@
+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 WalletTypeInfo, get_key_type, require_admin_key
+from lnbits.extensions.lnurldevice import lnurldevice_ext
+from lnbits.utils.exchange_rates import currencies
+
+from . import lnurldevice_ext
+from .crud import (
+ create_lnurldevice,
+ delete_lnurldevice,
+ get_lnurldevice,
+ get_lnurldevices,
+ update_lnurldevice,
+)
+from .models import createLnurldevice
+
+
+@nostr_ext.get("/api/v1/lnurlpos")
+async def api_check_daemon(wallet: WalletTypeInfo = Depends(get_key_type)):
+ wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
+ try:
+ return [
+ {**lnurldevice.dict()} for lnurldevice in await get_lnurldevices(wallet_ids)
+ ]
+ except:
+ return ""
+
+@nostr_ext.delete("/api/v1/lnurlpos/{lnurldevice_id}")
+async def api_lnurldevice_delete(
+ wallet: WalletTypeInfo = Depends(require_admin_key),
+ lnurldevice_id: str = Query(None),
+):
+ lnurldevice = await get_lnurldevice(lnurldevice_id)
+
+ if not lnurldevice:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND, detail="Wallet link does not exist."
+ )
+
+ await delete_lnurldevice(lnurldevice_id)
+
+ return "", HTTPStatus.NO_CONTENT
\ No newline at end of file
From d60d5c928cf7b7479c8232b06b9430ad21d189bf Mon Sep 17 00:00:00 2001
From: benarc
Date: Mon, 7 Feb 2022 20:01:01 +0000
Subject: [PATCH 042/226] extension loading
---
lnbits/extensions/nostr/__init__.py | 2 -
lnbits/extensions/nostr/crud.py | 71 +--
lnbits/extensions/nostr/migrations.py | 2 +-
lnbits/extensions/nostr/models.py | 12 +-
.../templates/lnurldevice/_api_docs.html | 169 ------
.../nostr/templates/lnurldevice/error.html | 34 --
.../nostr/templates/lnurldevice/index.html | 534 ------------------
.../nostr/templates/lnurldevice/paid.html | 27 -
.../nostr/templates/nostr/index.html | 159 ++++++
lnbits/extensions/nostr/views_api.py | 46 +-
10 files changed, 204 insertions(+), 852 deletions(-)
delete mode 100644 lnbits/extensions/nostr/templates/lnurldevice/_api_docs.html
delete mode 100644 lnbits/extensions/nostr/templates/lnurldevice/error.html
delete mode 100644 lnbits/extensions/nostr/templates/lnurldevice/index.html
delete mode 100644 lnbits/extensions/nostr/templates/lnurldevice/paid.html
create mode 100644 lnbits/extensions/nostr/templates/nostr/index.html
diff --git a/lnbits/extensions/nostr/__init__.py b/lnbits/extensions/nostr/__init__.py
index 775960e3..9774a279 100644
--- a/lnbits/extensions/nostr/__init__.py
+++ b/lnbits/extensions/nostr/__init__.py
@@ -11,7 +11,5 @@ nostr_ext: APIRouter = APIRouter(prefix="/nostr", tags=["nostr"])
def nostr_renderer():
return template_renderer(["lnbits/extensions/nostr/templates"])
-
-from .lnurl import * # noqa
from .views import * # noqa
from .views_api import * # noqa
diff --git a/lnbits/extensions/nostr/crud.py b/lnbits/extensions/nostr/crud.py
index 55e99ec6..0b30bc9a 100644
--- a/lnbits/extensions/nostr/crud.py
+++ b/lnbits/extensions/nostr/crud.py
@@ -1,33 +1,31 @@
from typing import List, Optional, Union
from lnbits.helpers import urlsafe_short_hash
-
+import shortuuid
from . import db
-from .models import nostrKeys, nostrNotes, nostrRelays, nostrConnections
+from .models import nostrKeys, nostrNotes, nostrCreateRelays, nostrRelays, nostrConnections, nostrCreateConnections
###############KEYS##################
async def create_nostrkeys(
data: nostrKeys
) -> nostrKeys:
- nostrkey_id = urlsafe_short_hash()
await db.execute(
"""
INSERT INTO nostr.keys (
- id,
pubkey,
privkey
)
- VALUES (?, ?, ?)
+ VALUES (?, ?)
""",
- (nostrkey_id, data.pubkey, data.privkey),
+ (data.pubkey, data.privkey),
)
return await get_nostrkeys(nostrkey_id)
-async def get_nostrkeys(nostrkey_id: str) -> nostrKeys:
+async def get_nostrkeys(pubkey: str) -> nostrKeys:
row = await db.fetchone(
- "SELECT * FROM nostr.keys WHERE id = ?",
- (lnurldevicepayment_id,),
+ "SELECT * FROM nostr.keys WHERE pubkey = ?",
+ (pubkey,),
)
return nostrKeys(**row) if row else None
@@ -64,9 +62,12 @@ async def get_nostrnotes(nostrnote_id: str) -> nostrNotes:
###############RELAYS##################
async def create_nostrrelays(
- relay: str
-) -> nostrRelays:
- nostrrelay_id = urlsafe_short_hash()
+ data: nostrCreateRelays
+) -> nostrCreateRelays:
+ nostrrelay_id = shortuuid.uuid(name=relay)
+
+ if await get_nostrrelays(nostrrelay_id):
+ return "error"
await db.execute(
"""
INSERT INTO nostr.relays (
@@ -75,7 +76,7 @@ async def create_nostrrelays(
)
VALUES (?, ?)
""",
- (nostrrelay_id, relay),
+ (nostrrelay_id, data.relay),
)
return await get_nostrnotes(nostrrelay_id)
@@ -90,45 +91,25 @@ async def get_nostrrelays(nostrrelay_id: str) -> nostrRelays:
###############CONNECTIONS##################
async def create_nostrconnections(
- data: nostrNotes
-) -> nostrNotes:
- nostrkey_id = urlsafe_short_hash()
+ data: nostrCreateConnections
+) -> nostrCreateConnections:
+ nostrrelay_id = shortuuid.uuid(name=data.relayid + data.pubkey)
await db.execute(
"""
- INSERT INTO nostr.notes (
+ INSERT INTO nostr.connections (
id,
pubkey,
- created_at,
- kind,
- tags,
- content,
- sig
+ relayid
)
- VALUES (?, ?, ?, ?, ?, ?, ?)
+ VALUES (?, ?, ?)
""",
- (data.id, data.pubkey, data.created_at, data.kind, data.tags, data.content, data.sig),
+ (data.id, data.pubkey, data.relayid),
)
- return await get_nostrnotes(data.id)
+ return await get_nostrconnections(data.id)
-async def get_nostrnotes(nostrnote_id: str) -> nostrNotes:
+async def get_nostrconnections(nostrconnections_id: str) -> nostrConnections:
row = await db.fetchone(
- "SELECT * FROM nostr.notes WHERE id = ?",
- (nostrnote_id,),
+ "SELECT * FROM nostr.connections WHERE id = ?",
+ (nostrconnections_id,),
)
- return nostrNotes(**row) if row else None
-
-
-
-async def update_lnurldevicepayment(
- lnurldevicepayment_id: str, **kwargs
-) -> Optional[lnurldevicepayment]:
- q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
- await db.execute(
- f"UPDATE lnurldevice.lnurldevicepayment SET {q} WHERE id = ?",
- (*kwargs.values(), lnurldevicepayment_id),
- )
- row = await db.fetchone(
- "SELECT * FROM lnurldevice.lnurldevicepayment WHERE id = ?",
- (lnurldevicepayment_id,),
- )
- return lnurldevicepayment(**row) if row else None
\ No newline at end of file
+ return nostrConnections(**row) if row else None
\ No newline at end of file
diff --git a/lnbits/extensions/nostr/migrations.py b/lnbits/extensions/nostr/migrations.py
index de32a578..0c616110 100644
--- a/lnbits/extensions/nostr/migrations.py
+++ b/lnbits/extensions/nostr/migrations.py
@@ -24,7 +24,7 @@ async def m001_initial(db):
kind INT NOT NULL,
tags TEXT NOT NULL,
content TEXT NOT NULL,
- sig TEXT NOT NULL,
+ sig TEXT NOT NULL
);
"""
)
diff --git a/lnbits/extensions/nostr/models.py b/lnbits/extensions/nostr/models.py
index ba0c87a5..e21e5d76 100644
--- a/lnbits/extensions/nostr/models.py
+++ b/lnbits/extensions/nostr/models.py
@@ -3,15 +3,10 @@ from sqlite3 import Row
from typing import Optional
from fastapi import Request
-from lnurl import Lnurl
-from lnurl import encode as lnurl_encode # type: ignore
-from lnurl.models import LnurlPaySuccessAction, UrlAction # type: ignore
-from lnurl.types import LnurlPayMetadata # type: ignore
from pydantic import BaseModel
from pydantic.main import BaseModel
class nostrKeys(BaseModel):
- id: str
pubkey: str
privkey: str
@@ -24,6 +19,13 @@ class nostrNotes(BaseModel):
content: str
sig: str
+class nostrCreateRelays(BaseModel):
+ relay: str
+
+class nostrCreateConnections(BaseModel):
+ pubkey: str
+ relayid: str
+
class nostrRelays(BaseModel):
id: str
relay: str
diff --git a/lnbits/extensions/nostr/templates/lnurldevice/_api_docs.html b/lnbits/extensions/nostr/templates/lnurldevice/_api_docs.html
deleted file mode 100644
index af69b76e..00000000
--- a/lnbits/extensions/nostr/templates/lnurldevice/_api_docs.html
+++ /dev/null
@@ -1,169 +0,0 @@
-
-
-
- Register LNURLDevice devices to receive payments in your LNbits wallet.
- Build your own here
- https://github.com/arcbtc/bitcoinpos
-
- Created by, Ben Arc
-
-
-
-
-
-
- /lnurldevice/api/v1/lnurlpos
- Headers
- {"X-Api-Key": <admin_key>}
-
- Body (application/json)
-
-
- Returns 200 OK (application/json)
-
- [<lnurldevice_object>, ...]
- Curl example
- curl -X POST {{ request.base_url }}api/v1/lnurldevice -d '{"title":
- <string>, "message":<string>, "currency":
- <integer>}' -H "Content-type: application/json" -H "X-Api-Key:
- {{user.wallets[0].adminkey }}"
-
-
-
-
-
-
-
- PUT
- /lnurldevice/api/v1/lnurlpos/<lnurldevice_id>
- Headers
- {"X-Api-Key": <admin_key>}
-
- Body (application/json)
-
-
- Returns 200 OK (application/json)
-
- [<lnurldevice_object>, ...]
- Curl example
- curl -X POST {{ request.base_url
- }}api/v1/lnurlpos/<lnurldevice_id> -d ''{"title":
- <string>, "message":<string>, "currency":
- <integer>} -H "Content-type: application/json" -H "X-Api-Key:
- {{user.wallets[0].adminkey }}"
-
-
-
-
-
-
-
-
- GET
- /lnurldevice/api/v1/lnurlpos/<lnurldevice_id>
- Headers
- {"X-Api-Key": <invoice_key>}
-
- Body (application/json)
-
-
- Returns 200 OK (application/json)
-
- [<lnurldevice_object>, ...]
- Curl example
- curl -X GET {{ request.base_url
- }}api/v1/lnurlpos/<lnurldevice_id> -H "X-Api-Key: {{
- user.wallets[0].inkey }}"
-
-
-
-
-
-
-
- GET
- /lnurldevice/api/v1/lnurlposs
- Headers
- {"X-Api-Key": <invoice_key>}
-
- Body (application/json)
-
-
- Returns 200 OK (application/json)
-
- [<lnurldevice_object>, ...]
- Curl example
- curl -X GET {{ request.base_url }}api/v1/lnurldevices -H
- "X-Api-Key: {{ user.wallets[0].inkey }}"
-
-
-
-
-
-
-
- DELETE
- /lnurldevice/api/v1/lnurlpos/<lnurldevice_id>
- Headers
- {"X-Api-Key": <admin_key>}
- Returns 204 NO CONTENT
-
- Curl example
- curl -X DELETE {{ request.base_url
- }}api/v1/lnurlpos/<lnurldevice_id> -H "X-Api-Key: {{
- user.wallets[0].adminkey }}"
-
-
-
-
-
-
diff --git a/lnbits/extensions/nostr/templates/lnurldevice/error.html b/lnbits/extensions/nostr/templates/lnurldevice/error.html
deleted file mode 100644
index d8e41832..00000000
--- a/lnbits/extensions/nostr/templates/lnurldevice/error.html
+++ /dev/null
@@ -1,34 +0,0 @@
-{% extends "public.html" %} {% block page %}
-
-
-
-
-
- LNURL-pay not paid
-
-
-
-
-
-
-
-
-
- {% endblock %} {% block scripts %}
-
-
-
- {% endblock %}
-
diff --git a/lnbits/extensions/nostr/templates/lnurldevice/index.html b/lnbits/extensions/nostr/templates/lnurldevice/index.html
deleted file mode 100644
index b51e2556..00000000
--- a/lnbits/extensions/nostr/templates/lnurldevice/index.html
+++ /dev/null
@@ -1,534 +0,0 @@
-{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
-%} {% block page %}
-
-
-
-
- {% raw %}
- New LNURLDevice instance
-
-
-
-
-
-
-
-
-
lNURLdevice
-
-
-
-
-
-
-
-
- Export to CSV
-
-
-
-
-
-
-
-
-
-
- {{ col.label }}
-
-
-
-
-
-
-
-
-
- Delete LNURLDevice
-
-
-
-
- LNURLDevice Settings
-
-
-
-
- {{ col.value }}
-
-
-
- {% endraw %}
-
-
-
-
-
-
-
-
-
- {{SITE_TITLE}} LNURLDevice Extension
-
-
-
-
- {% include "lnurldevice/_api_docs.html" %}
-
-
-
-
-
-
- LNURLDevice device string
- {% raw
- %}{{location}}/lnurldevice/api/v1/lnurl/{{settingsDialog.data.id}},
- {{settingsDialog.data.key}}, {{settingsDialog.data.currency}}{% endraw
- %} Click to copy URL
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Update lnurldevice
- Create lnurldevice
- Cancel
-
-
-
-
-
-{% endblock %} {% block scripts %} {{ window_vars(user) }}
-
-
-{% endblock %}
diff --git a/lnbits/extensions/nostr/templates/lnurldevice/paid.html b/lnbits/extensions/nostr/templates/lnurldevice/paid.html
deleted file mode 100644
index c185ecce..00000000
--- a/lnbits/extensions/nostr/templates/lnurldevice/paid.html
+++ /dev/null
@@ -1,27 +0,0 @@
-{% extends "public.html" %} {% block page %}
-
-
-
-
-
- {{ pin }}
-
-
-
-
-
-
- {% endblock %} {% block scripts %}
-
-
-
- {% endblock %}
-
diff --git a/lnbits/extensions/nostr/templates/nostr/index.html b/lnbits/extensions/nostr/templates/nostr/index.html
new file mode 100644
index 00000000..f17d0243
--- /dev/null
+++ b/lnbits/extensions/nostr/templates/nostr/index.html
@@ -0,0 +1,159 @@
+{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
+%} {% block page %}
+
+
+
+
+
+
+
Nostr
+
+
+
+
+
+
+
+
+ Export to CSV
+
+
+
+ {% raw %}
+
+
+
+
+
+
+
+ {{ col.label }}
+
+
+
+
+
+
+
+
+
+ {{ col.value }}
+
+
+
+ {% endraw %}
+
+
+
+
+
+
+
+
+ {{SITE_TITLE}} Nostr Extension
+
+ Okay
+
+
+
+
+{% endblock %} {% block scripts %} {{ window_vars(user) }}
+
+
+{% endblock %}
diff --git a/lnbits/extensions/nostr/views_api.py b/lnbits/extensions/nostr/views_api.py
index a479cad8..9e7ccfff 100644
--- a/lnbits/extensions/nostr/views_api.py
+++ b/lnbits/extensions/nostr/views_api.py
@@ -7,42 +7,18 @@ from starlette.exceptions import HTTPException
from lnbits.core.crud import get_user
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
-from lnbits.extensions.lnurldevice import lnurldevice_ext
+from lnbits.extensions.nostr import nostr_ext
from lnbits.utils.exchange_rates import currencies
-from . import lnurldevice_ext
+from . import nostr_ext
from .crud import (
- create_lnurldevice,
- delete_lnurldevice,
- get_lnurldevice,
- get_lnurldevices,
- update_lnurldevice,
+ create_nostrkeys,
+ get_nostrkeys,
+ create_nostrnotes,
+ get_nostrnotes,
+ create_nostrrelays,
+ get_nostrrelays,
+ create_nostrconnections,
+ get_nostrconnections,
)
-from .models import createLnurldevice
-
-
-@nostr_ext.get("/api/v1/lnurlpos")
-async def api_check_daemon(wallet: WalletTypeInfo = Depends(get_key_type)):
- wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
- try:
- return [
- {**lnurldevice.dict()} for lnurldevice in await get_lnurldevices(wallet_ids)
- ]
- except:
- return ""
-
-@nostr_ext.delete("/api/v1/lnurlpos/{lnurldevice_id}")
-async def api_lnurldevice_delete(
- wallet: WalletTypeInfo = Depends(require_admin_key),
- lnurldevice_id: str = Query(None),
-):
- lnurldevice = await get_lnurldevice(lnurldevice_id)
-
- if not lnurldevice:
- raise HTTPException(
- status_code=HTTPStatus.NOT_FOUND, detail="Wallet link does not exist."
- )
-
- await delete_lnurldevice(lnurldevice_id)
-
- return "", HTTPStatus.NO_CONTENT
\ No newline at end of file
+from .models import nostrKeys
\ No newline at end of file
From 30f9c3d290e9198e6585109d2fb5d19479b214e1 Mon Sep 17 00:00:00 2001
From: benarc
Date: Tue, 8 Feb 2022 10:35:20 +0000
Subject: [PATCH 043/226] Chnaged nostr to nostradmin
---
.env.example | 2 +-
lnbits/extensions/nostr/config.json | 6 -
lnbits/extensions/nostr/crud.py | 115 -------------
lnbits/extensions/nostr/views_api.py | 24 ---
.../{nostr => nostradmin}/README.md | 0
.../{nostr => nostradmin}/__init__.py | 7 +-
lnbits/extensions/nostradmin/config.json | 6 +
lnbits/extensions/nostradmin/crud.py | 156 ++++++++++++++++++
.../{nostr => nostradmin}/migrations.py | 32 +++-
.../{nostr => nostradmin}/models.py | 14 +-
.../templates/nostradmin}/index.html | 130 ++++++++++++++-
.../extensions/{nostr => nostradmin}/views.py | 2 +-
lnbits/extensions/nostradmin/views_api.py | 87 ++++++++++
13 files changed, 421 insertions(+), 160 deletions(-)
delete mode 100644 lnbits/extensions/nostr/config.json
delete mode 100644 lnbits/extensions/nostr/crud.py
delete mode 100644 lnbits/extensions/nostr/views_api.py
rename lnbits/extensions/{nostr => nostradmin}/README.md (100%)
rename lnbits/extensions/{nostr => nostradmin}/__init__.py (51%)
create mode 100644 lnbits/extensions/nostradmin/config.json
create mode 100644 lnbits/extensions/nostradmin/crud.py
rename lnbits/extensions/{nostr => nostradmin}/migrations.py (54%)
rename lnbits/extensions/{nostr => nostradmin}/models.py (73%)
rename lnbits/extensions/{nostr/templates/nostr => nostradmin/templates/nostradmin}/index.html (50%)
rename lnbits/extensions/{nostr => nostradmin}/views.py (90%)
create mode 100644 lnbits/extensions/nostradmin/views_api.py
diff --git a/.env.example b/.env.example
index 6ef60bc1..5926ee65 100644
--- a/.env.example
+++ b/.env.example
@@ -6,7 +6,7 @@ DEBUG=false
LNBITS_ALLOWED_USERS=""
LNBITS_ADMIN_USERS=""
# Extensions only admin can access
-LNBITS_ADMIN_EXTENSIONS="ngrok"
+LNBITS_ADMIN_EXTENSIONS="nostradmin"
LNBITS_DEFAULT_WALLET_NAME="LNbits wallet"
LNBITS_AD_SPACE="" # csv ad image filepaths or urls, extensions can choose to honor
diff --git a/lnbits/extensions/nostr/config.json b/lnbits/extensions/nostr/config.json
deleted file mode 100644
index a32e39a1..00000000
--- a/lnbits/extensions/nostr/config.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "name": "Nostr",
- "short_description": "Daemon for Nostr",
- "icon": "swap_horizontal_circle",
- "contributors": ["arcbtc"]
-}
diff --git a/lnbits/extensions/nostr/crud.py b/lnbits/extensions/nostr/crud.py
deleted file mode 100644
index 0b30bc9a..00000000
--- a/lnbits/extensions/nostr/crud.py
+++ /dev/null
@@ -1,115 +0,0 @@
-from typing import List, Optional, Union
-
-from lnbits.helpers import urlsafe_short_hash
-import shortuuid
-from . import db
-from .models import nostrKeys, nostrNotes, nostrCreateRelays, nostrRelays, nostrConnections, nostrCreateConnections
-
-###############KEYS##################
-
-async def create_nostrkeys(
- data: nostrKeys
-) -> nostrKeys:
- await db.execute(
- """
- INSERT INTO nostr.keys (
- pubkey,
- privkey
- )
- VALUES (?, ?)
- """,
- (data.pubkey, data.privkey),
- )
- return await get_nostrkeys(nostrkey_id)
-
-async def get_nostrkeys(pubkey: str) -> nostrKeys:
- row = await db.fetchone(
- "SELECT * FROM nostr.keys WHERE pubkey = ?",
- (pubkey,),
- )
- return nostrKeys(**row) if row else None
-
-
-###############NOTES##################
-
-async def create_nostrnotes(
- data: nostrNotes
-) -> nostrNotes:
- await db.execute(
- """
- INSERT INTO nostr.notes (
- id,
- pubkey,
- created_at,
- kind,
- tags,
- content,
- sig
- )
- VALUES (?, ?, ?, ?, ?, ?, ?)
- """,
- (data.id, data.pubkey, data.created_at, data.kind, data.tags, data.content, data.sig),
- )
- return await get_nostrnotes(data.id)
-
-async def get_nostrnotes(nostrnote_id: str) -> nostrNotes:
- row = await db.fetchone(
- "SELECT * FROM nostr.notes WHERE id = ?",
- (nostrnote_id,),
- )
- return nostrNotes(**row) if row else None
-
-###############RELAYS##################
-
-async def create_nostrrelays(
- data: nostrCreateRelays
-) -> nostrCreateRelays:
- nostrrelay_id = shortuuid.uuid(name=relay)
-
- if await get_nostrrelays(nostrrelay_id):
- return "error"
- await db.execute(
- """
- INSERT INTO nostr.relays (
- id,
- relay
- )
- VALUES (?, ?)
- """,
- (nostrrelay_id, data.relay),
- )
- return await get_nostrnotes(nostrrelay_id)
-
-async def get_nostrrelays(nostrrelay_id: str) -> nostrRelays:
- row = await db.fetchone(
- "SELECT * FROM nostr.relays WHERE id = ?",
- (nostrnote_id,),
- )
- return nostrRelays(**row) if row else None
-
-
-###############CONNECTIONS##################
-
-async def create_nostrconnections(
- data: nostrCreateConnections
-) -> nostrCreateConnections:
- nostrrelay_id = shortuuid.uuid(name=data.relayid + data.pubkey)
- await db.execute(
- """
- INSERT INTO nostr.connections (
- id,
- pubkey,
- relayid
- )
- VALUES (?, ?, ?)
- """,
- (data.id, data.pubkey, data.relayid),
- )
- return await get_nostrconnections(data.id)
-
-async def get_nostrconnections(nostrconnections_id: str) -> nostrConnections:
- row = await db.fetchone(
- "SELECT * FROM nostr.connections WHERE id = ?",
- (nostrconnections_id,),
- )
- return nostrConnections(**row) if row else None
\ No newline at end of file
diff --git a/lnbits/extensions/nostr/views_api.py b/lnbits/extensions/nostr/views_api.py
deleted file mode 100644
index 9e7ccfff..00000000
--- a/lnbits/extensions/nostr/views_api.py
+++ /dev/null
@@ -1,24 +0,0 @@
-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 WalletTypeInfo, get_key_type, require_admin_key
-from lnbits.extensions.nostr import nostr_ext
-from lnbits.utils.exchange_rates import currencies
-
-from . import nostr_ext
-from .crud import (
- create_nostrkeys,
- get_nostrkeys,
- create_nostrnotes,
- get_nostrnotes,
- create_nostrrelays,
- get_nostrrelays,
- create_nostrconnections,
- get_nostrconnections,
-)
-from .models import nostrKeys
\ No newline at end of file
diff --git a/lnbits/extensions/nostr/README.md b/lnbits/extensions/nostradmin/README.md
similarity index 100%
rename from lnbits/extensions/nostr/README.md
rename to lnbits/extensions/nostradmin/README.md
diff --git a/lnbits/extensions/nostr/__init__.py b/lnbits/extensions/nostradmin/__init__.py
similarity index 51%
rename from lnbits/extensions/nostr/__init__.py
rename to lnbits/extensions/nostradmin/__init__.py
index 9774a279..7034ca46 100644
--- a/lnbits/extensions/nostr/__init__.py
+++ b/lnbits/extensions/nostradmin/__init__.py
@@ -3,13 +3,14 @@ from fastapi import APIRouter
from lnbits.db import Database
from lnbits.helpers import template_renderer
-db = Database("ext_nostr")
+db = Database("ext_nostradmin")
-nostr_ext: APIRouter = APIRouter(prefix="/nostr", tags=["nostr"])
+nostr_ext: APIRouter = APIRouter(prefix="/nostradmin", tags=["nostradmin"])
def nostr_renderer():
- return template_renderer(["lnbits/extensions/nostr/templates"])
+ return template_renderer(["lnbits/extensions/nostradmin/templates"])
+
from .views import * # noqa
from .views_api import * # noqa
diff --git a/lnbits/extensions/nostradmin/config.json b/lnbits/extensions/nostradmin/config.json
new file mode 100644
index 00000000..2c4f76d3
--- /dev/null
+++ b/lnbits/extensions/nostradmin/config.json
@@ -0,0 +1,6 @@
+{
+ "name": "NostrAdmin",
+ "short_description": "Admin daemon for Nostr",
+ "icon": "swap_horizontal_circle",
+ "contributors": ["arcbtc"]
+}
diff --git a/lnbits/extensions/nostradmin/crud.py b/lnbits/extensions/nostradmin/crud.py
new file mode 100644
index 00000000..08908e85
--- /dev/null
+++ b/lnbits/extensions/nostradmin/crud.py
@@ -0,0 +1,156 @@
+from typing import List, Optional, Union
+
+from lnbits.helpers import urlsafe_short_hash
+import shortuuid
+from . import db
+from .models import (
+ nostrKeys,
+ nostrNotes,
+ nostrCreateRelays,
+ nostrRelays,
+ nostrConnections,
+ nostrCreateConnections,
+ nostrRelayList,
+)
+
+###############KEYS##################
+
+
+async def create_nostrkeys(data: nostrKeys) -> nostrKeys:
+ await db.execute(
+ """
+ INSERT INTO nostradmin.keys (
+ pubkey,
+ privkey
+ )
+ VALUES (?, ?)
+ """,
+ (data.pubkey, data.privkey),
+ )
+ return await get_nostrkeys(nostrkey_id)
+
+
+async def get_nostrkeys(pubkey: str) -> nostrKeys:
+ row = await db.fetchone("SELECT * FROM nostradmin.keys WHERE pubkey = ?", (pubkey,))
+ return nostrKeys(**row) if row else None
+
+
+###############NOTES##################
+
+
+async def create_nostrnotes(data: nostrNotes) -> nostrNotes:
+ await db.execute(
+ """
+ INSERT INTO nostradmin.notes (
+ id,
+ pubkey,
+ created_at,
+ kind,
+ tags,
+ content,
+ sig
+ )
+ VALUES (?, ?, ?, ?, ?, ?, ?)
+ """,
+ (
+ data.id,
+ data.pubkey,
+ data.created_at,
+ data.kind,
+ data.tags,
+ data.content,
+ data.sig,
+ ),
+ )
+ return await get_nostrnotes(data.id)
+
+
+async def get_nostrnotes(nostrnote_id: str) -> nostrNotes:
+ row = await db.fetchone("SELECT * FROM nostradmin.notes WHERE id = ?", (nostrnote_id,))
+ return nostrNotes(**row) if row else None
+
+
+###############RELAYS##################
+
+
+async def create_nostrrelays(data: nostrCreateRelays) -> nostrCreateRelays:
+ nostrrelay_id = shortuuid.uuid(name=data.relay)
+
+ if await get_nostrrelay(nostrrelay_id):
+ return "error"
+ await db.execute(
+ """
+ INSERT INTO nostradmin.relays (
+ id,
+ relay
+ )
+ VALUES (?, ?)
+ """,
+ (nostrrelay_id, data.relay),
+ )
+ return await get_nostrrelay(nostrrelay_id)
+
+
+async def get_nostrrelays() -> nostrRelays:
+ rows = await db.fetchall("SELECT * FROM nostradmin.relays")
+ return [nostrRelays(**row) for row in rows]
+
+
+async def get_nostrrelay(nostrrelay_id: str) -> nostrRelays:
+ row = await db.fetchone("SELECT * FROM nostradmin.relays WHERE id = ?", (nostrrelay_id,))
+ return nostrRelays(**row) if row else None
+
+
+async def update_nostrrelayallowlist(allowlist: str) -> nostrRelayList:
+ await db.execute(
+ """
+ UPDATE nostradmin.relaylist SET
+ allowlist = ?
+ WHERE id = ?
+ """,
+ (allowlist, 1),
+ )
+ return await get_nostrrelaylist()
+
+async def update_nostrrelaydenylist(denylist: str) -> nostrRelayList:
+ await db.execute(
+ """
+ UPDATE nostradmin.relaylist SET
+ denylist = ?
+ WHERE id = ?
+ """,
+ (denylist, 1),
+ )
+ return await get_nostrrelaylist()
+
+async def get_nostrrelaylist() -> nostrRelayList:
+ row = await db.fetchone("SELECT * FROM nostradmin.relaylist WHERE id = ?", (1,))
+ return nostrRelayList(**row) if row else None
+
+
+###############CONNECTIONS##################
+
+
+async def create_nostrconnections(
+ data: nostrCreateConnections
+) -> nostrCreateConnections:
+ nostrrelay_id = shortuuid.uuid(name=data.relayid + data.pubkey)
+ await db.execute(
+ """
+ INSERT INTO nostradmin.connections (
+ id,
+ pubkey,
+ relayid
+ )
+ VALUES (?, ?, ?)
+ """,
+ (data.id, data.pubkey, data.relayid),
+ )
+ return await get_nostrconnections(data.id)
+
+
+async def get_nostrconnections(nostrconnections_id: str) -> nostrConnections:
+ row = await db.fetchone(
+ "SELECT * FROM nostradmin.connections WHERE id = ?", (nostrconnections_id,)
+ )
+ return nostrConnections(**row) if row else None
diff --git a/lnbits/extensions/nostr/migrations.py b/lnbits/extensions/nostradmin/migrations.py
similarity index 54%
rename from lnbits/extensions/nostr/migrations.py
rename to lnbits/extensions/nostradmin/migrations.py
index 0c616110..09b28117 100644
--- a/lnbits/extensions/nostr/migrations.py
+++ b/lnbits/extensions/nostradmin/migrations.py
@@ -9,7 +9,7 @@ async def m001_initial(db):
"""
await db.execute(
f"""
- CREATE TABLE nostr.keys (
+ CREATE TABLE nostradmin.keys (
pubkey TEXT NOT NULL PRIMARY KEY,
privkey TEXT NOT NULL
);
@@ -17,7 +17,7 @@ async def m001_initial(db):
)
await db.execute(
f"""
- CREATE TABLE nostr.notes (
+ CREATE TABLE nostradmin.notes (
id TEXT NOT NULL PRIMARY KEY,
pubkey TEXT NOT NULL,
created_at TEXT NOT NULL,
@@ -30,7 +30,7 @@ async def m001_initial(db):
)
await db.execute(
f"""
- CREATE TABLE nostr.relays (
+ CREATE TABLE nostradmin.relays (
id TEXT NOT NULL PRIMARY KEY,
relay TEXT NOT NULL
);
@@ -38,10 +38,32 @@ async def m001_initial(db):
)
await db.execute(
f"""
- CREATE TABLE nostr.connections (
+ CREATE TABLE nostradmin.relaylists (
+ id TEXT NOT NULL PRIMARY KEY DEFAULT 1,
+ allowlist TEXT NOT NULL,
+ denylist TEXT NOT NULL
+ );
+ """
+ )
+ try:
+ await db.execute(
+ """
+ INSERT INTO nostradmin.relaylist (
+ id,
+ denylist
+ )
+ VALUES (?, ?,)
+ """,
+ (1, "\n".join(["wss://zucks-meta-relay.com", "wss://nostradmin.cia.gov"])),
+ )
+ except:
+ return
+ await db.execute(
+ f"""
+ CREATE TABLE nostradmin.connections (
id TEXT NOT NULL PRIMARY KEY,
publickey TEXT NOT NULL,
relayid TEXT NOT NULL
);
"""
- )
\ No newline at end of file
+ )
diff --git a/lnbits/extensions/nostr/models.py b/lnbits/extensions/nostradmin/models.py
similarity index 73%
rename from lnbits/extensions/nostr/models.py
rename to lnbits/extensions/nostradmin/models.py
index e21e5d76..fd89f515 100644
--- a/lnbits/extensions/nostr/models.py
+++ b/lnbits/extensions/nostradmin/models.py
@@ -6,6 +6,7 @@ from fastapi import Request
from pydantic import BaseModel
from pydantic.main import BaseModel
+
class nostrKeys(BaseModel):
pubkey: str
privkey: str
@@ -30,7 +31,18 @@ class nostrRelays(BaseModel):
id: str
relay: str
+class nostrRelayList(BaseModel):
+ id: str
+ allowlist: str
+ denylist: str
+
+class nostrRelayDenyList(BaseModel):
+ denylist: str
+
+class nostrRelayAllowList(BaseModel):
+ allowlist: str
+
class nostrConnections(BaseModel):
id: str
pubkey: str
- relayid: str
\ No newline at end of file
+ relayid: str
diff --git a/lnbits/extensions/nostr/templates/nostr/index.html b/lnbits/extensions/nostradmin/templates/nostradmin/index.html
similarity index 50%
rename from lnbits/extensions/nostr/templates/nostr/index.html
rename to lnbits/extensions/nostradmin/templates/nostradmin/index.html
index f17d0243..adab98e2 100644
--- a/lnbits/extensions/nostr/templates/nostr/index.html
+++ b/lnbits/extensions/nostradmin/templates/nostradmin/index.html
@@ -71,12 +71,77 @@
+
+
+
+
+
+
+
+
+
+
+
+ Deny List (denys use of relays in this list)
+
+
+
+
+
+
+
+
+ Update Deny List
+
+ Reset
+
+
+
+
+
+
+
+ Allow List (denys any relays not in this list)
+
+
+
+
+
+
+
+
+ Update Allow List
+
+ Reset
+
+
+
+
+
+
{{SITE_TITLE}} Nostr Extension
+ Only Admin users can manage this extension
Okay
@@ -109,6 +174,9 @@
mixins: [windowMixin],
data: function () {
return {
+ listSelection: 'denylist',
+ allowList: [],
+ denyList: [],
nostrTable: {
columns: [
{
@@ -136,12 +204,66 @@
LNbits.api
.request(
'GET',
- '/nostr/api/v1/relays',
+ '/nostradmin/api/v1/relays',
self.g.user.wallets[0].adminkey
)
.then(function (response) {
if (response.data) {
- self.lnurldeviceLinks = response.data.map(maplnurldevice)
+ self.nostrrelayLinks = response.data.map(maprelays)
+ }
+ })
+ .catch(function (error) {
+ LNbits.utils.notifyApiError(error)
+ })
+ },
+ setAllowList: function () {
+ var self = this
+ LNbits.api
+ .request(
+ 'POST',
+ '/nostradmin/api/v1/allowlist',
+ self.g.user.wallets[0].adminkey,
+ self.allowList
+ )
+ .then(function (response) {
+ if (response.data) {
+ self.allowList = response.data
+ }
+ })
+ .catch(function (error) {
+ LNbits.utils.notifyApiError(error)
+ })
+ },
+ setDenyList: function () {
+ var self = this
+ LNbits.api
+ .request(
+ 'POST',
+ '/nostradmin/api/v1/denylist',
+ self.g.user.wallets[0].adminkey,
+ self.allowList
+ )
+ .then(function (response) {
+ if (response.data) {
+ self.denyList = response.data
+ }
+ })
+ .catch(function (error) {
+ LNbits.utils.notifyApiError(error)
+ })
+ },
+ getLists: function () {
+ var self = this
+ LNbits.api
+ .request(
+ 'GET',
+ '/nostradmin/api/v1/relaylist',
+ self.g.user.wallets[0].adminkey
+ )
+ .then(function (response) {
+ if (response.data) {
+ self.denyList = response.data.denylist
+ self.allowList = response.data.allowlist
}
})
.catch(function (error) {
@@ -151,8 +273,8 @@
},
created: function () {
var self = this
- var getrelays = this.getrelays
- getrelays()
+ this.getrelays()
+ this.getLists()
}
})
diff --git a/lnbits/extensions/nostr/views.py b/lnbits/extensions/nostradmin/views.py
similarity index 90%
rename from lnbits/extensions/nostr/views.py
rename to lnbits/extensions/nostradmin/views.py
index 1dfc07da..5609c218 100644
--- a/lnbits/extensions/nostr/views.py
+++ b/lnbits/extensions/nostradmin/views.py
@@ -20,5 +20,5 @@ templates = Jinja2Templates(directory="templates")
@nostr_ext.get("/", response_class=HTMLResponse)
async def index(request: Request, user: User = Depends(check_user_exists)):
return nostr_renderer().TemplateResponse(
- "nostr/index.html", {"request": request, "user": user.dict()}
+ "nostradmin/index.html", {"request": request, "user": user.dict()}
)
diff --git a/lnbits/extensions/nostradmin/views_api.py b/lnbits/extensions/nostradmin/views_api.py
new file mode 100644
index 00000000..da6e140b
--- /dev/null
+++ b/lnbits/extensions/nostradmin/views_api.py
@@ -0,0 +1,87 @@
+from http import HTTPStatus
+import asyncio
+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 WalletTypeInfo, get_key_type, require_admin_key
+from lnbits.extensions.nostr import nostr_ext
+from lnbits.utils.exchange_rates import currencies
+
+from . import nostr_ext
+from lnbits.settings import LNBITS_ADMIN_USERS
+from .crud import (
+ create_nostrkeys,
+ get_nostrkeys,
+ create_nostrnotes,
+ get_nostrnotes,
+ create_nostrrelays,
+ get_nostrrelays,
+ get_nostrrelaylist,
+ update_nostrrelayallowlist,
+ update_nostrrelaydenylist,
+ create_nostrconnections,
+ get_nostrconnections,
+)
+from .models import nostrKeys, nostrCreateRelays, nostrRelayAllowList, nostrRelayDenyList
+
+
+# while True:
+async def nostr_subscribe():
+ return
+ # for the relays:
+ # async with websockets.connect("ws://localhost:8765") as websocket:
+ # for the public keys:
+ # await websocket.send("subscribe to events")
+ # await websocket.recv()
+
+
+websocket_queue = asyncio.Queue(1000)
+
+
+async def internal_invoice_listener():
+ while True:
+ checking_id = await internal_invoice_queue.get()
+ asyncio.create_task(invoice_callback_dispatcher(checking_id))
+
+
+@nostr_ext.get("/api/v1/relays")
+async def api_relays_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)):
+
+ relays = await get_nostrrelays()
+ if not relays:
+ await create_nostrrelays(nostrCreateRelays(relay="wss://relayer.fiatjaf.com"))
+ await create_nostrrelays(
+ nostrCreateRelays(relay="wss://nostr-pub.wellorder.net")
+ )
+ relays = await get_nostrrelays()
+ try:
+ return [{**relays.dict()} for relays in await relays]
+ except:
+ None
+
+@nostr_ext.get("/api/v1/relaylist")
+async def api_relaylist(wallet: WalletTypeInfo = Depends(get_key_type)):
+ if wallet.wallet.user not in LNBITS_ADMIN_USERS:
+ raise HTTPException(
+ status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
+ )
+ return await get_nostrrelaylist()
+
+@nostr_ext.post("/api/v1/allowlist")
+async def api_relaysallowed(data: nostrRelayAllowList, wallet: WalletTypeInfo = Depends(get_key_type)):
+ if wallet.wallet.user not in LNBITS_ADMIN_USERS:
+ raise HTTPException(
+ status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
+ )
+ return await update_nostrrelayallowlist(data)
+
+@nostr_ext.post("/api/v1/denylist")
+async def api_relaysdenyed(data: nostrRelayDenyList, wallet: WalletTypeInfo = Depends(get_key_type)):
+ if wallet.wallet.user not in LNBITS_ADMIN_USERS:
+ raise HTTPException(
+ status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
+ )
+ return await update_nostrrelaydenylist(data)
\ No newline at end of file
From 2c435ee9d906b214fbcc6b19541898dcab01d4a4 Mon Sep 17 00:00:00 2001
From: benarc
Date: Tue, 8 Feb 2022 13:13:36 +0000
Subject: [PATCH 044/226] models decided
---
lnbits/extensions/nostradmin/__init__.py | 2 +-
lnbits/extensions/nostradmin/crud.py | 22 ++----
lnbits/extensions/nostradmin/migrations.py | 41 ++++++-----
lnbits/extensions/nostradmin/models.py | 33 +++++----
.../templates/nostradmin/index.html | 71 +++++++++++--------
lnbits/extensions/nostradmin/views.py | 5 +-
lnbits/extensions/nostradmin/views_api.py | 33 +++------
7 files changed, 100 insertions(+), 107 deletions(-)
diff --git a/lnbits/extensions/nostradmin/__init__.py b/lnbits/extensions/nostradmin/__init__.py
index 7034ca46..797542c4 100644
--- a/lnbits/extensions/nostradmin/__init__.py
+++ b/lnbits/extensions/nostradmin/__init__.py
@@ -5,7 +5,7 @@ from lnbits.helpers import template_renderer
db = Database("ext_nostradmin")
-nostr_ext: APIRouter = APIRouter(prefix="/nostradmin", tags=["nostradmin"])
+nostradmin_ext: APIRouter = APIRouter(prefix="/nostradmin", tags=["nostradmin"])
def nostr_renderer():
diff --git a/lnbits/extensions/nostradmin/crud.py b/lnbits/extensions/nostradmin/crud.py
index 08908e85..1e1bf810 100644
--- a/lnbits/extensions/nostradmin/crud.py
+++ b/lnbits/extensions/nostradmin/crud.py
@@ -12,6 +12,7 @@ from .models import (
nostrCreateConnections,
nostrRelayList,
)
+from .models import nostrKeys, nostrCreateRelays, nostrRelaySetList
###############KEYS##################
@@ -100,31 +101,20 @@ async def get_nostrrelay(nostrrelay_id: str) -> nostrRelays:
row = await db.fetchone("SELECT * FROM nostradmin.relays WHERE id = ?", (nostrrelay_id,))
return nostrRelays(**row) if row else None
-
-async def update_nostrrelayallowlist(allowlist: str) -> nostrRelayList:
+async def update_nostrrelaysetlist(data: nostrRelaySetList) -> nostrRelayList:
await db.execute(
"""
- UPDATE nostradmin.relaylist SET
+ UPDATE nostradmin.relaylists SET
+ denylist = ?,
allowlist = ?
WHERE id = ?
""",
- (allowlist, 1),
- )
- return await get_nostrrelaylist()
-
-async def update_nostrrelaydenylist(denylist: str) -> nostrRelayList:
- await db.execute(
- """
- UPDATE nostradmin.relaylist SET
- denylist = ?
- WHERE id = ?
- """,
- (denylist, 1),
+ (data.denylist, data.allowlist, 1),
)
return await get_nostrrelaylist()
async def get_nostrrelaylist() -> nostrRelayList:
- row = await db.fetchone("SELECT * FROM nostradmin.relaylist WHERE id = ?", (1,))
+ row = await db.fetchone("SELECT * FROM nostradmin.relaylists WHERE id = ?", (1,))
return nostrRelayList(**row) if row else None
diff --git a/lnbits/extensions/nostradmin/migrations.py b/lnbits/extensions/nostradmin/migrations.py
index 09b28117..590f72ea 100644
--- a/lnbits/extensions/nostradmin/migrations.py
+++ b/lnbits/extensions/nostradmin/migrations.py
@@ -1,11 +1,8 @@
from lnbits.db import Database
-db2 = Database("ext_nostr")
-
-
async def m001_initial(db):
"""
- Initial nostr table.
+ Initial nostradmin table.
"""
await db.execute(
f"""
@@ -40,24 +37,23 @@ async def m001_initial(db):
f"""
CREATE TABLE nostradmin.relaylists (
id TEXT NOT NULL PRIMARY KEY DEFAULT 1,
- allowlist TEXT NOT NULL,
- denylist TEXT NOT NULL
+ allowlist TEXT,
+ denylist TEXT
);
"""
)
- try:
- await db.execute(
- """
- INSERT INTO nostradmin.relaylist (
- id,
- denylist
- )
- VALUES (?, ?,)
- """,
- (1, "\n".join(["wss://zucks-meta-relay.com", "wss://nostradmin.cia.gov"])),
+
+ await db.execute(
+ """
+ INSERT INTO nostradmin.relaylists (
+ id,
+ denylist
)
- except:
- return
+ VALUES (?, ?)
+ """,
+ ("1", "wss://zucks-meta-relay.com\nwss://nostr.cia.gov",),
+ )
+
await db.execute(
f"""
CREATE TABLE nostradmin.connections (
@@ -67,3 +63,12 @@ async def m001_initial(db):
);
"""
)
+ await db.execute(
+ f"""
+ CREATE TABLE nostradmin.subscribed (
+ id TEXT NOT NULL PRIMARY KEY,
+ userPubkey TEXT NOT NULL,
+ subscribedPubkey TEXT NOT NULL
+ );
+ """
+ )
\ No newline at end of file
diff --git a/lnbits/extensions/nostradmin/models.py b/lnbits/extensions/nostradmin/models.py
index fd89f515..1968567f 100644
--- a/lnbits/extensions/nostradmin/models.py
+++ b/lnbits/extensions/nostradmin/models.py
@@ -5,7 +5,7 @@ from typing import Optional
from fastapi import Request
from pydantic import BaseModel
from pydantic.main import BaseModel
-
+from fastapi.param_functions import Query
class nostrKeys(BaseModel):
pubkey: str
@@ -21,28 +21,31 @@ class nostrNotes(BaseModel):
sig: str
class nostrCreateRelays(BaseModel):
- relay: str
+ relay: str = Query(None)
class nostrCreateConnections(BaseModel):
- pubkey: str
- relayid: str
+ pubkey: str = Query(None)
+ relayid: str = Query(None)
class nostrRelays(BaseModel):
- id: str
- relay: str
+ id: Optional[str]
+ relay: Optional[str]
class nostrRelayList(BaseModel):
id: str
- allowlist: str
- denylist: str
+ allowlist: Optional[str]
+ denylist: Optional[str]
-class nostrRelayDenyList(BaseModel):
- denylist: str
-
-class nostrRelayAllowList(BaseModel):
- allowlist: str
+class nostrRelaySetList(BaseModel):
+ allowlist: Optional[str]
+ denylist: Optional[str]
class nostrConnections(BaseModel):
id: str
- pubkey: str
- relayid: str
+ pubkey: Optional[str]
+ relayid: Optional[str]
+
+class nostrSubscriptions(BaseModel):
+ id: str
+ userPubkey: Optional[str]
+ subscribedPubkey: Optional[str]
\ No newline at end of file
diff --git a/lnbits/extensions/nostradmin/templates/nostradmin/index.html b/lnbits/extensions/nostradmin/templates/nostradmin/index.html
index adab98e2..27decdc8 100644
--- a/lnbits/extensions/nostradmin/templates/nostradmin/index.html
+++ b/lnbits/extensions/nostradmin/templates/nostradmin/index.html
@@ -6,7 +6,7 @@
-
Nostr
+ NOSTR RELAYS ONLINE
@@ -29,7 +29,7 @@
-
-
+
+
-
+
-
+
-
- Deny List (denys use of relays in this list)
-
-
+ Relays in this list will NOT be used
+
-
+
Update Deny List
- Reset
-
- Allow List (denys any relays not in this list)
-
-
+ ONLY relays in this list will be used
+
-
+
Update Allow List
- Reset
@@ -175,8 +175,12 @@
data: function () {
return {
listSelection: 'denylist',
- allowList: [],
- denyList: [],
+ setList: {
+ allowlist: '',
+ denylist: ''
+ },
+ nostrLinks: [],
+ filter: '',
nostrTable: {
columns: [
{
@@ -234,18 +238,20 @@
LNbits.utils.notifyApiError(error)
})
},
- setDenyList: function () {
+ setRelayList: function () {
var self = this
+ console.log(self.setList)
LNbits.api
.request(
'POST',
- '/nostradmin/api/v1/denylist',
+ '/nostradmin/api/v1/setlist',
self.g.user.wallets[0].adminkey,
- self.allowList
+ self.setList
)
.then(function (response) {
if (response.data) {
- self.denyList = response.data
+ console.log(response.data)
+ // self.denyList = response.data
}
})
.catch(function (error) {
@@ -262,13 +268,18 @@
)
.then(function (response) {
if (response.data) {
- self.denyList = response.data.denylist
- self.allowList = response.data.allowlist
+ console.log(response.data)
+ self.setList.denylist = response.data.denylist
+ self.setList.allowlist = response.data.allowlist
}
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
+ },
+ exportlnurldeviceCSV: function () {
+ var self = this
+ LNbits.utils.exportCSV(self.nostrTable.columns, this.nostrLinks)
}
},
created: function () {
diff --git a/lnbits/extensions/nostradmin/views.py b/lnbits/extensions/nostradmin/views.py
index 5609c218..51297320 100644
--- a/lnbits/extensions/nostradmin/views.py
+++ b/lnbits/extensions/nostradmin/views.py
@@ -6,18 +6,19 @@ from fastapi.params import Depends
from fastapi.templating import Jinja2Templates
from starlette.exceptions import HTTPException
from starlette.responses import HTMLResponse
+from . import nostradmin_ext, nostr_renderer
from lnbits.core.crud import update_payment_status
from lnbits.core.models import User
from lnbits.core.views.api import api_payment
from lnbits.decorators import check_user_exists
-from . import nostr_ext, nostr_renderer
+
templates = Jinja2Templates(directory="templates")
-@nostr_ext.get("/", response_class=HTMLResponse)
+@nostradmin_ext.get("/", response_class=HTMLResponse)
async def index(request: Request, user: User = Depends(check_user_exists)):
return nostr_renderer().TemplateResponse(
"nostradmin/index.html", {"request": request, "user": user.dict()}
diff --git a/lnbits/extensions/nostradmin/views_api.py b/lnbits/extensions/nostradmin/views_api.py
index da6e140b..d79315ac 100644
--- a/lnbits/extensions/nostradmin/views_api.py
+++ b/lnbits/extensions/nostradmin/views_api.py
@@ -7,11 +7,10 @@ from starlette.exceptions import HTTPException
from lnbits.core.crud import get_user
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
-from lnbits.extensions.nostr import nostr_ext
from lnbits.utils.exchange_rates import currencies
-from . import nostr_ext
from lnbits.settings import LNBITS_ADMIN_USERS
+from . import nostradmin_ext
from .crud import (
create_nostrkeys,
get_nostrkeys,
@@ -20,13 +19,11 @@ from .crud import (
create_nostrrelays,
get_nostrrelays,
get_nostrrelaylist,
- update_nostrrelayallowlist,
- update_nostrrelaydenylist,
+ update_nostrrelaysetlist,
create_nostrconnections,
get_nostrconnections,
)
-from .models import nostrKeys, nostrCreateRelays, nostrRelayAllowList, nostrRelayDenyList
-
+from .models import nostrKeys, nostrCreateRelays, nostrRelaySetList
# while True:
async def nostr_subscribe():
@@ -41,13 +38,7 @@ async def nostr_subscribe():
websocket_queue = asyncio.Queue(1000)
-async def internal_invoice_listener():
- while True:
- checking_id = await internal_invoice_queue.get()
- asyncio.create_task(invoice_callback_dispatcher(checking_id))
-
-
-@nostr_ext.get("/api/v1/relays")
+@nostradmin_ext.get("/api/v1/relays")
async def api_relays_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)):
relays = await get_nostrrelays()
@@ -62,7 +53,7 @@ async def api_relays_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)):
except:
None
-@nostr_ext.get("/api/v1/relaylist")
+@nostradmin_ext.get("/api/v1/relaylist")
async def api_relaylist(wallet: WalletTypeInfo = Depends(get_key_type)):
if wallet.wallet.user not in LNBITS_ADMIN_USERS:
raise HTTPException(
@@ -70,18 +61,10 @@ async def api_relaylist(wallet: WalletTypeInfo = Depends(get_key_type)):
)
return await get_nostrrelaylist()
-@nostr_ext.post("/api/v1/allowlist")
-async def api_relaysallowed(data: nostrRelayAllowList, wallet: WalletTypeInfo = Depends(get_key_type)):
+@nostradmin_ext.post("/api/v1/setlist")
+async def api_relayssetlist(data: nostrRelaySetList, wallet: WalletTypeInfo = Depends(get_key_type)):
if wallet.wallet.user not in LNBITS_ADMIN_USERS:
raise HTTPException(
status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
)
- return await update_nostrrelayallowlist(data)
-
-@nostr_ext.post("/api/v1/denylist")
-async def api_relaysdenyed(data: nostrRelayDenyList, wallet: WalletTypeInfo = Depends(get_key_type)):
- if wallet.wallet.user not in LNBITS_ADMIN_USERS:
- raise HTTPException(
- status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
- )
- return await update_nostrrelaydenylist(data)
\ No newline at end of file
+ return await update_nostrrelaysetlist(data)
\ No newline at end of file
From 3f356f9de8d48995db0e301aa27015b2827fa70e Mon Sep 17 00:00:00 2001
From: benarc
Date: Tue, 8 Feb 2022 13:52:19 +0000
Subject: [PATCH 045/226] Started planning websocket daemon
---
lnbits/extensions/nostradmin/views.py | 70 ++++++++++++++++++++++-
lnbits/extensions/nostradmin/views_api.py | 12 ----
2 files changed, 68 insertions(+), 14 deletions(-)
diff --git a/lnbits/extensions/nostradmin/views.py b/lnbits/extensions/nostradmin/views.py
index 51297320..235ec7e9 100644
--- a/lnbits/extensions/nostradmin/views.py
+++ b/lnbits/extensions/nostradmin/views.py
@@ -1,5 +1,6 @@
from http import HTTPStatus
-
+import asyncio
+import asyncio
from fastapi import Request
from fastapi.param_functions import Query
from fastapi.params import Depends
@@ -7,13 +8,14 @@ from fastapi.templating import Jinja2Templates
from starlette.exceptions import HTTPException
from starlette.responses import HTMLResponse
from . import nostradmin_ext, nostr_renderer
+from fastapi import Request, WebSocket, WebSocketDisconnect
from lnbits.core.crud import update_payment_status
from lnbits.core.models import User
from lnbits.core.views.api import api_payment
from lnbits.decorators import check_user_exists
-
+from .crud import get_nostrkeys
templates = Jinja2Templates(directory="templates")
@@ -23,3 +25,67 @@ async def index(request: Request, user: User = Depends(check_user_exists)):
return nostr_renderer().TemplateResponse(
"nostradmin/index.html", {"request": request, "user": user.dict()}
)
+
+#####################################################################
+#################### NOSTR WEBSOCKET THREAD #########################
+##### THE QUEUE LOOP THREAD THING THAT LISTENS TO BUNCH OF ##########
+### WEBSOCKET CONNECTIONS, STORING DATA IN DB/PUSHING TO FRONTEND ###
+################### VIA updater() FUNCTION ##########################
+#####################################################################
+
+websocket_queue = asyncio.Queue(1000)
+
+# while True:
+async def nostr_subscribe():
+ return
+ # for the relays:
+ # async with websockets.connect("ws://localhost:8765") as websocket:
+ # for the public keys:
+ # await websocket.send("subscribe to events")
+ # await websocket.recv()
+
+#####################################################################
+################### LNBITS WEBSOCKET ROUTES #########################
+#### HERE IS WHERE LNBITS FRONTEND CAN RECEIVE AND SEND MESSAGES ####
+#####################################################################
+
+class ConnectionManager:
+ def __init__(self):
+ self.active_connections: List[WebSocket] = []
+
+ async def connect(self, websocket: WebSocket, nostr_id: str):
+ await websocket.accept()
+ websocket.id = nostr_id
+ self.active_connections.append(websocket)
+
+ def disconnect(self, websocket: WebSocket):
+ self.active_connections.remove(websocket)
+
+ async def send_personal_message(self, message: str, nostr_id: str):
+ for connection in self.active_connections:
+ if connection.id == nostr_id:
+ await connection.send_text(message)
+
+ async def broadcast(self, message: str):
+ for connection in self.active_connections:
+ await connection.send_text(message)
+
+
+manager = ConnectionManager()
+
+
+@nostradmin_ext.websocket("/nostradmin/ws/{nostr_id}", name="copilot.websocket_by_id")
+async def websocket_endpoint(websocket: WebSocket, copilot_id: str):
+ await manager.connect(websocket, nostr_id)
+ try:
+ while True:
+ data = await websocket.receive_text()
+ except WebSocketDisconnect:
+ manager.disconnect(websocket)
+
+
+async def updater(nostr_id, message):
+ copilot = await get_copilot(nostr_id)
+ if not copilot:
+ return
+ await manager.send_personal_message(f"{message}", nostr_id)
\ No newline at end of file
diff --git a/lnbits/extensions/nostradmin/views_api.py b/lnbits/extensions/nostradmin/views_api.py
index d79315ac..ad8bcf17 100644
--- a/lnbits/extensions/nostradmin/views_api.py
+++ b/lnbits/extensions/nostradmin/views_api.py
@@ -25,18 +25,6 @@ from .crud import (
)
from .models import nostrKeys, nostrCreateRelays, nostrRelaySetList
-# while True:
-async def nostr_subscribe():
- return
- # for the relays:
- # async with websockets.connect("ws://localhost:8765") as websocket:
- # for the public keys:
- # await websocket.send("subscribe to events")
- # await websocket.recv()
-
-
-websocket_queue = asyncio.Queue(1000)
-
@nostradmin_ext.get("/api/v1/relays")
async def api_relays_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)):
From 4e65c39b362921b81488f9a1212cb57714b879fe Mon Sep 17 00:00:00 2001
From: benarc
Date: Tue, 8 Feb 2022 14:02:50 +0000
Subject: [PATCH 046/226] typo
---
lnbits/extensions/nostradmin/views.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/nostradmin/views.py b/lnbits/extensions/nostradmin/views.py
index 235ec7e9..f00c43a3 100644
--- a/lnbits/extensions/nostradmin/views.py
+++ b/lnbits/extensions/nostradmin/views.py
@@ -74,7 +74,7 @@ class ConnectionManager:
manager = ConnectionManager()
-@nostradmin_ext.websocket("/nostradmin/ws/{nostr_id}", name="copilot.websocket_by_id")
+@nostradmin_ext.websocket("/nostradmin/ws/{nostr_id}", name="nostr_id.websocket_by_id")
async def websocket_endpoint(websocket: WebSocket, copilot_id: str):
await manager.connect(websocket, nostr_id)
try:
From dbc47332ee71508ea39861ef2d8cef9cf3236b25 Mon Sep 17 00:00:00 2001
From: benarc
Date: Thu, 10 Feb 2022 09:58:50 +0000
Subject: [PATCH 047/226] Working, looking good
---
lnbits/extensions/nostradmin/models.py | 1 +
.../templates/nostradmin/index.html | 39 ++++++++++++-------
lnbits/extensions/nostradmin/views.py | 21 +++++++---
lnbits/extensions/nostradmin/views_api.py | 17 +++++---
4 files changed, 53 insertions(+), 25 deletions(-)
diff --git a/lnbits/extensions/nostradmin/models.py b/lnbits/extensions/nostradmin/models.py
index 1968567f..dc99b083 100644
--- a/lnbits/extensions/nostradmin/models.py
+++ b/lnbits/extensions/nostradmin/models.py
@@ -30,6 +30,7 @@ class nostrCreateConnections(BaseModel):
class nostrRelays(BaseModel):
id: Optional[str]
relay: Optional[str]
+ status: Optional[bool] = False
class nostrRelayList(BaseModel):
id: str
diff --git a/lnbits/extensions/nostradmin/templates/nostradmin/index.html b/lnbits/extensions/nostradmin/templates/nostradmin/index.html
index 27decdc8..57552bf2 100644
--- a/lnbits/extensions/nostradmin/templates/nostradmin/index.html
+++ b/lnbits/extensions/nostradmin/templates/nostradmin/index.html
@@ -29,7 +29,7 @@
-
-
-
- {{ col.value }}
+
+
+ {{ col.value }}
+
+
{{ col.value }}
+
@@ -143,7 +145,7 @@
{{SITE_TITLE}} Nostr Extension
Only Admin users can manage this extension
- Okay
+
@@ -153,7 +155,7 @@
+{% endblock %}
+
+
+
diff --git a/lnbits/extensions/diagonalley/views.py b/lnbits/extensions/diagonalley/views.py
index c38f8d51..70d5c146 100644
--- a/lnbits/extensions/diagonalley/views.py
+++ b/lnbits/extensions/diagonalley/views.py
@@ -3,14 +3,13 @@ from http import HTTPStatus
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 starlette.exceptions import HTTPException
+from starlette.responses import HTMLResponse
-from .crud import get_diagonalley_products
+from .crud import get_diagonalley_products, get_diagonalley_stall
templates = Jinja2Templates(directory="templates")
@@ -24,17 +23,18 @@ async def index(request: Request, user: User = Depends(check_user_exists)):
@diagonalley_ext.get("/{stall_id}", response_class=HTMLResponse)
async def display(request: Request, stall_id):
- product = await get_diagonalley_products(stall_id)
+ stall = await get_diagonalley_stall(stall_id)
+ products = await get_diagonalley_products(stall_id)
- if not product:
+ if not stall:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Stall does not exist."
)
return diagonalley_renderer().TemplateResponse(
"diagonalley/stall.html",
{
- "stall": [
- product.dict() for product in await get_diagonalley_products(stall_id)
- ]
+ "request": request,
+ "stall": stall.dict(),
+ "products": [product.dict() for product in products]
},
)
From 6965459f5d8860343be25f16c434652425c6d5cb Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Wed, 20 Jul 2022 11:46:20 +0100
Subject: [PATCH 063/226] add StaticFiles
---
lnbits/extensions/diagonalley/__init__.py | 23 +++++++++++++++--------
1 file changed, 15 insertions(+), 8 deletions(-)
diff --git a/lnbits/extensions/diagonalley/__init__.py b/lnbits/extensions/diagonalley/__init__.py
index db88bdbd..23c02a5f 100644
--- a/lnbits/extensions/diagonalley/__init__.py
+++ b/lnbits/extensions/diagonalley/__init__.py
@@ -1,18 +1,23 @@
import asyncio
-from http import HTTPStatus
-
-from fastapi import APIRouter, Request
-from fastapi.staticfiles import StaticFiles
-from starlette.exceptions import HTTPException
-from starlette.responses import HTMLResponse
+from fastapi import APIRouter
from lnbits.db import Database
from lnbits.helpers import template_renderer
-from lnbits.settings import LNBITS_ADMIN_EXTENSIONS
from lnbits.tasks import catch_everything_and_restart
+from starlette.staticfiles import StaticFiles
+
+db = Database("ext_diagonalley")
diagonalley_ext: APIRouter = APIRouter(prefix="/diagonalley", tags=["diagonalley"])
-db = Database("ext_diagonalley")
+
+diagonalley_static_files = [
+ {
+ "path": "/diagonalley/static",
+ "app": StaticFiles(directory="lnbits/extensions/diagonalley/static"),
+ "name": "diagonalley_static",
+ }
+]
+
# if 'nostradmin' not in LNBITS_ADMIN_EXTENSIONS:
# @diagonalley_ext.get("/", response_class=HTMLResponse)
# async def index(request: Request):
@@ -20,8 +25,10 @@ db = Database("ext_diagonalley")
# "error.html", {"request": request, "err": "Ask system admin to enable NostrAdmin!"}
# )
# else:
+
def diagonalley_renderer():
return template_renderer(["lnbits/extensions/diagonalley/templates"])
+ # return template_renderer(["lnbits/extensions/diagonalley/templates"])
from .tasks import wait_for_paid_invoices
From b15134114885f41338157895316e035c3780c64e Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Wed, 20 Jul 2022 11:46:48 +0100
Subject: [PATCH 064/226] add a placeholder img for no photo product
---
.../diagonalley/static/images/placeholder.png | Bin 0 -> 2840 bytes
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 lnbits/extensions/diagonalley/static/images/placeholder.png
diff --git a/lnbits/extensions/diagonalley/static/images/placeholder.png b/lnbits/extensions/diagonalley/static/images/placeholder.png
new file mode 100644
index 0000000000000000000000000000000000000000..c7d3a94729f5835b922f71f2b57ccdb7c0ca6672
GIT binary patch
literal 2840
zcmeAS@N?(olHy`uVBq!ia0y~yV4lIiz_f{jnSp_!J*+XCfq{V~-O<;Pfnj4m_n$;o
z1_lO&WRD0By
z_2c^wZ(hGXapJ_OQ>U(6xpMW&l`EGoU%hhm{o8l%-n_ka^Va>l_fDQXdFITS%azA)?-@bkN^y#;6
z-aLNv_`><~Cr_Nbee3pv`wyN!efH+n>x&mIT)cSk^{ZDmu3x`=>GHX=XWzblbLaM*
zXHTD?k@bJ>bOSf*^eDdh=sgox!
zUby(+-u)|=uRMDA@ZQ~fr%#=J{p$6rm#^NwdHep|`-cx6oIP{q|NsBh!RwbZFfcGC
zdAqx0oX+{8#K6G7UgGKN%Kn%~oKs2Q;X9oK1_q9;o-U3d6}R5r{at>!MB>218v(Z*
z*d8xRo+&!v?9Rx!(=9(XiKi^8a8x(a&$0UW>HmLrKg*(oEt!|L{G9f!vd;hh^v$0w
z7v|1#5o8$7z-L#wl<^bE`urA6AAw~Cg3AmXXB={wafl_6OSpyekcb*b@C=UN845m$
zN6QYSjI8Ecsmk9=mO^4=Kmsb5d
zbF=vQ{MvO}1-OOP6wh#XpFG^W`P-$>8x0f-X3TvaZ}IEY&7pHuiy%g$d#|EK4#=M;8o**WjY-nwsd3>N+~2%pR-?8hPe
zitqE(cMCXg8YVjJO)Q(n-X9Supms&-*=pCcJ=3^^vv`UZ-p489O5&
zdPe@9$O12K5No~I+)vXC7OpdRe0EXrt>Qy1vkuKU&DCESJtH7^hIm*1O(C@_BF|<%
zO1=e>x}#&5?l&XAe}-qL<$Dg{SDc&A%vpV?CF+pR=`)skJ}aL0%$z#o^E!it^9**Y
zKHH=2vqJsMckeTut(?y7k*~f@mFKZ_n_;lc_}SJ+$?G_Tv$!^2*0V{A1=;no-F115
z2scP9EsBz7}rR^l6EFIM05*i1en&S0WX*QJ$@x
z2JQUDdL_M%Vec+FoesPrqIPIaLHy0IiKjo=>0Z4i-R`cl?2yZh!|gNA@yl(Dc&B{x
z^8b0s`5eNDeDZ0^azA=nmORN{^1u3EZlcl|u0%5{=6A^-Zk{d5jIH{x?Q1bquzTn8
z)849=e?2-_=)KZFFs;=B5Ot=_D&=cd}dU;00O
z^9C7i;lwb`%@-r~UVXiF|DJ$-Yu3xj-37T&EN$_|57#c{n{1ChyvO%WT}6$#sM?`5
z5^0MKzkmG{^l8Dll&74AxQpk+s)T{TW>x;|9=0(M8)tr89+XmF4yMEB`FI8#05VI3{t~@7?>i
zx8Ev>pTSYAlUSA~_J7NBPJ3?Q#6G(-&8w@wug>}9uH(~U`N$yu-M^Wkx3^r#-`~<{
z;9X<9$)qW?ytUK7UG&-AYcqe}^a#`aw1y>7=}e>EaqG_~UzF`t_i2f=Jk$L3=hBJq
zs{CefY)+bCoqqe~is`ljYKPW{JX@Kv8fdpz&eL1`Tl$d84Ci*s
zWqOM0F#Z^|b{ZTmnHddAAq(_Ap901B)l|Qkhg@bH-1R0NB)miK
z_cTqP7Re2#>r%Od6WiWw@&`#f?tT*slD?r=4VK<lD%!z5+KdA_B_%GkTehMe3@YYm^mi4H$2CN2vi?(#~eZJ*o!C8<`)MX8A;sSHL2hL*a9M!H7EAqJLK#wJ#VX4(b@Rt5$Qm;dfU(U6;;
zl9^VCTf@gbS5R0=f@}!RPb(=;EJ|f?Ovz75Rq)JBOiv9;O-!jQJeg_(3UE(XKbLh*
G2~7YIWw#>$
literal 0
HcmV?d00001
From e56b26146ad8bb583951b4869a7d99bcf3f35b5e Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Wed, 20 Jul 2022 11:47:03 +0100
Subject: [PATCH 065/226] toolbar
---
.../templates/diagonalley/stall.html | 208 ++++++++++--------
1 file changed, 114 insertions(+), 94 deletions(-)
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/stall.html b/lnbits/extensions/diagonalley/templates/diagonalley/stall.html
index 640a2428..a7a4ddca 100644
--- a/lnbits/extensions/diagonalley/templates/diagonalley/stall.html
+++ b/lnbits/extensions/diagonalley/templates/diagonalley/stall.html
@@ -1,101 +1,116 @@
{% extends "public.html" %} {% block page %}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Benling C200-BLK Smartwatch (Black Strap Free Size)
-
-
-
-
-
-
-
-
-
-
-
-
-
- Special Price
-
-
₹3,149 ₹3,699
-
20% off
-
-
-
-
-
-
-
-
-
-
-
-
-
- View details
-
-
-
-
+
+
+
+
+ {{ stall.name }}
+
+
+
+
+
+
+
+
+
+
-
-
- {{ item.product }}
- {{ item.description }}
+
+
+
+ Special Price
+
+
{{ item.price }} sats BTC {{ (item.price / 1e8).toFixed(8) }}
+
{{item.quantity}} left
+
+
+ {{cat}}
+
+
+ {{ item.description }}
+
- {{ lorem }}
+
+
+
+
+ View details
+
+
{% endraw %}
-
-->
+
{% endblock %} {% block scripts %}
-{% endblock %}
+{% endblock %}
+
+
+
+
+
+
+
+
+ Update Product
+
+ Create Product
+
+ Cancel
+
+
+
+
{% endblock %} {% block scripts %}
{% endblock %}
From 6d5e9aba39d2e3ecc93219859bc01172a51efb66 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Wed, 27 Jul 2022 15:28:58 +0100
Subject: [PATCH 068/226] initial launch market
---
lnbits/extensions/diagonalley/crud.py | 25 ++++++++++++
lnbits/extensions/diagonalley/migrations.py | 26 +++++++++++++
lnbits/extensions/diagonalley/models.py | 12 +++++-
.../templates/diagonalley/index.html | 18 ++++-----
lnbits/extensions/diagonalley/views.py | 18 +++++++++
lnbits/extensions/diagonalley/views_api.py | 39 +++++++++++++++++++
6 files changed, 127 insertions(+), 11 deletions(-)
diff --git a/lnbits/extensions/diagonalley/crud.py b/lnbits/extensions/diagonalley/crud.py
index 67ef15c9..fbc1d332 100644
--- a/lnbits/extensions/diagonalley/crud.py
+++ b/lnbits/extensions/diagonalley/crud.py
@@ -12,6 +12,7 @@ from lnbits.settings import WALLET
from . import db
from .models import (
+ Market,
Orders,
Products,
Stalls,
@@ -261,3 +262,27 @@ async def get_diagonalley_orders(wallet_ids: Union[str, List[str]]) -> List[Orde
async def delete_diagonalley_order(order_id: str) -> None:
await db.execute("DELETE FROM diagonalley.orders WHERE id = ?", (order_id,))
+
+### Market/Marketplace
+
+async def get_diagonalley_markets(user: str) -> List[Market]:
+ rows = await db.fetchall(
+ 'SELECT * FROM diagonalley.markets WHERE usr = ?', (user,)
+ )
+ return [Market(**row) for row in rows]
+
+
+async def get_diagonalley_market(market_id: str) -> Optional[Market]:
+ row = await db.fetchone(
+ 'SELECT * FROM diagonalley.markets WHERE id = ?', (market_id,)
+ )
+ Market(**row) if row else None
+
+
+async def get_diagonalley_market_stalls(market_id: str):
+ rows = await db.fetchall(
+ "SELECT * FROM diagonalley.market_stalls WHERE marketid = ?", (market_id,)
+ )
+ return [Stalls(**row) for row in rows]
+
+
diff --git a/lnbits/extensions/diagonalley/migrations.py b/lnbits/extensions/diagonalley/migrations.py
index 0ad308b8..e994d723 100644
--- a/lnbits/extensions/diagonalley/migrations.py
+++ b/lnbits/extensions/diagonalley/migrations.py
@@ -86,4 +86,30 @@ async def m001_initial(db):
);
"""
)
+
+ """
+ Initial market table.
+ """
+ await db.execute(
+ """
+ CREATE TABLE diagonalley.markets (
+ id TEXT PRIMARY KEY,
+ usr TEXT NOT NULL,
+ name TEXT
+ );
+ """
+ )
+
+ """
+ Initial market stalls table.
+ """
+ await db.execute(
+ """
+ CREATE TABLE diagonalley.market_stalls (
+ id TEXT PRIMARY KEY,
+ marketid TEXT NOT NULL REFERENCES {db.references_schema}markets (id),
+ stallid TEXT NOT NULL REFERENCES {db.references_schema}stalls (id)
+ );
+ """
+ )
diff --git a/lnbits/extensions/diagonalley/models.py b/lnbits/extensions/diagonalley/models.py
index 2132d72f..4fbf04ff 100644
--- a/lnbits/extensions/diagonalley/models.py
+++ b/lnbits/extensions/diagonalley/models.py
@@ -1,4 +1,4 @@
-from typing import Optional
+from typing import List, Optional
from fastapi.param_functions import Query
from pydantic import BaseModel
@@ -81,3 +81,13 @@ class Orders(BaseModel):
paid: bool
shipped: bool
time: int
+
+class CreateMarket(BaseModel):
+ usr: str = Query(...)
+ name: str = Query(None)
+ stalls: List[str] = Query(...)
+
+class Market(BaseModel):
+ id: str
+ usr: str
+ name: Optional[str]
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/index.html b/lnbits/extensions/diagonalley/templates/diagonalley/index.html
index 0a56408d..dbe443eb 100644
--- a/lnbits/extensions/diagonalley/templates/diagonalley/index.html
+++ b/lnbits/extensions/diagonalley/templates/diagonalley/index.html
@@ -816,7 +816,7 @@ new Vue({
{
name: 'stall',
align: 'left',
- label: 'Stall',
+ label: 'Store',
field: 'stall'
},
{
@@ -1384,23 +1384,21 @@ new Vue({
LNbits.utils.exportCSV(this.zonesTable.columns, this.zones)
},
////////////////////////////////////////
- //////////////////SHOP//////////////////
+ //////////////////MARKET//////////////////
////////////////////////////////////////
- getShops: function () {
- var self = this
-
+ getMarkets(){
LNbits.api
.request(
'GET',
- '/diagonalley/api/v1/shops?all_wallets=true',
+ '/diagonalley/api/v1/markets',
this.g.user.wallets[0].inkey
)
- .then(function (response) {
+ .then((response) => {
if (response.data) {
- self.shops = response.data.map(mapShops)
+ this.shops = response.data.map(mapShops)
}
})
- .catch(function (error) {
+ .catch((error) => {
LNbits.utils.notifyApiError(error)
})
},
@@ -1451,7 +1449,6 @@ new Vue({
})
},
createShop: function (data) {
- var self = this
console.log('cuntywoo')
LNbits.api
.request(
@@ -1591,6 +1588,7 @@ new Vue({
this.getProducts()
this.getZones()
this.getOrders()
+ this.getMarkets()
this.customerKeys = [
'cb4c0164fe03fcdadcbfb4f76611c71620790944c24f21a1cd119395cdedfe1b',
'a9c17358a6dc4ceb3bb4d883eb87967a66b3453a0f3199f0b1c8eef8070c6a07'
diff --git a/lnbits/extensions/diagonalley/views.py b/lnbits/extensions/diagonalley/views.py
index 70d5c146..2ec75589 100644
--- a/lnbits/extensions/diagonalley/views.py
+++ b/lnbits/extensions/diagonalley/views.py
@@ -38,3 +38,21 @@ async def display(request: Request, stall_id):
"products": [product.dict() for product in products]
},
)
+
+@diagonalley_ext.get("/{market_id}", response_class=HTMLResponse)
+async def display(request: Request, stall_id):
+ stalls = await get_diagonalley_stall(stall_id)
+ products = await get_diagonalley_products(stall_id)
+
+ if not stall:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND, detail="Stall does not exist."
+ )
+ return diagonalley_renderer().TemplateResponse(
+ "diagonalley/stall.html",
+ {
+ "request": request,
+ "stall": stall.dict(),
+ "products": [product.dict() for product in products]
+ },
+ )
diff --git a/lnbits/extensions/diagonalley/views_api.py b/lnbits/extensions/diagonalley/views_api.py
index c57eeaa3..b2fb5a47 100644
--- a/lnbits/extensions/diagonalley/views_api.py
+++ b/lnbits/extensions/diagonalley/views_api.py
@@ -25,6 +25,8 @@ from .crud import (
delete_diagonalley_product,
delete_diagonalley_stall,
delete_diagonalley_zone,
+ get_diagonalley_market,
+ get_diagonalley_markets,
get_diagonalley_order,
get_diagonalley_orders,
get_diagonalley_product,
@@ -38,6 +40,7 @@ from .crud import (
update_diagonalley_zone,
)
from .models import (
+ CreateMarket,
Orders,
Products,
Stalls,
@@ -383,3 +386,39 @@ async def api_diagonalley_stall_order(
),
)
return {"checking_id": checking_id, "payment_request": payment_request}
+
+
+##
+# MARKETS
+##
+
+@diagonalley_ext.get("/api/v1/markets")
+async def api_diagonalley_orders(
+ wallet: WalletTypeInfo = Depends(get_key_type)
+):
+ try:
+ return [market.dict() for market in await get_diagonalley_markets(wallet.wallet.user)]
+ except:
+ return {"message": "We could not retrieve the markets."}
+
+@diagonalley_ext.post("/api/v1/markets")
+@diagonalley_ext.put("/api/v1/markets/{market_id}")
+async def api_diagonalley_stall_create(
+ data: CreateMarket,
+ market_id: str = None,
+ wallet: WalletTypeInfo = Depends(require_invoice_key),
+):
+
+ if market_id:
+ market = await get_diagonalley_market(market_id)
+ if not market:
+ return {"message": "Market does not exist."}
+
+ if market.usr != wallet.wallet.user:
+ return {"message": "Not your market."}
+
+ market = await update_diagonalley_market(market_id, **data.dict())
+ else:
+ market = await create_diagonalley_market(data=data)
+
+ return market.dict()
From 3f58676849ef28b03c9ef05ac0537010e6d7b8f9 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Wed, 27 Jul 2022 16:46:27 +0100
Subject: [PATCH 069/226] rename shop to market and create market
---
.../templates/diagonalley/index.html | 52 +++++++++----------
1 file changed, 24 insertions(+), 28 deletions(-)
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/index.html b/lnbits/extensions/diagonalley/templates/diagonalley/index.html
index dbe443eb..893830a9 100644
--- a/lnbits/extensions/diagonalley/templates/diagonalley/index.html
+++ b/lnbits/extensions/diagonalley/templates/diagonalley/index.html
@@ -170,13 +170,13 @@
-
+
Launch
- Cancel
@@ -924,7 +924,7 @@ new Vue({
show: false,
data: {countries:[]}
},
- shopDialog: {
+ marketDialog: {
show: false,
data: {activate: false}
},
@@ -1410,16 +1410,14 @@ new Vue({
this.shopDialog.show = true
},
sendShopFormData: function () {
- if (this.shopDialog.data.id) {
- } else {
- var data = {
- countries: this.shopDialog.data.countries,
- cost: this.shopDialog.data.cost
- }
- }
+ let data = {...this.marketDialog.data}
- if (this.shopDialog.data.id) {
- this.updateZone(this.shopDialog.data)
+ if(!data.usr) {
+ data.usr = this.g.user.id
+ }
+
+ if (data.id) {
+ this.updateZone(data)
} else {
this.createZone(data)
}
@@ -1448,24 +1446,22 @@ new Vue({
LNbits.utils.notifyApiError(error)
})
},
- createShop: function (data) {
- console.log('cuntywoo')
+ createShop(data) {
+ console.log('data')
LNbits.api
.request(
'POST',
- '/diagonalley/api/v1/shops',
- _.findWhere(self.g.user.wallets, {
- id: self.shopDialog.data.wallet
- }).inkey,
+ '/diagonalley/api/v1/markets',
+ this.g.user.wallets[0].inkey,
data
)
- .then(function (response) {
- self.shops.push(mapShops(response.data))
- self.shopDialog.show = false
- self.shopDialog.data = {}
+ .then((response) => {
+ this.shops.push(mapShops(response.data))
+ this.shopDialog.show = false
+ this.shopDialog.data = {}
data = {}
})
- .catch(function (error) {
+ .catch((error) => {
LNbits.utils.notifyApiError(error)
})
},
From c4509d726aef79e772f4e773fc2c920f067149e5 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 16 Aug 2022 12:07:04 +0100
Subject: [PATCH 070/226] UI and checkout
---
coverage.xml | 13560 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 13560 insertions(+)
create mode 100644 coverage.xml
diff --git a/coverage.xml b/coverage.xml
new file mode 100644
index 00000000..dd7ec7b1
--- /dev/null
+++ b/coverage.xml
@@ -0,0 +1,13560 @@
+
+
+
+
+
+ /home/tvasconcelos/Work/lnbits/lnbits
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
From 0a54fe6db8349c36d1121cb446d0859133d799fb Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 16 Aug 2022 12:07:23 +0100
Subject: [PATCH 071/226] ui and checkout
---
.../templates/diagonalley/stall.html | 36 ++++++++-----------
1 file changed, 14 insertions(+), 22 deletions(-)
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/stall.html b/lnbits/extensions/diagonalley/templates/diagonalley/stall.html
index 226ecd2b..5638bc3d 100644
--- a/lnbits/extensions/diagonalley/templates/diagonalley/stall.html
+++ b/lnbits/extensions/diagonalley/templates/diagonalley/stall.html
@@ -112,9 +112,9 @@
-
- {{ item.stall }}
-
+
{{ item.price }} sats BTC {{ (item.price / 1e8).toFixed(8) }}
-
+
+ >
Update Product
-
-
Create Product Checkout
stall_ids.add(p.stall))
- console.log(stall_ids)
+ //let stall_ids = new Set()
+ //this.products.map(p => stall_ids.add(p.stall))
+
+ console.log(this.stall)
}
})
From d9242ddd5be6edc5d91e49828d4abcf789202c04 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 16 Aug 2022 12:07:56 +0100
Subject: [PATCH 072/226] remove privatekey from stall
---
lnbits/extensions/diagonalley/views.py | 43 ++++++++++++++------------
1 file changed, 24 insertions(+), 19 deletions(-)
diff --git a/lnbits/extensions/diagonalley/views.py b/lnbits/extensions/diagonalley/views.py
index 2ec75589..08c30428 100644
--- a/lnbits/extensions/diagonalley/views.py
+++ b/lnbits/extensions/diagonalley/views.py
@@ -3,11 +3,12 @@ from http import HTTPStatus
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 starlette.exceptions import HTTPException
-from starlette.responses import HTMLResponse
from .crud import get_diagonalley_products, get_diagonalley_stall
@@ -30,29 +31,33 @@ async def display(request: Request, stall_id):
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Stall does not exist."
)
+
+ stall = stall.dict()
+ del stall["privatekey"]
+
return diagonalley_renderer().TemplateResponse(
"diagonalley/stall.html",
{
"request": request,
- "stall": stall.dict(),
+ "stall": stall,
"products": [product.dict() for product in products]
},
)
-@diagonalley_ext.get("/{market_id}", response_class=HTMLResponse)
-async def display(request: Request, stall_id):
- stalls = await get_diagonalley_stall(stall_id)
- products = await get_diagonalley_products(stall_id)
+# @diagonalley_ext.get("/market/{market_id}", response_class=HTMLResponse)
+# async def display(request: Request, stall_id):
+# stalls = await get_diagonalley_stall(stall_id)
+# products = await get_diagonalley_products(stall_id)
- if not stall:
- raise HTTPException(
- status_code=HTTPStatus.NOT_FOUND, detail="Stall does not exist."
- )
- return diagonalley_renderer().TemplateResponse(
- "diagonalley/stall.html",
- {
- "request": request,
- "stall": stall.dict(),
- "products": [product.dict() for product in products]
- },
- )
+# if not stall:
+# raise HTTPException(
+# status_code=HTTPStatus.NOT_FOUND, detail="Stall does not exist."
+# )
+# return diagonalley_renderer().TemplateResponse(
+# "diagonalley/stall.html",
+# {
+# "request": request,
+# "stall": stall.dict(),
+# "products": [product.dict() for product in products]
+# },
+# )
From 8032566f4491a3cf5caaf5e8d719e0de3b30be34 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 16 Aug 2022 12:08:27 +0100
Subject: [PATCH 073/226] place order flow WIP
---
lnbits/extensions/diagonalley/crud.py | 45 +++++++++++++++------
lnbits/extensions/diagonalley/migrations.py | 9 ++---
lnbits/extensions/diagonalley/models.py | 21 ++++++++--
3 files changed, 53 insertions(+), 22 deletions(-)
diff --git a/lnbits/extensions/diagonalley/crud.py b/lnbits/extensions/diagonalley/crud.py
index fbc1d332..b915c81d 100644
--- a/lnbits/extensions/diagonalley/crud.py
+++ b/lnbits/extensions/diagonalley/crud.py
@@ -13,11 +13,13 @@ from lnbits.settings import WALLET
from . import db
from .models import (
Market,
+ OrderDetail,
Orders,
Products,
Stalls,
Zones,
createOrder,
+ createOrderDetails,
createProduct,
createStalls,
createZones,
@@ -208,38 +210,55 @@ async def delete_diagonalley_stall(stall_id: str) -> None:
###Orders
-async def create_diagonalley_order(data: createOrder) -> Orders:
+async def create_diagonalley_order(wallet_id: str, data: createOrder) -> Orders:
order_id = urlsafe_short_hash()
await db.execute(
f"""
- INSERT INTO diagonalley.orders (id, productid, wallet, product,
- quantity, shippingzone, address, email, invoiceid, paid, shipped)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ INSERT INTO diagonalley.orders (id, wallet, shippingzone, address, email, total, invoiceid, paid, shipped)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
order_id,
- data.productid,
- data.wallet,
- data.product,
- data.quantity,
+ wallet_id,
data.shippingzone,
data.address,
data.email,
+ data.total,
data.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 create_diagonalley_order_details(order_id: str, data: List[createOrderDetails]):
+ for item in data:
+ item_id = urlsafe_short_hash()
+ await db.execute(
+ """
+ INSERT INTO diagonalley.order_details (id, orderid, productid, quantity)
+ VALUES (?, ?, ?, ?)
+ """,
+ (
+ item_id,
+ order_id,
+ item.product_id,
+ item.quantity,
+ ),
+ )
+ order_details = await get_diagonalley_order_details(order_id)
+ return order_details
+
+async def get_diagonalley_order_details(order_id: str) -> List[OrderDetail]:
+ rows = await db.fetchall(
+ f"SELECT * FROM diagonalley.order_details WHERE order_id = ?", (order_id,)
+ )
+
+ return [OrderDetail(**row) for row in rows]
async def get_diagonalley_order(order_id: str) -> Optional[Orders]:
row = await db.fetchone(
diff --git a/lnbits/extensions/diagonalley/migrations.py b/lnbits/extensions/diagonalley/migrations.py
index e994d723..8a65df8a 100644
--- a/lnbits/extensions/diagonalley/migrations.py
+++ b/lnbits/extensions/diagonalley/migrations.py
@@ -56,12 +56,12 @@ async def m001_initial(db):
"""
CREATE TABLE diagonalley.orders (
id {db.serial_primary_key},
- productid TEXT NOT NULL,
- usr TEXT NOT NULL,
- pubkey TEXT NOT NULL,
+ wallet TEXT NOT NULL,
+ pubkey TEXT,
shippingzone INTEGER NOT NULL,
address TEXT NOT NULL,
email TEXT NOT NULL,
+ total INTEGER NOT NULL,
invoiceid TEXT NOT NULL,
paid BOOLEAN NOT NULL,
shipped BOOLEAN NOT NULL,
@@ -81,8 +81,7 @@ async def m001_initial(db):
id TEXT PRIMARY KEY,
orderid INTEGER NOT NULL REFERENCES {db.references_schema}orders (id)
productid TEXT NOT NULL REFERENCES {db.references_schema}products (id),
- quantity INTEGER NOT NULL,
- total INTEGER NOT NULL
+ quantity INTEGER NOT NULL
);
"""
)
diff --git a/lnbits/extensions/diagonalley/models.py b/lnbits/extensions/diagonalley/models.py
index 4fbf04ff..737f2b4b 100644
--- a/lnbits/extensions/diagonalley/models.py
+++ b/lnbits/extensions/diagonalley/models.py
@@ -55,16 +55,29 @@ class Zones(BaseModel):
cost: int
countries: str
+class OrderDetail(BaseModel):
+ id: str
+ order_id: str
+ product_id: str
+ quantity: int
+
+class createOrderDetails(BaseModel):
+ product_id: str = Query(...)
+ quantity: int = Query(..., ge=1)
+
class createOrder(BaseModel):
- productid: str = Query(...)
- stall: str = Query(...)
- product: str = Query(...)
- quantity: int = Query(..., ge=1)
+ wallet: str = Query(...)
+ pubkey: str = Query(None)
shippingzone: int = Query(...)
address: str = Query(...)
email: str = Query(...)
+ total: int = Query(...)
invoiceid: str = Query(...)
+ products: List[createOrderDetails]
+ # stall: str = Query(...)
+ # product: str = Query(...)
+ # quantity: int = Query(..., ge=1)
class Orders(BaseModel):
From 25d0309ff31d2e5563a02c4aa0f2fc1bbb09d3fc Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 16 Aug 2022 12:07:23 +0100
Subject: [PATCH 074/226] ui and checkout
---
.../templates/diagonalley/stall.html | 36 ++++++++-----------
1 file changed, 14 insertions(+), 22 deletions(-)
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/stall.html b/lnbits/extensions/diagonalley/templates/diagonalley/stall.html
index 226ecd2b..5638bc3d 100644
--- a/lnbits/extensions/diagonalley/templates/diagonalley/stall.html
+++ b/lnbits/extensions/diagonalley/templates/diagonalley/stall.html
@@ -112,9 +112,9 @@
-
- {{ item.stall }}
-
+
{{ item.price }} sats BTC {{ (item.price / 1e8).toFixed(8) }}
-
+
+ >
Update Product
-
-
Create Product Checkout
stall_ids.add(p.stall))
- console.log(stall_ids)
+ //let stall_ids = new Set()
+ //this.products.map(p => stall_ids.add(p.stall))
+
+ console.log(this.stall)
}
})
From 35cf441d91f46828069f0d10fc0af50a61281173 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 16 Aug 2022 12:07:56 +0100
Subject: [PATCH 075/226] remove privatekey from stall
---
lnbits/extensions/diagonalley/views.py | 43 ++++++++++++++------------
1 file changed, 24 insertions(+), 19 deletions(-)
diff --git a/lnbits/extensions/diagonalley/views.py b/lnbits/extensions/diagonalley/views.py
index 2ec75589..08c30428 100644
--- a/lnbits/extensions/diagonalley/views.py
+++ b/lnbits/extensions/diagonalley/views.py
@@ -3,11 +3,12 @@ from http import HTTPStatus
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 starlette.exceptions import HTTPException
-from starlette.responses import HTMLResponse
from .crud import get_diagonalley_products, get_diagonalley_stall
@@ -30,29 +31,33 @@ async def display(request: Request, stall_id):
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Stall does not exist."
)
+
+ stall = stall.dict()
+ del stall["privatekey"]
+
return diagonalley_renderer().TemplateResponse(
"diagonalley/stall.html",
{
"request": request,
- "stall": stall.dict(),
+ "stall": stall,
"products": [product.dict() for product in products]
},
)
-@diagonalley_ext.get("/{market_id}", response_class=HTMLResponse)
-async def display(request: Request, stall_id):
- stalls = await get_diagonalley_stall(stall_id)
- products = await get_diagonalley_products(stall_id)
+# @diagonalley_ext.get("/market/{market_id}", response_class=HTMLResponse)
+# async def display(request: Request, stall_id):
+# stalls = await get_diagonalley_stall(stall_id)
+# products = await get_diagonalley_products(stall_id)
- if not stall:
- raise HTTPException(
- status_code=HTTPStatus.NOT_FOUND, detail="Stall does not exist."
- )
- return diagonalley_renderer().TemplateResponse(
- "diagonalley/stall.html",
- {
- "request": request,
- "stall": stall.dict(),
- "products": [product.dict() for product in products]
- },
- )
+# if not stall:
+# raise HTTPException(
+# status_code=HTTPStatus.NOT_FOUND, detail="Stall does not exist."
+# )
+# return diagonalley_renderer().TemplateResponse(
+# "diagonalley/stall.html",
+# {
+# "request": request,
+# "stall": stall.dict(),
+# "products": [product.dict() for product in products]
+# },
+# )
From 777b9bef1ba4a8b16658a05f6a02d0e9e6bc1060 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 16 Aug 2022 12:08:27 +0100
Subject: [PATCH 076/226] place order flow WIP
---
lnbits/extensions/diagonalley/crud.py | 45 +++++++++++++++------
lnbits/extensions/diagonalley/migrations.py | 9 ++---
lnbits/extensions/diagonalley/models.py | 21 ++++++++--
3 files changed, 53 insertions(+), 22 deletions(-)
diff --git a/lnbits/extensions/diagonalley/crud.py b/lnbits/extensions/diagonalley/crud.py
index fbc1d332..b915c81d 100644
--- a/lnbits/extensions/diagonalley/crud.py
+++ b/lnbits/extensions/diagonalley/crud.py
@@ -13,11 +13,13 @@ from lnbits.settings import WALLET
from . import db
from .models import (
Market,
+ OrderDetail,
Orders,
Products,
Stalls,
Zones,
createOrder,
+ createOrderDetails,
createProduct,
createStalls,
createZones,
@@ -208,38 +210,55 @@ async def delete_diagonalley_stall(stall_id: str) -> None:
###Orders
-async def create_diagonalley_order(data: createOrder) -> Orders:
+async def create_diagonalley_order(wallet_id: str, data: createOrder) -> Orders:
order_id = urlsafe_short_hash()
await db.execute(
f"""
- INSERT INTO diagonalley.orders (id, productid, wallet, product,
- quantity, shippingzone, address, email, invoiceid, paid, shipped)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ INSERT INTO diagonalley.orders (id, wallet, shippingzone, address, email, total, invoiceid, paid, shipped)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
order_id,
- data.productid,
- data.wallet,
- data.product,
- data.quantity,
+ wallet_id,
data.shippingzone,
data.address,
data.email,
+ data.total,
data.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 create_diagonalley_order_details(order_id: str, data: List[createOrderDetails]):
+ for item in data:
+ item_id = urlsafe_short_hash()
+ await db.execute(
+ """
+ INSERT INTO diagonalley.order_details (id, orderid, productid, quantity)
+ VALUES (?, ?, ?, ?)
+ """,
+ (
+ item_id,
+ order_id,
+ item.product_id,
+ item.quantity,
+ ),
+ )
+ order_details = await get_diagonalley_order_details(order_id)
+ return order_details
+
+async def get_diagonalley_order_details(order_id: str) -> List[OrderDetail]:
+ rows = await db.fetchall(
+ f"SELECT * FROM diagonalley.order_details WHERE order_id = ?", (order_id,)
+ )
+
+ return [OrderDetail(**row) for row in rows]
async def get_diagonalley_order(order_id: str) -> Optional[Orders]:
row = await db.fetchone(
diff --git a/lnbits/extensions/diagonalley/migrations.py b/lnbits/extensions/diagonalley/migrations.py
index e994d723..8a65df8a 100644
--- a/lnbits/extensions/diagonalley/migrations.py
+++ b/lnbits/extensions/diagonalley/migrations.py
@@ -56,12 +56,12 @@ async def m001_initial(db):
"""
CREATE TABLE diagonalley.orders (
id {db.serial_primary_key},
- productid TEXT NOT NULL,
- usr TEXT NOT NULL,
- pubkey TEXT NOT NULL,
+ wallet TEXT NOT NULL,
+ pubkey TEXT,
shippingzone INTEGER NOT NULL,
address TEXT NOT NULL,
email TEXT NOT NULL,
+ total INTEGER NOT NULL,
invoiceid TEXT NOT NULL,
paid BOOLEAN NOT NULL,
shipped BOOLEAN NOT NULL,
@@ -81,8 +81,7 @@ async def m001_initial(db):
id TEXT PRIMARY KEY,
orderid INTEGER NOT NULL REFERENCES {db.references_schema}orders (id)
productid TEXT NOT NULL REFERENCES {db.references_schema}products (id),
- quantity INTEGER NOT NULL,
- total INTEGER NOT NULL
+ quantity INTEGER NOT NULL
);
"""
)
diff --git a/lnbits/extensions/diagonalley/models.py b/lnbits/extensions/diagonalley/models.py
index 4fbf04ff..737f2b4b 100644
--- a/lnbits/extensions/diagonalley/models.py
+++ b/lnbits/extensions/diagonalley/models.py
@@ -55,16 +55,29 @@ class Zones(BaseModel):
cost: int
countries: str
+class OrderDetail(BaseModel):
+ id: str
+ order_id: str
+ product_id: str
+ quantity: int
+
+class createOrderDetails(BaseModel):
+ product_id: str = Query(...)
+ quantity: int = Query(..., ge=1)
+
class createOrder(BaseModel):
- productid: str = Query(...)
- stall: str = Query(...)
- product: str = Query(...)
- quantity: int = Query(..., ge=1)
+ wallet: str = Query(...)
+ pubkey: str = Query(None)
shippingzone: int = Query(...)
address: str = Query(...)
email: str = Query(...)
+ total: int = Query(...)
invoiceid: str = Query(...)
+ products: List[createOrderDetails]
+ # stall: str = Query(...)
+ # product: str = Query(...)
+ # quantity: int = Query(..., ge=1)
class Orders(BaseModel):
From 58b046254ff1bfef39f6e8f4395f762247c8def1 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 16 Aug 2022 12:19:31 +0100
Subject: [PATCH 077/226] make format diagonalley
---
lnbits/extensions/diagonalley/__init__.py | 4 +-
lnbits/extensions/diagonalley/crud.py | 19 +-
lnbits/extensions/diagonalley/migrations.py | 1 -
lnbits/extensions/diagonalley/models.py | 4 +
.../templates/diagonalley/_api_docs.html | 42 +-
.../templates/diagonalley/index.html | 1916 +++++++++--------
lnbits/extensions/diagonalley/views.py | 5 +-
lnbits/extensions/diagonalley/views_api.py | 24 +-
8 files changed, 1037 insertions(+), 978 deletions(-)
diff --git a/lnbits/extensions/diagonalley/__init__.py b/lnbits/extensions/diagonalley/__init__.py
index 23c02a5f..ec193e3a 100644
--- a/lnbits/extensions/diagonalley/__init__.py
+++ b/lnbits/extensions/diagonalley/__init__.py
@@ -1,10 +1,11 @@
import asyncio
from fastapi import APIRouter
+from starlette.staticfiles import StaticFiles
+
from lnbits.db import Database
from lnbits.helpers import template_renderer
from lnbits.tasks import catch_everything_and_restart
-from starlette.staticfiles import StaticFiles
db = Database("ext_diagonalley")
@@ -26,6 +27,7 @@ diagonalley_static_files = [
# )
# else:
+
def diagonalley_renderer():
return template_renderer(["lnbits/extensions/diagonalley/templates"])
# return template_renderer(["lnbits/extensions/diagonalley/templates"])
diff --git a/lnbits/extensions/diagonalley/crud.py b/lnbits/extensions/diagonalley/crud.py
index b915c81d..8016fd07 100644
--- a/lnbits/extensions/diagonalley/crud.py
+++ b/lnbits/extensions/diagonalley/crud.py
@@ -230,12 +230,15 @@ async def create_diagonalley_order(wallet_id: str, data: createOrder) -> Orders:
False,
),
)
-
+
link = await get_diagonalley_order(order_id)
assert link, "Newly created link couldn't be retrieved"
return link
-async def create_diagonalley_order_details(order_id: str, data: List[createOrderDetails]):
+
+async def create_diagonalley_order_details(
+ order_id: str, data: List[createOrderDetails]
+):
for item in data:
item_id = urlsafe_short_hash()
await db.execute(
@@ -253,6 +256,7 @@ async def create_diagonalley_order_details(order_id: str, data: List[createOrder
order_details = await get_diagonalley_order_details(order_id)
return order_details
+
async def get_diagonalley_order_details(order_id: str) -> List[OrderDetail]:
rows = await db.fetchall(
f"SELECT * FROM diagonalley.order_details WHERE order_id = ?", (order_id,)
@@ -260,6 +264,7 @@ async def get_diagonalley_order_details(order_id: str) -> List[OrderDetail]:
return [OrderDetail(**row) for row in rows]
+
async def get_diagonalley_order(order_id: str) -> Optional[Orders]:
row = await db.fetchone(
"SELECT * FROM diagonalley.orders WHERE id = ?", (order_id,)
@@ -282,18 +287,18 @@ async def get_diagonalley_orders(wallet_ids: Union[str, List[str]]) -> List[Orde
async def delete_diagonalley_order(order_id: str) -> None:
await db.execute("DELETE FROM diagonalley.orders WHERE id = ?", (order_id,))
+
### Market/Marketplace
+
async def get_diagonalley_markets(user: str) -> List[Market]:
- rows = await db.fetchall(
- 'SELECT * FROM diagonalley.markets WHERE usr = ?', (user,)
- )
+ rows = await db.fetchall("SELECT * FROM diagonalley.markets WHERE usr = ?", (user,))
return [Market(**row) for row in rows]
async def get_diagonalley_market(market_id: str) -> Optional[Market]:
row = await db.fetchone(
- 'SELECT * FROM diagonalley.markets WHERE id = ?', (market_id,)
+ "SELECT * FROM diagonalley.markets WHERE id = ?", (market_id,)
)
Market(**row) if row else None
@@ -303,5 +308,3 @@ async def get_diagonalley_market_stalls(market_id: str):
"SELECT * FROM diagonalley.market_stalls WHERE marketid = ?", (market_id,)
)
return [Stalls(**row) for row in rows]
-
-
diff --git a/lnbits/extensions/diagonalley/migrations.py b/lnbits/extensions/diagonalley/migrations.py
index 8a65df8a..a7380b65 100644
--- a/lnbits/extensions/diagonalley/migrations.py
+++ b/lnbits/extensions/diagonalley/migrations.py
@@ -111,4 +111,3 @@ async def m001_initial(db):
);
"""
)
-
diff --git a/lnbits/extensions/diagonalley/models.py b/lnbits/extensions/diagonalley/models.py
index 737f2b4b..f07e9c80 100644
--- a/lnbits/extensions/diagonalley/models.py
+++ b/lnbits/extensions/diagonalley/models.py
@@ -55,12 +55,14 @@ class Zones(BaseModel):
cost: int
countries: str
+
class OrderDetail(BaseModel):
id: str
order_id: str
product_id: str
quantity: int
+
class createOrderDetails(BaseModel):
product_id: str = Query(...)
quantity: int = Query(..., ge=1)
@@ -95,11 +97,13 @@ class Orders(BaseModel):
shipped: bool
time: int
+
class CreateMarket(BaseModel):
usr: str = Query(...)
name: str = Query(None)
stalls: List[str] = Query(...)
+
class Market(BaseModel):
id: str
usr: str
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/_api_docs.html b/lnbits/extensions/diagonalley/templates/diagonalley/_api_docs.html
index 530c89e8..5418deba 100644
--- a/lnbits/extensions/diagonalley/templates/diagonalley/_api_docs.html
+++ b/lnbits/extensions/diagonalley/templates/diagonalley/_api_docs.html
@@ -9,26 +9,28 @@
Diagon Alley: Decentralised Market-Stalls
-
- Each Stall has its own keys!
-
- Create Shipping Zones you're willing to ship to
- Create a Stall to list yiur products on
- Create products to put on the Stall
- List stalls on a simple frontend shop page, or point at Nostr shop client key
-
- 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
-
+ Each Stall has its own keys!
+
+
+ Create Shipping Zones you're willing to ship to
+ Create a Stall to list yiur products on
+ Create products to put on the Stall
+
+ List stalls on a simple frontend shop page, or point at Nostr shop
+ client key
+
+
+ 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
+
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/index.html b/lnbits/extensions/diagonalley/templates/diagonalley/index.html
index 893830a9..5482cd3f 100644
--- a/lnbits/extensions/diagonalley/templates/diagonalley/index.html
+++ b/lnbits/extensions/diagonalley/templates/diagonalley/index.html
@@ -28,7 +28,7 @@
label="Description"
>
-
-
-
-
+
+
+
Create Product
-
Cancel
@@ -162,7 +167,12 @@
>Create Shipping Zone
- Cancel
@@ -174,10 +184,10 @@
+ label="Activate shop"
+ color="primary"
+ v-model="marketDialog.data.activate"
+ >
Launch
-
Cancel
@@ -284,7 +299,9 @@
v-model.trim="stallDialog.data.nostrShops"
label="Nostr shop public keys (seperate by comma)"
> -->
- Nostr support coming soon!
+
+ Nostr support coming soon!
+
Create Store
- Cancel
@@ -313,33 +335,50 @@
-
- + Product List a product
- + Product List a product
+ Shipping Zone Create a shipping zone
- + Store
Create a stall to list products on
- + Store
Create a store to list products on
Launch frontend shop (not Nostr)
- Makes a simple frontend shop for your stalls
+ Makes a simple frontend shop for your stalls
-
-
-
+
+
+
@@ -403,7 +442,8 @@
-
+
+
@@ -482,8 +522,9 @@
-
-
+
+
+
@@ -555,8 +596,9 @@
-
-
+
+
+
@@ -632,14 +674,18 @@
Messages
-
+
-
+
-
+
@@ -648,14 +694,11 @@
:text="[message[1]]"
sent
>
-
-
+
+
-
+
@@ -665,934 +708,935 @@
-
-
+
{% endblock %} {% block scripts %} {{ window_vars(user) }}
{% endblock %}
-
diff --git a/lnbits/extensions/diagonalley/views.py b/lnbits/extensions/diagonalley/views.py
index 08c30428..30dbebe7 100644
--- a/lnbits/extensions/diagonalley/views.py
+++ b/lnbits/extensions/diagonalley/views.py
@@ -34,16 +34,17 @@ async def display(request: Request, stall_id):
stall = stall.dict()
del stall["privatekey"]
-
+
return diagonalley_renderer().TemplateResponse(
"diagonalley/stall.html",
{
"request": request,
"stall": stall,
- "products": [product.dict() for product in products]
+ "products": [product.dict() for product in products],
},
)
+
# @diagonalley_ext.get("/market/{market_id}", response_class=HTMLResponse)
# async def display(request: Request, stall_id):
# stalls = await get_diagonalley_stall(stall_id)
diff --git a/lnbits/extensions/diagonalley/views_api.py b/lnbits/extensions/diagonalley/views_api.py
index b2fb5a47..cedd1d42 100644
--- a/lnbits/extensions/diagonalley/views_api.py
+++ b/lnbits/extensions/diagonalley/views_api.py
@@ -5,6 +5,8 @@ from uuid import uuid4
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.core.services import create_invoice
from lnbits.decorators import (
@@ -13,7 +15,6 @@ from lnbits.decorators import (
require_admin_key,
require_invoice_key,
)
-from starlette.exceptions import HTTPException
from . import db, diagonalley_ext
from .crud import (
@@ -94,22 +95,22 @@ 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: createProduct, product_id = None, wallet: WalletTypeInfo = Depends(get_key_type)
+ data: createProduct, product_id=None, wallet: WalletTypeInfo = Depends(get_key_type)
):
if product_id:
- product = await get_diagonalley_product(product_id)
+ product = await get_diagonalley_product(product_id)
if not product:
return {"message": "Withdraw product does not exist."}
-
- stall = await get_diagonalley_stall(stall_id = product.stall)
+
+ stall = await get_diagonalley_stall(stall_id=product.stall)
if stall.wallet != wallet.wallet.id:
return {"message": "Not your withdraw product."}
product = await update_diagonalley_product(product_id, **data.dict())
else:
product = await create_diagonalley_product(data=data)
-
+
return product.dict()
@@ -392,15 +393,18 @@ async def api_diagonalley_stall_order(
# MARKETS
##
+
@diagonalley_ext.get("/api/v1/markets")
-async def api_diagonalley_orders(
- wallet: WalletTypeInfo = Depends(get_key_type)
-):
+async def api_diagonalley_orders(wallet: WalletTypeInfo = Depends(get_key_type)):
try:
- return [market.dict() for market in await get_diagonalley_markets(wallet.wallet.user)]
+ return [
+ market.dict()
+ for market in await get_diagonalley_markets(wallet.wallet.user)
+ ]
except:
return {"message": "We could not retrieve the markets."}
+
@diagonalley_ext.post("/api/v1/markets")
@diagonalley_ext.put("/api/v1/markets/{market_id}")
async def api_diagonalley_stall_create(
From 4cd86d0dafb8b989ccdd601f9891a14e1991a433 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Thu, 18 Aug 2022 10:49:04 +0100
Subject: [PATCH 078/226] create order
---
lnbits/extensions/diagonalley/crud.py | 30 ++++----
lnbits/extensions/diagonalley/migrations.py | 20 ++---
lnbits/extensions/diagonalley/models.py | 8 +-
.../templates/diagonalley/stall.html | 75 ++++++++++++++-----
lnbits/extensions/diagonalley/views.py | 16 +++-
lnbits/extensions/diagonalley/views_api.py | 25 ++++++-
6 files changed, 124 insertions(+), 50 deletions(-)
diff --git a/lnbits/extensions/diagonalley/crud.py b/lnbits/extensions/diagonalley/crud.py
index 8016fd07..798705bf 100644
--- a/lnbits/extensions/diagonalley/crud.py
+++ b/lnbits/extensions/diagonalley/crud.py
@@ -210,30 +210,34 @@ async def delete_diagonalley_stall(stall_id: str) -> None:
###Orders
-async def create_diagonalley_order(wallet_id: str, data: createOrder) -> Orders:
+async def create_diagonalley_order(data: createOrder, invoiceid: str) -> Orders:
+ returning = "" if db.type == SQLITE else "RETURNING ID"
+ method = db.execute if db.type == SQLITE else db.fetchone
- order_id = urlsafe_short_hash()
- await db.execute(
+ result = await (method)(
f"""
- INSERT INTO diagonalley.orders (id, wallet, shippingzone, address, email, total, invoiceid, paid, shipped)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
+ INSERT INTO diagonalley.orders (wallet, shippingzone, address, email, total, invoiceid, paid, shipped)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
+ {returning}
""",
(
- order_id,
- wallet_id,
+ data.wallet,
data.shippingzone,
data.address,
data.email,
data.total,
- data.invoiceid,
+ invoiceid,
False,
False,
),
)
-
- link = await get_diagonalley_order(order_id)
- assert link, "Newly created link couldn't be retrieved"
- return link
+ if db.type == SQLITE:
+ return result._result_proxy.lastrowid
+ else:
+ return result[0]
+ # link = await get_diagonalley_order(link.id)
+ # assert link, "Newly created link couldn't be retrieved"
+ # return link
async def create_diagonalley_order_details(
@@ -243,7 +247,7 @@ async def create_diagonalley_order_details(
item_id = urlsafe_short_hash()
await db.execute(
"""
- INSERT INTO diagonalley.order_details (id, orderid, productid, quantity)
+ INSERT INTO diagonalley.order_details (id, order_id, product_id, quantity)
VALUES (?, ?, ?, ?)
""",
(
diff --git a/lnbits/extensions/diagonalley/migrations.py b/lnbits/extensions/diagonalley/migrations.py
index a7380b65..6e1510a7 100644
--- a/lnbits/extensions/diagonalley/migrations.py
+++ b/lnbits/extensions/diagonalley/migrations.py
@@ -11,7 +11,8 @@ async def m001_initial(db):
publickey TEXT,
privatekey TEXT,
relays TEXT,
- shippingzones TEXT NOT NULL
+ shippingzones TEXT NOT NULL,
+ rating INTEGER DEFAULT 0
);
"""
)
@@ -20,7 +21,7 @@ async def m001_initial(db):
Initial products table.
"""
await db.execute(
- """
+ f"""
CREATE TABLE diagonalley.products (
id TEXT PRIMARY KEY,
stall TEXT NOT NULL REFERENCES {db.references_schema}stalls (id),
@@ -30,7 +31,7 @@ async def m001_initial(db):
image TEXT,
price INTEGER NOT NULL,
quantity INTEGER NOT NULL,
- rating INTEGER NOT NULL
+ rating INTEGER DEFAULT 0
);
"""
)
@@ -53,12 +54,13 @@ async def m001_initial(db):
Initial orders table.
"""
await db.execute(
- """
+ f"""
CREATE TABLE diagonalley.orders (
id {db.serial_primary_key},
wallet TEXT NOT NULL,
+ username TEXT,
pubkey TEXT,
- shippingzone INTEGER NOT NULL,
+ shippingzone TEXT NOT NULL,
address TEXT NOT NULL,
email TEXT NOT NULL,
total INTEGER NOT NULL,
@@ -76,11 +78,11 @@ async def m001_initial(db):
Initial order details table.
"""
await db.execute(
- """
+ f"""
CREATE TABLE diagonalley.order_details (
id TEXT PRIMARY KEY,
- orderid INTEGER NOT NULL REFERENCES {db.references_schema}orders (id)
- productid TEXT NOT NULL REFERENCES {db.references_schema}products (id),
+ order_id INTEGER NOT NULL REFERENCES {db.references_schema}orders (id),
+ product_id TEXT NOT NULL REFERENCES {db.references_schema}products (id),
quantity INTEGER NOT NULL
);
"""
@@ -103,7 +105,7 @@ async def m001_initial(db):
Initial market stalls table.
"""
await db.execute(
- """
+ f"""
CREATE TABLE diagonalley.market_stalls (
id TEXT PRIMARY KEY,
marketid TEXT NOT NULL REFERENCES {db.references_schema}markets (id),
diff --git a/lnbits/extensions/diagonalley/models.py b/lnbits/extensions/diagonalley/models.py
index f07e9c80..82115be9 100644
--- a/lnbits/extensions/diagonalley/models.py
+++ b/lnbits/extensions/diagonalley/models.py
@@ -71,15 +71,11 @@ class createOrderDetails(BaseModel):
class createOrder(BaseModel):
wallet: str = Query(...)
pubkey: str = Query(None)
- shippingzone: int = Query(...)
+ shippingzone: str = Query(...)
address: str = Query(...)
email: str = Query(...)
total: int = Query(...)
- invoiceid: str = Query(...)
products: List[createOrderDetails]
- # stall: str = Query(...)
- # product: str = Query(...)
- # quantity: int = Query(..., ge=1)
class Orders(BaseModel):
@@ -89,7 +85,7 @@ class Orders(BaseModel):
pubkey: str
product: str
quantity: int
- shippingzone: int
+ shippingzone: str
address: str
email: str
invoiceid: str
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/stall.html b/lnbits/extensions/diagonalley/templates/diagonalley/stall.html
index 5638bc3d..28f6ad22 100644
--- a/lnbits/extensions/diagonalley/templates/diagonalley/stall.html
+++ b/lnbits/extensions/diagonalley/templates/diagonalley/stall.html
@@ -80,7 +80,7 @@
alt="Product Image"
loading="lazy"
spinner-color="white"
- fit="cover"
+ fit="contain"
height="300px"
>
@@ -166,15 +166,15 @@
-
+ > -->
+ Select the shipping zone:
+
+
+
+
+ {% raw %} Total: {{ finalCost }} {% endraw %}
+
Checkout
-
z.value == this.checkoutDialog.data.shippingzone
+ )
+
+ return this.cart.total + zoneCost.cost
}
},
methods: {
+ resetCart() {
+ this.cart = {
+ total: 0,
+ size: 0,
+ products: new Map()
+ }
+ },
addToCart(item) {
let prod = this.cart.products
if (prod.has(item.id)) {
@@ -266,17 +294,28 @@
this.cartMenu = Array.from(this.cart.products, item => {
return {id: item[0], ...item[1]}
})
- console.log(this.cartMenu)
+ console.log(this.cartMenu, this.cart)
},
placeOrder() {
- // 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(...)
+ let dialog = this.checkoutDialog.data
+ let data = {
+ ...this.checkoutDialog.data,
+ wallet: this.stall.wallet,
+ total: this.finalCost,
+ products: Array.from(this.cart.products, p => {
+ return {product_id: p[0], quantity: p[1].quantity}
+ })
+ }
+
+ LNbits.api
+ .request('POST', '/diagonalley/api/v1/orders', null, data)
+ .then(res => {
+ this.checkoutDialog = {show: false, data: {}}
+ this.resetCart()
+ console.log(res.data)
+ })
+ .catch(error => LNbits.utils.notifyApiError(error))
+
return
}
},
@@ -287,7 +326,7 @@
//let stall_ids = new Set()
//this.products.map(p => stall_ids.add(p.stall))
- console.log(this.stall)
+ console.log(this.stall, this.products)
}
})
diff --git a/lnbits/extensions/diagonalley/views.py b/lnbits/extensions/diagonalley/views.py
index 30dbebe7..f4446482 100644
--- a/lnbits/extensions/diagonalley/views.py
+++ b/lnbits/extensions/diagonalley/views.py
@@ -3,6 +3,7 @@ from http import HTTPStatus
from fastapi import Request
from fastapi.params import Depends
from fastapi.templating import Jinja2Templates
+from loguru import logger
from starlette.exceptions import HTTPException
from starlette.responses import HTMLResponse
@@ -10,7 +11,12 @@ 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, get_diagonalley_stall
+from .crud import (
+ get_diagonalley_products,
+ get_diagonalley_stall,
+ get_diagonalley_zone,
+ get_diagonalley_zones,
+)
templates = Jinja2Templates(directory="templates")
@@ -26,6 +32,13 @@ async def index(request: Request, user: User = Depends(check_user_exists)):
async def display(request: Request, stall_id):
stall = await get_diagonalley_stall(stall_id)
products = await get_diagonalley_products(stall_id)
+ zones = []
+ for id in stall.shippingzones.split(","):
+ z = await get_diagonalley_zone(id)
+ z = z.dict()
+ zones.append({"label": z["countries"], "cost": z["cost"], "value": z["id"]})
+
+ logger.debug(f"ZONES {zones}")
if not stall:
raise HTTPException(
@@ -34,6 +47,7 @@ async def display(request: Request, stall_id):
stall = stall.dict()
del stall["privatekey"]
+ stall["zones"] = zones
return diagonalley_renderer().TemplateResponse(
"diagonalley/stall.html",
diff --git a/lnbits/extensions/diagonalley/views_api.py b/lnbits/extensions/diagonalley/views_api.py
index cedd1d42..fd474f4a 100644
--- a/lnbits/extensions/diagonalley/views_api.py
+++ b/lnbits/extensions/diagonalley/views_api.py
@@ -5,6 +5,7 @@ from uuid import uuid4
from fastapi import Request
from fastapi.param_functions import Query
from fastapi.params import Depends
+from loguru import logger
from starlette.exceptions import HTTPException
from lnbits.core.crud import get_user
@@ -16,9 +17,11 @@ from lnbits.decorators import (
require_invoice_key,
)
+from ...helpers import urlsafe_short_hash
from . import db, diagonalley_ext
from .crud import (
create_diagonalley_order,
+ create_diagonalley_order_details,
create_diagonalley_product,
create_diagonalley_stall,
create_diagonalley_zone,
@@ -254,10 +257,26 @@ async def api_diagonalley_orders(
@diagonalley_ext.post("/api/v1/orders")
async def api_diagonalley_order_create(
- data: createOrder, wallet: WalletTypeInfo = Depends(get_key_type)
+ data: createOrder
):
- order = await create_diagonalley_order(wallet_id=wallet.wallet.id, data=data)
- return order.dict()
+ ref = urlsafe_short_hash()
+
+ payment_hash, payment_request = await create_invoice(
+ wallet_id=data.wallet,
+ amount=data.total,
+ memo=f"New order on Diagon alley",
+ extra={
+ "tag": "diagonalley",
+ "reference": ref,
+ }
+ )
+ order_id = await create_diagonalley_order(invoiceid=payment_hash, data=data)
+ logger.debug(f"ORDER ID {order_id}")
+ logger.debug(f"PRODUCTS {data.products}")
+ await create_diagonalley_order_details(order_id=order_id, data=data.products)
+ return {"payment_hash": payment_hash, "payment_request": payment_request, "order_reference": ref}
+ # order = await create_diagonalley_order(wallet_id=wallet.wallet.id, data=data)
+ # return order.dict()
@diagonalley_ext.delete("/api/v1/orders/{order_id}")
From d6fe562067e7b827531d1a824b19d7fcf97a71e1 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Thu, 18 Aug 2022 16:18:07 +0100
Subject: [PATCH 079/226] have orders show on user page and invoice init
---
lnbits/extensions/diagonalley/models.py | 9 +-
.../templates/diagonalley/index.html | 99 +++++++++++++++----
.../templates/diagonalley/stall.html | 85 +++++++++++++++-
lnbits/extensions/diagonalley/views.py | 3 +
lnbits/extensions/diagonalley/views_api.py | 29 ++----
5 files changed, 178 insertions(+), 47 deletions(-)
diff --git a/lnbits/extensions/diagonalley/models.py b/lnbits/extensions/diagonalley/models.py
index 82115be9..e6516037 100644
--- a/lnbits/extensions/diagonalley/models.py
+++ b/lnbits/extensions/diagonalley/models.py
@@ -80,14 +80,13 @@ class createOrder(BaseModel):
class Orders(BaseModel):
id: str
- productid: str
- stall: str
- pubkey: str
- product: str
- quantity: int
+ wallet: str
+ username: Optional[str]
+ pubkey: Optional[str]
shippingzone: str
address: str
email: str
+ total: int
invoiceid: str
paid: bool
shipped: bool
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/index.html b/lnbits/extensions/diagonalley/templates/diagonalley/index.html
index 5482cd3f..516544ea 100644
--- a/lnbits/extensions/diagonalley/templates/diagonalley/index.html
+++ b/lnbits/extensions/diagonalley/templates/diagonalley/index.html
@@ -401,6 +401,7 @@
{% raw %}
+
{{ col.label }}
@@ -409,6 +410,16 @@
+
+
+
{{ col.value }}
@@ -436,6 +447,61 @@
>
+
+
+
+
+
+ Order Details
+
+
+
+ Products
+ {{ products.length && (_.findWhere(products, {id:
+ col.product_id})).product }}
+ Quantity: {{ col.quantity }}
+
+
+
+
+
+ Shipping to
+ {{ props.row.address }}
+
+
+
+
+
+ User info
+ {{ props.row.username }}
+ {{ props.row.email }}
+ {{ props.row.pubkey }}
+
+
+
+
+ Total
+ {{ props.row.total }}
+
+
+
+
+
+
+
+
{% endraw %}
@@ -724,7 +790,6 @@
}
const mapProducts = obj => {
obj._data = _.clone(obj)
- console.log(obj)
return obj
}
const mapZone = obj => {
@@ -733,6 +798,10 @@
}
const mapOrders = obj => {
obj._data = _.clone(obj)
+ obj.time = Quasar.utils.date.formatDate(
+ new Date(obj.time * 1000),
+ 'YYYY-MM-DD HH:mm'
+ )
return obj
}
const mapKeys = obj => {
@@ -822,7 +891,7 @@
label: '',
ordersTable: {
columns: [
- {
+ /*{
name: 'product',
align: 'left',
label: 'Product',
@@ -833,12 +902,18 @@
align: 'left',
label: 'Quantity',
field: 'quantity'
+ },*/
+ {
+ name: 'id',
+ align: 'left',
+ label: 'ID',
+ field: 'id'
},
{
- name: 'address',
+ name: 'time',
align: 'left',
- label: 'Address',
- field: 'address'
+ label: 'Date',
+ field: 'time'
},
{
name: 'invoiceid',
@@ -1037,7 +1112,6 @@
////////////////STALLS//////////////////
////////////////////////////////////////
getStalls: function () {
- console.log(this.g.user)
var self = this
LNbits.api
.request(
@@ -1048,7 +1122,6 @@
.then(function (response) {
if (response.data) {
self.stalls = response.data.map(mapStalls)
- console.log(self.stalls)
}
})
.catch(function (error) {
@@ -1072,7 +1145,6 @@
this.stallDialog.data.shippingzones = shippingzones //this.stallDialog.data.shippingzones.split(",")
- console.log(this.stallDialog.data)
//let zones = this.zoneOptions
// .filter(z => z.id == )
this.stallDialog.show = true
@@ -1096,7 +1168,6 @@
}
},
updateStall: function (data) {
- console.log(data)
var self = this
LNbits.api
.request(
@@ -1179,7 +1250,6 @@
self.g.user.wallets[0].inkey
)
.then(function (response) {
- console.log('RESP DATA', response.data)
if (response.data) {
self.products = response.data.map(mapProducts)
}
@@ -1271,7 +1341,6 @@
let self = this
const walletId = _.findWhere(this.stalls, {id: data.stall}).wallet
- console.log('DATA', walletId, data)
LNbits.api
.request(
'POST',
@@ -1280,7 +1349,6 @@
data
)
.then(response => {
- console.log(response)
self.products.push(mapProducts(response.data))
self.resetDialog('productDialog')
})
@@ -1328,7 +1396,6 @@
)
.then(function (response) {
if (response.data) {
- console.log(response)
self.zones = response.data.map(mapZone)
}
})
@@ -1360,7 +1427,6 @@
}
},
updateZone: function (data) {
- console.log(data)
var self = this
LNbits.api
.request(
@@ -1370,7 +1436,6 @@
data
)
.then(function (response) {
- console.log(response)
self.zones = _.reject(self.zones, function (obj) {
return obj.id == data.id
})
@@ -1492,7 +1557,6 @@
})
},
createShop(data) {
- console.log('data')
LNbits.api
.request(
'POST',
@@ -1551,6 +1615,7 @@
.then(function (response) {
if (response.data) {
self.orders = response.data.map(mapOrders)
+ console.log(self.orders)
}
})
.catch(function (error) {
@@ -1629,7 +1694,7 @@
this.getProducts()
this.getZones()
this.getOrders()
- this.getMarkets()
+ //this.getMarkets() # NOT YET IMPLEMENTED
this.customerKeys = [
'cb4c0164fe03fcdadcbfb4f76611c71620790944c24f21a1cd119395cdedfe1b',
'a9c17358a6dc4ceb3bb4d883eb87967a66b3453a0f3199f0b1c8eef8070c6a07'
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/stall.html b/lnbits/extensions/diagonalley/templates/diagonalley/stall.html
index 28f6ad22..b33feb2a 100644
--- a/lnbits/extensions/diagonalley/templates/diagonalley/stall.html
+++ b/lnbits/extensions/diagonalley/templates/diagonalley/stall.html
@@ -216,6 +216,31 @@
+
+
+
+
+
+
+
+
+
+ Copy Invoice
+
+
{% endblock %} {% block scripts %}
-{% endblock %}
diff --git a/lnbits/extensions/nostradmin/views.py b/lnbits/extensions/nostradmin/views.py
deleted file mode 100644
index 705feba7..00000000
--- a/lnbits/extensions/nostradmin/views.py
+++ /dev/null
@@ -1,110 +0,0 @@
-from http import HTTPStatus
-import asyncio
-from fastapi import Request
-from fastapi.param_functions import Query
-from fastapi.params import Depends
-from fastapi.templating import Jinja2Templates
-from starlette.exceptions import HTTPException
-from starlette.responses import HTMLResponse
-from . import nostradmin_ext, nostr_renderer
-# FastAPI good for incoming
-from fastapi import Request, WebSocket, WebSocketDisconnect
-# Websockets needed for outgoing
-import websockets
-
-from lnbits.core.crud import update_payment_status
-from lnbits.core.models import User
-from lnbits.core.views.api import api_payment
-from lnbits.decorators import check_user_exists
-
-from .crud import get_nostrkeys, get_nostrrelay
-from .relay_manager import RelayManager, Relay
-
-templates = Jinja2Templates(directory="templates")
-
-nostradmin = True
-
-@nostradmin_ext.get("/", response_class=HTMLResponse)
-async def index(request: Request, user: User = Depends(check_user_exists)):
- return nostr_renderer().TemplateResponse(
- "nostradmin/index.html", {"request": request, "user": user.dict()}
- )
-
-#####################################################################
-#################### NOSTR WEBSOCKET THREAD #########################
-##### THE QUEUE LOOP THREAD THING THAT LISTENS TO BUNCH OF ##########
-### WEBSOCKET CONNECTIONS, STORING DATA IN DB/PUSHING TO FRONTEND ###
-################### VIA updater() FUNCTION ##########################
-#####################################################################
-
-websocket_queue = asyncio.Queue(1000)
-
-
-mgr: RelayManager = RelayManager(enable_ws_debugger=False)
-
-# listen for events coming from relays
-
-
-async def connectToNostr():
- while True:
- e = await mgr.msg_channel.get()
- print(e)
-connectToNostr
-#####################################################################
-################### LNBITS WEBSOCKET ROUTES #########################
-#### HERE IS WHERE LNBITS FRONTEND CAN RECEIVE AND SEND MESSAGES ####
-#####################################################################
-
-class ConnectionManager:
- def __init__(self):
- self.active_connections: List[WebSocket] = []
-
- async def connect(self, websocket: WebSocket, nostr_id: str):
- await websocket.accept()
- websocket.id = nostr_id
- self.active_connections.append(websocket)
-
- def disconnect(self, websocket: WebSocket):
- self.active_connections.remove(websocket)
-
- async def send_personal_message(self, message: str, nostr_id: str):
- for connection in self.active_connections:
- if connection.id == nostr_id:
- await connection.send_text(message)
-
- async def broadcast(self, message: str):
- for connection in self.active_connections:
- await connection.send_text(message)
-
-
-manager = ConnectionManager()
-
-
-@nostradmin_ext.websocket("/nostradmin/ws/relayevents/{nostr_id}", name="nostr_id.websocket_by_id")
-async def websocket_endpoint(websocket: WebSocket, nostr_id: str):
- await manager.connect(websocket, nostr_id)
- try:
- while True:
- data = await websocket.receive_text()
- except WebSocketDisconnect:
- manager.disconnect(websocket)
-
-
-async def updater(nostr_id, message):
- copilot = await get_copilot(nostr_id)
- if not copilot:
- return
- await manager.send_personal_message(f"{message}", nostr_id)
-
-
-async def relay_check(relay: str):
- async with websockets.connect(relay) as websocket:
- if str(websocket.state) == "State.OPEN":
- r = Relay(url=relay, read=True, write=True, active=True)
- try:
- await mgr.add_relay(r)
- except:
- None
- return True
- else:
- return False
\ No newline at end of file
diff --git a/lnbits/extensions/nostradmin/views_api.py b/lnbits/extensions/nostradmin/views_api.py
deleted file mode 100644
index 4ff89704..00000000
--- a/lnbits/extensions/nostradmin/views_api.py
+++ /dev/null
@@ -1,63 +0,0 @@
-from http import HTTPStatus
-import asyncio
-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 WalletTypeInfo, get_key_type, require_admin_key
-from lnbits.utils.exchange_rates import currencies
-
-from lnbits.settings import LNBITS_ADMIN_USERS
-from . import nostradmin_ext
-from .crud import (
- create_nostrkeys,
- get_nostrkeys,
- create_nostrnotes,
- get_nostrnotes,
- create_nostrrelays,
- get_nostrrelays,
- get_nostrrelaylist,
- update_nostrrelaysetlist,
- create_nostrconnections,
- get_nostrconnections,
-)
-from .models import nostrKeys, nostrCreateRelays, nostrRelaySetList
-from .views import relay_check
-
-@nostradmin_ext.get("/api/v1/relays")
-async def api_relays_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)):
- relays = await get_nostrrelays()
- if not relays:
- await create_nostrrelays(nostrCreateRelays(relay="wss://relayer.fiatjaf.com"))
- await create_nostrrelays(
- nostrCreateRelays(relay="wss://nostr-pub.wellorder.net")
- )
- relays = await get_nostrrelays()
- if not relays:
- raise HTTPException(
- status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
- )
- else:
- for relay in relays:
- relay.status = await relay_check(relay.relay)
- return relays
-
-
-
-@nostradmin_ext.get("/api/v1/relaylist")
-async def api_relaylist(wallet: WalletTypeInfo = Depends(get_key_type)):
- if wallet.wallet.user not in LNBITS_ADMIN_USERS:
- raise HTTPException(
- status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
- )
- return await get_nostrrelaylist()
-
-@nostradmin_ext.post("/api/v1/setlist")
-async def api_relayssetlist(data: nostrRelaySetList, wallet: WalletTypeInfo = Depends(get_key_type)):
- if wallet.wallet.user not in LNBITS_ADMIN_USERS:
- raise HTTPException(
- status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
- )
- return await update_nostrrelaysetlist(data)
\ No newline at end of file
From a5c6b1135c77930315a211a822ee2638a53217d3 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Fri, 19 Aug 2022 11:19:45 +0100
Subject: [PATCH 082/226] order paying works
---
lnbits/extensions/diagonalley/crud.py | 15 ++
lnbits/extensions/diagonalley/tasks.py | 19 +++
.../templates/diagonalley/stall.html | 146 +++++++++---------
lnbits/extensions/diagonalley/views.py | 4 -
lnbits/extensions/diagonalley/views_api.py | 17 ++
5 files changed, 123 insertions(+), 78 deletions(-)
diff --git a/lnbits/extensions/diagonalley/crud.py b/lnbits/extensions/diagonalley/crud.py
index 798705bf..0a825ca3 100644
--- a/lnbits/extensions/diagonalley/crud.py
+++ b/lnbits/extensions/diagonalley/crud.py
@@ -275,6 +275,21 @@ async def get_diagonalley_order(order_id: str) -> Optional[Orders]:
)
return Orders(**row) if row else None
+async def get_diagonalley_order_invoiceid(invoice_id: str) -> Optional[Orders]:
+ row = await db.fetchone(
+ "SELECT * FROM diagonalley.orders WHERE invoiceid = ?", (invoice_id,)
+ )
+ return Orders(**row) if row else None
+
+async def set_diagonalley_order_paid(payment_hash: str) -> Orders:
+ await db.execute(
+ """
+ UPDATE diagonalley.orders
+ SET paid = true
+ WHERE invoiceid = ?
+ """,
+ (payment_hash,),
+ )
async def get_diagonalley_orders(wallet_ids: Union[str, List[str]]) -> List[Orders]:
if isinstance(wallet_ids, str):
diff --git a/lnbits/extensions/diagonalley/tasks.py b/lnbits/extensions/diagonalley/tasks.py
index bcbb7025..6b64203e 100644
--- a/lnbits/extensions/diagonalley/tasks.py
+++ b/lnbits/extensions/diagonalley/tasks.py
@@ -1,8 +1,12 @@
import asyncio
+from loguru import logger
+
from lnbits.core.models import Payment
from lnbits.tasks import register_invoice_listener
+from .crud import get_diagonalley_order_invoiceid, set_diagonalley_order_paid
+
async def wait_for_paid_invoices():
invoice_queue = asyncio.Queue()
@@ -14,6 +18,21 @@ async def wait_for_paid_invoices():
async def on_invoice_paid(payment: Payment) -> None:
+ if payment.extra.get("tag") != "diagonalley":
+ return
+
+ order = await get_diagonalley_order_invoiceid(payment.payment_hash)
+ if not order:
+ logger.error("this should never happen", payment)
+ return
+
+ # set order as paid
+ await set_diagonalley_order_paid(payment.payment_hash)
+
+ # deduct items sold from stock
+ # TODO
+
+
"""
if "lnticket" != payment.extra.get("tag"):
# not a lnticket invoice
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/stall.html b/lnbits/extensions/diagonalley/templates/diagonalley/stall.html
index b33feb2a..f6333cfe 100644
--- a/lnbits/extensions/diagonalley/templates/diagonalley/stall.html
+++ b/lnbits/extensions/diagonalley/templates/diagonalley/stall.html
@@ -220,25 +220,41 @@
-
-
-
-
-
-
-
- Copy Invoice
+
+
+
+
+
+ Copy invoice
+ Close
+
@@ -252,9 +268,6 @@
return {
stall: null,
products: [],
- wallet: {
- inkey: null
- },
searchText: null,
cart: {
total: 0,
@@ -271,8 +284,7 @@
payment_request: null
},
show: false
- },
- cancelListener: () => {}
+ }
}
},
computed: {
@@ -297,6 +309,10 @@
}
},
methods: {
+ closeQrCodeDialog() {
+ this.qrCodeDialog.dismissMsg()
+ this.qrCodeDialog.show = false
+ },
resetCart() {
this.cart = {
total: 0,
@@ -341,16 +357,10 @@
return {product_id: p[0], quantity: p[1].quantity}
})
}
-
LNbits.api
- .request(
- 'POST',
- '/diagonalley/api/v1/orders',
- this.wallet.inkey,
- data
- )
+ .request('POST', '/diagonalley/api/v1/orders', null, data)
.then(res => {
- this.checkoutDialog = {show: false}
+ this.checkoutDialog = {show: false, data: {}}
return res.data
})
@@ -358,48 +368,46 @@
this.qrCodeDialog.data = data
this.qrCodeDialog.show = true
- qrCodeDialog.dismissMsg = this.$q.notify({
+ this.qrCodeDialog.dismissMsg = this.$q.notify({
timeout: 0,
message: 'Waiting for payment...'
})
+ return data
+ })
+ .then(data => {
+ this.qrCodeDialog.paymentChecker = setInterval(() => {
+ LNbits.api
+ .request(
+ 'GET',
+ `/diagonalley/api/v1/orders/payments/${this.qrCodeDialog.data.payment_hash}`
+ )
+ .then(res => {
+ if (res.data.paid) {
+ this.$q.notify({
+ type: 'positive',
+ message: 'Sats received, thanks!',
+ icon: 'thumb_up'
+ })
+ clearInterval(this.qrCodeDialog.paymentChecker)
+ this.resetCart()
+ this.closeQrCodeDialog()
+ }
+ })
+ .catch(error => {
+ console.error(error)
+ LNbits.utils.notifyApiError(error)
+ })
+ }, 3000)
+ })
+ .catch(error => {
+ console.error(error)
+ LNbits.utils.notifyApiError(error)
})
- .catch(error => LNbits.utils.notifyApiError(error))
-
- return
- },
- startPaymentNotifier() {
- this.cancelListener()
-
- this.cancelListener = LNbits.events.onInvoicePaid(
- this.wallet,
- payment => {
- this.qrCodeDialog = {
- show: false,
- data: {
- payment_request: null
- }
- }
-
- this.checkoutDialog = {data: {}}
- this.resetCart()
-
- this.$q.notify({
- type: 'positive',
- message: 'Sent, thank you!',
- icon: 'thumb_up'
- })
- }
- )
}
},
created() {
this.stall = JSON.parse('{{ stall | tojson }}')
this.products = JSON.parse('{{ products | tojson }}')
- this.wallet.inkey = '{{ inkey }}'
-
- this.startPaymentNotifier()
- //let stall_ids = new Set()
- //this.products.map(p => stall_ids.add(p.stall))
console.log(this.stall, this.products)
}
@@ -410,13 +418,3 @@
}
{% endblock %}
-
-
diff --git a/lnbits/extensions/diagonalley/views.py b/lnbits/extensions/diagonalley/views.py
index c440d2ae..af93d60c 100644
--- a/lnbits/extensions/diagonalley/views.py
+++ b/lnbits/extensions/diagonalley/views.py
@@ -33,15 +33,12 @@ async def index(request: Request, user: User = Depends(check_user_exists)):
async def display(request: Request, stall_id):
stall = await get_diagonalley_stall(stall_id)
products = await get_diagonalley_products(stall_id)
- wallet = await get_wallet(stall.wallet)
zones = []
for id in stall.shippingzones.split(","):
z = await get_diagonalley_zone(id)
z = z.dict()
zones.append({"label": z["countries"], "cost": z["cost"], "value": z["id"]})
- logger.debug(f"ZONES {zones}")
-
if not stall:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Stall does not exist."
@@ -57,7 +54,6 @@ async def display(request: Request, stall_id):
"request": request,
"stall": stall,
"products": [product.dict() for product in products],
- "inkey": wallet.inkey,
},
)
diff --git a/lnbits/extensions/diagonalley/views_api.py b/lnbits/extensions/diagonalley/views_api.py
index 5bdd3c2a..4236d742 100644
--- a/lnbits/extensions/diagonalley/views_api.py
+++ b/lnbits/extensions/diagonalley/views_api.py
@@ -10,6 +10,7 @@ from starlette.exceptions import HTTPException
from lnbits.core.crud import get_user
from lnbits.core.services import create_invoice
+from lnbits.core.views.api import api_payment
from lnbits.decorators import (
WalletTypeInfo,
get_key_type,
@@ -33,6 +34,7 @@ from .crud import (
get_diagonalley_markets,
get_diagonalley_order,
get_diagonalley_order_details,
+ get_diagonalley_order_invoiceid,
get_diagonalley_orders,
get_diagonalley_product,
get_diagonalley_products,
@@ -270,6 +272,21 @@ async def api_diagonalley_order_create(data: createOrder):
# return order.dict()
+@diagonalley_ext.get("/api/v1/orders/payments/{payment_hash}")
+async def api_diagonalley_check_payment(payment_hash: str):
+ order = await get_diagonalley_order_invoiceid(payment_hash)
+ if not order:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND, detail="Order does not exist."
+ )
+ try:
+ status = await api_payment(payment_hash)
+
+ except Exception as exc:
+ logger.error(exc)
+ return {"paid": False}
+ return status
+
@diagonalley_ext.delete("/api/v1/orders/{order_id}")
async def api_diagonalley_order_delete(
order_id: str, wallet: WalletTypeInfo = Depends(get_key_type)
From 7c8e3de261d49f5600e4b900249528c3dcdafc10 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Fri, 19 Aug 2022 13:24:18 +0100
Subject: [PATCH 083/226] update product stock tests
---
lnbits/extensions/diagonalley/crud.py | 19 ++++++++++++++++---
lnbits/extensions/diagonalley/tasks.py | 15 +--------------
lnbits/extensions/diagonalley/views.py | 10 +++++++++-
3 files changed, 26 insertions(+), 18 deletions(-)
diff --git a/lnbits/extensions/diagonalley/crud.py b/lnbits/extensions/diagonalley/crud.py
index 0a825ca3..402fb1f5 100644
--- a/lnbits/extensions/diagonalley/crud.py
+++ b/lnbits/extensions/diagonalley/crud.py
@@ -1,10 +1,7 @@
-import re
from base64 import urlsafe_b64encode
from typing import List, Optional, Union
from uuid import uuid4
-import httpx
-
# from lnbits.db import open_ext_db
from lnbits.db import SQLITE
from lnbits.helpers import urlsafe_short_hash
@@ -291,6 +288,22 @@ async def set_diagonalley_order_paid(payment_hash: str) -> Orders:
(payment_hash,),
)
+async def update_diagonalley_product_stock(products):
+
+ q = "\n".join([f"""WHEN id='{p["product_id"]}' THEN {p["quantity"]}""" for p in products])
+ v = ",".join(["?"] * len(products))
+
+ await db.execute(
+ f"""
+ UPDATE diagonalley.products
+ SET quantity=(CASE
+ {q}
+ END)
+ WHERE id IN ({v});
+ """,
+ (*[p["product_id"] for p in products],)
+ )
+
async def get_diagonalley_orders(wallet_ids: Union[str, List[str]]) -> List[Orders]:
if isinstance(wallet_ids, str):
wallet_ids = [wallet_ids]
diff --git a/lnbits/extensions/diagonalley/tasks.py b/lnbits/extensions/diagonalley/tasks.py
index 6b64203e..22ce0819 100644
--- a/lnbits/extensions/diagonalley/tasks.py
+++ b/lnbits/extensions/diagonalley/tasks.py
@@ -30,19 +30,6 @@ async def on_invoice_paid(payment: Payment) -> None:
await set_diagonalley_order_paid(payment.payment_hash)
# deduct items sold from stock
+
# TODO
-
- """
- 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)
- """
diff --git a/lnbits/extensions/diagonalley/views.py b/lnbits/extensions/diagonalley/views.py
index af93d60c..df65c541 100644
--- a/lnbits/extensions/diagonalley/views.py
+++ b/lnbits/extensions/diagonalley/views.py
@@ -17,6 +17,7 @@ from .crud import (
get_diagonalley_stall,
get_diagonalley_zone,
get_diagonalley_zones,
+ update_diagonalley_product_stock,
)
templates = Jinja2Templates(directory="templates")
@@ -31,6 +32,13 @@ async def index(request: Request, user: User = Depends(check_user_exists)):
@diagonalley_ext.get("/{stall_id}", response_class=HTMLResponse)
async def display(request: Request, stall_id):
+ # test_qty = 99
+ # test = [
+ # {"product_id": "55vpVjWAuQExHsJxT28MYe", "quantity": test_qty},
+ # {"product_id": "f2eGNsEWgbLJbfAApd3Jw5", "quantity": test_qty},
+ # {"product_id": "FVqZLZdemWCsiqe9gafvsC", "quantity": test_qty},
+ # ]
+ # await update_diagonalley_product_stock(test)
stall = await get_diagonalley_stall(stall_id)
products = await get_diagonalley_products(stall_id)
zones = []
@@ -43,7 +51,7 @@ async def display(request: Request, stall_id):
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Stall does not exist."
)
-
+
stall = stall.dict()
del stall["privatekey"]
stall["zones"] = zones
From c9884e512bbd1d4414b705e23b6787c683d61c12 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Sat, 20 Aug 2022 08:18:28 +0100
Subject: [PATCH 084/226] stock values update
---
lnbits/extensions/diagonalley/crud.py | 4 ++--
lnbits/extensions/diagonalley/tasks.py | 10 ++++++++--
lnbits/extensions/diagonalley/views.py | 2 +-
3 files changed, 11 insertions(+), 5 deletions(-)
diff --git a/lnbits/extensions/diagonalley/crud.py b/lnbits/extensions/diagonalley/crud.py
index 402fb1f5..557b45d2 100644
--- a/lnbits/extensions/diagonalley/crud.py
+++ b/lnbits/extensions/diagonalley/crud.py
@@ -290,7 +290,7 @@ async def set_diagonalley_order_paid(payment_hash: str) -> Orders:
async def update_diagonalley_product_stock(products):
- q = "\n".join([f"""WHEN id='{p["product_id"]}' THEN {p["quantity"]}""" for p in products])
+ q = "\n".join([f"""WHEN id='{p.product_id}' THEN quantity - {p.quantity}""" for p in products])
v = ",".join(["?"] * len(products))
await db.execute(
@@ -301,7 +301,7 @@ async def update_diagonalley_product_stock(products):
END)
WHERE id IN ({v});
""",
- (*[p["product_id"] for p in products],)
+ (*[p.product_id for p in products],)
)
async def get_diagonalley_orders(wallet_ids: Union[str, List[str]]) -> List[Orders]:
diff --git a/lnbits/extensions/diagonalley/tasks.py b/lnbits/extensions/diagonalley/tasks.py
index 22ce0819..b913ae60 100644
--- a/lnbits/extensions/diagonalley/tasks.py
+++ b/lnbits/extensions/diagonalley/tasks.py
@@ -5,7 +5,12 @@ from loguru import logger
from lnbits.core.models import Payment
from lnbits.tasks import register_invoice_listener
-from .crud import get_diagonalley_order_invoiceid, set_diagonalley_order_paid
+from .crud import (
+ get_diagonalley_order_details,
+ get_diagonalley_order_invoiceid,
+ set_diagonalley_order_paid,
+ update_diagonalley_product_stock,
+)
async def wait_for_paid_invoices():
@@ -30,6 +35,7 @@ async def on_invoice_paid(payment: Payment) -> None:
await set_diagonalley_order_paid(payment.payment_hash)
# deduct items sold from stock
+ details = await get_diagonalley_order_details(order.id)
+ await update_diagonalley_product_stock(details)
- # TODO
diff --git a/lnbits/extensions/diagonalley/views.py b/lnbits/extensions/diagonalley/views.py
index df65c541..e2905b62 100644
--- a/lnbits/extensions/diagonalley/views.py
+++ b/lnbits/extensions/diagonalley/views.py
@@ -32,7 +32,7 @@ async def index(request: Request, user: User = Depends(check_user_exists)):
@diagonalley_ext.get("/{stall_id}", response_class=HTMLResponse)
async def display(request: Request, stall_id):
- # test_qty = 99
+ # test_qty = 10
# test = [
# {"product_id": "55vpVjWAuQExHsJxT28MYe", "quantity": test_qty},
# {"product_id": "f2eGNsEWgbLJbfAApd3Jw5", "quantity": test_qty},
From 108e2f579db3d9e0ed6af32fa69d6dca994e0588 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Mon, 12 Sep 2022 10:20:58 +0100
Subject: [PATCH 085/226] ui cosmetic on cards
---
lnbits/extensions/diagonalley/templates/diagonalley/stall.html | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/stall.html b/lnbits/extensions/diagonalley/templates/diagonalley/stall.html
index f6333cfe..9396d663 100644
--- a/lnbits/extensions/diagonalley/templates/diagonalley/stall.html
+++ b/lnbits/extensions/diagonalley/templates/diagonalley/stall.html
@@ -130,11 +130,10 @@
>
- {{ item.description }}
+
{{ item.description }}
From 6ef7411999d43565a7a3f597e66dc541b512aa84 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Mon, 12 Sep 2022 10:21:17 +0100
Subject: [PATCH 086/226] clean up
---
lnbits/extensions/diagonalley/views.py | 7 -------
1 file changed, 7 deletions(-)
diff --git a/lnbits/extensions/diagonalley/views.py b/lnbits/extensions/diagonalley/views.py
index e2905b62..0388c285 100644
--- a/lnbits/extensions/diagonalley/views.py
+++ b/lnbits/extensions/diagonalley/views.py
@@ -32,13 +32,6 @@ async def index(request: Request, user: User = Depends(check_user_exists)):
@diagonalley_ext.get("/{stall_id}", response_class=HTMLResponse)
async def display(request: Request, stall_id):
- # test_qty = 10
- # test = [
- # {"product_id": "55vpVjWAuQExHsJxT28MYe", "quantity": test_qty},
- # {"product_id": "f2eGNsEWgbLJbfAApd3Jw5", "quantity": test_qty},
- # {"product_id": "FVqZLZdemWCsiqe9gafvsC", "quantity": test_qty},
- # ]
- # await update_diagonalley_product_stock(test)
stall = await get_diagonalley_stall(stall_id)
products = await get_diagonalley_products(stall_id)
zones = []
From f429037d636fd9e6a1d3f7f2100339afe6db9690 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Mon, 12 Sep 2022 16:58:56 +0100
Subject: [PATCH 087/226] create marketplace with stalls
---
lnbits/extensions/diagonalley/crud.py | 50 ++++++++++++++-
lnbits/extensions/diagonalley/models.py | 3 +
.../templates/diagonalley/index.html | 61 +++++++++++--------
lnbits/extensions/diagonalley/views_api.py | 5 +-
4 files changed, 91 insertions(+), 28 deletions(-)
diff --git a/lnbits/extensions/diagonalley/crud.py b/lnbits/extensions/diagonalley/crud.py
index 557b45d2..664dcb06 100644
--- a/lnbits/extensions/diagonalley/crud.py
+++ b/lnbits/extensions/diagonalley/crud.py
@@ -9,6 +9,8 @@ from lnbits.settings import WALLET
from . import db
from .models import (
+ CreateMarket,
+ CreateMarketStalls,
Market,
OrderDetail,
Orders,
@@ -332,11 +334,55 @@ async def get_diagonalley_market(market_id: str) -> Optional[Market]:
row = await db.fetchone(
"SELECT * FROM diagonalley.markets WHERE id = ?", (market_id,)
)
- Market(**row) if row else None
+ return Market(**row) if row else None
async def get_diagonalley_market_stalls(market_id: str):
rows = await db.fetchall(
"SELECT * FROM diagonalley.market_stalls WHERE marketid = ?", (market_id,)
)
- return [Stalls(**row) for row in rows]
+
+ return [{**row} for row in rows]
+
+async def create_diagonalley_market(data: CreateMarket):
+ market_id = urlsafe_short_hash()
+
+ await db.execute(
+ """
+ INSERT INTO diagonalley.markets (id, usr, name)
+ VALUES (?, ?, ?)
+ """,
+ (
+ market_id,
+ data.usr,
+ data.name,
+ ),
+ )
+ market = await get_diagonalley_market(market_id)
+ assert market, "Newly created market couldn't be retrieved"
+ return market
+
+
+async def create_diagonalley_market_stalls(
+ market_id: str, data: List[CreateMarketStalls]
+):
+ for stallid in data:
+ id = urlsafe_short_hash()
+
+ await db.execute(
+ """
+ INSERT INTO diagonalley.market_stalls (id, marketid, stallid)
+ VALUES (?, ?, ?)
+ """,
+ (
+ id,
+ market_id,
+ stallid,
+ ),
+ )
+ market_stalls = await get_diagonalley_market_stalls(market_id)
+ return market_stalls
+
+
+async def update_diagonalley_market(market_id):
+ pass
diff --git a/lnbits/extensions/diagonalley/models.py b/lnbits/extensions/diagonalley/models.py
index e6516037..0c32798c 100644
--- a/lnbits/extensions/diagonalley/models.py
+++ b/lnbits/extensions/diagonalley/models.py
@@ -103,3 +103,6 @@ class Market(BaseModel):
id: str
usr: str
name: Optional[str]
+
+class CreateMarketStalls(BaseModel):
+ stallid: str
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/index.html b/lnbits/extensions/diagonalley/templates/diagonalley/index.html
index 516544ea..8d812a4a 100644
--- a/lnbits/extensions/diagonalley/templates/diagonalley/index.html
+++ b/lnbits/extensions/diagonalley/templates/diagonalley/index.html
@@ -179,12 +179,12 @@
-
+
-
+
@@ -197,13 +197,19 @@
label="Stalls"
v-model.trim="marketDialog.data.stalls"
>
+
Update Relay Update Marketplace
Launch Launch Marketplace
+ Store
Create a store to list products on
-
Launch frontend shop (not Nostr)
Makes a simple frontend shop for your stalls {
+ obj._data = _.clone(obj)
+ return obj
+ }
+
const humanReadableZones = zones => {
return zones.map(z => `${z.id} - ${z.countries}`)
}
@@ -1505,7 +1516,7 @@
)
.then(response => {
if (response.data) {
- this.shops = response.data.map(mapShops)
+ this.markets = response.data.map(mapMarkets)
}
})
.catch(error => {
@@ -1514,12 +1525,12 @@
},
openShopUpdateDialog: function (linkId) {
var self = this
- var link = _.findWhere(self.shops, {id: linkId})
+ var link = _.findWhere(self.markets, {id: linkId})
- this.shopDialog.data = _.clone(link._data)
- this.shopDialog.show = true
+ this.marketDialog.data = _.clone(link._data)
+ this.marketDialog.show = true
},
- sendShopFormData: function () {
+ sendMarketplaceFormData: function () {
let data = {...this.marketDialog.data}
if (!data.usr) {
@@ -1529,7 +1540,7 @@
if (data.id) {
this.updateZone(data)
} else {
- this.createZone(data)
+ this.createMarketplace(data)
}
},
updateShop: function (data) {
@@ -1539,24 +1550,24 @@
'PUT',
'/diagonalley/api/v1/shops' + data.id,
_.findWhere(self.g.user.wallets, {
- id: self.shopDialog.data.wallet
+ id: self.marketDialog.data.wallet
}).inkey,
_.pick(data, 'countries', 'cost')
)
.then(function (response) {
- self.shops = _.reject(self.shops, function (obj) {
+ self.markets = _.reject(self.markets, function (obj) {
return obj.id == data.id
})
- self.shops.push(mapShops(response.data))
- self.shopDialog.show = false
- self.shopDialog.data = {}
+ self.markets.push(mapShops(response.data))
+ self.marketDialog.show = false
+ self.marketDialog.data = {}
data = {}
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
- createShop(data) {
+ createMarketplace(data) {
LNbits.api
.request(
'POST',
@@ -1565,9 +1576,9 @@
data
)
.then(response => {
- this.shops.push(mapShops(response.data))
- this.shopDialog.show = false
- this.shopDialog.data = {}
+ this.markets.push(mapMarkets(response.data))
+ this.marketDialog.show = false
+ this.marketDialog.data = {}
data = {}
})
.catch(error => {
@@ -1576,7 +1587,7 @@
},
deleteShop: function (shopId) {
var self = this
- var shop = _.findWhere(self.shops, {id: shopId})
+ var shop = _.findWhere(self.markets, {id: shopId})
LNbits.utils
.confirmDialog('Are you sure you want to delete this Shop link?')
@@ -1588,7 +1599,7 @@
_.findWhere(self.g.user.wallets, {id: shop.wallet}).inkey
)
.then(function (response) {
- self.shops = _.reject(self.shops, function (obj) {
+ self.markets = _.reject(self.markets, function (obj) {
return obj.id == shopId
})
})
@@ -1598,7 +1609,7 @@
})
},
exportShopsCSV: function () {
- LNbits.utils.exportCSV(this.shopsTable.columns, this.shops)
+ LNbits.utils.exportCSV(this.shopsTable.columns, this.markets)
},
////////////////////////////////////////
////////////////ORDERS//////////////////
@@ -1694,7 +1705,7 @@
this.getProducts()
this.getZones()
this.getOrders()
- //this.getMarkets() # NOT YET IMPLEMENTED
+ this.getMarkets() // NOT YET IMPLEMENTED
this.customerKeys = [
'cb4c0164fe03fcdadcbfb4f76611c71620790944c24f21a1cd119395cdedfe1b',
'a9c17358a6dc4ceb3bb4d883eb87967a66b3453a0f3199f0b1c8eef8070c6a07'
diff --git a/lnbits/extensions/diagonalley/views_api.py b/lnbits/extensions/diagonalley/views_api.py
index 4236d742..6226e28b 100644
--- a/lnbits/extensions/diagonalley/views_api.py
+++ b/lnbits/extensions/diagonalley/views_api.py
@@ -21,6 +21,8 @@ from lnbits.decorators import (
from ...helpers import urlsafe_short_hash
from . import db, diagonalley_ext
from .crud import (
+ create_diagonalley_market,
+ create_diagonalley_market_stalls,
create_diagonalley_order,
create_diagonalley_order_details,
create_diagonalley_product,
@@ -42,6 +44,7 @@ from .crud import (
get_diagonalley_stalls,
get_diagonalley_zone,
get_diagonalley_zones,
+ update_diagonalley_market,
update_diagonalley_product,
update_diagonalley_stall,
update_diagonalley_zone,
@@ -439,7 +442,6 @@ async def api_diagonalley_stall_create(
market_id: str = None,
wallet: WalletTypeInfo = Depends(require_invoice_key),
):
-
if market_id:
market = await get_diagonalley_market(market_id)
if not market:
@@ -451,5 +453,6 @@ async def api_diagonalley_stall_create(
market = await update_diagonalley_market(market_id, **data.dict())
else:
market = await create_diagonalley_market(data=data)
+ await create_diagonalley_market_stalls(market_id=market.id, data=data.stalls)
return market.dict()
From 418cc7bf863b27f1d999375b21b64493feb81750 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Mon, 12 Sep 2022 17:01:41 +0100
Subject: [PATCH 088/226] make format
---
lnbits/extensions/diagonalley/crud.py | 35 +++++++++++++---------
lnbits/extensions/diagonalley/models.py | 1 +
lnbits/extensions/diagonalley/tasks.py | 4 +--
lnbits/extensions/diagonalley/views.py | 2 +-
lnbits/extensions/diagonalley/views_api.py | 1 +
5 files changed, 25 insertions(+), 18 deletions(-)
diff --git a/lnbits/extensions/diagonalley/crud.py b/lnbits/extensions/diagonalley/crud.py
index 664dcb06..49a64fc7 100644
--- a/lnbits/extensions/diagonalley/crud.py
+++ b/lnbits/extensions/diagonalley/crud.py
@@ -274,27 +274,32 @@ async def get_diagonalley_order(order_id: str) -> Optional[Orders]:
)
return Orders(**row) if row else None
+
async def get_diagonalley_order_invoiceid(invoice_id: str) -> Optional[Orders]:
row = await db.fetchone(
"SELECT * FROM diagonalley.orders WHERE invoiceid = ?", (invoice_id,)
)
return Orders(**row) if row else None
+
async def set_diagonalley_order_paid(payment_hash: str) -> Orders:
await db.execute(
- """
+ """
UPDATE diagonalley.orders
SET paid = true
WHERE invoiceid = ?
""",
- (payment_hash,),
- )
+ (payment_hash,),
+ )
+
async def update_diagonalley_product_stock(products):
-
- q = "\n".join([f"""WHEN id='{p.product_id}' THEN quantity - {p.quantity}""" for p in products])
+
+ q = "\n".join(
+ [f"""WHEN id='{p.product_id}' THEN quantity - {p.quantity}""" for p in products]
+ )
v = ",".join(["?"] * len(products))
-
+
await db.execute(
f"""
UPDATE diagonalley.products
@@ -303,9 +308,10 @@ async def update_diagonalley_product_stock(products):
END)
WHERE id IN ({v});
""",
- (*[p.product_id for p in products],)
+ (*[p.product_id for p in products],),
)
+
async def get_diagonalley_orders(wallet_ids: Union[str, List[str]]) -> List[Orders]:
if isinstance(wallet_ids, str):
wallet_ids = [wallet_ids]
@@ -344,20 +350,21 @@ async def get_diagonalley_market_stalls(market_id: str):
return [{**row} for row in rows]
+
async def create_diagonalley_market(data: CreateMarket):
market_id = urlsafe_short_hash()
await db.execute(
- """
+ """
INSERT INTO diagonalley.markets (id, usr, name)
VALUES (?, ?, ?)
""",
- (
- market_id,
- data.usr,
- data.name,
- ),
- )
+ (
+ market_id,
+ data.usr,
+ data.name,
+ ),
+ )
market = await get_diagonalley_market(market_id)
assert market, "Newly created market couldn't be retrieved"
return market
diff --git a/lnbits/extensions/diagonalley/models.py b/lnbits/extensions/diagonalley/models.py
index 0c32798c..c3234bb6 100644
--- a/lnbits/extensions/diagonalley/models.py
+++ b/lnbits/extensions/diagonalley/models.py
@@ -104,5 +104,6 @@ class Market(BaseModel):
usr: str
name: Optional[str]
+
class CreateMarketStalls(BaseModel):
stallid: str
diff --git a/lnbits/extensions/diagonalley/tasks.py b/lnbits/extensions/diagonalley/tasks.py
index b913ae60..cd9e18a8 100644
--- a/lnbits/extensions/diagonalley/tasks.py
+++ b/lnbits/extensions/diagonalley/tasks.py
@@ -33,9 +33,7 @@ async def on_invoice_paid(payment: Payment) -> None:
# set order as paid
await set_diagonalley_order_paid(payment.payment_hash)
-
+
# deduct items sold from stock
details = await get_diagonalley_order_details(order.id)
await update_diagonalley_product_stock(details)
-
-
diff --git a/lnbits/extensions/diagonalley/views.py b/lnbits/extensions/diagonalley/views.py
index 0388c285..5a94d993 100644
--- a/lnbits/extensions/diagonalley/views.py
+++ b/lnbits/extensions/diagonalley/views.py
@@ -44,7 +44,7 @@ async def display(request: Request, stall_id):
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Stall does not exist."
)
-
+
stall = stall.dict()
del stall["privatekey"]
stall["zones"] = zones
diff --git a/lnbits/extensions/diagonalley/views_api.py b/lnbits/extensions/diagonalley/views_api.py
index 6226e28b..0e69e637 100644
--- a/lnbits/extensions/diagonalley/views_api.py
+++ b/lnbits/extensions/diagonalley/views_api.py
@@ -290,6 +290,7 @@ async def api_diagonalley_check_payment(payment_hash: str):
return {"paid": False}
return status
+
@diagonalley_ext.delete("/api/v1/orders/{order_id}")
async def api_diagonalley_order_delete(
order_id: str, wallet: WalletTypeInfo = Depends(get_key_type)
From 6a5c0bd8ee3bcb66acbb79292e1aca997b49ffd9 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 13 Sep 2022 12:52:26 +0100
Subject: [PATCH 089/226] list marketplaces with stalls in index.html
---
lnbits/extensions/diagonalley/crud.py | 13 +-
.../templates/diagonalley/index.html | 113 ++++++++++++++++++
lnbits/extensions/diagonalley/views_api.py | 12 +-
3 files changed, 135 insertions(+), 3 deletions(-)
diff --git a/lnbits/extensions/diagonalley/crud.py b/lnbits/extensions/diagonalley/crud.py
index 49a64fc7..b2689f21 100644
--- a/lnbits/extensions/diagonalley/crud.py
+++ b/lnbits/extensions/diagonalley/crud.py
@@ -201,6 +201,13 @@ async def get_diagonalley_stalls(wallet_ids: Union[str, List[str]]) -> List[Stal
)
return [Stalls(**row) for row in rows]
+async def get_diagonalley_stalls_by_ids(stall_ids: Union[str, List[str]]) -> List[Stalls]:
+ q = ",".join(["?"] * len(stall_ids))
+ rows = await db.fetchall(
+ f"SELECT * FROM diagonalley.stalls WHERE id IN ({q})", (*stall_ids,)
+ )
+ return [Stalls(**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,))
@@ -346,9 +353,11 @@ async def get_diagonalley_market(market_id: str) -> Optional[Market]:
async def get_diagonalley_market_stalls(market_id: str):
rows = await db.fetchall(
"SELECT * FROM diagonalley.market_stalls WHERE marketid = ?", (market_id,)
- )
+ )
- return [{**row} for row in rows]
+ ids = [row["stallid"] for row in rows]
+
+ return await get_diagonalley_stalls_by_ids(ids)
async def create_diagonalley_market(data: CreateMarket):
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/index.html b/lnbits/extensions/diagonalley/templates/diagonalley/index.html
index 8d812a4a..a8062910 100644
--- a/lnbits/extensions/diagonalley/templates/diagonalley/index.html
+++ b/lnbits/extensions/diagonalley/templates/diagonalley/index.html
@@ -669,6 +669,80 @@
+
+
+
+
+
+
Marketplaces
+
+
+ Export to CSV
+
+
+
+ {% raw %}
+
+
+
+
+ {{ col.label }}
+
+
+
+
+
+
+
+
+ Link to pass to stall relay
+
+
+ {{ col.value }}
+
+
+
+
+
+
+
+ {% endraw %}
+
+
+
+
@@ -817,6 +891,17 @@
const mapMarkets = obj => {
obj._data = _.clone(obj)
+ obj.stores = []
+ LNbits.api
+ .request('GET', `/diagonalley/api/v1/markets/${obj.id}/stalls`, null)
+ .then(response => {
+ if (response.data) {
+ obj.stores = response.data.map(s => s.name).toString()
+ }
+ })
+ .catch(error => {
+ LNbits.utils.notifyApiError(error)
+ })
return obj
}
@@ -832,6 +917,7 @@
products: [],
orders: [],
stalls: [],
+ markets: [],
zones: [],
zoneOptions: [],
customerKeys: [],
@@ -1015,6 +1101,31 @@
rowsPerPage: 10
}
},
+ marketTable: {
+ columns: [
+ {
+ name: 'id',
+ align: 'left',
+ label: 'ID',
+ field: 'id'
+ },
+ {
+ name: 'name',
+ align: 'left',
+ label: 'Name',
+ field: 'name'
+ },
+ {
+ name: 'stores',
+ align: 'left',
+ label: 'Stores',
+ field: 'stores'
+ }
+ ],
+ pagination: {
+ rowsPerPage: 10
+ }
+ },
zonesTable: {
columns: [
{
@@ -1517,12 +1628,14 @@
.then(response => {
if (response.data) {
this.markets = response.data.map(mapMarkets)
+ console.log(this.markets)
}
})
.catch(error => {
LNbits.utils.notifyApiError(error)
})
},
+
openShopUpdateDialog: function (linkId) {
var self = this
var link = _.findWhere(self.markets, {id: linkId})
diff --git a/lnbits/extensions/diagonalley/views_api.py b/lnbits/extensions/diagonalley/views_api.py
index 0e69e637..947538c8 100644
--- a/lnbits/extensions/diagonalley/views_api.py
+++ b/lnbits/extensions/diagonalley/views_api.py
@@ -1,5 +1,6 @@
from base64 import urlsafe_b64encode
from http import HTTPStatus
+from typing import List
from uuid import uuid4
from fastapi import Request
@@ -33,6 +34,7 @@ from .crud import (
delete_diagonalley_stall,
delete_diagonalley_zone,
get_diagonalley_market,
+ get_diagonalley_market_stalls,
get_diagonalley_markets,
get_diagonalley_order,
get_diagonalley_order_details,
@@ -42,6 +44,7 @@ from .crud import (
get_diagonalley_products,
get_diagonalley_stall,
get_diagonalley_stalls,
+ get_diagonalley_stalls_by_ids,
get_diagonalley_zone,
get_diagonalley_zones,
update_diagonalley_market,
@@ -426,7 +429,8 @@ async def api_diagonalley_stall_order(
@diagonalley_ext.get("/api/v1/markets")
-async def api_diagonalley_orders(wallet: WalletTypeInfo = Depends(get_key_type)):
+async def api_diagonalley_markets(wallet: WalletTypeInfo = Depends(get_key_type)):
+ # await get_diagonalley_market_stalls(market_id="FzpWnMyHQMcRppiGVua4eY")
try:
return [
market.dict()
@@ -436,6 +440,12 @@ async def api_diagonalley_orders(wallet: WalletTypeInfo = Depends(get_key_type))
return {"message": "We could not retrieve the markets."}
+@diagonalley_ext.get("/api/v1/markets/{market_id}/stalls")
+async def api_diagonalley_market_stalls(market_id: str):
+ stall_ids = await get_diagonalley_market_stalls(market_id)
+ return stall_ids
+
+
@diagonalley_ext.post("/api/v1/markets")
@diagonalley_ext.put("/api/v1/markets/{market_id}")
async def api_diagonalley_stall_create(
From ad98cb0d45dd466e2009f5921f4d31dc1b18f14f Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Thu, 15 Sep 2022 15:51:12 +0100
Subject: [PATCH 090/226] marketplace html skeleton
---
.../templates/diagonalley/index.html | 2 +-
.../templates/diagonalley/market.html | 419 ++++++++++++++++++
lnbits/extensions/diagonalley/views.py | 39 +-
3 files changed, 443 insertions(+), 17 deletions(-)
create mode 100644 lnbits/extensions/diagonalley/templates/diagonalley/market.html
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/index.html b/lnbits/extensions/diagonalley/templates/diagonalley/index.html
index a8062910..c951fb0a 100644
--- a/lnbits/extensions/diagonalley/templates/diagonalley/index.html
+++ b/lnbits/extensions/diagonalley/templates/diagonalley/index.html
@@ -1818,7 +1818,7 @@
this.getProducts()
this.getZones()
this.getOrders()
- this.getMarkets() // NOT YET IMPLEMENTED
+ this.getMarkets()
this.customerKeys = [
'cb4c0164fe03fcdadcbfb4f76611c71620790944c24f21a1cd119395cdedfe1b',
'a9c17358a6dc4ceb3bb4d883eb87967a66b3453a0f3199f0b1c8eef8070c6a07'
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/market.html b/lnbits/extensions/diagonalley/templates/diagonalley/market.html
new file mode 100644
index 00000000..b1a3ce88
--- /dev/null
+++ b/lnbits/extensions/diagonalley/templates/diagonalley/market.html
@@ -0,0 +1,419 @@
+{% extends "public.html" %} {% block page %}
+
+
+
+
+ {{ market.name }}
+
+
+
+
+
+
+
+
+
+ {% raw %}
+
+ {{ cart.size }}
+
+ {% endraw %}
+
+
+ {% raw %}
+
+
+ {{p.quantity}} x
+
+
+
+
+
+
+
+
+ {{ p.name }}
+
+
+
+ {{p.price}} sats
+
+
+ {% endraw %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {% raw %}
+
+
+
+ Add to cart
+
+
+
+ {{ item.product }}
+
+
+
+
+
+
+
+
+
+ {{ item.price }} sats BTC {{ (item.price / 1e8).toFixed(8) }}
+ {{item.quantity}} left
+
+
+ {{cat}}
+
+
+
{{ item.description }}
+
+
+
+
+
+
+
+ View details
+
+
+ {% endraw %}
+
+
+
+
+
+
+
+
+
+
+ Select the shipping zone:
+
+
+
+
+ {% raw %} Total: {{ finalCost }} {% endraw %}
+
+
+ Checkout
+ Cancel
+
+
+
+
+
+
+
+
+
+
+
+ Copy invoice
+ Close
+
+
+
+
+{% endblock %} {% block scripts %}
+
+
+{% endblock %}
diff --git a/lnbits/extensions/diagonalley/views.py b/lnbits/extensions/diagonalley/views.py
index 5a94d993..4e1db476 100644
--- a/lnbits/extensions/diagonalley/views.py
+++ b/lnbits/extensions/diagonalley/views.py
@@ -13,6 +13,8 @@ from lnbits.extensions.diagonalley import diagonalley_ext, diagonalley_renderer
from ...core.crud import get_wallet
from .crud import (
+ get_diagonalley_market,
+ get_diagonalley_market_stalls,
get_diagonalley_products,
get_diagonalley_stall,
get_diagonalley_zone,
@@ -59,20 +61,25 @@ async def display(request: Request, stall_id):
)
-# @diagonalley_ext.get("/market/{market_id}", response_class=HTMLResponse)
-# async def display(request: Request, stall_id):
-# stalls = await get_diagonalley_stall(stall_id)
-# products = await get_diagonalley_products(stall_id)
+@diagonalley_ext.get("/market/{market_id}", response_class=HTMLResponse)
+async def display(request: Request, market_id):
+ market = await get_diagonalley_market(market_id)
+
+ if not market:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND, detail="Marketplace doesn't exist."
+ )
+
+ stalls = await get_diagonalley_market_stalls(market_id)
+ stalls_ids = [stall.id for stall in stalls]
+ products = [product.dict() for product in await get_diagonalley_products(stalls_ids)]
-# if not stall:
-# raise HTTPException(
-# status_code=HTTPStatus.NOT_FOUND, detail="Stall does not exist."
-# )
-# return diagonalley_renderer().TemplateResponse(
-# "diagonalley/stall.html",
-# {
-# "request": request,
-# "stall": stall.dict(),
-# "products": [product.dict() for product in products]
-# },
-# )
+ return diagonalley_renderer().TemplateResponse(
+ "diagonalley/market.html",
+ {
+ "request": request,
+ "market": market,
+ "stalls": [stall.dict() for stall in stalls],
+ "products": products
+ },
+ )
From ee4757543123c6b96772b4862a01dd0216ce2381 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Thu, 15 Sep 2022 15:52:08 +0100
Subject: [PATCH 091/226] format
---
lnbits/extensions/diagonalley/crud.py | 13 ++++++++-----
lnbits/extensions/diagonalley/views.py | 10 ++++++----
lnbits/extensions/diagonalley/views_api.py | 2 +-
3 files changed, 15 insertions(+), 10 deletions(-)
diff --git a/lnbits/extensions/diagonalley/crud.py b/lnbits/extensions/diagonalley/crud.py
index b2689f21..8f053de1 100644
--- a/lnbits/extensions/diagonalley/crud.py
+++ b/lnbits/extensions/diagonalley/crud.py
@@ -201,12 +201,15 @@ async def get_diagonalley_stalls(wallet_ids: Union[str, List[str]]) -> List[Stal
)
return [Stalls(**row) for row in rows]
-async def get_diagonalley_stalls_by_ids(stall_ids: Union[str, List[str]]) -> List[Stalls]:
+
+async def get_diagonalley_stalls_by_ids(
+ stall_ids: Union[str, List[str]]
+) -> List[Stalls]:
q = ",".join(["?"] * len(stall_ids))
rows = await db.fetchall(
f"SELECT * FROM diagonalley.stalls WHERE id IN ({q})", (*stall_ids,)
)
- return [Stalls(**row) for row in rows]
+ return [Stalls(**row) for row in rows]
async def delete_diagonalley_stall(stall_id: str) -> None:
@@ -353,10 +356,10 @@ async def get_diagonalley_market(market_id: str) -> Optional[Market]:
async def get_diagonalley_market_stalls(market_id: str):
rows = await db.fetchall(
"SELECT * FROM diagonalley.market_stalls WHERE marketid = ?", (market_id,)
- )
+ )
+
+ ids = [row["stallid"] for row in rows]
- ids = [row["stallid"] for row in rows]
-
return await get_diagonalley_stalls_by_ids(ids)
diff --git a/lnbits/extensions/diagonalley/views.py b/lnbits/extensions/diagonalley/views.py
index 4e1db476..d0fee249 100644
--- a/lnbits/extensions/diagonalley/views.py
+++ b/lnbits/extensions/diagonalley/views.py
@@ -64,15 +64,17 @@ async def display(request: Request, stall_id):
@diagonalley_ext.get("/market/{market_id}", response_class=HTMLResponse)
async def display(request: Request, market_id):
market = await get_diagonalley_market(market_id)
-
+
if not market:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Marketplace doesn't exist."
)
-
+
stalls = await get_diagonalley_market_stalls(market_id)
stalls_ids = [stall.id for stall in stalls]
- products = [product.dict() for product in await get_diagonalley_products(stalls_ids)]
+ products = [
+ product.dict() for product in await get_diagonalley_products(stalls_ids)
+ ]
return diagonalley_renderer().TemplateResponse(
"diagonalley/market.html",
@@ -80,6 +82,6 @@ async def display(request: Request, market_id):
"request": request,
"market": market,
"stalls": [stall.dict() for stall in stalls],
- "products": products
+ "products": products,
},
)
diff --git a/lnbits/extensions/diagonalley/views_api.py b/lnbits/extensions/diagonalley/views_api.py
index 947538c8..f5751553 100644
--- a/lnbits/extensions/diagonalley/views_api.py
+++ b/lnbits/extensions/diagonalley/views_api.py
@@ -444,7 +444,7 @@ async def api_diagonalley_markets(wallet: WalletTypeInfo = Depends(get_key_type)
async def api_diagonalley_market_stalls(market_id: str):
stall_ids = await get_diagonalley_market_stalls(market_id)
return stall_ids
-
+
@diagonalley_ext.post("/api/v1/markets")
@diagonalley_ext.put("/api/v1/markets/{market_id}")
From e80daf27957b315676f51f39059e4f3488029e03 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Fri, 23 Sep 2022 10:25:57 +0100
Subject: [PATCH 092/226] rename stores to stalls
---
.../templates/diagonalley/index.html | 28 +++++++++++--------
1 file changed, 16 insertions(+), 12 deletions(-)
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/index.html b/lnbits/extensions/diagonalley/templates/diagonalley/index.html
index c951fb0a..eb290a92 100644
--- a/lnbits/extensions/diagonalley/templates/diagonalley/index.html
+++ b/lnbits/extensions/diagonalley/templates/diagonalley/index.html
@@ -314,7 +314,7 @@
unelevated
color="primary"
type="submit"
- >Update Store Update Stall
Create Store Create Stall
+ Store
- Create a stall to list products on + Stall
+
+ Create a market stall to list products on
+
+ Store
- Create a store to list products on + Stall
+
+ Create a market stall to list products on
+
Launch frontend shop (not Nostr)
+ >Create Market
- Makes a simple frontend shop for your stalls
@@ -596,11 +600,11 @@
-
+
-
Stores
+ Market Stalls
Date: Wed, 28 Sep 2022 11:11:52 +0100
Subject: [PATCH 093/226] WIP added keys to merchant
---
.../templates/diagonalley/index.html | 50 ++++++++++++++-----
lnbits/extensions/diagonalley/views_api.py | 13 +++++
2 files changed, 50 insertions(+), 13 deletions(-)
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/index.html b/lnbits/extensions/diagonalley/templates/diagonalley/index.html
index eb290a92..cd03e6f3 100644
--- a/lnbits/extensions/diagonalley/templates/diagonalley/index.html
+++ b/lnbits/extensions/diagonalley/templates/diagonalley/index.html
@@ -253,28 +253,32 @@
>
-
+ >
-->
{% endblock %} {% block scripts %} {{ window_vars(user) }}
+
+
+{% endblock %}
From e0b93fbd49df489bc8dcea3ec1cab41650b2a49f Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Wed, 28 Sep 2022 11:12:34 +0100
Subject: [PATCH 095/226] chat and WS initial commit
---
lnbits/extensions/diagonalley/views.py | 49 ++++++++++++++++++++++++--
1 file changed, 47 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/diagonalley/views.py b/lnbits/extensions/diagonalley/views.py
index d0fee249..d6e701a0 100644
--- a/lnbits/extensions/diagonalley/views.py
+++ b/lnbits/extensions/diagonalley/views.py
@@ -1,6 +1,7 @@
from http import HTTPStatus
+from typing import List
-from fastapi import Request
+from fastapi import Query, Request, WebSocket, WebSocketDisconnect
from fastapi.params import Depends
from fastapi.templating import Jinja2Templates
from loguru import logger
@@ -11,10 +12,10 @@ 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 ...core.crud import get_wallet
from .crud import (
get_diagonalley_market,
get_diagonalley_market_stalls,
+ get_diagonalley_order_details,
get_diagonalley_products,
get_diagonalley_stall,
get_diagonalley_zone,
@@ -31,6 +32,22 @@ async def index(request: Request, user: User = Depends(check_user_exists)):
"diagonalley/index.html", {"request": request, "user": user.dict()}
)
+@diagonalley_ext.get("/chat", response_class=HTMLResponse)
+async def chat_page(request: Request, merch: str = Query(...), order: str = Query(...)):
+ stall = await get_diagonalley_stall(merch)
+ orders = await get_diagonalley_order_details(order)
+
+ logger.debug(f"ORDER: {orders}")
+
+ return diagonalley_renderer().TemplateResponse(
+ "diagonalley/chat.html",
+ {
+ "request": request,
+ "stall": {"id": stall.id, "name": stall.name, "publickey": stall.publickey, "wallet": stall.wallet },
+ "orders": [details.dict() for details in orders]
+ },
+ )
+
@diagonalley_ext.get("/{stall_id}", response_class=HTMLResponse)
async def display(request: Request, stall_id):
@@ -85,3 +102,31 @@ async def display(request: Request, market_id):
"products": products,
},
)
+
+##################WEBSOCKET ROUTES########################
+
+class ConnectionManager:
+ def __init__(self):
+ self.active_connections: List[WebSocket] = []
+
+ async def connect(self, websocket: WebSocket, order_id: str):
+ await websocket.accept()
+ websocket.id = order_id
+ self.active_connections.append(websocket)
+
+ def disconnect(self, websocket: WebSocket):
+ self.active_connections.remove(websocket)
+
+ async def send_personal_message(self, message: str, order_id: str):
+ for connection in self.active_connections:
+ if connection.id == order_id:
+ await connection.send_text(message)
+
+ async def broadcast(self, message: str):
+ for connection in self.active_connections:
+ await connection.send_text(message)
+
+
+manager = ConnectionManager()
+
+
From b9371805e2eb0038b439a76589d26beca6c507ef Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Wed, 28 Sep 2022 17:02:15 +0100
Subject: [PATCH 096/226] websockets working localstorage for review
---
lnbits/extensions/diagonalley/notifier.py | 83 ++++++++++
.../templates/diagonalley/chat.html | 145 ++++++++++++++----
.../templates/diagonalley/index.html | 2 +-
lnbits/extensions/diagonalley/views.py | 85 +++++-----
lnbits/extensions/diagonalley/views_api.py | 5 +-
5 files changed, 254 insertions(+), 66 deletions(-)
create mode 100644 lnbits/extensions/diagonalley/notifier.py
diff --git a/lnbits/extensions/diagonalley/notifier.py b/lnbits/extensions/diagonalley/notifier.py
new file mode 100644
index 00000000..58a9f2bb
--- /dev/null
+++ b/lnbits/extensions/diagonalley/notifier.py
@@ -0,0 +1,83 @@
+## adapted from https://github.com/Sentymental/chat-fastapi-websocket
+"""
+Create a class Notifier that will handle messages
+and delivery to the specific person
+"""
+
+from collections import defaultdict
+
+from fastapi import WebSocket
+from loguru import logger
+
+
+class Notifier:
+ """
+ Manages chatrooms, sessions and members.
+
+ Methods:
+ - get_notification_generator(self): async generator with notification messages
+ - get_members(self, room_name: str): get members in room
+ - push(message: str, room_name: str): push message
+ - connect(websocket: WebSocket, room_name: str): connect to room
+ - remove(websocket: WebSocket, room_name: str): remove
+ - _notify(message: str, room_name: str): notifier
+ """
+
+ def __init__(self):
+ # Create sessions as a dict:
+ self.sessions: dict = defaultdict(dict)
+
+ # Create notification generator:
+ self.generator = self.get_notification_generator()
+
+ async def get_notification_generator(self):
+ """Notification Generator"""
+
+ while True:
+ message = yield
+ msg = message["message"]
+ room_name = message["room_name"]
+ await self._notify(msg, room_name)
+
+ def get_members(self, room_name: str):
+ """Get all members in a room"""
+
+ try:
+ logger.info(f"Looking for members in room: {room_name}")
+ return self.sessions[room_name]
+
+ except Exception:
+ logger.exception(f"There is no member in room: {room_name}")
+ return None
+
+ async def push(self, message: str, room_name: str = None):
+ """Push a message"""
+
+ message_body = {"message": message, "room_name": room_name}
+ await self.generator.asend(message_body)
+
+ async def connect(self, websocket: WebSocket, room_name: str):
+ """Connect to room"""
+
+ await websocket.accept()
+ if self.sessions[room_name] == {} or len(self.sessions[room_name]) == 0:
+ self.sessions[room_name] = []
+
+ self.sessions[room_name].append(websocket)
+ print(f"Connections ...: {self.sessions[room_name]}")
+
+ def remove(self, websocket: WebSocket, room_name: str):
+ """Remove websocket from room"""
+
+ self.sessions[room_name].remove(websocket)
+ print(f"Connection removed...\nOpen connections...: {self.sessions[room_name]}")
+
+ async def _notify(self, message: str, room_name: str):
+ """Notifier"""
+
+ remaining_sessions = []
+ while len(self.sessions[room_name]) > 0:
+ websocket = self.sessions[room_name].pop()
+ await websocket.send_text(message)
+ remaining_sessions.append(websocket)
+ self.sessions[room_name] = remaining_sessions
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/chat.html b/lnbits/extensions/diagonalley/templates/diagonalley/chat.html
index bb3e607f..1713c9e2 100644
--- a/lnbits/extensions/diagonalley/templates/diagonalley/chat.html
+++ b/lnbits/extensions/diagonalley/templates/diagonalley/chat.html
@@ -45,13 +45,15 @@
{% raw %}
{{ stall.name }}
-
+
Public Key: {{ sliceKey(stall.publickey) }}
- {{ stall.publickey }}
+ Click to copy
{% endraw %}
+
+
-
-
+ -->
{ changeOrder() }"
>
@@ -79,16 +81,26 @@
Lorem ipsum dolor, sit amet consectetur adipisicing elit. Dolore,
quasi.
-
-
-
-
-
-
+
+
+
+ {% raw %}
+
+
+ {{ user.keys[type] }}
+
+
{{ type == 'publickey' ? 'Public Key' : 'Private Key' }}
+ {% endraw %}
+
+
Lorem ipsum dolor, sit amet consectetur adipisicing elit. Dolore,
@@ -123,7 +135,9 @@
showMessages: false,
messages: {},
stall: null,
+ selectedOrder: null,
orders: [],
+ user: null,
// Mock data
model: null,
mockMerch: ['Google', 'Facebook', 'Twitter', 'Apple', 'Oracle'],
@@ -136,29 +150,108 @@
this.newMessage = ''
this.$refs.newMessage.focus()
},
- sendMessage() {
+ sendMessage(e) {
let message = {
key: Date.now(),
text: this.newMessage,
from: 'me'
}
- this.$set(this.messages, message.key, message)
+ ws.send(JSON.stringify(message))
this.clearMessage()
+ e.preventDefault()
},
sliceKey(key) {
if (!key) return ''
return `${key.slice(0, 4)}...${key.slice(-4)}`
+ },
+ async generateKeys() {
+ await LNbits.api
+ .request('GET', '/diagonalley/api/v1/keys', null)
+ .then(response => {
+ if (response.data) {
+ let data = {
+ keys: {
+ privatekey: response.data.privkey,
+ publickey: response.data.pubkey
+ }
+ }
+ this.user = data
+ console.log('Keys done', this.user)
+ return
+ }
+ })
+ .catch(function (error) {
+ LNbits.utils.notifyApiError(error)
+ })
+ },
+ changeOrder() {
+ console.log(this.selectedOrder)
+ },
+ startChat(room_name) {
+ if (location.protocol == 'https:') {
+ ws_scheme = 'wss://'
+ } else {
+ ws_scheme = 'ws://'
+ }
+ ws = new WebSocket(
+ ws_scheme + location.host + '/diagonalley/ws/' + room_name
+ )
+
+ function checkWebSocket(event) {
+ if (ws.readyState === WebSocket.CLOSED) {
+ console.log('WebSocket CLOSED: Reopening')
+ ws = new WebSocket(
+ ws_scheme + location.host + '/diagonalley/ws/' + room_name
+ )
+ }
+ }
+
+ ws.onmessage = event => {
+ let event_data = JSON.parse(event.data)
+
+ this.$set(this.messages, event_data.key, event_data)
+ this.$q.localStorage.set()
+ }
+
+ this.ws = ws
}
},
- created() {
- let stall = JSON.parse('{{ stall | tojson }}')
- let orders = JSON.parse('{{ orders | tojson }}')
+ async created() {
+ this.stall = JSON.parse('{{ stall | tojson }}')
- this.stall = stall
- this.orders = orders
- console.log(stall)
- console.log(orders)
+ let order_details = JSON.parse('{{ order | tojson }}')
+ let order_id = order_details[0].order_id
+
+ let data = this.$q.localStorage.getItem(`lnbits.diagonalley.data`)
+
+ try {
+ if (data) {
+ this.user = data
+ //add chat key (merchant pubkey) if not set
+ if (!this.user.chats[`${this.stall.publickey}`]) {
+ this.$set(this.user.chats, this.stall.publickey, [])
+ }
+ //this.$q.localStorage.set(`lnbits.diagonalley.data`, this.user)
+ } else {
+ // generate keys
+ await this.generateKeys()
+ // populate user data
+ this.user.chats = {
+ [`${this.stall.publickey}`]: []
+ }
+ this.user.orders = []
+ }
+
+ this.order_details = order_details
+ this.user.orders = [...new Set([...this.user.orders, order_id])]
+ this.selectedOrder = order_id
+
+ this.$q.localStorage.set(`lnbits.diagonalley.data`, this.user)
+ this.startChat(order_id)
+ } catch (e) {
+ console.error(e)
+ }
}
})
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/index.html b/lnbits/extensions/diagonalley/templates/diagonalley/index.html
index cd03e6f3..5ad6f6a5 100644
--- a/lnbits/extensions/diagonalley/templates/diagonalley/index.html
+++ b/lnbits/extensions/diagonalley/templates/diagonalley/index.html
@@ -644,7 +644,7 @@
icon="storefront"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
type="a"
- :href="'/diagonalley/' + props.row.id"
+ :href="'/diagonalley/stalls/' + props.row.id"
target="_blank"
>
Link to pass to stall relay
diff --git a/lnbits/extensions/diagonalley/views.py b/lnbits/extensions/diagonalley/views.py
index d6e701a0..a378658a 100644
--- a/lnbits/extensions/diagonalley/views.py
+++ b/lnbits/extensions/diagonalley/views.py
@@ -1,7 +1,8 @@
+import json
from http import HTTPStatus
from typing import List
-from fastapi import Query, Request, WebSocket, WebSocketDisconnect
+from fastapi import BackgroundTasks, Query, Request, WebSocket, WebSocketDisconnect
from fastapi.params import Depends
from fastapi.templating import Jinja2Templates
from loguru import logger
@@ -11,6 +12,7 @@ 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 lnbits.extensions.diagonalley.notifier import Notifier
from .crud import (
get_diagonalley_market,
@@ -32,24 +34,8 @@ async def index(request: Request, user: User = Depends(check_user_exists)):
"diagonalley/index.html", {"request": request, "user": user.dict()}
)
-@diagonalley_ext.get("/chat", response_class=HTMLResponse)
-async def chat_page(request: Request, merch: str = Query(...), order: str = Query(...)):
- stall = await get_diagonalley_stall(merch)
- orders = await get_diagonalley_order_details(order)
- logger.debug(f"ORDER: {orders}")
-
- return diagonalley_renderer().TemplateResponse(
- "diagonalley/chat.html",
- {
- "request": request,
- "stall": {"id": stall.id, "name": stall.name, "publickey": stall.publickey, "wallet": stall.wallet },
- "orders": [details.dict() for details in orders]
- },
- )
-
-
-@diagonalley_ext.get("/{stall_id}", response_class=HTMLResponse)
+@diagonalley_ext.get("/stalls/{stall_id}", response_class=HTMLResponse)
async def display(request: Request, stall_id):
stall = await get_diagonalley_stall(stall_id)
products = await get_diagonalley_products(stall_id)
@@ -103,30 +89,55 @@ async def display(request: Request, market_id):
},
)
+
+@diagonalley_ext.get("/chat", response_class=HTMLResponse)
+async def chat_page(request: Request, merch: str = Query(...), order: str = Query(...)):
+ stall = await get_diagonalley_stall(merch)
+ _order = await get_diagonalley_order_details(order)
+
+ return diagonalley_renderer().TemplateResponse(
+ "diagonalley/chat.html",
+ {
+ "request": request,
+ "stall": {
+ "id": stall.id,
+ "name": stall.name,
+ "publickey": stall.publickey,
+ "wallet": stall.wallet,
+ },
+ "order": [details.dict() for details in _order],
+ },
+ )
+
+
##################WEBSOCKET ROUTES########################
-class ConnectionManager:
- def __init__(self):
- self.active_connections: List[WebSocket] = []
+# Initialize Notifier:
+notifier = Notifier()
- async def connect(self, websocket: WebSocket, order_id: str):
- await websocket.accept()
- websocket.id = order_id
- self.active_connections.append(websocket)
+@diagonalley_ext.websocket("/ws/{room_name}")
+async def websocket_endpoint(
+ websocket: WebSocket, room_name: str, background_tasks: BackgroundTasks
+):
+ await notifier.connect(websocket, room_name)
+ try:
+ while True:
+ data = await websocket.receive_text()
+ d = json.loads(data)
+ d["room_name"] = room_name
- def disconnect(self, websocket: WebSocket):
- self.active_connections.remove(websocket)
+ room_members = (
+ notifier.get_members(room_name)
+ if notifier.get_members(room_name) is not None
+ else []
+ )
- async def send_personal_message(self, message: str, order_id: str):
- for connection in self.active_connections:
- if connection.id == order_id:
- await connection.send_text(message)
+ if websocket not in room_members:
+ print("Sender not in room member: Reconnecting...")
+ await notifier.connect(websocket, room_name)
- async def broadcast(self, message: str):
- for connection in self.active_connections:
- await connection.send_text(message)
-
-
-manager = ConnectionManager()
+ await notifier._notify(f"{data}", room_name)
+ except WebSocketDisconnect:
+ notifier.remove(websocket, room_name)
diff --git a/lnbits/extensions/diagonalley/views_api.py b/lnbits/extensions/diagonalley/views_api.py
index c3615c90..aa8d338d 100644
--- a/lnbits/extensions/diagonalley/views_api.py
+++ b/lnbits/extensions/diagonalley/views_api.py
@@ -469,14 +469,15 @@ async def api_diagonalley_stall_create(
return market.dict()
+
## KEYS
+
@diagonalley_ext.get("/api/v1/keys")
-async def api_diagonalley_generate_keys(wallet: WalletTypeInfo = Depends(require_admin_key)):
+async def api_diagonalley_generate_keys():
private_key = PrivateKey()
public_key = private_key.pubkey.serialize().hex()
while not public_key.startswith("02"):
private_key = PrivateKey()
public_key = private_key.pubkey.serialize().hex()
return {"privkey": private_key.serialize(), "pubkey": public_key[2:]}
-
From 8ab9590594fc55bfc0a4bf9d3a7a799554c2ad83 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Wed, 28 Sep 2022 17:03:09 +0100
Subject: [PATCH 097/226] format
---
lnbits/extensions/diagonalley/views.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/diagonalley/views.py b/lnbits/extensions/diagonalley/views.py
index a378658a..7b242aed 100644
--- a/lnbits/extensions/diagonalley/views.py
+++ b/lnbits/extensions/diagonalley/views.py
@@ -115,6 +115,7 @@ async def chat_page(request: Request, merch: str = Query(...), order: str = Quer
# Initialize Notifier:
notifier = Notifier()
+
@diagonalley_ext.websocket("/ws/{room_name}")
async def websocket_endpoint(
websocket: WebSocket, room_name: str, background_tasks: BackgroundTasks
@@ -140,4 +141,3 @@ async def websocket_endpoint(
except WebSocketDisconnect:
notifier.remove(websocket, room_name)
-
From 57bb9665c69e4194fce86ecea75b36b36c0dffd9 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Fri, 30 Sep 2022 10:16:34 +0100
Subject: [PATCH 098/226] chat messages stored in db
---
lnbits/extensions/diagonalley/crud.py | 15 +++++++++++
lnbits/extensions/diagonalley/migrations.py | 24 +++++++++++++++++
lnbits/extensions/diagonalley/models.py | 13 +++++++++
lnbits/extensions/diagonalley/notifier.py | 10 +++++++
.../templates/diagonalley/chat.html | 27 ++++++++++---------
lnbits/extensions/diagonalley/views.py | 6 +++--
6 files changed, 80 insertions(+), 15 deletions(-)
diff --git a/lnbits/extensions/diagonalley/crud.py b/lnbits/extensions/diagonalley/crud.py
index 8f053de1..9e3f2013 100644
--- a/lnbits/extensions/diagonalley/crud.py
+++ b/lnbits/extensions/diagonalley/crud.py
@@ -9,6 +9,7 @@ from lnbits.settings import WALLET
from . import db
from .models import (
+ CreateChatMessage,
CreateMarket,
CreateMarketStalls,
Market,
@@ -405,3 +406,17 @@ async def create_diagonalley_market_stalls(
async def update_diagonalley_market(market_id):
pass
+
+async def create_chat_message(data: CreateChatMessage):
+ print("DATA", data)
+ await db.execute(
+ """
+ INSERT INTO diagonalley.messages (msg, pubkey, id_conversation)
+ VALUES (?, ?, ?)
+ """,
+ (
+ data.msg,
+ data.pubkey,
+ data.room_name,
+ ),
+ )
diff --git a/lnbits/extensions/diagonalley/migrations.py b/lnbits/extensions/diagonalley/migrations.py
index 6e1510a7..3bcddcb0 100644
--- a/lnbits/extensions/diagonalley/migrations.py
+++ b/lnbits/extensions/diagonalley/migrations.py
@@ -113,3 +113,27 @@ async def m001_initial(db):
);
"""
)
+
+async def m002_add_chat_messages(db):
+ """
+ Initial chat messages table.
+ """
+ await db.execute(
+ f"""
+ CREATE TABLE diagonalley.messages (
+ id {db.serial_primary_key},
+ msg TEXT NOT NULL,
+ pubkey TEXT NOT NULL,
+ id_conversation TEXT NOT NULL,
+ timestamp TIMESTAMP NOT NULL DEFAULT """
+ + db.timestamp_now
+ + """
+ );
+ """
+ )
+
+ """
+ Create indexes for message fetching
+ """
+ await db.execute("CREATE INDEX idx_messages_timestamp ON diagonalley.messages (timestamp DESC)")
+ await db.execute("CREATE INDEX idx_messages_conversations ON diagonalley.messages (id_conversation)")
diff --git a/lnbits/extensions/diagonalley/models.py b/lnbits/extensions/diagonalley/models.py
index c3234bb6..f9fa1bca 100644
--- a/lnbits/extensions/diagonalley/models.py
+++ b/lnbits/extensions/diagonalley/models.py
@@ -107,3 +107,16 @@ class Market(BaseModel):
class CreateMarketStalls(BaseModel):
stallid: str
+
+
+class ChatMessage(BaseModel):
+ id: str
+ msg: str
+ pubkey: str
+ id_conversation: str
+ timestamp: int
+
+class CreateChatMessage(BaseModel):
+ msg: str = Query(..., min_length=1)
+ pubkey: str = Query(...)
+ room_name: str = Query(...)
diff --git a/lnbits/extensions/diagonalley/notifier.py b/lnbits/extensions/diagonalley/notifier.py
index 58a9f2bb..e21be500 100644
--- a/lnbits/extensions/diagonalley/notifier.py
+++ b/lnbits/extensions/diagonalley/notifier.py
@@ -4,11 +4,15 @@ Create a class Notifier that will handle messages
and delivery to the specific person
"""
+import json
from collections import defaultdict
from fastapi import WebSocket
from loguru import logger
+from lnbits.extensions.diagonalley.crud import create_chat_message
+from lnbits.extensions.diagonalley.models import CreateChatMessage
+
class Notifier:
"""
@@ -75,6 +79,12 @@ class Notifier:
async def _notify(self, message: str, room_name: str):
"""Notifier"""
+ d = json.loads(message)
+ d["room_name"] = room_name
+ db_msg = CreateChatMessage.parse_obj(d)
+ print("NOT:", db_msg)
+ await create_chat_message(data=db_msg)
+
remaining_sessions = []
while len(self.sessions[room_name]) > 0:
websocket = self.sessions[room_name].pop()
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/chat.html b/lnbits/extensions/diagonalley/templates/diagonalley/chat.html
index 1713c9e2..21f59361 100644
--- a/lnbits/extensions/diagonalley/templates/diagonalley/chat.html
+++ b/lnbits/extensions/diagonalley/templates/diagonalley/chat.html
@@ -7,12 +7,12 @@
@@ -125,6 +125,9 @@
{% endblock %} {% block scripts %}
+
+{% endblock %}
From d5388ba7dee48554fd9ba1f09fa8e887f0477187 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 4 Oct 2022 09:59:18 +0100
Subject: [PATCH 102/226] add publickey if stored
---
.../templates/diagonalley/stall.html | 29 +++++++++++++++++--
1 file changed, 27 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/stall.html b/lnbits/extensions/diagonalley/templates/diagonalley/stall.html
index 9396d663..a3a04b1e 100644
--- a/lnbits/extensions/diagonalley/templates/diagonalley/stall.html
+++ b/lnbits/extensions/diagonalley/templates/diagonalley/stall.html
@@ -162,6 +162,17 @@
v-model.trim="checkoutDialog.data.username"
label="Name *optional"
>
+
+
+
+ Click to restore saved public key
+
+
Cancel
Date: Tue, 4 Oct 2022 09:59:34 +0100
Subject: [PATCH 103/226] placeholder for product page WIP
---
.../diagonalley/templates/diagonalley/product.html | 14 ++++++++++++++
1 file changed, 14 insertions(+)
create mode 100644 lnbits/extensions/diagonalley/templates/diagonalley/product.html
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/product.html b/lnbits/extensions/diagonalley/templates/diagonalley/product.html
new file mode 100644
index 00000000..66f56691
--- /dev/null
+++ b/lnbits/extensions/diagonalley/templates/diagonalley/product.html
@@ -0,0 +1,14 @@
+{% extends "public.html" %} {% block page %}
+Product page
+{% endblock %} {% block scripts %}
+
+{% endblock %}
From c59e9efabc70735c1d9825557b70b254cbb6f5fa Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 4 Oct 2022 10:00:42 +0100
Subject: [PATCH 104/226] add public key and refactor endpoint
---
lnbits/extensions/diagonalley/crud.py | 1 +
lnbits/extensions/diagonalley/models.py | 1 +
.../templates/diagonalley/chat.html | 317 ------------------
lnbits/extensions/diagonalley/views.py | 13 +-
4 files changed, 11 insertions(+), 321 deletions(-)
delete mode 100644 lnbits/extensions/diagonalley/templates/diagonalley/chat.html
diff --git a/lnbits/extensions/diagonalley/crud.py b/lnbits/extensions/diagonalley/crud.py
index 18207888..f093ba69 100644
--- a/lnbits/extensions/diagonalley/crud.py
+++ b/lnbits/extensions/diagonalley/crud.py
@@ -408,6 +408,7 @@ async def create_diagonalley_market_stalls(
async def update_diagonalley_market(market_id):
pass
+### CHAT / MESSAGES
async def create_chat_message(data: CreateChatMessage):
print("DATA", data)
diff --git a/lnbits/extensions/diagonalley/models.py b/lnbits/extensions/diagonalley/models.py
index b1bfdc9c..e3ea2cc6 100644
--- a/lnbits/extensions/diagonalley/models.py
+++ b/lnbits/extensions/diagonalley/models.py
@@ -70,6 +70,7 @@ class createOrderDetails(BaseModel):
class createOrder(BaseModel):
wallet: str = Query(...)
+ username: str = Query(None)
pubkey: str = Query(None)
shippingzone: str = Query(...)
address: str = Query(...)
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/chat.html b/lnbits/extensions/diagonalley/templates/diagonalley/chat.html
deleted file mode 100644
index adbdb047..00000000
--- a/lnbits/extensions/diagonalley/templates/diagonalley/chat.html
+++ /dev/null
@@ -1,317 +0,0 @@
-{% extends "public.html" %} {% block page %}
-
-
-
-
-
- {% raw %}
- {{ stall.name }}
-
- Public Key: {{ sliceKey(stall.publickey) }}
- Click to copy
-
- {% endraw %}
-
-
-
-
- { changeOrder() }"
- >
-
-
-
-
-
- Lorem ipsum dolor, sit amet consectetur adipisicing elit. Dolore,
- quasi.
-
-
-
-
- {% raw %}
-
-
- {{ user.keys[type] }}
-
-
{{ type == 'publickey' ? 'Public Key' : 'Private Key' }}
- {% endraw %}
-
-
-
-
- Lorem ipsum dolor, sit amet consectetur adipisicing elit. Dolore,
- quasi.
-
-
-
-
-
- Backup keys
- Download your keys
-
- Delete data
- Delete all data from browser
-
-
-
-
-
-
-{% endblock %} {% block scripts %}
-
-
-{% endblock %}
diff --git a/lnbits/extensions/diagonalley/views.py b/lnbits/extensions/diagonalley/views.py
index 1addb8a7..27875287 100644
--- a/lnbits/extensions/diagonalley/views.py
+++ b/lnbits/extensions/diagonalley/views.py
@@ -20,6 +20,7 @@ from .crud import (
get_diagonalley_market,
get_diagonalley_market_stalls,
get_diagonalley_order_details,
+ get_diagonalley_order_invoiceid,
get_diagonalley_products,
get_diagonalley_stall,
get_diagonalley_zone,
@@ -92,13 +93,15 @@ async def display(request: Request, market_id):
)
-@diagonalley_ext.get("/chat", response_class=HTMLResponse)
-async def chat_page(request: Request, merch: str = Query(...), order: str = Query(...)):
+@diagonalley_ext.get("/order", response_class=HTMLResponse)
+async def chat_page(request: Request, merch: str = Query(...), invoice_id: str = Query(...)):
stall = await get_diagonalley_stall(merch)
- _order = await get_diagonalley_order_details(order)
+ order = await get_diagonalley_order_invoiceid(invoice_id)
+ _order = await get_diagonalley_order_details(order.id)
+ products = await get_diagonalley_products(stall.id)
return diagonalley_renderer().TemplateResponse(
- "diagonalley/chat.html",
+ "diagonalley/order.html",
{
"request": request,
"stall": {
@@ -107,7 +110,9 @@ async def chat_page(request: Request, merch: str = Query(...), order: str = Quer
"publickey": stall.publickey,
"wallet": stall.wallet,
},
+ "order_id": order.id,
"order": [details.dict() for details in _order],
+ "products": [product.dict() for product in products]
},
)
From ebe8d8b0b983eb16257ca66b0d3c1720a0816980 Mon Sep 17 00:00:00 2001
From: Tiago Vasconcelos
Date: Wed, 19 Oct 2022 22:07:38 +0100
Subject: [PATCH 105/226] just for bragging...
---
lnbits/extensions/diagonalley/config.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/diagonalley/config.json b/lnbits/extensions/diagonalley/config.json
index 8ad41727..194949d0 100644
--- a/lnbits/extensions/diagonalley/config.json
+++ b/lnbits/extensions/diagonalley/config.json
@@ -2,5 +2,5 @@
"name": "Diagon Alley",
"short_description": "Nostr shop system",
"icon": "add_shopping_cart",
- "contributors": ["benarc"]
+ "contributors": ["benarc", "talvasconcelos"]
}
From 532e12ec831b1a5908081edb5f32e95945e23023 Mon Sep 17 00:00:00 2001
From: Tiago Vasconcelos
Date: Wed, 19 Oct 2022 22:08:48 +0100
Subject: [PATCH 106/226] merchant chat functional almost out of WIP
---
lnbits/extensions/diagonalley/crud.py | 13 +
lnbits/extensions/diagonalley/notifier.py | 2 +-
.../templates/diagonalley/index.html | 389 +++++++++++++++---
.../templates/diagonalley/order.html | 52 +--
.../templates/diagonalley/stall.html | 16 +-
lnbits/extensions/diagonalley/views.py | 45 +-
lnbits/extensions/diagonalley/views_api.py | 23 +-
7 files changed, 461 insertions(+), 79 deletions(-)
diff --git a/lnbits/extensions/diagonalley/crud.py b/lnbits/extensions/diagonalley/crud.py
index f093ba69..8dadb739 100644
--- a/lnbits/extensions/diagonalley/crud.py
+++ b/lnbits/extensions/diagonalley/crud.py
@@ -408,8 +408,10 @@ async def create_diagonalley_market_stalls(
async def update_diagonalley_market(market_id):
pass
+
### CHAT / MESSAGES
+
async def create_chat_message(data: CreateChatMessage):
print("DATA", data)
await db.execute(
@@ -441,3 +443,14 @@ async def get_diagonalley_chat_messages(room_name: str):
)
return [ChatMessage(**row) for row in rows]
+
+
+async def get_diagonalley_chat_by_merchant(ids: List[str]) -> List[ChatMessage]:
+
+ q = ",".join(["?"] * len(ids))
+ rows = await db.fetchall(
+ f"SELECT * FROM diagonalley.messages WHERE id_conversation IN ({q})",
+ (*ids,),
+ )
+ print(ids, q, rows)
+ return [ChatMessage(**row) for row in rows]
diff --git a/lnbits/extensions/diagonalley/notifier.py b/lnbits/extensions/diagonalley/notifier.py
index e21be500..08badfc7 100644
--- a/lnbits/extensions/diagonalley/notifier.py
+++ b/lnbits/extensions/diagonalley/notifier.py
@@ -78,9 +78,9 @@ class Notifier:
async def _notify(self, message: str, room_name: str):
"""Notifier"""
-
d = json.loads(message)
d["room_name"] = room_name
+ print("hey", d)
db_msg = CreateChatMessage.parse_obj(d)
print("NOT:", db_msg)
await create_chat_message(data=db_msg)
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/index.html b/lnbits/extensions/diagonalley/templates/diagonalley/index.html
index 5ad6f6a5..f898fd40 100644
--- a/lnbits/extensions/diagonalley/templates/diagonalley/index.html
+++ b/lnbits/extensions/diagonalley/templates/diagonalley/index.html
@@ -345,20 +345,6 @@
- + Product List a product
- + Product List a product
+ Shipping Zone Create a shipping zone
@@ -382,7 +368,26 @@
Create a market stall to list products on
- + Product List a product
+ + Product List a product
+ Create Market
Makes a simple frontend shop for your stalls (not NOSTR)
+
{{ col.label }}
@@ -434,6 +440,23 @@
:icon="props.expand ? 'remove' : 'add'"
/>
+
+
+
+
+
{{ col.value }}
@@ -824,22 +847,114 @@
{% include "diagonalley/_api_docs.html" %}
+
Messages
-
-
-
+
{% endblock %} {% block scripts %} {{ window_vars(user) }}
@@ -873,6 +987,15 @@
const pica = window.pica()
+ function imgSizeFit(img, maxWidth = 1024, maxHeight = 768) {
+ let ratio = Math.min(
+ 1,
+ maxWidth / img.naturalWidth,
+ maxHeight / img.naturalHeight
+ )
+ return {width: img.naturalWidth * ratio, height: img.naturalHeight * ratio}
+ }
+
const mapStalls = obj => {
obj._data = _.clone(obj)
return obj
@@ -891,6 +1014,7 @@
new Date(obj.time * 1000),
'YYYY-MM-DD HH:mm'
)
+ // obj.unread = false
return obj
}
const mapKeys = obj => {
@@ -933,8 +1057,12 @@
customerKeys: [],
customerKey: '',
customerMessages: {},
+ messages: {},
+ newMessage: '',
+ orderMessages: {},
shippedModel: false,
shippingZoneOptions: [
+ 'Free (digital)',
'Worldwide',
'Europe',
'Australia',
@@ -999,17 +1127,17 @@
ordersTable: {
columns: [
/*{
- name: 'product',
- align: 'left',
- label: 'Product',
- field: 'product'
- },
- {
- name: 'quantity',
- align: 'left',
- label: 'Quantity',
- field: 'quantity'
- },*/
+ name: 'product',
+ align: 'left',
+ label: 'Product',
+ field: 'product'
+ },
+ {
+ name: 'quantity',
+ align: 'left',
+ label: 'Quantity',
+ field: 'quantity'
+ },*/
{
name: 'id',
align: 'left',
@@ -1443,9 +1571,10 @@
let image = new Image()
image.src = blobURL
image.onload = async () => {
+ let fit = imgSizeFit(image)
let canvas = document.createElement('canvas')
- canvas.setAttribute('width', 760)
- canvas.setAttribute('height', 490)
+ canvas.setAttribute('width', fit.width)
+ canvas.setAttribute('height', fit.height)
await pica.resize(image, canvas, {
quality: 0,
alpha: true,
@@ -1657,7 +1786,7 @@
.then(response => {
if (response.data) {
this.markets = response.data.map(mapMarkets)
- console.log(this.markets)
+ // console.log(this.markets)
}
})
.catch(error => {
@@ -1756,10 +1885,10 @@
////////////////////////////////////////
////////////////ORDERS//////////////////
////////////////////////////////////////
- getOrders: function () {
+ getOrders: async function () {
var self = this
- LNbits.api
+ await LNbits.api
.request(
'GET',
'/diagonalley/api/v1/orders?all_wallets=true',
@@ -1768,7 +1897,6 @@
.then(function (response) {
if (response.data) {
self.orders = response.data.map(mapOrders)
- console.log(self.orders)
}
})
.catch(function (error) {
@@ -1839,21 +1967,190 @@
},
exportOrdersCSV: function () {
LNbits.utils.exportCSV(this.ordersTable.columns, this.orders)
+ },
+ /// CHAT
+ async getAllMessages() {
+ await LNbits.api
+ .request(
+ 'GET',
+ `/diagonalley/api/v1/chat/messages/merchant?orders=${this.orders
+ .map(o => o.invoiceid)
+ .toString()}`,
+ this.g.user.wallets[0].adminkey
+ )
+ .then(res => {
+ this.messages = _.groupBy(res.data, 'id_conversation')
+ this.checkUnreadMessages()
+ console.log('Get new messages!')
+ })
+ .catch(error => {
+ LNbits.utils.notifyApiError(error)
+ })
+ },
+ updateLastSeenMsg(id) {
+ let data = this.$q.localStorage.getItem(
+ `lnbits.diagonalley.${this.g.user.id}`
+ )
+ let chat = {
+ ...data.chat,
+ [`${id}`]: {
+ timestamp: Object.keys(this.orderMessages)[
+ Object.keys(this.orderMessages).length - 1
+ ]
+ }
+ }
+ console.log({chat})
+ this.$q.localStorage.set(`lnbits.diagonalley.${this.g.user.id}`, {
+ ...data,
+ chat
+ })
+ this.checkUnreadMessages()
+ },
+ checkUnreadMessages() {
+ let lastMsgs = this.$q.localStorage.getItem(
+ `lnbits.diagonalley.${this.g.user.id}`
+ ).chat
+ for (let key in this.messages) {
+ let idx = this.orders.findIndex(f => f.invoiceid == key)
+ if (!lastMsgs[key]) {
+ this.updateLastSeenMsg(key)
+ //this.orders[idx].unread = true
+ return
+ }
+ console.log(
+ 'Key',
+ key,
+ 'saved:',
+ lastMsgs[key].timestamp,
+ 'messages: ',
+ Math.max(...this.messages[key].map(c => c.timestamp)),
+ lastMsgs[key].timestamp <
+ Math.max(...this.messages[key].map(c => c.timestamp))
+ )
+ if (
+ lastMsgs[key].timestamp <
+ Math.max(...this.messages[key].map(c => c.timestamp))
+ ) {
+ this.$set(this.orders[idx], 'unread', true)
+ // this.orders[idx].unread = true
+ } else {
+ this.$set(this.orders[idx], 'unread', false)
+ // this.orders[idx].unread = false
+ }
+ console.log('Order:', this.orders[idx])
+ }
+ },
+ clearMessage() {
+ this.newMessage = ''
+ this.$refs.newMessage.focus()
+ },
+ sendMessage() {
+ let message = {
+ msg: this.newMessage,
+ pubkey: this.keys.pubkey
+ }
+ this.ws.send(JSON.stringify(message))
+
+ this.clearMessage()
+ },
+ chatRoom(id) {
+ this.startChat(id)
+ this.orderMessages = {}
+ this.messages[id].map(m => {
+ this.$set(this.orderMessages, m.timestamp, {
+ msg: m.msg,
+ pubkey: m.pubkey
+ })
+ })
+ this.$refs.chatCard.scrollIntoView({
+ behavior: 'smooth',
+ inline: 'nearest'
+ })
+ this.updateLastSeenMsg(id)
+ //"ea2fbf6c91aa228603681e2cc34bb06e34e6d1375fa4d6c35756182b2fa3307f"
+ //"c7435a04875c26e28db91a377bd6e991dbfefeefea8258415f3ae0c716ed2335"
+ },
+ startChat(room_name) {
+ if (this.ws) {
+ this.ws.close()
+ }
+ if (location.protocol == 'https:') {
+ ws_scheme = 'wss://'
+ } else {
+ ws_scheme = 'ws://'
+ }
+ ws = new WebSocket(
+ ws_scheme + location.host + '/diagonalley/ws/' + room_name
+ )
+
+ function checkWebSocket(event) {
+ if (ws.readyState === WebSocket.CLOSED) {
+ console.log('WebSocket CLOSED: Reopening')
+ ws = new WebSocket(
+ ws_scheme + location.host + '/diagonalley/ws/' + room_name
+ )
+ }
+ }
+
+ ws.onmessage = event => {
+ let event_data = JSON.parse(event.data)
+
+ this.$set(this.orderMessages, Date.now(), event_data)
+ this.updateLastSeenMsg(room_name)
+ }
+
+ ws.onclose = event => {
+ this.updateLastSeenMsg(room_name)
+ }
+
+ this.ws = ws
}
},
- created: function () {
+ async created() {
if (this.g.user.wallets.length) {
this.getStalls()
this.getProducts()
this.getZones()
- this.getOrders()
+ await this.getOrders()
this.getMarkets()
- this.customerKeys = [
- 'cb4c0164fe03fcdadcbfb4f76611c71620790944c24f21a1cd119395cdedfe1b',
- 'a9c17358a6dc4ceb3bb4d883eb87967a66b3453a0f3199f0b1c8eef8070c6a07'
- ]
+ await this.getAllMessages()
+ let keys = this.$q.localStorage.getItem(
+ `lnbits.diagonalley.${this.g.user.id}`
+ )
+ if (keys) {
+ this.keys = keys
+ }
+ setInterval(() => {
+ this.getAllMessages()
+ }, 300000)
}
}
})
+
{% endblock %}
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/order.html b/lnbits/extensions/diagonalley/templates/diagonalley/order.html
index aa9d0de4..81cdfc36 100644
--- a/lnbits/extensions/diagonalley/templates/diagonalley/order.html
+++ b/lnbits/extensions/diagonalley/templates/diagonalley/order.html
@@ -68,7 +68,7 @@
dense
emit-value
v-model="selectedOrder"
- :options="user.orders"
+ :options="Object.keys(user.orders)"
label="Order"
hint="Select an order from this merchant"
@input="val => { changeOrder() }"
@@ -187,7 +187,7 @@
msg: this.newMessage,
pubkey: this.user.keys.publickey
}
- ws.send(JSON.stringify(message))
+ this.ws.send(JSON.stringify(message))
this.clearMessage()
},
@@ -236,10 +236,16 @@
LNbits.utils.notifyApiError(error)
})
},
- changeOrder() {
- console.log(this.selectedOrder)
+ async changeOrder() {
+ this.products = this.user.orders[this.selectedOrder]
+ this.messages = {}
+ await this.getMessages(this.selectedOrder)
+ this.startChat(this.selectedOrder)
},
startChat(room_name) {
+ if (this.ws) {
+ this.ws.close()
+ }
if (location.protocol == 'https:') {
ws_scheme = 'wss://'
} else {
@@ -268,51 +274,51 @@
}
},
async created() {
- this.stall = JSON.parse('{{ stall | tojson }}')
let order_details = JSON.parse('{{ order | tojson }}')
let products = JSON.parse('{{ products | tojson }}')
-
let order_id = '{{ order_id }}'
+ this.stall = JSON.parse('{{ stall | tojson }}')
+ this.products = order_details.map(o => {
+ let product = products.find(p => p.id == o.product_id)
+ return {
+ quantity: o.quantity,
+ name: product.product,
+ image: product.image,
+ price: product.price
+ }
+ })
+
let data = this.$q.localStorage.getItem(`lnbits.diagonalley.data`)
try {
if (data) {
this.user = data
//add chat key (merchant pubkey) if not set
- if (!this.user.chats[`${order_id}`]) {
- this.$set(this.user.chats, order_id, [])
+ if (!this.user.orders[`${order_id}`]) {
+ this.$set(this.user.orders, order_id, this.products)
}
//this.$q.localStorage.set(`lnbits.diagonalley.data`, this.user)
} else {
// generate keys
await this.generateKeys()
// populate user data
- this.user.chats = {
- [`${order_id}`]: []
+ this.user.orders = {
+ [`${order_id}`]: this.products
}
- this.user.orders = []
+ //this.user.orders = []
}
- this.order_details = order_details
- this.products = order_details.map(o => {
- let product = products.find(p => p.id == o.product_id)
- return {
- quantity: o.quantity,
- name: product.product,
- image: product.image,
- price: product.price
- }
- })
+ //this.order_details = order_details
- this.user.orders = [...new Set([...this.user.orders, order_id])]
+ //this.user.orders = [...new Set([...this.user.orders, order_id])]
this.selectedOrder = order_id
await this.getMessages(order_id)
this.$q.localStorage.set(`lnbits.diagonalley.data`, this.user)
this.startChat(order_id)
- console.log(this.products)
+ console.log(this.messages)
} catch (e) {
console.error(e)
}
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/stall.html b/lnbits/extensions/diagonalley/templates/diagonalley/stall.html
index a3a04b1e..05163573 100644
--- a/lnbits/extensions/diagonalley/templates/diagonalley/stall.html
+++ b/lnbits/extensions/diagonalley/templates/diagonalley/stall.html
@@ -125,7 +125,7 @@
>
- {{cat}}
@@ -409,8 +409,18 @@
if (res.data.paid) {
this.$q.notify({
type: 'positive',
- message: 'Sats received, thanks!',
- icon: 'thumb_up'
+ multiLine: true,
+ message:
+ "Sats received, thanks! You'l be redirected to the order page...",
+ icon: 'thumb_up',
+ actions: [
+ {
+ label: 'See Order',
+ handler: () => {
+ window.location.href = `/diagonalley/order/?merch=${this.stall.id}&invoice_id=${this.qrCodeDialog.data.payment_hash}`
+ }
+ }
+ ]
})
clearInterval(this.qrCodeDialog.paymentChecker)
this.resetCart()
diff --git a/lnbits/extensions/diagonalley/views.py b/lnbits/extensions/diagonalley/views.py
index 27875287..2bf98211 100644
--- a/lnbits/extensions/diagonalley/views.py
+++ b/lnbits/extensions/diagonalley/views.py
@@ -94,7 +94,9 @@ async def display(request: Request, market_id):
@diagonalley_ext.get("/order", response_class=HTMLResponse)
-async def chat_page(request: Request, merch: str = Query(...), invoice_id: str = Query(...)):
+async def chat_page(
+ request: Request, merch: str = Query(...), invoice_id: str = Query(...)
+):
stall = await get_diagonalley_stall(merch)
order = await get_diagonalley_order_invoiceid(invoice_id)
_order = await get_diagonalley_order_details(order.id)
@@ -110,9 +112,9 @@ async def chat_page(request: Request, merch: str = Query(...), invoice_id: str =
"publickey": stall.publickey,
"wallet": stall.wallet,
},
- "order_id": order.id,
+ "order_id": order.invoiceid,
"order": [details.dict() for details in _order],
- "products": [product.dict() for product in products]
+ "products": [product.dict() for product in products],
},
)
@@ -123,6 +125,41 @@ async def chat_page(request: Request, merch: str = Query(...), invoice_id: str =
notifier = Notifier()
+# class ConnectionManager:
+# def __init__(self):
+# self.active_connections: List[WebSocket] = []
+
+# async def connect(self, websocket: WebSocket, room_name: str):
+# await websocket.accept()
+# websocket.id = room_name
+# self.active_connections.append(websocket)
+
+# def disconnect(self, websocket: WebSocket):
+# self.active_connections.remove(websocket)
+
+# async def send_personal_message(self, message: str, room_name: str):
+# for connection in self.active_connections:
+# if connection.id == room_name:
+# await connection.send_text(message)
+
+# async def broadcast(self, message: str):
+# for connection in self.active_connections:
+# await connection.send_text(message)
+
+
+# manager = ConnectionManager()
+
+
+# @diagonalley_ext.websocket("/ws/{room_name}")
+# async def websocket_endpoint(websocket: WebSocket, room_name: str):
+# await manager.connect(websocket, room_name)
+# try:
+# while True:
+# data = await websocket.receive_text()
+# except WebSocketDisconnect:
+# manager.disconnect(websocket)
+
+
@diagonalley_ext.websocket("/ws/{room_name}")
async def websocket_endpoint(
websocket: WebSocket, room_name: str, background_tasks: BackgroundTasks
@@ -143,7 +180,7 @@ async def websocket_endpoint(
if websocket not in room_members:
print("Sender not in room member: Reconnecting...")
await notifier.connect(websocket, room_name)
-
+ print("ENDPOINT", data)
await notifier._notify(data, room_name)
except WebSocketDisconnect:
diff --git a/lnbits/extensions/diagonalley/views_api.py b/lnbits/extensions/diagonalley/views_api.py
index f84ea8e9..2f375357 100644
--- a/lnbits/extensions/diagonalley/views_api.py
+++ b/lnbits/extensions/diagonalley/views_api.py
@@ -1,10 +1,10 @@
from base64 import urlsafe_b64encode
from http import HTTPStatus
-from typing import List
+from typing import List, Union
from uuid import uuid4
from fastapi import Request
-from fastapi.param_functions import Query
+from fastapi.param_functions import Body, Query
from fastapi.params import Depends
from loguru import logger
from secp256k1 import PrivateKey, PublicKey
@@ -34,6 +34,7 @@ from .crud import (
delete_diagonalley_product,
delete_diagonalley_stall,
delete_diagonalley_zone,
+ get_diagonalley_chat_by_merchant,
get_diagonalley_chat_messages,
get_diagonalley_latest_chat_messages,
get_diagonalley_market,
@@ -255,6 +256,14 @@ async def api_diagonalley_orders(
return {"message": "We could not retrieve the orders."}
+@diagonalley_ext.get("/api/v1/orders/{order_id}")
+async def api_diagonalley_order_by_id(order_id: str):
+ order = (await get_diagonalley_order(order_id)).dict()
+ order["details"] = await get_diagonalley_order_details(order_id)
+
+ return order
+
+
@diagonalley_ext.post("/api/v1/orders")
async def api_diagonalley_order_create(data: createOrder):
ref = urlsafe_short_hash()
@@ -488,6 +497,16 @@ async def api_diagonalley_generate_keys():
## MESSAGES/CHAT
+@diagonalley_ext.get("/api/v1/chat/messages/merchant")
+async def api_get_merchant_messages(
+ orders: str = Query(...), wallet: WalletTypeInfo = Depends(require_admin_key)
+):
+
+ return [
+ msg.dict() for msg in await get_diagonalley_chat_by_merchant(orders.split(","))
+ ]
+
+
@diagonalley_ext.get("/api/v1/chat/messages/{room_name}")
async def api_get_latest_chat_msg(room_name: str, all_messages: bool = Query(False)):
if all_messages:
From 4dcb8d2ca047477f66b5a562735c85a2b1d52239 Mon Sep 17 00:00:00 2001
From: Tiago Vasconcelos
Date: Tue, 25 Oct 2022 11:55:55 +0100
Subject: [PATCH 107/226] redirect to order page
---
lnbits/extensions/diagonalley/templates/diagonalley/stall.html | 3 +++
1 file changed, 3 insertions(+)
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/stall.html b/lnbits/extensions/diagonalley/templates/diagonalley/stall.html
index 05163573..1299f694 100644
--- a/lnbits/extensions/diagonalley/templates/diagonalley/stall.html
+++ b/lnbits/extensions/diagonalley/templates/diagonalley/stall.html
@@ -425,6 +425,9 @@
clearInterval(this.qrCodeDialog.paymentChecker)
this.resetCart()
this.closeQrCodeDialog()
+ setTimeout(() => {
+ window.location.href = `/diagonalley/order/?merch=${this.stall.id}&invoice_id=${this.qrCodeDialog.data.payment_hash}`
+ }, 5000)
}
})
.catch(error => {
From 227eaeae75f7228934127fc114fffa1bff4e301f Mon Sep 17 00:00:00 2001
From: Tiago Vasconcelos
Date: Tue, 25 Oct 2022 11:57:01 +0100
Subject: [PATCH 108/226] some cleanup
---
.../templates/diagonalley/index.html | 34 ++++++++-----------
lnbits/extensions/diagonalley/views_api.py | 4 +--
2 files changed, 15 insertions(+), 23 deletions(-)
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/index.html b/lnbits/extensions/diagonalley/templates/diagonalley/index.html
index f898fd40..0882fd2f 100644
--- a/lnbits/extensions/diagonalley/templates/diagonalley/index.html
+++ b/lnbits/extensions/diagonalley/templates/diagonalley/index.html
@@ -580,6 +580,7 @@
- Link to pass to stall relay
+ Stall simple UI shopping cart
{{ col.value }}
@@ -1952,17 +1953,22 @@
})
})
},
- shipOrder: function (order_id) {
- var self = this
-
+ shipOrder(order_id){
LNbits.api
.request(
'GET',
'/diagonalley/api/v1/orders/shipped/' + order_id,
this.g.user.wallets[0].inkey
)
- .then(function (response) {
- self.orders.push(mapOrders(response.data))
+ .then((response) => {
+ console.log(response.data)
+ this.orders = _.reject(this.orders, (obj) => {
+ return obj.id == order_id
+ })
+ this.orders.push(mapOrders(response.data))
+ })
+ .catch((error) => {
+ LNbits.utils.notifyApiError(error)
})
},
exportOrdersCSV: function () {
@@ -1999,7 +2005,7 @@
]
}
}
- console.log({chat})
+ //console.log({chat})
this.$q.localStorage.set(`lnbits.diagonalley.${this.g.user.id}`, {
...data,
chat
@@ -2014,28 +2020,16 @@
let idx = this.orders.findIndex(f => f.invoiceid == key)
if (!lastMsgs[key]) {
this.updateLastSeenMsg(key)
- //this.orders[idx].unread = true
return
}
- console.log(
- 'Key',
- key,
- 'saved:',
- lastMsgs[key].timestamp,
- 'messages: ',
- Math.max(...this.messages[key].map(c => c.timestamp)),
- lastMsgs[key].timestamp <
- Math.max(...this.messages[key].map(c => c.timestamp))
- )
+
if (
lastMsgs[key].timestamp <
Math.max(...this.messages[key].map(c => c.timestamp))
) {
this.$set(this.orders[idx], 'unread', true)
- // this.orders[idx].unread = true
} else {
this.$set(this.orders[idx], 'unread', false)
- // this.orders[idx].unread = false
}
console.log('Order:', this.orders[idx])
}
diff --git a/lnbits/extensions/diagonalley/views_api.py b/lnbits/extensions/diagonalley/views_api.py
index 2f375357..e6a4c2de 100644
--- a/lnbits/extensions/diagonalley/views_api.py
+++ b/lnbits/extensions/diagonalley/views_api.py
@@ -286,8 +286,6 @@ async def api_diagonalley_order_create(data: createOrder):
"payment_request": payment_request,
"order_reference": ref,
}
- # order = await create_diagonalley_order(wallet_id=wallet.wallet.id, data=data)
- # return order.dict()
@diagonalley_ext.get("/api/v1/orders/payments/{payment_hash}")
@@ -352,7 +350,7 @@ async def api_diagonalley_order_shipped(
"SELECT * FROM diagonalley.orders WHERE id = ?", (order_id,)
)
- return [order.dict() for order in get_diagonalley_orders(order["wallet"])]
+ return order
###List products based on stall id
From c282f38726c2b62b4f4588cb665624aeb4fcd0ce Mon Sep 17 00:00:00 2001
From: Tiago Vasconcelos
Date: Tue, 25 Oct 2022 12:30:15 +0100
Subject: [PATCH 109/226] cleanup and keys management
---
.../templates/diagonalley/order.html | 104 ++++++++++++++++--
1 file changed, 92 insertions(+), 12 deletions(-)
diff --git a/lnbits/extensions/diagonalley/templates/diagonalley/order.html b/lnbits/extensions/diagonalley/templates/diagonalley/order.html
index 81cdfc36..fe5c7903 100644
--- a/lnbits/extensions/diagonalley/templates/diagonalley/order.html
+++ b/lnbits/extensions/diagonalley/templates/diagonalley/order.html
@@ -133,11 +133,15 @@
- Backup keys
Download your keys
- Restore keys
+ Restore keys
+
+ Delete data
Delete all data from browser
@@ -145,6 +149,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{% endblock %} {% block scripts %}
{% endblock %}
From 301cfb25d9cb20d9c6bfe94748ad4cd9683d46c8 Mon Sep 17 00:00:00 2001
From: Tiago Vasconcelos
Date: Fri, 30 Dec 2022 10:59:24 +0000
Subject: [PATCH 163/226] market is not fully ready! added stall name
---
lnbits/extensions/shop/templates/shop/market.html | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/lnbits/extensions/shop/templates/shop/market.html b/lnbits/extensions/shop/templates/shop/market.html
index a9585bca..29631f08 100644
--- a/lnbits/extensions/shop/templates/shop/market.html
+++ b/lnbits/extensions/shop/templates/shop/market.html
@@ -125,7 +125,7 @@
>
- {{cat}}
@@ -140,13 +140,17 @@
+ {{ stall.find(s => s.id == item.stall).name }}
- View details
+ Visit shop
{% endraw %}
From 77051d26090b4a8064d944fb48d2ca63e5f770d9 Mon Sep 17 00:00:00 2001
From: Tiago Vasconcelos
Date: Fri, 30 Dec 2022 12:00:16 +0000
Subject: [PATCH 164/226] toggle shipped
---
lnbits/extensions/shop/templates/shop/index.html | 4 +++-
lnbits/extensions/shop/views_api.py | 4 ++--
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/lnbits/extensions/shop/templates/shop/index.html b/lnbits/extensions/shop/templates/shop/index.html
index acb1e5a3..5c02677d 100644
--- a/lnbits/extensions/shop/templates/shop/index.html
+++ b/lnbits/extensions/shop/templates/shop/index.html
@@ -1194,10 +1194,12 @@
})
},
shipOrder(order_id) {
+ let shipped = this.orders.find(o => o.id == order_id).shipped
+ console.log(this.orders, order_id, shipped)
LNbits.api
.request(
'GET',
- '/shop/api/v1/orders/shipped/' + order_id,
+ `/shop/api/v1/orders/shipped/${order_id}?shipped=${!shipped}`,
this.g.user.wallets[0].inkey
)
.then(response => {
diff --git a/lnbits/extensions/shop/views_api.py b/lnbits/extensions/shop/views_api.py
index a85d1618..41e0ca88 100644
--- a/lnbits/extensions/shop/views_api.py
+++ b/lnbits/extensions/shop/views_api.py
@@ -358,12 +358,12 @@ async def api_shop_order_pubkey(payment_hash: str, pubkey: str):
@shop_ext.get("/api/v1/orders/shipped/{order_id}")
async def api_shop_order_shipped(
- order_id, wallet: WalletTypeInfo = Depends(get_key_type)
+ order_id, shipped: bool = Query(...), wallet: WalletTypeInfo = Depends(get_key_type)
):
await db.execute(
"UPDATE shop.orders SET shipped = ? WHERE id = ?",
(
- True,
+ shipped,
order_id,
),
)
From 715709ce2520c8f452c8e5f86a7a3864dcc97a54 Mon Sep 17 00:00:00 2001
From: Tiago Vasconcelos
Date: Fri, 30 Dec 2022 12:03:34 +0000
Subject: [PATCH 165/226] fix typo
---
lnbits/extensions/shop/templates/shop/stall.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/shop/templates/shop/stall.html b/lnbits/extensions/shop/templates/shop/stall.html
index f1050e23..08e59888 100644
--- a/lnbits/extensions/shop/templates/shop/stall.html
+++ b/lnbits/extensions/shop/templates/shop/stall.html
@@ -78,7 +78,7 @@
color="primary"
label="Reset"
@click="resetCart"
- />
+ >
From 7c1a639796e86330b9d4d4bf1df247b2c8beb1ee Mon Sep 17 00:00:00 2001
From: Tiago Vasconcelos
Date: Fri, 30 Dec 2022 16:51:09 +0000
Subject: [PATCH 166/226] make markets great again
---
lnbits/extensions/shop/crud.py | 14 +-
.../shop/templates/shop/_dialogs.html | 4 +-
.../shop/templates/shop/_tables.html | 6 +-
.../extensions/shop/templates/shop/index.html | 90 ++---
.../shop/templates/shop/market.html | 347 +++---------------
.../extensions/shop/templates/shop/stall.html | 2 +-
lnbits/extensions/shop/views_api.py | 5 +-
7 files changed, 101 insertions(+), 367 deletions(-)
diff --git a/lnbits/extensions/shop/crud.py b/lnbits/extensions/shop/crud.py
index 682c1708..2411fd38 100644
--- a/lnbits/extensions/shop/crud.py
+++ b/lnbits/extensions/shop/crud.py
@@ -399,8 +399,18 @@ async def create_shop_market_stalls(market_id: str, data: List[CreateMarketStall
return market_stalls
-async def update_shop_market(market_id):
- pass
+async def update_shop_market(market_id: str, name: str):
+ await db.execute(
+ "UPDATE shop.markets SET name = ? WHERE id = ?",
+ (name, market_id),
+ )
+ await db.execute(
+ "DELETE FROM shop.market_stalls WHERE marketid = ?",
+ (market_id,),
+ )
+
+ market = await get_shop_market(market_id)
+ return market
### CHAT / MESSAGES
diff --git a/lnbits/extensions/shop/templates/shop/_dialogs.html b/lnbits/extensions/shop/templates/shop/_dialogs.html
index 3718f38c..d2c22350 100644
--- a/lnbits/extensions/shop/templates/shop/_dialogs.html
+++ b/lnbits/extensions/shop/templates/shop/_dialogs.html
@@ -195,12 +195,12 @@
>
Link to pass to stall relay
- {{ col.value }}
+ {{ col.name == 'stalls' ? stallName(col.value) : col.value }}
@@ -369,7 +369,7 @@
flat
dense
size="xs"
- @click="deleteStall(props.row.id)"
+ @click="deleteMarket(props.row.id)"
icon="cancel"
color="pink"
>
diff --git a/lnbits/extensions/shop/templates/shop/index.html b/lnbits/extensions/shop/templates/shop/index.html
index 5c02677d..4d995fce 100644
--- a/lnbits/extensions/shop/templates/shop/index.html
+++ b/lnbits/extensions/shop/templates/shop/index.html
@@ -213,12 +213,12 @@
const mapMarkets = obj => {
obj._data = _.clone(obj)
- obj.stores = []
+ obj.stalls = []
LNbits.api
.request('GET', `/shop/api/v1/markets/${obj.id}/stalls`, null)
.then(response => {
if (response.data) {
- obj.stores = response.data.map(s => s.name).toString()
+ obj.stalls = response.data.map(s => s.id) //.toString()
}
})
.catch(error => {
@@ -460,10 +460,10 @@
field: 'name'
},
{
- name: 'stores',
+ name: 'stalls',
align: 'left',
label: 'Stalls',
- field: 'stores'
+ field: 'stalls'
}
],
pagination: {
@@ -637,6 +637,9 @@
////////////////////////////////////////
////////////////STALLS//////////////////
////////////////////////////////////////
+ stallName(id) {
+ return id.map(c => this.stalls.find(s => s.id == c).name).toString()
+ },
getStalls: function () {
var self = this
LNbits.api
@@ -1035,44 +1038,38 @@
})
},
- openShopUpdateDialog: function (linkId) {
- var self = this
- var link = _.findWhere(self.markets, {id: linkId})
-
- this.marketDialog.data = _.clone(link._data)
+ openMarketUpdateDialog(linkId) {
+ var link = _.findWhere(this.markets, {id: linkId})
+ this.marketDialog.data = link
this.marketDialog.show = true
},
- sendMarketplaceFormData: function () {
+ sendMarketplaceFormData() {
let data = {...this.marketDialog.data}
if (!data.usr) {
data.usr = this.g.user.id
}
-
if (data.id) {
- this.updateZone(data)
+ this.updateMarketplace(data)
} else {
this.createMarketplace(data)
}
},
- updateShop: function (data) {
- var self = this
+ updateMarketplace(data) {
LNbits.api
.request(
'PUT',
- '/shop/api/v1/shops' + data.id,
- _.findWhere(self.g.user.wallets, {
- id: self.marketDialog.data.wallet
- }).inkey,
- _.pick(data, 'countries', 'cost')
+ '/shop/api/v1/markets/' + data.id,
+ this.g.user.wallets[0].inkey,
+ data
)
- .then(function (response) {
- self.markets = _.reject(self.markets, function (obj) {
+ .then(response => {
+ this.markets = _.reject(this.markets, obj => {
return obj.id == data.id
})
- self.markets.push(mapShops(response.data))
- self.marketDialog.show = false
- self.marketDialog.data = {}
+ this.markets.push(mapMarkets(response.data))
+ this.marketDialog.show = false
+ this.marketDialog.data = {}
data = {}
})
.catch(function (error) {
@@ -1097,21 +1094,20 @@
LNbits.utils.notifyApiError(error)
})
},
- deleteShop: function (shopId) {
- var self = this
- var shop = _.findWhere(self.markets, {id: shopId})
+ deleteMarket(shopId) {
+ let market = _.findWhere(this.markets, {id: shopId})
LNbits.utils
- .confirmDialog('Are you sure you want to delete this Shop link?')
- .onOk(function () {
+ .confirmDialog('Are you sure you want to delete this Marketplace?')
+ .onOk(() => {
LNbits.api
.request(
'DELETE',
- '/shop/api/v1/shops/' + shopId,
- _.findWhere(self.g.user.wallets, {id: shop.wallet}).inkey
+ '/shop/api/v1/markets/' + shopId,
+ this.g.user.wallets[0].inkey
)
- .then(function (response) {
- self.markets = _.reject(self.markets, function (obj) {
+ .then(response => {
+ this.markets = _.reject(this.markets, obj => {
return obj.id == shopId
})
})
@@ -1144,32 +1140,6 @@
LNbits.utils.notifyApiError(error)
})
},
- /*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',
- '/shop/api/v1/orders',
- _.findWhere(self.g.user.wallets, {id: self.orderDialog.data.wallet})
- .inkey,
- data
- )
- .then(function (response) {
- self.orders.push(mapOrders(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})
@@ -1195,7 +1165,6 @@
},
shipOrder(order_id) {
let shipped = this.orders.find(o => o.id == order_id).shipped
- console.log(this.orders, order_id, shipped)
LNbits.api
.request(
'GET',
@@ -1383,7 +1352,6 @@
this.diagonAlley =
this.$q.localStorage.getItem('lnbits.DAmode') || false
this.currencies.unit = '{{ currency }}'
- console.log(this.currencies.unit, '{{currency}}')
await this.getCurrencies()
this.getStalls()
this.getProducts()
diff --git a/lnbits/extensions/shop/templates/shop/market.html b/lnbits/extensions/shop/templates/shop/market.html
index 29631f08..4aadfa26 100644
--- a/lnbits/extensions/shop/templates/shop/market.html
+++ b/lnbits/extensions/shop/templates/shop/market.html
@@ -21,49 +21,6 @@
-
- {% raw %}
-
- {{ cart.size }}
-
- {% endraw %}
-
-
- {% raw %}
-
-
- {{p.quantity}} x
-
-
-
-
-
-
-
-
- {{ p.name }}
-
-
-
- {{p.price}} sats
-
-
- {% endraw %}
-
-
-
-
-
-
-
@@ -85,22 +42,6 @@
>
- Add to cart
-
{{ item.product }}
@@ -112,13 +53,23 @@
-
-
{{ item.price }} sats BTC {{ (item.price / 1e8).toFixed(8) }}
+
+ {{ item.stallName }}
+
+
+ {{ item.price }} sats BTC {{ (item.price / 1e8).toFixed(8) }}
+
+
+ {{ getAmountFormated(item.price, item.currency) }}
+ ({{ getValueInSats(item.price, item.currency) }} sats)
+
{{item.quantity}} left
- {{ stall.find(s => s.id == item.stall).name }}
+ {{ item.stallName }}
-
-
-
-
-
-
-
-
- Select the shipping zone:
-
-
-
-
- {% raw %} Total: {{ finalCost }} {% endraw %}
-
-
- Checkout
- Cancel
-
-
-
-
-
-
-
-
-
-
-
- Copy invoice
- Close
-
-
-
{% endblock %} {% block scripts %}
-
{% endblock %}
diff --git a/lnbits/extensions/shop/templates/shop/stall.html b/lnbits/extensions/shop/templates/shop/stall.html
index 08e59888..dc6c2049 100644
--- a/lnbits/extensions/shop/templates/shop/stall.html
+++ b/lnbits/extensions/shop/templates/shop/stall.html
@@ -300,7 +300,7 @@
{% endblock %} {% block scripts %}
From f5250e0584c7fc656315e7b28a0ac7b1021d8972 Mon Sep 17 00:00:00 2001
From: ben
Date: Sat, 31 Dec 2022 01:46:55 +0000
Subject: [PATCH 168/226] initial
Added bunch tiles, more to add
---
lnbits/core/templates/core/extensions.html | 10 +++++-----
lnbits/extensions/bleskomat/config.json | 2 +-
.../bleskomat/static/image/bleskomat.png | Bin 0 -> 26909 bytes
lnbits/extensions/boltcards/config.json | 2 +-
.../boltcards/static/image/boltcard.png | Bin 0 -> 33935 bytes
lnbits/extensions/boltz/__init__.py | 8 ++++++++
lnbits/extensions/boltz/config.json | 2 +-
lnbits/extensions/boltz/static/image/boltz.png | Bin 0 -> 38310 bytes
lnbits/extensions/cashu/config.json | 2 +-
lnbits/extensions/cashu/static/image/cashu.png | Bin 0 -> 23602 bytes
lnbits/extensions/copilot/config.json | 2 +-
lnbits/extensions/discordbot/config.json | 2 +-
.../discordbot/static/image/discordbot.png | Bin 0 -> 18252 bytes
lnbits/extensions/events/__init__.py | 9 +++++++++
lnbits/extensions/events/config.json | 2 +-
.../extensions/events/static/image/events.png | Bin 0 -> 141835 bytes
lnbits/extensions/example/example.config.json | 2 +-
lnbits/extensions/gerty/config.json | 2 +-
lnbits/extensions/gerty/static/gerty.png | Bin 0 -> 94857 bytes
lnbits/extensions/hivemind/__init__.py | 8 ++++++++
lnbits/extensions/hivemind/config.json | 2 +-
.../hivemind/static/image/hivemind.png | Bin 0 -> 13505 bytes
lnbits/extensions/invoices/config.json | 2 +-
.../invoices/static/image/invoices.png | Bin 0 -> 9744 bytes
lnbits/extensions/jukebox/config.json | 2 +-
lnbits/extensions/livestream/config.json | 2 +-
.../livestream/static/image/livestream.png | Bin 0 -> 194290 bytes
lnbits/extensions/lnaddress/__init__.py | 8 ++++++++
lnbits/extensions/lnaddress/config.json | 2 +-
.../lnaddress/static/image/lnaddress.png | Bin 0 -> 27606 bytes
lnbits/extensions/lndhub/__init__.py | 9 +++++++++
lnbits/extensions/lndhub/config.json | 2 +-
.../extensions/lndhub/static/image/lndhub.png | Bin 0 -> 67296 bytes
lnbits/extensions/lnticket/config.json | 2 +-
lnbits/extensions/lnurldevice/config.json | 2 +-
lnbits/extensions/lnurlp/config.json | 2 +-
lnbits/extensions/nostrnip5/config.json | 2 +-
.../nostrnip5/static/image/nostrnip5.png | Bin 0 -> 20147 bytes
lnbits/extensions/offlineshop/config.json | 2 +-
lnbits/extensions/paywall/config.json | 2 +-
lnbits/extensions/satsdice/config.json | 2 +-
lnbits/extensions/satspay/config.json | 2 +-
lnbits/extensions/scrub/config.json | 2 +-
lnbits/extensions/splitpayments/config.json | 2 +-
lnbits/extensions/streamalerts/config.json | 2 +-
lnbits/extensions/subdomains/config.json | 2 +-
lnbits/extensions/tipjar/config.json | 2 +-
lnbits/extensions/tpos/config.json | 2 +-
lnbits/extensions/usermanager/config.json | 2 +-
lnbits/extensions/watchonly/config.json | 2 +-
lnbits/extensions/withdraw/config.json | 2 +-
lnbits/helpers.py | 4 ++--
lnbits/static/js/base.js | 2 +-
lnbits/static/js/components.js | 13 +++++++------
54 files changed, 89 insertions(+), 46 deletions(-)
create mode 100644 lnbits/extensions/bleskomat/static/image/bleskomat.png
create mode 100644 lnbits/extensions/boltcards/static/image/boltcard.png
create mode 100644 lnbits/extensions/boltz/static/image/boltz.png
create mode 100644 lnbits/extensions/cashu/static/image/cashu.png
create mode 100644 lnbits/extensions/discordbot/static/image/discordbot.png
create mode 100644 lnbits/extensions/events/static/image/events.png
create mode 100644 lnbits/extensions/gerty/static/gerty.png
create mode 100644 lnbits/extensions/hivemind/static/image/hivemind.png
create mode 100644 lnbits/extensions/invoices/static/image/invoices.png
create mode 100644 lnbits/extensions/livestream/static/image/livestream.png
create mode 100644 lnbits/extensions/lnaddress/static/image/lnaddress.png
create mode 100644 lnbits/extensions/lndhub/static/image/lndhub.png
create mode 100644 lnbits/extensions/nostrnip5/static/image/nostrnip5.png
diff --git a/lnbits/core/templates/core/extensions.html b/lnbits/core/templates/core/extensions.html
index 1b527903..6089eb19 100644
--- a/lnbits/core/templates/core/extensions.html
+++ b/lnbits/core/templates/core/extensions.html
@@ -23,11 +23,11 @@
>
-
+
{% raw %}
{{ extension.name }}
{{ extension.shortDescription }} {% endraw %}
diff --git a/lnbits/extensions/bleskomat/config.json b/lnbits/extensions/bleskomat/config.json
index 99244df1..42fa93bd 100644
--- a/lnbits/extensions/bleskomat/config.json
+++ b/lnbits/extensions/bleskomat/config.json
@@ -1,6 +1,6 @@
{
"name": "Bleskomat",
"short_description": "Connect a Bleskomat ATM to an lnbits",
- "icon": "money",
+ "tile": "./bleskomat/static/image/bleskomat.png",
"contributors": ["chill117"]
}
diff --git a/lnbits/extensions/bleskomat/static/image/bleskomat.png b/lnbits/extensions/bleskomat/static/image/bleskomat.png
new file mode 100644
index 0000000000000000000000000000000000000000..cc728083a9f4fe818727bfb88589fb9bae1e7d51
GIT binary patch
literal 26909
zcmeAS@N?(olHy`uVBq!ia0y~yU}OMc4mJh`hM1xiX$%Y!JXIkPB|(Yh3I#>^X_+~x
z3MG{VsS2qTnQ06R6}Oho&MZq_J*oA-ahDWRt+C9ltyyfQe}0*{m)-Jvy8GLl$?gymF^#Tg_3i+3#&ue*5|A`}WNr|NZ+>wEq76IP>}b
zw*8IAKkNzo{5n{E{qFp;5!WlH?K25^{<`<$-(Io#?(y{|Pv*BTkbC*F%JfPcFYEt3
z594^hq(_)-kJ`tV_~v+E-~C{=iIeD;OP
z!=J5LmNkpttyR4r{%Wr%d|h)$bZ_Q5u`cwT|5_}!wrvfJN3E}5+7w!c^3=40Kjg7`HH
ze>{8M8+T>9^}ZIduoXwj?rk{!!2b86ce%6YKD)QJ==swfSFELit^PdD|LkvH(C2l~
zGTQ!mS6$`$lgp&L=F}BS3Kl*$UHQ`Q!}0Iu^^ckg99$9^)@hYqfUTv`lR@hOUDI_Hm;1@
z{!IDkUGLgYdvYpd4U~mCe`y_A*6Oz*$>)iG=Tw+P2D7N|jbKMK(VbF;tS+{`uvO{u`42T|y@QUjHb~>yMcE|9s_ikzMr?JMTaK
zrkCYyVv>Jkp4OEmw;aBH{I~Azb-hi$-(LK2?8fa?^AgWWeovioqr%*%X{y^>|IFJRap2>QJ8zb?mptuxJ2T&7
zuBJgH$B~_a0`*Z`$>$Gqf3|q|KU41~OPO_f*zI?5oY!9!%D#_2-+jmC`0SgfJ{}1u
z4wH2(npszQzr5jI_1u-`EcVE^mdx~tmf!iNCT;E9&PP#sanp>?q})>3kgK^{xT-8K
zU(kuY;Z4pzuEt8CjII8ct}wjccrH)-u5ZT-(UlCvr}WOqGx{gHB+Y57SR(Asa*EI9
zMv;bPY31Y-wQDyfOP=0tXtd?&gseO%w%49(91Sv6mft8!Tw&9{SS7_JBlz$2C0TKA
z<>k)qHkVi)^}#FSQio#RU8ZfHHNWjG?+P`2c`c(_Q)uPvE2m#A*}HzNy6VkUA1c$T
zgk*{;m{a(#zV&2to$c;Zm-k$&!mcLPHfQNg6V)}&v!*(Scp5u2N1LC0f9mm-L+!`y
z&b{B~v9L7x;-!~|1NB!qmALFuN@DiAvRJkM;l5o9e!My*c`|rvjor@;#wSl-P1L{C
zxX503kClqn&uiE8IQSOZ+-aF|q5bKl&ft^Zez&fFy>H9HB|p9{`*8!~%b?Ph>5RA3j{Yvhg=F)5FOFuA1
zUq5}6KOu$1%hn`bT_Q?NhPm5h)s!c8vwhZBOB<~UeNdmj`v+sW)>i&`Z{jxk_sJ|*
zJLo6yG{aBp#>6bvIQ3{%E~g2^A(tRShO4T
zd**F_md1HycR~HTt4)wwNwpmyoIb(IDGCo7zhgZ)f;yZ@$a^$1b~PLPbJe
zvYy8A90$IXj<(5VTo%nduP=rMB{ACkvS&^xbDVYHzeKcrai8pxQ&9~cuC7i_lMQJN
zs+}uuEP9D+X>-!b^EO&3s^*UQ-gj?LuF9RrQ^K_@@|fmSYYVB(&CY9QiyT-W>3MaE
zRm|g1w~kcy17-7_y)S7BWX@Zzv2|%q$1LCN+~<`KaL&9x<4Lmg-HY0-H=lSe5-Dyl
zcsc*bu|*!SG0irTY!Hy?v!2s8`II|dJdLyF
zuz%XO@UTPSf0jM-PMaq#J~Odpr_`58JuxEM`wwiGACfGw;&a0@3sx6Zv6dHUXEkMa
z-kaRA;mLH@GnGI8WxQ_}{LUko)w*#?
z_5S?9`ni`2Zt^F*WBp^0w23=>oeHyj_cB?po!?_pJM^u0v9_&F
zI24{|a9;RQkDRcI`)iJ{815A-E~zbi(7j=bAm^%G8M)>eUYDj{ekwTQPNJPb*GD@I
z){`+EO0UB**BV?bU$9RjUC7yGcGi^KnyqTjq$^D|Shn7l{&V>n>(mKl{B>s=->kR$
zRk}sPY-;Pi2~)a#7QDavAo`bR&dx(mE!_28gysgS2g|zWFET3>;Hyg5vA9HF`j>f^
zX2zY?-^TY+`GUYybG3`XSvBcPyYHXm7dY*-{gNb~yFlPezTnBLE@<=oh}^r!6N
zXgisoma1#3bf*2~c*(n>^3%`Gmz}TKEozbuU#*z2;C=0{PZ{ro9X3rlslgo6vohUe
zt9#|Lm<4qK0)LpNa@_ckG3iXg#ZneQo)oj##>|CNF6Z|eMNSS^UdH=TsIA8S&sUvz
z4)>0A%~PAdpXB3t8?j)s#tl2$U+s-`^68(|E^y2`G-JlYADdSC3)o9AUO0GvV!;O4
zoY`q#qU>U96(zp4?F^nGz3AIH#iJ$7op
zlfPxZ>{*!1a`Mw28KV&G3rDZM(NVULN_178ue-iX=80`VsO>RLM-2v#ZS}RZUCG|ND1}ncIb*tmYTKSA-~k5fGL=Qf)7JvMt>
zZ!6m=RZKc!&8Ot=5#O89`+nzAXIzRcGU+1?BI{vYMAPKaNjH(7wSo-+jv)@4pS--J8F7IJ?*z=BVDtIp?6h
z!r552sBXe`2eBC~#uE>@FK3N=v4iuI
zx55rE$jy7Gx8&!E1=XfEn5$X*|4(YOyKwT4DsSv*SBH;%snyR`JvMc7kKWnP={jri
zBX?s?c9Fo+P0G!WWc>flasPCXqyNawueVAPL^U_TQ~oD
zF?CH{2%FoVb$kmBZPbh6?|7oxd{BRC#nYMtm*&*&vWga59C_&BzCyOCr4MTCe%^c&
zcYl^`eb%*_{O`G&rQ91ku5lgqSHHa_QDRO^M#gjPiG7lCp{87Ak7m7eTd~vFaHrX2
z#g$GQnN#k^q_V!wc=ymTxN#Hn6wOr~Nz%scXF}9Baay_?O{sNxbVc}H$eTU!m8{qO
zf;MSM{)&54&}*%*u)zI=hDGy}8%&=x7!`SDU2&?J9pxv_wkz~$0Sm+XsXNpj-xf0z
zzM|BT?6>i@sfI_E*gG#--iN!VNvl81k1EYQ!oW8(O7j1O>Sx^_ek=DcYxcSrcfOKC
zIDVsir^`_Tp;x(*YfI~*SubjteaYV}qQh3pHh;s5B5n?6&Bs@~7>q
zr74e;R@GPk7J7B7bcfm>?oj9B=Fex!W=V7?vd-x4XKK&9I60X&O+2cBXXQ#xH`6am
zrl+Kd7&zIOwv_GBy54wO(`_#M#*&+(~>zy0`0;|(u{MPi${=9bUm(aKUu
za9PafQN8B2Yqo#WnHg%$Mh8lx!c7*I?Wnyh6gcC>{oPE@w$$GeUgG;LT}1X;&&>1}
z8vZ%@GyHzGH`<(2ZJ)OFY}4fl`x#E}oFA59V^@~M@^Zzt$83|{Ex5b2_Rx*SDYX-N
z86IC-wBCmO)1!^7e~XVlGBNQCc;}cng=dq~1h*R%zg2SU@61xMoEBE>6R0I{>Sx`?
zSvKq)-&q!J{n_B1?z4i;Gs}FUtU>?g#g6V*_?|VDaA_Pm9D7nRCu7?L-KNHvJc9(+
z#ZMYToC^&bu3A|w3Ti9Mo^NLoyv!^)K=7QhnnI*jWA!3Ar;6k3^Ev-1@8vzy^7_}}
zngx4){Zm{o%jUiF_r#s%`DgVP7X*LLex#k^Zd=`=C>!`EIlga!+T?EShVvRu1BX_+9WRWg!E09U;dn1Gxpel
zvSlh;q&I(b_AK1bw>s>8?-gxN^HbZRBJN4|?pf1rzB7B_y}Ox#+!9w-d4J%%;yCNt
zB=dFbK`y=
zpCQY<32clSXIu*NOm?R$Pu(j2lyB!`wh8O*N~(l?>CCQJoAk2JbFR!A1&RL}b8OYO
zHd}r7;C^!Dfme-!R{U+dqHUY4p3O@L>Ju?NrDIib^a^MCs`(kG`jWLKiFxN8J-O@q
zx~Zy>D)$`!i+yFCztmQ`Wu;iahX1*Tvf2ONTpIHG*KXEd>+H_%*St{vHS?cu
zu{(Sq;AD2m%qm^xMJHwnY*^SnY1vX2Ir~*BgGs_AgzrvcPg}LFK*K6C1me`~}zD{JXw))6ed!m**~N
z^k@?iOuBU^Jl8h$@s?K$3oZ#JxICI#!Npgt!4~p6GJl-&*x^xi}rn*q-g+nZcjC
zX>W`34PT8D%NhC;R%I`Yl|C8McQqh?M>=9~S1EsHL+}X+T
z*17D~tT*TLZm;^e+s$OZ%ObOx_f8z)pZJ5l*Q4X&Lv1PZEe&7hEOdyTvQG8To#Kxi
zX2*oZc@>eDNhNNNHb#VqobP<rko~3c>+$v>8
zg{($j6J2ZRqkDfF8lL7(+atg6PF%OvIu(E7;b`<`~7S)4bC^`TfE(3tG)|)cg`o`W&0#>-J>frxv>v32|2+
z3YUksdZ{(VJ4*X-*B>goBe;ddc45?SHAUUfxoW3w8Xn!CaLD!b?)0((rR;aYUGu&$
z9=hli^?RGc_hr9cGqn7gQ~o_}d7{_FK=m6=+V2V+q^%S}T0S56Y1?^A`-4PNghaC&
zmqqh0o^6#c4ou1xIPADno_kU6(|dY~HC(Q){e>)yVz#Hmp1!%wP_pWqZ}v=U=4PR1
zR~OeFNU+$z`quih>?`4|D~?*MO1-wS#9Xg*g2dm31M?iMpRcIR2tR${+l!Eh9o+Q_
zbxk=d3U}m3Cp64celWrDjP3(@$E7#S3;KncP8Z&uI$K
ztzIa=)a2&$=Z7rgmH2co2dzT64ggdq$
z6|d;;`gr4n=F|&Sr|Oa_3=ZxLuCdc!b9#a58=qyf9vQLjX*DPf;Jdjc%ra_o$G@Yt
zJfTL~_k!!9vREUk8-r)fT@co9b^dt0KosA<2k)3qI-X3^D!T0}mp#3+`%?Y7JLiv|
z&)pb1y=9_bZC16UZhsBKqQ|?cf1TO0?ZraZirG$%-*Y)aqXKWA-8y6AB!#n;7OZ=6
zt?s6HUwFuV7|>_iftW{r^Ta2b1CJkH-$k#jzah?sfCLBm7EiVma&8G_%s&
z!?lkJe&p2ni5%K{`0=61J`W04Gbi&fPhacs`b=wS>f_FW$j|F+&&$;+2kh{ZwZC|u
z>A`#k>p<(kmtWuYW<7TP%HMq_c(u~uWtU@_)N&0oCa%$GUpq7S`HGaF8McidG=3>J
zyi)2pyId$qf58n8mI-k?>iCrs&!1v4Te@qDylIno$is>iQxrPyZe9F8B1uTzv{6ap
zo=QsU)%$%B8+fH|K76LSy_B`}!NXg-mDla}c)&DwYmZ=1qK)b{qX|h?|D(^^>?m2=
zyKITYlS|_-EK$rXEEI|1=Bv{QlZ}~E
zT3R5=a*$uZU0vN(T=QeS=R(&W_Tzsm%HJ1$=M#9Px-3%mal^9kGbuJ|%ZyhiJrGso
zSt#w;bLx!IGZjt7^B+t3<>TKkvN`?x&iuvhXQOyNbROA!#z6as^pVT6MVmj{70wV%
znfhZ+#hE4CLiR-x19|snrGpo^Xzs+W^$N;=_AcY9jjhW(O(i9W|?1j
z%`5cU9`*V}jn(UQiswF4cocC&NTnknz*SH+L||#C`cbDz-;(A2F&@{S=&cwo*dxQO
z;oSV@$HD&(%D0E*6&>>BI@)kf{PsTyHm2PUi_>o3Kfdj*1>>Ld2d3WpH2w8~GwLt5
zP7||as@mSz{d(H6H;mJ*9sW;NoV`i;XC~K9nGbbt_op!a%4>Wu>vnXp{BnMWu3cVt
z7}(nnc+A^$-S*zytM_j2PoMSoFGI0s(Q@Alq5cdEyi%DV5hW46K32*3xq68y`AMmI
z6}bfrAYfx(QIMFNom!%hl$xHIXRGvn_kJaX%oJOta8q9c-vZ~}1OnC3`zAn+mIn+=ATHl0=1y+?>2(s|s5su(?)w#a19;eI*63l9Fs&r3l{u1?T*t
zR0R_~6Fmc6*NV(CBPBa71)HLjG^-#NH>eRsDQUJ!86_nJR{Hwo<>h+i#(Mch>H3D2
zmX`VkM*2oZxJ9L6DxATL9Ko
zkz0VGvLrJN5m+d`0m*>9gY1xs+yc0{D5_z82P+1Lk(En+aw*7po-VdZAp5OS@{==D
ztia49GczOOl++~MRMWI1U6ZsVBi%%EBST%&L{sCWMAM`+^CU|oqdfD9OA_;vQ$a>m
zzX7bCF>?7r6uZG8X23Xq#0PG85vk08R1`)nVy-K
zn1k#pkWnd_DOQOV#;Hk`DdxJ#W=Tf6CP`^2x=9v>X1d0SCMibd$!W>S#t_$}BwM-V
z7v(0F*eYe_CZ?zA7v!ab?EnS1m1BUXt&)+R0YW4oC$S_gzbMyM$tN?fv;rX!l9`(t
zT#^V1O+zztBNG!#V@pd*6GKx|h(%!Y!cvQhGxPI6W*Qpk8AHqkMT?bxQD$mhNg^nn
z+A0}>jjhNnuyQU+O)SYT3dzsUu~h;Yp^%TiOo7Ae4_k~0$X(o<7xm7oa~pf3{!PY(#+CyEz-=)b&U;DQ`1b76U`Ho%~4G+&QB{TPb^AxOi#@#u~l-<%q;->
zMnMA{l$xlj%QI3z;bdTBq-$uTYit-|U}$A%W@T)tZD3$!V4$QA@uH1BC|ARLY@?46
zCJ+Tk#ef}`0z@px#m$b(Mju=jfyyO_fuM4MmKYirw6sD&Vbl_m!gn;dMuUr_5FknM
zXzCgbE|NlkB*mkti)z8eh3N34=B3ywl`GlX-Obp4nSp_Uv%n*=n1O*?7=#%aX3ddc
zU|?V`@$_|Nf5s!vD4?$IY5I$SL4m>3#WAGf)|fVI%mp1tDH82cd>7;Puis#
z_(-X9i9?~_NimNmhJ(D^fj79f-c>bz&aNYNHH%Y$aS_)Ee;3xJ6F9D%-Sd3*^Eoz^
zLKiYjPK#Y~4u~)@H^2Ph!v=rN&_fd4Md!R3r%1E~p0d;r^m1iz@wv<+)h5UwprEur
z=v0W;OqUb^!Ei4_1_h<1Qzm(7_IKDQ6*1oMS~(|3>#>DjnAc1u14f0HyYhm(ESF7@
z5_IBWmN4T{HZ?L*`tl}&@xmGTe+~))Uw_t2ot7FpW0|C>ukk4fZPtKAQ?!D-TBim%
zGAJ@QDsgEo{loC5$537{lHttCe#VCSqjS1f_Ol-d^|B0}FVi5?@aBp-vx2XSrqV2h
zMTJYR%n(RAQaC5^dTD#BK=aKUv&7rqBp6mM59V%o#(iUNwfXLXhfXRYTvLD5>g5oxh@>|
z_)6D1<`0qE=ju;P3}CQXI)(WF;{$;}wgVGh?~qHT#M
z8XFm9t#l094b@fohRm;3iSz2
zmvA$FUL!8p%izJV<8kG^Qiiudr}!B7zeF#wZE!OFm7yY(siUWtXUMbo=C-$K47x5?
zOhixr{;KiU)2&6Id}`2k$>h5|8>4r|th1hcvZS}S_bbzeuL7BjC(I))bUF%j)?ucR#
zYtOyC?c=<8a`Q81IW7%p4a(wv{wMO>PrYLivJDJr+uttxS)`)2UB!0km5wQ&X1(4e
z&+ub{VVS$8>9Q%OzJC2`S+!ydgO0`3t5@Gx-~Y)s*Q#_?Q9?q(>daZcc@J02>UcFZ
z=%|YbW5UacQ4)p$Ua5>d!WTQHY`R-|_cDXu`x_erkQ{f-yP
zsR(s)z9>r3Wr%kxEHwOCW7k|Pm)3Jt_+t06%zYj%*^DiQkt+^=@p!tIqas-Q|Agn2
z@)h&Gw*|dQ_Q^ARCe+y?#-z)@FWl?qsrl5LCwX#eq<+M-uWN!eQM5jS#h#8z(I5Pa5>(3k04G=~?f*Xz6_A
z30^CgO!*{y{&OAg182>rd;k6Wccn({Rv-`>{TvoY8uWj%KiRWp-MYN_i4ymdd-k4^
zxb5t6@#%**vm$pKaQU|~coy%R&shwrb^Gg;rh2Iw-jwrgXK3{(E!`ULbu%UMn%=pe
z=A9Y*f>U3G$9-1a;kEL1(5d?s^S-ZpUOAtSheu@#dw@g4w4<|T&pzz6(%0pS^XX0h
z!j*1Wh3;BCxn`FBR(2(=#GYK$MO8{g+S=N}2NN!Ms!a~!c9$da!^VJXL{8vBs
z^Zz+ZdmGW$vv2({Ek3~?(X%n@*bS?tA#Sk|k+YKp`8XgwhP+Fw>
zu;7Qi;juV|4Q%||7yJsE5+vYs;Iqw+b=IE+ogBPkZFeb5J0{VQ@$9e!^K+SLsRuXd
z%vi>`$o2AD0l~Fw0dGG4T-EWyTv%9Go$<>{8Hag+!NI|PE^B)>Dz_!t@Gq#_=JtM<
z-ldl&Qqs~Jv#x5Hn3yPtbuZkqMPy+B$F$VO;<@^B@}~q%2y*hWRNlAl^`ULKJv}`O
zHg6VAKR3tGXeP^ZnI7G&+G)B=bA+AtXfCyRRvGvEz=4JfOdD=|+5SOD9i96
zea`1FAGtd}y;=l5{QjGfm38P?uXJE|czcCS->X+yAt50PwrmkOm=M5yfXPUgA?TF7
zP{Z!>_m5s(UA?2?qtd$g{e2rZ8cMWnym5cKx7SZK!M}G3=e+*=_ph+V2R0Q}O@@OB
z6PgY#IB#+`>hz|%0}{XQg;eMM&zZk+#R`t>YikbP+gn}u;eq3k$Av6RjgsMCHm_Tm
z_Hb{b{wE$cH@AXMPdw*X6f%kH#~u0c<400`s9`DNspIpW|J|E=`}yUUUozAtYu=F9
zs4#t-gMxq&PqMdx#2@KnH!f;Eo?oh||6Hb>PxjEo#qNbaJ|u>OhAK+%95U#UEDx6c
zFC5cqr@U?cSymP%#v_jlk0coi2;RDVTiiC+(Zx}z%kbDWp^1+z&NVMuw5ZcZZ87Tu
z&w#M7FfkrBeio+2{qwxOT|M{Ba!L>fTl0;L$?S2JPetF{*vS0!=~Dw9XX$FWLlVvg
z5-y&eoIaOb^rkz<$IIKCk3REL$tO%O%)y
zo!E1D;#=gz;pP(*6iyBCMGP$3qK|O>(DqQkdE31v2
zojq2U;j>F_?%IdD^0q#<_`f+(qW+xa{g#u5=2=cW{d7a(VK$rZcZzRpNMv?eIAPl=
zrqjYtiY$5f`4=x=&hG5&T)ElW*~Rf##9{%ZyxZSAOr+j&vNb0qB`N(syryP;xn^p2
z(5rl&!$0(rb4=wvvfTgi!{XKJ*BeV;hqW9|%nfAq&=5H^%QRb{*X^N2WvMua@}f;E
zPbdB}*L+-o(ScqI_w12b7{F1tKmPbX)~@rkN1v|5sad*hOSE|HL4#gYUoVS81+H``5C7;hE6^
ziD>=@|F`I9M}&oa(`FD>R#vVIHJ#xv$DpmNdpBd&v--3iU-`##x4-{>lgs|>C4t78
z|6L4CJsW%HeBQ62rS(hhxy_vXc@FzdXk5L1J%47`&-i)kr~dz}-(&V~Y4RUdlj(Iz
zxA+`bnHqg;0J7br+Z0OQ+N?onrOlZGQc1{bOnPRkFqV1#au?XWYPi1DM
zKHy-`cr@p|`l9!BUoN^=zkdB%{OjKMDe-Y(VPR|!7?R(x_XpqGeP?1&Qc@Dr%^b15
z#|J*|nJE^{{p46vG2dmAbiWrr|JKc##Tj%sIyW}?$%>M0M|6|?jMVmJZFZZ8+Y^rwn1P_%7UP}{ho5ft8o4K4d;T~(k
zuHf@q4!SD|hJ=J9JUulvBRAKzxcKv#jj?Cf9NThj(;EBo*zb<6u1BSto16D~hcR@V
zF!0@c?)~@wUqgh?FQ{XEmiFiI6e|LER(?HV*6^F^M^ltFgQ2Yy50O<1@rMf*=7NzX{owVJbAO8ohi@G&p$rXIQ_z{
zTU`qmDsm`pD0>^l!o+A>8Q;sAU@TZ`F4opLXnq`7*t?7pmQgd$wC!Srt4w!g*&;rEqJ3zO3NF<;&Uk|GxYF!sW}8w?F^(
zx#Z0&-orn31-%L|kf)3i`7iWPsq|G!<{{e5fo!{S7w})sgbxnAuRM2Ah
zttK}&H?_FfSab>lhvKXjf%|M5IE!~IivGNT{lfkG{lCAzKcBNxRO+(gnY+8oA6Cwr
zcV*|z)EKXyO;bJv7|r}5-osFE?ljNg8>x}kv|U857XN&q{o=FW{~-nWesvN!s^vjVDK-Z1>S0i#Q!zU0FF5&9s;D8610b
z;dY(W*~tP891XqeUdv4jnx@yqAR;b4S>Vw7@4vqr9pihzIVb8;hDos7=?G^9feKRr
zjw?D{3@l8Hhc~V9b=lHwc#UE2^Ys^PJP)~Skv#A&LMQC&5!MDnF*D!KM_1}E{Oyo!
z;y+L5*bQ&H`T1vsMH>n%au`>b`xeU!n!c>OkRiq@pPa*={AOi5Dy>pnwA|wPYo^AAoeP)*xLU6XTuk&a
z`*9`1q_QC>Yi;JN_FB96f`cpzcI>$E{`1dkl5H2c_P$?#@yTTA3mK8u>dsZRrbg!6
zV_mRo*Q}Y#b~<|P?Cm(UVEaFlSv5io-5s-R&fg9W2?;TK&-7vL``ON;cUyci
zzNTlRZQcI*uQi+u;a;IqZ68epLtlQpn(;}hxp?2q;(byM7v6uroXiPo1#Jw<;ugH>
z9RGUl#V6K04l{$YVly*ecHInNt6Ryy(tObC+|SKkE0b)uN%=ZDFqpA0HRd)tG}umK
zkZ21$YkA+(W!ESF#hN1K4I4wJrRD~D{ro7pyR1HlLFaUrm*#bYW3Lv>uFW!Oeaz$&
zcV_dUZEv4>s3h4--|HYa%Sr*T4>z?MaS;eqLqHUw-uA9rYzG{D*VaH|hPon!y#waLVWMofT8M{=L2b@9q3E%zrMv
zx_A5b=_x_JM~@yoJLfY;$;?uKkwq;UhF9|SeK7Ey75b=L1(}T}HnU-bdF$AnS?z=cris^tv^waOZv-iKSx7)%i
z!NI~b@A=yKTB=tvOj`LL&sqOq&h^@r>;b{ShnGxA?b*nt#Pzc9a>lea27y3R!K2sL
zMvKSnF4L`D$e<$BxhO{OI*Y=h<35WcgCd+dD*XNZbMy1{e>*XDxG1T*Y~ix{JwIF^
z@L>J_m;GrU1H3|KF1wjDOYQ$5nTFKJrBMu97|xitB{mq=_PWUJaW}kdGW)~MBmdJ`
z7tH90>hPHV_x$t+y@oTEaUL(^D!tlx|9$)U<=3a!2sC(|-X!&L=iyr8tOJ(%sXe9+
z-~XMK_POk$AkYxBN@4&0DMAxHSQZ9&v@>+?pW(8+jA@O&5X1hURdYHPT{vF<`PkR*
zKWjQ%lvuSH<~>)G>Rq&EjZZsccfY*D(#IC6^S2hVF*G_Xh~Wra+iSm{qy4akre@=m
zO$z(#^`<+QmYP2LXkjt`ezSBm}@7Z+c=
zX3d%eRtAPut5*jG2OH-zIk-HSbNKyt@ipmNA~cpdxm-zzympu=Au{ae^tlq3bo@93
zdffs80~xQEa)0`re{YZE)TvW7w6tbj7f_JkS+r$K2=@VH9@*qI_e^|WD>54R8hg1I
z>N5Ofel8Ol8oFZDDlHHG2E(>%*TRlG{#aRhT{0Qe7V-7vZ4vn8I&-!LJMZBYroNZI
zyE7fITz`dWLOfeYXlU!KSyET7T@wpAbK26~)s;0WI(lQ$(XPtB@7fZF4eX{ighZ4+zflWocrJN9mk0F$b!szt#AhFP;_
z1;oVItOq5%4TX>0*2L}>o3_+D?AZ@L)@A2d|Fkpk@bmv>n3C{`r$Ma2Mz~Q_J1pV<
zzrQP1uV#L3v;J|MA`4R^!?pGC{p)MrX0KSidiM9X_U9}YUDPnkzt&&bNR@D
z0}Mi)M|v!t2+wTgAgiCxua{%zmvd3sRM76#$uV{6)QVRtmrw9po+!a~>-KF!E2~-4AI-?ulu~qI
zP1ey^x}{P>Bq}-b+UA~(S8p+&@RvyDU~A6E%yd)`aL}9XY&CaY<+h^*jh!6JGL=NQ
z6y^Gp|Nr}IQT0XR+nbxs1r~cAtTq%>6yZ`7>sFNNJyaUI04gWnK;i*%nT9mvHxVA2~TiQJD!I_!HJBpriy}P%!J4TQF
z+|Q;e*2~9KTO2AYEyLEw_2%B*miYbM-HeP3htSZgP3!*5KW@MSYWH7X7aOs&NY%>9
zs^Hxn%Q?2yV%ziXHVHV*cl*P{@1PlaOyV!&+;`YpC3-VQ3_SFt`(2<>FmPq&EJg+ci|4K`
zOY#}?6m(8I*~q!u*j1J~>rFp=>|8F=hJn
z#e4S1%zK_#Q6ZrnzV67gv$HSWzTG`JoT)o=LZ~sR?zUBjCRaMorXjet%fR>f}7}_#tDIS}VcXLtXr6d6k76A^HfT*Z0
z50$10o4(lHWe>&o|6sjW{eJHr?sTz&jR|u;3nZVJ^=VoUgWYvim&K8%?gbSV8g_Me
z2S!GAhG?-WvfQitoy*dET1nimXQRS0t9nBQ#usJ@vr;`&gbuEY-Mu0Ex}MA8)6dLo
z+*}T>{2{u=WxU;_RfwD3t!Pbi6{n>Stny0RD>eM
zDjAlF%>MRk>&I-9AN}1C(@y&xNN07BpK~pPamKFvgqc%9yp@73Gbw!il5KK&+2jdI
zN|t*+NHdfam-ePfE}SBCncF_tYiAUT$*iZz|6QK=R5B*GJjr0ZkTI>Ffni4E7fA;8
zhULdy7f0#|Hf&mch(Us(D7b5Z=9vFWp>>d2M(O
zH{5@JefvM1HS81Sa{W!zSZc%|b2o5V0Z%+fV`nGlmoHxy?A25sB
zZnr*9?tb%<ebk6Yjs?AE43l=uy&C+kJS{4S6?f{8d^}&Qp(gh4^6;9|
zo6=ksELp;G_3G6X>(?K@wKeP0y?L|lj+ve+)
z;1w%ZHr`(!9e(cc4DY2OUS2O58V*W$3r-YkurdGl@L^)io(jSL|K9)aGrwOWY&V}@
zRaMpdUF6?7d!>mSLY*ve)o)E>c9n42|9Qy2vHX3UiJ6&_zy;Yi^4FOeCa@$hPxd^Z
z(y&Hu`k&OC9G>a=@yGW6d%geBp;qqGhd7o^ZuwP}2nom1yO@!X_
zZ{j`-Q-VAdk1SzNxczNMqC^zKrztUd?d#U*{dtpae|T&5^#_%42i1>CBtMwbY{+*0
zvqH-culLWd@Be4&bJ-;@P%vqu1dp@%#;OoT0jHi@+vc(|s4bOYop9~(r1^$L;o;(b
zbFErGKR=(Gl(gtUZ#+liqen?=Vs;Ak_V$8m_p5rY?(Xb<^K3f#?f(euoYNN}pU%~h
z5#lwIS#3L`J7db19Z%+e^k4t)c%Q8HkLht2
zJoWTZ12o)>i7P~dEo2o>lg3ci`(F7zf|N;oqgEas8=w|?I*t!=rt*^~~g3|@ZW
z@@3=YE%Q<%U%YEx0+PdVP{#da2<3>=3|&YB4JCV%p2ShxR-^7NhCw~KGj
zyQ_7gMO^UUCY?2ro7q4^+M?vice)495XO9Y`ncaKRG9d
zr|aC
z#npZdeRF@m{OfCLolT^U9_0%hs(24SL+YGQ2b=`luDYzh`^p`gLWo?w8Y3
zzde}}#OZUDfgxDDi9=$#$V%8+0L3=RWKN}3SC}T)Gd-~2`}O;GU}R*Z#J;7k4;k>}
z+}OaVzxRvKw!FJtQ?ktj77uS+uM~Tc$zvUc&HRSJj6Q3vY0J4
zHum$R^E2)ReX+TFEU7Q0Mc}*b`QOU?2?i28b`9?Y=7ci^aQygNXJTslklp?VO0#^
zQH0AjWzP3^cMs1rPXF+E{(qTuuk}1QH@{0XG(X#`!B!q!Au{vyLaze)cvv0i{Hm{{_~fOcmE_F_`K)#k2|6a#+xUf
ze(J`1ct_{?5YEhL`j>TgExM?&@9WxpP?HYSdVBQohxoczL0(_Je0gwXWw1r{HyxqQ
zmb^O?)0rMv_?ZjVZe*yKXPzeErY`vMRaa_X?rjZC&4Zv;(XCrud#k@E9qkh3SN&yo
zzL}jLlxY6^d2WCF#fueY|Nb}$2HHRWbM8y8!{sv`m1eWgwmL10TY5#|i>u;kF(s$s
zVq-ozn~v%6b&~G=a&s*Y{cg>l7UZ!s2sA3JU;op)r>{@Uh4FHcQQgvzNru$X}cc}n73@-p8fdz=?uRzZ_Vm|FX#Vz*{}BeGt-3Bo~^~_%Ci|G>?-E92WYe`
zpI60o|JSwc8&Xe;g@lFGtvt=|^G$nC@pC@AKOdZHzFc%Kka1W4`-xw1>6U&HpgjHb-QQk}C(<@=JOBK3LXYZ~9l9?U2zY6-G&_QdskLj@R{VOo
zd}G<$Tanvu&f2%+$=2&}hu21L-}3SK`j5*kpU0j|+4NtV^}x1VeE|*?hC7S~ObdMa
zA6`1VNhc&MEa~p9Qj4M|9P{daWlotk?cAx_`F0(jUoM~j=>EUA_b*(&yg2*QXYuJB
z?+VXVe^7t-&tRW|+?Odq*L^PUIV^FRX~Pi(B@;8VM?0U-JIt-Wr(xMLHJ0Xsk>)Q2
zoE}W}w>wz!M{>PUReqFkt
z?B4g_!^6W7+w){!y?XWRO62uInL9g++0)O>Ir#DM@zS~V|0aL5d7or9TXxn~28lMs
zsX?+XS9lM!+DU9t;i>=n>Z(P>2L(_+wd`$_%i@cc((kwYwy&Li{>Tv)r-cF*&wr)F
z)m2p|H#$7%y8m8YR$6-YTm~K<9uJ1jXV0GHExp35wCZ3wSDQ&|>K{>2(HlDomAB>J
z?_*}?OE}sk+J9%lWfNZ$6O#p7w~E&7mp9M51Fq<@DUQA7zbZMlZ{(OPZC&vn#gZV4?(D(ng80_xF0m<0=@Vx8*oSM&3+*d)fGy
z0AKsY;^%&AqPK%8){5_U%R$|iJA13ePo@a1d;RL*dFCZk_+^g&tVl?B;GO^XR}iCy
zmewwo2NT2^4mBCJ?b>Apnic*1{k^-5oO@v4#XUz}_Za@Dv7521Gr^#v!ltj8o&V99
znZ_1nZzS^W?pms@U|;cIS6;N$+&U9R54FjARaI5zYb|x_II;e4!=c9FzP2{DEnBuM
z*toIK@!JbYP?wW~tyx1;)3LJB(k%B@%eQZ3GS+20Z{NQCVDp~4pEp}fhW&u-(vWJs
z>6_&}84N0Qe)LV*G}kc9%Vn3lL*vo^%*@O$-o0}(k#aSWiro-ha_HfQGl36x?XtRZ
z_3Fc4UtjO2`DvsVyNl&`VZy9s_Y5nF-qrU{`4pugA|)dwCDgCQaN=nZ=V1f84?k<9
z7tU@G;aT_k(Vd;epjIrXpIZC-+rydZ^BhA%MQiNtH*q=|NE~@}b@jzNcY40QzP@-@YvQ)s;Ex(MFa7rFV6b-)x(*X>LbW=L#48!h?G_
z7}Rc;$M3BYef#$9iggTZmV5idN%i^Q
zg1GdRh|PKCWS?s0&)w=v-voKZo=-92Jo9K?|N1+^LQXy2n)X|CviFKL9BMXXJH2Va
z6sPED>07sMZ76*mCSzA4@%s9D_u^vXq>VS4WS?!+;oca*BPuGYp{3;%94tJq@|h&4
z!=8SA-q8mS6qp(t#F{FF7|wq_S2?eod4Bf4pC_0WOgSZZQBui@CBgQ~Tt?Q{fvBGEy_gGMnLi8s)?PZvr6i2wqV4O-pR4{qJlM>B?%Z5!>y8z%nx+emSDw@n1x28L
z-6!Eccklm|t^2ll{=;WyXG=&+H^1J+Ewvn@
zb;kIr2sv;5>%CcvdvosXlTVA}XD{2?Tl}x-8`Ctl%h&t+`}&d!3IxjE-8neRGt4(GFTeb^_-FNIp@#ea|9#)I
zWy_bQ)JSoLH|!6tD{U}2#B|u;!7jbLdwV)T{Sr_+`q!^t1@G_eovFL`XUxLizke6L
zy=A&(`}W6APEOuY_ExGzz>59DkK&vB5B4rxxbR@5I0HjgNy!#{q0Sh_3kM~l`D?uj
zJUJUPXBqekZ_m4Xs8d+|!}R)}(>1iUot>Qz*R1jLP@DYX{rlrjPfsuW^P>>7`efeo
zmy=sQZ~n!vxl~WOtx$QYSE{%Z0|Ud$l3%;-zqfz)`RA;y3_7u8Z*R3;T^+u7-MV)h
zyW4VZA3Je^W7n=-AI@37f3Wxay~8I?oOp72xpc?5=?r(yq-}n$x%5);-`&Nt8XrD<
z82$C@S9QicO(#z|)cz~(Oq`|m?d|P$+v;x$a{cz37&sIkSn%!Iy}R(u4a1ln1&nj&
z&i!?-;itO0%X}`yd*V({#6lPto-COXJMVez0ZaXBYY$HsYfA)GjyHCf>u<}sX|z1g
zasJWIHh1>d%ZIOvIS9&UA0N4vl$W31@n=@0vzO+7g%%6;gp87s5S=Xy2MXt~w8)*VH`~pBKQZs~tj(a&?3B{dug8NcjQqsEmIpBsxU-PkJZhJnR(grP{
zS+#og^FPLFYHAZaR4!b+*mym@UKZ49Yin;W-Tk4}^j|c?IaQ%fy)%p6l*h3!G<>qj
z58?Rery!8=Y|i_O#eq9~w)5WJS*$*<{$FKCc=&lM?e$Zdo>zJ-zYIzgpk8A6{aW!(
z7p85who66b$rz_FB}nu9=X)P*-Ur(pi=Ed$rEO}^vyK-mf^P%7aupXTl*Bq5pV!EL>2o-!xbz5UcD?$0Ue4WJrPm7g2XXD)yBAbquZ!E;
zWt@KQ!LP5cKiK%kbN@>T``_|?G>gII*S+i&Fzw~4CO&y_57AtFO
zP-@%r_uFlcr9ltoyngd<^4#6Oy;fdsba+ryUtgaV$jZPl>Ex5I4hkQ%kKNExT6H;i
z%KyB26`_OEbfZ7?=GQQ5hp+n+^te1$d}jW9?`;LO&c409eImtZS4+8IQoZKW
zoh4R(*^+gP(|>sL&0aw@4G}tOCLLS?9-}Dc0ol6
z9=+v*?r<}%r0kY@KAXqcro&LVVLI9Yr+?~UfgOpK3nFnLuKX9?~}D|+nj#>(YLp^E332oHCZp41crvT254-NSyLaQS$fjs
z?ER@zr+)qR{kyV0>w?XjgBeeRg@(@EqOAM{h9UW^6GjMDc8zM%R>*B
z?%n61na`=%w=`(w=94K#n+5hKD{j*0d%dfk;lt^bmz%OnG(Kb)R)>a)N=Zo-e0_D*
zQhg6(Dd4VBZ9Z8mlgoG2=lU()zFmCo+_|8p=*Mf(`HIs|iykSARb)xpc%w&sb4xj+
zNW+$(S5tCt=eId6e3)XS$?eKuU}0f#OdMX0~YMO3r!a
z-*w}cij=*%(Fhu&Iz3I-fQNbU;>9msy*l*1?z=UpwekJk-DZJB37cNdd%kelGB)e7
zHx*0jSXo;ZPuX=wqI&nrl`k*tEPj5(>F*-e**|0S%$H30_5V*@y=X%|!-aI`=Nb&W
zyu2BCd2XShSI-=swfU>us@1DM9^%$dxWBKKp&@#E-omwOc|mJStgNjKO-xv>mvS$g
z<*(VgXps_V+znJcc%GcJ)?AoDDCpGuCq+B?#V-924`HaPuAVz-(j>lPH?rFjT_lsw
zXKkJr&3EAa_r(hrGV1U7z!X>i_iN<+dBPklAJXf;rQg_9sx5Q;pu2o+3n)iEI@-OX
z%}?q{
zopkca%14hLSu{u;i`aaQ{fsY5R8-W5H=EBd+PhaaHa0eXS621B_6VJ}W_JEX+qQ{0
zJ3E7HxmWpIw&u^r;~r|0S#K^p{@Ety-X6)_>}{COO7%ItVyocGe8@(&%c
zb)Q@r42+BxF??}XnpobsqOGIO!zJ)V^5M;DQoTu+m-$vaof`h(_Wgfl&y;%4uW#_u
zWMN_q2@6}aX_FAYoQ*?twDi4-$Gl4}Prkc|`|OXUQxudy13gQp#~oT1yE`B>bZf}u
z`gK9CRtY=`UzQo?x%~1?^^e~3J-!|Pyr=B2LEXYBQJyZ-n)5P=hY@t(C
zyF+^8HJ0vT{L^Rnjjci5EYshGfsKvLrL^>G>0yI^^E$jbUu3=rdKh`w(p^XFaQXh<
zu_58%k8f;D1_hhZOdgx_%~OPW41Iij7VO?F?b70-)*Pc3K7sA9WqWEQsPV=pZ`T6~
z>}AW+R!Z$xS!AgBRDbi$H|upy&)QM{e_vD(69WTi>G0I4Q=c>bDL-sbEGYqlaF?Zf~I%w)&H*rwS>OEzt5h`vn*3-`s_CXrgLAMelA+~%U@HqMc{)C
ze~%$(B=5ZKcbT$xcRG(9b32l>ap|304c(R>Tc20v^YQT9Vg2Cpdj0$-Y#V0Hm?3dG
zZS#J8UEQ@aUde8Vn*HrfV$Z@&n}k$VRXdI*Wn^R=n5gUyTJdCE{to0nTU%QR35f$A
zJ{TN3eq4TKTf)NdZwUq+9x6>9DovLz1%W2IcI}e-`t>WQ0bf~Z`RdiHfbj6`S6=^B
zUoz$W?sdPr7cN}rZ@81SW2qLyiL}j|_UzeXKKtyyYwy3i^Bn%ct+cAekLw)g=}n+T
zhOx1{nyPH4W_uFJHbSl$40f^kG}~+KFRg`Te;a2SAI=+Y%K7I22^~l%;wX?b;=^
zb?a7+_QR(iuBrF)`q`O#+y0x)d*R#L#8o3dAMrlW*47qcA+s+n@>+h2K&;mga|
zO6pg-8>pSL)Y8@#>~-5%`T1FU%xk9~|1Q_pg-J|vTy5y@ReALDp83KTJ7e_B_hl@6
zn)K&Vh?eU1oSQ|Z6%{{@zbx6+Q(V{3acYaFeO1N=p6C=&EhmnP2PJ-&hK63v|5Nw>
z$IHvhuWk}zV6dqDRkCjD)~{Y#Q-7^@;*ft{BQGfW^1JiqDdBZXuh=fVa=zn5eaw!6
z#Dt6D{YU>iO+P=+*Z7zpr{X_fub+bR9{gMG_PJ8bB1gz;Wt`^I^_?%w@7}!|`}4z@
z?mC_`g>&WzJlei2bC%ai0m0m(PyRBQ+@HL9{hCWPH-9U(M64)xVS2&(VO!9v{d~N<
zcTY`!9{1(I#{K)_ITeex|P191g#3!{5nnG(f+Sr
zzQi0<3i&Czgu$n4qt~*_FXLOB7V7wJYV9~w(R1X%jT;g{E3cfH`6IPtWlNgavHaU+
zYHDiF4l{o+nEK(f&HMdJf>yq(G5uE~+VIkjNx0GBK{kHYlRws}GUK5swoS6lk0zEz<7__1T3tlph@{m$lO
zijg>1D_h43@yTahwL4!-+!f_>e^YQwOwZQr>w4YZswSHkFJ8v3>P
zNJ@UJ`?6U6u2ucNKN|!NrSyaf1hN$0I66a&LCLAM*7nrt)1?v{&YVo!4C-!x$|g{|
zx&KzNg67gaFLu4#9~u(!=h3aL+26S`dAQOzYrfwt4>gf`d-iG3_RbRz=a?sadSz%Q
zF1R6Ay3>Wp*VlK&nl(p4?lx{~OWpSA_O{;N-`_tr-~W^MUe#;eih1VOzdJB_cxv9?
zp8I?Ll%V{OkdPjmlX-Thy%-o0N=ib0{rzXiP(C$?Un03sY1NFk#R7sY0uqvvjk&i!
z{QhfvH*E`tj(pKhnO(bgf4r5w{^6CC!7pCFo}F!>!O+90Sog5;pMHJ2JCD
z6>_Tq(xP$x@ORn-U3pw8U#dk6jNe+8!)&9pqbHb`sblwBMT!nfuA-?D!F
z`&|Nu^qv%IRqWrsbLUL!zRmMG>JlW5dAnTUNw^l>+G7|ot@Y8PBv4yx=~C9--d@m1
zOJ${HS67z+2g|gg;8UvN_qi1lZkq+I6lrU3zj*z6d-V3ahqtz7?k;Q)Y@s)_1-r)qLubpTAz`%f~38#{Amay)P@{d^Eq#Isg1O_puw_GzDh_H(hxD
zU0H??ghaRwzW;uBlTN|8t>OU=9-4|vTVC%v@wDhfiqVW2GaQU&&dJ``&-8#nq9peF
z^uEV?(q=sqWw<$Sp4?86V_BN1`yx!Fbop7BqZ_)R8^Q~LACdmWIeT^vfLo*`8wbQ+ogX`?Y_6m2R9OUv-k}z>OOcn{KW-E+MV2
zr?>A_^)k|_BS=r5Kvjb~oas$j35#hOq1Wsh4v{rofU{@&{A
zaa|w(iEn%pb^x@N*F$B~Y{PB+9p8j59$VS$I_s&>3vJaYLAG8i%^2jX=9RD7zWuuz
zL;23$qno_Do)+!QF`D@%#OtmBPx1sqH-?T5CWe|@Hq*B4w!gaY`=%+MzR&b|7H>53
zjCFH!b8W?;?Vqw3BT{@W`?MZ@INNJw@w(S)7SHG3)qG%OzhR4#qfgniU9T(e<=dYB
z?S9#${%z$w+w-62IIVqrUGaboXa!-bz*4*U`tK_5y)5$`W5ANu1rH_$AFDt8
zc~3dR9^IB5FL&iBP4svXz2eL1zaJt*#KptgoEC1KmYU1>fhRdfU2Qd|X=}Ug(mkuD
zSUJ1ou6w<0dCFKE9%u$34F
z-n(I=)?FdQVAU${{&~^P`M0kRH(&pR<@yOh
z+oak)+9bcpR9bcD$+||d=A#E^|5BM6v|Z-7<+>?V{l#_4(@!srxqh~{x0ln_CyL=&
zYk4Ar4_0*vG
zLV>%3UbQ{8FrN~%UCyS$U`e|>_kl&OTuYZO)n;luPBkiD^U6CzfFOWm7)g-gY-f;+Wa~`}9sUO!l7VQ;YzX4vFn!u7yUZ~E^WrFYGiURk~{Aj4q#Y0ul+
zj^4O2pX^9XaA+->|06
zbqj-z_OOT8_|K8R&7J|$Nvv$9oX)~crS&>ht>xiNKg!=Qx4-|3m
z>vk|$-|Tr?Ryd=xBdkN2>Fty&pV%Yx$|Kjk5{PRTxVGw7*As3A?n&G{6Vr+rHecNO
zV}0rr;RBsd4!5?wXDE3baGhUsY5CGAx2Ig19rgP5+p_BIx8E+CR;Sdky!ULH^6$U*
zj!NuZwM$|#yFjp4_|hrTf|1tGudS+Dyh{JL%i`A?oj)+xcz%E3d$9L#5X=5a{ZEqi
zKQ(9nvBz$m*QUOzzcFe`>3`=j-kJO-CGEYpX0`F{>rwfxPt00A7#^$e(TvnQ8ss%~
zi7a3H%kq0J48s2y_Q&WYb1dAs;>Dcqt=C#-#?HQ3%%D9Io!FQ!k4xeTezw@}q{KN56_1dot4_0^72u`$q
z|2O^pUvq}NGndXO_R?HB=krSW2_Ic9m8vn#_+Wm~#Me0gcA1)zacFMnR#{$e-yXvi
zSqDDwJ9=t)sw?@q-0@%#5bRqazI|7xhuX~F`_6B@#-cDYa(=Ph)hPd^Q4#bP0l+XkK^(wmm
literal 0
HcmV?d00001
diff --git a/lnbits/extensions/boltcards/config.json b/lnbits/extensions/boltcards/config.json
index e46070d3..97a88029 100644
--- a/lnbits/extensions/boltcards/config.json
+++ b/lnbits/extensions/boltcards/config.json
@@ -1,6 +1,6 @@
{
"name": "Bolt Cards",
"short_description": "Self custody Bolt Cards with one time LNURLw",
- "icon": "payment",
+ "tile": "./boltcards/static/image/boltcard.png",
"contributors": ["iwarpbtc", "arcbtc", "leesalminen"]
}
diff --git a/lnbits/extensions/boltcards/static/image/boltcard.png b/lnbits/extensions/boltcards/static/image/boltcard.png
new file mode 100644
index 0000000000000000000000000000000000000000..ce79906eb4fb52d29f26d6941dd2338197a5740f
GIT binary patch
literal 33935
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