From 0cd828d43fb6a32d5bd9da3165d1f03efd4ca7f1 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Fri, 20 Aug 2021 10:34:52 +0100 Subject: [PATCH 01/12] added tpos back --- lnbits/extensions/tpos/README.md | 15 + lnbits/extensions/tpos/__init__.py | 12 + lnbits/extensions/tpos/config.json | 6 + lnbits/extensions/tpos/crud.py | 42 ++ lnbits/extensions/tpos/migrations.py | 14 + lnbits/extensions/tpos/models.py | 13 + .../tpos/templates/tpos/_api_docs.html | 78 ++++ .../extensions/tpos/templates/tpos/_tpos.html | 18 + .../extensions/tpos/templates/tpos/index.html | 423 ++++++++++++++++++ .../extensions/tpos/templates/tpos/tpos.html | 264 +++++++++++ lnbits/extensions/tpos/views.py | 23 + lnbits/extensions/tpos/views_api.py | 101 +++++ 12 files changed, 1009 insertions(+) create mode 100644 lnbits/extensions/tpos/README.md create mode 100644 lnbits/extensions/tpos/__init__.py create mode 100644 lnbits/extensions/tpos/config.json create mode 100644 lnbits/extensions/tpos/crud.py create mode 100644 lnbits/extensions/tpos/migrations.py create mode 100644 lnbits/extensions/tpos/models.py create mode 100644 lnbits/extensions/tpos/templates/tpos/_api_docs.html create mode 100644 lnbits/extensions/tpos/templates/tpos/_tpos.html create mode 100644 lnbits/extensions/tpos/templates/tpos/index.html create mode 100644 lnbits/extensions/tpos/templates/tpos/tpos.html create mode 100644 lnbits/extensions/tpos/views.py create mode 100644 lnbits/extensions/tpos/views_api.py diff --git a/lnbits/extensions/tpos/README.md b/lnbits/extensions/tpos/README.md new file mode 100644 index 000000000..04e049e37 --- /dev/null +++ b/lnbits/extensions/tpos/README.md @@ -0,0 +1,15 @@ +# TPoS + +## A Shareable PoS (Point of Sale) that doesn't need to be installed and can run in the browser! + +An easy, fast and secure way to accept Bitcoin, over Lightning Network, at your business. The PoS is isolated from the wallet, so it's safe for any employee to use. You can create as many TPOS's as you need, for example one for each employee, or one for each branch of your business. + +### Usage + +1. Enable extension +2. Create a TPOS\ + ![create](https://imgur.com/8jNj8Zq.jpg) +3. Open TPOS on the browser\ + ![open](https://imgur.com/LZuoWzb.jpg) +4. Present invoice QR to costumer\ + ![pay](https://imgur.com/tOwxn77.jpg) diff --git a/lnbits/extensions/tpos/__init__.py b/lnbits/extensions/tpos/__init__.py new file mode 100644 index 000000000..daa3022e5 --- /dev/null +++ b/lnbits/extensions/tpos/__init__.py @@ -0,0 +1,12 @@ +from quart import Blueprint +from lnbits.db import Database + +db = Database("ext_tpos") + +tpos_ext: Blueprint = Blueprint( + "tpos", __name__, static_folder="static", template_folder="templates" +) + + +from .views_api import * # noqa +from .views import * # noqa diff --git a/lnbits/extensions/tpos/config.json b/lnbits/extensions/tpos/config.json new file mode 100644 index 000000000..c5789afb7 --- /dev/null +++ b/lnbits/extensions/tpos/config.json @@ -0,0 +1,6 @@ +{ + "name": "TPoS", + "short_description": "A shareable PoS terminal!", + "icon": "dialpad", + "contributors": ["talvasconcelos", "arcbtc"] +} diff --git a/lnbits/extensions/tpos/crud.py b/lnbits/extensions/tpos/crud.py new file mode 100644 index 000000000..99dab6627 --- /dev/null +++ b/lnbits/extensions/tpos/crud.py @@ -0,0 +1,42 @@ +from typing import List, Optional, Union + +from lnbits.helpers import urlsafe_short_hash + +from . import db +from .models import TPoS + + +async def create_tpos(*, wallet_id: str, name: str, currency: str) -> TPoS: + tpos_id = urlsafe_short_hash() + await db.execute( + """ + INSERT INTO tpos.tposs (id, wallet, name, currency) + VALUES (?, ?, ?, ?) + """, + (tpos_id, wallet_id, name, currency), + ) + + tpos = await get_tpos(tpos_id) + assert tpos, "Newly created tpos couldn't be retrieved" + return tpos + + +async def get_tpos(tpos_id: str) -> Optional[TPoS]: + row = await db.fetchone("SELECT * FROM tpos.tposs WHERE id = ?", (tpos_id,)) + return TPoS.from_row(row) if row else None + + +async def get_tposs(wallet_ids: Union[str, List[str]]) -> List[TPoS]: + if isinstance(wallet_ids, str): + wallet_ids = [wallet_ids] + + q = ",".join(["?"] * len(wallet_ids)) + rows = await db.fetchall( + f"SELECT * FROM tpos.tposs WHERE wallet IN ({q})", (*wallet_ids,) + ) + + return [TPoS.from_row(row) for row in rows] + + +async def delete_tpos(tpos_id: str) -> None: + await db.execute("DELETE FROM tpos.tposs WHERE id = ?", (tpos_id,)) diff --git a/lnbits/extensions/tpos/migrations.py b/lnbits/extensions/tpos/migrations.py new file mode 100644 index 000000000..7a7fff0d5 --- /dev/null +++ b/lnbits/extensions/tpos/migrations.py @@ -0,0 +1,14 @@ +async def m001_initial(db): + """ + Initial tposs table. + """ + await db.execute( + """ + CREATE TABLE tpos.tposs ( + id TEXT PRIMARY KEY, + wallet TEXT NOT NULL, + name TEXT NOT NULL, + currency TEXT NOT NULL + ); + """ + ) diff --git a/lnbits/extensions/tpos/models.py b/lnbits/extensions/tpos/models.py new file mode 100644 index 000000000..e10615672 --- /dev/null +++ b/lnbits/extensions/tpos/models.py @@ -0,0 +1,13 @@ +from sqlite3 import Row +from typing import NamedTuple + + +class TPoS(NamedTuple): + id: str + wallet: str + name: str + currency: str + + @classmethod + def from_row(cls, row: Row) -> "TPoS": + return cls(**dict(row)) diff --git a/lnbits/extensions/tpos/templates/tpos/_api_docs.html b/lnbits/extensions/tpos/templates/tpos/_api_docs.html new file mode 100644 index 000000000..6ceab7284 --- /dev/null +++ b/lnbits/extensions/tpos/templates/tpos/_api_docs.html @@ -0,0 +1,78 @@ + + + + + GET /tpos/api/v1/tposs +
Headers
+ {"X-Api-Key": <invoice_key>}
+
Body (application/json)
+
+ Returns 200 OK (application/json) +
+ [<tpos_object>, ...] +
Curl example
+ curl -X GET {{ request.url_root }}api/v1/tposs -H "X-Api-Key: + <invoice_key>" + +
+
+
+ + + + POST /tpos/api/v1/tposs +
Headers
+ {"X-Api-Key": <invoice_key>}
+
Body (application/json)
+ {"name": <string>, "currency": <string*ie USD*>} +
+ Returns 201 CREATED (application/json) +
+ {"currency": <string>, "id": <string>, "name": + <string>, "wallet": <string>} +
Curl example
+ curl -X POST {{ request.url_root }}api/v1/tposs -d '{"name": + <string>, "currency": <string>}' -H "Content-type: + application/json" -H "X-Api-Key: <admin_key>" + +
+
+
+ + + + + DELETE + /tpos/api/v1/tposs/<tpos_id> +
Headers
+ {"X-Api-Key": <admin_key>}
+
Returns 204 NO CONTENT
+ +
Curl example
+ curl -X DELETE {{ request.url_root }}api/v1/tposs/<tpos_id> -H + "X-Api-Key: <admin_key>" + +
+
+
+
diff --git a/lnbits/extensions/tpos/templates/tpos/_tpos.html b/lnbits/extensions/tpos/templates/tpos/_tpos.html new file mode 100644 index 000000000..54ddcd0f9 --- /dev/null +++ b/lnbits/extensions/tpos/templates/tpos/_tpos.html @@ -0,0 +1,18 @@ + + + +

+ Thiago's Point of Sale is a secure, mobile-ready, instant and shareable + point of sale terminal (PoS) for merchants. The PoS is linked to your + LNbits wallet but completely air-gapped so users can ONLY create + invoices. To share the TPoS hit the hash on the terminal. +

+ Created by + Tiago Vasconcelos. +
+
+
diff --git a/lnbits/extensions/tpos/templates/tpos/index.html b/lnbits/extensions/tpos/templates/tpos/index.html new file mode 100644 index 000000000..f3b55b37d --- /dev/null +++ b/lnbits/extensions/tpos/templates/tpos/index.html @@ -0,0 +1,423 @@ +{% extends "base.html" %} {% from "macros.jinja" import window_vars with context +%} {% block page %} +
+
+ + + New TPoS + + + + + +
+
+
TPoS
+
+
+ Export to CSV +
+
+ + {% raw %} + + + + {% endraw %} + +
+
+
+ +
+ + +
{{SITE_TITLE}} TPoS extension
+
+ + + + {% include "tpos/_api_docs.html" %} + + {% include "tpos/_tpos.html" %} + + +
+
+ + + + + + + +
+ Create TPoS + Cancel +
+
+
+
+
+{% endblock %} {% block scripts %} {{ window_vars(user) }} + +{% endblock %} diff --git a/lnbits/extensions/tpos/templates/tpos/tpos.html b/lnbits/extensions/tpos/templates/tpos/tpos.html new file mode 100644 index 000000000..1727e6e99 --- /dev/null +++ b/lnbits/extensions/tpos/templates/tpos/tpos.html @@ -0,0 +1,264 @@ +{% extends "public.html" %} {% block toolbar_title %}{{ tpos.name }}{% endblock +%} {% block footer %}{% endblock %} {% block page_container %} + + + +
+
+

{% raw %}{{ famount }}{% endraw %}

+
+ {% raw %}{{ fsat }}{% endraw %} sat +
+
+
+
+ +
+
+
+ 1 + 2 + 3 + C + 4 + 5 + 6 + 7 + 8 + 9 + OK + DEL + 0 + # +
+
+
+
+ + + + + +
+

{% raw %}{{ famount }}{% endraw %}

+
+ {% raw %}{{ fsat }}{% endraw %} sat +
+
+
+ Close +
+
+
+ + + + + +
+

+ {{ tpos.name }}
{{ request.url }} +

+
+
+ Copy URL + Close +
+
+
+
+
+{% endblock %} {% block styles %} + +{% endblock %} {% block scripts %} + +{% endblock %} diff --git a/lnbits/extensions/tpos/views.py b/lnbits/extensions/tpos/views.py new file mode 100644 index 000000000..ce8422956 --- /dev/null +++ b/lnbits/extensions/tpos/views.py @@ -0,0 +1,23 @@ +from quart import g, abort, render_template +from http import HTTPStatus + +from lnbits.decorators import check_user_exists, validate_uuids + +from . import tpos_ext +from .crud import get_tpos + + +@tpos_ext.route("/") +@validate_uuids(["usr"], required=True) +@check_user_exists() +async def index(): + return await render_template("tpos/index.html", user=g.user) + + +@tpos_ext.route("/") +async def tpos(tpos_id): + tpos = await get_tpos(tpos_id) + if not tpos: + abort(HTTPStatus.NOT_FOUND, "TPoS does not exist.") + + return await render_template("tpos/tpos.html", tpos=tpos) diff --git a/lnbits/extensions/tpos/views_api.py b/lnbits/extensions/tpos/views_api.py new file mode 100644 index 000000000..1f0802c77 --- /dev/null +++ b/lnbits/extensions/tpos/views_api.py @@ -0,0 +1,101 @@ +from quart import g, jsonify, request +from http import HTTPStatus + +from lnbits.core.crud import get_user, get_wallet +from lnbits.core.services import create_invoice, check_invoice_status +from lnbits.decorators import api_check_wallet_key, api_validate_post_request + +from . import tpos_ext +from .crud import create_tpos, get_tpos, get_tposs, delete_tpos + + +@tpos_ext.route("/api/v1/tposs", methods=["GET"]) +@api_check_wallet_key("invoice") +async def api_tposs(): + wallet_ids = [g.wallet.id] + if "all_wallets" in request.args: + wallet_ids = (await get_user(g.wallet.user)).wallet_ids + + return ( + jsonify([tpos._asdict() for tpos in await get_tposs(wallet_ids)]), + HTTPStatus.OK, + ) + + +@tpos_ext.route("/api/v1/tposs", methods=["POST"]) +@api_check_wallet_key("invoice") +@api_validate_post_request( + schema={ + "name": {"type": "string", "empty": False, "required": True}, + "currency": {"type": "string", "empty": False, "required": True}, + } +) +async def api_tpos_create(): + tpos = await create_tpos(wallet_id=g.wallet.id, **g.data) + return jsonify(tpos._asdict()), HTTPStatus.CREATED + + +@tpos_ext.route("/api/v1/tposs/", methods=["DELETE"]) +@api_check_wallet_key("admin") +async def api_tpos_delete(tpos_id): + tpos = await get_tpos(tpos_id) + + if not tpos: + return jsonify({"message": "TPoS does not exist."}), HTTPStatus.NOT_FOUND + + if tpos.wallet != g.wallet.id: + return jsonify({"message": "Not your TPoS."}), HTTPStatus.FORBIDDEN + + await delete_tpos(tpos_id) + + return "", HTTPStatus.NO_CONTENT + + +@tpos_ext.route("/api/v1/tposs//invoices/", methods=["POST"]) +@api_validate_post_request( + schema={"amount": {"type": "integer", "min": 1, "required": True}} +) +async def api_tpos_create_invoice(tpos_id): + tpos = await get_tpos(tpos_id) + + if not tpos: + return jsonify({"message": "TPoS does not exist."}), HTTPStatus.NOT_FOUND + + try: + payment_hash, payment_request = await create_invoice( + wallet_id=tpos.wallet, + amount=g.data["amount"], + memo=f"{tpos.name}", + extra={"tag": "tpos"}, + ) + except Exception as e: + return jsonify({"message": str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR + + return ( + jsonify({"payment_hash": payment_hash, "payment_request": payment_request}), + HTTPStatus.CREATED, + ) + + +@tpos_ext.route("/api/v1/tposs//invoices/", methods=["GET"]) +async def api_tpos_check_invoice(tpos_id, payment_hash): + tpos = await get_tpos(tpos_id) + + if not tpos: + return jsonify({"message": "TPoS does not exist."}), HTTPStatus.NOT_FOUND + + try: + status = await check_invoice_status(tpos.wallet, payment_hash) + is_paid = not status.pending + except Exception as exc: + print(exc) + return jsonify({"paid": False}), HTTPStatus.OK + + if is_paid: + wallet = await get_wallet(tpos.wallet) + payment = await wallet.get_payment(payment_hash) + await payment.set_pending(False) + + return jsonify({"paid": True}), HTTPStatus.OK + + return jsonify({"paid": False}), HTTPStatus.OK From 22b57d99d558c962ef2af1e16d9dc5d6a5a4045f Mon Sep 17 00:00:00 2001 From: Ben Arc Date: Fri, 20 Aug 2021 16:47:55 +0100 Subject: [PATCH 02/12] Added CreateData model to withdraw --- lnbits/extensions/events/views_api.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lnbits/extensions/events/views_api.py b/lnbits/extensions/events/views_api.py index e6aea102a..fddbf2a45 100644 --- a/lnbits/extensions/events/views_api.py +++ b/lnbits/extensions/events/views_api.py @@ -25,6 +25,7 @@ from .crud import ( # Events + @events_ext.route("/api/v1/events", methods=["GET"]) @api_check_wallet_key("invoice") async def api_events(): @@ -38,6 +39,15 @@ async def api_events(): HTTPStatus.OK, ) +class CreateData(BaseModel): + wallet: str = Query(...), + name: str = Query(...), + info: str = Query(...), + closing_date: str = Query(...), + event_start_date: str = Query(...), + event_end_date: str = Query(...), + amount_tickets: int = Query(..., ge=0), + price_per_ticket: int = Query(..., ge=0), @events_ext.route("/api/v1/events", methods=["POST"]) @events_ext.route("/api/v1/events/", methods=["PUT"]) @@ -54,7 +64,7 @@ async def api_events(): "price_per_ticket": {"type": "integer", "min": 0, "required": True}, } ) -async def api_event_create(event_id=None): +async def api_event_create(data: CreateData, event_id=None): if event_id: event = await get_event(event_id) if not event: From de0f6389ae2a1ceb40d984852310c7082d529ef9 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Fri, 20 Aug 2021 17:14:26 +0100 Subject: [PATCH 03/12] change class to pydantic --- lnbits/extensions/tpos/models.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lnbits/extensions/tpos/models.py b/lnbits/extensions/tpos/models.py index e10615672..3400924af 100644 --- a/lnbits/extensions/tpos/models.py +++ b/lnbits/extensions/tpos/models.py @@ -1,8 +1,9 @@ from sqlite3 import Row -from typing import NamedTuple +from pydantic import BaseModel +#from typing import NamedTuple -class TPoS(NamedTuple): +class TPoS(BaseModel): id: str wallet: str name: str From edf748d6900a1fca07c24035442f685b65badcad Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Fri, 20 Aug 2021 17:14:44 +0100 Subject: [PATCH 04/12] refactor for fastAPI --- lnbits/extensions/tpos/views_api.py | 80 +++++++++++++++-------------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/lnbits/extensions/tpos/views_api.py b/lnbits/extensions/tpos/views_api.py index 1f0802c77..6b1374484 100644 --- a/lnbits/extensions/tpos/views_api.py +++ b/lnbits/extensions/tpos/views_api.py @@ -1,101 +1,103 @@ from quart import g, jsonify, request from http import HTTPStatus +from fastapi import FastAPI, Query +from fastapi.encoders import jsonable_encoder +from fastapi.responses import JSONResponse +from pydantic import BaseModel + from lnbits.core.crud import get_user, get_wallet from lnbits.core.services import create_invoice, check_invoice_status from lnbits.decorators import api_check_wallet_key, api_validate_post_request from . import tpos_ext from .crud import create_tpos, get_tpos, get_tposs, delete_tpos +from .models import TPoS -@tpos_ext.route("/api/v1/tposs", methods=["GET"]) +@tpos_ext.get("/api/v1/tposs") @api_check_wallet_key("invoice") -async def api_tposs(): +async def api_tposs(all_wallets: boolean = Query(None)): wallet_ids = [g.wallet.id] - if "all_wallets" in request.args: - wallet_ids = (await get_user(g.wallet.user)).wallet_ids + if all_wallets: + wallet_ids = wallet_ids = (await get_user(g.wallet.user)).wallet_ids(await get_user(g.wallet.user)).wallet_ids + # if "all_wallets" in request.args: + # wallet_ids = (await get_user(g.wallet.user)).wallet_ids - return ( - jsonify([tpos._asdict() for tpos in await get_tposs(wallet_ids)]), - HTTPStatus.OK, - ) + return [tpos._asdict() for tpos in await get_tposs(wallet_ids)], HTTPStatus.OK -@tpos_ext.route("/api/v1/tposs", methods=["POST"]) +@tpos_ext.post("/api/v1/tposs") @api_check_wallet_key("invoice") -@api_validate_post_request( - schema={ - "name": {"type": "string", "empty": False, "required": True}, - "currency": {"type": "string", "empty": False, "required": True}, - } -) -async def api_tpos_create(): +# @api_validate_post_request( +# schema={ +# "name": {"type": "string", "empty": False, "required": True}, +# "currency": {"type": "string", "empty": False, "required": True}, +# } +# ) +async def api_tpos_create(name: str = Query(...), currency: str = Query(...)): tpos = await create_tpos(wallet_id=g.wallet.id, **g.data) - return jsonify(tpos._asdict()), HTTPStatus.CREATED + return tpos._asdict(), HTTPStatus.CREATED -@tpos_ext.route("/api/v1/tposs/", methods=["DELETE"]) +@tpos_ext.delete("/api/v1/tposs/{tpos_id}") @api_check_wallet_key("admin") -async def api_tpos_delete(tpos_id): +async def api_tpos_delete(tpos_id: str): tpos = await get_tpos(tpos_id) if not tpos: - return jsonify({"message": "TPoS does not exist."}), HTTPStatus.NOT_FOUND + return {"message": "TPoS does not exist."}, HTTPStatus.NOT_FOUND if tpos.wallet != g.wallet.id: - return jsonify({"message": "Not your TPoS."}), HTTPStatus.FORBIDDEN + return {"message": "Not your TPoS."}, HTTPStatus.FORBIDDEN await delete_tpos(tpos_id) return "", HTTPStatus.NO_CONTENT -@tpos_ext.route("/api/v1/tposs//invoices/", methods=["POST"]) -@api_validate_post_request( - schema={"amount": {"type": "integer", "min": 1, "required": True}} -) -async def api_tpos_create_invoice(tpos_id): +@tpos_ext.post("/api/v1/tposs/{tpos_id}/invoices/") +# @api_validate_post_request( +# schema={"amount": {"type": "integer", "min": 1, "required": True}} +# ) +async def api_tpos_create_invoice(amount: int = Query(..., ge=1), tpos_id: str): tpos = await get_tpos(tpos_id) if not tpos: - return jsonify({"message": "TPoS does not exist."}), HTTPStatus.NOT_FOUND + return {"message": "TPoS does not exist."}, HTTPStatus.NOT_FOUND try: payment_hash, payment_request = await create_invoice( wallet_id=tpos.wallet, - amount=g.data["amount"], + amount=amount, memo=f"{tpos.name}", extra={"tag": "tpos"}, ) except Exception as e: - return jsonify({"message": str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR + return {"message": str(e)}, HTTPStatus.INTERNAL_SERVER_ERROR - return ( - jsonify({"payment_hash": payment_hash, "payment_request": payment_request}), - HTTPStatus.CREATED, - ) + return {"payment_hash": payment_hash, "payment_request": payment_request}), HTTPStatus.CREATED -@tpos_ext.route("/api/v1/tposs//invoices/", methods=["GET"]) -async def api_tpos_check_invoice(tpos_id, payment_hash): +@tpos_ext.get("/api/v1/tposs/{tpos_id}/invoices/{payment_hash}") +async def api_tpos_check_invoice(tpos_id: str, payment_hash: str): tpos = await get_tpos(tpos_id) if not tpos: - return jsonify({"message": "TPoS does not exist."}), HTTPStatus.NOT_FOUND + return {"message": "TPoS does not exist."}, HTTPStatus.NOT_FOUND try: status = await check_invoice_status(tpos.wallet, payment_hash) is_paid = not status.pending except Exception as exc: print(exc) - return jsonify({"paid": False}), HTTPStatus.OK + return {"paid": False}, HTTPStatus.OK if is_paid: wallet = await get_wallet(tpos.wallet) payment = await wallet.get_payment(payment_hash) await payment.set_pending(False) - return jsonify({"paid": True}), HTTPStatus.OK + return {"paid": True}, HTTPStatus.OK - return jsonify({"paid": False}), HTTPStatus.OK + return {"paid": False}, HTTPStatus.OK From 9ea3c51b92eeeafe722189b40b7e939b680890eb Mon Sep 17 00:00:00 2001 From: Ben Arc Date: Fri, 20 Aug 2021 17:15:04 +0100 Subject: [PATCH 05/12] Changed routs to request types --- lnbits/extensions/paywall/views_api.py | 49 +++++++++++-------------- lnbits/extensions/withdraw/views_api.py | 14 +++---- 2 files changed, 29 insertions(+), 34 deletions(-) diff --git a/lnbits/extensions/paywall/views_api.py b/lnbits/extensions/paywall/views_api.py index 9fbda2f17..a5d8e96d5 100644 --- a/lnbits/extensions/paywall/views_api.py +++ b/lnbits/extensions/paywall/views_api.py @@ -10,8 +10,9 @@ from .crud import create_paywall, get_paywall, get_paywalls, delete_paywall from typing import Optional from pydantic import BaseModel from fastapi import FastAPI, Query +from fastapi.encoders import jsonable_encoder -@paywall_ext.route("/api/v1/paywalls", methods=["GET"]) +@paywall_ext.get("/api/v1/paywalls") @api_check_wallet_key("invoice") async def api_paywalls(): wallet_ids = [g.wallet.id] @@ -20,7 +21,7 @@ async def api_paywalls(): wallet_ids = (await get_user(g.wallet.user)).wallet_ids return ( - jsonify([paywall._asdict() for paywall in await get_paywalls(wallet_ids)]), + jsonable_encoder([paywall._asdict() for paywall in await get_paywalls(wallet_ids)]), HTTPStatus.OK, ) @@ -32,45 +33,42 @@ class CreateData(BaseModel): amount: int = Query(None), remembers: bool = Query(None) -@paywall_ext.route("/api/v1/paywalls", methods=["POST"]) +@paywall_ext.post("/api/v1/paywalls") @api_check_wallet_key("invoice") async def api_paywall_create(data: CreateData): paywall = await create_paywall(wallet_id=g.wallet.id, **data) return paywall, HTTPStatus.CREATED -@paywall_ext.route("/api/v1/paywalls/", methods=["DELETE"]) +@paywall_ext.delete("/api/v1/paywalls/") @api_check_wallet_key("invoice") async def api_paywall_delete(paywall_id): paywall = await get_paywall(paywall_id) if not paywall: - return jsonify({"message": "Paywall does not exist."}), HTTPStatus.NOT_FOUND + return jsonable_encoder({"message": "Paywall does not exist."}), HTTPStatus.NOT_FOUND if paywall.wallet != g.wallet.id: - return jsonify({"message": "Not your paywall."}), HTTPStatus.FORBIDDEN + return jsonable_encoder({"message": "Not your paywall."}), HTTPStatus.FORBIDDEN await delete_paywall(paywall_id) return "", HTTPStatus.NO_CONTENT -@paywall_ext.route("/api/v1/paywalls//invoice", methods=["POST"]) -@api_validate_post_request( - schema={"amount": {"type": "integer", "min": 1, "required": True}} -) -async def api_paywall_create_invoice(paywall_id): +@paywall_ext.post("/api/v1/paywalls//invoice") +async def api_paywall_create_invoice(amount: int = Query(..., ge=1), paywall_id = None): paywall = await get_paywall(paywall_id) - if g.data["amount"] < paywall.amount: + if amount < paywall.amount: return ( - jsonify({"message": f"Minimum amount is {paywall.amount} sat."}), + jsonable_encoder({"message": f"Minimum amount is {paywall.amount} sat."}), HTTPStatus.BAD_REQUEST, ) try: amount = ( - g.data["amount"] if g.data["amount"] > paywall.amount else paywall.amount + amount if amount > paywall.amount else paywall.amount ) payment_hash, payment_request = await create_invoice( wallet_id=paywall.wallet, @@ -79,38 +77,35 @@ async def api_paywall_create_invoice(paywall_id): extra={"tag": "paywall"}, ) except Exception as e: - return jsonify({"message": str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR + return jsonable_encoder({"message": str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR return ( - jsonify({"payment_hash": payment_hash, "payment_request": payment_request}), + jsonable_encoder({"payment_hash": payment_hash, "payment_request": payment_request}), HTTPStatus.CREATED, ) -@paywall_ext.route("/api/v1/paywalls//check_invoice", methods=["POST"]) -@api_validate_post_request( - schema={"payment_hash": {"type": "string", "empty": False, "required": True}} -) -async def api_paywal_check_invoice(paywall_id): +@paywall_ext.post("/api/v1/paywalls//check_invoice") +async def api_paywal_check_invoice(payment_hash: str = Query(...), paywall_id = None): paywall = await get_paywall(paywall_id) if not paywall: - return jsonify({"message": "Paywall does not exist."}), HTTPStatus.NOT_FOUND + return jsonable_encoder({"message": "Paywall does not exist."}), HTTPStatus.NOT_FOUND try: - status = await check_invoice_status(paywall.wallet, g.data["payment_hash"]) + status = await check_invoice_status(paywall.wallet, payment_hash) is_paid = not status.pending except Exception: - return jsonify({"paid": False}), HTTPStatus.OK + return jsonable_encoder({"paid": False}), HTTPStatus.OK if is_paid: wallet = await get_wallet(paywall.wallet) - payment = await wallet.get_payment(g.data["payment_hash"]) + payment = await wallet.get_payment(payment_hash) await payment.set_pending(False) return ( - jsonify({"paid": True, "url": paywall.url, "remembers": paywall.remembers}), + jsonable_encoder({"paid": True, "url": paywall.url, "remembers": paywall.remembers}), HTTPStatus.OK, ) - return jsonify({"paid": False}), HTTPStatus.OK + return jsonable_encoder({"paid": False}), HTTPStatus.OK diff --git a/lnbits/extensions/withdraw/views_api.py b/lnbits/extensions/withdraw/views_api.py index 55f3c5e86..e306aac6e 100644 --- a/lnbits/extensions/withdraw/views_api.py +++ b/lnbits/extensions/withdraw/views_api.py @@ -20,7 +20,7 @@ from .crud import ( ) -@withdraw_ext.route("/api/v1/links", methods=["GET"]) +@withdraw_ext.get("/api/v1/links") @api_check_wallet_key("invoice") async def api_links(): wallet_ids = [g.wallet.id] @@ -51,7 +51,7 @@ async def api_links(): ) -@withdraw_ext.route("/api/v1/links/", methods=["GET"]) +@withdraw_ext.get("/api/v1/links/") @api_check_wallet_key("invoice") async def api_link_retrieve(link_id): link = await get_withdraw_link(link_id, 0) @@ -75,8 +75,8 @@ class CreateData(BaseModel): wait_time: int = Query(..., ge=1), is_unique: bool -@withdraw_ext.route("/api/v1/links", methods=["POST"]) -@withdraw_ext.route("/api/v1/links/", methods=["PUT"]) +@withdraw_ext.post("/api/v1/links") +@withdraw_ext.put("/api/v1/links/") @api_check_wallet_key("admin") async def api_link_create_or_update(data: CreateData, link_id: str = None): if data.max_withdrawable < data.min_withdrawable: @@ -118,7 +118,7 @@ async def api_link_create_or_update(data: CreateData, link_id: str = None): ) -@withdraw_ext.route("/api/v1/links/", methods=["DELETE"]) +@withdraw_ext.delete("/api/v1/links/") @api_check_wallet_key("admin") async def api_link_delete(link_id): link = await get_withdraw_link(link_id) @@ -137,8 +137,8 @@ async def api_link_delete(link_id): return "", HTTPStatus.NO_CONTENT -@withdraw_ext.route("/api/v1/links//", methods=["GET"]) +@withdraw_ext.get("/api/v1/links//") @api_check_wallet_key("invoice") async def api_hash_retrieve(the_hash, lnurl_id): hashCheck = await get_hash_check(the_hash, lnurl_id) - return jsonify(hashCheck), HTTPStatus.OK + return jsonable_encoder(hashCheck), HTTPStatus.OK From 539ae10c6f973e06d5826ab4bc5743ec95093ecb Mon Sep 17 00:00:00 2001 From: Ben Arc Date: Fri, 20 Aug 2021 17:46:57 +0100 Subject: [PATCH 06/12] started events --- lnbits/extensions/events/views_api.py | 98 +++++++++++---------------- 1 file changed, 38 insertions(+), 60 deletions(-) diff --git a/lnbits/extensions/events/views_api.py b/lnbits/extensions/events/views_api.py index fddbf2a45..16603a0f4 100644 --- a/lnbits/extensions/events/views_api.py +++ b/lnbits/extensions/events/views_api.py @@ -4,6 +4,7 @@ from http import HTTPStatus from lnbits.core.crud import get_user, get_wallet from lnbits.core.services import create_invoice, check_invoice_status from lnbits.decorators import api_check_wallet_key, api_validate_post_request +from fastapi.encoders import jsonable_encoder from . import events_ext from .crud import ( @@ -26,7 +27,7 @@ from .crud import ( -@events_ext.route("/api/v1/events", methods=["GET"]) +@events_ext.get("/api/v1/events") @api_check_wallet_key("invoice") async def api_events(): wallet_ids = [g.wallet.id] @@ -35,7 +36,7 @@ async def api_events(): wallet_ids = (await get_user(g.wallet.user)).wallet_ids return ( - jsonify([event._asdict() for event in await get_events(wallet_ids)]), + [event._asdict() for event in await get_events(wallet_ids)], HTTPStatus.OK, ) @@ -49,46 +50,34 @@ class CreateData(BaseModel): amount_tickets: int = Query(..., ge=0), price_per_ticket: int = Query(..., ge=0), -@events_ext.route("/api/v1/events", methods=["POST"]) -@events_ext.route("/api/v1/events/", methods=["PUT"]) +@events_ext.post("/api/v1/events") +@events_ext.put("/api/v1/events/") @api_check_wallet_key("invoice") -@api_validate_post_request( - schema={ - "wallet": {"type": "string", "empty": False, "required": True}, - "name": {"type": "string", "empty": False, "required": True}, - "info": {"type": "string", "min": 0, "required": True}, - "closing_date": {"type": "string", "empty": False, "required": True}, - "event_start_date": {"type": "string", "empty": False, "required": True}, - "event_end_date": {"type": "string", "empty": False, "required": True}, - "amount_tickets": {"type": "integer", "min": 0, "required": True}, - "price_per_ticket": {"type": "integer", "min": 0, "required": True}, - } -) async def api_event_create(data: CreateData, event_id=None): if event_id: event = await get_event(event_id) if not event: - return jsonify({"message": "Form does not exist."}), HTTPStatus.NOT_FOUND + return jsonable_encoder({"message": "Form does not exist."}), HTTPStatus.NOT_FOUND if event.wallet != g.wallet.id: - return jsonify({"message": "Not your event."}), HTTPStatus.FORBIDDEN + return jsonable_encoder({"message": "Not your event."}), HTTPStatus.FORBIDDEN - event = await update_event(event_id, **g.data) + event = await update_event(event_id, **data) else: - event = await create_event(**g.data) + event = await create_event(**data) - return jsonify(event._asdict()), HTTPStatus.CREATED + return event._asdict(), HTTPStatus.CREATED -@events_ext.route("/api/v1/events/", methods=["DELETE"]) +@events_ext.delete("/api/v1/events/") @api_check_wallet_key("invoice") async def api_form_delete(event_id): event = await get_event(event_id) if not event: - return jsonify({"message": "Event does not exist."}), HTTPStatus.NOT_FOUND + return jsonable_encoder({"message": "Event does not exist."}), HTTPStatus.NOT_FOUND if event.wallet != g.wallet.id: - return jsonify({"message": "Not your event."}), HTTPStatus.FORBIDDEN + return jsonable_encoder({"message": "Not your event."}), HTTPStatus.FORBIDDEN await delete_event(event_id) return "", HTTPStatus.NO_CONTENT @@ -97,7 +86,7 @@ async def api_form_delete(event_id): #########Tickets########## -@events_ext.route("/api/v1/tickets", methods=["GET"]) +@events_ext.get("/api/v1/tickets") @api_check_wallet_key("invoice") async def api_tickets(): wallet_ids = [g.wallet.id] @@ -106,22 +95,19 @@ async def api_tickets(): wallet_ids = (await get_user(g.wallet.user)).wallet_ids return ( - jsonify([ticket._asdict() for ticket in await get_tickets(wallet_ids)]), + [ticket._asdict() for ticket in await get_tickets(wallet_ids)], HTTPStatus.OK, ) +class CreateTicketData(BaseModel): + name: str = Query(...), + email: str -@events_ext.route("/api/v1/tickets//", methods=["POST"]) -@api_validate_post_request( - schema={ - "name": {"type": "string", "empty": False, "required": True}, - "email": {"type": "string", "empty": False, "required": True}, - } -) -async def api_ticket_make_ticket(event_id, sats): +@events_ext.post("/api/v1/tickets//") +async def api_ticket_make_ticket(data: CreateTicketData, event_id, sats): event = await get_event(event_id) if not event: - return jsonify({"message": "Event does not exist."}), HTTPStatus.NOT_FOUND + return jsonable_encoder({"message": "Event does not exist."}), HTTPStatus.NOT_FOUND try: payment_hash, payment_request = await create_invoice( wallet_id=event.wallet, @@ -133,19 +119,19 @@ async def api_ticket_make_ticket(event_id, sats): return jsonify({"message": str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR ticket = await create_ticket( - payment_hash=payment_hash, wallet=event.wallet, event=event_id, **g.data + payment_hash=payment_hash, wallet=event.wallet, event=event_id, **data ) if not ticket: - return jsonify({"message": "Event could not be fetched."}), HTTPStatus.NOT_FOUND + return jsonable_encoder({"message": "Event could not be fetched."}), HTTPStatus.NOT_FOUND return ( - jsonify({"payment_hash": payment_hash, "payment_request": payment_request}), + jsonable_encoder({"payment_hash": payment_hash, "payment_request": payment_request}), HTTPStatus.OK, ) -@events_ext.route("/api/v1/tickets/", methods=["GET"]) +@events_ext.get("/api/v1/tickets/") async def api_ticket_send_ticket(payment_hash): ticket = await get_ticket(payment_hash) @@ -153,7 +139,7 @@ async def api_ticket_send_ticket(payment_hash): status = await check_invoice_status(ticket.wallet, payment_hash) is_paid = not status.pending except Exception: - return jsonify({"message": "Not paid."}), HTTPStatus.NOT_FOUND + return jsonable_encoder({"message": "Not paid."}), HTTPStatus.NOT_FOUND if is_paid: wallet = await get_wallet(ticket.wallet) @@ -161,21 +147,21 @@ async def api_ticket_send_ticket(payment_hash): await payment.set_pending(False) ticket = await set_ticket_paid(payment_hash=payment_hash) - return jsonify({"paid": True, "ticket_id": ticket.id}), HTTPStatus.OK + return jsonable_encoder({"paid": True, "ticket_id": ticket.id}), HTTPStatus.OK - return jsonify({"paid": False}), HTTPStatus.OK + return jsonable_encoder({"paid": False}), HTTPStatus.OK -@events_ext.route("/api/v1/tickets/", methods=["DELETE"]) +@events_ext.delete("/api/v1/tickets/") @api_check_wallet_key("invoice") async def api_ticket_delete(ticket_id): ticket = await get_ticket(ticket_id) if not ticket: - return jsonify({"message": "Ticket does not exist."}), HTTPStatus.NOT_FOUND + return jsonable_encoder({"message": "Ticket does not exist."}), HTTPStatus.NOT_FOUND if ticket.wallet != g.wallet.id: - return jsonify({"message": "Not your ticket."}), HTTPStatus.FORBIDDEN + return jsonable_encoder({"message": "Not your ticket."}), HTTPStatus.FORBIDDEN await delete_ticket(ticket_id) return "", HTTPStatus.NO_CONTENT @@ -184,34 +170,26 @@ async def api_ticket_delete(ticket_id): # Event Tickets -@events_ext.route("/api/v1/eventtickets//", methods=["GET"]) +@events_ext.get("/api/v1/eventtickets//") async def api_event_tickets(wallet_id, event_id): - return ( - jsonify( - [ - ticket._asdict() - for ticket in await get_event_tickets( - wallet_id=wallet_id, event_id=event_id - ) - ] - ), + return ([ticket._asdict() for ticket in await get_event_tickets(wallet_id=wallet_id, event_id=event_id)], HTTPStatus.OK, ) -@events_ext.route("/api/v1/register/ticket/", methods=["GET"]) +@events_ext.get("/api/v1/register/ticket/") async def api_event_register_ticket(ticket_id): ticket = await get_ticket(ticket_id) if not ticket: - return jsonify({"message": "Ticket does not exist."}), HTTPStatus.FORBIDDEN + return jsonable_encoder({"message": "Ticket does not exist."}), HTTPStatus.FORBIDDEN if not ticket.paid: - return jsonify({"message": "Ticket not paid for."}), HTTPStatus.FORBIDDEN + return jsonable_encoder({"message": "Ticket not paid for."}), HTTPStatus.FORBIDDEN if ticket.registered == True: - return jsonify({"message": "Ticket already registered"}), HTTPStatus.FORBIDDEN + return jsonable_encoder({"message": "Ticket already registered"}), HTTPStatus.FORBIDDEN return ( - jsonify([ticket._asdict() for ticket in await reg_ticket(ticket_id)]), + [ticket._asdict() for ticket in await reg_ticket(ticket_id)], HTTPStatus.OK, ) From 12846f5caca42369da97cfde20d76a9079428120 Mon Sep 17 00:00:00 2001 From: Tiago vasconcelos Date: Fri, 20 Aug 2021 17:51:30 +0100 Subject: [PATCH 07/12] testing push --- lnbits/extensions/tpos/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lnbits/extensions/tpos/views.py b/lnbits/extensions/tpos/views.py index ce8422956..1088ae2ec 100644 --- a/lnbits/extensions/tpos/views.py +++ b/lnbits/extensions/tpos/views.py @@ -7,14 +7,14 @@ from . import tpos_ext from .crud import get_tpos -@tpos_ext.route("/") +@tpos_ext.get("/") @validate_uuids(["usr"], required=True) @check_user_exists() async def index(): return await render_template("tpos/index.html", user=g.user) -@tpos_ext.route("/") +@tpos_ext.get("/{tpos_id}") async def tpos(tpos_id): tpos = await get_tpos(tpos_id) if not tpos: From 09eebd83512c488057bdf14468f967f19fc82d98 Mon Sep 17 00:00:00 2001 From: Ben Arc Date: Fri, 20 Aug 2021 18:43:11 +0100 Subject: [PATCH 08/12] Starting jukebox --- lnbits/extensions/jukebox/views_api.py | 96 ++++++++++++-------------- 1 file changed, 46 insertions(+), 50 deletions(-) diff --git a/lnbits/extensions/jukebox/views_api.py b/lnbits/extensions/jukebox/views_api.py index 1390a019a..317134b15 100644 --- a/lnbits/extensions/jukebox/views_api.py +++ b/lnbits/extensions/jukebox/views_api.py @@ -19,16 +19,15 @@ from .crud import ( update_jukebox_payment, ) from lnbits.core.services import create_invoice, check_invoice_status +from fastapi.encoders import jsonable_encoder -@jukebox_ext.route("/api/v1/jukebox", methods=["GET"]) +@jukebox_ext.get("/api/v1/jukebox") @api_check_wallet_key("admin") async def api_get_jukeboxs(): try: return ( - jsonify( - [{**jukebox._asdict()} for jukebox in await get_jukeboxs(g.wallet.user)] - ), + [{**jukebox._asdict()} for jukebox in await get_jukeboxs(g.wallet.user)], HTTPStatus.OK, ) except: @@ -38,7 +37,7 @@ async def api_get_jukeboxs(): ##################SPOTIFY AUTH##################### -@jukebox_ext.route("/api/v1/jukebox/spotify/cb/", methods=["GET"]) +@jukebox_ext.get("/api/v1/jukebox/spotify/cb/") async def api_check_credentials_callbac(juke_id): sp_code = "" sp_access_token = "" @@ -47,7 +46,7 @@ async def api_check_credentials_callbac(juke_id): jukebox = await get_jukebox(juke_id) except: return ( - jsonify({"error": "No Jukebox"}), + jsonable_encoder({"error": "No Jukebox"}), HTTPStatus.FORBIDDEN, ) if request.args.get("code"): @@ -67,48 +66,45 @@ async def api_check_credentials_callbac(juke_id): return "

Success!

You can close this window

" -@jukebox_ext.route("/api/v1/jukebox/", methods=["GET"]) +@jukebox_ext.get("/api/v1/jukebox/") @api_check_wallet_key("admin") async def api_check_credentials_check(juke_id): jukebox = await get_jukebox(juke_id) return jsonify(jukebox._asdict()), HTTPStatus.CREATED -@jukebox_ext.route("/api/v1/jukebox/", methods=["POST"]) -@jukebox_ext.route("/api/v1/jukebox/", methods=["PUT"]) +class CreateData(BaseModel): + user: str = None, + title: str = None, + wallet: str = None, + sp_user: str = None, + sp_secret: str = None, + sp_access_token: Optional[str] = None, + sp_refresh_token: Optional[str] = None, + sp_device: Optional[str] = None, + sp_playlists: Optional[str] = None, + price: Optional[str] = None, + +@jukebox_ext.post("/api/v1/jukebox/") +@jukebox_ext.put("/api/v1/jukebox/") @api_check_wallet_key("admin") -@api_validate_post_request( - schema={ - "user": {"type": "string", "empty": False, "required": True}, - "title": {"type": "string", "empty": False, "required": True}, - "wallet": {"type": "string", "empty": False, "required": True}, - "sp_user": {"type": "string", "empty": False, "required": True}, - "sp_secret": {"type": "string", "required": True}, - "sp_access_token": {"type": "string", "required": False}, - "sp_refresh_token": {"type": "string", "required": False}, - "sp_device": {"type": "string", "required": False}, - "sp_playlists": {"type": "string", "required": False}, - "price": {"type": "string", "required": False}, - } -) -async def api_create_update_jukebox(juke_id=None): +async def api_create_update_jukebox(data: CreateData, juke_id=None): if juke_id: - jukebox = await update_jukebox(juke_id=juke_id, inkey=g.wallet.inkey, **g.data) + jukebox = await update_jukebox(juke_id=juke_id, inkey=g.wallet.inkey, **data) else: - jukebox = await create_jukebox(inkey=g.wallet.inkey, **g.data) + jukebox = await create_jukebox(inkey=g.wallet.inkey, **data) - return jsonify(jukebox._asdict()), HTTPStatus.CREATED + return jukebox._asdict(), HTTPStatus.CREATED -@jukebox_ext.route("/api/v1/jukebox/", methods=["DELETE"]) +@jukebox_ext.delete("/api/v1/jukebox/") @api_check_wallet_key("admin") async def api_delete_item(juke_id): await delete_jukebox(juke_id) try: return ( - jsonify( [{**jukebox._asdict()} for jukebox in await get_jukeboxs(g.wallet.user)] - ), + , HTTPStatus.OK, ) except: @@ -120,15 +116,15 @@ async def api_delete_item(juke_id): ######GET ACCESS TOKEN###### -@jukebox_ext.route( - "/api/v1/jukebox/jb/playlist//", methods=["GET"] +@jukebox_ext.get( + "/api/v1/jukebox/jb/playlist//" ) async def api_get_jukebox_song(juke_id, sp_playlist, retry=False): try: jukebox = await get_jukebox(juke_id) except: return ( - jsonify({"error": "No Jukebox"}), + jsonable_encoder({"error": "No Jukebox"}), HTTPStatus.FORBIDDEN, ) tracks = [] @@ -146,7 +142,7 @@ async def api_get_jukebox_song(juke_id, sp_playlist, retry=False): return False elif retry: return ( - jsonify({"error": "Failed to get auth"}), + jsonable_encoder({"error": "Failed to get auth"}), HTTPStatus.FORBIDDEN, ) else: @@ -166,7 +162,7 @@ async def api_get_jukebox_song(juke_id, sp_playlist, retry=False): ) except AssertionError: something = None - return jsonify([track for track in tracks]) + return [track for track in tracks] async def api_get_token(juke_id): @@ -174,7 +170,7 @@ async def api_get_token(juke_id): jukebox = await get_jukebox(juke_id) except: return ( - jsonify({"error": "No Jukebox"}), + jsonable_encoder({"error": "No Jukebox"}), HTTPStatus.FORBIDDEN, ) @@ -217,7 +213,7 @@ async def api_get_jukebox_device_check(juke_id, retry=False): jukebox = await get_jukebox(juke_id) except: return ( - jsonify({"error": "No Jukebox"}), + jsonable_encoder({"error": "No Jukebox"}), HTTPStatus.FORBIDDEN, ) async with httpx.AsyncClient() as client: @@ -236,19 +232,19 @@ async def api_get_jukebox_device_check(juke_id, retry=False): token = await api_get_token(juke_id) if token == False: return ( - jsonify({"error": "No device connected"}), + jsonable_encoder({"error": "No device connected"}), HTTPStatus.FORBIDDEN, ) elif retry: return ( - jsonify({"error": "Failed to get auth"}), + jsonable_encoder({"error": "Failed to get auth"}), HTTPStatus.FORBIDDEN, ) else: return api_get_jukebox_device_check(juke_id, retry=True) else: return ( - jsonify({"error": "No device connected"}), + jsonable_encoder({"error": "No device connected"}), HTTPStatus.FORBIDDEN, ) @@ -262,7 +258,7 @@ async def api_get_jukebox_invoice(juke_id, song_id): jukebox = await get_jukebox(juke_id) except: return ( - jsonify({"error": "No Jukebox"}), + jsonable_encoder({"error": "No Jukebox"}), HTTPStatus.FORBIDDEN, ) try: @@ -274,12 +270,12 @@ async def api_get_jukebox_invoice(juke_id, song_id): deviceConnected = True if not deviceConnected: return ( - jsonify({"error": "No device connected"}), + jsonable_encoder({"error": "No device connected"}), HTTPStatus.NOT_FOUND, ) except: return ( - jsonify({"error": "No device connected"}), + jsonable_encoder({"error": "No device connected"}), HTTPStatus.NOT_FOUND, ) @@ -292,32 +288,32 @@ async def api_get_jukebox_invoice(juke_id, song_id): jukebox_payment = await create_jukebox_payment(song_id, invoice[0], juke_id) - return jsonify(invoice, jukebox_payment) + return invoice, jukebox_payment -@jukebox_ext.route( - "/api/v1/jukebox/jb/checkinvoice//", methods=["GET"] +@jukebox_ext.get( + "/api/v1/jukebox/jb/checkinvoice//" ) async def api_get_jukebox_invoice_check(pay_hash, juke_id): try: jukebox = await get_jukebox(juke_id) except: return ( - jsonify({"error": "No Jukebox"}), + jsonable_encoder({"error": "No Jukebox"}), HTTPStatus.FORBIDDEN, ) try: status = await check_invoice_status(jukebox.wallet, pay_hash) is_paid = not status.pending except Exception as exc: - return jsonify({"paid": False}), HTTPStatus.OK + return jsonable_encoder({"paid": False}), HTTPStatus.OK if is_paid: wallet = await get_wallet(jukebox.wallet) payment = await wallet.get_payment(pay_hash) await payment.set_pending(False) await update_jukebox_payment(pay_hash, paid=True) - return jsonify({"paid": True}), HTTPStatus.OK - return jsonify({"paid": False}), HTTPStatus.OK + return jsonable_encoder({"paid": True}), HTTPStatus.OK + return jsonable_encoder({"paid": False}), HTTPStatus.OK @jukebox_ext.route( From 76cb93b276296e0ad5c20746d04177bab19f6748 Mon Sep 17 00:00:00 2001 From: Ben Arc Date: Fri, 20 Aug 2021 19:48:44 +0100 Subject: [PATCH 09/12] Started watchonly --- lnbits/extensions/tpos/views_api.py | 14 +++++++++----- lnbits/extensions/watchonly/views_api.py | 6 ++---- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/lnbits/extensions/tpos/views_api.py b/lnbits/extensions/tpos/views_api.py index 6b1374484..3b95491d5 100644 --- a/lnbits/extensions/tpos/views_api.py +++ b/lnbits/extensions/tpos/views_api.py @@ -17,7 +17,7 @@ from .models import TPoS @tpos_ext.get("/api/v1/tposs") @api_check_wallet_key("invoice") -async def api_tposs(all_wallets: boolean = Query(None)): +async def api_tposs(all_wallets: bool = Query(None)): wallet_ids = [g.wallet.id] if all_wallets: wallet_ids = wallet_ids = (await get_user(g.wallet.user)).wallet_ids(await get_user(g.wallet.user)).wallet_ids @@ -27,6 +27,10 @@ async def api_tposs(all_wallets: boolean = Query(None)): return [tpos._asdict() for tpos in await get_tposs(wallet_ids)], HTTPStatus.OK +class CreateData(BaseModel): + name: str + currency: str + @tpos_ext.post("/api/v1/tposs") @api_check_wallet_key("invoice") # @api_validate_post_request( @@ -35,8 +39,8 @@ async def api_tposs(all_wallets: boolean = Query(None)): # "currency": {"type": "string", "empty": False, "required": True}, # } # ) -async def api_tpos_create(name: str = Query(...), currency: str = Query(...)): - tpos = await create_tpos(wallet_id=g.wallet.id, **g.data) +async def api_tpos_create(data: CreateData): + tpos = await create_tpos(wallet_id=g.wallet.id, **data) return tpos._asdict(), HTTPStatus.CREATED @@ -60,7 +64,7 @@ async def api_tpos_delete(tpos_id: str): # @api_validate_post_request( # schema={"amount": {"type": "integer", "min": 1, "required": True}} # ) -async def api_tpos_create_invoice(amount: int = Query(..., ge=1), tpos_id: str): +async def api_tpos_create_invoice(amount: int = Query(..., ge=1), tpos_id: str = None): tpos = await get_tpos(tpos_id) if not tpos: @@ -76,7 +80,7 @@ async def api_tpos_create_invoice(amount: int = Query(..., ge=1), tpos_id: str): except Exception as e: return {"message": str(e)}, HTTPStatus.INTERNAL_SERVER_ERROR - return {"payment_hash": payment_hash, "payment_request": payment_request}), HTTPStatus.CREATED + return {"payment_hash": payment_hash, "payment_request": payment_request}, HTTPStatus.CREATED @tpos_ext.get("/api/v1/tposs/{tpos_id}/invoices/{payment_hash}") diff --git a/lnbits/extensions/watchonly/views_api.py b/lnbits/extensions/watchonly/views_api.py index 01ae25277..108ef2a9b 100644 --- a/lnbits/extensions/watchonly/views_api.py +++ b/lnbits/extensions/watchonly/views_api.py @@ -24,15 +24,13 @@ from .crud import ( ###################WALLETS############################# -@watchonly_ext.route("/api/v1/wallet", methods=["GET"]) +@watchonly_ext.get("/api/v1/wallet") @api_check_wallet_key("invoice") async def api_wallets_retrieve(): try: return ( - jsonify( - [wallet._asdict() for wallet in await get_watch_wallets(g.wallet.user)] - ), + [wallet._asdict() for wallet in await get_watch_wallets(g.wallet.user)], HTTPStatus.OK, ) except: From 37a7950f0fc044e568236e5c80d992813eb43d94 Mon Sep 17 00:00:00 2001 From: Stefan Stammberger Date: Fri, 20 Aug 2021 20:54:59 +0200 Subject: [PATCH 10/12] fix: syntax errors --- lnbits/core/views/api.py | 17 ++++------------- lnbits/extensions/events/views_api.py | 20 +++++++++++--------- lnbits/extensions/jukebox/views_api.py | 23 +++++++++++++---------- lnbits/extensions/paywall/views_api.py | 8 ++++---- lnbits/extensions/tpos/views_api.py | 6 +++--- lnbits/extensions/withdraw/views_api.py | 10 +++++----- 6 files changed, 40 insertions(+), 44 deletions(-) diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index c7e75f7a4..2a905bcf2 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -1,3 +1,4 @@ +from pydantic.types import constr import trio import json import httpx @@ -5,15 +6,13 @@ import hashlib from urllib.parse import urlparse, urlunparse, urlencode, parse_qs, ParseResult from quart import g, current_app, make_response, url_for -from fastapi import FastAPI +from fastapi import Query from fastapi.encoders import jsonable_encoder -from fastapi.responses import JSONResponse -from pydantic import BaseModel from http import HTTPStatus from binascii import unhexlify -from typing import Dict, Union +from typing import Dict, List, Optional, Union from lnbits import bolt11, lnurl from lnbits.decorators import api_check_wallet_key, api_validate_post_request @@ -95,15 +94,7 @@ async def api_payments(): # async def api_payments_create_invoice(amount: List[str] = Query([type: str = Query(None)])): -class Memo(BaseModel): - type: str - empty: bool - required: bool - excludes: bool - excludes: bool = Query("description_hash") - - -async def api_payments_create_invoice(amount: int = Query(...), memo: Memo ): +async def api_payments_create_invoice(memo: Union[None, constr(min_length=1)], amount: int): if "description_hash" in g.data: description_hash = unhexlify(g.data["description_hash"]) memo = "" diff --git a/lnbits/extensions/events/views_api.py b/lnbits/extensions/events/views_api.py index 16603a0f4..600c87676 100644 --- a/lnbits/extensions/events/views_api.py +++ b/lnbits/extensions/events/views_api.py @@ -5,6 +5,8 @@ from lnbits.core.crud import get_user, get_wallet from lnbits.core.services import create_invoice, check_invoice_status from lnbits.decorators import api_check_wallet_key, api_validate_post_request from fastapi.encoders import jsonable_encoder +from fastapi import Query +from pydantic import BaseModel from . import events_ext from .crud import ( @@ -41,14 +43,14 @@ async def api_events(): ) class CreateData(BaseModel): - wallet: str = Query(...), - name: str = Query(...), - info: str = Query(...), - closing_date: str = Query(...), - event_start_date: str = Query(...), - event_end_date: str = Query(...), - amount_tickets: int = Query(..., ge=0), - price_per_ticket: int = Query(..., ge=0), + wallet: str = Query(...) + name: str = Query(...) + info: str = Query(...) + closing_date: str = Query(...) + event_start_date: str = Query(...) + event_end_date: str = Query(...) + amount_tickets: int = Query(..., ge=0) + price_per_ticket: int = Query(..., ge=0) @events_ext.post("/api/v1/events") @events_ext.put("/api/v1/events/") @@ -100,7 +102,7 @@ async def api_tickets(): ) class CreateTicketData(BaseModel): - name: str = Query(...), + name: str = Query(...) email: str @events_ext.post("/api/v1/tickets//") diff --git a/lnbits/extensions/jukebox/views_api.py b/lnbits/extensions/jukebox/views_api.py index 317134b15..88a40fa8f 100644 --- a/lnbits/extensions/jukebox/views_api.py +++ b/lnbits/extensions/jukebox/views_api.py @@ -4,6 +4,9 @@ import base64 from lnbits.core.crud import get_wallet from lnbits.core.services import create_invoice, check_invoice_status import json +from typing import Optional +from pydantic import BaseModel + from lnbits.decorators import api_check_wallet_key, api_validate_post_request import httpx @@ -74,16 +77,16 @@ async def api_check_credentials_check(juke_id): class CreateData(BaseModel): - user: str = None, - title: str = None, - wallet: str = None, - sp_user: str = None, - sp_secret: str = None, - sp_access_token: Optional[str] = None, - sp_refresh_token: Optional[str] = None, - sp_device: Optional[str] = None, - sp_playlists: Optional[str] = None, - price: Optional[str] = None, + user: str = None + title: str = None + wallet: str = None + sp_user: str = None + sp_secret: str = None + sp_access_token: Optional[str] = None + sp_refresh_token: Optional[str] = None + sp_device: Optional[str] = None + sp_playlists: Optional[str] = None + price: Optional[str] = None @jukebox_ext.post("/api/v1/jukebox/") @jukebox_ext.put("/api/v1/jukebox/") diff --git a/lnbits/extensions/paywall/views_api.py b/lnbits/extensions/paywall/views_api.py index a5d8e96d5..f036c2079 100644 --- a/lnbits/extensions/paywall/views_api.py +++ b/lnbits/extensions/paywall/views_api.py @@ -27,10 +27,10 @@ async def api_paywalls(): class CreateData(BaseModel): - url: Optional[str] = Query(...), - memo: Optional[str] = Query(...), - description: str = Query(None), - amount: int = Query(None), + url: Optional[str] = Query(...) + memo: Optional[str] = Query(...) + description: str = Query(None) + amount: int = Query(None) remembers: bool = Query(None) @paywall_ext.post("/api/v1/paywalls") diff --git a/lnbits/extensions/tpos/views_api.py b/lnbits/extensions/tpos/views_api.py index 6b1374484..fc530e651 100644 --- a/lnbits/extensions/tpos/views_api.py +++ b/lnbits/extensions/tpos/views_api.py @@ -17,7 +17,7 @@ from .models import TPoS @tpos_ext.get("/api/v1/tposs") @api_check_wallet_key("invoice") -async def api_tposs(all_wallets: boolean = Query(None)): +async def api_tposs(all_wallets: bool = Query(None)): wallet_ids = [g.wallet.id] if all_wallets: wallet_ids = wallet_ids = (await get_user(g.wallet.user)).wallet_ids(await get_user(g.wallet.user)).wallet_ids @@ -60,7 +60,7 @@ async def api_tpos_delete(tpos_id: str): # @api_validate_post_request( # schema={"amount": {"type": "integer", "min": 1, "required": True}} # ) -async def api_tpos_create_invoice(amount: int = Query(..., ge=1), tpos_id: str): +async def api_tpos_create_invoice(tpos_id: str, amount: int = Query(..., ge=1)): tpos = await get_tpos(tpos_id) if not tpos: @@ -76,7 +76,7 @@ async def api_tpos_create_invoice(amount: int = Query(..., ge=1), tpos_id: str): except Exception as e: return {"message": str(e)}, HTTPStatus.INTERNAL_SERVER_ERROR - return {"payment_hash": payment_hash, "payment_request": payment_request}), HTTPStatus.CREATED + return {"payment_hash": payment_hash, "payment_request": payment_request}, HTTPStatus.CREATED @tpos_ext.get("/api/v1/tposs/{tpos_id}/invoices/{payment_hash}") diff --git a/lnbits/extensions/withdraw/views_api.py b/lnbits/extensions/withdraw/views_api.py index e306aac6e..6e6d6cab2 100644 --- a/lnbits/extensions/withdraw/views_api.py +++ b/lnbits/extensions/withdraw/views_api.py @@ -68,11 +68,11 @@ async def api_link_retrieve(link_id): return jsonable_encoder({**link, **{"lnurl": link.lnurl}}), HTTPStatus.OK class CreateData(BaseModel): - title: str = Query(...), - min_withdrawable: int = Query(..., ge=1), - max_withdrawable: int = Query(..., ge=1), - uses: int = Query(..., ge=1), - wait_time: int = Query(..., ge=1), + title: str = Query(...) + min_withdrawable: int = Query(..., ge=1) + max_withdrawable: int = Query(..., ge=1) + uses: int = Query(..., ge=1) + wait_time: int = Query(..., ge=1) is_unique: bool @withdraw_ext.post("/api/v1/links") From c824155ea48dcc8ceb6dde54b33a0d94ddd00bf7 Mon Sep 17 00:00:00 2001 From: Ben Arc Date: Fri, 20 Aug 2021 20:12:03 +0100 Subject: [PATCH 11/12] Working on jukebox --- lnbits/extensions/jukebox/views.py | 4 +- lnbits/extensions/jukebox/views_api.py | 66 ++++++++++++------------ lnbits/extensions/watchonly/views.py | 10 +--- lnbits/extensions/watchonly/views_api.py | 55 ++++++++------------ lnbits/extensions/withdraw/views.py | 8 +-- 5 files changed, 62 insertions(+), 81 deletions(-) diff --git a/lnbits/extensions/jukebox/views.py b/lnbits/extensions/jukebox/views.py index f439110a0..b0769cfdd 100644 --- a/lnbits/extensions/jukebox/views.py +++ b/lnbits/extensions/jukebox/views.py @@ -12,14 +12,14 @@ from .crud import get_jukebox from .views_api import api_get_jukebox_device_check -@jukebox_ext.route("/") +@jukebox_ext.get("/") @validate_uuids(["usr"], required=True) @check_user_exists() async def index(): return await render_template("jukebox/index.html", user=g.user) -@jukebox_ext.route("/") +@jukebox_ext.get("/") async def connect_to_jukebox(juke_id): jukebox = await get_jukebox(juke_id) if not jukebox: diff --git a/lnbits/extensions/jukebox/views_api.py b/lnbits/extensions/jukebox/views_api.py index 317134b15..4f303a664 100644 --- a/lnbits/extensions/jukebox/views_api.py +++ b/lnbits/extensions/jukebox/views_api.py @@ -46,7 +46,7 @@ async def api_check_credentials_callbac(juke_id): jukebox = await get_jukebox(juke_id) except: return ( - jsonable_encoder({"error": "No Jukebox"}), + {"error": "No Jukebox"}, HTTPStatus.FORBIDDEN, ) if request.args.get("code"): @@ -207,13 +207,13 @@ async def api_get_token(juke_id): ######CHECK DEVICE -@jukebox_ext.route("/api/v1/jukebox/jb/", methods=["GET"]) +@jukebox_ext.get("/api/v1/jukebox/jb/") async def api_get_jukebox_device_check(juke_id, retry=False): try: jukebox = await get_jukebox(juke_id) except: return ( - jsonable_encoder({"error": "No Jukebox"}), + {"error": "No Jukebox"}, HTTPStatus.FORBIDDEN, ) async with httpx.AsyncClient() as client: @@ -252,13 +252,13 @@ async def api_get_jukebox_device_check(juke_id, retry=False): ######GET INVOICE STUFF -@jukebox_ext.route("/api/v1/jukebox/jb/invoice//", methods=["GET"]) +@jukebox_ext.get("/api/v1/jukebox/jb/invoice//") async def api_get_jukebox_invoice(juke_id, song_id): try: jukebox = await get_jukebox(juke_id) except: return ( - jsonable_encoder({"error": "No Jukebox"}), + {"error": "No Jukebox"}, HTTPStatus.FORBIDDEN, ) try: @@ -270,12 +270,12 @@ async def api_get_jukebox_invoice(juke_id, song_id): deviceConnected = True if not deviceConnected: return ( - jsonable_encoder({"error": "No device connected"}), + {"error": "No device connected"}, HTTPStatus.NOT_FOUND, ) except: return ( - jsonable_encoder({"error": "No device connected"}), + {"error": "No device connected"}, HTTPStatus.NOT_FOUND, ) @@ -299,25 +299,25 @@ async def api_get_jukebox_invoice_check(pay_hash, juke_id): jukebox = await get_jukebox(juke_id) except: return ( - jsonable_encoder({"error": "No Jukebox"}), + {"error": "No Jukebox"}, HTTPStatus.FORBIDDEN, ) try: status = await check_invoice_status(jukebox.wallet, pay_hash) is_paid = not status.pending except Exception as exc: - return jsonable_encoder({"paid": False}), HTTPStatus.OK + return {"paid": False}, HTTPStatus.OK if is_paid: wallet = await get_wallet(jukebox.wallet) payment = await wallet.get_payment(pay_hash) await payment.set_pending(False) await update_jukebox_payment(pay_hash, paid=True) - return jsonable_encoder({"paid": True}), HTTPStatus.OK - return jsonable_encoder({"paid": False}), HTTPStatus.OK + return {"paid": True}, HTTPStatus.OK + return {"paid": False}, HTTPStatus.OK -@jukebox_ext.route( - "/api/v1/jukebox/jb/invoicep///", methods=["GET"] +@jukebox_ext.get( + "/api/v1/jukebox/jb/invoicep///" ) async def api_get_jukebox_invoice_paid(song_id, juke_id, pay_hash, retry=False): try: @@ -356,17 +356,17 @@ async def api_get_jukebox_invoice_paid(song_id, juke_id, pay_hash, retry=False): headers={"Authorization": "Bearer " + jukebox.sp_access_token}, ) if r.status_code == 204: - return jsonify(jukebox_payment), HTTPStatus.OK + return jukebox_payment, HTTPStatus.OK elif r.status_code == 401 or r.status_code == 403: token = await api_get_token(juke_id) if token == False: return ( - jsonify({"error": "Invoice not paid"}), + {"error": "Invoice not paid"}, HTTPStatus.FORBIDDEN, ) elif retry: return ( - jsonify({"error": "Failed to get auth"}), + {"error": "Failed to get auth"}, HTTPStatus.FORBIDDEN, ) else: @@ -375,7 +375,7 @@ async def api_get_jukebox_invoice_paid(song_id, juke_id, pay_hash, retry=False): ) else: return ( - jsonify({"error": "Invoice not paid"}), + {"error": "Invoice not paid"}, HTTPStatus.FORBIDDEN, ) elif r.status_code == 200: @@ -389,18 +389,18 @@ async def api_get_jukebox_invoice_paid(song_id, juke_id, pay_hash, retry=False): headers={"Authorization": "Bearer " + jukebox.sp_access_token}, ) if r.status_code == 204: - return jsonify(jukebox_payment), HTTPStatus.OK + return jukebox_payment, HTTPStatus.OK elif r.status_code == 401 or r.status_code == 403: token = await api_get_token(juke_id) if token == False: return ( - jsonify({"error": "Invoice not paid"}), + {"error": "Invoice not paid"}, HTTPStatus.OK, ) elif retry: return ( - jsonify({"error": "Failed to get auth"}), + {"error": "Failed to get auth"}, HTTPStatus.FORBIDDEN, ) else: @@ -409,38 +409,38 @@ async def api_get_jukebox_invoice_paid(song_id, juke_id, pay_hash, retry=False): ) else: return ( - jsonify({"error": "Invoice not paid"}), + {"error": "Invoice not paid"}, HTTPStatus.OK, ) elif r.status_code == 401 or r.status_code == 403: token = await api_get_token(juke_id) if token == False: return ( - jsonify({"error": "Invoice not paid"}), + {"error": "Invoice not paid"}, HTTPStatus.OK, ) elif retry: return ( - jsonify({"error": "Failed to get auth"}), + {"error": "Failed to get auth"}, HTTPStatus.FORBIDDEN, ) else: return await api_get_jukebox_invoice_paid( song_id, juke_id, pay_hash ) - return jsonify({"error": "Invoice not paid"}), HTTPStatus.OK + return {"error": "Invoice not paid"}, HTTPStatus.OK ############################GET TRACKS -@jukebox_ext.route("/api/v1/jukebox/jb/currently/", methods=["GET"]) +@jukebox_ext.get("/api/v1/jukebox/jb/currently/") async def api_get_jukebox_currently(juke_id, retry=False): try: jukebox = await get_jukebox(juke_id) except: return ( - jsonify({"error": "No Jukebox"}), + {"error": "No Jukebox"}, HTTPStatus.FORBIDDEN, ) async with httpx.AsyncClient() as client: @@ -451,7 +451,7 @@ async def api_get_jukebox_currently(juke_id, retry=False): headers={"Authorization": "Bearer " + jukebox.sp_access_token}, ) if r.status_code == 204: - return jsonify({"error": "Nothing"}), HTTPStatus.OK + return {"error": "Nothing"}, HTTPStatus.OK elif r.status_code == 200: try: response = r.json() @@ -463,25 +463,25 @@ async def api_get_jukebox_currently(juke_id, retry=False): "artist": response["item"]["artists"][0]["name"], "image": response["item"]["album"]["images"][0]["url"], } - return jsonify(track), HTTPStatus.OK + return track, HTTPStatus.OK except: - return jsonify("Something went wrong"), HTTPStatus.NOT_FOUND + return "Something went wrong", HTTPStatus.NOT_FOUND elif r.status_code == 401: token = await api_get_token(juke_id) if token == False: return ( - jsonify({"error": "Invoice not paid"}), + {"error": "Invoice not paid"}, HTTPStatus.FORBIDDEN, ) elif retry: return ( - jsonify({"error": "Failed to get auth"}), + {"error": "Failed to get auth"}, HTTPStatus.FORBIDDEN, ) else: return await api_get_jukebox_currently(juke_id, retry=True) else: - return jsonify("Something went wrong"), HTTPStatus.NOT_FOUND + return "Something went wrong", HTTPStatus.NOT_FOUND except AssertionError: - return jsonify("Something went wrong"), HTTPStatus.NOT_FOUND + return "Something went wrong", HTTPStatus.NOT_FOUND diff --git a/lnbits/extensions/watchonly/views.py b/lnbits/extensions/watchonly/views.py index e82469680..f0c38712b 100644 --- a/lnbits/extensions/watchonly/views.py +++ b/lnbits/extensions/watchonly/views.py @@ -6,17 +6,9 @@ from lnbits.decorators import check_user_exists, validate_uuids from . import watchonly_ext -@watchonly_ext.route("/") +@watchonly_ext.get("/") @validate_uuids(["usr"], required=True) @check_user_exists() async def index(): return await render_template("watchonly/index.html", user=g.user) - -@watchonly_ext.route("/") -async def display(charge_id): - link = get_payment(charge_id) or abort( - HTTPStatus.NOT_FOUND, "Charge link does not exist." - ) - - return await render_template("watchonly/display.html", link=link) diff --git a/lnbits/extensions/watchonly/views_api.py b/lnbits/extensions/watchonly/views_api.py index 108ef2a9b..24a269bf8 100644 --- a/lnbits/extensions/watchonly/views_api.py +++ b/lnbits/extensions/watchonly/views_api.py @@ -37,71 +37,65 @@ async def api_wallets_retrieve(): return "" -@watchonly_ext.route("/api/v1/wallet/", methods=["GET"]) +@watchonly_ext.get("/api/v1/wallet/") @api_check_wallet_key("invoice") async def api_wallet_retrieve(wallet_id): wallet = await get_watch_wallet(wallet_id) if not wallet: - return jsonify({"message": "wallet does not exist"}), HTTPStatus.NOT_FOUND + return {"message": "wallet does not exist"}, HTTPStatus.NOT_FOUND - return jsonify(wallet._asdict()), HTTPStatus.OK + return wallet._asdict(), HTTPStatus.OK -@watchonly_ext.route("/api/v1/wallet", methods=["POST"]) +@watchonly_ext.post("/api/v1/wallet") @api_check_wallet_key("admin") -@api_validate_post_request( - schema={ - "masterpub": {"type": "string", "empty": False, "required": True}, - "title": {"type": "string", "empty": False, "required": True}, - } -) -async def api_wallet_create_or_update(wallet_id=None): +async def api_wallet_create_or_update(masterPub: str, Title: str, wallet_id=None): try: wallet = await create_watch_wallet( - user=g.wallet.user, masterpub=g.data["masterpub"], title=g.data["title"] + user=g.wallet.user, masterpub=masterPub, title=Title ) except Exception as e: - return jsonify({"message": str(e)}), HTTPStatus.BAD_REQUEST + return {"message": str(e)}, HTTPStatus.BAD_REQUEST mempool = await get_mempool(g.wallet.user) if not mempool: create_mempool(user=g.wallet.user) - return jsonify(wallet._asdict()), HTTPStatus.CREATED + return wallet._asdict(), HTTPStatus.CREATED -@watchonly_ext.route("/api/v1/wallet/", methods=["DELETE"]) +@watchonly_ext.delete("/api/v1/wallet/") @api_check_wallet_key("admin") async def api_wallet_delete(wallet_id): wallet = await get_watch_wallet(wallet_id) if not wallet: - return jsonify({"message": "Wallet link does not exist."}), HTTPStatus.NOT_FOUND + return {"message": "Wallet link does not exist."}, HTTPStatus.NOT_FOUND await delete_watch_wallet(wallet_id) - return jsonify({"deleted": "true"}), HTTPStatus.NO_CONTENT + return {"deleted": "true"}, HTTPStatus.NO_CONTENT #############################ADDRESSES########################## -@watchonly_ext.route("/api/v1/address/", methods=["GET"]) +@watchonly_ext.get("/api/v1/address/") @api_check_wallet_key("invoice") async def api_fresh_address(wallet_id): await get_fresh_address(wallet_id) addresses = await get_addresses(wallet_id) - return jsonify([address._asdict() for address in addresses]), HTTPStatus.OK + return [address._asdict() for address in addresses], HTTPStatus.OK -@watchonly_ext.route("/api/v1/addresses/", methods=["GET"]) +@watchonly_ext.get("/api/v1/addresses/") @api_check_wallet_key("invoice") async def api_get_addresses(wallet_id): wallet = await get_watch_wallet(wallet_id) if not wallet: - return jsonify({"message": "wallet does not exist"}), HTTPStatus.NOT_FOUND + return {"message": "wallet does not exist"}, HTTPStatus.NOT_FOUND addresses = await get_addresses(wallet_id) @@ -109,28 +103,23 @@ async def api_get_addresses(wallet_id): await get_fresh_address(wallet_id) addresses = await get_addresses(wallet_id) - return jsonify([address._asdict() for address in addresses]), HTTPStatus.OK + return [address._asdict() for address in addresses], HTTPStatus.OK #############################MEMPOOL########################## -@watchonly_ext.route("/api/v1/mempool", methods=["PUT"]) +@watchonly_ext.put("/api/v1/mempool") @api_check_wallet_key("admin") -@api_validate_post_request( - schema={ - "endpoint": {"type": "string", "empty": False, "required": True}, - } -) -async def api_update_mempool(): - mempool = await update_mempool(user=g.wallet.user, **g.data) - return jsonify(mempool._asdict()), HTTPStatus.OK +async def api_update_mempool(endpoint: str): + mempool = await update_mempool(user=g.wallet.user, **endpoint) + return mempool._asdict(), HTTPStatus.OK -@watchonly_ext.route("/api/v1/mempool", methods=["GET"]) +@watchonly_ext.get("/api/v1/mempool") @api_check_wallet_key("admin") async def api_get_mempool(): mempool = await get_mempool(g.wallet.user) if not mempool: mempool = await create_mempool(user=g.wallet.user) - return jsonify(mempool._asdict()), HTTPStatus.OK + return mempool._asdict(), HTTPStatus.OK diff --git a/lnbits/extensions/withdraw/views.py b/lnbits/extensions/withdraw/views.py index 28f25756f..1f1f6e6bb 100644 --- a/lnbits/extensions/withdraw/views.py +++ b/lnbits/extensions/withdraw/views.py @@ -8,14 +8,14 @@ from . import withdraw_ext from .crud import get_withdraw_link, chunks -@withdraw_ext.route("/") +@withdraw_ext.get("/") @validate_uuids(["usr"], required=True) @check_user_exists() async def index(): return await render_template("withdraw/index.html", user=g.user) -@withdraw_ext.route("/") +@withdraw_ext.get("/") async def display(link_id): link = await get_withdraw_link(link_id, 0) or abort( HTTPStatus.NOT_FOUND, "Withdraw link does not exist." @@ -23,7 +23,7 @@ async def display(link_id): return await render_template("withdraw/display.html", link=link, unique=True) -@withdraw_ext.route("/img/") +@withdraw_ext.get("/img/") async def img(link_id): link = await get_withdraw_link(link_id, 0) or abort( HTTPStatus.NOT_FOUND, "Withdraw link does not exist." @@ -43,7 +43,7 @@ async def img(link_id): ) -@withdraw_ext.route("/print/") +@withdraw_ext.get("/print/") async def print_qr(link_id): link = await get_withdraw_link(link_id) or abort( HTTPStatus.NOT_FOUND, "Withdraw link does not exist." From c4b37c65089399a9d6ea891d314397455c103b59 Mon Sep 17 00:00:00 2001 From: Ben Arc Date: Fri, 20 Aug 2021 21:31:01 +0100 Subject: [PATCH 12/12] Converted some core stuff --- lnbits/core/models.py | 10 +- lnbits/core/views/api.py | 190 ++++++++++++-------------------- lnbits/core/views/generic.py | 42 +++---- lnbits/core/views/public_api.py | 14 +-- 4 files changed, 99 insertions(+), 157 deletions(-) diff --git a/lnbits/core/models.py b/lnbits/core/models.py index 29d6f7d12..8b6ec6ade 100644 --- a/lnbits/core/models.py +++ b/lnbits/core/models.py @@ -6,11 +6,11 @@ from ecdsa import SECP256k1, SigningKey # type: ignore from lnurl import encode as lnurl_encode # type: ignore from typing import List, NamedTuple, Optional, Dict from sqlite3 import Row - +from pydantic import BaseModel from lnbits.settings import WALLET -class User(NamedTuple): +class User(BaseModel): id: str email: str extensions: List[str] = [] @@ -26,7 +26,7 @@ class User(NamedTuple): return w[0] if w else None -class Wallet(NamedTuple): +class Wallet(BaseModel): id: str name: str user: str @@ -73,7 +73,7 @@ class Wallet(NamedTuple): return await get_wallet_payment(self.id, payment_hash) -class Payment(NamedTuple): +class Payment(BaseModel): checking_id: str pending: bool amount: int @@ -161,7 +161,7 @@ class Payment(NamedTuple): await delete_payment(self.checking_id) -class BalanceCheck(NamedTuple): +class BalanceCheck(BaseModel): wallet: str service: str url: str diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py index 2a905bcf2..debdfa282 100644 --- a/lnbits/core/views/api.py +++ b/lnbits/core/views/api.py @@ -67,45 +67,30 @@ async def api_payments(): HTTPStatus.OK, ) +class CreateInvoiceData(BaseModel): + amount: int = Query(None, ge=1) + memo: str = None + unit: Optional[str] = None + description_hash: str = None + lnurl_callback: Optional[str] = None + lnurl_balance_check: Optional[str] = None + extra: Optional[dict] = None + webhook: Optional[str] = None @api_check_wallet_key("invoice") -@api_validate_post_request( - schema={ - "amount": {"type": "number", "min": 0.001, "required": True}, - "memo": { - "type": "string", - "empty": False, - "required": True, - "excludes": "description_hash", - }, - "unit": {"type": "string", "empty": False, "required": False}, - "description_hash": { - "type": "string", - "empty": False, - "required": True, - "excludes": "memo", - }, - "lnurl_callback": {"type": "string", "nullable": True, "required": False}, - "lnurl_balance_check": {"type": "string", "required": False}, - "extra": {"type": "dict", "nullable": True, "required": False}, - "webhook": {"type": "string", "empty": False, "required": False}, - } -) # async def api_payments_create_invoice(amount: List[str] = Query([type: str = Query(None)])): - - -async def api_payments_create_invoice(memo: Union[None, constr(min_length=1)], amount: int): - if "description_hash" in g.data: - description_hash = unhexlify(g.data["description_hash"]) +async def api_payments_create_invoice(data: CreateInvoiceData): + if "description_hash" in data: + description_hash = unhexlify(data.description_hash) memo = "" else: description_hash = b"" - memo = g.data["memo"] + memo = data.memo - if g.data.get("unit") or "sat" == "sat": - amount = g.data["amount"] + if data.unit or "sat" == "sat": + amount = data.amount else: - price_in_sats = await fiat_amount_as_satoshis(g.data["amount"], g.data["unit"]) + price_in_sats = await fiat_amount_as_satoshis(data.amount, data.unit) amount = price_in_sats async with db.connect() as conn: @@ -115,31 +100,31 @@ async def api_payments_create_invoice(memo: Union[None, constr(min_length=1)], a amount=amount, memo=memo, description_hash=description_hash, - extra=g.data.get("extra"), - webhook=g.data.get("webhook"), + extra=data.extra, + webhook=data.webhook, conn=conn, ) except InvoiceFailure as e: - return jsonable_encoder({"message": str(e)}), 520 + return {"message": str(e)}, 520 except Exception as exc: raise exc invoice = bolt11.decode(payment_request) lnurl_response: Union[None, bool, str] = None - if g.data.get("lnurl_callback"): + if data.lnurl_callback: if "lnurl_balance_check" in g.data: - save_balance_check(g.wallet.id, g.data["lnurl_balance_check"]) + save_balance_check(g.wallet.id, data.lnurl_balance_check) async with httpx.AsyncClient() as client: try: r = await client.get( - g.data["lnurl_callback"], + data.lnurl_callback, params={ "pr": payment_request, "balanceNotify": url_for( "core.lnurl_balance_notify", - service=urlparse(g.data["lnurl_callback"]).netloc, + service=urlparse(data.lnurl_callback).netloc, wal=g.wallet.id, _external=True, ), @@ -158,15 +143,13 @@ async def api_payments_create_invoice(memo: Union[None, constr(min_length=1)], a lnurl_response = False return ( - jsonable_encoder( { "payment_hash": invoice.payment_hash, "payment_request": payment_request, # maintain backwards compatibility with API clients: "checking_id": invoice.payment_hash, "lnurl_response": lnurl_response, - } - ), + }, HTTPStatus.CREATED, ) @@ -181,97 +164,76 @@ async def api_payments_pay_invoice( payment_request=bolt11, ) except ValueError as e: - return jsonable_encoder({"message": str(e)}), HTTPStatus.BAD_REQUEST + return {"message": str(e)}, HTTPStatus.BAD_REQUEST except PermissionError as e: - return jsonable_encoder({"message": str(e)}), HTTPStatus.FORBIDDEN + return {"message": str(e)}, HTTPStatus.FORBIDDEN except PaymentFailure as e: - return jsonable_encoder({"message": str(e)}), 520 + return {"message": str(e)}, 520 except Exception as exc: raise exc return ( - jsonable_encoder( { "payment_hash": payment_hash, # maintain backwards compatibility with API clients: "checking_id": payment_hash, - } - ), + }, HTTPStatus.CREATED, ) -@core_app.route("/api/v1/payments", methods=["POST"]) -@api_validate_post_request(schema={"out": {"type": "boolean", "required": True}}) -async def api_payments_create(): - if g.data["out"] is True: +@core_app.post("/api/v1/payments") +async def api_payments_create(out: bool = True): + if out is True: return await api_payments_pay_invoice() return await api_payments_create_invoice() +class CreateLNURLData(BaseModel): + description_hash: str + callback: str + amount: int + comment: Optional[str] = None + description: Optional[str] = None -@core_app.route("/api/v1/payments/lnurl", methods=["POST"]) +@core_app.post("/api/v1/payments/lnurl") @api_check_wallet_key("admin") -@api_validate_post_request( - schema={ - "description_hash": {"type": "string", "empty": False, "required": True}, - "callback": {"type": "string", "empty": False, "required": True}, - "amount": {"type": "number", "empty": False, "required": True}, - "comment": { - "type": "string", - "nullable": True, - "empty": True, - "required": False, - }, - "description": { - "type": "string", - "nullable": True, - "empty": True, - "required": False, - }, - } -) -async def api_payments_pay_lnurl(): - domain = urlparse(g.data["callback"]).netloc +async def api_payments_pay_lnurl(data: CreateLNURLData): + domain = urlparse(data.callback).netloc async with httpx.AsyncClient() as client: try: r = await client.get( - g.data["callback"], - params={"amount": g.data["amount"], "comment": g.data["comment"]}, + data.callback, + params={"amount": data.amount, "comment": data.comment}, timeout=40, ) if r.is_error: raise httpx.ConnectError except (httpx.ConnectError, httpx.RequestError): return ( - jsonify({"message": f"Failed to connect to {domain}."}), + {"message": f"Failed to connect to {domain}."}, HTTPStatus.BAD_REQUEST, ) params = json.loads(r.text) if params.get("status") == "ERROR": - return ( - jsonify({"message": f"{domain} said: '{params.get('reason', '')}'"}), + return ({"message": f"{domain} said: '{params.get('reason', '')}'"}, HTTPStatus.BAD_REQUEST, ) invoice = bolt11.decode(params["pr"]) - if invoice.amount_msat != g.data["amount"]: + if invoice.amount_msat != data.amount: return ( - jsonify( { "message": f"{domain} returned an invalid invoice. Expected {g.data['amount']} msat, got {invoice.amount_msat}." - } - ), + }, HTTPStatus.BAD_REQUEST, ) if invoice.description_hash != g.data["description_hash"]: return ( - jsonify( { "message": f"{domain} returned an invalid invoice. Expected description_hash == {g.data['description_hash']}, got {invoice.description_hash}." - } - ), + }, HTTPStatus.BAD_REQUEST, ) @@ -279,51 +241,49 @@ async def api_payments_pay_lnurl(): if params.get("successAction"): extra["success_action"] = params["successAction"] - if g.data["comment"]: - extra["comment"] = g.data["comment"] + if data.comment: + extra["comment"] = data.comment payment_hash = await pay_invoice( wallet_id=g.wallet.id, payment_request=params["pr"], - description=g.data.get("description", ""), + description=data.description, extra=extra, ) return ( - jsonify( { "success_action": params.get("successAction"), "payment_hash": payment_hash, # maintain backwards compatibility with API clients: "checking_id": payment_hash, - } - ), + }, HTTPStatus.CREATED, ) -@core_app.route("/api/v1/payments/", methods=["GET"]) +@core_app.get("/api/v1/payments/") @api_check_wallet_key("invoice") async def api_payment(payment_hash): payment = await g.wallet.get_payment(payment_hash) if not payment: - return jsonify({"message": "Payment does not exist."}), HTTPStatus.NOT_FOUND + return {"message": "Payment does not exist."}, HTTPStatus.NOT_FOUND elif not payment.pending: - return jsonify({"paid": True, "preimage": payment.preimage}), HTTPStatus.OK + return {"paid": True, "preimage": payment.preimage}, HTTPStatus.OK try: await payment.check_pending() except Exception: - return jsonify({"paid": False}), HTTPStatus.OK + return {"paid": False}, HTTPStatus.OK return ( - jsonify({"paid": not payment.pending, "preimage": payment.preimage}), + {"paid": not payment.pending, "preimage": payment.preimage}, HTTPStatus.OK, ) -@core_app.route("/api/v1/payments/sse", methods=["GET"]) +@core_app.get("/api/v1/payments/sse") @api_check_wallet_key("invoice", accept_querystring=True) async def api_payments_sse(): this_wallet_id = g.wallet.id @@ -376,7 +336,7 @@ async def api_payments_sse(): return response -@core_app.route("/api/v1/lnurlscan/", methods=["GET"]) +@core_app.get("/api/v1/lnurlscan/") @api_check_wallet_key("invoice") async def api_lnurlscan(code: str): try: @@ -395,7 +355,7 @@ async def api_lnurlscan(code: str): ) # will proceed with these values else: - return jsonify({"message": "invalid lnurl"}), HTTPStatus.BAD_REQUEST + return {"message": "invalid lnurl"}, HTTPStatus.BAD_REQUEST # params is what will be returned to the client params: Dict = {"domain": domain} @@ -411,7 +371,7 @@ async def api_lnurlscan(code: str): r = await client.get(url, timeout=5) if r.is_error: return ( - jsonify({"domain": domain, "message": "failed to get parameters"}), + {"domain": domain, "message": "failed to get parameters"}, HTTPStatus.SERVICE_UNAVAILABLE, ) @@ -419,12 +379,10 @@ async def api_lnurlscan(code: str): data = json.loads(r.text) except json.decoder.JSONDecodeError: return ( - jsonify( { "domain": domain, "message": f"got invalid response '{r.text[:200]}'", - } - ), + }, HTTPStatus.SERVICE_UNAVAILABLE, ) @@ -432,9 +390,7 @@ async def api_lnurlscan(code: str): tag = data["tag"] if tag == "channelRequest": return ( - jsonify( - {"domain": domain, "kind": "channel", "message": "unsupported"} - ), + {"domain": domain, "kind": "channel", "message": "unsupported"}, HTTPStatus.BAD_REQUEST, ) @@ -481,32 +437,24 @@ async def api_lnurlscan(code: str): params.update(commentAllowed=data.get("commentAllowed", 0)) except KeyError as exc: return ( - jsonify( { "domain": domain, "message": f"lnurl JSON response invalid: {exc}", - } - ), + }, HTTPStatus.SERVICE_UNAVAILABLE, ) - - return jsonify(params) + return params -@core_app.route("/api/v1/lnurlauth", methods=["POST"]) +@core_app.post("/api/v1/lnurlauth", methods=["POST"]) @api_check_wallet_key("admin") -@api_validate_post_request( - schema={ - "callback": {"type": "string", "required": True}, - } -) -async def api_perform_lnurlauth(): - err = await perform_lnurlauth(g.data["callback"]) +async def api_perform_lnurlauth(callback: str): + err = await perform_lnurlauth(callback) if err: - return jsonify({"reason": err.reason}), HTTPStatus.SERVICE_UNAVAILABLE + return {"reason": err.reason}, HTTPStatus.SERVICE_UNAVAILABLE return "", HTTPStatus.OK @core_app.route("/api/v1/currencies", methods=["GET"]) async def api_list_currencies_available(): - return jsonify(list(currencies.keys())) + return list(currencies.keys()) diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py index 0d1b78a99..12446c5b1 100644 --- a/lnbits/core/views/generic.py +++ b/lnbits/core/views/generic.py @@ -4,7 +4,6 @@ from quart import ( g, current_app, abort, - jsonify, request, redirect, render_template, @@ -28,21 +27,21 @@ from ..crud import ( from ..services import redeem_lnurl_withdraw, pay_invoice -@core_app.route("/favicon.ico") +@core_app.get("/favicon.ico") async def favicon(): return await send_from_directory( path.join(core_app.root_path, "static"), "favicon.ico" ) -@core_app.route("/") +@core_app.get("/") async def home(): return await render_template( "core/index.html", lnurl=request.args.get("lightning", None) ) -@core_app.route("/extensions") +@core_app.get("/extensions") @validate_uuids(["usr"], required=True) @check_user_exists() async def extensions(): @@ -66,7 +65,7 @@ async def extensions(): return await render_template("core/extensions.html", user=await get_user(g.user.id)) -@core_app.route("/wallet") +@core_app.get("/wallet") @validate_uuids(["usr", "wal"]) async def wallet(): user_id = request.args.get("usr", type=str) @@ -108,19 +107,18 @@ async def wallet(): ) -@core_app.route("/withdraw") +@core_app.get("/withdraw") @validate_uuids(["usr", "wal"], required=True) async def lnurl_full_withdraw(): user = await get_user(request.args.get("usr")) if not user: - return jsonify({"status": "ERROR", "reason": "User does not exist."}) + return {"status": "ERROR", "reason": "User does not exist."} wallet = user.get_wallet(request.args.get("wal")) if not wallet: - return jsonify({"status": "ERROR", "reason": "Wallet does not exist."}) + return{"status": "ERROR", "reason": "Wallet does not exist."} - return jsonify( - { + return { "tag": "withdrawRequest", "callback": url_for( "core.lnurl_full_withdraw_callback", @@ -136,19 +134,18 @@ async def lnurl_full_withdraw(): "core.lnurl_full_withdraw", usr=user.id, wal=wallet.id, _external=True ), } - ) -@core_app.route("/withdraw/cb") +@core_app.get("/withdraw/cb") @validate_uuids(["usr", "wal"], required=True) async def lnurl_full_withdraw_callback(): user = await get_user(request.args.get("usr")) if not user: - return jsonify({"status": "ERROR", "reason": "User does not exist."}) + return {"status": "ERROR", "reason": "User does not exist."} wallet = user.get_wallet(request.args.get("wal")) if not wallet: - return jsonify({"status": "ERROR", "reason": "Wallet does not exist."}) + return {"status": "ERROR", "reason": "Wallet does not exist."} pr = request.args.get("pr") @@ -164,10 +161,10 @@ async def lnurl_full_withdraw_callback(): if balance_notify: await save_balance_notify(wallet.id, balance_notify) - return jsonify({"status": "OK"}) + return {"status": "OK"} -@core_app.route("/deletewallet") +@core_app.get("/deletewallet") @validate_uuids(["usr", "wal"], required=True) @check_user_exists() async def deletewallet(): @@ -186,7 +183,7 @@ async def deletewallet(): return redirect(url_for("core.home")) -@core_app.route("/withdraw/notify/") +@core_app.get("/withdraw/notify/") @validate_uuids(["wal"], required=True) async def lnurl_balance_notify(service: str): bc = await get_balance_check(request.args.get("wal"), service) @@ -194,7 +191,7 @@ async def lnurl_balance_notify(service: str): redeem_lnurl_withdraw(bc.wallet, bc.url) -@core_app.route("/lnurlwallet") +@core_app.get("/lnurlwallet") async def lnurlwallet(): async with db.connect() as conn: account = await create_account(conn=conn) @@ -213,14 +210,13 @@ async def lnurlwallet(): return redirect(url_for("core.wallet", usr=user.id, wal=wallet.id)) -@core_app.route("/manifest/.webmanifest") +@core_app.get("/manifest/.webmanifest") async def manifest(usr: str): user = await get_user(usr) if not user: return "", HTTPStatus.NOT_FOUND - return jsonify( - { + return { "short_name": "LNbits", "name": "LNbits Wallet", "icons": [ @@ -244,6 +240,4 @@ async def manifest(usr: str): "url": "/wallet?usr=" + usr + "&wal=" + wallet.id, } for wallet in user.wallets - ], - } - ) + ],} diff --git a/lnbits/core/views/public_api.py b/lnbits/core/views/public_api.py index 167352acd..d404e293e 100644 --- a/lnbits/core/views/public_api.py +++ b/lnbits/core/views/public_api.py @@ -10,22 +10,22 @@ from ..crud import get_standalone_payment from ..tasks import api_invoice_listeners -@core_app.route("/public/v1/payment/", methods=["GET"]) +@core_app.get("/public/v1/payment/") async def api_public_payment_longpolling(payment_hash): payment = await get_standalone_payment(payment_hash) if not payment: - return jsonify({"message": "Payment does not exist."}), HTTPStatus.NOT_FOUND + return {"message": "Payment does not exist."}, HTTPStatus.NOT_FOUND elif not payment.pending: - return jsonify({"status": "paid"}), HTTPStatus.OK + return {"status": "paid"}, HTTPStatus.OK try: invoice = bolt11.decode(payment.bolt11) expiration = datetime.datetime.fromtimestamp(invoice.date + invoice.expiry) if expiration < datetime.datetime.now(): - return jsonify({"status": "expired"}), HTTPStatus.OK + return {"status": "expired"}, HTTPStatus.OK except: - return jsonify({"message": "Invalid bolt11 invoice."}), HTTPStatus.BAD_REQUEST + return {"message": "Invalid bolt11 invoice."}, HTTPStatus.BAD_REQUEST send_payment, receive_payment = trio.open_memory_channel(0) @@ -38,7 +38,7 @@ async def api_public_payment_longpolling(payment_hash): async for payment in receive_payment: if payment.payment_hash == payment_hash: nonlocal response - response = (jsonify({"status": "paid"}), HTTPStatus.OK) + response = ({"status": "paid"}, HTTPStatus.OK) cancel_scope.cancel() async def timeouter(cancel_scope): @@ -52,4 +52,4 @@ async def api_public_payment_longpolling(payment_hash): if response: return response else: - return jsonify({"message": "timeout"}), HTTPStatus.REQUEST_TIMEOUT + return {"message": "timeout"}, HTTPStatus.REQUEST_TIMEOUT