diff --git a/lnbits/extensions/usermanager/README.md b/lnbits/extensions/usermanager/README.md new file mode 100644 index 00000000..005c5206 --- /dev/null +++ b/lnbits/extensions/usermanager/README.md @@ -0,0 +1,9 @@ +

User Manager

+

Make and manager users/wallets

+To help developers use LNbits to manage their users, the User Manager extention allows the creation and management of users and wallets. For example, a games developer may be developing a game that needs each user to have their own wallet, LNbits can be included in the develpoers stack as the user and wallet manager. + + + +

API endpoints

+ +curl -X GET http://YOUR-TOR-ADDRESS diff --git a/lnbits/extensions/usermanager/__init__.py b/lnbits/extensions/usermanager/__init__.py new file mode 100644 index 00000000..7381fd1c --- /dev/null +++ b/lnbits/extensions/usermanager/__init__.py @@ -0,0 +1,8 @@ +from flask import Blueprint + + +usermanager_ext: Blueprint = Blueprint("usermanager", __name__, static_folder="static", template_folder="templates") + + +from .views_api import * # noqa +from .views import * # noqa diff --git a/lnbits/extensions/usermanager/config.json b/lnbits/extensions/usermanager/config.json new file mode 100644 index 00000000..7391ec29 --- /dev/null +++ b/lnbits/extensions/usermanager/config.json @@ -0,0 +1,6 @@ +{ + "name": "User Manager", + "short_description": "Generate users and wallets", + "icon": "person_add", + "contributors": ["benarc"] +} diff --git a/lnbits/extensions/usermanager/crud.py b/lnbits/extensions/usermanager/crud.py new file mode 100644 index 00000000..61718ddb --- /dev/null +++ b/lnbits/extensions/usermanager/crud.py @@ -0,0 +1,117 @@ +from lnbits.db import open_ext_db +from lnbits.settings import WALLET +from .models import Users, Wallets +from typing import List, Optional, Union + +from ...core.crud import ( + create_account, + get_user, + update_user_extension, + get_wallet_payments, + create_wallet, + delete_wallet, +) + + + + + +###Users + +def create_usermanager_user(user_name: str, wallet_name: str, admin_id: str) -> Users: + user = get_user(create_account().id) + + wallet = create_wallet(user_id=user.id, wallet_name=wallet_name) + + with open_ext_db("usermanager") as db: + db.execute( + """ + INSERT INTO users (id, name, admin) + VALUES (?, ?, ?) + """, + (user.id, user_name, admin_id), + ) + + db.execute( + """ + INSERT INTO wallets (id, admin, name, user, adminkey, inkey) + VALUES (?, ?, ?, ?, ?, ?) + """, + (wallet.id, admin_id, wallet_name, user.id, wallet.adminkey, wallet.inkey) + ) + + return get_usermanager_user(user.id) + + +def get_usermanager_user(user_id: str) -> Users: + with open_ext_db("usermanager") as db: + + row = db.fetchone("SELECT * FROM users WHERE id = ?", (user_id,)) + + + return Users(**row) if row else None + + +def get_usermanager_users(user_id: str) -> Users: + + with open_ext_db("usermanager") as db: + rows = db.fetchall("SELECT * FROM users WHERE admin = ?", (user_id,)) + + return [Users(**row) for row in rows] + + +def delete_usermanager_user(user_id: str) -> None: + row = get_usermanager_wallets(user_id) + print("test") + with open_ext_db("usermanager") as db: + db.execute("DELETE FROM users WHERE id = ?", (user_id,)) + row + for r in row: + delete_wallet( user_id=user_id, wallet_id=r.id) + with open_ext_db("usermanager") as dbb: + dbb.execute("DELETE FROM wallets WHERE user = ?", (user_id,)) + +###Wallets + +def create_usermanager_wallet(user_id: str, wallet_name: str, admin_id: str) -> Wallets: + wallet = create_wallet(user_id=user_id, wallet_name=wallet_name) + with open_ext_db("usermanager") as db: + + db.execute( + """ + INSERT INTO wallets (id, admin, name, user, adminkey, inkey) + VALUES (?, ?, ?, ?, ?, ?) + """, + (wallet.id, admin_id, wallet_name, user_id, wallet.adminkey, wallet.inkey) + ) + + return get_usermanager_wallet(wallet.id) + +def get_usermanager_wallet(wallet_id: str) -> Optional[Wallets]: + with open_ext_db("usermanager") as db: + row = db.fetchone("SELECT * FROM wallets WHERE id = ?", (wallet_id,)) + + return Wallets(**row) if row else None + + +def get_usermanager_wallets(user_id: str) -> Wallets: + + with open_ext_db("usermanager") as db: + rows = db.fetchall("SELECT * FROM wallets WHERE admin = ?", (user_id,)) + + return [Wallets(**row) for row in rows] + + +def get_usermanager_wallet_transactions(wallet_id: str) -> Users: + return get_wallet_payments(wallet_id=wallet_id,include_all_pending=False) + +def get_usermanager_wallet_balances(user_id: str) -> Users: + user = get_user(user_id) + return (user.wallets) + + +def delete_usermanager_wallet(wallet_id: str, user_id: str) -> None: + delete_wallet( user_id=user_id, wallet_id=wallet_id) + with open_ext_db("usermanager") as db: + db.execute("DELETE FROM wallets WHERE id = ?", (wallet_id,)) + diff --git a/lnbits/extensions/usermanager/migrations.py b/lnbits/extensions/usermanager/migrations.py new file mode 100644 index 00000000..26726b3c --- /dev/null +++ b/lnbits/extensions/usermanager/migrations.py @@ -0,0 +1,36 @@ +from lnbits.db import open_ext_db + + +def m001_initial(db): + """ + Initial users table. + """ + db.execute(""" + CREATE TABLE IF NOT EXISTS users ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + admin TEXT NOT NULL, + email TEXT, + password TEXT + ); + """) + + + """ + Initial wallets table. + """ + db.execute(""" + CREATE TABLE IF NOT EXISTS wallets ( + id TEXT PRIMARY KEY, + admin TEXT NOT NULL, + name TEXT NOT NULL, + user TEXT NOT NULL, + adminkey TEXT NOT NULL, + inkey TEXT NOT NULL + ); + """) + +def migrate(): + with open_ext_db("usermanager") as db: + m001_initial(db) + diff --git a/lnbits/extensions/usermanager/models.py b/lnbits/extensions/usermanager/models.py new file mode 100644 index 00000000..d7362328 --- /dev/null +++ b/lnbits/extensions/usermanager/models.py @@ -0,0 +1,18 @@ +from typing import NamedTuple + +class Users(NamedTuple): + id: str + name: str + admin: str + email: str + password: str + +class Wallets(NamedTuple): + id: str + admin: str + name: str + user: str + adminkey: str + inkey: str + + diff --git a/lnbits/extensions/usermanager/templates/usermanager/_api_docs.html b/lnbits/extensions/usermanager/templates/usermanager/_api_docs.html new file mode 100644 index 00000000..ff15e3ad --- /dev/null +++ b/lnbits/extensions/usermanager/templates/usermanager/_api_docs.html @@ -0,0 +1,121 @@ + + + + +
User Manager: Make and manager users/wallets
+

To help developers use LNbits to manage their users, the User Manager extention allows the creation and management of users and wallets.
For example, a games developer may be developing a game that needs each user to have their own wallet, LNbits can be included in the develpoers stack as the user and wallet manager.
+ Created by, Ben Arc

+
+ + + +
+ + + + + + GET /usermanager/api/v1/users +
Body (application/json)
+
Returns 201 CREATED (application/json)
+ JSON list of users +
Curl example
+ curl -X GET {{ request.url_root }}usermanager/api/v1/users -H "X-Api-Key: {{ g.user.wallets[0].inkey }}" +
+
+
+ + + + GET /usermanager/api/v1/wallets/<user_id> +
Headers
+ {"X-Api-Key": <string>} +
Body (application/json)
+
Returns 201 CREATED (application/json)
+ JSON wallet data +
Curl example
+ curl -X GET {{ request.url_root }}usermanager/api/v1/wallets/<user_id> -H "X-Api-Key: {{ g.user.wallets[0].inkey }}" +
+
+
+ + + + GET /usermanager/api/v1/wallets<wallet_id> +
Headers
+ {"X-Api-Key": <string>} +
Body (application/json)
+
Returns 201 CREATED (application/json)
+ JSON a wallets transactions +
Curl example
+ curl -X GET {{ request.url_root }}usermanager/api/v1/wallets<wallet_id> -H "X-Api-Key: {{ g.user.wallets[0].inkey }}" +
+
+
+ + + + POST /usermanager/api/v1/users +
Headers
+ {"X-Api-Key": <string>, "Content-type": "application/json"} +
Body (application/json) - "admin_id" is a YOUR user ID
+ {"admin_id": <string>, "user_name": <string>, "wallet_name": <string>} +
Returns 201 CREATED (application/json)
+ {"checking_id": <string>,"payment_request": <string>} +
Curl example
+ curl -X POST {{ request.url_root }}usermanager/api/v1/users -d '{"admin_id": "{{ g.user.id }}", "wallet_name": <string>, "user_name": <string>}' -H "X-Api-Key: {{ g.user.wallets[0].inkey }}" -H "Content-type: application/json" + +
+
+
+ + + + POST /usermanager/api/v1/wallets +
Headers
+ {"X-Api-Key": <string>, "Content-type": "application/json"} +
Body (application/json) - "admin_id" is a YOUR user ID
+ {"user_id": <string>, "wallet_name": <string>, "admin_id": <string>} +
Returns 201 CREATED (application/json)
+ {"checking_id": <string>,"payment_request": <string>} +
Curl example
+ curl -X POST {{ request.url_root }}usermanager/api/v1/wallets -d '{"user_id": <string>, "wallet_name": <string>, "admin_id": "{{ g.user.id }}"}' -H "X-Api-Key: {{ g.user.wallets[0].inkey }}" -H "Content-type: application/json" + +
+
+
+ + + + DELETE /usermanager/api/v1/users/<user_id> +
Headers
+ {"X-Api-Key": <string>} +
Curl example
+ curl -X DELETE {{ request.url_root }}usermanager/api/v1/users/<user_id> -H "X-Api-Key: {{ g.user.wallets[0].inkey }}" +
+
+
+ + + + DELETE /usermanager/api/v1/wallets/<wallet_id> +
Headers
+ {"X-Api-Key": <string>} +
Curl example
+ curl -X DELETE {{ request.url_root }}usermanager/api/v1/wallets/<wallet_id> -H "X-Api-Key: {{ g.user.wallets[0].inkey }}" +
+
+
+ + +
diff --git a/lnbits/extensions/usermanager/templates/usermanager/index.html b/lnbits/extensions/usermanager/templates/usermanager/index.html new file mode 100644 index 00000000..f7b462bb --- /dev/null +++ b/lnbits/extensions/usermanager/templates/usermanager/index.html @@ -0,0 +1,416 @@ +{% extends "base.html" %} + +{% from "macros.jinja" import window_vars with context %} + + +{% block page %} +
+
+ + + New User + New Wallet + + + + + + +
+
+
Users
+
+
+ Export to CSV +
+
+ + {% raw %} + + + {% endraw %} + +
+
+ + + +
+
+
Wallets
+
+
+ Export to CSV +
+
+ + {% raw %} + + + {% endraw %} + +
+
+ + +
+ +
+ + +
LNbits User Manager Extension
+
+ + + + {% include "usermanager/_api_docs.html" %} + + +
+
+ + + + + + + + + Create User + Cancel +
+ + + + + + + + + + + + + Create User + Cancel + + + + + + + +{% endblock %} + +{% block scripts %} + {{ window_vars(user) }} + +{% endblock %} diff --git a/lnbits/extensions/usermanager/views.py b/lnbits/extensions/usermanager/views.py new file mode 100644 index 00000000..625d7d3f --- /dev/null +++ b/lnbits/extensions/usermanager/views.py @@ -0,0 +1,14 @@ +from flask import g, abort, render_template, jsonify +import json +from lnbits.decorators import check_user_exists, validate_uuids +from lnbits.extensions.usermanager import usermanager_ext +from lnbits.helpers import Status +from lnbits.db import open_ext_db + + +@usermanager_ext.route("/") +@validate_uuids(["usr"], required=True) +@check_user_exists() +def index(): + + return render_template("usermanager/index.html", user=g.user) \ No newline at end of file diff --git a/lnbits/extensions/usermanager/views_api.py b/lnbits/extensions/usermanager/views_api.py new file mode 100644 index 00000000..d845ec2d --- /dev/null +++ b/lnbits/extensions/usermanager/views_api.py @@ -0,0 +1,93 @@ +from flask import g, jsonify, request + +from lnbits.core.crud import get_user +from lnbits.decorators import api_check_wallet_key, api_validate_post_request +from lnbits.helpers import Status + +from lnbits.extensions.usermanager import usermanager_ext +from .crud import create_usermanager_user, get_usermanager_user, get_usermanager_users, get_usermanager_wallet_transactions, get_usermanager_wallet_balances, delete_usermanager_user, create_usermanager_wallet, get_usermanager_wallet, get_usermanager_wallets, delete_usermanager_wallet +from lnbits.core.services import create_invoice +from base64 import urlsafe_b64encode +from uuid import uuid4 +from lnbits.db import open_ext_db + + + +###Users + +@usermanager_ext.route("/api/v1/users", methods=["GET"]) +@api_check_wallet_key(key_type="invoice") +def api_usermanager_users(): + user_id = g.wallet.user + return jsonify([user._asdict() for user in get_usermanager_users(user_id)]), Status.OK + + +@usermanager_ext.route("/api/v1/users", methods=["POST"]) +@api_check_wallet_key(key_type="invoice") +@api_validate_post_request(schema={ + "admin_id": {"type": "string", "empty": False, "required": True}, + "user_name": {"type": "string", "empty": False, "required": True}, + "wallet_name": {"type": "string", "empty": False, "required": True} +}) +def api_usermanager_users_create(): + user = create_usermanager_user(g.data["user_name"], g.data["wallet_name"], g.data["admin_id"]) + return jsonify(user._asdict()), Status.CREATED + + +@usermanager_ext.route("/api/v1/users/", methods=["DELETE"]) +@api_check_wallet_key(key_type="invoice") +def api_usermanager_users_delete(user_id): + print("cunt") + user = get_usermanager_user(user_id) + if not user: + return jsonify({"message": "User does not exist."}), Status.NOT_FOUND + delete_usermanager_user(user_id) + return "", Status.NO_CONTENT + + +###Wallets + +@usermanager_ext.route("/api/v1/wallets", methods=["GET"]) +@api_check_wallet_key(key_type="invoice") +def api_usermanager_wallets(): + user_id = g.wallet.user + return jsonify([wallet._asdict() for wallet in get_usermanager_wallets(user_id)]), Status.OK + + +@usermanager_ext.route("/api/v1/wallets", methods=["POST"]) +@api_check_wallet_key(key_type="invoice") +@api_validate_post_request(schema={ + "user_id": {"type": "string", "empty": False, "required": True}, + "wallet_name": {"type": "string", "empty": False, "required": True}, + "admin_id": {"type": "string", "empty": False, "required": True} + +}) +def api_usermanager_wallets_create(): + user = create_usermanager_wallet(g.data["user_id"], g.data["wallet_name"], g.data["admin_id"]) + return jsonify(user._asdict()), Status.CREATED + + +@usermanager_ext.route("/api/v1/wallets", methods=["GET"]) +@api_check_wallet_key(key_type="invoice") +def api_usermanager_wallet_transactions(wallet_id): + + return jsonify(get_usermanager_wallet_transactions(wallet_id)), Status.OK + +@usermanager_ext.route("/api/v1/wallets/", methods=["GET"]) +@api_check_wallet_key(key_type="invoice") +def api_usermanager_wallet_balances(user_id): + return jsonify(get_usermanager_wallet_balances(user_id)), Status.OK + + +@usermanager_ext.route("/api/v1/wallets/", methods=["DELETE"]) +@api_check_wallet_key(key_type="invoice") +def api_usermanager_wallets_delete(wallet_id): + wallet = get_usermanager_wallet(wallet_id) + print(wallet.id) + if not wallet: + return jsonify({"message": "Wallet does not exist."}), Status.NOT_FOUND + + delete_usermanager_wallet(wallet_id, wallet.user) + + return "", Status.NO_CONTENT +