Wallet system (sats ledger + deposit + spend), HydraProxy fulfillment queue, paid IP-quality scorer (85/100 verified)
This commit is contained in:
173
app.py
173
app.py
@@ -14,12 +14,14 @@ from flask import (Flask, abort, flash, jsonify, redirect, render_template,
|
||||
import db
|
||||
import iproyal
|
||||
import hydraproxy
|
||||
import quality
|
||||
|
||||
CFG = db.CFG
|
||||
app = Flask(__name__)
|
||||
app.secret_key = CFG["secret_key"]
|
||||
app.config["SESSION_COOKIE_SAMESITE"] = "Lax" # mitigate CSRF on modern browsers
|
||||
app.config["SESSION_COOKIE_HTTPONLY"] = True # block JS session theft (XSS)
|
||||
db.init_db() # ensure schema + migrations on gunicorn start
|
||||
|
||||
# simple SQLite-backed rate limiter (shared across gunicorn workers)
|
||||
def _client_ip():
|
||||
@@ -196,20 +198,24 @@ def plans():
|
||||
@app.route("/buy/<int:plan_id>", methods=["POST"])
|
||||
@login_required
|
||||
def buy(plan_id):
|
||||
u = current_user()
|
||||
d = db.get_db()
|
||||
plan = d.execute("SELECT * FROM plans WHERE id=?", (plan_id,)).fetchone()
|
||||
d.close()
|
||||
if not plan:
|
||||
abort(404)
|
||||
order_id = db.random_token(12)
|
||||
price = plan["price_sats"]
|
||||
if db.get_balance_sats(u["id"]) < price:
|
||||
flash("Insufficient wallet balance. Deposit first.", "warn")
|
||||
return redirect(url_for("wallet"))
|
||||
db.add_transaction(u["id"], -price, "gb", f"plan_{plan_id}")
|
||||
d = db.get_db()
|
||||
d.execute("INSERT INTO orders (user_id,plan_id,invoice_id,amount_sats,status,created_at) "
|
||||
"VALUES (?,?,?,?,'pending',?)",
|
||||
(current_user()["id"], plan_id, order_id, plan["price_sats"], int(time.time())))
|
||||
d.execute("UPDATE users SET balance_gb = balance_gb + ?, active=1 WHERE id=?",
|
||||
(plan["gb"], u["id"]))
|
||||
d.commit()
|
||||
d.close()
|
||||
inv = create_invoice(plan["price_sats"], order_id)
|
||||
return redirect(inv["checkoutLink"])
|
||||
flash(f"Added {plan['gb']} GB. Happy scraping.", "ok")
|
||||
return redirect(url_for("dashboard"))
|
||||
|
||||
|
||||
@app.route("/order/<order_id>")
|
||||
@@ -235,6 +241,20 @@ def webhook_btcpay():
|
||||
kind = (payload.get("metadata") or {}).get("kind")
|
||||
if not order_id:
|
||||
return "ok"
|
||||
if kind == "deposit":
|
||||
uid = (payload.get("metadata") or {}).get("user_id")
|
||||
amt = (payload.get("metadata") or {}).get("amount_sats")
|
||||
d = db.get_db()
|
||||
already = d.execute("SELECT 1 FROM transactions WHERE ref=? AND type='deposit'",
|
||||
(order_id,)).fetchone()
|
||||
if not already and uid and amt:
|
||||
d.execute("UPDATE users SET balance_sats = balance_sats + ? WHERE id=?",
|
||||
(int(amt), uid))
|
||||
d.execute("INSERT INTO transactions (user_id, amount_sats, type, ref, ts) "
|
||||
"VALUES (?,?,?,?,?)", (uid, int(amt), "deposit", order_id, int(time.time())))
|
||||
d.commit()
|
||||
d.close()
|
||||
return "ok"
|
||||
d = db.get_db()
|
||||
if kind == "dedicated":
|
||||
row = d.execute("SELECT * FROM dedicated_ips WHERE invoice_id=? AND status='pending_payment'",
|
||||
@@ -265,6 +285,31 @@ def webhook_btcpay():
|
||||
return "ok"
|
||||
|
||||
|
||||
@app.route("/wallet")
|
||||
@login_required
|
||||
def wallet():
|
||||
u = current_user()
|
||||
bal = db.get_balance_sats(u["id"])
|
||||
txs = db.get_transactions(u["id"])
|
||||
return render_template("wallet.html", user=u, balance=bal, txs=txs)
|
||||
|
||||
|
||||
@app.route("/wallet/deposit", methods=["POST"])
|
||||
@login_required
|
||||
def wallet_deposit():
|
||||
u = current_user()
|
||||
try:
|
||||
sats = int(request.form.get("sats", 0))
|
||||
except ValueError:
|
||||
sats = 0
|
||||
if sats < 500:
|
||||
flash("Minimum deposit is 500 sats.", "warn")
|
||||
return redirect(url_for("wallet"))
|
||||
ref = db.random_token(12)
|
||||
inv = create_invoice(sats, ref, meta={"kind": "deposit", "user_id": u["id"], "amount_sats": sats})
|
||||
return redirect(inv["checkoutLink"])
|
||||
|
||||
|
||||
@app.route("/dashboard")
|
||||
@login_required
|
||||
def dashboard():
|
||||
@@ -277,11 +322,15 @@ def dashboard():
|
||||
"WHERE o.user_id=? ORDER BY o.id DESC LIMIT 20", (u["id"],)).fetchall()]
|
||||
sessions = [dict(r) for r in d.execute(
|
||||
"SELECT * FROM usage_log WHERE user_id=? ORDER BY id DESC LIMIT 10", (u["id"],)).fetchall()]
|
||||
hydra = [dict(r) for r in d.execute(
|
||||
"SELECT * FROM hydraproxy_orders WHERE user_id=? ORDER BY id DESC", (u["id"],)).fetchall()]
|
||||
d.close()
|
||||
bal = db.get_balance_sats(u["id"])
|
||||
socks = f"socks5://{u['username']}:{u['proxy_password_plain']}@{PROXY['host']}:{PROXY['socks_port']}"
|
||||
http = f"http://{u['username']}:{u['proxy_password_plain']}@{PROXY['host']}:{PROXY['http_port']}"
|
||||
return render_template("dashboard.html", user=u, used_gb=used / 1e9,
|
||||
socks=socks, http=http, dedicated=deds, orders=orders, sessions=sessions)
|
||||
socks=socks, http=http, dedicated=deds, orders=orders, sessions=sessions,
|
||||
hydra=hydra, balance=bal)
|
||||
|
||||
|
||||
@app.route("/dashboard/test")
|
||||
@@ -410,16 +459,39 @@ def dedicated_buy():
|
||||
flash("Pick a location.", "warn")
|
||||
return redirect(url_for("dedicated"))
|
||||
_, price = DEDICATED_PRODUCTS[pid]["plans"][days]
|
||||
u = current_user()
|
||||
if db.get_balance_sats(u["id"]) < price:
|
||||
flash("Insufficient wallet balance. Deposit first.", "warn")
|
||||
return redirect(url_for("wallet"))
|
||||
oid = db.random_token(12)
|
||||
d = db.get_db()
|
||||
d.execute("INSERT INTO dedicated_ips (user_id,product_id,product_name,plan_days,location_id,"
|
||||
"location_name,invoice_id,price_sats,status,created_at) VALUES (?,?,?,?,?,?,?,?,?,?)",
|
||||
(current_user()["id"], pid, DEDICATED_PRODUCTS[pid]["name"], days, loc_id, loc_name,
|
||||
(u["id"], pid, DEDICATED_PRODUCTS[pid]["name"], days, loc_id, loc_name,
|
||||
oid, price, "pending_payment", int(time.time())))
|
||||
d.commit()
|
||||
d.close()
|
||||
inv = create_invoice(price, oid, meta={"kind": "dedicated"})
|
||||
return redirect(inv["checkoutLink"])
|
||||
db.add_transaction(u["id"], -price, "dedicated", oid)
|
||||
# place the IPRoyal order directly (no webhook needed in wallet model)
|
||||
try:
|
||||
plan_id = DEDICATED_PRODUCTS[pid]["plans"][days][0]
|
||||
o = iproyal.create_order(pid, plan_id, loc_id, 1)
|
||||
oid2 = o.get("id") or o.get("order_id") or (o.get("data") or {}).get("id")
|
||||
d = db.get_db()
|
||||
d.execute("UPDATE dedicated_ips SET status='provisioning', iproyal_order_id=? WHERE invoice_id=?",
|
||||
(oid2, oid))
|
||||
d.commit()
|
||||
d.close()
|
||||
flash("Dedicated IP ordered — provisioning now.", "ok")
|
||||
except Exception as e:
|
||||
app.logger.error("dedicated order failed: %s", e)
|
||||
d = db.get_db()
|
||||
d.execute("UPDATE dedicated_ips SET status='failed' WHERE invoice_id=?", (oid,))
|
||||
d.commit()
|
||||
d.close()
|
||||
db.add_transaction(u["id"], price, "dedicated_refund", oid)
|
||||
flash("Dedicated order failed — wallet refunded.", "warn")
|
||||
return redirect(url_for("dashboard"))
|
||||
|
||||
|
||||
@app.route("/dedicated/poll", methods=["POST"])
|
||||
@@ -504,6 +576,53 @@ def api_hydraproxy_info():
|
||||
return jsonify({"error": str(e)}), 502
|
||||
|
||||
|
||||
@app.route("/hydraproxy/order", methods=["POST"])
|
||||
@login_required
|
||||
def hydraproxy_order():
|
||||
u = current_user()
|
||||
product = request.form.get("product", "").strip()
|
||||
location = request.form.get("location", "").strip()
|
||||
if product not in HYDRA_PRODUCTS:
|
||||
flash("Bad product.", "warn")
|
||||
return redirect(url_for("hydraproxy_page"))
|
||||
price = HYDRA_PRODUCTS[product]["price_sats"]
|
||||
if db.get_balance_sats(u["id"]) < price:
|
||||
flash("Insufficient wallet balance. Deposit first.", "warn")
|
||||
return redirect(url_for("wallet"))
|
||||
d = db.get_db()
|
||||
d.execute("INSERT INTO hydraproxy_orders (user_id, product, location, price_sats, status, created_at) "
|
||||
"VALUES (?,?,?,?,'pending_fulfillment',?)",
|
||||
(u["id"], product, location, price, int(time.time())))
|
||||
d.commit()
|
||||
d.close()
|
||||
db.add_transaction(u["id"], -price, "hydraproxy", product)
|
||||
flash("Order placed — our broker fulfills it shortly and delivers your proxy to the dashboard.", "ok")
|
||||
return redirect(url_for("dashboard"))
|
||||
|
||||
|
||||
@app.route("/quality")
|
||||
@login_required
|
||||
def quality_page():
|
||||
return render_template("quality.html", user=current_user(),
|
||||
price=CFG.get("quality_price_sats", 220))
|
||||
|
||||
|
||||
@app.route("/quality/run", methods=["POST"])
|
||||
@login_required
|
||||
def quality_run():
|
||||
u = current_user()
|
||||
price = CFG.get("quality_price_sats", 220)
|
||||
if db.get_balance_sats(u["id"]) < price:
|
||||
return jsonify({"error": "Insufficient balance. Deposit first."}), 402
|
||||
proxy = request.form.get("proxy", "").strip()
|
||||
if not proxy:
|
||||
proxy = f"socks5h://{u['username']}:{u['proxy_password_plain']}@127.0.0.1:{PROXY['socks_port']}"
|
||||
db.add_transaction(u["id"], -price, "quality", "ip_check")
|
||||
result = quality.score_proxy(proxy)
|
||||
result["charged_sats"] = price
|
||||
return jsonify(result)
|
||||
|
||||
|
||||
# ---------- admin ----------
|
||||
ADMIN_PASSWORD = CFG.get("admin_password", "")
|
||||
|
||||
@@ -530,6 +649,7 @@ def admin():
|
||||
"FROM users ORDER BY id DESC LIMIT 50")]
|
||||
orders = [dict(r) for r in d.execute("SELECT * FROM orders ORDER BY id DESC LIMIT 20")]
|
||||
deds = [dict(r) for r in d.execute("SELECT * FROM dedicated_ips ORDER BY id DESC LIMIT 30")]
|
||||
hydra_orders = [dict(r) for r in d.execute("SELECT * FROM hydraproxy_orders ORDER BY id DESC LIMIT 30")]
|
||||
d.close()
|
||||
try:
|
||||
ipr_balance = iproyal.get_balance()
|
||||
@@ -544,7 +664,7 @@ def admin():
|
||||
ipr_balance < CFG.get("alerts", {}).get("low_balance_threshold", 10.0)
|
||||
return render_template("admin.html", stats=stats, users=users, orders=orders,
|
||||
dedicated=deds, ipr_balance=ipr_balance, low_balance=low_balance,
|
||||
hydra_balance=hydra_balance)
|
||||
hydra_balance=hydra_balance, hydra_orders=hydra_orders)
|
||||
|
||||
|
||||
@app.route("/admin/credit", methods=["POST"])
|
||||
@@ -564,6 +684,37 @@ def admin_credit():
|
||||
return redirect(url_for("admin"))
|
||||
|
||||
|
||||
@app.route("/admin/hydraproxy/fulfill", methods=["POST"])
|
||||
def admin_hydraproxy_fulfill():
|
||||
if not session.get("admin"):
|
||||
abort(403)
|
||||
oid = request.form.get("order_id", "").strip()
|
||||
hydra_order_id = request.form.get("hydra_order_id", "").strip()
|
||||
if not oid or not hydra_order_id:
|
||||
flash("Need our order id and the HydraProxy order id.", "warn")
|
||||
return redirect(url_for("admin"))
|
||||
try:
|
||||
details = hydraproxy.proxy_details(hydra_order_id)
|
||||
pinfo = details.get("proxy_info", {})
|
||||
proxy = details.get("proxy", {})
|
||||
host = proxy.get("hostname") or proxy.get("server_ip")
|
||||
port = proxy.get("port")
|
||||
if isinstance(port, list):
|
||||
port = port[0]
|
||||
user = pinfo.get("username", "")
|
||||
pw = pinfo.get("password", "")
|
||||
d = db.get_db()
|
||||
d.execute("UPDATE hydraproxy_orders SET status='active', hydra_order_id=?, proxy_host=?, "
|
||||
"proxy_port=?, proxy_user=?, proxy_pass=?, fulfilled_at=? WHERE id=?",
|
||||
(hydra_order_id, host, str(port), user, pw, int(time.time()), oid))
|
||||
d.commit()
|
||||
d.close()
|
||||
flash(f"Fulfilled order {oid} with HydraProxy order {hydra_order_id}.", "ok")
|
||||
except Exception as e:
|
||||
flash(f"Fulfill failed: {e}", "warn")
|
||||
return redirect(url_for("admin"))
|
||||
|
||||
|
||||
# ---------- agent discovery ----------
|
||||
@app.route("/llms.txt")
|
||||
def llms_txt():
|
||||
|
||||
Reference in New Issue
Block a user