feat: add optional domain field (#120)

* feat: add optional domain field
closes #119
This commit is contained in:
dni ⚡ 2026-01-15 09:20:54 +01:00 committed by GitHub
commit 17135b45ae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 107 additions and 46 deletions

View file

@ -65,6 +65,7 @@ async def create_pay_link(data: CreatePayLinkData) -> PayLink:
created_at=now,
updated_at=now,
disposable=data.disposable if data.disposable is not None else True,
domain=data.domain,
)
await db.insert("lnurlp.pay_links", link)

View file

@ -10,11 +10,16 @@ def parse_nostr_private_key(key: str) -> PrivateKey:
return PrivateKey(bytes.fromhex(key))
def lnurl_encode_link_id(req: Request, link_id: str) -> str:
def lnurl_encode_link(req: Request, link_id: str, domain: str | None = None) -> str:
if domain:
url_str = f"https://{domain}/lnurlp/{link_id}"
return str(lnurl_encode(url_str).bech32)
url = req.url_for("lnurlp.api_lnurl_response", link_id=link_id)
url = url.replace(path=url.path)
url_str = str(url)
if url.netloc.endswith(".onion"):
# change url string scheme to http
url_str = url_str.replace("https://", "http://")
return str(lnurl_encode(url_str).bech32)

View file

@ -35,6 +35,7 @@ class CreatePayLinkData(BaseModel):
username: str | None = Query(None)
zaps: bool | None = Query(False)
disposable: bool | None = Query(True)
domain: str | None = Query(None)
class PayLink(BaseModel):
@ -67,8 +68,25 @@ class PayLink(BaseModel):
success_url: str | None = None
currency: str | None = None
fiat_base_multiplier: int | None = None
disposable: bool
# TODO deprecated, unused in the code, should be deleted from db.
domain: str | None = None
class PublicPayLink(BaseModel):
id: str
username: str | None = None
description: str
min: float
max: float
domain: str | None = None
currency: str | None = None
lnurl: str | None = Field(
default=None,
no_database=True,
deprecated=True,
description=(
"Deprecated: Instead of using this bech32 encoded string, dynamically "
"generate your own static link (lud17/bech32) on the client side. "
"Example: lnurlp://${window.location.hostname}/lnurlp/${paylink_id}"
),
)

View file

@ -2,10 +2,26 @@ window.PageLnurlpPublic = {
template: '#page-lnurlp-public',
data() {
return {
url: ''
url: '',
payLink: null
}
},
methods: {
setUrl(link_id, domain) {
this.url = `https://${domain || window.location.host}/lnurlp/${link_id}`
},
getPayLink() {
this.api
.request('GET', `/lnurlp/api/v1/links/public/${this.$route.params.id}`)
.then(res => {
this.payLink = res.data
this.setUrl(this.payLink.id, this.payLink.domain)
})
.catch(this.utils.notifyApiError)
}
},
created() {
this.url = window.location.origin + '/lnurlp/' + this.$route.params.id
this.setUrl(this.$route.params.id)
this.getPayLink()
}
}

View file

@ -84,6 +84,10 @@ window.PageLnurlp = {
}
},
methods: {
lnaddress(link) {
const domain = link.domain || window.location.host
return `${link.username}@${domain}`
},
mapPayLink(obj) {
const locationPath = [
window.location.protocol,
@ -140,11 +144,13 @@ window.PageLnurlp = {
(link.success_url ? ' and URL "' + link.success_url + '"' : '')
: 'do nothing',
lnurl: link.lnurl,
domain: link.domain,
pay_url: link.pay_url,
print_url: link.print_url,
username: link.username
}
this.activeUrl = window.location.origin + '/lnurlp/' + link.id
const domain = link.domain || window.location.host
this.activeUrl = `https://${domain}/lnurlp//${link.id}`
this.qrCodeDialog.show = true
},
openUpdateDialog(linkId) {

View file

@ -36,7 +36,6 @@
<span v-text="col.label"></span>
</q-th>
<q-th auto-width></q-th>
<q-th auto-width></q-th>
</q-tr>
</template>
<template v-slot:body="props">
@ -55,7 +54,6 @@
><q-tooltip>Shareable Page</q-tooltip></q-btn
>
<q-btn
unelevated
dense
size="xs"
icon="visibility"
@ -64,6 +62,27 @@
@click="openQrCodeDialog(props.row.id)"
><q-tooltip>View Link</q-tooltip></q-btn
>
<q-btn
flat
dense
size="xs"
@click="openUpdateDialog(props.row.id)"
icon="edit"
color="light-blue"
class="q-ml-sm"
>
<q-tooltip>Edit</q-tooltip>
</q-btn>
<q-btn
flat
dense
size="xs"
@click="deletePayLink(props.row.id)"
icon="cancel"
color="pink"
class="q-ml-sm"
><q-tooltip>Delete</q-tooltip></q-btn
>
</q-td>
<q-td
v-for="col in props.cols"
@ -104,27 +123,6 @@
</q-tooltip>
</q-icon>
</q-td>
<q-td auto-width>
<q-btn
flat
dense
size="xs"
@click="openUpdateDialog(props.row.id)"
icon="edit"
color="light-blue"
>
<q-tooltip>Edit</q-tooltip>
</q-btn>
<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-tr>
</template>
</q-table>
@ -402,10 +400,17 @@
"
/>
</div>
<div class="col" style="margin-top: 10px">
<span class="label">
&nbsp;@&nbsp;<span v-text="domain"></span>
</span>
<div class="col" style="flex: 0 0 auto; margin-top: 10px">
<span class="label"> &nbsp;@&nbsp; </span>
</div>
<div class="col">
<q-input
filled
dense
v-model.trim="formDialog.data.domain"
type="text"
:label="domain"
/>
</div>
</div>
<div class="row q-col-gutter-sm q-mx-sm">
@ -642,7 +647,7 @@
<span v-text="qrCodeDialog.data.success"></span><br />
<span v-if="qrCodeDialog.data.username">
<strong>Lightning Address: </strong>
<span v-text="qrCodeDialog.data.username + '@' + domain"></span>
<span v-text="lnaddress(qrCodeDialog.data)"></span>
<br />
</span>
</p>

View file

@ -23,15 +23,15 @@ from .crud import (
update_lnurlp_settings,
update_pay_link,
)
from .helpers import lnurl_encode_link_id, parse_nostr_private_key
from .models import CreatePayLinkData, LnurlpSettings, PayLink
from .helpers import lnurl_encode_link, parse_nostr_private_key
from .models import CreatePayLinkData, LnurlpSettings, PayLink, PublicPayLink
lnurlp_api_router = APIRouter()
def check_lnurl_encode(req: Request, link_id: str) -> str:
def check_lnurl_encode(req: Request, link: PayLink) -> str:
try:
return lnurl_encode_link_id(req, link_id)
return lnurl_encode_link(req, link.id, link.domain)
except InvalidUrl as exc:
raise HTTPException(
detail=(
@ -60,11 +60,11 @@ async def api_links(
links = await get_pay_links(wallet_ids)
for link in links:
link.lnurl = check_lnurl_encode(req=req, link_id=link.id)
link.lnurl = check_lnurl_encode(req, link)
return links
@lnurlp_api_router.get("/api/v1/links/{link_id}", status_code=HTTPStatus.OK)
@lnurlp_api_router.get("/api/v1/links/{link_id}")
async def api_link_retrieve(
req: Request, link_id: str, key_info: WalletTypeInfo = Depends(require_invoice_key)
) -> PayLink:
@ -85,7 +85,18 @@ async def api_link_retrieve(
detail="Not your pay link.", status_code=HTTPStatus.FORBIDDEN
)
link.lnurl = check_lnurl_encode(req, link.id)
link.lnurl = check_lnurl_encode(req, link)
return link
@lnurlp_api_router.get("/api/v1/links/public/{link_id}", response_model=PublicPayLink)
async def api_link_public_retrieve(req: Request, link_id: str) -> PayLink:
link = await get_pay_link(link_id)
if not link:
raise HTTPException(
detail="Pay link does not exist.", status_code=HTTPStatus.NOT_FOUND
)
link.lnurl = lnurl_encode_link(req, link.id, link.domain)
return link
@ -168,7 +179,7 @@ async def api_link_create_or_update(
detail="Wallet does not exist.", status_code=HTTPStatus.FORBIDDEN
)
# admins are allowed to create/edit paylinks beloging to regular users
# admins are allowed to create/edit paylinks belonging to regular users
user = await get_user(key_info.wallet.user)
admin_user = user.admin if user else False
if not admin_user and new_wallet.user != key_info.wallet.user:
@ -197,8 +208,7 @@ async def api_link_create_or_update(
link = await create_pay_link(data)
link.lnurl = check_lnurl_encode(req=req, link_id=link.id)
link.lnurl = check_lnurl_encode(req, link)
return link

View file

@ -99,7 +99,7 @@ async def api_lnurl_callback(
extra["nostr"] = nostr # put it here for later publishing in tasks.py
if link.username:
identifier = f"{link.username}@{request.url.netloc}"
identifier = f"{link.username}@{link.domain or request.url.netloc}"
text = f"Payment to {link.username}"
_metadata = [["text/plain", text], ["text/identifier", identifier]]
extra["lnaddress"] = identifier
@ -173,7 +173,7 @@ async def api_lnurl_response(
callback_url = parse_obj_as(CallbackUrl, str(url))
if link.username:
identifier = f"{link.username}@{request.url.netloc}"
identifier = f"{link.username}@{link.domain or request.url.netloc}"
text = f"Payment to {link.username}"
metadata = [["text/plain", text], ["text/identifier", identifier]]
else: