From 3c398a82760a78d05674dd9d5b0ada6b0c4828b2 Mon Sep 17 00:00:00 2001 From: Kristjan Date: Mon, 28 Dec 2020 19:51:45 +0100 Subject: [PATCH] started working on subdomains extension --- lnbits/extensions/subdomains/README.md | 65 ++++ lnbits/extensions/subdomains/__init__.py | 10 + lnbits/extensions/subdomains/config.json | 6 + lnbits/extensions/subdomains/migrations.py | 34 ++ lnbits/extensions/subdomains/models.py | 26 ++ .../templates/subdomains/index.html | 296 ++++++++++++++++++ lnbits/extensions/subdomains/views.py | 12 + lnbits/extensions/subdomains/views_api.py | 40 +++ 8 files changed, 489 insertions(+) create mode 100644 lnbits/extensions/subdomains/README.md create mode 100644 lnbits/extensions/subdomains/__init__.py create mode 100644 lnbits/extensions/subdomains/config.json create mode 100644 lnbits/extensions/subdomains/migrations.py create mode 100644 lnbits/extensions/subdomains/models.py create mode 100644 lnbits/extensions/subdomains/templates/subdomains/index.html create mode 100644 lnbits/extensions/subdomains/views.py create mode 100644 lnbits/extensions/subdomains/views_api.py diff --git a/lnbits/extensions/subdomains/README.md b/lnbits/extensions/subdomains/README.md new file mode 100644 index 000000000..ca3fce5ae --- /dev/null +++ b/lnbits/extensions/subdomains/README.md @@ -0,0 +1,65 @@ +

Subdomains Extension

+ +#TODO - fix formatting etc... +on lnbits there should be an interface with input fields: +subdomain (for example: subdomain1) +ip address (for example: 192.168.21.21) +duration (1 month / 1 year etc...) + +then when user presses SUBMIT button the ln invoice is shown that has to be paid... + +when invoice is paid, the lnbits backend send request to the cloudflare domain registration service, that creates a new A record for that subdomain + +for example, i am hosting lnbits on +lnbits.grmkris.com + +and i am selling my subdomains +subdomain1.grmkris.com +subdomain2.grmkris.com +subdomain3.grmkris.com + +there should be checks if that subdomain is already taken + +and maybe an option to blacklist certain subdomains that i don't want to sell + + +

If your extension has API endpoints, include useful ones here

+ +curl -H "Content-type: application/json" -X POST https://YOUR-LNBITS/YOUR-EXTENSION/api/v1/EXAMPLE -d '{"amount":"100","memo":"subdomains"}' -H "X-Api-Key: YOUR_WALLET-ADMIN/INVOICE-KEY" + +## cloudflare + +- Cloudflare offers programmatic subdomain registration... (create new A record) +- you can keep your existing domain's registrar, you just have to transfer dns records to the cloudflare (free service) +- more information: + - https://api.cloudflare.com/#getting-started-requests + - API endpoints needed for our project: + - https://api.cloudflare.com/#dns-records-for-a-zone-list-dns-records + - https://api.cloudflare.com/#dns-records-for-a-zone-create-dns-record + - https://api.cloudflare.com/#dns-records-for-a-zone-delete-dns-record + - https://api.cloudflare.com/#dns-records-for-a-zone-update-dns-record +- api can be used by providing authorization token OR authorization key + - check API Tokens and API Keys : https://api.cloudflare.com/#getting-started-requests + + + +example curls: +List dns records +```bash +curl --location --request GET 'https://api.cloudflare.com/client/v4/zones/bf3c1e516b35878c9f6532db2f2705ee/dns_records?type=A' \ +--header 'Content-Type: application/json' \ +--header 'Authorization: Bearer mS3gGFC3ySLqBe2ERtRTlh7H2YiGbFp2KLDK62uu' +``` + +```bash +curl --location --request POST 'https://api.cloudflare.com/client/v4/zones/bf3c1e516b35878c9f6532db2f2705ee/dns_records' \ +--header 'Content-Type: application/json' \ +--header 'Authorization: Bearer mS3gGFC3ySLqBe2ERtRTlh7H2YiGbFp2KLDK62uu' \ +--data-raw '{ + "type":"A", + "name":"subdomain1.grmkris.com", + "content":"31.15.150.237", + "ttl":0, + "proxied":true +}' +``` \ No newline at end of file diff --git a/lnbits/extensions/subdomains/__init__.py b/lnbits/extensions/subdomains/__init__.py new file mode 100644 index 000000000..51a821174 --- /dev/null +++ b/lnbits/extensions/subdomains/__init__.py @@ -0,0 +1,10 @@ +from quart import Blueprint +from lnbits.db import Database + +db = Database("ext_subdomains") + +subdomains_ext: Blueprint = Blueprint("subdomains", __name__, static_folder="static", template_folder="templates") + + +from .views_api import * # noqa +from .views import * # noqa diff --git a/lnbits/extensions/subdomains/config.json b/lnbits/extensions/subdomains/config.json new file mode 100644 index 000000000..4a34be565 --- /dev/null +++ b/lnbits/extensions/subdomains/config.json @@ -0,0 +1,6 @@ +{ + "name": "Subdomains", + "short_description": "Sell subdomains of your domain", + "icon": "domain", + "contributors": ["grmkris"] +} diff --git a/lnbits/extensions/subdomains/migrations.py b/lnbits/extensions/subdomains/migrations.py new file mode 100644 index 000000000..00010f60f --- /dev/null +++ b/lnbits/extensions/subdomains/migrations.py @@ -0,0 +1,34 @@ +async def m001_initial(db): + + await db.execute( + """ + CREATE TABLE IF NOT EXISTS domain ( + id TEXT PRIMARY KEY, + wallet TEXT NOT NULL, + domain_name TEXT NOT NULL, + webhook TEXT, + cf_token TEXT NOT NULL, + cf_zone_id TEXT NOT NULL, + description TEXT NOT NULL, + cost INTEGER NOT NULL, + amountmade INTEGER NOT NULL, + time TIMESTAMP NOT NULL DEFAULT (strftime('%s', 'now')) + ); + """ + ) + + await db.execute( + """ + CREATE TABLE IF NOT EXISTS subdomain ( + id TEXT PRIMARY KEY, + domain_name TEXT NOT NULL, + email TEXT NOT NULL, + subdomain TEXT NOT NULL, + ip TEXT NOT NULL, + wallet TEXT NOT NULL, + sats INTEGER NOT NULL, + paid BOOLEAN NOT NULL, + time TIMESTAMP NOT NULL DEFAULT (strftime('%s', 'now')) + ); + """ + ) \ No newline at end of file diff --git a/lnbits/extensions/subdomains/models.py b/lnbits/extensions/subdomains/models.py new file mode 100644 index 000000000..9b330b858 --- /dev/null +++ b/lnbits/extensions/subdomains/models.py @@ -0,0 +1,26 @@ +from typing import NamedTuple + + +class Domains(NamedTuple): + id: str + wallet: str + domainName: str + cfToken: str + cfZoneId: str + webhook: str + description: str + cost: int + amountmade: int + time: int + + +class Subdomains(NamedTuple): + id: str + domainName: str + email: str + subdomain: str + ip: str + wallet: str + sats: int + paid: bool + time: int diff --git a/lnbits/extensions/subdomains/templates/subdomains/index.html b/lnbits/extensions/subdomains/templates/subdomains/index.html new file mode 100644 index 000000000..cc6a37362 --- /dev/null +++ b/lnbits/extensions/subdomains/templates/subdomains/index.html @@ -0,0 +1,296 @@ +{% extends "base.html" %} {% from "macros.jinja" import window_vars with context +%} {% block page %} + +
+
+ + + New Domain + + + + + +
+
+
Domains
+
+
+ Export to CSV +
+
+ + {% raw %} + + + {% endraw %} + +
+
+
+ + + + + + + + + + + + + + +
+ Update Form + Create Domain + Cancel +
+
+
+
+ +
+ +{% endblock %} {% block scripts %} {{ window_vars(user) }} + +{% endblock %} \ No newline at end of file diff --git a/lnbits/extensions/subdomains/views.py b/lnbits/extensions/subdomains/views.py new file mode 100644 index 000000000..b75c4906b --- /dev/null +++ b/lnbits/extensions/subdomains/views.py @@ -0,0 +1,12 @@ +from quart import g, render_template + +from lnbits.decorators import check_user_exists, validate_uuids + +from . import subdomains_ext + + +@subdomains_ext.route("/") +@validate_uuids(["usr"], required=True) +@check_user_exists() +async def index(): + return await render_template("subdomains/index.html", user=g.user) diff --git a/lnbits/extensions/subdomains/views_api.py b/lnbits/extensions/subdomains/views_api.py new file mode 100644 index 000000000..bfcac16c7 --- /dev/null +++ b/lnbits/extensions/subdomains/views_api.py @@ -0,0 +1,40 @@ +# views_api.py is for you API endpoints that could be hit by another service + +# add your dependencies here + +# import json +# import httpx +# (use httpx just like requests, except instead of response.ok there's only the +# response.is_error that is its inverse) + +from quart import jsonify +from http import HTTPStatus + +from . import subdomains_ext + + +# add your endpoints here + + +@subdomains_ext.route("/api/v1/tools", methods=["GET"]) +async def api_subdomains(): + """Try to add descriptions for others.""" + tools = [ + { + "name": "Quart", + "url": "https://pgjones.gitlab.io/quart/", + "language": "Python", + }, + { + "name": "Vue.js", + "url": "https://vuejs.org/", + "language": "JavaScript", + }, + { + "name": "Quasar Framework", + "url": "https://quasar.dev/", + "language": "JavaScript", + }, + ] + + return jsonify(tools), HTTPStatus.OK