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:
parent
32bf4ae1d6
commit
5042d40af6
11 changed files with 1300 additions and 1218 deletions
|
|
@ -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
32
crud.py
|
|
@ -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,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -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} (
|
||||||
|
|
|
||||||
12
models.py
12
models.py
|
|
@ -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
2360
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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()
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
11
tasks.py
11
tasks.py
|
|
@ -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")
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
6
views.py
6
views.py
|
|
@ -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()}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue