mirror of https://github.com/lukechilds/lnbits.git
Arc
5 years ago
committed by
GitHub
11 changed files with 1167 additions and 0 deletions
@ -0,0 +1,10 @@ |
|||||
|
<h1>Diagon Alley</h1> |
||||
|
<h2>A movable market stand</h2> |
||||
|
Make a list of products to sell, point the list to an indexer (or many), stack sats. |
||||
|
Diagon Alley is a movable market stand, for anon transactions. You then give permission for an indexer to list those products. Delivery addresses are sent through the Lightning Network. |
||||
|
<img src="https://i.imgur.com/P1tvBSG.png"> |
||||
|
|
||||
|
|
||||
|
<h2>API endpoints</h2> |
||||
|
|
||||
|
<code>curl -X GET http://YOUR-TOR-ADDRESS</code> |
@ -0,0 +1,8 @@ |
|||||
|
from flask import Blueprint |
||||
|
|
||||
|
|
||||
|
diagonalley_ext = Blueprint("diagonalley", __name__, static_folder="static", template_folder="templates") |
||||
|
|
||||
|
|
||||
|
from .views_api import * # noqa |
||||
|
from .views import * # noqa |
@ -0,0 +1,6 @@ |
|||||
|
{ |
||||
|
"name": "Diagon Alley", |
||||
|
"short_description": "Movable anonymous market stand", |
||||
|
"icon": "add_shopping_cart", |
||||
|
"contributors": ["eillarra"] |
||||
|
} |
@ -0,0 +1,128 @@ |
|||||
|
from base64 import urlsafe_b64encode |
||||
|
from uuid import uuid4 |
||||
|
from typing import List, Optional, Union |
||||
|
|
||||
|
from lnbits.db import open_ext_db |
||||
|
|
||||
|
from .models import Products, Orders, Indexers |
||||
|
|
||||
|
|
||||
|
###Products |
||||
|
|
||||
|
def create_diagonalleys_product(*, wallet_id: str, product: str, categories: str, description: str, image: str, price: int, quantity: int) -> Products: |
||||
|
with open_ext_db("diagonalley") as db: |
||||
|
product_id = urlsafe_b64encode(uuid4().bytes_le).decode('utf-8') |
||||
|
db.execute( |
||||
|
""" |
||||
|
INSERT INTO products (id, wallet, product, categories, description, image, price, quantity) |
||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?) |
||||
|
""", |
||||
|
(product_id, wallet_id, product, categories, description, image, price, quantity), |
||||
|
) |
||||
|
|
||||
|
return get_diagonalleys_product(product_id) |
||||
|
|
||||
|
|
||||
|
def get_diagonalleys_product(product_id: str) -> Optional[Products]: |
||||
|
with open_ext_db("diagonalley") as db: |
||||
|
row = db.fetchone("SELECT * FROM products WHERE id = ?", (product_id,)) |
||||
|
|
||||
|
return Products(**row) if row else None |
||||
|
|
||||
|
|
||||
|
def get_diagonalleys_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 = db.fetchall(f"SELECT * FROM products WHERE wallet IN ({q})", (*wallet_ids,)) |
||||
|
|
||||
|
return [Products(**row) for row in rows] |
||||
|
|
||||
|
|
||||
|
def delete_diagonalleys_product(product_id: str) -> None: |
||||
|
with open_ext_db("diagonalley") as db: |
||||
|
db.execute("DELETE FROM products WHERE id = ?", (product_id,)) |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
###Indexers |
||||
|
|
||||
|
def create_diagonalleys_indexer(*, wallet_id: str, shopname: str, indexeraddress: str, shippingzone1: str, shippingzone2: str, zone1cost: int, zone2cost: int, email: str) -> Indexers: |
||||
|
with open_ext_db("diagonalley") as db: |
||||
|
indexer_id = urlsafe_b64encode(uuid4().bytes_le).decode('utf-8') |
||||
|
rating_key = urlsafe_b64encode(uuid4().bytes_le).decode('utf-8') |
||||
|
db.execute( |
||||
|
""" |
||||
|
INSERT INTO indexers (id, wallet, shopname, indexeraddress, ratingkey, rating, shippingzone1, shippingzone2, zone1cost, zone2cost, email) |
||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) |
||||
|
""", |
||||
|
(indexer_id, wallet_id, shopname, indexeraddress, rating_key, 100, shippingzone1, shippingzone2, zone1cost, zone2cost, email), |
||||
|
) |
||||
|
|
||||
|
return get_diagonalleys_indexer(indexer_id) |
||||
|
|
||||
|
|
||||
|
def get_diagonalleys_indexer(indexer_id: str) -> Optional[Indexers]: |
||||
|
with open_ext_db("diagonalley") as db: |
||||
|
row = db.fetchone("SELECT * FROM indexers WHERE id = ?", (indexer_id,)) |
||||
|
|
||||
|
return Indexers(**row) if row else None |
||||
|
|
||||
|
|
||||
|
def get_diagonalleys_indexers(wallet_ids: Union[str, List[str]]) -> List[Indexers]: |
||||
|
if isinstance(wallet_ids, str): |
||||
|
wallet_ids = [wallet_ids] |
||||
|
|
||||
|
with open_ext_db("diagonalley") as db: |
||||
|
q = ",".join(["?"] * len(wallet_ids)) |
||||
|
rows = db.fetchall(f"SELECT * FROM indexers WHERE wallet IN ({q})", (*wallet_ids,)) |
||||
|
|
||||
|
return [Indexers(**row) for row in rows] |
||||
|
|
||||
|
|
||||
|
def delete_diagonalleys_indexer(indexer_id: str) -> None: |
||||
|
with open_ext_db("diagonalley") as db: |
||||
|
db.execute("DELETE FROM indexers WHERE id = ?", (indexer_id,)) |
||||
|
|
||||
|
|
||||
|
|
||||
|
###Orders |
||||
|
|
||||
|
def create_diagonalleys_order(*, productid: str, wallet: str, product: str, quantity: int, shippingzone: str, address: str, email: str, invoiceid: str, paid: bool, shipped: bool) -> Indexers: |
||||
|
with open_ext_db("diagonalley") as db: |
||||
|
order_id = urlsafe_b64encode(uuid4().bytes_le).decode('utf-8') |
||||
|
db.execute( |
||||
|
""" |
||||
|
INSERT INTO orders (id, productid, wallet, product, quantity, shippingzone, address, email, invoiceid, paid, shipped) |
||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) |
||||
|
""", |
||||
|
(order_id, productid, wallet, product, quantity, shippingzone, address, email, invoiceid, False, False), |
||||
|
) |
||||
|
|
||||
|
return get_diagonalleys_order(order_id) |
||||
|
|
||||
|
|
||||
|
def get_diagonalleys_order(order_id: str) -> Optional[Orders]: |
||||
|
with open_ext_db("diagonalley") as db: |
||||
|
row = db.fetchone("SELECT * FROM orders WHERE id = ?", (order_id,)) |
||||
|
|
||||
|
return Orders(**row) if row else None |
||||
|
|
||||
|
|
||||
|
def get_diagonalleys_orders(wallet_ids: Union[str, List[str]]) -> List[Orders]: |
||||
|
if isinstance(wallet_ids, str): |
||||
|
wallet_ids = [wallet_ids] |
||||
|
|
||||
|
with open_ext_db("diagonalley") as db: |
||||
|
q = ",".join(["?"] * len(wallet_ids)) |
||||
|
rows = db.fetchall(f"SELECT * FROM orders WHERE wallet IN ({q})", (*wallet_ids,)) |
||||
|
|
||||
|
return [Orders(**row) for row in rows] |
||||
|
|
||||
|
|
||||
|
def delete_diagonalleys_order(order_id: str) -> None: |
||||
|
with open_ext_db("diagonalley") as db: |
||||
|
db.execute("DELETE FROM orders WHERE id = ?", (order_id,)) |
@ -0,0 +1,64 @@ |
|||||
|
from lnbits.db import open_ext_db |
||||
|
|
||||
|
|
||||
|
def m001_initial(db): |
||||
|
""" |
||||
|
Initial products table. |
||||
|
""" |
||||
|
db.execute(""" |
||||
|
CREATE TABLE IF NOT EXISTS products ( |
||||
|
id TEXT PRIMARY KEY, |
||||
|
wallet 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 indexers table. |
||||
|
""" |
||||
|
db.execute(""" |
||||
|
CREATE TABLE IF NOT EXISTS indexers ( |
||||
|
id TEXT PRIMARY KEY, |
||||
|
wallet TEXT NOT NULL, |
||||
|
shopname TEXT NOT NULL, |
||||
|
indexeraddress TEXT NOT NULL, |
||||
|
ratingkey TEXT NOT NULL, |
||||
|
rating INTEGER NOT NULL, |
||||
|
shippingzone1 TEXT NOT NULL, |
||||
|
shippingzone2 TEXT NOT NULL, |
||||
|
zone1cost INTEGER NOT NULL, |
||||
|
zone2cost INTEGER NOT NULL, |
||||
|
email TEXT NOT NULL |
||||
|
); |
||||
|
""") |
||||
|
|
||||
|
|
||||
|
""" |
||||
|
Initial indexers table. |
||||
|
""" |
||||
|
db.execute(""" |
||||
|
CREATE TABLE IF NOT EXISTS 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 |
||||
|
); |
||||
|
""") |
||||
|
|
||||
|
def migrate(): |
||||
|
with open_ext_db("diagonalley") as db: |
||||
|
m001_initial(db) |
||||
|
|
@ -0,0 +1,39 @@ |
|||||
|
from typing import NamedTuple |
||||
|
|
||||
|
class Indexers(NamedTuple): |
||||
|
id: str |
||||
|
wallet: str |
||||
|
shopname: str |
||||
|
indexeraddress: str |
||||
|
ratingkey: str |
||||
|
rating: str |
||||
|
shippingzone1: str |
||||
|
shippingzone2: str |
||||
|
zone1cost: int |
||||
|
zone2cost: int |
||||
|
email: str |
||||
|
|
||||
|
class Products(NamedTuple): |
||||
|
id: str |
||||
|
wallet: str |
||||
|
product: str |
||||
|
categories: str |
||||
|
description: str |
||||
|
image: str |
||||
|
price: int |
||||
|
quantity: int |
||||
|
|
||||
|
class Orders(NamedTuple): |
||||
|
id: str |
||||
|
productid: str |
||||
|
wallet: str |
||||
|
product: str |
||||
|
quantity: int |
||||
|
shippingzone: int |
||||
|
address: str |
||||
|
email: str |
||||
|
invoiceid: str |
||||
|
paid: bool |
||||
|
shipped: bool |
||||
|
|
||||
|
|
@ -0,0 +1,64 @@ |
|||||
|
|
||||
|
<q-expansion-item |
||||
|
group="extras" |
||||
|
icon="swap_vertical_circle" |
||||
|
label="Info" |
||||
|
:content-inset-level="0.5" |
||||
|
> |
||||
|
<q-card> |
||||
|
<q-card-section> |
||||
|
<h5 class="text-subtitle1 q-my-none">Diagon Alley: Movable, anonymous market stall</h5> |
||||
|
<p>Make a list of products to sell, point your list of products at a public indexer. Buyers browse your products on the indexer, and pay you directly. Ratings are managed by the indexer. Your stall can be listed in multiple indexers, even over TOR, if you wish to be anonymous.<br/> |
||||
|
<small> Created by, <a href="https://github.com/benarc">Ben Arc</a></small></p> |
||||
|
</q-card> |
||||
|
</q-card-section> |
||||
|
|
||||
|
</q-card-section> |
||||
|
</q-expansion-item> |
||||
|
<q-expansion-item |
||||
|
group="extras" |
||||
|
icon="swap_vertical_circle" |
||||
|
label="API info" |
||||
|
:content-inset-level="0.5" |
||||
|
> |
||||
|
|
||||
|
<q-expansion-item group="api" dense expand-separator label="Get prodcuts, categorised by wallet"> |
||||
|
<q-card> |
||||
|
<q-card-section> |
||||
|
<code><span class="text-light-blue">GET</span> /api/v1/diagonalley/stall/products/<wallet_id></code> |
||||
|
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5> |
||||
|
<h5 class="text-caption q-mt-sm q-mb-none">Returns 201 CREATED (application/json)</h5> |
||||
|
<code>Product JSON list</code> |
||||
|
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5> |
||||
|
<code>curl -X GET http://127.0.0.1:5000/diagonalley/api/v1/diagonalley/stall/products/<wallet_id></code> |
||||
|
</q-card-section> |
||||
|
</q-card> |
||||
|
</q-expansion-item> |
||||
|
<q-expansion-item group="api" dense expand-separator label="Get invoice for product"> |
||||
|
<q-card> |
||||
|
<q-card-section> |
||||
|
<code><span class="text-light-green">POST</span> /api/v1/diagonalley/stall/order/<wallet_id></code> |
||||
|
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5> |
||||
|
<code>{"id": <string>, "address": <string>, "shippingzone": <integer>, "email": <string>, "quantity": <integer>}</code> |
||||
|
<h5 class="text-caption q-mt-sm q-mb-none">Returns 201 CREATED (application/json)</h5> |
||||
|
<code>{"checking_id": <string>,"payment_request": <string>}</code> |
||||
|
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5> |
||||
|
<code>curl -X POST http://127.0.0.1:5000/diagonalley/api/v1/diagonalley/stall/order/<wallet_id> -d '{"id": <product_id&>, "email": <customer_email>, "address": <customer_address>, "quantity": 2, "shippingzone": 1}' -H "Content-type: application/json" |
||||
|
</code> |
||||
|
</q-card-section> |
||||
|
</q-card> |
||||
|
</q-expansion-item> |
||||
|
<q-expansion-item group="api" dense expand-separator label="Check a product has been shipped" |
||||
|
class="q-mb-md"> |
||||
|
<q-card> |
||||
|
<q-card-section> |
||||
|
<code><span class="text-light-blue">GET</span> /diagonalley/api/v1/diagonalley/stall/checkshipped/<checking_id></code> |
||||
|
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5> |
||||
|
<h5 class="text-caption q-mt-sm q-mb-none">Returns 200 OK (application/json)</h5> |
||||
|
<code>{"shipped": <boolean>}</code> |
||||
|
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5> |
||||
|
<code>curl -X GET http://127.0.0.1:5000/diagonalley/api/v1/diagonalley/stall/checkshipped/<checking_id> -H "Content-type: application/json"</code> |
||||
|
</q-card-section> |
||||
|
</q-card> |
||||
|
</q-expansion-item> |
||||
|
</q-expansion-item> |
@ -0,0 +1,622 @@ |
|||||
|
{% extends "base.html" %} |
||||
|
|
||||
|
{% from "macros.jinja" import window_vars with context %} |
||||
|
|
||||
|
|
||||
|
{% block page %} |
||||
|
<div class="row q-col-gutter-md"> |
||||
|
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md"> |
||||
|
<q-card> |
||||
|
<q-card-section> |
||||
|
<q-btn unelevated color="deep-purple" @click="productDialog.show = true">New Product</q-btn> |
||||
|
<q-btn unelevated color="deep-purple" @click="indexerDialog.show = true">New Indexer |
||||
|
<q-tooltip> |
||||
|
Frontend shop your stall will list its products in |
||||
|
</q-tooltip></q-btn> |
||||
|
</q-card-section> |
||||
|
</q-card> |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
<q-card> |
||||
|
<q-card-section> |
||||
|
<div class="row items-center no-wrap q-mb-md"> |
||||
|
<div class="col"> |
||||
|
<h5 class="text-subtitle1 q-my-none">Products</h5> |
||||
|
</div> |
||||
|
<div class="col-auto"> |
||||
|
<q-btn flat color="grey" @click="exportProductsCSV">Export to CSV</q-btn> |
||||
|
</div> |
||||
|
</div> |
||||
|
<q-table dense flat |
||||
|
:data="products" |
||||
|
row-key="id" |
||||
|
:columns="productsTable.columns" |
||||
|
:pagination.sync="productsTable.pagination"> |
||||
|
{% raw %} |
||||
|
<template v-slot:header="props"> |
||||
|
<q-tr :props="props"> |
||||
|
<q-th auto-width></q-th> |
||||
|
<q-th |
||||
|
v-for="col in props.cols" |
||||
|
:key="col.name" |
||||
|
:props="props" |
||||
|
> |
||||
|
{{ col.label }} |
||||
|
</q-th> |
||||
|
<q-th auto-width></q-th> |
||||
|
</q-tr> |
||||
|
</template> |
||||
|
<template v-slot:body="props"> |
||||
|
<q-tr :props="props"> |
||||
|
<q-td auto-width> |
||||
|
<q-btn unelevated dense size="xs" icon="add_shopping_cart" :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'" type="a" :href="props.row.wallet" target="_blank"></q-btn> |
||||
|
<q-tooltip> |
||||
|
Link to pass to stall indexer |
||||
|
</q-tooltip> |
||||
|
</q-td> |
||||
|
<q-td |
||||
|
v-for="col in props.cols" |
||||
|
:key="col.name" |
||||
|
:props="props" |
||||
|
> |
||||
|
{{ col.value }} |
||||
|
</q-td> |
||||
|
<q-td auto-width> |
||||
|
<q-btn flat dense size="xs" @click="deleteProduct(props.row.id)" icon="cancel" color="pink"></q-btn> |
||||
|
</q-td> |
||||
|
</q-tr> |
||||
|
</template> |
||||
|
{% endraw %} |
||||
|
</q-table> |
||||
|
</q-card-section> |
||||
|
</q-card> |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
<q-card> |
||||
|
<q-card-section> |
||||
|
<div class="row items-center no-wrap q-mb-md"> |
||||
|
<div class="col"> |
||||
|
<h5 class="text-subtitle1 q-my-none">Indexers</h5> |
||||
|
</div> |
||||
|
<div class="col-auto"> |
||||
|
<q-btn flat color="grey" @click="exportIndexersCSV">Export to CSV</q-btn> |
||||
|
</div> |
||||
|
</div> |
||||
|
<q-table dense flat |
||||
|
:data="indexers" |
||||
|
row-key="id" |
||||
|
:columns="indexersTable.columns" |
||||
|
:pagination.sync="indexersTable.pagination"> |
||||
|
{% raw %} |
||||
|
<template v-slot:header="props"> |
||||
|
<q-tr :props="props"> |
||||
|
|
||||
|
<q-th |
||||
|
v-for="col in props.cols" |
||||
|
:key="col.name" |
||||
|
:props="props" |
||||
|
> |
||||
|
{{ col.label }} |
||||
|
</q-th> |
||||
|
<q-th auto-width></q-th> |
||||
|
</q-tr> |
||||
|
</template> |
||||
|
<template v-slot:body="props"> |
||||
|
<q-tr :props="props"> |
||||
|
|
||||
|
<q-td |
||||
|
v-for="col in props.cols" |
||||
|
:key="col.name" |
||||
|
:props="props" |
||||
|
> |
||||
|
{{ col.value }} |
||||
|
</q-td> |
||||
|
<q-td auto-width> |
||||
|
<q-btn flat dense size="xs" @click="deleteIndexer(props.row.id)" icon="cancel" color="pink"></q-btn> |
||||
|
</q-td> |
||||
|
</q-tr> |
||||
|
</template> |
||||
|
{% endraw %} |
||||
|
</q-table> |
||||
|
</q-card-section> |
||||
|
</q-card> |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
<q-card> |
||||
|
<q-card-section> |
||||
|
<div class="row items-center no-wrap q-mb-md"> |
||||
|
<div class="col"> |
||||
|
<h5 class="text-subtitle1 q-my-none">Orders</h5> |
||||
|
</div> |
||||
|
<div class="col-auto"> |
||||
|
<q-btn flat color="grey" @click="exportOrdersCSV">Export to CSV</q-btn> |
||||
|
</div> |
||||
|
</div> |
||||
|
<q-table dense flat |
||||
|
:data="orders" |
||||
|
row-key="id" |
||||
|
:columns="ordersTable.columns" |
||||
|
:pagination.sync="ordersTable.pagination"> |
||||
|
{% raw %} |
||||
|
<template v-slot:header="props"> |
||||
|
<q-tr :props="props"> |
||||
|
|
||||
|
<q-th |
||||
|
v-for="col in props.cols" |
||||
|
:key="col.name" |
||||
|
:props="props" |
||||
|
> |
||||
|
{{ col.label }} |
||||
|
</q-th> |
||||
|
<q-th auto-width></q-th> |
||||
|
</q-tr> |
||||
|
</template> |
||||
|
<template v-slot:body="props"> |
||||
|
<q-tr :props="props"> |
||||
|
<q-td |
||||
|
v-for="col in props.cols" |
||||
|
:key="col.name" |
||||
|
:props="props" |
||||
|
> |
||||
|
{{ col.value }} |
||||
|
</q-td> |
||||
|
|
||||
|
<q-td auto-width> |
||||
|
<q-btn flat dense size="xs" @click="shipOrder(props.row.id)" icon="add_shopping_cart" color="green"> |
||||
|
<q-tooltip> |
||||
|
Product shipped? |
||||
|
</q-tooltip> |
||||
|
|
||||
|
</q-btn> |
||||
|
</q-td> |
||||
|
<q-td auto-width> |
||||
|
<q-btn flat dense size="xs" @click="deleteOrder(props.row.id)" icon="cancel" color="pink"></q-btn> |
||||
|
|
||||
|
</q-td> |
||||
|
</q-tr> |
||||
|
</template> |
||||
|
{% endraw %} |
||||
|
</q-table> |
||||
|
</q-card-section> |
||||
|
</q-card> |
||||
|
|
||||
|
|
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<div class="col-12 col-md-4 col-lg-5 q-gutter-y-md"> |
||||
|
<q-card> |
||||
|
<q-card-section> |
||||
|
<h6 class="text-subtitle1 q-my-none">LNbits Diagon Alley Extension</h6> |
||||
|
</q-card-section> |
||||
|
<q-card-section class="q-pa-none"> |
||||
|
<q-separator></q-separator> |
||||
|
<q-list> |
||||
|
{% include "diagonalley/_api_docs.html" %} |
||||
|
</q-list> |
||||
|
</q-card-section> |
||||
|
</q-card> |
||||
|
</div> |
||||
|
|
||||
|
<q-dialog v-model="productDialog.show" position="top"> |
||||
|
<q-card class="q-pa-lg q-pt-xl" style="width: 500px"> |
||||
|
<q-form class="q-gutter-md"> |
||||
|
<q-select filled dense emit-value v-model="productDialog.data.wallet" :options="g.user.walletOptions" label="Wallet *"> |
||||
|
</q-select> |
||||
|
<q-input filled dense |
||||
|
v-model.trim="productDialog.data.product" |
||||
|
label="Product"></q-input> |
||||
|
<q-input filled dense |
||||
|
v-model.trim="productDialog.data.categories" |
||||
|
placeholder="cakes, guns, wool, drugs" |
||||
|
label="Categories seperated by comma"></q-input> |
||||
|
<q-input filled dense |
||||
|
v-model.trim="productDialog.data.description" |
||||
|
label="Description"></q-input> |
||||
|
<q-input filled dense |
||||
|
v-model.trim="productDialog.data.image" |
||||
|
label="Image" placeholder="Imagur link (max 500/500px)"></q-input> |
||||
|
<q-input filled dense |
||||
|
v-model.number="productDialog.data.price" |
||||
|
type="number" |
||||
|
label="Price"></q-input> |
||||
|
<q-input filled dense |
||||
|
v-model.number="productDialog.data.quantity" |
||||
|
type="number" |
||||
|
label="Quantity" |
||||
|
></q-input> |
||||
|
<q-btn unelevated |
||||
|
color="deep-purple" |
||||
|
:disable="productDialog.data.image == null |
||||
|
|| productDialog.data.product == null |
||||
|
|| productDialog.data.description == null |
||||
|
|| productDialog.data.quantity == null" |
||||
|
@click="createProduct">Create Product</q-btn> |
||||
|
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn> |
||||
|
</q-form> |
||||
|
</q-card> |
||||
|
</q-dialog> |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
<q-dialog v-model="indexerDialog.show" position="top"> |
||||
|
<q-card class="q-pa-lg q-pt-xl" style="width: 500px"> |
||||
|
<q-form class="q-gutter-md"> |
||||
|
<q-select filled dense emit-value v-model="indexerDialog.data.wallet" :options="g.user.walletOptions" label="Wallet *"> |
||||
|
</q-select> |
||||
|
<q-input filled dense |
||||
|
v-model.trim="indexerDialog.data.shopname" |
||||
|
label="Shop Name"></q-input> |
||||
|
<q-input filled dense |
||||
|
v-model.trim="indexerDialog.data.indexeraddress" |
||||
|
label="Shop link (LNbits will point to)"></q-input> |
||||
|
<q-select |
||||
|
filled |
||||
|
v-model.trim="indexerDialog.data.shippingzone1" |
||||
|
multiple |
||||
|
:options="shippingoptions" |
||||
|
label="Shipping Zone 1" |
||||
|
></q-select> |
||||
|
<q-input filled dense |
||||
|
v-model.number="indexerDialog.data.zone1cost" |
||||
|
type="number" |
||||
|
label="Zone 1 Cost"></q-input> |
||||
|
|
||||
|
<q-select |
||||
|
filled |
||||
|
v-model.trim="indexerDialog.data.shippingzone2" |
||||
|
multiple |
||||
|
:options="shippingoptions" |
||||
|
label="Shipping Zone 2" |
||||
|
></q-select> |
||||
|
<q-input filled dense |
||||
|
v-model.number="indexerDialog.data.zone2cost" |
||||
|
type="number" |
||||
|
label="Zone 2 Cost"></q-input> |
||||
|
|
||||
|
<q-input filled dense |
||||
|
v-model.trim="indexerDialog.data.email" |
||||
|
label="Email to share with customers"></q-input> |
||||
|
|
||||
|
<q-btn unelevated |
||||
|
color="deep-purple" |
||||
|
:disable="indexerDialog.data.shopname == null |
||||
|
|| indexerDialog.data.shippingzone1 == null |
||||
|
|| indexerDialog.data.indexeraddress == null |
||||
|
|| indexerDialog.data.zone1cost == null |
||||
|
|| indexerDialog.data.shippingzone2 == null |
||||
|
|| indexerDialog.data.zone2cost == null |
||||
|
|| indexerDialog.data.email == null" |
||||
|
@click="createIndexer">Create Product</q-btn> |
||||
|
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn> |
||||
|
</q-form> |
||||
|
</q-card> |
||||
|
</q-dialog> |
||||
|
|
||||
|
|
||||
|
|
||||
|
</div> |
||||
|
{% endblock %} |
||||
|
|
||||
|
{% block scripts %} |
||||
|
{{ window_vars(user) }} |
||||
|
<script> |
||||
|
|
||||
|
var mapDiagonAlley = function (obj) { |
||||
|
obj.date = Quasar.utils.date.formatDate(new Date(obj.time * 1000), 'YYYY-MM-DD HH:mm'); |
||||
|
obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.amount); |
||||
|
obj.wall = ['/diagonalley/', obj.id].join(''); |
||||
|
return obj; |
||||
|
} |
||||
|
|
||||
|
new Vue({ |
||||
|
el: '#vue', |
||||
|
mixins: [windowMixin], |
||||
|
data: function () { |
||||
|
return { |
||||
|
|
||||
|
products: [], |
||||
|
orders: [], |
||||
|
indexers: [], |
||||
|
shippedModel: false, |
||||
|
shippingoptions: [ |
||||
|
'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' |
||||
|
|
||||
|
], |
||||
|
label: '', |
||||
|
indexersTable: { |
||||
|
columns: [ |
||||
|
{name: 'shopname', align: 'left', label: 'Shop', field: 'shopname'}, |
||||
|
{name: 'indexeraddress', align: 'left', label: 'Address', field: 'indexeraddress'}, |
||||
|
{name: 'rating', align: 'left', label: 'Your Rating', field: 'rating'}, |
||||
|
{name: 'email', align: 'left', label: 'Your email', field: 'email'} |
||||
|
], |
||||
|
pagination: { |
||||
|
rowsPerPage: 10 |
||||
|
} |
||||
|
}, |
||||
|
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: 'product', align: 'left', label: 'Product', field: 'product'}, |
||||
|
{name: 'description', align: 'left', label: 'Description', field: 'description'}, |
||||
|
{name: 'categories', align: 'left', label: 'Categories', field: 'categories'}, |
||||
|
{name: 'image', align: 'left', label: 'Image', field: 'image'}, |
||||
|
{name: 'price', align: 'left', label: 'Price', field: 'price'}, |
||||
|
{name: 'quantity', align: 'left', label: 'Quantity', field: 'quantity'}, |
||||
|
{name: 'id', align: 'left', label: 'ID', field: 'id'}, |
||||
|
{name: 'wallet', align: 'left', label: 'Wallet', field: 'wallet'} |
||||
|
], |
||||
|
pagination: { |
||||
|
rowsPerPage: 10 |
||||
|
} |
||||
|
}, |
||||
|
productDialog: { |
||||
|
show: false, |
||||
|
data: {} |
||||
|
}, |
||||
|
orderDialog: { |
||||
|
show: false, |
||||
|
data: {} |
||||
|
}, |
||||
|
indexerDialog: { |
||||
|
show: false, |
||||
|
data: {} |
||||
|
}, |
||||
|
}; |
||||
|
}, |
||||
|
methods: { |
||||
|
|
||||
|
getIndexers: function () { |
||||
|
var self = this; |
||||
|
|
||||
|
LNbits.api.request( |
||||
|
'GET', |
||||
|
'/diagonalley/api/v1/diagonalley/indexers?all_wallets', |
||||
|
this.g.user.wallets[0].inkey |
||||
|
).then(function (response) { |
||||
|
self.indexers = response.data.map(function (obj) { |
||||
|
return mapDiagonAlley(obj); |
||||
|
}); |
||||
|
}); |
||||
|
}, |
||||
|
createIndexer: function () { |
||||
|
|
||||
|
var data = { |
||||
|
shopname: this.indexerDialog.data.shopname, |
||||
|
indexeraddress: this.indexerDialog.data.indexeraddress, |
||||
|
shippingzone1: this.indexerDialog.data.shippingzone1.join(', '), |
||||
|
zone1cost: this.indexerDialog.data.zone1cost, |
||||
|
shippingzone2: this.indexerDialog.data.shippingzone2.join(', '), |
||||
|
zone2cost: this.indexerDialog.data.zone2cost, |
||||
|
email: this.indexerDialog.data.email |
||||
|
}; |
||||
|
var self = this; |
||||
|
|
||||
|
console.log(data); |
||||
|
|
||||
|
LNbits.api.request( |
||||
|
'POST', |
||||
|
'/diagonalley/api/v1/diagonalley/indexers', |
||||
|
_.findWhere(this.g.user.wallets, {id: this.indexerDialog.data.wallet}).inkey, |
||||
|
data |
||||
|
).then(function (response) { |
||||
|
self.indexers.push(mapDiagonAlley(response.data)); |
||||
|
self.indexerDialog.show = false; |
||||
|
self.indexerDialog.data = {}; |
||||
|
}).catch(function (error) { |
||||
|
LNbits.utils.notifyApiError(error); |
||||
|
}); |
||||
|
}, |
||||
|
deleteIndexer: function (indexerId) { |
||||
|
var self = this; |
||||
|
var indexer = _.findWhere(this.indexers, {id: indexerId}); |
||||
|
|
||||
|
this.$q.dialog({ |
||||
|
message: 'Are you sure you want to delete this Indexer link?', |
||||
|
ok: { |
||||
|
flat: true, |
||||
|
color: 'orange' |
||||
|
}, |
||||
|
cancel: { |
||||
|
flat: true, |
||||
|
color: 'grey' |
||||
|
} |
||||
|
}).onOk(function () { |
||||
|
LNbits.api.request( |
||||
|
'DELETE', |
||||
|
'/diagonalley/api/v1/diagonalley/indexers/' + indexerId, |
||||
|
_.findWhere(self.g.user.wallets, {id: indexer.wallet}).inkey |
||||
|
).then(function (response) { |
||||
|
self.indexers = _.reject(self.indexers, function (obj) { return obj.id == indexerId; }); |
||||
|
}).catch(function (error) { |
||||
|
LNbits.utils.notifyApiError(error); |
||||
|
}); |
||||
|
}); |
||||
|
}, |
||||
|
exportIndexersCSV: function () { |
||||
|
LNbits.utils.exportCSV(this.indexersTable.columns, this.indexers); |
||||
|
}, |
||||
|
|
||||
|
|
||||
|
getOrders: function () { |
||||
|
var self = this; |
||||
|
|
||||
|
LNbits.api.request( |
||||
|
'GET', |
||||
|
'/diagonalley/api/v1/diagonalley/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/diagonalley/orders', |
||||
|
_.findWhere(this.g.user.wallets, {id: this.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(this.orders, {id: orderId}); |
||||
|
|
||||
|
this.$q.dialog({ |
||||
|
message: 'Are you sure you want to delete this order link?', |
||||
|
ok: { |
||||
|
flat: true, |
||||
|
color: 'orange' |
||||
|
}, |
||||
|
cancel: { |
||||
|
flat: true, |
||||
|
color: 'grey' |
||||
|
} |
||||
|
}).onOk(function () { |
||||
|
LNbits.api.request( |
||||
|
'DELETE', |
||||
|
'/diagonalley/api/v1/diagonalley/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/diagonalley/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); |
||||
|
}, |
||||
|
|
||||
|
|
||||
|
getProducts: function () { |
||||
|
var self = this; |
||||
|
|
||||
|
LNbits.api.request( |
||||
|
'GET', |
||||
|
'/diagonalley/api/v1/diagonalley/products?all_wallets', |
||||
|
this.g.user.wallets[0].inkey |
||||
|
).then(function (response) { |
||||
|
self.products = response.data.map(function (obj) { |
||||
|
return mapDiagonAlley(obj); |
||||
|
}); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
createProduct: function () { |
||||
|
var data = { |
||||
|
product: this.productDialog.data.product, |
||||
|
categories: this.productDialog.data.categories, |
||||
|
description: this.productDialog.data.description, |
||||
|
image: this.productDialog.data.image, |
||||
|
price: this.productDialog.data.price, |
||||
|
quantity: this.productDialog.data.quantity |
||||
|
}; |
||||
|
var self = this; |
||||
|
|
||||
|
LNbits.api.request( |
||||
|
'POST', |
||||
|
'/diagonalley/api/v1/diagonalley/products', |
||||
|
_.findWhere(this.g.user.wallets, {id: this.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}); |
||||
|
|
||||
|
this.$q.dialog({ |
||||
|
message: 'Are you sure you want to delete this products link?', |
||||
|
ok: { |
||||
|
flat: true, |
||||
|
color: 'orange' |
||||
|
}, |
||||
|
cancel: { |
||||
|
flat: true, |
||||
|
color: 'grey' |
||||
|
} |
||||
|
}).onOk(function () { |
||||
|
LNbits.api.request( |
||||
|
'DELETE', |
||||
|
'/diagonalley/api/v1/diagonalley/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); |
||||
|
} |
||||
|
}, |
||||
|
created: function () { |
||||
|
if (this.g.user.wallets.length) { |
||||
|
this.getProducts(); |
||||
|
this.getOrders(); |
||||
|
this.getIndexers(); |
||||
|
// console.log(this); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
</script> |
||||
|
{% endblock %} |
@ -0,0 +1 @@ |
|||||
|
<script>console.log("{{ stall }}")</script> |
@ -0,0 +1,14 @@ |
|||||
|
from flask import g, abort, render_template, jsonify |
||||
|
import json |
||||
|
from lnbits.decorators import check_user_exists, validate_uuids |
||||
|
from lnbits.extensions.diagonalley import diagonalley_ext |
||||
|
from lnbits.helpers import Status |
||||
|
from lnbits.db import open_ext_db |
||||
|
|
||||
|
|
||||
|
@diagonalley_ext.route("/") |
||||
|
@validate_uuids(["usr"], required=True) |
||||
|
@check_user_exists() |
||||
|
def index(): |
||||
|
|
||||
|
return render_template("diagonalley/index.html", user=g.user) |
@ -0,0 +1,211 @@ |
|||||
|
from flask import g, jsonify, request |
||||
|
|
||||
|
from lnbits.core.crud import get_user |
||||
|
from lnbits.decorators import api_check_wallet_key, api_validate_post_request |
||||
|
from lnbits.helpers import Status |
||||
|
|
||||
|
from lnbits.extensions.diagonalley import diagonalley_ext |
||||
|
from .crud import create_diagonalleys_product,get_diagonalleys_product,get_diagonalleys_products,delete_diagonalleys_product,create_diagonalleys_indexer,get_diagonalleys_indexer,get_diagonalleys_indexers,delete_diagonalleys_indexer,create_diagonalleys_order,get_diagonalleys_order,get_diagonalleys_orders,delete_diagonalleys_order |
||||
|
from lnbits.core.services import create_invoice |
||||
|
from base64 import urlsafe_b64encode |
||||
|
from uuid import uuid4 |
||||
|
from lnbits.db import open_ext_db |
||||
|
|
||||
|
###Products |
||||
|
|
||||
|
@diagonalley_ext.route("/api/v1/diagonalley/products", methods=["GET"]) |
||||
|
@api_check_wallet_key(key_type="invoice") |
||||
|
def api_diagonalley_products(): |
||||
|
wallet_ids = [g.wallet.id] |
||||
|
|
||||
|
if "all_wallets" in request.args: |
||||
|
wallet_ids = get_user(g.wallet.user).wallet_ids |
||||
|
|
||||
|
return jsonify([product._asdict() for product in get_diagonalleys_products(wallet_ids)]), Status.OK |
||||
|
|
||||
|
|
||||
|
@diagonalley_ext.route("/api/v1/diagonalley/products", methods=["POST"]) |
||||
|
@api_check_wallet_key(key_type="invoice") |
||||
|
@api_validate_post_request(schema={ |
||||
|
"product": {"type": "string", "empty": False, "required": True}, |
||||
|
"categories": {"type": "string", "empty": False, "required": True}, |
||||
|
"description": {"type": "string", "empty": False, "required": True}, |
||||
|
"image": {"type": "string", "empty": False, "required": True}, |
||||
|
"price": {"type": "integer", "min": 0, "required": True}, |
||||
|
"quantity": {"type": "integer", "min": 0, "required": True} |
||||
|
}) |
||||
|
def api_diagonalley_products_create(): |
||||
|
product = create_diagonalleys_product(wallet_id=g.wallet.id, **g.data) |
||||
|
|
||||
|
return jsonify(product._asdict()), Status.CREATED |
||||
|
|
||||
|
|
||||
|
@diagonalley_ext.route("/api/v1/diagonalley/products/<product_id>", methods=["DELETE"]) |
||||
|
@api_check_wallet_key(key_type="invoice") |
||||
|
def api_diagonalley_products_delete(product_id): |
||||
|
product = get_diagonalleys_product(product_id) |
||||
|
|
||||
|
if not product: |
||||
|
return jsonify({"message": "Product does not exist."}), Status.NOT_FOUND |
||||
|
|
||||
|
if product.wallet != g.wallet.id: |
||||
|
return jsonify({"message": "Not your Diagon Alley."}), Status.FORBIDDEN |
||||
|
|
||||
|
delete_diagonalleys_product(product_id) |
||||
|
|
||||
|
return "", Status.NO_CONTENT |
||||
|
|
||||
|
|
||||
|
|
||||
|
###Indexers |
||||
|
|
||||
|
@diagonalley_ext.route("/api/v1/diagonalley/indexers", methods=["GET"]) |
||||
|
@api_check_wallet_key(key_type="invoice") |
||||
|
def api_diagonalley_indexers(): |
||||
|
wallet_ids = [g.wallet.id] |
||||
|
|
||||
|
if "all_wallets" in request.args: |
||||
|
wallet_ids = get_user(g.wallet.user).wallet_ids |
||||
|
|
||||
|
return jsonify([indexer._asdict() for indexer in get_diagonalleys_indexers(wallet_ids)]), Status.OK |
||||
|
|
||||
|
|
||||
|
@diagonalley_ext.route("/api/v1/diagonalley/indexers", methods=["POST"]) |
||||
|
@api_check_wallet_key(key_type="invoice") |
||||
|
@api_validate_post_request(schema={ |
||||
|
"shopname": {"type": "string", "empty": False, "required": True}, |
||||
|
"indexeraddress": {"type": "string", "empty": False, "required": True}, |
||||
|
"shippingzone1": {"type": "string", "empty": False, "required": True}, |
||||
|
"shippingzone2": {"type": "string", "empty": False, "required": True}, |
||||
|
"email": {"type": "string", "empty": False, "required": True}, |
||||
|
"zone1cost": {"type": "integer", "min": 0, "required": True}, |
||||
|
"zone2cost": {"type": "integer", "min": 0, "required": True} |
||||
|
}) |
||||
|
def api_diagonalley_indexer_create(): |
||||
|
indexer = create_diagonalleys_indexer(wallet_id=g.wallet.id, **g.data) |
||||
|
|
||||
|
return jsonify(indexer._asdict()), Status.CREATED |
||||
|
|
||||
|
|
||||
|
@diagonalley_ext.route("/api/v1/diagonalley/indexers/<indexer_id>", methods=["DELETE"]) |
||||
|
@api_check_wallet_key(key_type="invoice") |
||||
|
def api_diagonalley_indexer_delete(indexer_id): |
||||
|
indexer = get_diagonalleys_indexer(indexer_id) |
||||
|
|
||||
|
if not indexer: |
||||
|
return jsonify({"message": "Indexer does not exist."}), Status.NOT_FOUND |
||||
|
|
||||
|
if indexer.wallet != g.wallet.id: |
||||
|
return jsonify({"message": "Not your Indexer."}), Status.FORBIDDEN |
||||
|
|
||||
|
delete_diagonalleys_indexer(indexer_id) |
||||
|
|
||||
|
return "", Status.NO_CONTENT |
||||
|
|
||||
|
|
||||
|
###Orders |
||||
|
|
||||
|
@diagonalley_ext.route("/api/v1/diagonalley/orders", methods=["GET"]) |
||||
|
@api_check_wallet_key(key_type="invoice") |
||||
|
def api_diagonalley_orders(): |
||||
|
wallet_ids = [g.wallet.id] |
||||
|
|
||||
|
if "all_wallets" in request.args: |
||||
|
wallet_ids = get_user(g.wallet.user).wallet_ids |
||||
|
|
||||
|
return jsonify([order._asdict() for order in get_diagonalleys_orders(wallet_ids)]), Status.OK |
||||
|
|
||||
|
|
||||
|
@diagonalley_ext.route("/api/v1/diagonalley/orders", methods=["POST"]) |
||||
|
@api_check_wallet_key(key_type="invoice") |
||||
|
@api_validate_post_request(schema={ |
||||
|
"id": {"type": "string", "empty": False, "required": True}, |
||||
|
"address": {"type": "string", "empty": False, "required": True}, |
||||
|
"email": {"type": "string", "empty": False, "required": True}, |
||||
|
"quantity": {"type": "integer", "empty": False, "required": True}, |
||||
|
"shippingzone": {"type": "integer", "empty": False, "required": True}, |
||||
|
}) |
||||
|
def api_diagonalley_order_create(): |
||||
|
order = create_diagonalleys_order(wallet_id=g.wallet.id, **g.data) |
||||
|
return jsonify(order._asdict()), Status.CREATED |
||||
|
|
||||
|
|
||||
|
@diagonalley_ext.route("/api/v1/diagonalley/orders/<order_id>", methods=["DELETE"]) |
||||
|
@api_check_wallet_key(key_type="invoice") |
||||
|
def api_diagonalley_order_delete(order_id): |
||||
|
order = get_diagonalleys_order(order_id) |
||||
|
|
||||
|
if not order: |
||||
|
return jsonify({"message": "Indexer does not exist."}), Status.NOT_FOUND |
||||
|
|
||||
|
if order.wallet != g.wallet.id: |
||||
|
return jsonify({"message": "Not your Indexer."}), Status.FORBIDDEN |
||||
|
|
||||
|
delete_diagonalleys_indexer(order_id) |
||||
|
|
||||
|
return "", Status.NO_CONTENT |
||||
|
|
||||
|
|
||||
|
@diagonalley_ext.route("/api/v1/diagonalley/orders/paid/<order_id>", methods=["GET"]) |
||||
|
@api_check_wallet_key(key_type="invoice") |
||||
|
def api_diagonalleys_order_paid(order_id): |
||||
|
with open_ext_db("diagonalley") as db: |
||||
|
db.execute("UPDATE orders SET paid = ? WHERE id = ?", (True, order_id,)) |
||||
|
return "", Status.OK |
||||
|
|
||||
|
|
||||
|
@diagonalley_ext.route("/api/v1/diagonalley/orders/shipped/<order_id>", methods=["GET"]) |
||||
|
@api_check_wallet_key(key_type="invoice") |
||||
|
def api_diagonalleys_order_shipped(order_id): |
||||
|
with open_ext_db("diagonalley") as db: |
||||
|
db.execute("UPDATE orders SET shipped = ? WHERE id = ?", (True, order_id,)) |
||||
|
order = db.fetchone("SELECT * FROM orders WHERE id = ?", (order_id,)) |
||||
|
|
||||
|
return jsonify([order._asdict() for order in get_diagonalleys_orders(order["wallet"])]), Status.OK |
||||
|
|
||||
|
|
||||
|
###List products based on wallet |
||||
|
|
||||
|
@diagonalley_ext.route("/api/v1/diagonalley/stall/products/<wallet_id>", methods=["GET"]) |
||||
|
def api_diagonalleys_stall_products(wallet_id): |
||||
|
with open_ext_db("diagonalley") as db: |
||||
|
rows = db.fetchall("SELECT * FROM products WHERE WALLET = ?", (wallet_id,)) |
||||
|
|
||||
|
return jsonify([products._asdict() for products in get_diagonalleys_products(wallet_id)]), Status.OK |
||||
|
|
||||
|
###Check a product has been shipped |
||||
|
|
||||
|
@diagonalley_ext.route("/api/v1/diagonalley/stall/checkshipped/<checking_id>", methods=["GET"]) |
||||
|
def api_diagonalleys_stall_checkshipped(checking_id): |
||||
|
with open_ext_db("diagonalley") as db: |
||||
|
rows = db.fetchone("SELECT * FROM orders WHERE invoiceid = ?", (checking_id,)) |
||||
|
|
||||
|
return jsonify({"shipped": rows["shipped"]}), Status.OK |
||||
|
|
||||
|
###Place order |
||||
|
|
||||
|
@diagonalley_ext.route("/api/v1/diagonalley/stall/order/<wallet_id>", methods=["POST"]) |
||||
|
@api_validate_post_request(schema={ |
||||
|
"id": {"type": "string", "empty": False, "required": True}, |
||||
|
"email": {"type": "string", "empty": False, "required": True}, |
||||
|
"address": {"type": "string", "empty": False, "required": True}, |
||||
|
"quantity": {"type": "integer", "empty": False, "required": True}, |
||||
|
"shippingzone": {"type": "integer", "empty": False, "required": True}, |
||||
|
}) |
||||
|
def api_diagonalley_stall_order(wallet_id): |
||||
|
product = get_diagonalleys_product(g.data["id"]) |
||||
|
|
||||
|
checking_id, payment_request = create_invoice(wallet_id=wallet_id, amount=(g.data["quantity"] * product.price), memo=g.data["id"]) |
||||
|
selling_id = urlsafe_b64encode(uuid4().bytes_le).decode('utf-8') |
||||
|
with open_ext_db("diagonalley") as db: |
||||
|
db.execute( |
||||
|
""" |
||||
|
INSERT INTO orders (id, productid, wallet, product, quantity, shippingzone, address, email, invoiceid, paid, shipped) |
||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) |
||||
|
""", |
||||
|
(selling_id ,g.data["id"] ,wallet_id, product.product, g.data["quantity"], g.data["shippingzone"], g.data["address"], g.data["email"], checking_id, False, False), |
||||
|
) |
||||
|
return jsonify({"checking_id": checking_id, "payment_request": payment_request}), Status.OK |
||||
|
|
||||
|
|
||||
|
|
Loading…
Reference in new issue