From e1d9bf31d17fc147099893c2494773581b632f12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dni=20=E2=9A=A1?= Date: Wed, 15 Feb 2023 10:17:13 +0100 Subject: [PATCH] remove subdomains --- lnbits/extensions/subdomains/README.md | 54 -- lnbits/extensions/subdomains/__init__.py | 34 -- lnbits/extensions/subdomains/cloudflare.py | 49 -- lnbits/extensions/subdomains/config.json | 6 - lnbits/extensions/subdomains/crud.py | 161 ----- lnbits/extensions/subdomains/migrations.py | 41 -- lnbits/extensions/subdomains/models.py | 52 -- .../subdomains/static/image/subdomains.png | Bin 37360 -> 0 bytes lnbits/extensions/subdomains/tasks.py | 65 --- .../templates/subdomains/_api_docs.html | 31 - .../templates/subdomains/display.html | 221 ------- .../templates/subdomains/index.html | 549 ------------------ lnbits/extensions/subdomains/util.py | 32 - lnbits/extensions/subdomains/views.py | 47 -- lnbits/extensions/subdomains/views_api.py | 199 ------- 15 files changed, 1541 deletions(-) delete mode 100644 lnbits/extensions/subdomains/README.md delete mode 100644 lnbits/extensions/subdomains/__init__.py delete mode 100644 lnbits/extensions/subdomains/cloudflare.py delete mode 100644 lnbits/extensions/subdomains/config.json delete mode 100644 lnbits/extensions/subdomains/crud.py delete mode 100644 lnbits/extensions/subdomains/migrations.py delete mode 100644 lnbits/extensions/subdomains/models.py delete mode 100644 lnbits/extensions/subdomains/static/image/subdomains.png delete mode 100644 lnbits/extensions/subdomains/tasks.py delete mode 100644 lnbits/extensions/subdomains/templates/subdomains/_api_docs.html delete mode 100644 lnbits/extensions/subdomains/templates/subdomains/display.html delete mode 100644 lnbits/extensions/subdomains/templates/subdomains/index.html delete mode 100644 lnbits/extensions/subdomains/util.py delete mode 100644 lnbits/extensions/subdomains/views.py delete mode 100644 lnbits/extensions/subdomains/views_api.py diff --git a/lnbits/extensions/subdomains/README.md b/lnbits/extensions/subdomains/README.md deleted file mode 100644 index 3797c761..00000000 --- a/lnbits/extensions/subdomains/README.md +++ /dev/null @@ -1,54 +0,0 @@ -

Subdomains Extension

- -So the goal of the extension is to allow the owner of a domain to sell subdomains to anyone who is willing to pay some money for it. - -[![video tutorial livestream](http://img.youtube.com/vi/O1X0fy3uNpw/0.jpg)](https://youtu.be/O1X0fy3uNpw 'video tutorial subdomains') - -## Requirements - -- Free Cloudflare account -- Cloudflare as a DNS server provider -- Cloudflare TOKEN and Cloudflare zone-ID where the domain is parked - -## Usage - -1. Register at Cloudflare and setup your domain with them. (Just follow instructions they provide...) -2. Change DNS server at your domain registrar to point to Cloudflare's -3. Get Cloudflare zone-ID for your domain - -4. Get Cloudflare API TOKEN - - -5. Open the LNbits subdomains extension and register your domain -6. Click on the button in the table to open the public form that was generated for your domain - - - Extension also supports webhooks so you can get notified when someone buys a new subdomain\ - - -## API Endpoints - -- **Domains** - - GET /api/v1/domains - - POST /api/v1/domains - - PUT /api/v1/domains/ - - DELETE /api/v1/domains/ -- **Subdomains** - - GET /api/v1/subdomains - - POST /api/v1/subdomains/ - - GET /api/v1/subdomains/ - - DELETE /api/v1/subdomains/ - -### 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 -- Cloudflare API postman collection: https://support.cloudflare.com/hc/en-us/articles/115002323852-Using-Cloudflare-API-with-Postman-Collections diff --git a/lnbits/extensions/subdomains/__init__.py b/lnbits/extensions/subdomains/__init__.py deleted file mode 100644 index 7434555d..00000000 --- a/lnbits/extensions/subdomains/__init__.py +++ /dev/null @@ -1,34 +0,0 @@ -import asyncio - -from fastapi import APIRouter -from fastapi.staticfiles import StaticFiles - -from lnbits.db import Database -from lnbits.helpers import template_renderer -from lnbits.tasks import catch_everything_and_restart - -db = Database("ext_subdomains") - -subdomains_ext: APIRouter = APIRouter(prefix="/subdomains", tags=["subdomains"]) - -subdomains_static_files = [ - { - "path": "/subdomains/static", - "app": StaticFiles(directory="lnbits/extensions/subdomains/static"), - "name": "subdomains_static", - } -] - - -def subdomains_renderer(): - return template_renderer(["lnbits/extensions/subdomains/templates"]) - - -from .tasks import wait_for_paid_invoices -from .views import * # noqa: F401,F403 -from .views_api import * # noqa: F401,F403 - - -def subdomains_start(): - loop = asyncio.get_event_loop() - loop.create_task(catch_everything_and_restart(wait_for_paid_invoices)) diff --git a/lnbits/extensions/subdomains/cloudflare.py b/lnbits/extensions/subdomains/cloudflare.py deleted file mode 100644 index 3d3b9bde..00000000 --- a/lnbits/extensions/subdomains/cloudflare.py +++ /dev/null @@ -1,49 +0,0 @@ -import httpx - -from .models import Domains - - -async def cloudflare_create_subdomain( - domain: Domains, subdomain: str, record_type: str, ip: str -): - # Call to cloudflare sort of a dry-run - if success delete the domain and wait for payment - ### SEND REQUEST TO CLOUDFLARE - url = ( - "https://api.cloudflare.com/client/v4/zones/" - + domain.cf_zone_id - + "/dns_records" - ) - header = { - "Authorization": "Bearer " + domain.cf_token, - "Content-Type": "application/json", - } - aRecord = subdomain + "." + domain.domain - async with httpx.AsyncClient() as client: - r = await client.post( - url, - headers=header, - json={ - "type": record_type, - "name": aRecord, - "content": ip, - "ttl": 0, - "proxied": False, - }, - timeout=40, - ) - r.raise_for_status() - return r.json() - - -async def cloudflare_deletesubdomain(domain: Domains, domain_id: str): - url = ( - "https://api.cloudflare.com/client/v4/zones/" - + domain.cf_zone_id - + "/dns_records" - ) - header = { - "Authorization": "Bearer " + domain.cf_token, - "Content-Type": "application/json", - } - async with httpx.AsyncClient() as client: - await client.delete(url + "/" + domain_id, headers=header, timeout=40) diff --git a/lnbits/extensions/subdomains/config.json b/lnbits/extensions/subdomains/config.json deleted file mode 100644 index cec2ec64..00000000 --- a/lnbits/extensions/subdomains/config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Subdomains", - "short_description": "Sell subdomains of your domain", - "tile": "/subdomains/static/image/subdomains.png", - "contributors": ["grmkris"] -} diff --git a/lnbits/extensions/subdomains/crud.py b/lnbits/extensions/subdomains/crud.py deleted file mode 100644 index b3476ed9..00000000 --- a/lnbits/extensions/subdomains/crud.py +++ /dev/null @@ -1,161 +0,0 @@ -from typing import List, Optional, Union - -from lnbits.helpers import urlsafe_short_hash - -from . import db -from .models import CreateDomain, CreateSubdomain, Domains, Subdomains - - -async def create_subdomain(payment_hash, wallet, data: CreateSubdomain) -> Subdomains: - await db.execute( - """ - INSERT INTO subdomains.subdomain (id, domain, email, subdomain, ip, wallet, sats, duration, paid, record_type) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - """, - ( - payment_hash, - data.domain, - data.email, - data.subdomain, - data.ip, - wallet, - data.sats, - data.duration, - False, - data.record_type, - ), - ) - - new_subdomain = await get_subdomain(payment_hash) - assert new_subdomain, "Newly created subdomain couldn't be retrieved" - return new_subdomain - - -async def set_subdomain_paid(payment_hash: str) -> Subdomains: - row = await db.fetchone( - "SELECT s.*, d.domain as domain_name FROM subdomains.subdomain s INNER JOIN subdomains.domain d ON (s.domain = d.id) WHERE s.id = ?", - (payment_hash,), - ) - if row[8] is False: - await db.execute( - """ - UPDATE subdomains.subdomain - SET paid = true - WHERE id = ? - """, - (payment_hash,), - ) - - domaindata = await get_domain(row[1]) - assert domaindata, "Couldn't get domain from paid subdomain" - - amount = domaindata.amountmade + row[8] - await db.execute( - """ - UPDATE subdomains.domain - SET amountmade = ? - WHERE id = ? - """, - (amount, row[1]), - ) - - new_subdomain = await get_subdomain(payment_hash) - assert new_subdomain, "Newly paid subdomain couldn't be retrieved" - return new_subdomain - - -async def get_subdomain(subdomain_id: str) -> Optional[Subdomains]: - row = await db.fetchone( - "SELECT s.*, d.domain as domain_name FROM subdomains.subdomain s INNER JOIN subdomains.domain d ON (s.domain = d.id) WHERE s.id = ?", - (subdomain_id,), - ) - return Subdomains(**row) if row else None - - -async def get_subdomainBySubdomain(subdomain: str) -> Optional[Subdomains]: - row = await db.fetchone( - "SELECT s.*, d.domain as domain_name FROM subdomains.subdomain s INNER JOIN subdomains.domain d ON (s.domain = d.id) WHERE s.subdomain = ?", - (subdomain,), - ) - return Subdomains(**row) if row else None - - -async def get_subdomains(wallet_ids: Union[str, List[str]]) -> List[Subdomains]: - if isinstance(wallet_ids, str): - wallet_ids = [wallet_ids] - - q = ",".join(["?"] * len(wallet_ids)) - rows = await db.fetchall( - f"SELECT s.*, d.domain as domain_name FROM subdomains.subdomain s INNER JOIN subdomains.domain d ON (s.domain = d.id) WHERE s.wallet IN ({q})", - (*wallet_ids,), - ) - - return [Subdomains(**row) for row in rows] - - -async def delete_subdomain(subdomain_id: str) -> None: - await db.execute("DELETE FROM subdomains.subdomain WHERE id = ?", (subdomain_id,)) - - -# Domains - - -async def create_domain(data: CreateDomain) -> Domains: - domain_id = urlsafe_short_hash() - await db.execute( - """ - INSERT INTO subdomains.domain (id, wallet, domain, webhook, cf_token, cf_zone_id, description, cost, amountmade, allowed_record_types) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - """, - ( - domain_id, - data.wallet, - data.domain, - data.webhook, - data.cf_token, - data.cf_zone_id, - data.description, - data.cost, - 0, - data.allowed_record_types, - ), - ) - - new_domain = await get_domain(domain_id) - assert new_domain, "Newly created domain couldn't be retrieved" - return new_domain - - -async def update_domain(domain_id: str, **kwargs) -> Domains: - q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) - await db.execute( - f"UPDATE subdomains.domain SET {q} WHERE id = ?", (*kwargs.values(), domain_id) - ) - row = await db.fetchone( - "SELECT * FROM subdomains.domain WHERE id = ?", (domain_id,) - ) - assert row, "Newly updated domain couldn't be retrieved" - return Domains(**row) - - -async def get_domain(domain_id: str) -> Optional[Domains]: - row = await db.fetchone( - "SELECT * FROM subdomains.domain WHERE id = ?", (domain_id,) - ) - return Domains(**row) if row else None - - -async def get_domains(wallet_ids: Union[str, List[str]]) -> List[Domains]: - if isinstance(wallet_ids, str): - wallet_ids = [wallet_ids] - - q = ",".join(["?"] * len(wallet_ids)) - rows = await db.fetchall( - f"SELECT * FROM subdomains.domain WHERE wallet IN ({q})", (*wallet_ids,) - ) - - return [Domains(**row) for row in rows] - - -async def delete_domain(domain_id: str) -> None: - await db.execute("DELETE FROM subdomains.domain WHERE id = ?", (domain_id,)) diff --git a/lnbits/extensions/subdomains/migrations.py b/lnbits/extensions/subdomains/migrations.py deleted file mode 100644 index 292d1f18..00000000 --- a/lnbits/extensions/subdomains/migrations.py +++ /dev/null @@ -1,41 +0,0 @@ -async def m001_initial(db): - - await db.execute( - """ - CREATE TABLE subdomains.domain ( - id TEXT PRIMARY KEY, - wallet TEXT NOT NULL, - domain 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, - allowed_record_types TEXT NOT NULL, - time TIMESTAMP NOT NULL DEFAULT """ - + db.timestamp_now - + """ - ); - """ - ) - - await db.execute( - """ - CREATE TABLE subdomains.subdomain ( - id TEXT PRIMARY KEY, - domain TEXT NOT NULL, - email TEXT NOT NULL, - subdomain TEXT NOT NULL, - ip TEXT NOT NULL, - wallet TEXT NOT NULL, - sats INTEGER NOT NULL, - duration INTEGER NOT NULL, - paid BOOLEAN NOT NULL, - record_type TEXT NOT NULL, - time TIMESTAMP NOT NULL DEFAULT """ - + db.timestamp_now - + """ - ); - """ - ) diff --git a/lnbits/extensions/subdomains/models.py b/lnbits/extensions/subdomains/models.py deleted file mode 100644 index 552c37c7..00000000 --- a/lnbits/extensions/subdomains/models.py +++ /dev/null @@ -1,52 +0,0 @@ -from fastapi import Query -from pydantic import BaseModel - - -class CreateDomain(BaseModel): - wallet: str = Query(...) - domain: str = Query(...) - cf_token: str = Query(...) - cf_zone_id: str = Query(...) - webhook: str = Query("") - description: str = Query(..., min_length=0) - cost: int = Query(..., ge=0) - allowed_record_types: str = Query(...) - - -class CreateSubdomain(BaseModel): - domain: str = Query(...) - subdomain: str = Query(...) - email: str = Query(...) - ip: str = Query(...) - sats: int = Query(..., ge=0) - duration: int = Query(...) - record_type: str = Query(...) - - -class Domains(BaseModel): - id: str - wallet: str - domain: str - cf_token: str - cf_zone_id: str - webhook: str - description: str - cost: int - amountmade: int - time: int - allowed_record_types: str - - -class Subdomains(BaseModel): - id: str - wallet: str - domain: str - domain_name: str - subdomain: str - email: str - ip: str - sats: int - duration: int - paid: bool - time: int - record_type: str diff --git a/lnbits/extensions/subdomains/static/image/subdomains.png b/lnbits/extensions/subdomains/static/image/subdomains.png deleted file mode 100644 index c552cb7bdfcc754a96288deb9aa68961abb136a6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37360 zcmeAS@N?(olHy`uVBq!ia0y~yU}ykg4mJh`hQoG=rx_URpI3!MlmsP~D-;yvr)B1( zDwI?fq$;FVWTr7NRNPv+H+tGri*>m_*N4qf>saz5tIxwL`Ho!3=WM@Ica)WsjozPJ zRk35{?3>r7c_bw23N*-`|NpuE-sAdz&+89ey;T{rbK50v%b(9*h4-7!-}3qQ^LzDe zpKsN_tFNo=-v9sU>*Me9UG`1+F{3Unpsw!U_3MY^V?SP-|N2_| z_nW@>ugb+wWL@nd?Zsc&i+g?i?e=5mfxoubBCA%{*R_1Ck-xrPJ@fwm_WC8e*8lyj z^#9lQ)tjocKOYr&JlVm;ZHo-JgFte?DLDufMxi``;grU(;Rn_S^q^e7%3d|H|-hm;S$;yZ+nf z$Um>IhkLx;y|_NI_J95TtM~q%u{l~RI&Ie=$PTl;e{+Uqe z|Jy&eCtjT#8~xmd{r|ZO&MCX^6|{=J`_PtNKPR!MJG`bZ;-|_n$;_z0dCSk)|1>O9*<;^5>pRo`U*F^Z7sd-)&xlQpU9x$@6KWEYr%%8h+=7bxJWx7jbyezVNOj*P>ZFDr>E_Yd!-CCfiuX}b+xL51m z%!}KfshYl#@3s8&w0S*q+8Y-?)zEKj@yVOlX(!u%&AM@6Zt%XMbE?KNLit8ck7e9;2TD$gJ)%GjvrbTCN-Fofz-DLlUw0BFd->Z86{cQEUn4izCn%fuL zSMD=eH?cp^O*Z#Sq5qlmIOFq1uX8q^uRc0e?8nAat5&bw_fFkr#-(E0pPK%)w=U2B zee}w`l0DaF-~V)N``*8M>)+oxzwO`K`~Q=!A3DG8=-%Le34ixy*KEBOKb_-{XW981 z8!BQNW!L91vdP}vyt(yt*Sk5@Wn~ZapPukd`<}yE-Xptbp1^16y0>=)_sf<(|5yL^ zcHsK`Z=(PF-&Ncm{XPEOo%`S4&M(WAYivzp!{7J+ulQ3_)PC#z&Kswnzgb*t`{wt|PWyFR z=3Tt}q3*VXe}h_r`%}NgThncA>^*8E479|n=Plp(=X!>YwSJ{j{^#0xAFtJR{%YGL zzjwV8Oa1Yw-ur*#?Yb8Ap4(~K)~MR~s>W53(=F2Kly0*tT%B~!&^#loKFYh{j z*We(#GK<@_rWL&>x7`n~6)rbDH1(&y(WiX7G@U%#wfBEGSFgWyf3>{!{qE;ns!P-N z{&-uJCcj5e&-OjvQbw+M#Z!-k-e@SfHuL)Jb#II}{1RPXIlt_|J_Zl}v%3qg9y;9g zvE=JP=Eq$TwVIhpH*9k2g!&rpUbXDpw$<+U*FAqfid=hZSKBPGUi~aP^V8}(`tK_? zTjsyNp>ckH*l~u-lkd-FU&GR3J?%Z?$A7E0m6YC_!G5Iaa^yU7JG%$gS-v(#v&Ghz z&wl>B;s1^r_W23Bm)fS!78cOfZa+F#Rp8@Z#!j}VDRs{)i{4M}mCL=h>)q?M>Uka9 ziTbv(n?J4j{PA94{2q<#?&plX>y%H&$u}%od*S>orEsR-AD(@5Sy!MZZ@=%l&-abt zPU{adF|@a=+*hD<=H0Wm*J9P5olg7s!{*72&z78wfC5y-o4HH1iv%v ziSxN-enjE%-Rxr%xdM)=N_EdYX3QLY{AuL9=Q^IgxW7d8mhaHIUw0sz>8@qZ^q7kzm=tw8gjFx}oa$*+tpMr?0QE`fj^p_08yY->sj=WbbR4RMnqw;zYN{ zQM)77F*7pjqUPGfHEj@X^P4uAH?v$adC%*$+ICwR)WX{fziBh%HJ+Ec&UE8L|B-(! z=Vss0j%D`V+duR66GOxQoLY{}trLAX_MWo6vCZUu!SY*&YpfTDSLdz}oVWj5$Lx)% z{QQ>~&AzqSCVP8%OHYiGTQ|+-SMcLHlW!l-Uf)%5X!ksA`|oQ$Y_L1HY`bu{QOwVT z^c@@zrg+s0mvcYc+SQxjVV)p*>2w%F?a!m<9~FOAGBY+;I>h{M3PavD@fIG&jHF^l z$p^b__9*wsznbB=;hp+LTc(tVeG0$z_SD&CZ%T2{eo!aG%G_VZc#&<}_cZ6*kM1#^ zKKN~;^pUN1m=!zWr$=xdW<7dfh4vCXe>35d zQ^!l4oQ*$;{kossyA@*Sb2VP zN`L%udW)cW#bYJEnE!fjZ0u$0j>G+!(m^sKbwXK^ilH5|w zzvTr_nYZ(Zw&{MLxbiGkbIM=y_(EN^lHdEM0WOt8T%IgO5UWH-7dvpbYlW%QNhk^*#eOaZh^US1y33ME;rx!xcI2BaN6?wHy81FCB_AOwzkqwDN#j(UV4JC9X(E4Qq2>hRWhYz4Onf{Ax8%W;mfD zVzOn~vomS&qVL;&9v11yD^F+cH($nQd-BAG)uuWYDaKk0=9V=6?Y@39CYDF!q$|hC zwet<<8Ji{UWH?*Nd4MX49m5S{e?@=da`al^Dnx zCbTVg1IzIVVvjs`C_iFtooM~3UAp1bbQgg>X1R?CEf0hyE-AV2Z0~EQuZ(UQOpm?? zUoR|Fe#7*p$u~QP_q`~CQioYWhoNxgg&DhKKK-jZz%Itx`*}-;bgcdd)?;z)4S5f8 zl-)wKrI}gp{}cI;*>rqa;6pb-hvo*0MQ?8`N@~(hc+~dd=sH{HNv%uYtDpLJM!MTA z=Lb_kvd~0_uR3<#TRvo)@$NgZ_n=EqZuTMtf4PQG?j6?AW@2hTAF8IQ+$8Csz|LQD&zeo)T>VtANk~Q)DDFLwtLdrhFlCeN0MBewI8SkIEr<(*1PVQm3(Pl zzHQe5^BvZ8IkIbnquLDu&#X)G^^jj7d1#%|#3y?scs#!U3i!P1pw3pAveQu?Jet+D zT5czAj(yO)*NK1bjn|8xSbXGHk?-rj?_jjy%T4cw4m-t-k;)$aq89{=PH2dCx!CI{ z7L|rE6tW!Ot+;4H?T?=aUUL=A`TO9cQZ;+#6-k8&>gNI?ZgsiNIX?gJyZv=4|7N|6 z@|dG)(t9R;;{MO94=#w#a0nM@zWC5#mQdUUyTm!>f*)Kw80{zNJG{CY68Sad&YrDL z-OYDAn9jm^Rpr3MQ^MyS_peyV@qR%o%X|g1GgSgxuV+p;HQ|2OZ>A-3&o&q(z08)= zNZ?9m+x)zvc8Pgmf12}!xE48&-`bWA0STO|g*gxBwlFf@7oOesAvEJ5ug;RqYrn6M zW^^?Fwn9`SsdJ^{!j7ajJgbiU6))~d$yj2YXJ&q8t%D|S^Gi2%dEV8}4xQ#%B(nP8 z{wj8*i!TDo_jqz@uxp-RE|Z&Nk#NZ4%Fci}Hy9Ru@^eq*Juqi&)e_sUP7kJqHY}O$ z-aLQShMnqqU1z?0Sen$BB|rtby29U?uX-W+a;*|Sr@aMFJVhnuS9EI+p7 zzO1Nc*_?8ApOD>BwlrOS9m$8q`z-i61RvD)D>**%ec;ld9N_9b!PveaAlMAbZ97 zmLn-$TnCwzO?Z`B^E3~cZ)@pz>~^NNk6n6Agw7kD>V^*4*QXhk>famRQ(}5DKVSGn z(7O$`OIWV|b+DTm>?t7Had+8+#gcDsEG|)LQjlPlZ`|~`iEmco+4I|llvO0w_)TBR z`@GmOTJBJx^z4gw6|NmN{#v6lvGB^?-)}_owPxPz40yCOg~yDitSD1L>dvD2iT@8Z z&7W2i#qj80-!`Ab$|-v|zjz+0*jCB=smTzDHw2P@!2>Y$v*k9saB9TV*OWvV>i+>OqHa|BKqtft^RFO zlqel(@J=Ygl2M>U@^&AG3eVAFkLE``{KUYZR}{NJLSu*e@$X@OX7JR1y7N}pAouvs zQ&ormT`X$x+QTl~S+_^!Bn$9%(z`x;Bl<_{+p zmWWRc-nEeF?u_%3(pp=lt<^i0xmw-iu!ySCqPcGMW_63UU1(Tf^UB-7>CV@rv{i>c z1}s?U|41zGDMR$Js}Dc#NKn1N=<|bHt@hx5&Z+Awr)`m!Ue*3LbOzJ4dO^waUM)<^ zR+|bQNp9iNTF@-Uyr!!{uI0K4@ASQK9ENN5thIjD*u6kV!GZH>Q;LksgleH*ZdNx= zb2YMv>J=Yhe1H6g&$fAo4pfx!eNdHg$=Q5Ibc6DS#~;79W~3gMlX$kbPw%7a8Fx$l zM6KmNmT-L$*6@*@amjHKLwQ5;jhmBM=Q-SIm_GL;ug7n5`AxigE}FbBV>qPpNRlh+ zY|#??_3GN4p9LBtuEkYw+o?ahGd=UA@*InQcDx^Ui0wYj#WR8F-^s%vFIjp5Iay*) zZ&%k)&yiWLI4Nd*Q~`+axoI=Jw618}bjCDlR?X zA33@2a#HI7tB=bkf*bwg`upS<3Xg{7UpV)FeQ_pT7Q^!H9lq9I+Oj8 zVZjVH9rY#qx;@sgKRL13d2>gfxC}36_rD9-c`6e%Yn=G!%vs&A_^m;BpPKagdgU#P zjz{p?%xO5Y*OA-zwZK8eB{n@5J$!lWTYl$s_dodV{YIXDj%?n^qdT3hF&C_M^|(Kk zsll&s>CKQI>UNFUQ*!yQ&k8eH&}6oOv4m@uy4?lA7FVZF`W$9O+qmb}tbMTJM~;q> zk(s0t=YPk91In+N7rIy-Kb(DEZn5C{=lia5{{8B^V=^0OBJX6TXPaykg=;|QEV@~0?dZy;c%8G~(bG=&rBOH5} zULNpGTD``nxnq-a)sxh+b_f09wk40H-yXQFSS)vLjrg}S-|usLPkcUKSAo&$iC4fS z#ret`o_;h=V>e{9NSYilWlO{xJ^P$_2Ahv5Zk7t*@XveI{dUG_RlQHfcNt=9_9*(g z9e$u;p_;h$-Q&;{op=1_b8qWBx}5Sz<5F+8+OdYc>vEl^FO=>$F(-Oo^p_KguX$Wf zJU%AO$*vH|v+;D{N39i8=1q>%?b+^VazN|7I#)!?%X|8hc`~+h7G|^ESFX)W|6}<> zhK>9DjdQirE+?j07$j6SPH%Y>BKG~k&S^y|4|KYG-c`$FS!y)?{&FbM*2u|Bj4AEp z_1qW7T*GH++CTqv?ZMs)tC`+C_}|-cU4PFj%LfY%x*Fx)tQ2uvx^eC%9w(C-iyAlY zVM~9vevuNx6*W)B=lb(lyz?JSR5w@a-<`gbQTnh&)55!xxhCuHJQ7i+@>= zaFw~M`tX5lexnbUzdTJ{D-_jWt-5d5y^OCM6B{4;Zfyj#P{AJDs88Pg}~ct4)HVbwl~_N(LrP(LIb^8|=iErpGEoG8wIZ^xx=SU8D*VbNa;t zwbx1_9UQhuz1O+P5IOa?;HsPc9&8;;&!uP27G)2WKFt2@;!Y9!L&w*$C{FX{!{ z7=M%EH!e%B&hM|T&gGNHX?ydg{q#Hz_NAc-f;+?-g+jaCD&m{0O8O53Ut;W98+)Fy zl4W|ttM4ygwr%XuU8(WrtK|``KXS!VHZUtU$DLQKZN2%qHS$M{*0W{2 zk3`S4f1G<@W{jk{w81)4)vs&V%GQ6KeSUuV8~3A@9~c6DO?94P&mF9h{GLPebj%q! zK5^loB-hC&FS$+-;ryX4`*C9ltI@etz4?db7U?}<4G1dAaK5l!qqu|f*5Nu$ms8oX zf;v2N*-pvuo7wp+*gwyq<&vI^6t_-9nTbxw*&jvs1Lks0+7S@7c|+l6_i*LvbH9JA zunOa6nbP>BvSA6!wL`@X8||N%?wL}4d5QArGTv)5W3%TfGtIy=<-G=QCUG8{0l^*|7PbdCd01-M!9#r<_{1L#v~{ zZS}MDW*r)bluYN<+8#P{);F@ua_Pa(c9V7{Dm_iU={k`;enR`{R~tf8zb!actmOEs z!81X@SW|m*TgFzm2B$0BfyuEmJ!d@(U8R2d;$h_nQWvIr`yR2jE1UVk#Ana38w;M> zyjbG4#JD$@-|5)pHH$w?n8bZo_W9GcBS)knCf~mD?6QO4p4Q$K{Yw*epJ1|xn&m{=_NlNH@cmP5y5qeOwd{*!a#{`RQWm2X(m_JErUBRNHO;nIpI8$6_1J7x|kO z75snjERJt;WDD1}1&9Bh;ZFMZW@qH)UBwy&>!kSl?fpd;)=aLQzvJWq_Z`(`lLW4P zx6Io5>cJE@0l#oN+vU7A+gMtUYKVR?SKjhesqM+kn-#W3CoeYdHEv#+eeji|3dhl& zyN_d14j6^sJ^O3VGsb((6D)1#z5f~`mUEy!AvVo0?0|n@f|E)=L*pKfe`_{Lrj|^e z*_^so_|39D=39KChiCZrYkXL?M#fZd#lfeskC}hjNhU5eU}E3h?l{-{j^>$%KW1sB&tr1FJZs_m# zx3k${U$3_P`Wm5tu1u zBrW^4P1?++D(1(x?ds!MSvVf{=q}jwSnbI)kppImzYkriPJWm=(^O55SzW*^Z|wo+ zH4HUhZx+t-+jnkj_n~W*EpxLwSFDqj4N$WXJ;GOWB<$kn zME#c^&%L$ZKP^LF;7X0Mx8=~76g|1~4OQ;jbvIq$BdSAjmFXwNnf$z*?SsX{i3s1{( zUcaf6QWJY@(vDk;52)oSvkAE0G?I~!4QQ2`{kO?SXo9fG4A-Wf#%5I+^F>12kFWeV z^Pfn~hUnDGwyc(GC8U1~Xw1Df`@x}&$B$h*SoFHDHgfL=Hm7f|rQ&b)?pnyb=1{nC z)%^e8HfC4bNV05O~HlZ=JWb4DEz-O6JN4+L-`~P}% zz2Vs={vO--+wW)D9B<~USYfvIbMUT%_c?YhcvMrq$-|>z(cRDUbKd@#ywUfCW`7=! zV%*jFvRwZ!zFQwJZu_s7_4#?-bMotVG(J#Y_^9yLbnDtpoSS~~rLc4GR9lqgO^=Wc zsD9;k&*IDE8$bIdDR95xmU+?G5-Zqi^B}{ZK3TBi0#jJRF*N~&_-w;hY~~;4yDPdq zT4~`rH#}$yd%vqq3g&O;gU}X;A9qoSn{}FVm{TyRj>=UjEp} zou>^yzBzBjRFM2svc+=`$K3^j5~GY;`U5Z z^|eWH4!kz)K@_jjEuD#l&AVNh)$OF0J>*bLczZfK$$WRf+mpu*-tYf!`?v0!b>!~0 zHNP*P{;~V!$}$m{d@db_BZ4Ir#~ZRTI|~+G?B}9PpN3gii$;THy0mI zY~8c@N@v3DH)6X5*`mBV7Ds+ljyUvdt(C6$wcI7$;pWe$uS?tREM#_e>HPUQx3sJN zlzZPBaIv$ z`mN}3HcedU$;khF!z7L^y@KXD%e-4Huf-j*et1_oLOXZT`&*@2`8}oI6?sf0|a{IreQ6tuIw;>r2$JF7%)Z2ZwDu+c|J z#O;$Jv!ZyGp^lmNn)jKDG=DHJ$lU&V+48~}tF*+}{8qbhs3acDn{~?4KJ{L*iNS`= z4#f_0CfbL$+=i?|8>r% z_`~mhvJJ18)IQ?~f42Rbibsh$rfcQii^c^P%=cdS(`!nEaS!)1;r`>RPBjP}jJNf9 zUEnokb>Yn?EHPq1L2O5uVwadWy{l6A@aD^}+sfxoe_Q*w*zc;{p!cn(OC*z$VtW%LMXsR^3*3CN*=`{t2xTzn{oiy;>j={Xk3VoC{aCt<;QU z);?K5xkz*27h<`GDnBR9{Um(;sGVI!m&}y+-P2jp52*>TuFUkeU+{Qq>^#oGLYs<3 zD^G=dJXbio@YJ_^I)aU_V`?j>)jPg>sDE+M%~wA?6)v^yez9%!-dYXwhlXOuLp=3c zW}FE15tL5rE`8~5a=52IW|L^f%bzo*S>1~`FD~)b;eSiqnajez=N#~TcV$sbwpZo7 z4pyb}aX-4&F!}Pg%1pENEv|Km>JT@`;Z*Yb5Tz4zRv_vlC(m`hk4lH`g}Jic&e?D= zqt(tWN1Zv&%f+l~L16{=ULn5C70ZIdQo_$aUMn~ud`o*;R$KX|e_tFq_OM=ST_-s& zV#((NQCV-ME^RG~EZ`DOJ$o$g`it$AYro~pHe6_b>iWg!)ed3#>nayZTs+I3aAt4S zxwB#ne3IR5B?tfd{PnX4TFz4k z*OTvfrDGX3dL8*1aQ}${e}rw(tLW4vU89k9&{8&m<4K%V@Hwp; zQ>$)@eu_wo{=U#6JW+FxGLQM{Wv^>;*w)=y*QzQRd7^b)p6eFZL{DpVdO!Zs9 zFmFxQO+U8!sVD8$>x5Ni9X)+++Tta@8*S#~en`lAAyu_IjCFU#od8=arIfGYO9~@z zSe%ZvUeR`6`QbG$*X2)w+V^I!Y>EEc-EqI|;BOVNTn;~vW9KWxKi%7O`pH7abw9MF z{ykw|>-?gyJGt89^VBzITpt{{d*k~wdx2ZqHrqe`{Gz`7IkULyq9^6^U!L14cUffN zqAMJN-)FjezHOg$-mb|)1&@w@I%~KT;{~E>PTsp*=vJ03zhh;`NFk3^sMRKe*|tFRgiG6{ zCaKhj`mW1-($^vzw?|FR%a}O%u3^vG`Fi~`uTRgO{pwfJ%=6DSb67D?aP3!=zj)cU ziLK3wd44&!@V)O&BDvFq9v#0bcfp?Tj48DRQ1cZ!cOW8;0$Fe$cq={ENkV&2ENhXLBrS{m$j9 zI!ToCrriFEZK*bge>XH$DI^LVesuX_UlwcfKA&%W-@dY z>OPp}e9obvntzSdyHwMCI}J^`p7OUW%Hh1Wgt;gEV}+{nn%sE%7w9KpRqlti`g+RHRNi^>+5$c_MHoRv4z!G`TAi= z_T1+Re$2Aso-E=0m+zIG>^0}zJo$4gN4+Oc0PigEeNo@8f7`7s`&-F{Q#`c|HI7s~@j#>t_2op8J2h zcI*D!qaUmFAF$TPFWRqjVg1va&(}YE?(j_k8Ki z^o`%(bF}E!+4*J-%lQw~KbUR)pY;Lr=NkSQUsxKsHeK1Z_1*&3!;bn3SLsa2f2+2u zu-8k3{kFahgXAm!`}4Nw`g`3@Jm{=?pxph`8PSg9c?&HIm&ES*ovm1U_v~MPjcMzJ z&Qw~iZ|u2s+GOFjgKb{vkvGaNl}$aky(IIxgxYlHu3t+&$?mZ_^UA88i)W$eMYpfr z*8BKF4nKa$D)s1*S%Z*v(z+YAi&6>`w|PvcetJFFp*rmFvZ(@&O64neN1VT;)%iJD z@Znzm#``jF?>?zrpQ8SH*MSoStDHGA6c}ZevH5>3|7zCF zpS)c8_0VH#aT)V}hR&G&*gCX2FHG##X0%S8$sD{fXP|_G6RmLf)tD@3`r>+w9xVT?ftE zf)|BeYHisVceO~&XXV`j50CD%`WPbL+%V=T%M1zpDr~%# zJ*wAyca@dRuS?U)J}+FBTWPLxn(5Xew+}n6nQ6{AR(0xK(ecuK&k~;;(tCQyr!Oh@ z-PS{~UF|QJ6O+_keUJ2KE#2_cn9J>UPlCJ-_$#fHy5EENl(& zYtFkaP8OS|Ubxu%;^ET*(Q+U2-)5eD>e=%BhejmBt^Tapsw=vgCZ4;Z#I}G zh}AK4#q;GHysB_`1Jgg7ITu+3ZV1i$7QWn9W2v^Kiqwg_7u*x`BQu`&U$LFY_;$nj zX8khd2D6776s~&RGCw<2Pc=wKN+zc1c9V(w?j=znYx$pKHhV-H*{Z}Rti0*A!qrMA zrTyF8J4as~6w=e~59hq`OmAJuo(p>CZtbnwZNYu}_Vk-itNct(7t1~UXz+jIW14K=2j%#yBf-|u{d{T_I1teY$q1J%Xu1Q=yp8)LF4hag(l~_u54G}{$;;LS50c! zHt(Qv(>IF^mG5?Jd~ik5RM7uS-0OGKc0N6<6~Xdp)2guSoy#A4IZRGtUHrA~e(k%i zwfpb?$rDNnRft;AU+Q#qjsN(uDh5^K+NV3ElMG zcKZCWa+!QSTUWV9f#>GA?EJ>^yZmi*a^u?1w`^C3O+Nip*JS7PxobDeuUPF~^LH_G z%^N<`CHrKv4)Sf?lB_Pat)2a8;Um_L16t>9K0bX^?B0Kq3zwZX96us#(fF!R!~0!r zy5MH(D7+)vFdE&Oz$W8RZFE9bZ5 zm^fa)wf1?F%9iem+YbqnTZPQMXp%K6{=%KK}}8M7@8 zolju@@NeUlOV=KKy_KJHlx0VHRNk!kl;Y`)d#CI2e6D?9V45d#uIzaH!&{q!~8=!RfQ-fAdqmW3w%;)BStI0hOCa z_x5G`b0mD6$Cvz(LD#PAtvokZ*0zK96crfXJFzM`bzbFuwDHlqAVVFd$Ei1NC$q8e zR-I}s&+dP0+}GG>zj7|?)6Ak77A&(iweAl1==w4`I%jRXd22iSw7Bx4e=;|vPQP`o z%Jz84p+4thSB_RbZ};6)YS$-k&0}#k)7&Z?19 z(+vvjeE3SBjbmC;e%jg#-q-APSJ%3m#y_0c?A|21wX#6pa*HJEtzGWNwzMDrD)WxB zPndJh)h$6ULTmkZOKo_$i+xVU@%5ji4y_Y_)TRE-do6PG_}y7Ya+!98$!zqucz1ro&Yd#5QuB{2{=vU~ zarzsT!$0}*SPtw8y#9h;a?#qSeoacNV*gB8>3rh*Io6s9Zw1U8CN|idN|imLJ3+Q^ zdvj{(ila)+2E3Ls7Q8wff9}3r>6*&As`NS2-I)$&7S$g8b?}|M4)?LLTO91JTX{>< zHZZL_xi0Z_X;oCB``4Yf&lvE`oc&k&`tCK4+7n!~%Xf#a`SvT{NPhq0Lnoh=DREmx za%foI2>1JTg;YPLMGw*qna`(MWnR)r2&g)6h&5HzI zKD4*bNV2cI_k=^levPZzp&r{uj?!JY`=5h1${IBdGW+=oZLCfn5KOCB&?&d*(S{DO4ae>E zFXvBqef*x&wv>#-mP3h`%5OA(j*^Kkbn*{~tvUZxzM8doyTs8(3A3A#aSWArc=!BB zP7=S&9DmxQRG!Z;^Pc~jVZGm; z`z4&yET;zt7rlDbRQCK$i>$>%zhJ^9A}1$Hm)db9t~f@R!tI~G3=zxuIMd5+8> zhVNaU-6Ykt8{Py4AJ`sS)?Itc+mqwPxvNvn7w256+_LMo_^)@5QUs6PIn1Z0cq)bS z!;yU({7yd;F)vUSzLuYSFn9rT%Z`ivGMkE-uQeFln=V>jb|j3&xn#ldwKGm;ta}(T zr7<<)a{F^#9j0Yd&)-ci*_?fJpTU}4@rLh|wq(h=DW1|iH$iHZ(!sBH54U`>JGZG; zMQB1KSMcKRclq}`_#@f-u_U)7){*2{XT#%tr^*>xv@wcWyd<=WM9O>5C6zE-SX4)BU;fMOoLW*E-W} zjTV$%JJ83{{m9?va?<+zMF*pO?Z1BPO)A{+F!Sb@f@Nv^Hg#;T6?>JpJ!uq-j{Era z_t%o<*6q_o zr60#bJ}#=fEhi^tW05JA#~|9^x8-p5)Yr4)u04K|`Ki?EzO<_Qf?j{u)Mm4m&mFBI z-i9G=M_(=e^5!SIy=ck>>8AgC*|l=zrhV&a^9_Cbyt^wo_)+u)Hfx@?$(^GU7hQA~3=`0jl{wnPYROad)QYc+DQWiXd0S+>zEyc-L|@5Uw=MJS&tns; z_Ulwx@!8Kdy0VsA*ZpDmFLFK!XL#qon_^MU;KD`;=ym5hyPrfH_Vv;R{^|Vcw%ytX=nQxRn&*psZTfv`s*_LNFMXB!A zD*3keYQl#n?{s<|yqs`&-M(omvrB$t&Rsv_%)Z_fmLP0EhP=WlkC)K<9F3ya4xcpF_?O(1YCTotX`f@M7*jm0h zb5-sC+p3~^kB5&7`fWcx;Rs`WH*;a~)w^MDUmf77yYn^Oq*FF@JCEiBKuhVm=5R~{9;kf)((${K>&bzi}nZCdH@c7TW&xP&rikde*&-SQh zTqt0D_U4LD&o5f@E$Tcc{qe?MNj`^jVqtyR7A5D)jPvYQFeh8>Yb;&K?q5-ml3tT^ zq(bM3lB8|7FV~+JYd3|-Se0)`?DW$;@PdQUMqY1|`<$Zd6>E7L3WS_iI1A4TcyCy2 zzJx*V+oId8XD5sHzYtupBW2ElQkyrED+2fO?d}%H{wx&MvvB40y|1$aLf*e~4!co( zqNd;9-!@{?+4#!~3mn#_p5MGcHYb)<;|$aKr+419)$N-zHHP6wm&_g?Q_H{1mxA=q zh|Fb?^}R6ju+wawxwHIiPw{QuoOP!*VPQbjjOC``JJQduYCP-wW8+S5@3U45mR;mw zm-&BrQ|0{d*p|g}j_W;I5wY7*<-~r6GcwHf%Yq!vG_(buJ6pEyo|DC`c~Z|#7%%z3 zTYZjqPh`2X*qnJU_M0_W>FtS*S-XBtn%b_^$=0`Oi%t9M=VkMDSVf;>c6fi(aYosZ zw~jLs4j4^m7y8Sy{ZzsG$K5^Kqz*3D5ieY1xK6;amgRF^Vd?v6YN?hQvp?Qam^0T$Esx+yC!8fNGHAs zdoIp2RbBRk-pRF#H_2`GI#<=aRN`fB;#|q4TtBNiWTfz_j zSrENpa{2jS_v-mA7pHyyAhml{;r7hw$qnq5XTB6%>s}@=Dy8ttRA%MNj)NIO``^}O z{HRu%_FF~k_~}^>Y-KJy(Kz$z+<8Npd*zn#7s^hRzxlqmZoj_8G+`#rlGcUCmp$Fw znW!G8BUA9`J*%X}jVkBnV&{EnnRhOk3BNe?L(Qgf!ShO}wxmn0MN#dtw>8>dKZxF~4?s@g?F)hMDLYr8!P}{m+~8+2%dJRXE|*2feWE z?{+r*^I~N#+BB)_`$rd6)>-c}Pd+WZ%^oIuE2%xb@u#rHb-y=erp>j}&TZTwcYa$= zG-uzH9l6WhuLxV}-CcA&NAX&N*89l&?-Q+06@OfOa7tO$^5^HhC!M=*JdM5JtQhl~ z!)!6><(uA%Z>Xx)V&%0rcek@S*i^G|@$ZgD+KtDjISctj#6QxoPTZF#^moDU0?Q>! zFWs7>nO5;Tnyv4_hpKE}+by#$PD`4XDsU?_>%`Js>T=g(CraAXTh^y-+^>A-OTwY* zd-JYd_?+l{cD(T)sM>RPsN%z$Y|ac^;jWo#*@n(1H;CGs<>|-c6kI|L&gCl3&dB%

)ggu|HAC8?>7-A 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 diff --git a/lnbits/extensions/subdomains/tasks.py b/lnbits/extensions/subdomains/tasks.py deleted file mode 100644 index 42b7885a..00000000 --- a/lnbits/extensions/subdomains/tasks.py +++ /dev/null @@ -1,65 +0,0 @@ -import asyncio - -import httpx -from loguru import logger - -from lnbits.core.models import Payment -from lnbits.helpers import get_current_extension_name -from lnbits.tasks import register_invoice_listener - -from .cloudflare import cloudflare_create_subdomain -from .crud import get_domain, set_subdomain_paid - - -async def wait_for_paid_invoices(): - invoice_queue = asyncio.Queue() - register_invoice_listener(invoice_queue, get_current_extension_name()) - - while True: - payment = await invoice_queue.get() - await on_invoice_paid(payment) - - -async def on_invoice_paid(payment: Payment) -> None: - if payment.extra.get("tag") != "lnsubdomain": - # not an lnsubdomain invoice - return - - await payment.set_pending(False) - subdomain = await set_subdomain_paid(payment_hash=payment.payment_hash) - domain = await get_domain(subdomain.domain) - - ### Create subdomain - try: - cf_response = await cloudflare_create_subdomain( - domain=domain, # type: ignore - subdomain=subdomain.subdomain, - record_type=subdomain.record_type, - ip=subdomain.ip, - ) - except Exception as exc: - logger.error(exc) - logger.error("could not create subdomain on cloudflare") - return - - ### Use webhook to notify about cloudflare registration - if domain and domain.webhook: - async with httpx.AsyncClient() as client: - try: - r = await client.post( - domain.webhook, - json={ - "domain": subdomain.domain_name, - "subdomain": subdomain.subdomain, - "record_type": subdomain.record_type, - "email": subdomain.email, - "ip": subdomain.ip, - "cost:": str(subdomain.sats) + " sats", - "duration": str(subdomain.duration) + " days", - "cf_response": cf_response, - }, - timeout=40, - ) - assert r - except AssertionError: - pass diff --git a/lnbits/extensions/subdomains/templates/subdomains/_api_docs.html b/lnbits/extensions/subdomains/templates/subdomains/_api_docs.html deleted file mode 100644 index 035d67a6..00000000 --- a/lnbits/extensions/subdomains/templates/subdomains/_api_docs.html +++ /dev/null @@ -1,31 +0,0 @@ - - - -

- lnSubdomains: Get paid sats to sell your subdomains -
-

- Charge people for using your subdomain name...
- -
More details -
- - Created by, - Kris -

- - - - diff --git a/lnbits/extensions/subdomains/templates/subdomains/display.html b/lnbits/extensions/subdomains/templates/subdomains/display.html deleted file mode 100644 index f11c9ddc..00000000 --- a/lnbits/extensions/subdomains/templates/subdomains/display.html +++ /dev/null @@ -1,221 +0,0 @@ -{% extends "public.html" %} {% block page %} -
-
- - -

{{ domain_domain }}

-
-
{{ domain_desc }}
-
- - - - - - - - - -

- Cost per day: {{ domain_cost }} sats
- {% raw %} Total cost: {{amountSats}} sats {% endraw %} -

-
- Submit - Cancel -
-
-
-
-
- - - - - - -
- Copy invoice - Close -
-
-
-
- -{% endblock %} {% block scripts %} - -{% endblock %} diff --git a/lnbits/extensions/subdomains/templates/subdomains/index.html b/lnbits/extensions/subdomains/templates/subdomains/index.html deleted file mode 100644 index a39773e7..00000000 --- a/lnbits/extensions/subdomains/templates/subdomains/index.html +++ /dev/null @@ -1,549 +0,0 @@ -{% extends "base.html" %} {% from "macros.jinja" import window_vars with context -%} {% block page %} - -
-
- - - New Domain - - - - - -
-
-
Domains
-
-
- Export to CSV -
-
- - {% raw %} - - - {% endraw %} - -
-
- - - -
-
-
Subdomains
-
-
- Export to CSV -
-
- - {% raw %} - - - {% endraw %} - -
-
-
-
- - -
- {{SITE_TITLE}} Subdomain extension -
-
- - - {% include "subdomains/_api_docs.html" %} - -
-
- - - - - - - - - - - - - - - - -
- Update Form - Create Domain - Cancel -
-
-
-
-
- -{% endblock %} {% block scripts %} {{ window_vars(user) }} - -{% endblock %} diff --git a/lnbits/extensions/subdomains/util.py b/lnbits/extensions/subdomains/util.py deleted file mode 100644 index 9265e870..00000000 --- a/lnbits/extensions/subdomains/util.py +++ /dev/null @@ -1,32 +0,0 @@ -import re -import socket - - -# Function to validate domain name. -def isValidDomain(str): - # Regex to check valid - # domain name. - regex = "^((?!-)[A-Za-z0-9-]{1,63}(?