fix: lnurlp, withdraw

This commit is contained in:
Tiago vasconcelos
2021-10-05 09:19:21 +01:00
parent 6edac8ae8d
commit 81826f3c13
14 changed files with 108 additions and 85 deletions

View File

@@ -27,4 +27,3 @@ print(
- service fee: {SERVICE_FEE}
"""
)

View File

@@ -1,34 +1,48 @@
import asyncio
from fastapi import APIRouter, FastAPI
from fastapi.staticfiles import StaticFiles
from starlette.routing import Mount
from lnbits.db import Database
from lnbits.helpers import template_renderer
from lnbits.tasks import catch_everything_and_restart
db = Database("ext_lnurlp")
lnurlp_static_files = [
{
"path": "/lnurlp/static",
"app": StaticFiles(directory="lnbits/extensions/lnurlp/static"),
"name": "lnurlp_static",
}
]
lnurlp_ext: APIRouter = APIRouter(
prefix="/lnurlp",
static_folder="static",
tags=["lnurlp"]
# "lnurlp", __name__, static_folder="static", template_folder="templates"
)
def lnurlp_renderer():
return template_renderer(
[
"lnbits/extensions/lnticket/templates",
"lnbits/extensions/lnurlp/templates",
]
)
from .views_api import * # noqa
from .views import * # noqa
from .tasks import wait_for_paid_invoices
def lnurlp_start():
loop = asyncio.get_event_loop()
loop.create_task(catch_everything_and_restart(wait_for_paid_invoices))
@lnurlp_ext.on_event("startup")
def _do_it():
register_listeners()
# from .lnurl import * # noqa
# from .tasks import register_listeners
# from lnbits.tasks import record_async

View File

@@ -2,25 +2,17 @@ from typing import List, Optional, Union
from lnbits.db import SQLITE
from . import db
from .models import PayLink
from .models import PayLink, CreatePayLinkData
async def create_pay_link(
*,
wallet_id: str,
description: str,
min: int,
max: int,
comment_chars: int = 0,
currency: Optional[str] = None,
webhook_url: Optional[str] = None,
success_text: Optional[str] = None,
success_url: Optional[str] = None,
data: CreatePayLinkData,
wallet_id: str
) -> PayLink:
returning = "" if db.type == SQLITE else "RETURNING ID"
method = db.execute if db.type == SQLITE else db.fetchone
print("CPL", wallet_id, data)
result = await (method)(
f"""
INSERT INTO lnurlp.pay_links (
@@ -41,14 +33,14 @@ async def create_pay_link(
""",
(
wallet_id,
description,
min,
max,
webhook_url,
success_text,
success_url,
comment_chars,
currency,
data.description,
data.min,
data.max,
data.webhook_url,
data.success_text,
data.success_url,
data.comment_chars,
data.currency,
),
)
if db.type == SQLITE:

View File

@@ -11,7 +11,7 @@ from . import lnurlp_ext
from .crud import increment_pay_link
@lnurlp_ext.get("/api/v1/lnurl/{link_id}", status_code=HTTPStatus.OK)
@lnurlp_ext.get("/api/v1/lnurl/{link_id}", status_code=HTTPStatus.OK, name="lnurlp.api_lnurl_response")
async def api_lnurl_response(request: Request, link_id):
link = await increment_pay_link(link_id, served_meta=1)
if not link:

View File

@@ -1,12 +1,23 @@
import json
from urllib.parse import urlparse, urlunparse, parse_qs, urlencode, ParseResult
from quart import url_for
from starlette.requests import Request
from fastapi.param_functions import Query
from typing import Optional, Dict
from lnbits.lnurl import encode as lnurl_encode # type: ignore
from lnurl.types import LnurlPayMetadata # type: ignore
from sqlite3 import Row
from pydantic import BaseModel
class CreatePayLinkData(BaseModel):
description: str
min: int = Query(0.01, ge=0.01)
max: int = Query(0.01, ge=0.01)
currency: str = Query(None)
comment_chars: int = Query(0, ge=0, lt=800)
webhook_url: str = Query(None)
success_text: str = Query(None)
success_url: str = Query(None)
class PayLink(BaseModel):
id: int
wallet: str
@@ -14,10 +25,10 @@ class PayLink(BaseModel):
min: int
served_meta: int
served_pr: int
webhook_url: str
success_text: str
success_url: str
currency: str
webhook_url: Optional[str]
success_text: Optional[str]
success_url: Optional[str]
currency: Optional[str]
comment_chars: int
max: int
@@ -28,7 +39,8 @@ class PayLink(BaseModel):
@property
def lnurl(self) -> str:
url = url_for("lnurlp.api_lnurl_response", link_id=self.id, _external=True)
r = Request
url = r.url_for("lnurlp.api_lnurl_response", link_id=self.id, _external=True)
return lnurl_encode(url)
@property

View File

@@ -1,4 +1,4 @@
import trio
import asyncio
import json
import httpx
@@ -9,17 +9,14 @@ from lnbits.tasks import register_invoice_listener
from .crud import get_pay_link
async def register_listeners():
invoice_paid_chan_send, invoice_paid_chan_recv = trio.open_memory_channel(2)
register_invoice_listener(invoice_paid_chan_send)
await wait_for_paid_invoices(invoice_paid_chan_recv)
async def wait_for_paid_invoices():
invoice_queue = asyncio.Queue()
register_invoice_listener(invoice_queue)
async def wait_for_paid_invoices(invoice_paid_chan: trio.MemoryReceiveChannel):
async for payment in invoice_paid_chan:
while True:
payment = await invoice_queue.get()
await on_invoice_paid(payment)
async def on_invoice_paid(payment: Payment) -> None:
if "lnurlp" != payment.extra.get("tag"):
# not an lnurlp invoice

View File

@@ -18,7 +18,7 @@
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ request.url_root }}api/v1/links -H "X-Api-Key: {{
g.user.wallets[0].inkey }}"
user.wallets[0].inkey }}"
</code>
</q-card-section>
</q-card>
@@ -27,7 +27,8 @@
<q-card>
<q-card-section>
<code
><span class="text-blue">GET</span> /lnurlp/api/v1/links/&lt;pay_id&gt;</code
><span class="text-blue">GET</span>
/lnurlp/api/v1/links/&lt;pay_id&gt;</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;invoice_key&gt;}</code><br />
@@ -39,7 +40,7 @@
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ request.url_root }}api/v1/links/&lt;pay_id&gt; -H
"X-Api-Key: {{ g.user.wallets[0].inkey }}"
"X-Api-Key: {{ user.wallets[0].inkey }}"
</code>
</q-card-section>
</q-card>
@@ -56,7 +57,11 @@
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;admin_key&gt;}</code><br />
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
<code>{"description": &lt;string&gt; "amount": &lt;integer&gt; "max": &lt;integer&gt; "min": &lt;integer&gt; "comment_chars": &lt;integer&gt;}</code>
<code
>{"description": &lt;string&gt; "amount": &lt;integer&gt; "max":
&lt;integer&gt; "min": &lt;integer&gt; "comment_chars":
&lt;integer&gt;}</code
>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 201 CREATED (application/json)
</h5>
@@ -64,8 +69,10 @@
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X POST {{ request.url_root }}api/v1/links -d '{"description":
&lt;string&gt;, "amount": &lt;integer&gt;, "max": &lt;integer&gt;, "min": &lt;integer&gt;, "comment_chars": &lt;integer&gt;}' -H "Content-type:
application/json" -H "X-Api-Key: {{ g.user.wallets[0].adminkey }}"
&lt;string&gt;, "amount": &lt;integer&gt;, "max": &lt;integer&gt;,
"min": &lt;integer&gt;, "comment_chars": &lt;integer&gt;}' -H
"Content-type: application/json" -H "X-Api-Key: {{
user.wallets[0].adminkey }}"
</code>
</q-card-section>
</q-card>
@@ -95,7 +102,7 @@
>curl -X PUT {{ request.url_root }}api/v1/links/&lt;pay_id&gt; -d
'{"description": &lt;string&gt;, "amount": &lt;integer&gt;}' -H
"Content-type: application/json" -H "X-Api-Key: {{
g.user.wallets[0].adminkey }}"
user.wallets[0].adminkey }}"
</code>
</q-card-section>
</q-card>
@@ -120,7 +127,7 @@
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X DELETE {{ request.url_root }}api/v1/links/&lt;pay_id&gt; -H
"X-Api-Key: {{ g.user.wallets[0].adminkey }}"
"X-Api-Key: {{ user.wallets[0].adminkey }}"
</code>
</q-card-section>
</q-card>

View File

@@ -1,6 +1,6 @@
from http import HTTPStatus
from lnbits.decorators import check_user_exists, validate_uuids
from lnbits.decorators import check_user_exists
from . import lnurlp_ext, lnurlp_renderer
from .crud import get_pay_link
@@ -15,7 +15,7 @@ from lnbits.core.models import User
templates = Jinja2Templates(directory="templates")
@lnurlp_ext.get("/", response_class=HTMLResponse)
@validate_uuids(["usr"], required=True)
# @validate_uuids(["usr"], required=True)
# @check_user_exists()
async def index(request: Request, user: User = Depends(check_user_exists)):
return lnurlp_renderer().TemplateResponse("lnurlp/index.html", {"request": request, "user": user.dict()})

View File

@@ -10,8 +10,9 @@ from starlette.requests import Request
from starlette.responses import HTMLResponse, JSONResponse # type: ignore
from lnbits.core.crud import get_user
from lnbits.decorators import api_check_wallet_key, api_validate_post_request
from lnbits.decorators import WalletTypeInfo, get_key_type
from lnbits.utils.exchange_rates import currencies, get_fiat_rate_satoshis
from .models import CreatePayLinkData
from . import lnurlp_ext
from .crud import (
@@ -75,20 +76,11 @@ async def api_link_retrieve(link_id, wallet: WalletTypeInfo = Depends(get_key_ty
return {**link._asdict(), **{"lnurl": link.lnurl}}
class CreateData(BaseModel):
description: str
min: int = Query(0.01, ge=0.01)
max: int = Query(0.01, ge=0.01)
currency: Optional[str]
comment_chars: int = Query(0, ge=0, lt=800)
webhook_url: Optional[str]
success_text: Optional[str]
success_url: Optional[str]
@lnurlp_ext.post("/api/v1/links", status_code=HTTPStatus.CREATED)
@lnurlp_ext.put("/api/v1/links/{link_id}", status_code=HTTPStatus.OK)
# @api_check_wallet_key("invoice")
async def api_link_create_or_update(data: CreateData, link_id=None, wallet: WalletTypeInfo = Depends(get_key_type)):
async def api_link_create_or_update(data: CreatePayLinkData, link_id=None, wallet: WalletTypeInfo = Depends(get_key_type)):
if data.min > data.max:
raise HTTPException(
detail="Min is greater than max.",
@@ -128,18 +120,18 @@ async def api_link_create_or_update(data: CreateData, link_id=None, wallet: Wall
# HTTPStatus.NOT_FOUND,
# )
if link.wallet != g.wallet.id:
if link.wallet != wallet.wallet.id:
raise HTTPException(
detail="Not your pay link.",
status_code=HTTPStatus.FORBIDDEN
)
# return {"message": "Not your pay link."}, HTTPStatus.FORBIDDEN
link = await update_pay_link(link_id, **data)
link = await update_pay_link(link_id, data)
else:
link = await create_pay_link(wallet_id=wallet.wallet.id, **data)
return {**link._asdict(), **{"lnurl": link.lnurl}}
link = await create_pay_link(data, wallet_id=wallet.wallet.id)
print("LINK", link)
return {**link.dict(), "lnurl": link.lnurl}
@lnurlp_ext.delete("/api/v1/links/{link_id}")

View File

@@ -1,13 +1,23 @@
from fastapi import APIRouter
from fastapi.staticfiles import StaticFiles
from lnbits.db import Database
from lnbits.helpers import template_renderer
db = Database("ext_withdraw")
withdraw_static_files = [
{
"path": "/withdraw/static",
"app": StaticFiles(directory="lnbits/extensions/withdraw/static"),
"name": "withdraw_static",
}
]
withdraw_ext: APIRouter = APIRouter(
prefix="/withdraw",
static_folder="static"
tags=["withdraw"],
# "withdraw", __name__, static_folder="static", template_folder="templates"
)
@@ -23,6 +33,7 @@ from .views_api import * # noqa
from .views import * # noqa
from .lnurl import * # noqa
@withdraw_ext.on_event("startup")
def _do_it():
register_listeners()
# @withdraw_ext.on_event("startup")
# def _do_it():
# register_listeners()

View File

@@ -1,7 +1,6 @@
import shortuuid # type: ignore
from http import HTTPStatus
from datetime import datetime
from quart import jsonify, request
from lnbits.core.services import pay_invoice

View File

@@ -1,4 +1,4 @@
from quart import url_for
from fastapi import Request
from lnurl import Lnurl, LnurlWithdrawResponse, encode as lnurl_encode # type: ignore
from sqlite3 import Row
from pydantic import BaseModel
@@ -33,19 +33,19 @@ class WithdrawLink(BaseModel):
return self.used >= self.uses
@property
def lnurl(self) -> Lnurl:
def lnurl(self, req: Request) -> Lnurl:
if self.is_unique:
usescssv = self.usescsv.split(",")
tohash = self.id + self.unique_hash + usescssv[self.number]
multihash = shortuuid.uuid(name=tohash)
url = url_for(
url = req.url_for(
"withdraw.api_lnurl_multi_response",
unique_hash=self.unique_hash,
id_unique_hash=multihash,
_external=True,
)
else:
url = url_for(
url = req.url_for(
"withdraw.api_lnurl_response",
unique_hash=self.unique_hash,
_external=True,
@@ -55,7 +55,7 @@ class WithdrawLink(BaseModel):
@property
def lnurl_response(self) -> LnurlWithdrawResponse:
url = url_for(
url = req.url_for(
"withdraw.api_lnurl_callback", unique_hash=self.unique_hash, _external=True
)
return LnurlWithdrawResponse(

View File

@@ -1,7 +1,7 @@
from http import HTTPStatus
import pyqrcode
from io import BytesIO
from lnbits.decorators import check_user_exists, validate_uuids
from lnbits.decorators import check_user_exists
from . import withdraw_ext, withdraw_renderer
from .crud import get_withdraw_link, chunks
@@ -16,7 +16,7 @@ from lnbits.core.models import User
templates = Jinja2Templates(directory="templates")
@withdraw_ext.get("/", response_class=HTMLResponse)
@validate_uuids(["usr"], required=True)
# @validate_uuids(["usr"], required=True)
# @check_user_exists()
async def index(request: Request, user: User = Depends(check_user_exists)):
return withdraw_renderer().TemplateResponse("withdraw/index.html", {"request":request,"user": user.dict()})
@@ -36,7 +36,7 @@ async def display(request: Request, link_id):
@withdraw_ext.get("/img/{link_id}", response_class=HTMLResponse)
async def img(request: Request, link_id, response: Response):
async def img(request: Request, link_id):
link = await get_withdraw_link(link_id, 0)
if not link:
raise HTTPException(

View File

@@ -9,7 +9,7 @@ from starlette.requests import Request
from starlette.responses import HTMLResponse, JSONResponse # type: ignore
from lnbits.core.crud import get_user
from lnbits.decorators import api_check_wallet_key, api_validate_post_request
from lnbits.decorators import WalletTypeInfo, get_key_type
# from fastapi import FastAPI, Query, Response
@@ -83,8 +83,8 @@ class CreateData(BaseModel):
@withdraw_ext.post("/api/v1/links", status_code=HTTPStatus.CREATED)
@withdraw_ext.put("/api/v1/links/{link_id}", status_code=HTTPStatus.OK)
@api_check_wallet_key("admin")
async def api_link_create_or_update(data: CreateData, link_id: str = None, response: Response):
# @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:
raise HTTPException(
detail="`max_withdrawable` needs to be at least `min_withdrawable`.",