Switching lnurl links from model to crud

This commit is contained in:
arcbtc 2023-12-30 21:37:52 +00:00
parent c749da80dd
commit ba7e11e30d
14 changed files with 100 additions and 180 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

12
crud.py
View file

@ -5,7 +5,8 @@ from lnbits.helpers import urlsafe_short_hash
from . import db
from .models import CreateTempData, Temp
from loguru import logger
from fastapi import Request
from lnurl import encode as lnurl_encode
async def create_temp(wallet_id: str, data: CreateTempData) -> Temp:
temp_id = urlsafe_short_hash()
@ -31,7 +32,7 @@ async def get_temp(temp_id: str) -> Optional[Temp]:
row = await db.fetchone("SELECT * FROM tempextension.temp WHERE id = ?", (temp_id,))
return Temp(**row) if row else None
async def get_temps(wallet_ids: Union[str, List[str]]) -> List[Temp]:
async def get_temps(wallet_ids: Union[str, List[str]], req: Request) -> List[Temp]:
if isinstance(wallet_ids, str):
wallet_ids = [wallet_ids]
@ -39,7 +40,12 @@ async def get_temps(wallet_ids: Union[str, List[str]]) -> List[Temp]:
rows = await db.fetchall(
f"SELECT * FROM tempextension.temp WHERE wallet IN ({q})", (*wallet_ids,)
)
return [Temp(**row) for row in rows]
tempRows = [Temp(**row) for row in rows]
logger.debug(req.url_for("temp.api_lnurl_pay", temp_id=row.id))
for row in tempRows:
row.lnurlpay = req.url_for("temp.api_lnurl_pay", temp_id=row.id)
row.lnurlwithdraw = req.url_for("temp.api_lnurl_withdraw", temp_id=row.id)
return tempRows
async def update_temp(temp_id: str, **kwargs) -> Temp:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])

View file

@ -15,7 +15,9 @@ from lnbits.core.services import create_invoice
#################################################
@temp_ext.get(
"/api/v1/lnurl/pay/{temp_id}", status_code=HTTPStatus.OK
"/api/v1/lnurl/pay/{temp_id}",
status_code=HTTPStatus.OK,
name="temp.api_lnurl_pay",
)
async def api_lnurl_pay(
request: Request,
@ -67,7 +69,9 @@ async def api_lnurl_pay_cb(
@temp_ext.get(
"/api/v1/lnurl/withdraw/{temp_id}", status_code=HTTPStatus.OK
"/api/v1/lnurl/withdraw/{temp_id}",
status_code=HTTPStatus.OK,
name="temp.api_lnurl_withdraw",
)
async def api_lnurl_pay(
request: Request,
@ -77,7 +81,7 @@ async def api_lnurl_pay(
if not temp:
return {"status": "ERROR", "reason": "No temp found"}
return {
"callback": str(request.url_for("temp.api_lnurl_pay_callback", temp_id=temp_id)),
"callback": str(request.url_for("temp.api_lnurl_withdraw_callback", temp_id=temp_id)),
"maxSendable": temp.lnurlwithdrawamount,
"minSendable": temp.lnurlwithdrawamount,
"k1": "",
@ -89,7 +93,7 @@ async def api_lnurl_pay(
@temp_ext.get(
"/api/v1/lnurl/pay/cb/{temp_id}",
status_code=HTTPStatus.OK,
name="temp.api_lnurl_pay_callback",
name="temp.api_lnurl_withdraw_callback",
)
async def api_lnurl_pay_cb(
request: Request,

View file

@ -27,3 +27,27 @@ async def m002_addtip_wallet(db):
ALTER TABLE tempextension.temp ADD lnurlwithdrawamount INTEGER DEFAULT 0;
"""
)
# Here we add another field to the database, always add never edit!
async def m004_addtip_wallet(db):
"""
Add total to templates table
"""
await db.execute(
"""
ALTER TABLE tempextension.temp ADD lnurlwithdraw TEXT;
"""
)
# Here we add another field to the database
async def m005_addtip_wallet(db):
"""
Add total to templates table
"""
await db.execute(
"""
ALTER TABLE tempextension.temp ADD lnurlpay TEXT;
"""
)

View file

@ -1,8 +1,13 @@
# Models for retrieving data from the database
# Includes some classmethods where we can add some logic to the data
from sqlite3 import Row
from typing import Optional, List
from pydantic import BaseModel
from fastapi import Request
from lnbits.lnurl import encode as lnurl_encode
from urllib.parse import urlparse
class CreateTempData(BaseModel):
wallet: Optional[str]
@ -18,6 +23,8 @@ class Temp(BaseModel):
total: Optional[int]
lnurlpayamount: Optional[int]
lnurlwithdrawamount: Optional[int]
lnurlpay: str
lnurlwithdraw: str
@classmethod
def from_row(cls, row: Row) -> "Temp":

View file

@ -33,8 +33,6 @@
<q-th v-for="col in props.cols" :key="col.name" :props="props">
${ col.label }
</q-th>
<q-th auto-width>LNURL-pay</q-th>
<q-th auto-width>LNURL-withdraw</q-th>
</q-tr>
</temp>
@ -46,27 +44,42 @@
:props="props"
>
${ col.value }
</q-td>
<q-td auto-width>
<q-btn
unelevated
dense
size="sm"
v-if="col.name == 'lnurlpayamount'"
icon="qr_code"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
class="q-mr-sm"
@click="openUrlDialog(props.row.id)"
></q-btn>
<q-btn
unelevated
dense
size="sm"
v-if="col.name == 'lnurlwithdrawamount'"
icon="qr_code"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
class="q-mr-sm"
@click="openUrlDialog(props.row.id)"
></q-btn>
</q-td>
></q-btn></q-td>
<q-td auto-width>
<q-btn
unelevated
dense
size="sm"
icon="launch"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
type="a"
:href="props.row.temp"
target="_blank"
><q-tooltip>Open public page</q-tooltip></q-btn
></q-td>
<q-td auto-width>
<q-btn
unelevated
dense
size="md"
copy="copy"
@click="copyText(props.row.id)"
><q-tooltip>Click to copy</q-tooltip
>${props.row.id.substring(0,6)}...</q-btn
>
</q-td>
</q-tr>
</template>
@ -151,11 +164,11 @@
<q-dialog v-model="urlDialog.show" position="top">
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-responsive :ratio="1" class="q-mx-xl q-mb-md">
<lnbits-qrcode :value="urlDialog.data.shareUrl"></lnbits-qrcode>
<lnbits-qrcode :value="urlDialog.data.lnurlpay"></lnbits-qrcode>
</q-responsive>
<div class="text-center q-mb-xl">
<p style="word-break: break-all">
<strong>${ urlDialog.data.name }</strong><br />${
<strong>${ urlDialog.data.lnurlpay }</strong><br />${
urlDialog.data.shareUrl }
</p>
</div>
@ -163,7 +176,7 @@
<q-btn
outline
color="grey"
@click="copyText(urlDialog.data.shareUrl, 'Temp URL copied to clipboard!')"
@click="copyText(urlDialog.data.lnurlpay, 'Temp URL copied to clipboard!')"
>Copy URL</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
@ -199,18 +212,6 @@
align: 'left',
label: 'Wallet',
field: 'wallet'
},
{
name: 'lnurlwithdrawamount',
align: 'left',
label: 'LNURL Withdraw amount',
field: 'lnurlwithdrawamount'
},
{
name: 'lnurlpayamount',
align: 'left',
label: 'LNURL Pay amount',
field: 'lnurlpayamount'
}
],
pagination: {
@ -379,141 +380,6 @@
disabled: false
}
},
deleteItem(id) {
const [tempId, itemId] = id.split(':')
const temp = _.findWhere(this.temps, {id: tempId})
const wallet = _.findWhere(this.g.user.wallets, {
id: temp.wallet
})
LNbits.utils
.confirmDialog('Are you sure you want to delete this item?')
.onOk(() => {
temp.itemsMap.delete(id)
const data = {
items: [...temp.itemsMap.values()]
}
this.updateTempItems(temp.id, wallet, data)
})
},
addItems() {
const temp = _.findWhere(this.temps, {id: this.formDialog.data.temp})
const wallet = _.findWhere(this.g.user.wallets, {
id: temp.wallet
})
if (this.formDialog.data.id) {
temp.itemsMap.set(this.formDialog.data.id, this.formDialog.data)
}
const data = {
items: this.formDialog.data.id
? [...temp.itemsMap.values()]
: [...temp.items, this.formDialog.data]
}
this.updateTempItems(temp.id, wallet, data)
},
deleteAllItems(tempId) {
const temp = _.findWhere(this.temps, {id: tempId})
const wallet = _.findWhere(this.g.user.wallets, {
id: temp.wallet
})
LNbits.utils
.confirmDialog('Are you sure you want to delete ALL items?')
.onOk(() => {
temp.itemsMap.clear()
const data = {
items: []
}
this.updateTempItems(temp.id, wallet, data)
})
},
updateTempItems(tempId, wallet, data) {
LNbits.api
.request(
'PUT',
`/temp/api/v1/temps/${tempId}/items`,
wallet.adminkey,
data
)
.then(response => {
this.temps = _.reject(this.temps, obj => {
return obj.id == tempId
})
this.temps.push(mapTemp(response.data))
this.closeformDialog()
})
.catch(error => {
LNbits.utils.notifyApiError(error)
})
},
exportJSON(tempId) {
const temp = _.findWhere(this.temps, {id: tempId})
const data = [...temp.items]
const filename = `items_${temp.id}.json`
const json = JSON.stringify(data, null, 2)
let status = Quasar.utils.exportFile(filename, json)
if (status !== true) {
this.$q.notify({
message: 'Browser denied file download...',
color: 'negative'
})
}
},
importJSON(tempId) {
try {
let input = document.getElementById('import')
input.click()
input.onchange = e => {
let file = e.target.files[0]
let reader = new FileReader()
reader.readAsText(file, 'UTF-8')
reader.onload = async readerEvent => {
try {
let content = readerEvent.target.result
let data = JSON.parse(content).filter(
obj => obj.title && obj.price
)
if (!data.length) {
throw new Error('Invalid JSON or missing data.')
}
this.openFileDataDialog(tempId, data)
} catch (error) {
this.$q.notify({
message: `Error importing file. ${error.message}`,
color: 'negative'
})
return
}
}
}
} catch (error) {
this.$q.notify({
message: 'Error importing file',
color: 'negative'
})
}
},
openFileDataDialog(tempId, data) {
const temp = _.findWhere(this.temps, {id: tempId})
const wallet = _.findWhere(this.g.user.wallets, {
id: temp.wallet
})
data.forEach(item => {
item.formattedPrice = LNbits.utils.formatCurrency(
Number(item.price).toFixed(2),
temp.currency
)
})
this.fileDataDialog.data = data
this.fileDataDialog.count = data.length
this.fileDataDialog.show = true
this.fileDataDialog.import = () => {
let updatedData = {
items: [...temp.items, ...data]
}
this.updateTempItems(temp.id, wallet, updatedData)
this.fileDataDialog.data = {}
this.fileDataDialog.show = false
}
},
openUrlDialog(id) {
this.urlDialog.data = _.findWhere(this.temps, {id})
this.urlDialog.show = true
@ -522,6 +388,7 @@
created: function () {
if (this.g.user.wallets.length) {
this.getTemps()
}
}
})

View file

@ -37,7 +37,7 @@
</q-card-section>
<q-card-section class="q-pa-none">
<q-separator></q-separator>
<q-list> {% include "lnurlp/_lnurl.html" %} </q-list>
<q-list> </q-list>
</q-card-section>
</q-card>
</div>
@ -48,7 +48,18 @@
new Vue({
el: '#vue',
mixins: [windowMixin]
mixins: [windowMixin],
data: function () {
return {
lnurlpay: "",
lnurlwithdraw: "",
}
},
created: function () {
if (this.g.user.wallets.length) {
this.getTemps()
}
}
})
</script>
{% endblock %}

View file

@ -42,14 +42,15 @@ async def temp(request: Request, temp_id):
"temp/temp.html",
{
"request": request,
"temp": temp,
"withdrawamtemps": temp.withdrawamtemps,
"temp_id": temp_id,
"lnurlpay": temp.lnurlpayamount,
"lnurlwithdraw": temp.lnurlwithdrawamount,
"web_manifest": f"/temp/manifest/{temp_id}.webmanifest",
},
)
# Manifest, customise or remove manifest completely
# Manifest for public page, customise or remove manifest completely
@temp_ext.get("/manifest/{temp_id}.webmanifest")
async def manifest(temp_id: str):

View file

@ -38,13 +38,13 @@ from .models import CreateTempData
@temp_ext.get("/api/v1/temps", status_code=HTTPStatus.OK)
async def api_temps(
all_wallets: bool = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)
req: Request, all_wallets: bool = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)
):
wallet_ids = [wallet.wallet.id]
if all_wallets:
user = await get_user(wallet.wallet.user)
wallet_ids = user.wallet_ids if user else []
return [temp.dict() for temp in await get_temps(wallet_ids)]
return [temp.dict() for temp in await get_temps(wallet_ids, req)]
## Get a specific record belonging to a user