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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*.env
*.env.example
*.env.local
*.env.staging
215 changes: 152 additions & 63 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

import os
import flask
from flask import request, jsonify
from flask import request, jsonify, render_template
from flask_cors import CORS
import sqlite3

app = flask.Flask(__name__)
CORS(app, resources={r"/api/*": {"origins": "*"}})
#app.config["DEBUG"] = True
# app.config["DEBUG"] = True


def dict_factory(cursor, row):
d = {}
Expand All @@ -17,86 +18,172 @@ def dict_factory(cursor, row):
return d


@app.route('/', methods=['GET'])
@app.route("/", methods=["GET"])
def home():
return '''<h1>Banano address API</h1>
<p>Banano address API by Kirby. Work in progress</p>
<a href="/api/v1/resources/addresses?address=ban_31dhbgirwzd3ce7naor6o94woefws9hpxu4q8uxm1bz98w89zqpfks5rk3ad">/api/v1/resources/addresses?address=ban_31dhbgirwzd3ce7naor6o94woefws9hpxu4q8uxm1bz98w89zqpfks5rk3ad</a>
<br>
<a href="/api/v1/resources/addresses?type=Distribution">/api/v1/resources/addresses?type=Distribution</a>
<br>
<a href="/api/v1/resources/addresses?type=Exchange">/api/v1/resources/addresses?type=Exchange</a>
<br>
<a href="/api/v1/resources/addresses?type=Gambling">/api/v1/resources/addresses?type=Gambling</a>
<br>
<a href="/api/v1/resources/addresses/all">/api/v1/resources/addresses/all</a>
<br>
<a href="/api/v1/resources/addresses?illicit=1">/api/v1/resources/addresses?illicit=1</a>
<br>
<br>
<a href="/api/v1/resources/intermediaries/all">/api/v1/resources/intermediaries/all</a>
<br>
<a href="/api/v1/resources/intermediaries?address=ban_113sf8z98qqihjis3m95gkb35z7ckfakbrtrmbtawf37hc6ixx7rtz53bq8o">/api/v1/resources/intermediaries?address=ban_113sf8z98qqihjis3m95gkb35z7ckfakbrtrmbtawf37hc6ixx7rtz53bq8o</a>
<br>
<a href="/api/v1/resources/intermediaries?address=ban_113sf8z98qqihjis3m95gkb35z7ckfakbrtrmbtawf37hc6ixx7rtz53bq8o&address=ban_111c3xcromzadabqud7yer7ptzrocecsum9d9t8feoz81zdbu7gh63hnk7n4">/api/v1/resources/intermediaries?address=ban_113sf8z98qqihjis3m95gkb35z7ckfakbrtrmbtawf37hc6ixx7rtz53bq8o&address=ban_111c3xcromzadabqud7yer7ptzrocecsum9d9t8feoz81zdbu7gh63hnk7n4</a>
<br>
<a href="/api/v1/resources/intermediaries?service=ban_1gooj14qko1u6md87aga9c53nf4iphyt1ua7x3kq1wnkdh49u5mndqygbr1q">/api/v1/resources/intermediaries?service=ban_1gooj14qko1u6md87aga9c53nf4iphyt1ua7x3kq1wnkdh49u5mndqygbr1q</a>
<br>
<a href="/api/v1/resources/intermediaries?service=ban_1gooj14qko1u6md87aga9c53nf4iphyt1ua7x3kq1wnkdh49u5mndqygbr1q&service=ban_1oaocnrcaystcdtaae6woh381wftyg4k7bespu19m5w18ze699refhyzu6bo">/api/v1/resources/intermediaries?service=ban_1gooj14qko1u6md87aga9c53nf4iphyt1ua7x3kq1wnkdh49u5mndqygbr1q&service=ban_1oaocnrcaystcdtaae6woh381wftyg4k7bespu19m5w18ze699refhyzu6bo</a>
<br>
<a href="/api/v1/resources/intermediaries?service=ban_1oaocnrcaystcdtaae6woh381wftyg4k7bespu19m5w18ze699refhyzu6bo&address=ban_3fhhttfufikxikuxj5j6gndu5dokupa7j7t7dq5qkat19fm3mo84spe8krhz">/api/v1/resources/intermediaries?service=ban_1oaocnrcaystcdtaae6woh381wftyg4k7bespu19m5w18ze699refhyzu6bo&address=ban_3fhhttfufikxikuxj5j6gndu5dokupa7j7t7dq5qkat19fm3mo84spe8krhz</a>
<br>
<a href="/api/v1/resources/intermediaries/status">/api/v1/resources/intermediaries/status</a>
<br>
<br>
<a href="https://github.com/Kirby1997/BananoAddressAPI">Source on Github</a>
'''


@app.route('/api/v1/resources/addresses/all', methods=['GET'])
return render_template("home.html")

@app.route("/api/v1/resources/addresses/all", methods=["GET"])
def known_all():
conn = sqlite3.connect(os.getcwd() + "/addresses.sqlite")
conn.row_factory = dict_factory
cur = conn.cursor()
all_addresses = cur.execute('SELECT * FROM addresses;').fetchall()
all_addresses = cur.execute("SELECT * FROM addresses;").fetchall()

return jsonify(all_addresses)



@app.errorhandler(404)
def page_not_found(e):
return "<h1>404</h1><p>The resource could not be found.</p>", 404


@app.route('/api/v1/resources/addresses', methods=['GET'])
@app.route("/query-address", methods=["GET"])
def update_address_form():
return render_template("update_form.html")

@app.route("/manage-records", methods=["GET"])
def manage_addresses():
return render_template("manage_records.html")

@app.route("/api/v1/resources/addresses/update", methods=["PUT"])
def update_address():
update_password = os.environ.get("UPDATE_PASSWORD", "default_password")
request_data = request.get_json()

# Check password
if request_data.get("password") != update_password:
return jsonify({"error": "Unauthorized: Incorrect password"}), 401

address_id = request_data.get("id")
new_data = request_data.get("new_data")

if not all([address_id, new_data]):
return jsonify({"error": "Missing data"}), 400

conn = sqlite3.connect(os.getcwd() + "/addresses.sqlite")
cursor = conn.cursor()

try:
# Special handling for updating address itself
if "address" in new_data:
new_address = new_data.pop("address")
cursor.execute(
"UPDATE addresses SET address = ? WHERE address = ?",
(new_address, address_id),
)

# Update other fields
updates = ", ".join([f"{key} = ?" for key in new_data.keys()])
parameters = list(new_data.values()) + [
address_id if "address" not in new_data else new_address
]

if updates:
cursor.execute(
f"UPDATE addresses SET {updates} WHERE address = ?", parameters
)

conn.commit()
except sqlite3.Error as e:
conn.rollback()
return jsonify({"error": str(e)}), 500
finally:
conn.close()

return jsonify({"success": True}), 200


@app.route("/api/v1/resources/addresses/create", methods=["POST"])
def create_address():
update_password = os.environ.get("UPDATE_PASSWORD", "default_password")
request_data = request.get_json()

# Check password
if request_data.get("password") != update_password:
return jsonify({"error": "Unauthorized: Incorrect password"}), 401

new_address = request_data.get("address")
alias = request_data.get("alias")
owner = request_data.get("owner")
address_type = request_data.get("type")

if not all([new_address, alias, owner, address_type]):
return jsonify({"error": "Missing data"}), 400

conn = sqlite3.connect(os.getcwd() + "/addresses.sqlite")
cursor = conn.cursor()

try:
cursor.execute(
"INSERT INTO addresses (address, alias, owner, type) VALUES (?, ?, ?, ?)",
(new_address, alias, owner, address_type),
)
conn.commit()
except sqlite3.Error as e:
conn.rollback()
return jsonify({"error": str(e)}), 500
finally:
conn.close()

return jsonify({"success": True}), 200


@app.route("/api/v1/resources/addresses/delete", methods=["DELETE"])
def delete_address():
update_password = os.environ.get("UPDATE_PASSWORD", "default_password")
request_data = request.get_json()

# Check password
if request_data.get("password") != update_password:
return jsonify({"error": "Unauthorized: Incorrect password"}), 401

address_id = request_data.get("id")

if not address_id:
return jsonify({"error": "Missing data"}), 400

conn = sqlite3.connect(os.getcwd() + "/addresses.sqlite")
cursor = conn.cursor()

try:
cursor.execute("DELETE FROM addresses WHERE address = ?", (address_id,))
conn.commit()
except sqlite3.Error as e:
conn.rollback()
return jsonify({"error": str(e)}), 500
finally:
conn.close()

return jsonify({"success": True}), 200

@app.route("/api/v1/resources/addresses", methods=["GET"])
def known_filter():
query_parameters = request.args

addresses = query_parameters.getlist('address')
adtypes = query_parameters.getlist('type')
owners = query_parameters.getlist('owner')
illicit = query_parameters.get('illicit')
addresses = query_parameters.getlist("address")
adtypes = query_parameters.getlist("type")
owners = query_parameters.getlist("owner")
illicit = query_parameters.get("illicit")
query = "SELECT * FROM addresses WHERE"
to_filter = []

if addresses:
query += ' address IN ({}) AND'.format(', '.join('?' * len(addresses)))
query += " address IN ({}) AND".format(", ".join("?" * len(addresses)))
to_filter.extend(addresses)
if adtypes:
query += ' type IN ({}) AND'.format(', '.join('?' * len(adtypes)))
query += " type IN ({}) AND".format(", ".join("?" * len(adtypes)))
to_filter.extend(adtypes)
if owners:
query += ' owner IN ({}) AND'.format(', '.join('?' * len(owners)))
query += " owner IN ({}) AND".format(", ".join("?" * len(owners)))
to_filter.extend(owners)
if illicit:
query += ' illicit=? AND'
query += " illicit=? AND"
to_filter.extend(illicit)

if not (addresses or adtypes or owners or illicit):
return page_not_found(404)

query = query[:-4] + ';'
query = query[:-4] + ";"

conn = sqlite3.connect(os.getcwd() + "/addresses.sqlite")
conn.row_factory = dict_factory
Expand All @@ -106,36 +193,37 @@ def known_filter():

return jsonify(results)

@app.route('/api/v1/resources/intermediaries/all', methods=['GET'])

@app.route("/api/v1/resources/intermediaries/all", methods=["GET"])
def intermediaries_all():
conn = sqlite3.connect(os.getcwd() + "/addresses.sqlite")
conn.row_factory = dict_factory
cur = conn.cursor()
all_addresses = cur.execute('SELECT * FROM intermediaries;').fetchall()
all_addresses = cur.execute("SELECT * FROM intermediaries;").fetchall()

return jsonify(all_addresses)

@app.route('/api/v1/resources/intermediaries', methods=['GET'])

@app.route("/api/v1/resources/intermediaries", methods=["GET"])
def intermediaries_filter():
query_parameters = request.args

addresses = query_parameters.getlist('address')
services = query_parameters.getlist('service')
addresses = query_parameters.getlist("address")
services = query_parameters.getlist("service")
query = "SELECT * FROM intermediaries WHERE"
to_filter = []

if addresses:
query += ' address IN ({}) AND'.format(', '.join('?' * len(addresses)))
query += " address IN ({}) AND".format(", ".join("?" * len(addresses)))
to_filter.extend(addresses)
if services:
query += ' service IN ({}) AND'.format(', '.join('?' * len(services)))
query += " service IN ({}) AND".format(", ".join("?" * len(services)))
to_filter.extend(services)


if not (addresses or services):
return page_not_found(404)

query = query[:-4] + ';'
query = query[:-4] + ";"

conn = sqlite3.connect(os.getcwd() + "/addresses.sqlite")
conn.row_factory = dict_factory
Expand All @@ -145,15 +233,16 @@ def intermediaries_filter():

return jsonify(results)

@app.route('/api/v1/resources/intermediaries/status', methods=['GET'])

@app.route("/api/v1/resources/intermediaries/status", methods=["GET"])
def intermediaries_status():
conn = sqlite3.connect(os.getcwd() + "/addresses.sqlite")
conn.row_factory = dict_factory
cur = conn.cursor()
all_addresses = cur.execute('SELECT * FROM last_run;').fetchall()
all_addresses = cur.execute("SELECT * FROM last_run;").fetchall()

return jsonify(all_addresses)


if __name__ == '__main__':
app.run()
if __name__ == "__main__":
app.run()
57 changes: 57 additions & 0 deletions templates/home.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<title>Banano Address API</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>

<body class="bg-dark">
<!-- Navigation Bar -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary mb-4">
<div class="container">
<a class="navbar-brand" href="/">Banano Address API</a>
<div class="navbar-nav">
<a class="nav-link active" href="/">Home</a>
<a class="nav-link" href="/query-address">Query an Address</a>
<a class="nav-link" href="/manage-records">Manage Records (Admin)</a>
</div>
</div>
</nav>

<div class="container text-light">
<h1>Welcome to the Banano Address API</h1>
<p>This API allows you to query Banano address records. Admins can also update and manage records.</p>

<!-- List of API endpoints -->
<div class="list-group my-4">
<a href="/api/v1/resources/addresses?address=ban_31dhbgirwzd3ce7naor6o94woefws9hpxu4q8uxm1bz98w89zqpfks5rk3ad"
class="list-group-item list-group-item-action">Query by Address</a>
<a href="/api/v1/resources/addresses?type=Distribution" class="list-group-item list-group-item-action">Query
by Type: Distribution</a>
<a href="/api/v1/resources/addresses?type=Exchange" class="list-group-item list-group-item-action">Query by
Type: Exchange</a>
<a href="/api/v1/resources/addresses?type=Gambling" class="list-group-item list-group-item-action">Query by
Type: Gambling</a>
<a href="/api/v1/resources/addresses/all" class="list-group-item list-group-item-action">Query All
Addresses</a>
<a href="/api/v1/resources/addresses?illicit=1" class="list-group-item list-group-item-action">Query Illicit
Addresses</a>
<a href="/api/v1/resources/intermediaries/all" class="list-group-item list-group-item-action">View All
Intermediaries</a>
<a href="/api/v1/resources/intermediaries?address=ban_113sf8z98qqihjis3m95gkb35z7ckfakbrtrmbtawf37hc6ixx7rtz53bq8o"
class="list-group-item list-group-item-action">Query Intermediaries by Address</a>
<a href="/api/v1/resources/intermediaries/status"
class="list-group-item list-group-item-action">Intermediaries Status</a>
</div>

<p>Check out the source code on <a href="https://github.com/Kirby1997/BananoAddressAPI"
class="text-primary">GitHub</a>.</p>
</div>

<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.bundle.min.js"></script>
</body>

</html>
Loading