feat: update to lnbits 1.0.0 (#27)

* feat: update to lnbits 1.0.0
* fix select wallet
* fix splits
* fix: types, postgres errors with cache
---------

Co-authored-by: Tiago Vasconcelos <talvasconcelos@gmail.com>
This commit is contained in:
dni ⚡ 2024-11-28 12:28:00 +01:00 committed by GitHub
commit 5042d40af6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 1300 additions and 1218 deletions

View file

@ -2,7 +2,7 @@
"name": "Split Payments", "name": "Split Payments",
"short_description": "Split incoming payments across wallets", "short_description": "Split incoming payments across wallets",
"tile": "/splitpayments/static/image/split-payments.png", "tile": "/splitpayments/static/image/split-payments.png",
"min_lnbits_version": "0.11.0", "min_lnbits_version": "1.0.0",
"contributors": [ "contributors": [
{ {
"name": "cryptograffiti", "name": "cryptograffiti",

32
crud.py
View file

@ -1,37 +1,23 @@
from typing import List
from lnbits.db import Database from lnbits.db import Database
from lnbits.helpers import urlsafe_short_hash
from .models import Target from .models import Target
db = Database("ext_splitpayments") db = Database("ext_splitpayments")
async def get_targets(source_wallet: str) -> List[Target]: async def get_targets(source_wallet: str) -> list[Target]:
rows = await db.fetchall( return await db.fetchall(
"SELECT * FROM splitpayments.targets WHERE source = ?", (source_wallet,) "SELECT * FROM splitpayments.targets WHERE source = :source_wallet",
{"source_wallet": source_wallet},
Target,
) )
return [Target(**row) for row in rows]
async def set_targets(source_wallet: str, targets: List[Target]): async def set_targets(source_wallet: str, targets: list[Target]):
async with db.connect() as conn: async with db.connect() as conn:
await conn.execute( await conn.execute(
"DELETE FROM splitpayments.targets WHERE source = ?", (source_wallet,) "DELETE FROM splitpayments.targets WHERE source = :source_wallet",
{"source_wallet": source_wallet},
) )
for target in targets: for target in targets:
await conn.execute( await conn.insert("splitpayments.targets", target)
"""
INSERT INTO splitpayments.targets
(id, source, wallet, percent, alias)
VALUES (?, ?, ?, ?, ?)
""",
(
urlsafe_short_hash(),
source_wallet,
target.wallet,
target.percent,
target.alias,
),
)

View file

@ -1,7 +1,8 @@
from lnbits.db import Connection
from lnbits.helpers import urlsafe_short_hash from lnbits.helpers import urlsafe_short_hash
async def m001_initial(db): async def m001_initial(db: Connection):
""" """
Initial split payment table. Initial split payment table.
""" """
@ -19,49 +20,24 @@ async def m001_initial(db):
) )
async def m002_float_percent(db): async def m002_float_percent(db: Connection):
""" """
Add float percent and migrates the existing data. Add float percent and migrates the existing data.
""" """
await db.execute("ALTER TABLE splitpayments.targets RENAME TO splitpayments_old")
await db.execute( await db.execute(
""" """
CREATE TABLE splitpayments.targets ( ALTER TABLE splitpayments.targets
wallet TEXT NOT NULL, ADD COLUMN percent REAL NOT NULL CHECK (percent >= 0 AND percent <= 100)
source TEXT NOT NULL, """
percent REAL NOT NULL CHECK (percent >= 0 AND percent <= 100),
alias TEXT,
UNIQUE (source, wallet)
);
"""
) )
for row in [
list(row)
for row in await db.fetchall("SELECT * FROM splitpayments.splitpayments_old")
]:
await db.execute(
"""
INSERT INTO splitpayments.targets (
wallet,
source,
percent,
alias
)
VALUES (?, ?, ?, ?)
""",
(row[0], row[1], row[2], row[3]),
)
await db.execute("DROP TABLE splitpayments.splitpayments_old") async def m003_add_id_and_tag(db: Connection):
async def m003_add_id_and_tag(db):
""" """
Add float percent and migrates the existing data. Add id, tag and migrates the existing data.
""" """
await db.execute("ALTER TABLE splitpayments.targets RENAME TO splitpayments_old") await db.execute("ALTER TABLE splitpayments.targets RENAME TO splitpayments_m002")
await db.execute( await db.execute(
""" """
CREATE TABLE splitpayments.targets ( CREATE TABLE splitpayments.targets (
@ -76,11 +52,9 @@ async def m003_add_id_and_tag(db):
); );
""" """
) )
result = await db.execute("SELECT * FROM splitpayments.splitpayments_m002")
for row in [ rows = result.mappings().all()
list(row) for row in rows:
for row in await db.fetchall("SELECT * FROM splitpayments.splitpayments_old")
]:
await db.execute( await db.execute(
""" """
INSERT INTO splitpayments.targets ( INSERT INTO splitpayments.targets (
@ -91,23 +65,31 @@ async def m003_add_id_and_tag(db):
tag, tag,
alias alias
) )
VALUES (?, ?, ?, ?, ?, ?) VALUES (:id, :wallet, :source, :percent, :tag, :alias)
""", """,
(urlsafe_short_hash(), row[0], row[1], row[2], "", row[3]), {
"id": urlsafe_short_hash(),
"wallet": row["wallet"],
"source": row["source"],
"percent": row["percent"],
"tag": row["tag"],
"alias": row["alias"],
},
) )
await db.execute("DROP TABLE splitpayments.splitpayments_old") await db.execute("DROP TABLE splitpayments.splitpayments_m002")
async def m004_remove_tag(db): async def m004_remove_tag(db: Connection):
""" """
This removes tag This removes tag
""" """
keys = "id,wallet,source,percent,alias" keys = "id,wallet,source,percent,alias"
new_db = "splitpayments.targets" new_db = "splitpayments.targets"
old_db = "splitpayments.targets_old" old_db = "splitpayments.targets_m003"
await db.execute(f"ALTER TABLE {new_db} RENAME TO targets_m003")
await db.execute(f"ALTER TABLE {new_db} RENAME TO targets_old")
await db.execute( await db.execute(
f""" f"""
CREATE TABLE {new_db} ( CREATE TABLE {new_db} (

View file

@ -1,19 +1,15 @@
from sqlite3 import Row from typing import Optional
from typing import List, Optional
from fastapi import Query from fastapi import Query
from pydantic import BaseModel from pydantic import BaseModel
class Target(BaseModel): class Target(BaseModel):
id: str
wallet: str wallet: str
source: str source: str
percent: float percent: float
alias: Optional[str] alias: Optional[str] = None
@classmethod
def from_row(cls, row: Row):
return cls(**dict(row))
class TargetPut(BaseModel): class TargetPut(BaseModel):
@ -23,4 +19,4 @@ class TargetPut(BaseModel):
class TargetPutList(BaseModel): class TargetPutList(BaseModel):
targets: List[TargetPut] targets: list[TargetPut]

2360
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,3 @@
/* globals Quasar, Vue, _, VueQrcode, windowMixin, LNbits, LOCALE */
Vue.component(VueQrcode.name, VueQrcode)
function hashTargets(targets) { function hashTargets(targets) {
return targets return targets
.filter(isTargetComplete) .filter(isTargetComplete)
@ -17,9 +13,14 @@ function isTargetComplete(target) {
) )
} }
new Vue({ window.app = Vue.createApp({
el: '#vue', el: '#vue',
mixins: [windowMixin], mixins: [windowMixin],
watch: {
selectedWallet() {
this.getTargets()
}
},
data() { data() {
return { return {
selectedWallet: null, selectedWallet: null,
@ -38,7 +39,7 @@ new Vue({
return this.deleteTargets() return this.deleteTargets()
} }
this.targets.splice(index, 1) this.targets.splice(index, 1)
this.$q.notify({ Quasar.Notify.create({
message: 'Removed item. You must click to save manually.', message: 'Removed item. You must click to save manually.',
timeout: 500 timeout: 500
}) })
@ -75,7 +76,7 @@ new Vue({
} }
) )
.then(response => { .then(response => {
this.$q.notify({ Quasar.Notify.create({
message: 'Split payments targets set.', message: 'Split payments targets set.',
timeout: 700 timeout: 700
}) })
@ -96,7 +97,7 @@ new Vue({
this.selectedWallet.adminkey this.selectedWallet.adminkey
) )
.then(response => { .then(response => {
this.$q.notify({ Quasar.Notify.create({
message: 'Split payments targets deleted.', message: 'Split payments targets deleted.',
timeout: 700 timeout: 700
}) })
@ -109,6 +110,5 @@ new Vue({
}, },
created() { created() {
this.selectedWallet = this.g.user.wallets[0] this.selectedWallet = this.g.user.wallets[0]
this.getTargets()
} }
}) })

View file

@ -8,7 +8,6 @@ import httpx
from lnbits.core.crud import get_standalone_payment from lnbits.core.crud import get_standalone_payment
from lnbits.core.models import Payment from lnbits.core.models import Payment
from lnbits.core.services import create_invoice, fee_reserve, pay_invoice from lnbits.core.services import create_invoice, fee_reserve, pay_invoice
from lnbits.helpers import get_current_extension_name
from lnbits.tasks import register_invoice_listener from lnbits.tasks import register_invoice_listener
from loguru import logger from loguru import logger
@ -17,8 +16,7 @@ from .crud import get_targets
async def wait_for_paid_invoices(): async def wait_for_paid_invoices():
invoice_queue = asyncio.Queue() invoice_queue = asyncio.Queue()
register_invoice_listener(invoice_queue, get_current_extension_name()) register_invoice_listener(invoice_queue, "ext_splitpayments_invoice_listener")
while True: while True:
payment = await invoice_queue.get() payment = await invoice_queue.get()
await on_invoice_paid(payment) await on_invoice_paid(payment)
@ -58,14 +56,15 @@ async def on_invoice_paid(payment: Payment) -> None:
target.wallet, payment.wallet_id, safe_amount_msat, memo target.wallet, payment.wallet_id, safe_amount_msat, memo
) )
else: else:
_, payment_request = await create_invoice( new_payment = await create_invoice(
wallet_id=target.wallet, wallet_id=target.wallet,
amount=int(amount_msat / 1000), amount=int(amount_msat / 1000),
internal=True, internal=True,
memo=memo, memo=memo,
) )
payment_request = new_payment.bolt11
extra = {**payment.extra, "tag": "splitpayments", "splitted": True} extra = {**payment.extra, "splitted": True}
if payment_request: if payment_request:
await pay_invoice( await pay_invoice(
@ -90,7 +89,7 @@ async def get_lnurl_invoice(
r = await client.get( r = await client.get(
data["callback"], data["callback"],
params={"amount": rounded_amount, "comment": memo}, params={"amount": rounded_amount, "comment": memo},
timeout=40, timeout=5,
) )
if r.is_error: if r.is_error:
raise httpx.ConnectError("issue with scrub callback") raise httpx.ConnectError("issue with scrub callback")

View file

@ -9,10 +9,9 @@
filled filled
dense dense
:options="g.user.wallets" :options="g.user.wallets"
:value="selectedWallet" v-model="selectedWallet"
label="Source Wallet:" label="Source Wallet:"
option-label="name" option-label="name"
@input="changedWallet"
> >
</q-select> </q-select>
</q-form> </q-form>

View file

@ -1,11 +1,9 @@
from fastapi import APIRouter, Depends, Request from fastapi import APIRouter, Depends, Request
from fastapi.templating import Jinja2Templates from fastapi.responses import HTMLResponse
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
from starlette.responses import HTMLResponse
templates = Jinja2Templates(directory="templates")
splitpayments_generic_router = APIRouter() splitpayments_generic_router = APIRouter()
@ -16,5 +14,5 @@ def splitpayments_renderer():
@splitpayments_generic_router.get("/", response_class=HTMLResponse) @splitpayments_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 splitpayments_renderer().TemplateResponse( return splitpayments_renderer().TemplateResponse(
"splitpayments/index.html", {"request": request, "user": user.dict()} "splitpayments/index.html", {"request": request, "user": user.json()}
) )

View file

@ -4,6 +4,7 @@ from fastapi import APIRouter, Depends, HTTPException
from lnbits.core.crud import get_wallet, get_wallet_for_key from lnbits.core.crud import get_wallet, get_wallet_for_key
from lnbits.core.models import WalletTypeInfo from lnbits.core.models import WalletTypeInfo
from lnbits.decorators import require_admin_key from lnbits.decorators import require_admin_key
from lnbits.helpers import urlsafe_short_hash
from loguru import logger from loguru import logger
from .crud import get_targets, set_targets from .crud import get_targets, set_targets
@ -53,6 +54,7 @@ async def api_targets_set(
targets.append( targets.append(
Target( Target(
id=urlsafe_short_hash(),
wallet=entry.wallet, wallet=entry.wallet,
source=source_wallet.wallet.id, source=source_wallet.wallet.id,
percent=entry.percent, percent=entry.percent,