feat: update to lnbits 1.0.0

This commit is contained in:
dni ⚡ 2024-09-07 21:03:57 +02:00
commit fe6cbe4e2d
No known key found for this signature in database
GPG key ID: D1F416F29AD26E87
8 changed files with 898 additions and 862 deletions

View file

@ -2,7 +2,7 @@
"name": "Pay Links", "name": "Pay Links",
"short_description": "Make reusable LNURL pay links", "short_description": "Make reusable LNURL pay links",
"tile": "/lnurlp/static/image/lnurl-pay.png", "tile": "/lnurlp/static/image/lnurl-pay.png",
"min_lnbits_version": "0.12.4", "min_lnbits_version": "1.0.0",
"contributors": [ "contributors": [
{ {
"name": "arcbtc", "name": "arcbtc",

117
crud.py
View file

@ -15,16 +15,14 @@ async def get_or_create_lnurlp_settings() -> LnurlpSettings:
return LnurlpSettings(**row) return LnurlpSettings(**row)
else: else:
settings = LnurlpSettings(nostr_private_key=PrivateKey().hex()) settings = LnurlpSettings(nostr_private_key=PrivateKey().hex())
await db.execute( await db.execute(insert_query("lnurlp.settings", settings), settings.dict())
insert_query("lnurlp.settings", settings), (*settings.dict().values(),)
)
return settings return settings
async def update_lnurlp_settings(settings: LnurlpSettings) -> LnurlpSettings: async def update_lnurlp_settings(settings: LnurlpSettings) -> LnurlpSettings:
await db.execute( await db.execute(
update_query("lnurlp.settings", settings, where=""), update_query("lnurlp.settings", settings, where=""),
(*settings.dict().values(),), settings.dict(),
) )
return settings return settings
@ -35,109 +33,76 @@ async def delete_lnurlp_settings() -> None:
async def get_pay_link_by_username(username: str) -> Optional[PayLink]: async def get_pay_link_by_username(username: str) -> Optional[PayLink]:
row = await db.fetchone( row = await db.fetchone(
"SELECT * FROM lnurlp.pay_links WHERE username = ?", (username,) "SELECT * FROM lnurlp.pay_links WHERE username = :username",
{"username": username},
) )
return PayLink.from_row(row) if row else None return PayLink(**row) if row else None
async def create_pay_link(data: CreatePayLinkData) -> PayLink: async def create_pay_link(data: CreatePayLinkData) -> PayLink:
link_id = urlsafe_short_hash()[:6] link_id = urlsafe_short_hash()[:6]
result = await db.execute( assert data.wallet, "Wallet is required"
"""
INSERT INTO lnurlp.pay_links (
id,
wallet,
description,
min,
max,
served_meta,
served_pr,
webhook_url,
webhook_headers,
webhook_body,
success_text,
success_url,
comment_chars,
currency,
fiat_base_multiplier,
username,
zaps
) link = PayLink(
VALUES (?, ?, ?, ?, ?, 0, 0, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) id=link_id,
""", wallet=data.wallet,
( description=data.description,
link_id, min=data.min,
data.wallet, max=data.max,
data.description, served_meta=0,
data.min, served_pr=0,
data.max, username=data.username,
data.webhook_url, zaps=data.zaps,
data.webhook_headers, domain=None,
data.webhook_body, webhook_url=data.webhook_url,
data.success_text, webhook_headers=data.webhook_headers,
data.success_url, webhook_body=data.webhook_body,
data.comment_chars, success_text=data.success_text,
data.currency, success_url=data.success_url,
data.fiat_base_multiplier, currency=data.currency,
data.username, comment_chars=data.comment_chars,
data.zaps, fiat_base_multiplier=data.fiat_base_multiplier,
),
) )
assert result
link = await get_pay_link(link_id) await db.execute(insert_query("lnurlp.pay_links", link), link.dict())
assert link, "Newly created link couldn't be retrieved"
return link return link
async def get_address_data(username: str) -> Optional[PayLink]: async def get_address_data(username: str) -> Optional[PayLink]:
row = await db.fetchone( row = await db.fetchone(
"SELECT * FROM lnurlp.pay_links WHERE username = ?", (username,) "SELECT * FROM lnurlp.pay_links WHERE username = :username",
{"username": username},
) )
return PayLink.from_row(row) if row else None return PayLink(**row) if row else None
async def get_pay_link(link_id: str) -> Optional[PayLink]: async def get_pay_link(link_id: str) -> Optional[PayLink]:
row = await db.fetchone("SELECT * FROM lnurlp.pay_links WHERE id = ?", (link_id,)) row = await db.fetchone(
return PayLink.from_row(row) if row else None "SELECT * FROM lnurlp.pay_links WHERE id = :id", {"id": link_id}
)
return PayLink(**row) if row else None
async def get_pay_links(wallet_ids: Union[str, List[str]]) -> List[PayLink]: async def get_pay_links(wallet_ids: Union[str, List[str]]) -> List[PayLink]:
if isinstance(wallet_ids, str): if isinstance(wallet_ids, str):
wallet_ids = [wallet_ids] wallet_ids = [wallet_ids]
q = ",".join(["?"] * len(wallet_ids)) q = ",".join([f"'{wallet_id}'" for wallet_id in wallet_ids])
rows = await db.fetchall( rows = await db.fetchall(
f""" f"SELECT * FROM lnurlp.pay_links WHERE wallet IN ({q}) ORDER BY Id"
SELECT * FROM lnurlp.pay_links WHERE wallet IN ({q})
ORDER BY Id
""",
(*wallet_ids,),
) )
return [PayLink.from_row(row) for row in rows] return [PayLink(**row) for row in rows]
async def update_pay_link(link_id: str, **kwargs) -> Optional[PayLink]: async def update_pay_link(link: PayLink) -> PayLink:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
await db.execute( await db.execute(
f"UPDATE lnurlp.pay_links SET {q} WHERE id = ?", (*kwargs.values(), link_id) update_query("lnurlp.pay_links", link),
link.dict(),
) )
row = await db.fetchone("SELECT * FROM lnurlp.pay_links WHERE id = ?", (link_id,)) return link
return PayLink.from_row(row) if row else None
async def increment_pay_link(link_id: str, **kwargs) -> Optional[PayLink]:
q = ", ".join([f"{field[0]} = {field[0]} + ?" for field in kwargs.items()])
await db.execute(
f"UPDATE lnurlp.pay_links SET {q} WHERE id = ?", (*kwargs.values(), link_id)
)
row = await db.fetchone("SELECT * FROM lnurlp.pay_links WHERE id = ?", (link_id,))
return PayLink.from_row(row) if row else None
async def delete_pay_link(link_id: str) -> None: async def delete_pay_link(link_id: str) -> None:
await db.execute("DELETE FROM lnurlp.pay_links WHERE id = ?", (link_id,)) await db.execute("DELETE FROM lnurlp.pay_links WHERE id = :id", {"id": link_id})

View file

@ -1,9 +1,7 @@
import json import json
from sqlite3 import Row
from typing import Optional from typing import Optional
from fastapi import Request from fastapi import Query, Request
from fastapi.param_functions import Query
from lnurl import encode as lnurl_encode from lnurl import encode as lnurl_encode
from lnurl.types import LnurlPayMetadata from lnurl.types import LnurlPayMetadata
from pydantic import BaseModel from pydantic import BaseModel
@ -61,14 +59,6 @@ class PayLink(BaseModel):
max: float max: float
fiat_base_multiplier: int fiat_base_multiplier: int
@classmethod
def from_row(cls, row: Row) -> "PayLink":
data = dict(row)
if data["currency"] and data["fiat_base_multiplier"]:
data["min"] /= data["fiat_base_multiplier"]
data["max"] /= data["fiat_base_multiplier"]
return cls(**data)
def lnurl(self, req: Request) -> str: def lnurl(self, req: Request) -> str:
url = req.url_for("lnurlp.api_lnurl_response", link_id=self.id) url = req.url_for("lnurlp.api_lnurl_response", link_id=self.id)
url_str = str(url) url_str = str(url)

1588
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -6,7 +6,7 @@ authors = ["Alan Bits <alan@lnbits.com>"]
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.10 | ^3.9" python = "^3.10 | ^3.9"
lnbits = "*" lnbits = {version = "*", allow-prereleases = true}
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
black = "^24.3.0" black = "^24.3.0"

View file

@ -1,7 +1,6 @@
from http import HTTPStatus from http import HTTPStatus
from fastapi import APIRouter, Depends, Request from fastapi import APIRouter, Depends, Request
from fastapi.templating import Jinja2Templates
from lnbits.core.models import User from lnbits.core.models import User
from lnbits.decorators import check_user_exists from lnbits.decorators import check_user_exists
from lnbits.helpers import template_renderer from lnbits.helpers import template_renderer
@ -17,9 +16,6 @@ def lnurlp_renderer():
return template_renderer(["lnurlp/templates"]) return template_renderer(["lnurlp/templates"])
templates = Jinja2Templates(directory="templates")
@lnurlp_generic_router.get("/", response_class=HTMLResponse) @lnurlp_generic_router.get("/", response_class=HTMLResponse)
async def index(request: Request, user: User = Depends(check_user_exists)): async def index(request: Request, user: User = Depends(check_user_exists)):
return lnurlp_renderer().TemplateResponse( return lnurlp_renderer().TemplateResponse(

View file

@ -8,7 +8,6 @@ from lnbits.core.crud import get_user, get_wallet
from lnbits.core.models import WalletTypeInfo from lnbits.core.models import WalletTypeInfo
from lnbits.decorators import ( from lnbits.decorators import (
check_admin, check_admin,
get_key_type,
require_admin_key, require_admin_key,
require_invoice_key, require_invoice_key,
) )
@ -41,13 +40,13 @@ async def api_list_currencies_available():
@lnurlp_api_router.get("/api/v1/links", status_code=HTTPStatus.OK) @lnurlp_api_router.get("/api/v1/links", status_code=HTTPStatus.OK)
async def api_links( async def api_links(
req: Request, req: Request,
wallet: WalletTypeInfo = Depends(get_key_type), key_info: WalletTypeInfo = Depends(require_invoice_key),
all_wallets: bool = Query(False), all_wallets: bool = Query(False),
): ):
wallet_ids = [wallet.wallet.id] wallet_ids = [key_info.wallet.id]
if all_wallets: if all_wallets:
user = await get_user(wallet.wallet.user) user = await get_user(key_info.wallet.user)
wallet_ids = user.wallet_ids if user else [] wallet_ids = user.wallet_ids if user else []
try: try:
@ -189,7 +188,10 @@ async def api_link_create_or_update(
if data.username and data.username != link.username: if data.username and data.username != link.username:
await check_username_exists(data.username) await check_username_exists(data.username)
link = await update_pay_link(**data.dict(), link_id=link_id) for k, v in data.dict().items():
setattr(link, k, v)
link = await update_pay_link(link)
else: else:
if data.username: if data.username:
await check_username_exists(data.username) await check_username_exists(data.username)
@ -201,7 +203,9 @@ async def api_link_create_or_update(
@lnurlp_api_router.delete("/api/v1/links/{link_id}", status_code=HTTPStatus.OK) @lnurlp_api_router.delete("/api/v1/links/{link_id}", status_code=HTTPStatus.OK)
async def api_link_delete(link_id: str, wallet: WalletTypeInfo = Depends(get_key_type)): async def api_link_delete(
link_id: str, key_info: WalletTypeInfo = Depends(require_admin_key)
):
link = await get_pay_link(link_id) link = await get_pay_link(link_id)
if not link: if not link:
@ -210,9 +214,9 @@ async def api_link_delete(link_id: str, wallet: WalletTypeInfo = Depends(get_key
) )
# admins are allowed to delete paylinks beloging to regular users # admins are allowed to delete paylinks beloging to regular users
user = await get_user(wallet.wallet.user) user = await get_user(key_info.wallet.user)
admin_user = user.admin if user else False admin_user = user.admin if user else False
if not admin_user and link.wallet != wallet.wallet.id: if not admin_user and link.wallet != key_info.wallet.id:
raise HTTPException( raise HTTPException(
detail="Not your pay link.", status_code=HTTPStatus.FORBIDDEN detail="Not your pay link.", status_code=HTTPStatus.FORBIDDEN
) )

View file

@ -19,7 +19,8 @@ from pydantic import parse_obj_as
from .crud import ( from .crud import (
get_address_data, get_address_data,
get_or_create_lnurlp_settings, get_or_create_lnurlp_settings,
increment_pay_link, get_pay_link,
update_pay_link,
) )
lnurlp_lnurl_router = APIRouter() lnurlp_lnurl_router = APIRouter()
@ -36,11 +37,13 @@ async def api_lnurl_callback(
amount: int = Query(...), amount: int = Query(...),
webhook_data: str = Query(None), webhook_data: str = Query(None),
): ):
link = await increment_pay_link(link_id, served_pr=1) link = await get_pay_link(link_id)
if not link: if not link:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Pay link does not exist." status_code=HTTPStatus.NOT_FOUND, detail="Pay link does not exist."
) )
link.served_pr = 1
await update_pay_link(link)
mininum = link.min mininum = link.min
maximum = link.max maximum = link.max
@ -136,13 +139,15 @@ async def api_lnurl_callback(
name="lnurlp.api_lnurl_response", name="lnurlp.api_lnurl_response",
) )
async def api_lnurl_response( async def api_lnurl_response(
request: Request, link_id, webhook_data: Optional[str] = Query(None) request: Request, link_id: str, webhook_data: Optional[str] = Query(None)
): ):
link = await increment_pay_link(link_id, served_meta=1) link = await get_pay_link(link_id)
if not link: if not link:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Pay link does not exist." status_code=HTTPStatus.NOT_FOUND, detail="Pay link does not exist."
) )
link.served_meta = 1
await update_pay_link(link)
rate = await get_fiat_rate_satoshis(link.currency) if link.currency else 1 rate = await get_fiat_rate_satoshis(link.currency) if link.currency else 1
url = request.url_for("lnurlp.api_lnurl_callback", link_id=link.id) url = request.url_for("lnurlp.api_lnurl_callback", link_id=link.id)