refactor(withdraw): migrate extension to Vue

This commit is contained in:
Eneko Illarramendi
2020-04-16 20:58:16 +02:00
parent dd23b20090
commit a834a64319
15 changed files with 896 additions and 1638 deletions
+124 -177
View File
@@ -1,201 +1,148 @@
import uuid
import json
import requests
from flask import jsonify, request, url_for
from lnurl import LnurlWithdrawResponse, encode as lnurl_encode
from datetime import datetime
from flask import g, jsonify, request
from lnbits.core.crud import get_user, get_wallet
from lnbits.core.services import pay_invoice
from lnbits.decorators import api_check_wallet_macaroon, api_validate_post_request
from lnbits.helpers import urlsafe_short_hash, Status
from lnbits.db import open_ext_db, open_db
from lnbits.extensions.withdraw import withdraw_ext
from .crud import (
create_withdraw_link,
get_withdraw_link,
get_withdraw_link_by_hash,
get_withdraw_links,
update_withdraw_link,
delete_withdraw_link,
)
@withdraw_ext.route("/api/v1/lnurlencode/<urlstr>/<parstr>", methods=["GET"])
def api_lnurlencode(urlstr, parstr):
"""Returns encoded LNURL if web url and parameter gieven."""
@withdraw_ext.route("/api/v1/links", methods=["GET"])
@api_check_wallet_macaroon(key_type="invoice")
def api_links():
wallet_ids = [g.wallet.id]
if not urlstr:
return jsonify({"status": "FALSE"}), 200
if "all_wallets" in request.args:
wallet_ids = get_user(g.wallet.user).wallet_ids
with open_ext_db("withdraw") as withdraw_ext_db:
user_fau = withdraw_ext_db.fetchall("SELECT * FROM withdraws WHERE uni = ?", (parstr,))
randar = user_fau[0][15].split(",")
print(randar)
# randar = randar[:-1]
# If "Unique links" selected get correct rand, if not there is only one rand
if user_fau[0][12] > 0:
rand = randar[user_fau[0][10] - 1]
else:
rand = randar[0]
url = url_for("withdraw.api_lnurlfetch", _external=True, urlstr=urlstr, parstr=parstr, rand=rand)
if "onion" in url:
return jsonify({"status": "TRUE", "lnurl": lnurl_encode(url)}), 200
print(url)
return jsonify({"status": "TRUE", "lnurl": lnurl_encode(url.replace("http://", "https://"))}), 200
return jsonify([{**link._asdict(), **{"lnurl": link.lnurl}} for link in get_withdraw_links(wallet_ids)]), Status.OK
@withdraw_ext.route("/api/v1/links/<link_id>", methods=["GET"])
@api_check_wallet_macaroon(key_type="invoice")
def api_link_retrieve(link_id):
link = get_withdraw_link(link_id)
@withdraw_ext.route("/api/v1/lnurlfetch/<urlstr>/<parstr>/<rand>", methods=["GET"])
def api_lnurlfetch(parstr, urlstr, rand):
"""Returns LNURL json."""
if not link:
return jsonify({"message": "Withdraw link does not exist."}), Status.NOT_FOUND
if not parstr:
return jsonify({"status": "FALSE", "ERROR": "NO WALL ID"}), 200
if link.wallet != g.wallet.id:
return jsonify({"message": "Not your withdraw link."}), Status.FORBIDDEN
if not urlstr:
return jsonify({"status": "FALSE", "ERROR": "NO URL"}), 200
return jsonify({**link._asdict(), **{"lnurl": link.lnurl}}), Status.OK
with open_ext_db("withdraw") as withdraw_ext_db:
user_fau = withdraw_ext_db.fetchall("SELECT * FROM withdraws WHERE uni = ?", (parstr,))
k1str = uuid.uuid4().hex
withdraw_ext_db.execute("UPDATE withdraws SET withdrawals = ? WHERE uni = ?", (k1str, parstr,))
precallback = url_for("withdraw.api_lnurlwithdraw", _external=True, rand=rand)
if "onion" in precallback:
print(precallback)
@withdraw_ext.route("/api/v1/links", methods=["POST"])
@withdraw_ext.route("/api/v1/links/<link_id>", methods=["PUT"])
@api_check_wallet_macaroon(key_type="invoice")
@api_validate_post_request(
schema={
"title": {"type": "string", "empty": False, "required": True},
"min_withdrawable": {"type": "integer", "min": 1, "required": True},
"max_withdrawable": {"type": "integer", "min": 1, "required": True},
"uses": {"type": "integer", "min": 1, "required": True},
"wait_time": {"type": "integer", "min": 1, "required": True},
"is_unique": {"type": "boolean", "required": True},
}
)
def api_link_create(link_id=None):
if g.data["max_withdrawable"] < g.data["min_withdrawable"]:
return jsonify({"message": "`max_withdrawable` needs to be at least `min_withdrawable`."}), Status.BAD_REQUEST
if (g.data["max_withdrawable"] * g.data["uses"] * 1000) > g.wallet.balance_msat:
return jsonify({"message": "Insufficient balance."}), Status.FORBIDDEN
if link_id:
link = get_withdraw_link(link_id)
if not link:
return jsonify({"message": "Withdraw link does not exist."}), Status.NOT_FOUND
if link.wallet != g.wallet.id:
return jsonify({"message": "Not your withdraw link."}), Status.FORBIDDEN
link = update_withdraw_link(link_id, **g.data)
else:
precallback = url_for("withdraw.api_lnurlwithdraw", _external=True, rand=rand).replace("http://", "https://")
res = LnurlWithdrawResponse(
callback=precallback,
k1=k1str,
min_withdrawable=user_fau[0][8] * 1000,
max_withdrawable=user_fau[0][7] * 1000,
default_description="LNbits LNURL withdraw",
)
link = create_withdraw_link(wallet_id=g.wallet.id, **g.data)
return res.json(), 200
return jsonify({**link._asdict(), **{"lnurl": link.lnurl}}), Status.OK if link_id else Status.CREATED
@withdraw_ext.route("/api/v1/lnurlwithdraw/<rand>/", methods=["GET"])
def api_lnurlwithdraw(rand):
"""Pays invoice if passed k1 invoice and rand."""
@withdraw_ext.route("/api/v1/links/<link_id>", methods=["DELETE"])
@api_check_wallet_macaroon(key_type="invoice")
def api_link_delete(link_id):
link = get_withdraw_link(link_id)
k1 = request.args.get("k1")
pr = request.args.get("pr")
if not link:
return jsonify({"message": "Withdraw link does not exist."}), Status.NOT_FOUND
if not k1:
return jsonify({"status": "FALSE", "ERROR": "NO k1"}), 200
if link.wallet != g.wallet.id:
return jsonify({"message": "Not your withdraw link."}), Status.FORBIDDEN
if not pr:
return jsonify({"status": "FALSE", "ERROR": "NO PR"}), 200
delete_withdraw_link(link_id)
with open_ext_db("withdraw") as withdraw_ext_db:
user_fau = withdraw_ext_db.fetchall("SELECT * FROM withdraws WHERE withdrawals = ?", (k1,))
if not user_fau:
return jsonify({"status": "ERROR", "reason": "NO AUTH"}), 400
if user_fau[0][10] < 1:
return jsonify({"status": "ERROR", "reason": "withdraw SPENT"}), 400
# Check withdraw time
dt = datetime.now()
seconds = dt.timestamp()
secspast = seconds - user_fau[0][14]
if secspast < user_fau[0][11]:
return jsonify({"status": "ERROR", "reason": "WAIT " + str(int(user_fau[0][11] - secspast)) + "s"}), 400
randar = user_fau[0][15].split(",")
if rand not in randar:
print("huhhh")
return jsonify({"status": "ERROR", "reason": "BAD AUTH"}), 400
if len(randar) > 2:
randar.remove(rand)
randstr = ",".join(randar)
# Update time and increments
upinc = int(user_fau[0][10]) - 1
withdraw_ext_db.execute(
"UPDATE withdraws SET inc = ?, rand = ?, tmestmp = ? WHERE withdrawals = ?", (upinc, randstr, seconds, k1,)
)
header = {"Content-Type": "application/json", "Grpc-Metadata-macaroon": str(user_fau[0][4])}
data = {"payment_request": pr}
# this works locally but not being served over host, bug, needs fixing
# r = requests.post(url="https://lnbits.com/api/v1/channels/transactions", headers=header, data=json.dumps(data))
r = requests.post(url=url_for("api_transactions", _external=True), headers=header, data=json.dumps(data))
r_json = r.json()
if "ERROR" in r_json:
return jsonify({"status": "ERROR", "reason": r_json["ERROR"]}), 400
with open_ext_db("withdraw") as withdraw_ext_db:
user_fau = withdraw_ext_db.fetchall("SELECT * FROM withdraws WHERE withdrawals = ?", (k1,))
return jsonify({"status": "OK"}), 200
@withdraw_ext.route("/api/v1/lnurlmaker", methods=["GET","POST"])
def api_lnurlmaker():
if request.headers["Content-Type"] != "application/json":
return jsonify({"ERROR": "MUST BE JSON"}), 400
with open_db() as db:
wallet = db.fetchall(
"SELECT * FROM wallets WHERE adminkey = ?",
(request.headers["Grpc-Metadata-macaroon"],),
)
if not wallet:
return jsonify({"ERROR": "NO KEY"}), 200
balance = db.fetchone("SELECT balance/1000 FROM balances WHERE wallet = ?", (wallet[0][0],))[0]
print(balance)
postedjson = request.json
print(postedjson["amount"])
if balance < int(postedjson["amount"]):
return jsonify({"ERROR": "NOT ENOUGH FUNDS"}), 200
uni = uuid.uuid4().hex
rand = uuid.uuid4().hex[0:5]
with open_ext_db("withdraw") as withdraw_ext_db:
withdraw_ext_db.execute(
"""
INSERT OR IGNORE INTO withdraws
(usr, wal, walnme, adm, uni, tit, maxamt, minamt, spent, inc, tme, uniq, withdrawals, tmestmp, rand)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
wallet[0][2],
wallet[0][0],
wallet[0][1],
wallet[0][3],
uni,
postedjson["memo"],
postedjson["amount"],
postedjson["amount"],
0,
1,
1,
0,
0,
1,
rand,
),
)
user_fau = withdraw_ext_db.fetchone("SELECT * FROM withdraws WHERE uni = ?", (uni,))
if not user_fau:
return jsonify({"ERROR": "WITHDRAW NOT MADE"}), 401
url = url_for("withdraw.api_lnurlfetch", _external=True, urlstr=request.host, parstr=uni, rand=rand)
if "onion" in url:
return jsonify({"status": "TRUE", "lnurl": lnurl_encode(url)}), 200
print(url)
return jsonify({"status": "TRUE", "lnurl": lnurl_encode(url.replace("http://", "https://"))}), 200
return "", Status.NO_CONTENT
@withdraw_ext.route("/api/v1/lnurl/<unique_hash>", methods=["GET"])
def api_lnurl_response(unique_hash):
link = get_withdraw_link_by_hash(unique_hash)
if not link:
return jsonify({"status": "ERROR", "reason": "LNURL-withdraw not found."}), Status.OK
link = update_withdraw_link(link.id, k1=urlsafe_short_hash())
return jsonify(link.lnurl_response.dict()), Status.OK
@withdraw_ext.route("/api/v1/lnurl/cb/<unique_hash>", methods=["GET"])
def api_lnurl_callback(unique_hash):
link = get_withdraw_link_by_hash(unique_hash)
k1 = request.args.get("k1", type=str)
payment_request = request.args.get("pr", type=str)
now = int(datetime.now().timestamp())
if not link:
return jsonify({"status": "ERROR", "reason": "LNURL-withdraw not found."}), Status.OK
if link.is_spent:
return jsonify({"status": "ERROR", "reason": "Withdraw is spent."}), Status.OK
if link.k1 != k1:
return jsonify({"status": "ERROR", "reason": "Bad request."}), Status.OK
if now < link.open_time:
return jsonify({"status": "ERROR", "reason": f"Wait {link.open_time - now} seconds."}), Status.OK
try:
pay_invoice(wallet=get_wallet(link.wallet), bolt11=payment_request, max_sat=link.max_withdrawable)
changes = {
"used": link.used + 1,
"open_time": link.wait_time + now,
}
if link.is_unique:
changes["unique_hash"] = urlsafe_short_hash()
update_withdraw_link(link.id, **changes)
except ValueError as e:
return jsonify({"status": "ERROR", "reason": str(e)}), Status.OK
except PermissionError:
return jsonify({"status": "ERROR", "reason": "Withdraw link is empty."}), Status.OK
except Exception as e:
return jsonify({"status": "ERROR", "reason": str(e)}), Status.OK
return jsonify({"status": "OK"}), Status.OK