mirror of
https://github.com/lnbits/lnbits.git
synced 2025-09-29 13:22:37 +02:00
Updated Withdraw extension to handle unique facets
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import List, Optional, Union
|
from typing import List, Optional, Union
|
||||||
|
import shortuuid # type: ignore
|
||||||
from lnbits.db import open_ext_db
|
from lnbits.db import open_ext_db
|
||||||
from lnbits.helpers import urlsafe_short_hash
|
from lnbits.helpers import urlsafe_short_hash
|
||||||
|
|
||||||
@@ -17,6 +17,7 @@ def create_withdraw_link(
|
|||||||
wait_time: int,
|
wait_time: int,
|
||||||
is_unique: bool,
|
is_unique: bool,
|
||||||
) -> WithdrawLink:
|
) -> WithdrawLink:
|
||||||
|
|
||||||
with open_ext_db("withdraw") as db:
|
with open_ext_db("withdraw") as db:
|
||||||
link_id = urlsafe_short_hash()
|
link_id = urlsafe_short_hash()
|
||||||
db.execute(
|
db.execute(
|
||||||
@@ -32,9 +33,10 @@ def create_withdraw_link(
|
|||||||
is_unique,
|
is_unique,
|
||||||
unique_hash,
|
unique_hash,
|
||||||
k1,
|
k1,
|
||||||
open_time
|
open_time,
|
||||||
|
usescsv
|
||||||
)
|
)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
link_id,
|
link_id,
|
||||||
@@ -48,24 +50,36 @@ def create_withdraw_link(
|
|||||||
urlsafe_short_hash(),
|
urlsafe_short_hash(),
|
||||||
urlsafe_short_hash(),
|
urlsafe_short_hash(),
|
||||||
int(datetime.now().timestamp()) + wait_time,
|
int(datetime.now().timestamp()) + wait_time,
|
||||||
|
usescsv,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
return get_withdraw_link(link_id, uses)
|
||||||
return get_withdraw_link(link_id)
|
|
||||||
|
|
||||||
|
|
||||||
def get_withdraw_link(link_id: str) -> Optional[WithdrawLink]:
|
def get_withdraw_link(link_id: str, num=None) -> Optional[WithdrawLink]:
|
||||||
with open_ext_db("withdraw") as db:
|
with open_ext_db("withdraw") as db:
|
||||||
row = db.fetchone("SELECT * FROM withdraw_links WHERE id = ?", (link_id,))
|
row = db.fetchone("SELECT * FROM withdraw_links WHERE id = ?", (link_id,))
|
||||||
|
link = []
|
||||||
return WithdrawLink.from_row(row) if row else None
|
for item in row:
|
||||||
|
link.append(item)
|
||||||
|
tohash = row["id"] + row["unique_hash"] + str(num)
|
||||||
|
link.append(shortuuid.uuid(name=tohash))
|
||||||
|
return WithdrawLink._make(link)
|
||||||
|
|
||||||
|
|
||||||
def get_withdraw_link_by_hash(unique_hash: str) -> Optional[WithdrawLink]:
|
def get_withdraw_link_by_hash(unique_hash: str, num=None) -> Optional[WithdrawLink]:
|
||||||
with open_ext_db("withdraw") as db:
|
with open_ext_db("withdraw") as db:
|
||||||
row = db.fetchone("SELECT * FROM withdraw_links WHERE unique_hash = ?", (unique_hash,))
|
row = db.fetchone("SELECT * FROM withdraw_links WHERE unique_hash = ?", (unique_hash,))
|
||||||
|
link = []
|
||||||
|
for item in row:
|
||||||
|
link.append(item)
|
||||||
|
if not num:
|
||||||
|
link.append("")
|
||||||
|
return WithdrawLink._make(link)
|
||||||
|
tohash = row["id"] + row["unique_hash"] + str(num)
|
||||||
|
link.append(shortuuid.uuid(name=tohash))
|
||||||
|
return WithdrawLink._make(link)
|
||||||
|
|
||||||
return WithdrawLink.from_row(row) if row else None
|
|
||||||
|
|
||||||
|
|
||||||
def get_withdraw_links(wallet_ids: Union[str, List[str]]) -> List[WithdrawLink]:
|
def get_withdraw_links(wallet_ids: Union[str, List[str]]) -> List[WithdrawLink]:
|
||||||
@@ -76,12 +90,14 @@ def get_withdraw_links(wallet_ids: Union[str, List[str]]) -> List[WithdrawLink]:
|
|||||||
q = ",".join(["?"] * len(wallet_ids))
|
q = ",".join(["?"] * len(wallet_ids))
|
||||||
rows = db.fetchall(f"SELECT * FROM withdraw_links WHERE wallet IN ({q})", (*wallet_ids,))
|
rows = db.fetchall(f"SELECT * FROM withdraw_links WHERE wallet IN ({q})", (*wallet_ids,))
|
||||||
|
|
||||||
return [WithdrawLink.from_row(row) for row in rows]
|
links = []
|
||||||
|
for x in rows:
|
||||||
|
links.append(WithdrawLink.from_row(row) for row in rows)
|
||||||
|
return links
|
||||||
|
|
||||||
|
|
||||||
def update_withdraw_link(link_id: str, **kwargs) -> Optional[WithdrawLink]:
|
def update_withdraw_link(link_id: str, **kwargs) -> Optional[WithdrawLink]:
|
||||||
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
|
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
|
||||||
|
|
||||||
with open_ext_db("withdraw") as db:
|
with open_ext_db("withdraw") as db:
|
||||||
db.execute(f"UPDATE withdraw_links SET {q} WHERE id = ?", (*kwargs.values(), link_id))
|
db.execute(f"UPDATE withdraw_links SET {q} WHERE id = ?", (*kwargs.values(), link_id))
|
||||||
row = db.fetchone("SELECT * FROM withdraw_links WHERE id = ?", (link_id,))
|
row = db.fetchone("SELECT * FROM withdraw_links WHERE id = ?", (link_id,))
|
||||||
@@ -92,3 +108,7 @@ def update_withdraw_link(link_id: str, **kwargs) -> Optional[WithdrawLink]:
|
|||||||
def delete_withdraw_link(link_id: str) -> None:
|
def delete_withdraw_link(link_id: str) -> None:
|
||||||
with open_ext_db("withdraw") as db:
|
with open_ext_db("withdraw") as db:
|
||||||
db.execute("DELETE FROM withdraw_links WHERE id = ?", (link_id,))
|
db.execute("DELETE FROM withdraw_links WHERE id = ?", (link_id,))
|
||||||
|
|
||||||
|
def chunks(lst, n):
|
||||||
|
for i in range(0, len(lst), n):
|
||||||
|
yield lst[i:i + n]
|
@@ -5,34 +5,6 @@ from lnbits.helpers import urlsafe_short_hash
|
|||||||
|
|
||||||
|
|
||||||
def m001_initial(db):
|
def m001_initial(db):
|
||||||
"""
|
|
||||||
Initial withdraw table.
|
|
||||||
"""
|
|
||||||
db.execute(
|
|
||||||
"""
|
|
||||||
CREATE TABLE IF NOT EXISTS withdraws (
|
|
||||||
key INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
usr TEXT,
|
|
||||||
wal TEXT,
|
|
||||||
walnme TEXT,
|
|
||||||
adm INTEGER,
|
|
||||||
uni TEXT,
|
|
||||||
tit TEXT,
|
|
||||||
maxamt INTEGER,
|
|
||||||
minamt INTEGER,
|
|
||||||
spent INTEGER,
|
|
||||||
inc INTEGER,
|
|
||||||
tme INTEGER,
|
|
||||||
uniq INTEGER DEFAULT 0,
|
|
||||||
withdrawals TEXT,
|
|
||||||
tmestmp INTEGER,
|
|
||||||
rand TEXT
|
|
||||||
);
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def m002_change_withdraw_table(db):
|
|
||||||
"""
|
"""
|
||||||
Creates an improved withdraw table and migrates the existing data.
|
Creates an improved withdraw table and migrates the existing data.
|
||||||
"""
|
"""
|
||||||
@@ -50,52 +22,13 @@ def m002_change_withdraw_table(db):
|
|||||||
unique_hash TEXT UNIQUE,
|
unique_hash TEXT UNIQUE,
|
||||||
k1 TEXT,
|
k1 TEXT,
|
||||||
open_time INTEGER,
|
open_time INTEGER,
|
||||||
used INTEGER DEFAULT 0
|
used INTEGER DEFAULT 0,
|
||||||
|
usescsv TEXT
|
||||||
);
|
);
|
||||||
"""
|
""")
|
||||||
)
|
|
||||||
db.execute("CREATE INDEX IF NOT EXISTS wallet_idx ON withdraw_links (wallet)")
|
|
||||||
db.execute("CREATE UNIQUE INDEX IF NOT EXISTS unique_hash_idx ON withdraw_links (unique_hash)")
|
|
||||||
|
|
||||||
for row in [list(row) for row in db.fetchall("SELECT * FROM withdraws")]:
|
|
||||||
db.execute(
|
|
||||||
"""
|
|
||||||
INSERT INTO withdraw_links (
|
|
||||||
id,
|
|
||||||
wallet,
|
|
||||||
title,
|
|
||||||
min_withdrawable,
|
|
||||||
max_withdrawable,
|
|
||||||
uses,
|
|
||||||
wait_time,
|
|
||||||
is_unique,
|
|
||||||
unique_hash,
|
|
||||||
k1,
|
|
||||||
open_time,
|
|
||||||
used
|
|
||||||
)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
||||||
""",
|
|
||||||
(
|
|
||||||
row[5], # uni
|
|
||||||
row[2], # wal
|
|
||||||
row[6], # tit
|
|
||||||
row[8], # minamt
|
|
||||||
row[7], # maxamt
|
|
||||||
row[10], # inc
|
|
||||||
row[11], # tme
|
|
||||||
row[12], # uniq
|
|
||||||
urlsafe_short_hash(),
|
|
||||||
urlsafe_short_hash(),
|
|
||||||
int(datetime.now().timestamp()) + row[11],
|
|
||||||
row[9], # spent
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
db.execute("DROP TABLE withdraws")
|
|
||||||
|
|
||||||
|
|
||||||
def migrate():
|
def migrate():
|
||||||
with open_ext_db("withdraw") as db:
|
with open_ext_db("withdraw") as db:
|
||||||
m001_initial(db)
|
m001_initial(db)
|
||||||
m002_change_withdraw_table(db)
|
|
||||||
|
@@ -19,11 +19,14 @@ class WithdrawLink(NamedTuple):
|
|||||||
k1: str
|
k1: str
|
||||||
open_time: int
|
open_time: int
|
||||||
used: int
|
used: int
|
||||||
|
usescsv: str
|
||||||
|
multihash: str
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_row(cls, row: Row) -> "WithdrawLink":
|
def from_row(cls, row: Row) -> "WithdrawLink":
|
||||||
data = dict(row)
|
data = dict(row)
|
||||||
data["is_unique"] = bool(data["is_unique"])
|
data["is_unique"] = bool(data["is_unique"])
|
||||||
|
data["multihash"] = ""
|
||||||
return cls(**data)
|
return cls(**data)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -33,14 +36,20 @@ class WithdrawLink(NamedTuple):
|
|||||||
@property
|
@property
|
||||||
def lnurl(self) -> Lnurl:
|
def lnurl(self) -> Lnurl:
|
||||||
scheme = "https" if FORCE_HTTPS else None
|
scheme = "https" if FORCE_HTTPS else None
|
||||||
url = url_for("withdraw.api_lnurl_response", unique_hash=self.unique_hash, _external=True, _scheme=scheme)
|
print(self.is_unique)
|
||||||
|
if self.is_unique:
|
||||||
|
url = url_for("withdraw.api_lnurl_multi_response", unique_hash=self.unique_hash, id_unique_hash=self.multihash, _external=True, _scheme=scheme)
|
||||||
|
else:
|
||||||
|
url = url_for("withdraw.api_lnurl_response", unique_hash=self.unique_hash, _external=True, _scheme=scheme)
|
||||||
|
|
||||||
return lnurl_encode(url)
|
return lnurl_encode(url)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def lnurl_response(self) -> LnurlWithdrawResponse:
|
def lnurl_response(self) -> LnurlWithdrawResponse:
|
||||||
scheme = "https" if FORCE_HTTPS else None
|
scheme = "https" if FORCE_HTTPS else None
|
||||||
url = url_for("withdraw.api_lnurl_callback", unique_hash=self.unique_hash, _external=True, _scheme=scheme)
|
|
||||||
|
|
||||||
|
url = url_for("withdraw.api_lnurl_callback", unique_hash=self.unique_hash, _external=True, _scheme=scheme)
|
||||||
|
print(url)
|
||||||
return LnurlWithdrawResponse(
|
return LnurlWithdrawResponse(
|
||||||
callback=url,
|
callback=url,
|
||||||
k1=self.k1,
|
k1=self.k1,
|
||||||
|
@@ -1,16 +1,24 @@
|
|||||||
Vue.component(VueQrcode.name, VueQrcode);
|
Vue.component(VueQrcode.name, VueQrcode)
|
||||||
|
|
||||||
var locationPath = [window.location.protocol, '//', window.location.host, window.location.pathname].join('');
|
var locationPath = [
|
||||||
|
window.location.protocol,
|
||||||
|
'//',
|
||||||
|
window.location.host,
|
||||||
|
window.location.pathname
|
||||||
|
].join('')
|
||||||
|
|
||||||
var mapWithdrawLink = function (obj) {
|
var mapWithdrawLink = function (obj) {
|
||||||
obj._data = _.clone(obj);
|
obj._data = _.clone(obj)
|
||||||
obj.date = Quasar.utils.date.formatDate(new Date(obj.time * 1000), 'YYYY-MM-DD HH:mm');
|
obj.date = Quasar.utils.date.formatDate(
|
||||||
obj.min_fsat = new Intl.NumberFormat(LOCALE).format(obj.min_withdrawable);
|
new Date(obj.time * 1000),
|
||||||
obj.max_fsat = new Intl.NumberFormat(LOCALE).format(obj.max_withdrawable);
|
'YYYY-MM-DD HH:mm'
|
||||||
obj.uses_left = obj.uses - obj.used;
|
)
|
||||||
obj.print_url = [locationPath, 'print/', obj.id].join('');
|
obj.min_fsat = new Intl.NumberFormat(LOCALE).format(obj.min_withdrawable)
|
||||||
obj.withdraw_url = [locationPath, obj.id].join('');
|
obj.max_fsat = new Intl.NumberFormat(LOCALE).format(obj.max_withdrawable)
|
||||||
return obj;
|
obj.uses_left = obj.uses - obj.used
|
||||||
|
obj.print_url = [locationPath, 'print/', obj.id].join('')
|
||||||
|
obj.withdraw_url = [locationPath, obj.id].join('')
|
||||||
|
return obj
|
||||||
}
|
}
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
@@ -24,8 +32,18 @@ new Vue({
|
|||||||
columns: [
|
columns: [
|
||||||
{name: 'id', align: 'left', label: 'ID', field: 'id'},
|
{name: 'id', align: 'left', label: 'ID', field: 'id'},
|
||||||
{name: 'title', align: 'left', label: 'Title', field: 'title'},
|
{name: 'title', align: 'left', label: 'Title', field: 'title'},
|
||||||
{name: 'wait_time', align: 'right', label: 'Wait', field: 'wait_time'},
|
{
|
||||||
{name: 'uses_left', align: 'right', label: 'Uses left', field: 'uses_left'},
|
name: 'wait_time',
|
||||||
|
align: 'right',
|
||||||
|
label: 'Wait',
|
||||||
|
field: 'wait_time'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'uses_left',
|
||||||
|
align: 'right',
|
||||||
|
label: 'Uses left',
|
||||||
|
field: 'uses_left'
|
||||||
|
},
|
||||||
{name: 'min', align: 'right', label: 'Min (sat)', field: 'min_fsat'},
|
{name: 'min', align: 'right', label: 'Min (sat)', field: 'min_fsat'},
|
||||||
{name: 'max', align: 'right', label: 'Max (sat)', field: 'max_fsat'}
|
{name: 'max', align: 'right', label: 'Max (sat)', field: 'max_fsat'}
|
||||||
],
|
],
|
||||||
@@ -45,118 +63,146 @@ new Vue({
|
|||||||
show: false,
|
show: false,
|
||||||
data: null
|
data: null
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
sortedWithdrawLinks: function () {
|
sortedWithdrawLinks: function () {
|
||||||
return this.withdrawLinks.sort(function (a, b) {
|
return this.withdrawLinks.sort(function (a, b) {
|
||||||
return b.uses_left - a.uses_left;
|
return b.uses_left - a.uses_left
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getWithdrawLinks: function () {
|
getWithdrawLinks: function () {
|
||||||
var self = this;
|
var self = this
|
||||||
|
|
||||||
LNbits.api.request(
|
LNbits.api
|
||||||
'GET',
|
.request(
|
||||||
'/withdraw/api/v1/links?all_wallets',
|
'GET',
|
||||||
this.g.user.wallets[0].inkey
|
'/withdraw/api/v1/links?all_wallets',
|
||||||
).then(function (response) {
|
this.g.user.wallets[0].inkey
|
||||||
self.withdrawLinks = response.data.map(function (obj) {
|
)
|
||||||
return mapWithdrawLink(obj);
|
.then(function (response) {
|
||||||
});
|
self.withdrawLinks = response.data.map(function (obj) {
|
||||||
}).catch(function (error) {
|
return mapWithdrawLink(obj)
|
||||||
clearInterval(self.checker);
|
})
|
||||||
LNbits.utils.notifyApiError(error);
|
})
|
||||||
});
|
.catch(function (error) {
|
||||||
|
clearInterval(self.checker)
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
})
|
||||||
},
|
},
|
||||||
closeFormDialog: function () {
|
closeFormDialog: function () {
|
||||||
this.formDialog.data = {
|
this.formDialog.data = {
|
||||||
is_unique: false
|
is_unique: false
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
openQrCodeDialog: function (linkId) {
|
openQrCodeDialog: function (linkId) {
|
||||||
var link = _.findWhere(this.withdrawLinks, {id: linkId});
|
var link = _.findWhere(this.withdrawLinks, {id: linkId})
|
||||||
this.qrCodeDialog.data = _.clone(link);
|
this.qrCodeDialog.data = _.clone(link)
|
||||||
this.qrCodeDialog.show = true;
|
this.qrCodeDialog.show = true
|
||||||
},
|
},
|
||||||
openUpdateDialog: function (linkId) {
|
openUpdateDialog: function (linkId) {
|
||||||
var link = _.findWhere(this.withdrawLinks, {id: linkId});
|
var link = _.findWhere(this.withdrawLinks, {id: linkId})
|
||||||
this.formDialog.data = _.clone(link._data);
|
this.formDialog.data = _.clone(link._data)
|
||||||
this.formDialog.show = true;
|
this.formDialog.show = true
|
||||||
},
|
},
|
||||||
sendFormData: function () {
|
sendFormData: function () {
|
||||||
var wallet = _.findWhere(this.g.user.wallets, {id: this.formDialog.data.wallet});
|
var wallet = _.findWhere(this.g.user.wallets, {
|
||||||
var data = _.omit(this.formDialog.data, 'wallet');
|
id: this.formDialog.data.wallet
|
||||||
|
})
|
||||||
|
var data = _.omit(this.formDialog.data, 'wallet')
|
||||||
|
|
||||||
data.wait_time = data.wait_time * {
|
data.wait_time =
|
||||||
'seconds': 1,
|
data.wait_time *
|
||||||
'minutes': 60,
|
{
|
||||||
'hours': 3600
|
seconds: 1,
|
||||||
}[this.formDialog.secondMultiplier];
|
minutes: 60,
|
||||||
|
hours: 3600
|
||||||
|
}[this.formDialog.secondMultiplier]
|
||||||
|
|
||||||
if (data.id) { this.updateWithdrawLink(wallet, data); }
|
if (data.id) {
|
||||||
else { this.createWithdrawLink(wallet, data); }
|
this.updateWithdrawLink(wallet, data)
|
||||||
|
} else {
|
||||||
|
this.createWithdrawLink(wallet, data)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
updateWithdrawLink: function (wallet, data) {
|
updateWithdrawLink: function (wallet, data) {
|
||||||
var self = this;
|
var self = this
|
||||||
|
|
||||||
LNbits.api.request(
|
LNbits.api
|
||||||
'PUT',
|
.request(
|
||||||
'/withdraw/api/v1/links/' + data.id,
|
'PUT',
|
||||||
wallet.adminkey,
|
'/withdraw/api/v1/links/' + data.id,
|
||||||
_.pick(data, 'title', 'min_withdrawable', 'max_withdrawable', 'uses', 'wait_time', 'is_unique')
|
wallet.adminkey,
|
||||||
).then(function (response) {
|
_.pick(
|
||||||
self.withdrawLinks = _.reject(self.withdrawLinks, function (obj) { return obj.id == data.id; });
|
data,
|
||||||
self.withdrawLinks.push(mapWithdrawLink(response.data));
|
'title',
|
||||||
self.formDialog.show = false;
|
'min_withdrawable',
|
||||||
}).catch(function (error) {
|
'max_withdrawable',
|
||||||
LNbits.utils.notifyApiError(error);
|
'uses',
|
||||||
});
|
'wait_time',
|
||||||
|
'is_unique'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(function (response) {
|
||||||
|
self.withdrawLinks = _.reject(self.withdrawLinks, function (obj) {
|
||||||
|
return obj.id == data.id
|
||||||
|
})
|
||||||
|
self.withdrawLinks.push(mapWithdrawLink(response.data))
|
||||||
|
self.formDialog.show = false
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
})
|
||||||
},
|
},
|
||||||
createWithdrawLink: function (wallet, data) {
|
createWithdrawLink: function (wallet, data) {
|
||||||
var self = this;
|
var self = this
|
||||||
|
|
||||||
LNbits.api.request(
|
LNbits.api
|
||||||
'POST',
|
.request('POST', '/withdraw/api/v1/links', wallet.adminkey, data)
|
||||||
'/withdraw/api/v1/links',
|
.then(function (response) {
|
||||||
wallet.adminkey,
|
self.withdrawLinks.push(mapWithdrawLink(response.data))
|
||||||
data
|
self.formDialog.show = false
|
||||||
).then(function (response) {
|
})
|
||||||
self.withdrawLinks.push(mapWithdrawLink(response.data));
|
.catch(function (error) {
|
||||||
self.formDialog.show = false;
|
LNbits.utils.notifyApiError(error)
|
||||||
}).catch(function (error) {
|
})
|
||||||
LNbits.utils.notifyApiError(error);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
deleteWithdrawLink: function (linkId) {
|
deleteWithdrawLink: function (linkId) {
|
||||||
var self = this;
|
var self = this
|
||||||
var link = _.findWhere(this.withdrawLinks, {id: linkId});
|
var link = _.findWhere(this.withdrawLinks, {id: linkId})
|
||||||
|
|
||||||
LNbits.utils.confirmDialog(
|
LNbits.utils
|
||||||
'Are you sure you want to delete this withdraw link?'
|
.confirmDialog('Are you sure you want to delete this withdraw link?')
|
||||||
).onOk(function () {
|
.onOk(function () {
|
||||||
LNbits.api.request(
|
LNbits.api
|
||||||
'DELETE',
|
.request(
|
||||||
'/withdraw/api/v1/links/' + linkId,
|
'DELETE',
|
||||||
_.findWhere(self.g.user.wallets, {id: link.wallet}).adminkey
|
'/withdraw/api/v1/links/' + linkId,
|
||||||
).then(function (response) {
|
_.findWhere(self.g.user.wallets, {id: link.wallet}).adminkey
|
||||||
self.withdrawLinks = _.reject(self.withdrawLinks, function (obj) { return obj.id == linkId; });
|
)
|
||||||
}).catch(function (error) {
|
.then(function (response) {
|
||||||
LNbits.utils.notifyApiError(error);
|
self.withdrawLinks = _.reject(self.withdrawLinks, function (obj) {
|
||||||
});
|
return obj.id == linkId
|
||||||
});
|
})
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
},
|
},
|
||||||
exportCSV: function () {
|
exportCSV: function () {
|
||||||
LNbits.utils.exportCSV(this.paywallsTable.columns, this.paywalls);
|
LNbits.utils.exportCSV(this.paywallsTable.columns, this.paywalls)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created: function () {
|
created: function () {
|
||||||
if (this.g.user.wallets.length) {
|
if (this.g.user.wallets.length) {
|
||||||
var getWithdrawLinks = this.getWithdrawLinks;
|
var getWithdrawLinks = this.getWithdrawLinks
|
||||||
getWithdrawLinks();
|
getWithdrawLinks()
|
||||||
this.checker = setInterval(function () { getWithdrawLinks(); }, 20000);
|
this.checker = setInterval(function () {
|
||||||
|
getWithdrawLinks()
|
||||||
|
}, 20000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
@@ -45,13 +45,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %} {% block scripts %}
|
{% endblock %} {% block scripts %}
|
||||||
|
|
||||||
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
|
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
|
||||||
<script>
|
<script>
|
||||||
Vue.component(VueQrcode.name, VueQrcode)
|
Vue.component(VueQrcode.name, VueQrcode)
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
el: '#vue',
|
el: '#vue',
|
||||||
mixins: [windowMixin]
|
mixins: [windowMixin],
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
theurl: location.host,
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@@ -274,7 +274,6 @@
|
|||||||
>Shareable link</q-btn
|
>Shareable link</q-btn
|
||||||
>
|
>
|
||||||
<q-btn
|
<q-btn
|
||||||
v-if="!qrCodeDialog.data.is_unique"
|
|
||||||
outline
|
outline
|
||||||
color="grey"
|
color="grey"
|
||||||
icon="print"
|
icon="print"
|
||||||
|
@@ -1,58 +1,79 @@
|
|||||||
{% extends "print.html" %} {% block page %}
|
<!DOCTYPE html>
|
||||||
|
{% block page %}
|
||||||
|
|
||||||
<div class="row justify-center">
|
<div class="row justify-center">
|
||||||
<div class="col-12 col-sm-8 col-lg-6 text-center">
|
<div class="col-12 col-sm-8 col-lg-6 text-center" id="vue">
|
||||||
{% for i in range(link.uses) %}
|
{% for page in link %}
|
||||||
<div class="zimbabwe">
|
<page size="A4" id="pdfprint">
|
||||||
<div class="qr">
|
<table style="width: 100%;">
|
||||||
<qrcode value="{{ link.lnurl }}" :options="{width: 150}"></qrcode>
|
{% for threes in page %}
|
||||||
<br /><br />
|
<tr style="height: 37.125mm;">
|
||||||
<strong>{{ SITE_TITLE }}</strong><br />
|
{% for one in threes %}
|
||||||
<strong>{{ link.max_withdrawable }} FREE SATS</strong><br />
|
<td style="width: 52.5mm;">
|
||||||
<small>Scan and follow link<br />or use Lightning wallet</small>
|
<center>
|
||||||
</div>
|
<h3 class="q-mb-md"></h3>
|
||||||
<img src="{{ url_for('static', filename='images/note.jpg') }}" />
|
<qrcode
|
||||||
</div>
|
:value="theurl + '/lnurlwallet?lightning={{one.lnurl}}'"
|
||||||
|
:options="{width: 110}"
|
||||||
|
></qrcode>
|
||||||
|
</center>
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</page>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %} {% block styles %}
|
{% endblock %} {% block styles %}
|
||||||
<style>
|
<style>
|
||||||
.zimbabwe {
|
body {
|
||||||
page-break-inside: avoid;
|
background: rgb(204, 204, 204);
|
||||||
height: 7cm;
|
|
||||||
width: 16cm;
|
|
||||||
position: relative;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
.zimbabwe img {
|
page {
|
||||||
position: absolute;
|
background: white;
|
||||||
top: 0;
|
display: block;
|
||||||
left: 0;
|
margin: 0 auto;
|
||||||
z-index: 0;
|
margin-bottom: 0.5cm;
|
||||||
width: 100%;
|
box-shadow: 0 0 0.5cm rgba(0, 0, 0, 0.5);
|
||||||
}
|
}
|
||||||
.zimbabwe .qr {
|
page[size='A4'] {
|
||||||
position: absolute;
|
width: 21cm;
|
||||||
top: 0;
|
height: 29.7cm;
|
||||||
bottom: 0;
|
}
|
||||||
right: 0;
|
@media print {
|
||||||
z-index: 10;
|
body,
|
||||||
background: rgb(255, 255, 255, 0.7);
|
page {
|
||||||
padding: 10px;
|
margin: 0;
|
||||||
text-align: center;
|
box-shadow: 0;
|
||||||
line-height: 1.1;
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %} {% block scripts %}
|
{% endblock %} {% block scripts %}
|
||||||
|
<script src="{{ url_for('static', filename='vendor/vue@2.6.11/vue.js') }}"></script>
|
||||||
|
<script src="{{ url_for('static', filename='vendor/vue-router@3.1.6/vue-router.js') }}"></script>
|
||||||
|
<script src="{{ url_for('static', filename='vendor/vuex@3.1.3/vuex.js') }}"></script>
|
||||||
|
<script src="{{ url_for('static', filename='vendor/quasar@1.10.4/quasar.umd.js') }}"></script>
|
||||||
|
<script
|
||||||
|
type="text/javascript"
|
||||||
|
src="/static/__bundle__/base.js?a52a989e"
|
||||||
|
></script>
|
||||||
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
|
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
|
||||||
<script>
|
<script>
|
||||||
Vue.component(VueQrcode.name, VueQrcode)
|
Vue.component(VueQrcode.name, VueQrcode)
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
el: '#vue',
|
el: '#vue',
|
||||||
created: function () {
|
mixins: [windowMixin],
|
||||||
window.print()
|
data: function () {
|
||||||
|
return {
|
||||||
|
theurl: location.host,
|
||||||
|
printDialog: {
|
||||||
|
show: true,
|
||||||
|
data: null
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@@ -4,7 +4,7 @@ from http import HTTPStatus
|
|||||||
from lnbits.decorators import check_user_exists, validate_uuids
|
from lnbits.decorators import check_user_exists, validate_uuids
|
||||||
|
|
||||||
from lnbits.extensions.withdraw import withdraw_ext
|
from lnbits.extensions.withdraw import withdraw_ext
|
||||||
from .crud import get_withdraw_link
|
from .crud import get_withdraw_link, chunks
|
||||||
|
|
||||||
|
|
||||||
@withdraw_ext.route("/")
|
@withdraw_ext.route("/")
|
||||||
@@ -17,6 +17,7 @@ def index():
|
|||||||
@withdraw_ext.route("/<link_id>")
|
@withdraw_ext.route("/<link_id>")
|
||||||
def display(link_id):
|
def display(link_id):
|
||||||
link = get_withdraw_link(link_id) or abort(HTTPStatus.NOT_FOUND, "Withdraw link does not exist.")
|
link = get_withdraw_link(link_id) or abort(HTTPStatus.NOT_FOUND, "Withdraw link does not exist.")
|
||||||
|
link = get_withdraw_link(link_id, len(link.usescsv.split(","))) or abort(HTTPStatus.NOT_FOUND, "Withdraw link does not exist.")
|
||||||
|
|
||||||
return render_template("withdraw/display.html", link=link)
|
return render_template("withdraw/display.html", link=link)
|
||||||
|
|
||||||
@@ -24,5 +25,12 @@ def display(link_id):
|
|||||||
@withdraw_ext.route("/print/<link_id>")
|
@withdraw_ext.route("/print/<link_id>")
|
||||||
def print_qr(link_id):
|
def print_qr(link_id):
|
||||||
link = get_withdraw_link(link_id) or abort(HTTPStatus.NOT_FOUND, "Withdraw link does not exist.")
|
link = get_withdraw_link(link_id) or abort(HTTPStatus.NOT_FOUND, "Withdraw link does not exist.")
|
||||||
|
if link.uses == 0:
|
||||||
return render_template("withdraw/print_qr.html", link=link)
|
return render_template("withdraw/print_qr.html", link=link, unique=False)
|
||||||
|
links = []
|
||||||
|
for x in link.usescsv.split(","):
|
||||||
|
linkk = get_withdraw_link(link_id, x) or abort(HTTPStatus.NOT_FOUND, "Withdraw link does not exist.")
|
||||||
|
links.append(linkk)
|
||||||
|
page_link = list(chunks(links, 4))
|
||||||
|
linked = list(chunks(page_link, 8))
|
||||||
|
return render_template("withdraw/print_qr.html", link=linked, unique=True)
|
||||||
|
@@ -2,6 +2,7 @@ from datetime import datetime
|
|||||||
from flask import g, jsonify, request
|
from flask import g, jsonify, request
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl
|
from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl
|
||||||
|
import shortuuid # type: ignore
|
||||||
|
|
||||||
from lnbits.core.crud import get_user
|
from lnbits.core.crud import get_user
|
||||||
from lnbits.core.services import pay_invoice
|
from lnbits.core.services import pay_invoice
|
||||||
@@ -26,10 +27,9 @@ def api_links():
|
|||||||
|
|
||||||
if "all_wallets" in request.args:
|
if "all_wallets" in request.args:
|
||||||
wallet_ids = get_user(g.wallet.user).wallet_ids
|
wallet_ids = get_user(g.wallet.user).wallet_ids
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return (
|
return (
|
||||||
jsonify([{**link._asdict(), **{"lnurl": link.lnurl}} for link in get_withdraw_links(wallet_ids)]),
|
jsonify([{**link._asdict(), **{"lnurl": link.lnurl}} for link in get_withdraw_links(wallet_ids)[0]]),
|
||||||
HTTPStatus.OK,
|
HTTPStatus.OK,
|
||||||
)
|
)
|
||||||
except LnurlInvalidUrl:
|
except LnurlInvalidUrl:
|
||||||
@@ -76,6 +76,15 @@ def api_link_create_or_update(link_id=None):
|
|||||||
if (g.data["max_withdrawable"] * g.data["uses"] * 1000) > g.wallet.balance_msat:
|
if (g.data["max_withdrawable"] * g.data["uses"] * 1000) > g.wallet.balance_msat:
|
||||||
return jsonify({"message": "Insufficient balance."}), HTTPStatus.FORBIDDEN
|
return jsonify({"message": "Insufficient balance."}), HTTPStatus.FORBIDDEN
|
||||||
|
|
||||||
|
usescsv = ""
|
||||||
|
|
||||||
|
for i in range(g.data["uses"]):
|
||||||
|
if g.data["is_unique"]:
|
||||||
|
usescsv += "," + str(i + 1)
|
||||||
|
else:
|
||||||
|
usescsv += "," + str(1)
|
||||||
|
usescsv = usescsv[1:]
|
||||||
|
|
||||||
if link_id:
|
if link_id:
|
||||||
link = get_withdraw_link(link_id)
|
link = get_withdraw_link(link_id)
|
||||||
|
|
||||||
@@ -85,9 +94,9 @@ def api_link_create_or_update(link_id=None):
|
|||||||
if link.wallet != g.wallet.id:
|
if link.wallet != g.wallet.id:
|
||||||
return jsonify({"message": "Not your withdraw link."}), HTTPStatus.FORBIDDEN
|
return jsonify({"message": "Not your withdraw link."}), HTTPStatus.FORBIDDEN
|
||||||
|
|
||||||
link = update_withdraw_link(link_id, **g.data)
|
link = update_withdraw_link(link_id, **g.data, usescsv=usescsv, used=0)
|
||||||
else:
|
else:
|
||||||
link = create_withdraw_link(wallet_id=g.wallet.id, **g.data)
|
link = create_withdraw_link(wallet_id=g.wallet.id, **g.data, usescsv=usescsv)
|
||||||
|
|
||||||
return jsonify({**link._asdict(), **{"lnurl": link.lnurl}}), HTTPStatus.OK if link_id else HTTPStatus.CREATED
|
return jsonify({**link._asdict(), **{"lnurl": link.lnurl}}), HTTPStatus.OK if link_id else HTTPStatus.CREATED
|
||||||
|
|
||||||
@@ -107,6 +116,7 @@ def api_link_delete(link_id):
|
|||||||
|
|
||||||
return "", HTTPStatus.NO_CONTENT
|
return "", HTTPStatus.NO_CONTENT
|
||||||
|
|
||||||
|
#FOR LNURLs WHICH ARE NOT UNIQUE
|
||||||
|
|
||||||
@withdraw_ext.route("/api/v1/lnurl/<unique_hash>", methods=["GET"])
|
@withdraw_ext.route("/api/v1/lnurl/<unique_hash>", methods=["GET"])
|
||||||
def api_lnurl_response(unique_hash):
|
def api_lnurl_response(unique_hash):
|
||||||
@@ -115,8 +125,49 @@ def api_lnurl_response(unique_hash):
|
|||||||
if not link:
|
if not link:
|
||||||
return jsonify({"status": "ERROR", "reason": "LNURL-withdraw not found."}), HTTPStatus.OK
|
return jsonify({"status": "ERROR", "reason": "LNURL-withdraw not found."}), HTTPStatus.OK
|
||||||
|
|
||||||
link = update_withdraw_link(link.id, k1=urlsafe_short_hash())
|
if link.is_unique == 1:
|
||||||
|
return jsonify({"status": "ERROR", "reason": "LNURL-withdraw not found."}), HTTPStatus.OK
|
||||||
|
usescsv = ""
|
||||||
|
for x in range(1, link.uses - link.used):
|
||||||
|
usescsv += "," + str(1)
|
||||||
|
usescsv = usescsv[1:]
|
||||||
|
link = update_withdraw_link(link.id, used=link.used + 1, usescsv=usescsv)
|
||||||
|
|
||||||
|
|
||||||
|
return jsonify(link.lnurl_response.dict()), HTTPStatus.OK
|
||||||
|
|
||||||
|
#FOR LNURLs WHICH ARE UNIQUE
|
||||||
|
|
||||||
|
@withdraw_ext.route("/api/v1/lnurl/<unique_hash>/<id_unique_hash>", methods=["GET"])
|
||||||
|
def api_lnurl_multi_response(unique_hash, id_unique_hash):
|
||||||
|
link = get_withdraw_link_by_hash(unique_hash)
|
||||||
|
|
||||||
|
if not link:
|
||||||
|
return jsonify({"status": "ERROR", "reason": "LNURL-withdraw not found."}), HTTPStatus.OK
|
||||||
|
useslist = link.usescsv.split(",")
|
||||||
|
usescsv = ""
|
||||||
|
hashed = []
|
||||||
|
found = False
|
||||||
|
print(link.uses - link.used)
|
||||||
|
print("link.uses - link.used")
|
||||||
|
print("svfsfv")
|
||||||
|
if link.is_unique == 0:
|
||||||
|
for x in range(link.uses - link.used):
|
||||||
|
usescsv += "," + str(1)
|
||||||
|
else:
|
||||||
|
for x in useslist:
|
||||||
|
tohash = link.id + link.unique_hash + str(x)
|
||||||
|
if id_unique_hash == shortuuid.uuid(name=tohash):
|
||||||
|
found = True
|
||||||
|
else:
|
||||||
|
usescsv += "," + x
|
||||||
|
print(x)
|
||||||
|
print("usescsv: " + usescsv)
|
||||||
|
if not found:
|
||||||
|
return jsonify({"status": "ERROR", "reason": "LNURL-withdraw not found."}), HTTPStatus.OK
|
||||||
|
|
||||||
|
usescsv = usescsv[1:]
|
||||||
|
link = update_withdraw_link(link.id, used=link.used + 1, usescsv=usescsv)
|
||||||
return jsonify(link.lnurl_response.dict()), HTTPStatus.OK
|
return jsonify(link.lnurl_response.dict()), HTTPStatus.OK
|
||||||
|
|
||||||
|
|
||||||
@@ -143,13 +194,9 @@ def api_lnurl_callback(unique_hash):
|
|||||||
pay_invoice(wallet_id=link.wallet, bolt11=payment_request, max_sat=link.max_withdrawable)
|
pay_invoice(wallet_id=link.wallet, bolt11=payment_request, max_sat=link.max_withdrawable)
|
||||||
|
|
||||||
changes = {
|
changes = {
|
||||||
"used": link.used + 1,
|
|
||||||
"open_time": link.wait_time + now,
|
"open_time": link.wait_time + now,
|
||||||
}
|
}
|
||||||
|
|
||||||
if link.is_unique:
|
|
||||||
changes["unique_hash"] = urlsafe_short_hash()
|
|
||||||
|
|
||||||
update_withdraw_link(link.id, **changes)
|
update_withdraw_link(link.id, **changes)
|
||||||
|
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
Reference in New Issue
Block a user