Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ async def create_withdraw_link(
created_at=datetime.now(),
open_time=int(datetime.now().timestamp()) + data.wait_time,
title=data.title,
min_withdrawable=data.min_withdrawable,
max_withdrawable=data.max_withdrawable,
currency=data.currency,
min_withdrawable=int(data.min_withdrawable),
max_withdrawable=int(data.max_withdrawable),
uses=data.uses,
wait_time=data.wait_time,
is_unique=data.is_unique,
Expand Down Expand Up @@ -141,7 +142,6 @@ async def create_hash_check(the_hash: str, lnurl_id: str) -> HashCheck:


async def get_hash_check(the_hash: str, lnurl_id: str) -> HashCheck:

hash_check = await db.fetchone(
"""
SELECT id as hash, lnurl_id as lnurl
Expand Down
13 changes: 13 additions & 0 deletions helpers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from fastapi import Request
from lnbits.utils.exchange_rates import get_fiat_rate_satoshis
from lnurl import Lnurl
from lnurl import encode as lnurl_encode
from shortuuid import uuid
Expand Down Expand Up @@ -26,3 +27,15 @@ def create_lnurl(link: WithdrawLink, req: Request) -> Lnurl:
f"Error creating LNURL with url: `{url!s}`, "
"check your webserver proxy configuration."
) from e


async def min_max_withdrawable(link: WithdrawLink) -> tuple[int, int]:
min_withdrawable = link.min_withdrawable
max_withdrawable = link.max_withdrawable

if link.currency:
rate = await get_fiat_rate_satoshis(link.currency)
min_withdrawable = round(min_withdrawable / 100 * rate)
max_withdrawable = round(max_withdrawable / 100 * rate)

return min_withdrawable, max_withdrawable
10 changes: 10 additions & 0 deletions migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,13 @@ async def m007_add_created_at_timestamp(db):
"ALTER TABLE withdraw.withdraw_link "
f"ADD COLUMN created_at TIMESTAMP DEFAULT {db.timestamp_column_default}"
)


async def m008_add_enabled_column(db):
await db.execute(
"ALTER TABLE withdraw.withdraw_link ADD COLUMN enabled BOOLEAN DEFAULT true;"
)


async def m009_add_currency(db):
await db.execute("ALTER TABLE withdraw.withdraw_link ADD COLUMN currency TEXT;")
8 changes: 6 additions & 2 deletions models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@

class CreateWithdrawData(BaseModel):
title: str = Query(...)
min_withdrawable: int = Query(..., ge=1)
max_withdrawable: int = Query(..., ge=1)
min_withdrawable: float = Query(..., ge=0.01)
max_withdrawable: float = Query(..., ge=0.01)
uses: int = Query(..., ge=1)
wait_time: int = Query(..., ge=1)
is_unique: bool
webhook_url: str = Query(None)
webhook_headers: str = Query(None)
webhook_body: str = Query(None)
custom_url: str = Query(None)
enabled: bool = Query(True)
currency: str = Query(None)


class WithdrawLink(BaseModel):
Expand All @@ -37,6 +39,8 @@ class WithdrawLink(BaseModel):
webhook_body: str = Query(None)
custom_url: str = Query(None)
created_at: datetime
enabled: bool = Query(True)
currency: str = Query(None)
lnurl: str | None = Field(
default=None,
no_database=True,
Expand Down
53 changes: 47 additions & 6 deletions static/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ const mapWithdrawLink = function (obj) {
obj._data = _.clone(obj)
obj.uses_left = obj.uses - obj.used
obj._data.use_custom = Boolean(obj.custom_url)
if (obj.currency) {
obj.min_withdrawable = obj.min_withdrawable / 100
obj.max_withdrawable = obj.max_withdrawable / 100
}
return obj
}

Expand All @@ -14,6 +18,7 @@ window.app = Vue.createApp({
return {
checker: null,
withdrawLinks: [],
currencyOptions: [],
lnurl: '',
withdrawLinksTable: {
columns: [
Expand Down Expand Up @@ -46,12 +51,24 @@ window.app = Vue.createApp({
label: 'Uses left',
field: 'uses_left'
},
{
name: 'currency',
align: 'right',
label: 'Currency',
field: 'currency',
format: function (val) {
return val ? val.toUpperCase() : 'sat'
}
},
{
name: 'max_withdrawable',
align: 'right',
label: 'Max (sat)',
label: `Max`,
field: 'max_withdrawable',
format: LNbits.utils.formatSat
format: (val, row) =>
row.currency
? LNbits.utils.formatCurrency(val, row.currency)
: val
}
],
pagination: {
Expand All @@ -68,7 +85,8 @@ window.app = Vue.createApp({
data: {
is_unique: false,
use_custom: false,
has_webhook: false
has_webhook: false,
enabled: true
}
},
simpleformDialog: {
Expand All @@ -78,7 +96,8 @@ window.app = Vue.createApp({
use_custom: false,
title: 'Vouchers',
min_withdrawable: 0,
wait_time: 1
wait_time: 1,
enabled: true
}
},
qrCodeDialog: {
Expand All @@ -92,6 +111,24 @@ window.app = Vue.createApp({
return this.withdrawLinks.sort(function (a, b) {
return b.uses_left - a.uses_left
})
},
assertMinimumWithdrawable() {
const dialog = this.formDialog.show
? this.formDialog
: this.simpleformDialog
return dialog.data.currency
? dialog.data.min_withdrawable >= 0.01
: dialog.data.min_withdrawable >= 1
},
assertMaximumWithdrawable() {
const dialog = this.formDialog.show
? this.formDialog
: this.simpleformDialog
return dialog.data.currency
? dialog.data.max_withdrawable >= 0.01 &&
dialog.data.max_withdrawable >= dialog.data.min_withdrawable
: dialog.data.max_withdrawable >= 1 &&
dialog.data.max_withdrawable >= dialog.data.min_withdrawable
}
},
methods: {
Expand Down Expand Up @@ -125,13 +162,15 @@ window.app = Vue.createApp({
this.formDialog.data = {
is_unique: false,
use_custom: false,
has_webhook: false
has_webhook: false,
enabled: true
}
},
simplecloseFormDialog() {
this.simpleformDialog.data = {
is_unique: false,
use_custom: false
use_custom: false,
enabled: true
}
},
openQrCodeDialog(linkId) {
Expand Down Expand Up @@ -227,6 +266,7 @@ window.app = Vue.createApp({
})
},
createWithdrawLink(wallet, data) {
console.log(data)
LNbits.api
.request('POST', '/withdraw/api/v1/links', wallet.adminkey, data)
.then(response => {
Expand Down Expand Up @@ -310,5 +350,6 @@ window.app = Vue.createApp({
this.getWithdrawLinks()
this.checker = setInterval(this.getWithdrawLinks, 300000)
}
this.currencyOptions = this.g.allowedCurrencies
}
})
9 changes: 8 additions & 1 deletion templates/withdraw/display.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
<q-badge v-if="spent" color="red" class="q-mb-md"
>Withdraw is spent.</q-badge
>
<q-badge v-if="spent" color="red" class="q-mb-md"
>Withdraw is spent.</q-badge
>
<q-badge v-else-if="!enabled" color="grey" class="q-mb-md"
>Withdraw is disabled.</q-badge
>
<a v-else class="text-secondary" :href="link">
<lnbits-qrcode-lnurl
prefix="lnurlw"
Expand Down Expand Up @@ -57,7 +63,8 @@ <h6 class="text-subtitle1 q-mb-sm q-mt-none">
spent: {{ 'true' if spent else 'false' }},
url: '{{ lnurl_url }}',
lnurl: '',
nfcTagWriting: false
nfcTagWriting: false,
enabled: {{ 'true' if enabled else 'false' }}
}
}
})
Expand Down
90 changes: 75 additions & 15 deletions templates/withdraw/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ <h5 class="text-subtitle1 q-my-none">Withdraw links</h5>
>
<template v-slot:header="props">
<q-tr :props="props">
<q-th auto-width></q-th>
<q-th auto-width></q-th>
<q-th auto-width></q-th>
<q-th
Expand All @@ -51,6 +52,19 @@ <h5 class="text-subtitle1 q-my-none">Withdraw links</h5>
</template>
<template v-slot:body="props">
<q-tr :props="props">
<q-td auto-width>
<q-icon
name="power_settings_new"
:color="props.row.enabled ? 'green' : 'red'"
size="xs"
>
<q-tooltip>
<span
v-text="props.row.enabled ? 'Withdraw link is enabled' : 'Withdraw link is disabled'"
></span>
</q-tooltip>
</q-icon>
</q-td>
<q-td auto-width>
<q-btn
unelevated
Expand Down Expand Up @@ -158,21 +172,33 @@ <h6 class="text-subtitle1 q-my-none">
type="text"
label="Link title *"
></q-input>
<q-select
filled
dense
clearable
v-model="formDialog.data.currency"
:options="currencyOptions"
label="Currency (optional)"
hint="Leave blank for sats"
>
</q-select>
<q-input
filled
dense
v-model.number="formDialog.data.min_withdrawable"
type="number"
min="10"
label="Min withdrawable (sat, at least 10) *"
:min="formDialog.data.currency ? 0.01 : 10"
:step="formDialog.data.currency ? 0.01 : 1"
:label="formDialog.data.currency ? 'Min withdrawable (' + formDialog.data.currency + ', at least 0.01) *' : 'Min withdrawable (sat, at least 10) *'"
></q-input>
<q-input
filled
dense
v-model.number="formDialog.data.max_withdrawable"
type="number"
min="10"
label="Max withdrawable (sat, at least 10) *"
:min="formDialog.data.currency ? 0.01 : 10"
:step="formDialog.data.currency ? 0.01 : 1"
:label="formDialog.data.currency ? 'Max withdrawable (' + formDialog.data.currency + ', at least 0.01) *' : 'Max withdrawable (sat, at least 10) *'"
></q-input>
<q-input
filled
Expand Down Expand Up @@ -238,6 +264,20 @@ <h6 class="text-subtitle1 q-my-none">
hint="Custom data as JSON string, will get posted along with webhook 'body' field."
></q-input>
<q-list>
<q-item tag="label" class="rounded-borders">
<q-item-section avatar>
<q-checkbox
v-model="formDialog.data.enabled"
color="primary"
></q-checkbox>
</q-item-section>
<q-item-section>
<q-item-label>Enable / Disable </q-item-label>
<q-item-label caption
>You can enable or disable these vouchers</q-item-label
>
</q-item-section>
</q-item>
<q-item tag="label" class="rounded-borders">
<q-item-section avatar>
<q-checkbox
Expand Down Expand Up @@ -297,12 +337,8 @@ <h6 class="text-subtitle1 q-my-none">
:disable="
formDialog.data.wallet == null ||
formDialog.data.title == null ||
(formDialog.data.min_withdrawable == null || formDialog.data.min_withdrawable < 1) ||
(
formDialog.data.max_withdrawable == null ||
formDialog.data.max_withdrawable < 1 ||
formDialog.data.max_withdrawable < formDialog.data.min_withdrawable
) ||
!assertMinimumWithdrawable ||
!assertMaximumWithdrawable ||
formDialog.data.uses == null ||
formDialog.data.wait_time == null"
type="submit"
Expand Down Expand Up @@ -332,13 +368,24 @@ <h6 class="text-subtitle1 q-my-none">
label="Wallet *"
>
</q-select>
<q-select
filled
dense
clearable
v-model="simpleformDialog.data.currency"
:options="currencyOptions"
label="Currency (optional)"
hint="Leave blank for sats"
>
</q-select>
<q-input
filled
dense
v-model.number="simpleformDialog.data.max_withdrawable"
type="number"
min="10"
label="Withdraw amount per voucher (sat, at least 10)"
:min="simpleformDialog.data.currency ? 0.01 : 10"
:step="simpleformDialog.data.currency ? 0.01 : 1"
:label="simpleformDialog.data.currency ? 'Withdraw amount per voucher (' + simpleformDialog.data.currency + ', at least 0.01)' : 'Withdraw amount per voucher (sat, at least 10)'"
></q-input>
<q-input
filled
Expand All @@ -350,6 +397,20 @@ <h6 class="text-subtitle1 q-my-none">
label="Number of vouchers"
></q-input>
<q-list>
<q-item tag="label" class="rounded-borders">
<q-item-section avatar>
<q-checkbox
v-model="simpleformDialog.data.enabled"
color="primary"
></q-checkbox>
</q-item-section>
<q-item-section>
<q-item-label>Enable / Disable </q-item-label>
<q-item-label caption
>You can enable or disable these vouchers</q-item-label
>
</q-item-section>
</q-item>
<q-item tag="label" class="rounded-borders">
<q-item-section avatar>
<q-checkbox
Expand Down Expand Up @@ -382,9 +443,8 @@ <h6 class="text-subtitle1 q-my-none">
color="primary"
:disable="
simpleformDialog.data.wallet == null ||

simpleformDialog.data.max_withdrawable == null ||
simpleformDialog.data.max_withdrawable < 1 ||
simpleformDialog.data.max_withdrawable == null ||
!assertMaximumWithdrawable ||
simpleformDialog.data.uses == null"
type="submit"
>Create vouchers</q-btn
Expand Down
Loading