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 @@ +
+
+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 @@
+
+ Each Stall has its own keys!
+
GET
+ /diagonalley/api/v1/stall/products/<relay_id>
+ Product JSON list
+ curl -X GET {{ request.url_root
+ }}api/v1/stall/products/<relay_id>
+ POST
+ /diagonalley/api/v1/stall/order/<relay_id>
+ {"id": <string>, "address": <string>, "shippingzone":
+ <integer>, "email": <string>, "quantity":
+ <integer>}
+ {"checking_id": <string>,"payment_request":
+ <string>}
+ 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>
+ {"shipped": <boolean>}
+ curl -X GET {{ request.url_root
+ }}api/v1/stall/checkshipped/<checking_id> -H "Content-type:
+ application/json"
+