Fix update validations (#30)

* fix: no `username` update
* fix: update for old pay_links
* chore: code format
This commit is contained in:
Vlad Stan 2023-11-02 16:09:32 +02:00 committed by GitHub
commit 257f5d34d2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 252 additions and 94 deletions

View file

@ -1,4 +1,5 @@
# LNURLp - <small>[LNbits](https://github.com/lnbits/lnbits) extension</small> # LNURLp - <small>[LNbits](https://github.com/lnbits/lnbits) extension</small>
<small>For more about LNBits extension check [this tutorial](https://github.com/lnbits/lnbits/wiki/LNbits-Extensions)</small> <small>For more about LNBits extension check [this tutorial](https://github.com/lnbits/lnbits/wiki/LNbits-Extensions)</small>
## Create a static QR code or LNaddress people can use to pay over Lightning Network ## Create a static QR code or LNaddress people can use to pay over Lightning Network
@ -39,7 +40,7 @@ Now that the extensions are taken out of core LNbits we can update each extensio
This new version of the extension will give you the option to add a Lightning Address to each LNURLpay link. This new version of the extension will give you the option to add a Lightning Address to each LNURLpay link.
- Open your LNbits instance as super admin (not as a regular user. You will find the SuperUser-ID in your server logs on restart of LNbits. Use that to bookmark and manage LNbits from there in the future.) - Open your LNbits instance as super admin (not as a regular user. You will find the SuperUser-ID in your server logs on restart of LNbits. Use that to bookmark and manage LNbits from there in the future.)
Now lets install the new version of a given extension like extensively [described in this guide](https://github.com/lnbits/lnbits/blob/main/docs/guide/extension-install.md#install-new-extension). In short: Now lets install the new version of a given extension like extensively [described in this guide](https://github.com/lnbits/lnbits/blob/main/docs/guide/extension-install.md#install-new-extension). In short:
- Go to "Mange extensions", click on "ALL", search for e.g. LNURLp, click on "Manage" - Go to "Mange extensions", click on "ALL", search for e.g. LNURLp, click on "Manage"
- Open the details of the extension and click on version 0.2.1, click "Install". You´re done! - Open the details of the extension and click on version 0.2.1, click "Install". You´re done!
@ -55,7 +56,4 @@ Now you can receive sats to your newly created LN address. You will find this in
[![lnurl-details.jpg](https://i.postimg.cc/zDwq1V2X/lnurl-details.jpg)](https://postimg.cc/3WwsXJHP) [![lnurl-details.jpg](https://i.postimg.cc/zDwq1V2X/lnurl-details.jpg)](https://postimg.cc/3WwsXJHP)
</details> </details>

View file

@ -1,17 +1,16 @@
import asyncio import asyncio
from typing import List from typing import List
from environs import Env
from fastapi import APIRouter from fastapi import APIRouter
from loguru import logger
from lnbits.db import Database from lnbits.db import Database
from lnbits.helpers import template_renderer from lnbits.helpers import template_renderer
from lnbits.tasks import catch_everything_and_restart from lnbits.tasks import catch_everything_and_restart
from loguru import logger
from .nostr.event import Event from .nostr.event import Event
from .nostr.key import PrivateKey, PublicKey from .nostr.key import PrivateKey, PublicKey
from environs import Env
def generate_keys(private_key: str = ""): def generate_keys(private_key: str = ""):

View file

@ -1,4 +1,3 @@
import re
from typing import List, Optional, Union from typing import List, Optional, Union
from lnbits.helpers import urlsafe_short_hash from lnbits.helpers import urlsafe_short_hash
@ -105,7 +104,7 @@ async def get_pay_links(wallet_ids: Union[str, List[str]]) -> List[PayLink]:
async def update_pay_link(link_id: str, **kwargs) -> Optional[PayLink]: async def update_pay_link(link_id: str, **kwargs) -> Optional[PayLink]:
if "username" in kwargs and len(kwargs["username"]) > 0: if "username" in kwargs and len(kwargs["username"] or "") > 0:
await check_lnaddress_format(kwargs["username"]) await check_lnaddress_format(kwargs["username"])
await check_lnaddress_not_exists(kwargs["username"]) await check_lnaddress_not_exists(kwargs["username"])

View file

@ -3,18 +3,13 @@ from urllib.parse import urlparse
from fastapi import Query, Request from fastapi import Query, Request
from lnurl import LnurlErrorResponse, LnurlPayActionResponse, LnurlPayResponse from lnurl import LnurlErrorResponse, LnurlPayActionResponse, LnurlPayResponse
from loguru import logger
from starlette.exceptions import HTTPException from starlette.exceptions import HTTPException
from lnbits.core.services import create_invoice from lnbits.core.services import create_invoice
from lnbits.utils.exchange_rates import get_fiat_rate_satoshis from lnbits.utils.exchange_rates import get_fiat_rate_satoshis
from . import lnurlp_ext from . import lnurlp_ext, nostr_publickey
from .crud import increment_pay_link, get_pay_link, get_address_data from .crud import increment_pay_link
from loguru import logger
from urllib.parse import urlparse
import json
from . import nostr_publickey
@lnurlp_ext.get( @lnurlp_ext.get(
@ -132,7 +127,9 @@ async def api_lnurl_response(request: Request, link_id, lnaddress=False):
if lnaddress: if lnaddress:
# for lnaddress, we have to set this otherwise the metadata won't have the identifier # for lnaddress, we have to set this otherwise the metadata won't have the identifier
link.domain = urlparse(str(request.url)).netloc link.domain = urlparse(str(request.url)).netloc
callback = str(request.url_for("lnurlp.api_lnurl_lnaddr_callback", link_id=link.id)) callback = str(
request.url_for("lnurlp.api_lnurl_lnaddr_callback", link_id=link.id)
)
else: else:
callback = str(request.url_for("lnurlp.api_lnurl_callback", link_id=link.id)) callback = str(request.url_for("lnurlp.api_lnurl_callback", link_id=link.id))

View file

@ -24,7 +24,7 @@ class CreatePayLinkData(BaseModel):
success_url: str = Query(None) success_url: str = Query(None)
fiat_base_multiplier: int = Query(100, ge=1) fiat_base_multiplier: int = Query(100, ge=1)
username: str = Query(None) username: str = Query(None)
zaps: bool = Query(False) zaps: Optional[bool] = Query(False)
class PayLink(BaseModel): class PayLink(BaseModel):

View file

@ -41,7 +41,7 @@ new Vue({
show: false, show: false,
fixedAmount: true, fixedAmount: true,
data: { data: {
zaps:false zaps: false
} }
}, },
qrCodeDialog: { qrCodeDialog: {

View file

@ -1,23 +1,21 @@
import asyncio import asyncio
import json import json
import time
from threading import Thread
from typing import List
import httpx import httpx
from loguru import logger from loguru import logger
from websocket import WebSocketApp
from lnbits.core.crud import update_payment_extra from lnbits.core.crud import update_payment_extra
from lnbits.core.models import Payment from lnbits.core.models import Payment
from lnbits.helpers import get_current_extension_name from lnbits.helpers import get_current_extension_name
from lnbits.tasks import register_invoice_listener from lnbits.tasks import register_invoice_listener
from websocket import WebSocketApp
from lnbits.settings import settings
from .crud import get_pay_link
from threading import Thread
from . import nostr_privatekey
from typing import List
import time
from . import nostr_privatekey
from .crud import get_pay_link
from .nostr.event import Event from .nostr.event import Event
from .nostr.key import PrivateKey, PublicKey
async def wait_for_paid_invoices(): async def wait_for_paid_invoices():

View file

@ -4,7 +4,9 @@
<div class="col-12 col-md-7 q-gutter-y-md"> <div class="col-12 col-md-7 q-gutter-y-md">
<q-card> <q-card>
<q-card-section> <q-card-section>
<q-btn unelevated color="primary" @click="formDialog.show = true">New pay link</q-btn> <q-btn unelevated color="primary" @click="formDialog.show = true"
>New pay link</q-btn
>
</q-card-section> </q-card-section>
</q-card> </q-card>
@ -15,7 +17,13 @@
<h5 class="text-subtitle1 q-my-none">Pay links</h5> <h5 class="text-subtitle1 q-my-none">Pay links</h5>
</div> </div>
</div> </div>
<q-table dense flat :data="payLinks" row-key="id" :pagination.sync="payLinksTable.pagination"> <q-table
dense
flat
:data="payLinks"
row-key="id"
:pagination.sync="payLinksTable.pagination"
>
{% raw %} {% raw %}
<template v-slot:header="props"> <template v-slot:header="props">
<q-tr class="text-left" :props="props"> <q-tr class="text-left" :props="props">
@ -31,10 +39,26 @@
<template v-slot:body="props"> <template v-slot:body="props">
<q-tr :props="props"> <q-tr :props="props">
<q-td auto-width> <q-td auto-width>
<q-btn unelevated dense size="xs" icon="launch" :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'" <q-btn
type="a" :href="props.row.pay_url" target="_blank"><q-tooltip>Shareable Page</q-tooltip></q-btn> unelevated
<q-btn unelevated dense size="xs" icon="visibility" :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'" dense
@click="openQrCodeDialog(props.row.id)"><q-tooltip>View Link</q-tooltip></q-btn> size="xs"
icon="launch"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
type="a"
:href="props.row.pay_url"
target="_blank"
><q-tooltip>Shareable Page</q-tooltip></q-btn
>
<q-btn
unelevated
dense
size="xs"
icon="visibility"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="openQrCodeDialog(props.row.id)"
><q-tooltip>View Link</q-tooltip></q-btn
>
</q-td> </q-td>
<q-td auto-width>{{ props.row.description }}</q-td> <q-td auto-width>{{ props.row.description }}</q-td>
<q-td auto-width> <q-td auto-width>
@ -44,30 +68,57 @@
<span v-else>{{ props.row.min }} - {{ props.row.max }}</span> <span v-else>{{ props.row.min }} - {{ props.row.max }}</span>
</q-td> </q-td>
<q-td>{{ props.row.currency || 'sat' }}</q-td> <q-td>{{ props.row.currency || 'sat' }}</q-td>
<q-td auto-width :class="(props.row.username) ? 'text-normal' : 'text-grey'">{{ props.row.username || <q-td
'None' }}</q-td> auto-width
:class="(props.row.username) ? 'text-normal' : 'text-grey'"
>{{ props.row.username || 'None' }}</q-td
>
<q-td> <q-td>
<q-icon v-if="props.row.webhook_url" size="14px" name="http"> <q-icon v-if="props.row.webhook_url" size="14px" name="http">
<q-tooltip>Webhook to {{ props.row.webhook_url }}</q-tooltip> <q-tooltip>Webhook to {{ props.row.webhook_url }}</q-tooltip>
</q-icon> </q-icon>
<q-icon v-if="props.row.success_text || props.row.success_url" size="14px" name="call_to_action"> <q-icon
v-if="props.row.success_text || props.row.success_url"
size="14px"
name="call_to_action"
>
<q-tooltip> <q-tooltip>
On success, show message '{{ props.row.success_text }}' On success, show message '{{ props.row.success_text }}'
<span v-if="props.row.success_url">and URL '{{ props.row.success_url }}'</span> <span v-if="props.row.success_url"
>and URL '{{ props.row.success_url }}'</span
>
</q-tooltip> </q-tooltip>
</q-icon> </q-icon>
<q-icon v-if="props.row.comment_chars > 0" size="14px" name="insert_comment"> <q-icon
v-if="props.row.comment_chars > 0"
size="14px"
name="insert_comment"
>
<q-tooltip> <q-tooltip>
{{ props.row.comment_chars }}-char comment allowed {{ props.row.comment_chars }}-char comment allowed
</q-tooltip> </q-tooltip>
</q-icon> </q-icon>
</q-td> </q-td>
<q-td auto-width> <q-td auto-width>
<q-btn flat dense size="xs" @click="openUpdateDialog(props.row.id)" icon="edit" color="light-blue"> <q-btn
flat
dense
size="xs"
@click="openUpdateDialog(props.row.id)"
icon="edit"
color="light-blue"
>
<q-tooltip>Edit</q-tooltip> <q-tooltip>Edit</q-tooltip>
</q-btn> </q-btn>
<q-btn flat dense size="xs" @click="deletePayLink(props.row.id)" icon="cancel" <q-btn
color="pink"><q-tooltip>Delete</q-tooltip></q-btn> flat
dense
size="xs"
@click="deletePayLink(props.row.id)"
icon="cancel"
color="pink"
><q-tooltip>Delete</q-tooltip></q-btn
>
</q-td> </q-td>
</q-tr> </q-tr>
</template> </template>
@ -98,14 +149,33 @@
<q-dialog v-model="formDialog.show" @hide="closeFormDialog"> <q-dialog v-model="formDialog.show" @hide="closeFormDialog">
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card"> <q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-form @submit="sendFormData" class="q-gutter-md"> <q-form @submit="sendFormData" class="q-gutter-md">
<q-select filled dense emit-value v-model="formDialog.data.wallet" :options="g.user.walletOptions" <q-select
label="Wallet *"> filled
dense
emit-value
v-model="formDialog.data.wallet"
:options="g.user.walletOptions"
label="Wallet *"
>
</q-select> </q-select>
<q-input filled dense v-model.trim="formDialog.data.description" type="text" label="Item description *"> <q-input
filled
dense
v-model.trim="formDialog.data.description"
type="text"
label="Item description *"
>
</q-input> </q-input>
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<q-input filled dense v-model.trim="formDialog.data.username" type="text" label="Lightning Address" @input="formDialog.data.username = formDialog.data.username.toLowerCase()" /> <q-input
filled
dense
v-model.trim="formDialog.data.username"
type="text"
label="Lightning Address"
@input="formDialog.data.username = formDialog.data.username.toLowerCase()"
/>
<span class="label"> <span class="label">
&nbsp; @ {% raw %} {{ domain }} {% endraw %} &nbsp; @ {% raw %} {{ domain }} {% endraw %}
</span> </span>
@ -117,64 +187,122 @@
</div> </div>
</div> </div>
<div class="row q-col-gutter-sm q-mx-sm"> <div class="row q-col-gutter-sm q-mx-sm">
<q-input filled dense v-model.number="formDialog.data.min" type="number" <q-input
filled
dense
v-model.number="formDialog.data.min"
type="number"
:step="formDialog.data.currency && formDialog.data.currency !== 'satoshis' ? '0.01' : '1'" :step="formDialog.data.currency && formDialog.data.currency !== 'satoshis' ? '0.01' : '1'"
:label="formDialog.fixedAmount ? 'Amount *' : 'Min *'"></q-input> :label="formDialog.fixedAmount ? 'Amount *' : 'Min *'"
<q-input v-if="!formDialog.fixedAmount" filled dense v-model.number="formDialog.data.max" type="number" ></q-input>
:step="formDialog.data.currency && formDialog.data.currency !== 'satoshis' ? '0.01' : '1'" label="Max *"> <q-input
v-if="!formDialog.fixedAmount"
filled
dense
v-model.number="formDialog.data.max"
type="number"
:step="formDialog.data.currency && formDialog.data.currency !== 'satoshis' ? '0.01' : '1'"
label="Max *"
>
</q-input> </q-input>
</div> </div>
<div class="row q-col-gutter-sm"> <div class="row q-col-gutter-sm">
<div class="col"> <div class="col">
<q-checkbox dense v-model="formDialog.fixedAmount" label="Fixed amount" /> <q-checkbox
dense
v-model="formDialog.fixedAmount"
label="Fixed amount"
/>
</div> </div>
<div class="col"> <div class="col">
<q-select dense :options="currencies" v-model="formDialog.data.currency" <q-select
:display-value="formDialog.data.currency || 'satoshis'" label="Currency" dense
:options="currencies"
v-model="formDialog.data.currency"
:display-value="formDialog.data.currency || 'satoshis'"
label="Currency"
:hint="'Converted to satoshis at each payment. ' + (formDialog.data.currency && fiatRates[formDialog.data.currency] ? `Currently 1 ${formDialog.data.currency} = ${fiatRates[formDialog.data.currency]} sat` : '')" :hint="'Converted to satoshis at each payment. ' + (formDialog.data.currency && fiatRates[formDialog.data.currency] ? `Currently 1 ${formDialog.data.currency} = ${fiatRates[formDialog.data.currency]} sat` : '')"
@input="updateFiatRate" /> @input="updateFiatRate"
/>
</div> </div>
</div> </div>
<q-expansion-item group="advanced" icon="settings" label="Advanced options"> <q-expansion-item
group="advanced"
icon="settings"
label="Advanced options"
>
<q-card> <q-card>
<q-card-section> <q-card-section>
<h5 class="text-caption q-mt-sm q-mb-none">LNURL</h5> <h5 class="text-caption q-mt-sm q-mb-none">LNURL</h5>
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<q-input filled dense v-model.number="formDialog.data.comment_chars" type="number" <q-input
label="Comment maximum characters" hint="Allow the payer to attach a comment."> filled
dense
v-model.number="formDialog.data.comment_chars"
type="number"
label="Comment maximum characters"
hint="Allow the payer to attach a comment."
>
</q-input> </q-input>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<q-input filled dense v-model="formDialog.data.webhook_url" type="text" label="Webhook URL (optional)" <q-input
hint="A URL to be called whenever this link receives a payment."></q-input> filled
dense
v-model="formDialog.data.webhook_url"
type="text"
label="Webhook URL (optional)"
hint="A URL to be called whenever this link receives a payment."
></q-input>
</div> </div>
</div> </div>
<div class="row" v-if="formDialog.data.webhook_url"> <div class="row" v-if="formDialog.data.webhook_url">
<div class="col-12"> <div class="col-12">
<q-input filled dense v-model="formDialog.data.webhook_headers" type="text" <q-input
filled
dense
v-model="formDialog.data.webhook_headers"
type="text"
label="Webhook headers (optional)" label="Webhook headers (optional)"
hint="Custom data as JSON string, send headers along with the webhook."></q-input> hint="Custom data as JSON string, send headers along with the webhook."
></q-input>
</div> </div>
<div class="col-12"> <div class="col-12">
<q-input filled dense v-model="formDialog.data.webhook_body" type="text" <q-input
filled
dense
v-model="formDialog.data.webhook_body"
type="text"
label="Webhook custom data (optional)" label="Webhook custom data (optional)"
hint="Custom data as JSON string, will get posted along with webhook 'body' field."></q-input> hint="Custom data as JSON string, will get posted along with webhook 'body' field."
></q-input>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<q-input filled dense v-model="formDialog.data.success_text" type="text" <q-input
filled
dense
v-model="formDialog.data.success_text"
type="text"
label="Success message (optional)" label="Success message (optional)"
hint="Will be shown to the user in his wallet after a successful payment."></q-input> hint="Will be shown to the user in his wallet after a successful payment."
></q-input>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<q-input filled dense v-model="formDialog.data.success_url" type="text" label="Success URL (optional)" <q-input
hint="Link will be shown to the sender after a successful payment."> filled
dense
v-model="formDialog.data.success_url"
type="text"
label="Success URL (optional)"
hint="Link will be shown to the sender after a successful payment."
>
</q-input> </q-input>
</div> </div>
</div> </div>
@ -183,24 +311,43 @@
<h5 class="text-caption q-mt-sm q-mb-none">Nostr</h5> <h5 class="text-caption q-mt-sm q-mb-none">Nostr</h5>
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<q-checkbox :toggle-indeterminate="false" dense v-model="formDialog.data.zaps" <q-checkbox
label="Enable nostr zaps" /> :toggle-indeterminate="false"
dense
v-model="formDialog.data.zaps"
label="Enable nostr zaps"
/>
</div> </div>
</div> </div>
</q-card-section> </q-card-section>
</q-card> </q-card>
</q-expansion-item> </q-expansion-item>
<div class="row q-mt-lg"> <div class="row q-mt-lg">
<q-btn v-if="formDialog.data.id" unelevated color="primary" type="submit">Update pay link</q-btn> <q-btn
<q-btn v-else unelevated color="primary" :disable=" v-if="formDialog.data.id"
unelevated
color="primary"
type="submit"
>Update pay link</q-btn
>
<q-btn
v-else
unelevated
color="primary"
:disable="
formDialog.data.wallet == null || formDialog.data.wallet == null ||
formDialog.data.description == null || formDialog.data.description == null ||
( (
formDialog.data.min == null || formDialog.data.min == null ||
formDialog.data.min <= 0 formDialog.data.min <= 0
) )
" type="submit">Create pay link</q-btn> "
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn> type="submit"
>Create pay link</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
>Cancel</q-btn
>
</div> </div>
</q-form> </q-form>
</q-card> </q-card>
@ -210,20 +357,22 @@
<q-card v-if="qrCodeDialog.data" class="q-pa-lg lnbits__dialog-card"> <q-card v-if="qrCodeDialog.data" class="q-pa-lg lnbits__dialog-card">
{% raw %} {% raw %}
<q-responsive :ratio="1" class="q-mx-xl q-mb-md"> <q-responsive :ratio="1" class="q-mx-xl q-mb-md">
<qrcode :value="'lightning:' + qrCodeDialog.data.lnurl" :options="{width: 800}" class="rounded-borders"> <qrcode
:value="'lightning:' + qrCodeDialog.data.lnurl"
:options="{width: 800}"
class="rounded-borders"
>
</qrcode> </qrcode>
</q-responsive> </q-responsive>
<p style="word-break: break-all"> <p style="word-break: break-all">
<strong>ID:</strong> {{ qrCodeDialog.data.id }}<br /> <strong>ID:</strong> {{ qrCodeDialog.data.id }}<br />
<strong>Amount:</strong> {{ qrCodeDialog.data.amount }}<br /> <strong>Amount:</strong> {{ qrCodeDialog.data.amount }}<br />
<span v-if="qrCodeDialog.data.currency"><strong>{{ qrCodeDialog.data.currency }} price:</strong> <span v-if="qrCodeDialog.data.currency"
{{ ><strong>{{ qrCodeDialog.data.currency }} price:</strong> {{
fiatRates[qrCodeDialog.data.currency] fiatRates[qrCodeDialog.data.currency] ?
? fiatRates[qrCodeDialog.data.currency] + ' sat' fiatRates[qrCodeDialog.data.currency] + ' sat' : 'Loading...' }}<br
: 'Loading...' /></span>
}}<br /></span> <strong>Accepts comments:</strong> {{ qrCodeDialog.data.comments }}<br />
<strong>Accepts comments:</strong> {{ qrCodeDialog.data.comments
}}<br />
<strong>Dispatches webhook to:</strong> {{ qrCodeDialog.data.webhook <strong>Dispatches webhook to:</strong> {{ qrCodeDialog.data.webhook
}}<br /> }}<br />
<strong>On success:</strong> {{ qrCodeDialog.data.success }}<br /> <strong>On success:</strong> {{ qrCodeDialog.data.success }}<br />
@ -235,17 +384,37 @@
</p> </p>
{% endraw %} {% endraw %}
<div class="row q-mt-lg q-gutter-sm"> <div class="row q-mt-lg q-gutter-sm">
<q-btn outline color="grey" @click="copyText(qrCodeDialog.data.lnurl, 'LNURL copied to clipboard!')" <q-btn
class="q-ml-sm">Copy LNURL</q-btn> outline
<q-btn outline color="grey" icon="link" color="grey"
@click="copyText(qrCodeDialog.data.pay_url, 'Link copied to clipboard!')"><q-tooltip>Copy sharable @click="copyText(qrCodeDialog.data.lnurl, 'LNURL copied to clipboard!')"
link</q-tooltip> class="q-ml-sm"
>Copy LNURL</q-btn
>
<q-btn
outline
color="grey"
icon="link"
@click="copyText(qrCodeDialog.data.pay_url, 'Link copied to clipboard!')"
><q-tooltip>Copy sharable link</q-tooltip>
</q-btn> </q-btn>
<q-btn outline color="grey" icon="nfc" @click="writeNfcTag(qrCodeDialog.data.lnurl)" <q-btn
:disable="nfcTagWriting"><q-tooltip>Write to NFC</q-tooltip> outline
color="grey"
icon="nfc"
@click="writeNfcTag(qrCodeDialog.data.lnurl)"
:disable="nfcTagWriting"
><q-tooltip>Write to NFC</q-tooltip>
</q-btn> </q-btn>
<q-btn outline color="grey" icon="print" type="a" :href="qrCodeDialog.data.print_url" <q-btn
target="_blank"><q-tooltip>Print</q-tooltip></q-btn> outline
color="grey"
icon="print"
type="a"
:href="qrCodeDialog.data.print_url"
target="_blank"
><q-tooltip>Print</q-tooltip></q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn> <q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
</div> </div>
</q-card> </q-card>

View file

@ -1,7 +1,6 @@
import json import json
from asyncio.log import logger from asyncio.log import logger
from http import HTTPStatus from http import HTTPStatus
from urllib.parse import urlparse
from fastapi import Depends, Query, Request from fastapi import Depends, Query, Request
from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl
@ -89,7 +88,6 @@ async def api_link_create_or_update(
link_id=None, link_id=None,
wallet: WalletTypeInfo = Depends(get_key_type), wallet: WalletTypeInfo = Depends(get_key_type),
): ):
if data.min > data.max: if data.min > data.max:
raise HTTPException( raise HTTPException(
detail="Min is greater than max.", status_code=HTTPStatus.BAD_REQUEST detail="Min is greater than max.", status_code=HTTPStatus.BAD_REQUEST