Merge pull request #93 from lnbits/quart
This commit is contained in:
commit
dbabf937c4
84 changed files with 1027 additions and 962 deletions
10
.env.example
10
.env.example
|
|
@ -1,12 +1,16 @@
|
||||||
FLASK_APP=lnbits.app
|
QUART_APP=lnbits.app
|
||||||
FLASK_ENV=development
|
QUART_ENV=development
|
||||||
|
QUART_DEBUG=true
|
||||||
|
|
||||||
|
HOST=127.0.0.1
|
||||||
|
PORT=5000
|
||||||
|
|
||||||
LNBITS_SITE_TITLE=LNbits
|
LNBITS_SITE_TITLE=LNbits
|
||||||
LNBITS_ALLOWED_USERS=""
|
LNBITS_ALLOWED_USERS=""
|
||||||
LNBITS_DEFAULT_WALLET_NAME="LNbits wallet"
|
LNBITS_DEFAULT_WALLET_NAME="LNbits wallet"
|
||||||
LNBITS_DATA_FOLDER="/your_custom_data_folder" #IMPORTANT! i.e. "/home/satoshi/lnbits/lnbits/data"
|
LNBITS_DATA_FOLDER="/your_custom_data_folder" #IMPORTANT! i.e. "/home/satoshi/lnbits/lnbits/data"
|
||||||
LNBITS_DISABLED_EXTENSIONS="amilk"
|
LNBITS_DISABLED_EXTENSIONS="amilk"
|
||||||
LNBITS_FORCE_HTTPS=1
|
LNBITS_FORCE_HTTPS=true
|
||||||
LNBITS_SERVICE_FEE="0.0"
|
LNBITS_SERVICE_FEE="0.0"
|
||||||
|
|
||||||
# Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, LndWallet (gRPC), LndRestWallet, CLightningWallet, LnbitsWallet
|
# Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, LndWallet (gRPC), LndRestWallet, CLightningWallet, LnbitsWallet
|
||||||
|
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -30,3 +30,4 @@ venv
|
||||||
__bundle__
|
__bundle__
|
||||||
|
|
||||||
node_modules
|
node_modules
|
||||||
|
lnbits/static/bundle.*
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ FROM python:3.7-slim
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY requirements.txt /app/
|
COPY requirements.txt /app/
|
||||||
RUN pip install --no-cache-dir -q -r requirements.txt
|
RUN pip install --no-cache-dir -q -r requirements.txt
|
||||||
RUN pip install --no-cache-dir -q gunicorn gevent
|
RUN pip install --no-cache-dir -q hypercorn
|
||||||
COPY . /app
|
COPY . /app
|
||||||
|
|
||||||
EXPOSE 5000
|
EXPOSE 5000
|
||||||
|
|
|
||||||
5
Makefile
5
Makefile
|
|
@ -1,4 +1,4 @@
|
||||||
all: format check
|
all: format check requirements.txt
|
||||||
|
|
||||||
format: prettier black
|
format: prettier black
|
||||||
|
|
||||||
|
|
@ -18,3 +18,6 @@ checkprettier: $(shell find lnbits -name "*.js" -name ".html")
|
||||||
|
|
||||||
checkblack: $(shell find lnbits -name "*.py")
|
checkblack: $(shell find lnbits -name "*.py")
|
||||||
./venv/bin/black --check lnbits
|
./venv/bin/black --check lnbits
|
||||||
|
|
||||||
|
requirements.txt: Pipfile.lock
|
||||||
|
cat Pipfile.lock | jq -r '.default | map_values(.version) | to_entries | map("\(.key)\(.value)") | join("\n")' > requirements.txt
|
||||||
|
|
|
||||||
14
Pipfile
14
Pipfile
|
|
@ -11,19 +11,19 @@ bitstring = "*"
|
||||||
cerberus = "*"
|
cerberus = "*"
|
||||||
ecdsa = "*"
|
ecdsa = "*"
|
||||||
environs = "*"
|
environs = "*"
|
||||||
flask = "*"
|
|
||||||
flask-assets = "*"
|
|
||||||
flask-compress = "*"
|
|
||||||
flask-cors = "*"
|
|
||||||
flask-talisman = "*"
|
|
||||||
lnurl = "*"
|
lnurl = "*"
|
||||||
pyscss = "*"
|
pyscss = "*"
|
||||||
requests = "*"
|
requests = "*"
|
||||||
shortuuid = "*"
|
shortuuid = "*"
|
||||||
|
quart = "*"
|
||||||
|
quart-cors = "*"
|
||||||
|
quart-compress = "*"
|
||||||
|
secure = "*"
|
||||||
|
typing-extensions = "*"
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
black = "==20.8b1"
|
black = "==20.8b1"
|
||||||
flake8 = "*"
|
|
||||||
flake8-mypy = "*"
|
|
||||||
pytest = "*"
|
pytest = "*"
|
||||||
pytest-cov = "*"
|
pytest-cov = "*"
|
||||||
|
pytest-asyncio = "*"
|
||||||
|
mypy = "==0.761"
|
||||||
|
|
|
||||||
311
Pipfile.lock
generated
311
Pipfile.lock
generated
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "2270f2525e54e976b09491e458033d25ec5bbdea9e74d417e787df33031c6948"
|
"sha256": "775e2ea809508c83df4696bba3a38a03e288cba22f0a7f562120230f40351ab9"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
|
@ -16,6 +16,13 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"default": {
|
"default": {
|
||||||
|
"aiofiles": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:377fdf7815cc611870c59cbd07b68b180841d2a2b79812d8c218be02448c2acb",
|
||||||
|
"sha256:98e6bcfd1b50f97db4980e182ddd509b7cc35909e903a8fe50d8849e02d815af"
|
||||||
|
],
|
||||||
|
"version": "==0.5.0"
|
||||||
|
},
|
||||||
"bech32": {
|
"bech32": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:7d6db8214603bd7871fcfa6c0826ef68b85b0abd90fa21c285a9c5e21d2bd899",
|
"sha256:7d6db8214603bd7871fcfa6c0826ef68b85b0abd90fa21c285a9c5e21d2bd899",
|
||||||
|
|
@ -31,6 +38,12 @@
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==3.1.7"
|
"version": "==3.1.7"
|
||||||
},
|
},
|
||||||
|
"blinker": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:471aee25f3992bd325afa3772f1063dbdbbca947a041b8b89466dc00d606f8b6"
|
||||||
|
],
|
||||||
|
"version": "==1.4"
|
||||||
|
},
|
||||||
"brotli": {
|
"brotli": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:160c78292e98d21e73a4cc7f76a234390e516afcd982fa17e1422f7c6a9ce9c8",
|
"sha256:160c78292e98d21e73a4cc7f76a234390e516afcd982fa17e1422f7c6a9ce9c8",
|
||||||
|
|
@ -108,44 +121,41 @@
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==8.0.0"
|
"version": "==8.0.0"
|
||||||
},
|
},
|
||||||
"flask": {
|
"h11": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060",
|
"sha256:311dc5478c2568cc07262e0381cdfc5b9c6ba19775905736c87e81ae6662b9fd",
|
||||||
"sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"
|
"sha256:9eecfbafc980976dbff26a01dd3487644dd5d00f8038584451fc64a660f7c502"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"version": "==0.10.0"
|
||||||
"version": "==1.1.2"
|
|
||||||
},
|
},
|
||||||
"flask-assets": {
|
"h2": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:1dfdea35e40744d46aada72831f7613d67bf38e8b20ccaaa9e91fdc37aa3b8c2",
|
"sha256:61e0f6601fa709f35cdb730863b4e5ec7ad449792add80d1410d4174ed139af5",
|
||||||
"sha256:2845bd3b479be9db8556801e7ebc2746ce2d9edb4e7b64a1c786ecbfc1e5867b"
|
"sha256:875f41ebd6f2c44781259005b157faed1a5031df3ae5aa7bcb4628a6c0782f14"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"version": "==3.2.0"
|
||||||
"version": "==2.0"
|
|
||||||
},
|
},
|
||||||
"flask-compress": {
|
"hpack": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:f367b2b46003dd62be34f7fb1379938032656dca56377a9bc90e7188e4289a7c"
|
"sha256:0edd79eda27a53ba5be2dfabf3b15780928a0dff6eb0c60a3d6767720e970c89",
|
||||||
|
"sha256:8eec9c1f4bfae3408a3f30500261f7e6a65912dc138526ea054f9ad98892e9d2"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"version": "==3.0.0"
|
||||||
"version": "==1.5.0"
|
|
||||||
},
|
},
|
||||||
"flask-cors": {
|
"hypercorn": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:6bcfc100288c5d1bcb1dbb854babd59beee622ffd321e444b05f24d6d58466b8",
|
"sha256:19f32e7267225c8108ad585b2c5deddf1fe75950797a0e87a682a3a00ef1af95",
|
||||||
"sha256:cee4480aaee421ed029eaa788f4049e3e26d15b5affb6a880dade6bafad38324"
|
"sha256:809d77f3bf9fa0794a598d8dfa0f8d889e7e1c2f927581cd33068803169dc474"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"markers": "python_version >= '3.7'",
|
||||||
"version": "==3.0.9"
|
"version": "==0.10.2"
|
||||||
},
|
},
|
||||||
"flask-talisman": {
|
"hyperframe": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:468131464a249274ed226efc21b372518f442487e58918ccab8357eaa638fd1f",
|
"sha256:5187962cb16dcc078f23cb5a4b110098d546c3f41ff2d4038a9896893bbd0b40",
|
||||||
"sha256:eaa754f4b771dfbe473843391d69643b79e3a38c865790011ac5e4179c68e3ec"
|
"sha256:a9f5c17f2cc3c719b917c4f33ed1c61bd1f8dfac4b1bd23b7c80b3400971b41f"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"version": "==5.2.0"
|
||||||
"version": "==0.7.0"
|
|
||||||
},
|
},
|
||||||
"idna": {
|
"idna": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
@ -219,11 +229,18 @@
|
||||||
},
|
},
|
||||||
"marshmallow": {
|
"marshmallow": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:67bf4cae9d3275b3fc74bd7ff88a7c98ee8c57c94b251a67b031dc293ecc4b76",
|
"sha256:2272273505f1644580fbc66c6b220cc78f893eb31f1ecde2af98ad28011e9811",
|
||||||
"sha256:a2a5eefb4b75a3b43f05be1cca0b6686adf56af7465c3ca629e5ad8d1e1fe13d"
|
"sha256:47911dd7c641a27160f0df5fd0fe94667160ffe97f70a42c3cc18388d86098cc"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.5'",
|
"markers": "python_version >= '3.5'",
|
||||||
"version": "==3.7.1"
|
"version": "==3.8.0"
|
||||||
|
},
|
||||||
|
"priority": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:6bc1961a6d7fcacbfc337769f1a382c8e746566aaa365e78047abe9f66b2ffbe",
|
||||||
|
"sha256:be4fcb94b5e37cdeb40af5533afe6dd603bd665fe9c8b3052610fc1001d5d1eb"
|
||||||
|
],
|
||||||
|
"version": "==1.3.0"
|
||||||
},
|
},
|
||||||
"pydantic": {
|
"pydantic": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
@ -262,6 +279,30 @@
|
||||||
],
|
],
|
||||||
"version": "==0.14.0"
|
"version": "==0.14.0"
|
||||||
},
|
},
|
||||||
|
"quart": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:9c634e4c1e4b21b824003c676de1583581258c72b0ac4d2ba747db846e97ff56",
|
||||||
|
"sha256:d885d782edd9d5dcfd2c4a56e020db3b82493d4c3950f91c221b7d88d239ac93"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.13.1"
|
||||||
|
},
|
||||||
|
"quart-compress": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:41cd0cc8d26905a45025ddda7022461a71b9d1d950b21b006dc106a1c41c75ef",
|
||||||
|
"sha256:63af5e6370aa7850fb219d22e1db89965aeb13b8f27bc83e7f9a44118faa3c54"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.2.1"
|
||||||
|
},
|
||||||
|
"quart-cors": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:020a17d504264db86cada3c1335ef174af28b33f57cee321ddc46d69c33d5c8e",
|
||||||
|
"sha256:c08bdb326219b6c186d19ed6a97a7fd02de8fe36c7856af889494c69b525c53c"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.3.0"
|
||||||
|
},
|
||||||
"requests": {
|
"requests": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b",
|
"sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b",
|
||||||
|
|
@ -270,6 +311,14 @@
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==2.24.0"
|
"version": "==2.24.0"
|
||||||
},
|
},
|
||||||
|
"secure": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:4dc8dd4b548831c3ad7f94079332c41d67c781eccc32215ff5a8a49582c1a447",
|
||||||
|
"sha256:b3bf1e39ebf40040fc3248392343a5052aa14cb45fc87ec91b0bd11f19cc46bd"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.2.1"
|
||||||
|
},
|
||||||
"shortuuid": {
|
"shortuuid": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:3c11d2007b915c43bee3e10625f068d8a349e04f0d81f08f5fa08507427ebf1f",
|
"sha256:3c11d2007b915c43bee3e10625f068d8a349e04f0d81f08f5fa08507427ebf1f",
|
||||||
|
|
@ -286,13 +335,20 @@
|
||||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
"version": "==1.15.0"
|
"version": "==1.15.0"
|
||||||
},
|
},
|
||||||
|
"toml": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f",
|
||||||
|
"sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"
|
||||||
|
],
|
||||||
|
"version": "==0.10.1"
|
||||||
|
},
|
||||||
"typing-extensions": {
|
"typing-extensions": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918",
|
"sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918",
|
||||||
"sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c",
|
"sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c",
|
||||||
"sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"
|
"sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"
|
||||||
],
|
],
|
||||||
"markers": "python_version < '3.8'",
|
"index": "pypi",
|
||||||
"version": "==3.7.4.3"
|
"version": "==3.7.4.3"
|
||||||
},
|
},
|
||||||
"urllib3": {
|
"urllib3": {
|
||||||
|
|
@ -303,13 +359,6 @@
|
||||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
|
||||||
"version": "==1.25.10"
|
"version": "==1.25.10"
|
||||||
},
|
},
|
||||||
"webassets": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:167132337677c8cedc9705090f6d48da3fb262c8e0b2773b29f3352f050181cd",
|
|
||||||
"sha256:a31a55147752ba1b3dc07dee0ad8c8efff274464e08bbdb88c1fd59ffd552724"
|
|
||||||
],
|
|
||||||
"version": "==2.0"
|
|
||||||
},
|
|
||||||
"werkzeug": {
|
"werkzeug": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43",
|
"sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43",
|
||||||
|
|
@ -317,6 +366,14 @@
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||||
"version": "==1.0.1"
|
"version": "==1.0.1"
|
||||||
|
},
|
||||||
|
"wsproto": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:614798c30e5dc2b3f65acc03d2d50842b97621487350ce79a80a711229edfa9d",
|
||||||
|
"sha256:e3d190a11d9307112ba23bbe60055604949b172143969c8f641318476a9b6f1d"
|
||||||
|
],
|
||||||
|
"markers": "python_full_version >= '3.6.1'",
|
||||||
|
"version": "==0.15.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"develop": {
|
"develop": {
|
||||||
|
|
@ -329,16 +386,15 @@
|
||||||
},
|
},
|
||||||
"attrs": {
|
"attrs": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a",
|
"sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594",
|
||||||
"sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff"
|
"sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
"version": "==20.1.0"
|
"version": "==20.2.0"
|
||||||
},
|
},
|
||||||
"black": {
|
"black": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea",
|
"sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"
|
||||||
"sha256:70b62ef1527c950db59062cda342ea224d772abdf6adc58b86a45421bab20a6b"
|
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==20.8b1"
|
"version": "==20.8b1"
|
||||||
|
|
@ -353,67 +409,43 @@
|
||||||
},
|
},
|
||||||
"coverage": {
|
"coverage": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:098a703d913be6fbd146a8c50cc76513d726b022d170e5e98dc56d958fd592fb",
|
"sha256:0203acd33d2298e19b57451ebb0bed0ab0c602e5cf5a818591b4918b1f97d516",
|
||||||
"sha256:16042dc7f8e632e0dcd5206a5095ebd18cb1d005f4c89694f7f8aafd96dd43a3",
|
"sha256:0f313707cdecd5cd3e217fc68c78a960b616604b559e9ea60cc16795c4304259",
|
||||||
"sha256:1adb6be0dcef0cf9434619d3b892772fdb48e793300f9d762e480e043bd8e716",
|
"sha256:1c6703094c81fa55b816f5ae542c6ffc625fec769f22b053adb42ad712d086c9",
|
||||||
"sha256:27ca5a2bc04d68f0776f2cdcb8bbd508bbe430a7bf9c02315cd05fb1d86d0034",
|
"sha256:1d44bb3a652fed01f1f2c10d5477956116e9b391320c94d36c6bf13b088a1097",
|
||||||
"sha256:28f42dc5172ebdc32622a2c3f7ead1b836cdbf253569ae5673f499e35db0bac3",
|
"sha256:280baa8ec489c4f542f8940f9c4c2181f0306a8ee1a54eceba071a449fb870a0",
|
||||||
"sha256:2fcc8b58953d74d199a1a4d633df8146f0ac36c4e720b4a1997e9b6327af43a8",
|
"sha256:29a6272fec10623fcbe158fdf9abc7a5fa032048ac1d8631f14b50fbfc10d17f",
|
||||||
"sha256:304fbe451698373dc6653772c72c5d5e883a4aadaf20343592a7abb2e643dae0",
|
"sha256:2b31f46bf7b31e6aa690d4c7a3d51bb262438c6dcb0d528adde446531d0d3bb7",
|
||||||
"sha256:30bc103587e0d3df9e52cd9da1dd915265a22fad0b72afe54daf840c984b564f",
|
"sha256:2d43af2be93ffbad25dd959899b5b809618a496926146ce98ee0b23683f8c51c",
|
||||||
"sha256:40f70f81be4d34f8d491e55936904db5c527b0711b2a46513641a5729783c2e4",
|
"sha256:381ead10b9b9af5f64646cd27107fb27b614ee7040bb1226f9c07ba96625cbb5",
|
||||||
"sha256:4186fc95c9febeab5681bc3248553d5ec8c2999b8424d4fc3a39c9cba5796962",
|
"sha256:47a11bdbd8ada9b7ee628596f9d97fbd3851bd9999d398e9436bd67376dbece7",
|
||||||
"sha256:46794c815e56f1431c66d81943fa90721bb858375fb36e5903697d5eef88627d",
|
"sha256:4d6a42744139a7fa5b46a264874a781e8694bb32f1d76d8137b68138686f1729",
|
||||||
"sha256:4869ab1c1ed33953bb2433ce7b894a28d724b7aa76c19b11e2878034a4e4680b",
|
"sha256:50691e744714856f03a86df3e2bff847c2acede4c191f9a1da38f088df342978",
|
||||||
"sha256:4f6428b55d2916a69f8d6453e48a505c07b2245653b0aa9f0dee38785939f5e4",
|
"sha256:530cc8aaf11cc2ac7430f3614b04645662ef20c348dce4167c22d99bec3480e9",
|
||||||
"sha256:52f185ffd3291196dc1aae506b42e178a592b0b60a8610b108e6ad892cfc1bb3",
|
"sha256:582ddfbe712025448206a5bc45855d16c2e491c2dd102ee9a2841418ac1c629f",
|
||||||
"sha256:538f2fd5eb64366f37c97fdb3077d665fa946d2b6d95447622292f38407f9258",
|
"sha256:63808c30b41f3bbf65e29f7280bf793c79f54fb807057de7e5238ffc7cc4d7b9",
|
||||||
"sha256:64c4f340338c68c463f1b56e3f2f0423f7b17ba6c3febae80b81f0e093077f59",
|
"sha256:71b69bd716698fa62cd97137d6f2fdf49f534decb23a2c6fc80813e8b7be6822",
|
||||||
"sha256:675192fca634f0df69af3493a48224f211f8db4e84452b08d5fcebb9167adb01",
|
"sha256:7858847f2d84bf6e64c7f66498e851c54de8ea06a6f96a32a1d192d846734418",
|
||||||
"sha256:700997b77cfab016533b3e7dbc03b71d33ee4df1d79f2463a318ca0263fc29dd",
|
"sha256:78e93cc3571fd928a39c0b26767c986188a4118edc67bc0695bc7a284da22e82",
|
||||||
"sha256:8505e614c983834239f865da2dd336dcf9d72776b951d5dfa5ac36b987726e1b",
|
"sha256:7f43286f13d91a34fadf61ae252a51a130223c52bfefb50310d5b2deb062cf0f",
|
||||||
"sha256:962c44070c281d86398aeb8f64e1bf37816a4dfc6f4c0f114756b14fc575621d",
|
"sha256:86e9f8cd4b0cdd57b4ae71a9c186717daa4c5a99f3238a8723f416256e0b064d",
|
||||||
"sha256:9e536783a5acee79a9b308be97d3952b662748c4037b6a24cbb339dc7ed8eb89",
|
"sha256:8f264ba2701b8c9f815b272ad568d555ef98dfe1576802ab3149c3629a9f2221",
|
||||||
"sha256:9ea749fd447ce7fb1ac71f7616371f04054d969d412d37611716721931e36efd",
|
"sha256:9342dd70a1e151684727c9c91ea003b2fb33523bf19385d4554f7897ca0141d4",
|
||||||
"sha256:a34cb28e0747ea15e82d13e14de606747e9e484fb28d63c999483f5d5188e89b",
|
"sha256:9361de40701666b034c59ad9e317bae95c973b9ff92513dd0eced11c6adf2e21",
|
||||||
"sha256:a3ee9c793ffefe2944d3a2bd928a0e436cd0ac2d9e3723152d6fd5398838ce7d",
|
"sha256:9669179786254a2e7e57f0ecf224e978471491d660aaca833f845b72a2df3709",
|
||||||
"sha256:aab75d99f3f2874733946a7648ce87a50019eb90baef931698f96b76b6769a46",
|
"sha256:aac1ba0a253e17889550ddb1b60a2063f7474155465577caa2a3b131224cfd54",
|
||||||
"sha256:b1ed2bdb27b4c9fc87058a1cb751c4df8752002143ed393899edb82b131e0546",
|
"sha256:aef72eae10b5e3116bac6957de1df4d75909fc76d1499a53fb6387434b6bcd8d",
|
||||||
"sha256:b360d8fd88d2bad01cb953d81fd2edd4be539df7bfec41e8753fe9f4456a5082",
|
"sha256:bd3166bb3b111e76a4f8e2980fa1addf2920a4ca9b2b8ca36a3bc3dedc618270",
|
||||||
"sha256:b8f58c7db64d8f27078cbf2a4391af6aa4e4767cc08b37555c4ae064b8558d9b",
|
"sha256:c1b78fb9700fc961f53386ad2fd86d87091e06ede5d118b8a50dea285a071c24",
|
||||||
"sha256:c1bbb628ed5192124889b51204de27c575b3ffc05a5a91307e7640eff1d48da4",
|
"sha256:c3888a051226e676e383de03bf49eb633cd39fc829516e5334e69b8d81aae751",
|
||||||
"sha256:c2ff24df02a125b7b346c4c9078c8936da06964cc2d276292c357d64378158f8",
|
"sha256:c5f17ad25d2c1286436761b462e22b5020d83316f8e8fcb5deb2b3151f8f1d3a",
|
||||||
"sha256:c890728a93fffd0407d7d37c1e6083ff3f9f211c83b4316fae3778417eab9811",
|
"sha256:c851b35fc078389bc16b915a0a7c1d5923e12e2c5aeec58c52f4aa8085ac8237",
|
||||||
"sha256:c96472b8ca5dc135fb0aa62f79b033f02aa434fb03a8b190600a5ae4102df1fd",
|
"sha256:cb7df71de0af56000115eafd000b867d1261f786b5eebd88a0ca6360cccfaca7",
|
||||||
"sha256:ce7866f29d3025b5b34c2e944e66ebef0d92e4a4f2463f7266daa03a1332a651",
|
"sha256:cedb2f9e1f990918ea061f28a0f0077a07702e3819602d3507e2ff98c8d20636",
|
||||||
"sha256:e26c993bd4b220429d4ec8c1468eca445a4064a61c74ca08da7429af9bc53bb0"
|
"sha256:e8caf961e1b1a945db76f1b5fa9c91498d15f545ac0ababbe575cfab185d3bd8"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
|
||||||
"version": "==5.2.1"
|
"version": "==5.3"
|
||||||
},
|
|
||||||
"flake8": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c",
|
|
||||||
"sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"version": "==3.8.3"
|
|
||||||
},
|
|
||||||
"flake8-mypy": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:47120db63aff631ee1f84bac6fe8e64731dc66da3efc1c51f85e15ade4a3ba18",
|
|
||||||
"sha256:cff009f4250e8391bf48990093cff85802778c345c8449d6498b62efefeebcbc"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"version": "==17.8.0"
|
|
||||||
},
|
|
||||||
"importlib-metadata": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83",
|
|
||||||
"sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070"
|
|
||||||
],
|
|
||||||
"markers": "python_version < '3.8'",
|
|
||||||
"version": "==1.7.0"
|
|
||||||
},
|
},
|
||||||
"iniconfig": {
|
"iniconfig": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
@ -422,13 +454,6 @@
|
||||||
],
|
],
|
||||||
"version": "==1.0.1"
|
"version": "==1.0.1"
|
||||||
},
|
},
|
||||||
"mccabe": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
|
|
||||||
"sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
|
|
||||||
],
|
|
||||||
"version": "==0.6.1"
|
|
||||||
},
|
|
||||||
"more-itertools": {
|
"more-itertools": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:6f83822ae94818eae2612063a5101a7311e68ae8002005b5e05f03fd74a86a20",
|
"sha256:6f83822ae94818eae2612063a5101a7311e68ae8002005b5e05f03fd74a86a20",
|
||||||
|
|
@ -439,23 +464,23 @@
|
||||||
},
|
},
|
||||||
"mypy": {
|
"mypy": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2c6cde8aa3426c1682d35190b59b71f661237d74b053822ea3d748e2c9578a7c",
|
"sha256:0a9a45157e532da06fe56adcfef8a74629566b607fa2c1ac0122d1ff995c748a",
|
||||||
"sha256:3fdda71c067d3ddfb21da4b80e2686b71e9e5c72cca65fa216d207a358827f86",
|
"sha256:2c35cae79ceb20d47facfad51f952df16c2ae9f45db6cb38405a3da1cf8fc0a7",
|
||||||
"sha256:5dd13ff1f2a97f94540fd37a49e5d255950ebcdf446fb597463a40d0df3fac8b",
|
"sha256:4b9365ade157794cef9685791032521233729cb00ce76b0ddc78749abea463d2",
|
||||||
"sha256:6731603dfe0ce4352c555c6284c6db0dc935b685e9ce2e4cf220abe1e14386fd",
|
"sha256:53ea810ae3f83f9c9b452582261ea859828a9ed666f2e1ca840300b69322c474",
|
||||||
"sha256:6bb93479caa6619d21d6e7160c552c1193f6952f0668cdda2f851156e85186fc",
|
"sha256:634aef60b4ff0f650d3e59d4374626ca6153fcaff96ec075b215b568e6ee3cb0",
|
||||||
"sha256:81c7908b94239c4010e16642c9102bfc958ab14e36048fa77d0be3289dda76ea",
|
"sha256:7e396ce53cacd5596ff6d191b47ab0ea18f8e0ec04e15d69728d530e86d4c217",
|
||||||
"sha256:9c7a9a7ceb2871ba4bac1cf7217a7dd9ccd44c27c2950edbc6dc08530f32ad4e",
|
"sha256:7eadc91af8270455e0d73565b8964da1642fe226665dd5c9560067cd64d56749",
|
||||||
"sha256:a4a2cbcfc4cbf45cd126f531dedda8485671545b43107ded25ce952aac6fb308",
|
"sha256:7f672d02fffcbace4db2b05369142e0506cdcde20cea0e07c7c2171c4fd11dd6",
|
||||||
"sha256:b7fbfabdbcc78c4f6fc4712544b9b0d6bf171069c6e0e3cb82440dd10ced3406",
|
"sha256:85baab8d74ec601e86134afe2bcccd87820f79d2f8d5798c889507d1088287bf",
|
||||||
"sha256:c05b9e4fb1d8a41d41dec8786c94f3b95d3c5f528298d769eb8e73d293abc48d",
|
"sha256:87c556fb85d709dacd4b4cb6167eecc5bbb4f0a9864b69136a0d4640fdc76a36",
|
||||||
"sha256:d7df6eddb6054d21ca4d3c6249cae5578cb4602951fd2b6ee2f5510ffb098707",
|
"sha256:a6bd44efee4dc8c3324c13785a9dc3519b3ee3a92cada42d2b57762b7053b49b",
|
||||||
"sha256:e0b61738ab504e656d1fe4ff0c0601387a5489ca122d55390ade31f9ca0e252d",
|
"sha256:c6d27bd20c3ba60d5b02f20bd28e20091d6286a699174dfad515636cb09b5a72",
|
||||||
"sha256:eff7d4a85e9eea55afa34888dfeaccde99e7520b51f867ac28a48492c0b1130c",
|
"sha256:e2bb577d10d09a2d8822a042a23b8d62bc3b269667c9eb8e60a6edfa000211b1",
|
||||||
"sha256:f05644db6779387ccdb468cc47a44b4356fc2ffa9287135d05b70a98dc83b89a"
|
"sha256:f97a605d7c8bc2c6d1172c2f0d5a65b24142e11a58de689046e62c2d632ca8c1"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.5'",
|
"index": "pypi",
|
||||||
"version": "==0.782"
|
"version": "==0.761"
|
||||||
},
|
},
|
||||||
"mypy-extensions": {
|
"mypy-extensions": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
@ -495,22 +520,6 @@
|
||||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
"version": "==1.9.0"
|
"version": "==1.9.0"
|
||||||
},
|
},
|
||||||
"pycodestyle": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367",
|
|
||||||
"sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"
|
|
||||||
],
|
|
||||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
|
||||||
"version": "==2.6.0"
|
|
||||||
},
|
|
||||||
"pyflakes": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92",
|
|
||||||
"sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"
|
|
||||||
],
|
|
||||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
|
||||||
"version": "==2.2.0"
|
|
||||||
},
|
|
||||||
"pyparsing": {
|
"pyparsing": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
|
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
|
||||||
|
|
@ -521,11 +530,19 @@
|
||||||
},
|
},
|
||||||
"pytest": {
|
"pytest": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4",
|
"sha256:0e37f61339c4578776e090c3b8f6b16ce4db333889d65d0efb305243ec544b40",
|
||||||
"sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad"
|
"sha256:c8f57c2a30983f469bf03e68cdfa74dc474ce56b8f280ddcb080dfd91df01043"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==6.0.1"
|
"version": "==6.0.2"
|
||||||
|
},
|
||||||
|
"pytest-asyncio": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:2eae1e34f6c68fc0a9dc12d4bea190483843ff4708d24277c41568d6b6044f1d",
|
||||||
|
"sha256:9882c0c6b24429449f5f969a5158b528f39bde47dc32e85b9f0403965017e700"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.14.0"
|
||||||
},
|
},
|
||||||
"pytest-cov": {
|
"pytest-cov": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
@ -608,16 +625,8 @@
|
||||||
"sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c",
|
"sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c",
|
||||||
"sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"
|
"sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"
|
||||||
],
|
],
|
||||||
"markers": "python_version < '3.8'",
|
"index": "pypi",
|
||||||
"version": "==3.7.4.3"
|
"version": "==3.7.4.3"
|
||||||
},
|
|
||||||
"zipp": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b",
|
|
||||||
"sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"
|
|
||||||
],
|
|
||||||
"markers": "python_version >= '3.6'",
|
|
||||||
"version": "==3.1.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
Procfile
2
Procfile
|
|
@ -1 +1 @@
|
||||||
web: gunicorn -b :5000 lnbits:app -k gevent
|
web: hypercorn --bind 0.0.0.0:5000 lnbits:app
|
||||||
|
|
|
||||||
4
app.json
4
app.json
|
|
@ -1,7 +1,5 @@
|
||||||
{
|
{
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dokku": {
|
"dokku": {}
|
||||||
"predeploy": "flask migrate"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,23 +42,19 @@ Take a look at [Polar][polar] for an excellent way of spinning up a Lightning Ne
|
||||||
Running the server
|
Running the server
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
LNbits uses [Flask][flask] as an application server.
|
LNbits uses [Quart][quart] as an application server.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ pipenv run python -m lnbits
|
$ pipenv run python -m lnbits
|
||||||
```
|
```
|
||||||
|
|
||||||
There is an environment variable called `FLASK_ENV` that has to be set to `development`
|
|
||||||
if you want to run Flask in debug mode with autoreload
|
|
||||||
|
|
||||||
|
|
||||||
Frontend
|
Frontend
|
||||||
--------
|
--------
|
||||||
|
|
||||||
The frontend uses [Vue.js and Quasar][quasar].
|
The frontend uses [Vue.js and Quasar][quasar].
|
||||||
|
|
||||||
|
|
||||||
[flask]: http://flask.pocoo.org/
|
[quart]: https://pgjones.gitlab.io/
|
||||||
[pipenv]: https://pipenv.pypa.io/
|
[pipenv]: https://pipenv.pypa.io/
|
||||||
[polar]: https://lightningpolar.com/
|
[polar]: https://lightningpolar.com/
|
||||||
[quasar]: https://quasar.dev/start/how-to-use-vue
|
[quasar]: https://quasar.dev/start/how-to-use-vue
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
from .app import create_app
|
from .app import create_app
|
||||||
from .commands import migrate_databases
|
from .commands import migrate_databases, transpile_scss, bundle_vendored
|
||||||
|
|
||||||
|
|
||||||
migrate_databases()
|
migrate_databases()
|
||||||
|
transpile_scss()
|
||||||
|
bundle_vendored()
|
||||||
|
|
||||||
app = create_app()
|
app = create_app()
|
||||||
app.run()
|
app.run(host=app.config["HOST"], port=app.config["PORT"])
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,31 @@
|
||||||
import importlib
|
import importlib
|
||||||
|
|
||||||
from flask import Flask, g
|
from quart import Quart, g
|
||||||
from flask_assets import Bundle # type: ignore
|
from quart_cors import cors # type: ignore
|
||||||
from flask_cors import CORS # type: ignore
|
from quart_compress import Compress # type: ignore
|
||||||
from flask_talisman import Talisman # type: ignore
|
from secure import SecureHeaders # type: ignore
|
||||||
from werkzeug.middleware.proxy_fix import ProxyFix
|
|
||||||
|
|
||||||
from .commands import flask_migrate
|
from .commands import db_migrate
|
||||||
from .core import core_app
|
from .core import core_app
|
||||||
from .db import open_db
|
from .db import open_db
|
||||||
from .ext import assets, compress
|
from .helpers import get_valid_extensions, get_js_vendored, get_css_vendored, url_for_vendored
|
||||||
from .helpers import get_valid_extensions
|
from .proxy_fix import ProxyFix
|
||||||
|
|
||||||
|
secure_headers = SecureHeaders(hsts=False)
|
||||||
|
|
||||||
|
|
||||||
def create_app(config_object="lnbits.settings") -> Flask:
|
def create_app(config_object="lnbits.settings") -> Quart:
|
||||||
"""Create application factory, as explained here: http://flask.pocoo.org/docs/patterns/appfactories/.
|
"""Create application factory.
|
||||||
:param config_object: The configuration object to use.
|
:param config_object: The configuration object to use.
|
||||||
"""
|
"""
|
||||||
app = Flask(__name__, static_folder="static")
|
app = Quart(__name__, static_folder="static")
|
||||||
app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1, x_host=1) # type: ignore
|
|
||||||
app.config.from_object(config_object)
|
app.config.from_object(config_object)
|
||||||
|
|
||||||
register_flask_extensions(app)
|
cors(app)
|
||||||
|
Compress(app)
|
||||||
|
ProxyFix(app, x_proto=1, x_host=1)
|
||||||
|
|
||||||
|
register_assets(app)
|
||||||
register_blueprints(app)
|
register_blueprints(app)
|
||||||
register_filters(app)
|
register_filters(app)
|
||||||
register_commands(app)
|
register_commands(app)
|
||||||
|
|
@ -30,7 +34,7 @@ def create_app(config_object="lnbits.settings") -> Flask:
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
def register_blueprints(app) -> None:
|
def register_blueprints(app: Quart) -> None:
|
||||||
"""Register Flask blueprints / LNbits extensions."""
|
"""Register Flask blueprints / LNbits extensions."""
|
||||||
app.register_blueprint(core_app)
|
app.register_blueprint(core_app)
|
||||||
|
|
||||||
|
|
@ -42,48 +46,42 @@ def register_blueprints(app) -> None:
|
||||||
raise ImportError(f"Please make sure that the extension `{ext.code}` follows conventions.")
|
raise ImportError(f"Please make sure that the extension `{ext.code}` follows conventions.")
|
||||||
|
|
||||||
|
|
||||||
def register_commands(app):
|
def register_commands(app: Quart):
|
||||||
"""Register Click commands."""
|
"""Register Click commands."""
|
||||||
app.cli.add_command(flask_migrate)
|
app.cli.add_command(db_migrate)
|
||||||
|
|
||||||
|
|
||||||
def register_flask_extensions(app):
|
def register_assets(app: Quart):
|
||||||
"""Register Flask extensions."""
|
"""Serve each vendored asset separately or a bundle."""
|
||||||
"""If possible we use the .init_app() option so that Blueprints can also use extensions."""
|
|
||||||
CORS(app)
|
|
||||||
Talisman(
|
|
||||||
app,
|
|
||||||
force_https=app.config["FORCE_HTTPS"],
|
|
||||||
content_security_policy={
|
|
||||||
"default-src": [
|
|
||||||
"'self'",
|
|
||||||
"'unsafe-eval'",
|
|
||||||
"'unsafe-inline'",
|
|
||||||
"blob:",
|
|
||||||
"api.opennode.co",
|
|
||||||
]
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
assets.init_app(app)
|
@app.before_request
|
||||||
assets.register("base_css", Bundle("scss/base.scss", filters="pyscss", output="css/base.css"))
|
async def vendored_assets_variable():
|
||||||
compress.init_app(app)
|
if app.config["DEBUG"]:
|
||||||
|
g.VENDORED_JS = map(url_for_vendored, get_js_vendored())
|
||||||
|
g.VENDORED_CSS = map(url_for_vendored, get_css_vendored())
|
||||||
|
else:
|
||||||
|
g.VENDORED_JS = ["/static/bundle.js"]
|
||||||
|
g.VENDORED_CSS = ["/static/bundle.css"]
|
||||||
|
|
||||||
|
|
||||||
def register_filters(app):
|
def register_filters(app: Quart):
|
||||||
"""Jinja filters."""
|
"""Jinja filters."""
|
||||||
app.jinja_env.globals["DEBUG"] = app.config["DEBUG"]
|
|
||||||
app.jinja_env.globals["EXTENSIONS"] = get_valid_extensions()
|
|
||||||
app.jinja_env.globals["SITE_TITLE"] = app.config["LNBITS_SITE_TITLE"]
|
app.jinja_env.globals["SITE_TITLE"] = app.config["LNBITS_SITE_TITLE"]
|
||||||
|
app.jinja_env.globals["EXTENSIONS"] = get_valid_extensions()
|
||||||
|
|
||||||
|
|
||||||
def register_request_hooks(app):
|
def register_request_hooks(app: Quart):
|
||||||
"""Open the core db for each request so everything happens in a big transaction"""
|
"""Open the core db for each request so everything happens in a big transaction"""
|
||||||
|
|
||||||
@app.before_request
|
@app.before_request
|
||||||
def before_request():
|
async def before_request():
|
||||||
g.db = open_db()
|
g.db = open_db()
|
||||||
|
|
||||||
|
@app.after_request
|
||||||
|
async def set_secure_headers(response):
|
||||||
|
secure_headers.quart(response)
|
||||||
|
return response
|
||||||
|
|
||||||
@app.teardown_request
|
@app.teardown_request
|
||||||
def after_request(exc):
|
async def after_request(exc):
|
||||||
g.db.__exit__(type(exc), exc, None)
|
g.db.__exit__(type(exc), exc, None)
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,47 @@
|
||||||
import click
|
import click
|
||||||
import importlib
|
import importlib
|
||||||
import re
|
import re
|
||||||
|
import os
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
|
||||||
|
from scss.compiler import compile_string # type: ignore
|
||||||
|
|
||||||
from .core import migrations as core_migrations
|
from .core import migrations as core_migrations
|
||||||
from .db import open_db, open_ext_db
|
from .db import open_db, open_ext_db
|
||||||
from .helpers import get_valid_extensions
|
from .helpers import get_valid_extensions, get_css_vendored, get_js_vendored, url_for_vendored
|
||||||
|
from .settings import LNBITS_PATH
|
||||||
|
|
||||||
|
|
||||||
@click.command("migrate")
|
@click.command("migrate")
|
||||||
def flask_migrate():
|
def db_migrate():
|
||||||
migrate_databases()
|
migrate_databases()
|
||||||
|
|
||||||
|
|
||||||
|
@click.command("assets")
|
||||||
|
def handle_assets():
|
||||||
|
transpile_scss()
|
||||||
|
bundle_vendored()
|
||||||
|
|
||||||
|
|
||||||
|
def transpile_scss():
|
||||||
|
with open(os.path.join(LNBITS_PATH, "static/scss/base.scss")) as scss:
|
||||||
|
with open(os.path.join(LNBITS_PATH, "static/css/base.css"), "w") as css:
|
||||||
|
css.write(compile_string(scss.read()))
|
||||||
|
|
||||||
|
|
||||||
|
def bundle_vendored():
|
||||||
|
for getfiles, outputpath in [
|
||||||
|
(get_js_vendored, os.path.join(LNBITS_PATH, "static/bundle.js")),
|
||||||
|
(get_css_vendored, os.path.join(LNBITS_PATH, "static/bundle.css")),
|
||||||
|
]:
|
||||||
|
output = ""
|
||||||
|
for path in getfiles():
|
||||||
|
with open(path) as f:
|
||||||
|
output += "/* " + url_for_vendored(path) + " */\n" + f.read() + ";\n"
|
||||||
|
with open(outputpath, "w") as f:
|
||||||
|
f.write(output)
|
||||||
|
|
||||||
|
|
||||||
def migrate_databases():
|
def migrate_databases():
|
||||||
"""Creates the necessary databases if they don't exist already; or migrates them."""
|
"""Creates the necessary databases if they don't exist already; or migrates them."""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
from flask import Blueprint
|
from quart import Blueprint
|
||||||
|
|
||||||
|
|
||||||
core_app: Blueprint = Blueprint("core", __name__, template_folder="templates", static_folder="static")
|
core_app: Blueprint = Blueprint(
|
||||||
|
"core", __name__, template_folder="templates", static_folder="static", static_url_path="/core/static"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
from .views.api import * # noqa
|
from .views.api import * # noqa
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import json
|
||||||
import datetime
|
import datetime
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from typing import List, Optional, Dict
|
from typing import List, Optional, Dict
|
||||||
from flask import g
|
from quart import g
|
||||||
|
|
||||||
from lnbits import bolt11
|
from lnbits import bolt11
|
||||||
from lnbits.settings import DEFAULT_WALLET_NAME
|
from lnbits.settings import DEFAULT_WALLET_NAME
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from typing import Optional, Tuple, Dict
|
from typing import Optional, Tuple, Dict
|
||||||
from flask import g
|
from quart import g
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from typing import TypedDict # type: ignore
|
from typing import TypedDict # type: ignore
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
|
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
|
||||||
%} {% block scripts %} {{ window_vars(user) }} {% assets filters='rjsmin',
|
%} {% block scripts %} {{ window_vars(user) }}
|
||||||
output='__bundle__/core/extensions.js', 'core/js/extensions.js' %}
|
<script src="/core/static/js/extensions.js"></script>
|
||||||
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
|
{% endblock %} {% block page %}
|
||||||
{% endassets %} {% endblock %} {% block page %}
|
|
||||||
<div class="row q-col-gutter-md">
|
<div class="row q-col-gutter-md">
|
||||||
<div
|
<div
|
||||||
class="col-6 col-md-4 col-lg-3"
|
class="col-6 col-md-4 col-lg-3"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
{% extends "public.html" %} {% block scripts %} {% assets filters='rjsmin',
|
{% extends "public.html" %} {% block scripts %}
|
||||||
output='__bundle__/core/index.js', 'core/js/index.js' %}
|
<script src="/core/static/js/index.js"></script>
|
||||||
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
|
{% endblock %} {% block page %}
|
||||||
{% endassets %} {% endblock %} {% block page %}
|
|
||||||
<div class="row q-col-gutter-md justify-center">
|
<div class="row q-col-gutter-md justify-center">
|
||||||
<div class="col-12 col-md-7 col-lg-6 q-gutter-y-md">
|
<div class="col-12 col-md-7 col-lg-6 q-gutter-y-md">
|
||||||
<q-card>
|
<q-card>
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,7 @@
|
||||||
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
|
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
|
||||||
%} {% block styles %}
|
%} {% block scripts %} {{ window_vars(user, wallet) }}
|
||||||
<link
|
<script src="/core/static/js/wallet.js"></script>
|
||||||
rel="stylesheet"
|
{% endblock %} {% block page %}
|
||||||
type="text/css"
|
|
||||||
href="{{ url_for('static', filename='vendor/vue-qrcode-reader@2.2.0/vue-qrcode-reader.min.css') }}"
|
|
||||||
/>
|
|
||||||
{% endblock %} {% block scripts %} {{ window_vars(user, wallet) }}
|
|
||||||
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
|
|
||||||
{% assets filters='rjsmin', output='__bundle__/core/chart.js',
|
|
||||||
'vendor/moment@2.27.0/moment.min.js', 'vendor/chart.js@2.9.3/chart.min.js' %}
|
|
||||||
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
|
|
||||||
{% endassets %} {% assets filters='rjsmin', output='__bundle__/core/wallet.js',
|
|
||||||
'vendor/bolt11/utils.js', 'vendor/bolt11/decoder.js',
|
|
||||||
'vendor/vue-qrcode-reader@2.2.0/vue-qrcode-reader.min.js', 'core/js/wallet.js'
|
|
||||||
%}
|
|
||||||
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
|
|
||||||
{% endassets %} {% endblock %} {% block page %}
|
|
||||||
<div class="row q-col-gutter-md">
|
<div class="row q-col-gutter-md">
|
||||||
<div class="col-12 col-md-7 q-gutter-y-md">
|
<div class="col-12 col-md-7 q-gutter-y-md">
|
||||||
<q-card>
|
<q-card>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from flask import g, jsonify, request
|
from quart import g, jsonify, request
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from binascii import unhexlify
|
from binascii import unhexlify
|
||||||
|
|
||||||
|
|
@ -12,7 +12,7 @@ from lnbits.settings import WALLET
|
||||||
|
|
||||||
@core_app.route("/api/v1/payments", methods=["GET"])
|
@core_app.route("/api/v1/payments", methods=["GET"])
|
||||||
@api_check_wallet_key("invoice")
|
@api_check_wallet_key("invoice")
|
||||||
def api_payments():
|
async def api_payments():
|
||||||
if "check_pending" in request.args:
|
if "check_pending" in request.args:
|
||||||
delete_expired_invoices()
|
delete_expired_invoices()
|
||||||
|
|
||||||
|
|
@ -33,7 +33,7 @@ def api_payments():
|
||||||
"description_hash": {"type": "string", "empty": False, "required": True, "excludes": "memo"},
|
"description_hash": {"type": "string", "empty": False, "required": True, "excludes": "memo"},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def api_payments_create_invoice():
|
async def api_payments_create_invoice():
|
||||||
if "description_hash" in g.data:
|
if "description_hash" in g.data:
|
||||||
description_hash = unhexlify(g.data["description_hash"])
|
description_hash = unhexlify(g.data["description_hash"])
|
||||||
memo = ""
|
memo = ""
|
||||||
|
|
@ -65,7 +65,7 @@ def api_payments_create_invoice():
|
||||||
|
|
||||||
@api_check_wallet_key("admin")
|
@api_check_wallet_key("admin")
|
||||||
@api_validate_post_request(schema={"bolt11": {"type": "string", "empty": False, "required": True}})
|
@api_validate_post_request(schema={"bolt11": {"type": "string", "empty": False, "required": True}})
|
||||||
def api_payments_pay_invoice():
|
async def api_payments_pay_invoice():
|
||||||
try:
|
try:
|
||||||
payment_hash = pay_invoice(wallet_id=g.wallet.id, payment_request=g.data["bolt11"])
|
payment_hash = pay_invoice(wallet_id=g.wallet.id, payment_request=g.data["bolt11"])
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
|
@ -91,15 +91,15 @@ def api_payments_pay_invoice():
|
||||||
|
|
||||||
@core_app.route("/api/v1/payments", methods=["POST"])
|
@core_app.route("/api/v1/payments", methods=["POST"])
|
||||||
@api_validate_post_request(schema={"out": {"type": "boolean", "required": True}})
|
@api_validate_post_request(schema={"out": {"type": "boolean", "required": True}})
|
||||||
def api_payments_create():
|
async def api_payments_create():
|
||||||
if g.data["out"] is True:
|
if g.data["out"] is True:
|
||||||
return api_payments_pay_invoice()
|
return await api_payments_pay_invoice()
|
||||||
return api_payments_create_invoice()
|
return await api_payments_create_invoice()
|
||||||
|
|
||||||
|
|
||||||
@core_app.route("/api/v1/payments/<payment_hash>", methods=["GET"])
|
@core_app.route("/api/v1/payments/<payment_hash>", methods=["GET"])
|
||||||
@api_check_wallet_key("invoice")
|
@api_check_wallet_key("invoice")
|
||||||
def api_payment(payment_hash):
|
async def api_payment(payment_hash):
|
||||||
payment = g.wallet.get_payment(payment_hash)
|
payment = g.wallet.get_payment(payment_hash)
|
||||||
|
|
||||||
if not payment:
|
if not payment:
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from flask import g, abort, redirect, request, render_template, send_from_directory, url_for
|
from quart import g, abort, redirect, request, render_template, send_from_directory, url_for
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from os import path
|
from os import path
|
||||||
|
|
||||||
|
|
@ -16,19 +16,19 @@ from ..crud import (
|
||||||
|
|
||||||
|
|
||||||
@core_app.route("/favicon.ico")
|
@core_app.route("/favicon.ico")
|
||||||
def favicon():
|
async def favicon():
|
||||||
return send_from_directory(path.join(core_app.root_path, "static"), "favicon.ico")
|
return await send_from_directory(path.join(core_app.root_path, "static"), "favicon.ico")
|
||||||
|
|
||||||
|
|
||||||
@core_app.route("/")
|
@core_app.route("/")
|
||||||
def home():
|
async def home():
|
||||||
return render_template("core/index.html", lnurl=request.args.get("lightning", None))
|
return await render_template("core/index.html", lnurl=request.args.get("lightning", None))
|
||||||
|
|
||||||
|
|
||||||
@core_app.route("/extensions")
|
@core_app.route("/extensions")
|
||||||
@validate_uuids(["usr"], required=True)
|
@validate_uuids(["usr"], required=True)
|
||||||
@check_user_exists()
|
@check_user_exists()
|
||||||
def extensions():
|
async def extensions():
|
||||||
extension_to_enable = request.args.get("enable", type=str)
|
extension_to_enable = request.args.get("enable", type=str)
|
||||||
extension_to_disable = request.args.get("disable", type=str)
|
extension_to_disable = request.args.get("disable", type=str)
|
||||||
|
|
||||||
|
|
@ -40,12 +40,12 @@ def extensions():
|
||||||
elif extension_to_disable:
|
elif extension_to_disable:
|
||||||
update_user_extension(user_id=g.user.id, extension=extension_to_disable, active=0)
|
update_user_extension(user_id=g.user.id, extension=extension_to_disable, active=0)
|
||||||
|
|
||||||
return render_template("core/extensions.html", user=get_user(g.user.id))
|
return await render_template("core/extensions.html", user=get_user(g.user.id))
|
||||||
|
|
||||||
|
|
||||||
@core_app.route("/wallet")
|
@core_app.route("/wallet")
|
||||||
@validate_uuids(["usr", "wal"])
|
@validate_uuids(["usr", "wal"])
|
||||||
def wallet():
|
async def wallet():
|
||||||
user_id = request.args.get("usr", type=str)
|
user_id = request.args.get("usr", type=str)
|
||||||
wallet_id = request.args.get("wal", type=str)
|
wallet_id = request.args.get("wal", type=str)
|
||||||
wallet_name = request.args.get("nme", type=str)
|
wallet_name = request.args.get("nme", type=str)
|
||||||
|
|
@ -76,13 +76,15 @@ def wallet():
|
||||||
if wallet_id not in user.wallet_ids:
|
if wallet_id not in user.wallet_ids:
|
||||||
abort(HTTPStatus.FORBIDDEN, "Not your wallet.")
|
abort(HTTPStatus.FORBIDDEN, "Not your wallet.")
|
||||||
|
|
||||||
return render_template("core/wallet.html", user=user, wallet=user.get_wallet(wallet_id), service_fee=service_fee)
|
return await render_template(
|
||||||
|
"core/wallet.html", user=user, wallet=user.get_wallet(wallet_id), service_fee=service_fee
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@core_app.route("/deletewallet")
|
@core_app.route("/deletewallet")
|
||||||
@validate_uuids(["usr", "wal"], required=True)
|
@validate_uuids(["usr", "wal"], required=True)
|
||||||
@check_user_exists()
|
@check_user_exists()
|
||||||
def deletewallet():
|
async def deletewallet():
|
||||||
wallet_id = request.args.get("wal", type=str)
|
wallet_id = request.args.get("wal", type=str)
|
||||||
user_wallet_ids = g.user.wallet_ids
|
user_wallet_ids = g.user.wallet_ids
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from flask import abort, redirect, request, url_for
|
from quart import abort, redirect, request, url_for
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from lnurl import LnurlWithdrawResponse, handle as handle_lnurl # type: ignore
|
from lnurl import LnurlWithdrawResponse, handle as handle_lnurl # type: ignore
|
||||||
from lnurl.exceptions import LnurlException # type: ignore
|
from lnurl.exceptions import LnurlException # type: ignore
|
||||||
|
|
@ -13,7 +13,7 @@ from ..crud import create_account, get_user, create_wallet, create_payment
|
||||||
|
|
||||||
|
|
||||||
@core_app.route("/lnurlwallet")
|
@core_app.route("/lnurlwallet")
|
||||||
def lnurlwallet():
|
async def lnurlwallet():
|
||||||
memo = "LNbits LNURL funding"
|
memo = "LNbits LNURL funding"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from cerberus import Validator # type: ignore
|
from cerberus import Validator # type: ignore
|
||||||
from flask import g, abort, jsonify, request
|
from quart import g, abort, jsonify, request
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import List, Union
|
from typing import List, Union
|
||||||
|
|
@ -12,7 +12,7 @@ from lnbits.settings import LNBITS_ALLOWED_USERS
|
||||||
def api_check_wallet_key(key_type: str = "invoice"):
|
def api_check_wallet_key(key_type: str = "invoice"):
|
||||||
def wrap(view):
|
def wrap(view):
|
||||||
@wraps(view)
|
@wraps(view)
|
||||||
def wrapped_view(**kwargs):
|
async def wrapped_view(**kwargs):
|
||||||
try:
|
try:
|
||||||
g.wallet = get_wallet_for_key(request.headers["X-Api-Key"], key_type)
|
g.wallet = get_wallet_for_key(request.headers["X-Api-Key"], key_type)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
|
@ -24,7 +24,7 @@ def api_check_wallet_key(key_type: str = "invoice"):
|
||||||
if not g.wallet:
|
if not g.wallet:
|
||||||
return jsonify({"message": "Wrong keys."}), HTTPStatus.UNAUTHORIZED
|
return jsonify({"message": "Wrong keys."}), HTTPStatus.UNAUTHORIZED
|
||||||
|
|
||||||
return view(**kwargs)
|
return await view(**kwargs)
|
||||||
|
|
||||||
return wrapped_view
|
return wrapped_view
|
||||||
|
|
||||||
|
|
@ -34,7 +34,7 @@ def api_check_wallet_key(key_type: str = "invoice"):
|
||||||
def api_validate_post_request(*, schema: dict):
|
def api_validate_post_request(*, schema: dict):
|
||||||
def wrap(view):
|
def wrap(view):
|
||||||
@wraps(view)
|
@wraps(view)
|
||||||
def wrapped_view(**kwargs):
|
async def wrapped_view(**kwargs):
|
||||||
if "application/json" not in request.headers["Content-Type"]:
|
if "application/json" not in request.headers["Content-Type"]:
|
||||||
return (
|
return (
|
||||||
jsonify({"message": "Content-Type must be `application/json`."}),
|
jsonify({"message": "Content-Type must be `application/json`."}),
|
||||||
|
|
@ -42,7 +42,8 @@ def api_validate_post_request(*, schema: dict):
|
||||||
)
|
)
|
||||||
|
|
||||||
v = Validator(schema)
|
v = Validator(schema)
|
||||||
g.data = {key: request.json[key] for key in schema.keys() if key in request.json}
|
data = await request.get_json()
|
||||||
|
g.data = {key: data[key] for key in schema.keys() if key in data}
|
||||||
|
|
||||||
if not v.validate(g.data):
|
if not v.validate(g.data):
|
||||||
return (
|
return (
|
||||||
|
|
@ -50,7 +51,7 @@ def api_validate_post_request(*, schema: dict):
|
||||||
HTTPStatus.BAD_REQUEST,
|
HTTPStatus.BAD_REQUEST,
|
||||||
)
|
)
|
||||||
|
|
||||||
return view(**kwargs)
|
return await view(**kwargs)
|
||||||
|
|
||||||
return wrapped_view
|
return wrapped_view
|
||||||
|
|
||||||
|
|
@ -60,13 +61,13 @@ def api_validate_post_request(*, schema: dict):
|
||||||
def check_user_exists(param: str = "usr"):
|
def check_user_exists(param: str = "usr"):
|
||||||
def wrap(view):
|
def wrap(view):
|
||||||
@wraps(view)
|
@wraps(view)
|
||||||
def wrapped_view(**kwargs):
|
async def wrapped_view(**kwargs):
|
||||||
g.user = get_user(request.args.get(param, type=str)) or abort(HTTPStatus.NOT_FOUND, "User does not exist.")
|
g.user = get_user(request.args.get(param, type=str)) or abort(HTTPStatus.NOT_FOUND, "User does not exist.")
|
||||||
|
|
||||||
if LNBITS_ALLOWED_USERS and g.user.id not in LNBITS_ALLOWED_USERS:
|
if LNBITS_ALLOWED_USERS and g.user.id not in LNBITS_ALLOWED_USERS:
|
||||||
abort(HTTPStatus.UNAUTHORIZED, "User not authorized.")
|
abort(HTTPStatus.UNAUTHORIZED, "User not authorized.")
|
||||||
|
|
||||||
return view(**kwargs)
|
return await view(**kwargs)
|
||||||
|
|
||||||
return wrapped_view
|
return wrapped_view
|
||||||
|
|
||||||
|
|
@ -76,7 +77,7 @@ def check_user_exists(param: str = "usr"):
|
||||||
def validate_uuids(params: List[str], *, required: Union[bool, List[str]] = False, version: int = 4):
|
def validate_uuids(params: List[str], *, required: Union[bool, List[str]] = False, version: int = 4):
|
||||||
def wrap(view):
|
def wrap(view):
|
||||||
@wraps(view)
|
@wraps(view)
|
||||||
def wrapped_view(**kwargs):
|
async def wrapped_view(**kwargs):
|
||||||
query_params = {param: request.args.get(param, type=str) for param in params}
|
query_params = {param: request.args.get(param, type=str) for param in params}
|
||||||
|
|
||||||
for param, value in query_params.items():
|
for param, value in query_params.items():
|
||||||
|
|
@ -89,7 +90,7 @@ def validate_uuids(params: List[str], *, required: Union[bool, List[str]] = Fals
|
||||||
except ValueError:
|
except ValueError:
|
||||||
abort(HTTPStatus.BAD_REQUEST, f"`{param}` is not a valid UUID.")
|
abort(HTTPStatus.BAD_REQUEST, f"`{param}` is not a valid UUID.")
|
||||||
|
|
||||||
return view(**kwargs)
|
return await view(**kwargs)
|
||||||
|
|
||||||
return wrapped_view
|
return wrapped_view
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
from flask_assets import Environment # type: ignore
|
|
||||||
from flask_compress import Compress # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
assets = Environment()
|
|
||||||
compress = Compress()
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from flask import Blueprint
|
from quart import Blueprint
|
||||||
|
|
||||||
|
|
||||||
amilk_ext: Blueprint = Blueprint("amilk", __name__, static_folder="static", template_folder="templates")
|
amilk_ext: Blueprint = Blueprint("amilk", __name__, static_folder="static", template_folder="templates")
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from flask import g, abort, render_template
|
from quart import g, abort, render_template
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
|
||||||
from lnbits.decorators import check_user_exists, validate_uuids
|
from lnbits.decorators import check_user_exists, validate_uuids
|
||||||
|
|
@ -10,12 +10,11 @@ from .crud import get_amilk
|
||||||
@amilk_ext.route("/")
|
@amilk_ext.route("/")
|
||||||
@validate_uuids(["usr"], required=True)
|
@validate_uuids(["usr"], required=True)
|
||||||
@check_user_exists()
|
@check_user_exists()
|
||||||
def index():
|
async def index():
|
||||||
return render_template("amilk/index.html", user=g.user)
|
return await render_template("amilk/index.html", user=g.user)
|
||||||
|
|
||||||
|
|
||||||
@amilk_ext.route("/<amilk_id>")
|
@amilk_ext.route("/<amilk_id>")
|
||||||
def wall(amilk_id):
|
async def wall(amilk_id):
|
||||||
amilk = get_amilk(amilk_id) or abort(HTTPStatus.NOT_FOUND, "AMilk does not exist.")
|
amilk = get_amilk(amilk_id) or abort(HTTPStatus.NOT_FOUND, "AMilk does not exist.")
|
||||||
|
return await render_template("amilk/wall.html", amilk=amilk)
|
||||||
return render_template("amilk/wall.html", amilk=amilk)
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import requests
|
import requests
|
||||||
from flask import g, jsonify, request, abort
|
from quart import g, jsonify, request, abort
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from lnurl import LnurlWithdrawResponse, handle as handle_lnurl
|
from lnurl import LnurlWithdrawResponse, handle as handle_lnurl
|
||||||
from lnurl.exceptions import LnurlException
|
from lnurl.exceptions import LnurlException
|
||||||
|
|
@ -15,7 +15,7 @@ from .crud import create_amilk, get_amilk, get_amilks, delete_amilk
|
||||||
|
|
||||||
@amilk_ext.route("/api/v1/amilk", methods=["GET"])
|
@amilk_ext.route("/api/v1/amilk", methods=["GET"])
|
||||||
@api_check_wallet_key("invoice")
|
@api_check_wallet_key("invoice")
|
||||||
def api_amilks():
|
async def api_amilks():
|
||||||
wallet_ids = [g.wallet.id]
|
wallet_ids = [g.wallet.id]
|
||||||
|
|
||||||
if "all_wallets" in request.args:
|
if "all_wallets" in request.args:
|
||||||
|
|
@ -25,7 +25,7 @@ def api_amilks():
|
||||||
|
|
||||||
|
|
||||||
@amilk_ext.route("/api/v1/amilk/milk/<amilk_id>", methods=["GET"])
|
@amilk_ext.route("/api/v1/amilk/milk/<amilk_id>", methods=["GET"])
|
||||||
def api_amilkit(amilk_id):
|
async def api_amilkit(amilk_id):
|
||||||
milk = get_amilk(amilk_id)
|
milk = get_amilk(amilk_id)
|
||||||
memo = milk.id
|
memo = milk.id
|
||||||
|
|
||||||
|
|
@ -66,7 +66,7 @@ def api_amilkit(amilk_id):
|
||||||
"amount": {"type": "integer", "min": 0, "required": True},
|
"amount": {"type": "integer", "min": 0, "required": True},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def api_amilk_create():
|
async def api_amilk_create():
|
||||||
amilk = create_amilk(wallet_id=g.wallet.id, lnurl=g.data["lnurl"], atime=g.data["atime"], amount=g.data["amount"])
|
amilk = create_amilk(wallet_id=g.wallet.id, lnurl=g.data["lnurl"], atime=g.data["atime"], amount=g.data["amount"])
|
||||||
|
|
||||||
return jsonify(amilk._asdict()), HTTPStatus.CREATED
|
return jsonify(amilk._asdict()), HTTPStatus.CREATED
|
||||||
|
|
@ -74,7 +74,7 @@ def api_amilk_create():
|
||||||
|
|
||||||
@amilk_ext.route("/api/v1/amilk/<amilk_id>", methods=["DELETE"])
|
@amilk_ext.route("/api/v1/amilk/<amilk_id>", methods=["DELETE"])
|
||||||
@api_check_wallet_key("invoice")
|
@api_check_wallet_key("invoice")
|
||||||
def api_amilk_delete(amilk_id):
|
async def api_amilk_delete(amilk_id):
|
||||||
amilk = get_amilk(amilk_id)
|
amilk = get_amilk(amilk_id)
|
||||||
|
|
||||||
if not amilk:
|
if not amilk:
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from flask import Blueprint
|
from quart import Blueprint
|
||||||
|
|
||||||
|
|
||||||
diagonalley_ext: Blueprint = Blueprint("diagonalley", __name__, static_folder="static", template_folder="templates")
|
diagonalley_ext: Blueprint = Blueprint("diagonalley", __name__, static_folder="static", template_folder="templates")
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,11 @@
|
||||||
import json
|
from quart import g, render_template
|
||||||
|
|
||||||
from flask import g, abort, render_template, jsonify
|
|
||||||
|
|
||||||
from lnbits.decorators import check_user_exists, validate_uuids
|
from lnbits.decorators import check_user_exists, validate_uuids
|
||||||
from lnbits.extensions.diagonalley import diagonalley_ext
|
from lnbits.extensions.diagonalley import diagonalley_ext
|
||||||
from lnbits.db import open_ext_db
|
|
||||||
|
|
||||||
|
|
||||||
@diagonalley_ext.route("/")
|
@diagonalley_ext.route("/")
|
||||||
@validate_uuids(["usr"], required=True)
|
@validate_uuids(["usr"], required=True)
|
||||||
@check_user_exists()
|
@check_user_exists()
|
||||||
def index():
|
async def index():
|
||||||
|
return await render_template("diagonalley/index.html", user=g.user)
|
||||||
return render_template("diagonalley/index.html", user=g.user)
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from flask import g, jsonify, request
|
from quart import g, jsonify, request
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
|
||||||
from lnbits.core.crud import get_user
|
from lnbits.core.crud import get_user
|
||||||
|
|
@ -18,19 +18,19 @@ from .crud import (
|
||||||
create_diagonalleys_order,
|
create_diagonalleys_order,
|
||||||
get_diagonalleys_order,
|
get_diagonalleys_order,
|
||||||
get_diagonalleys_orders,
|
get_diagonalleys_orders,
|
||||||
delete_diagonalleys_order,
|
update_diagonalleys_product,
|
||||||
)
|
)
|
||||||
from lnbits.core.services import create_invoice
|
from lnbits.core.services import create_invoice
|
||||||
from base64 import urlsafe_b64encode
|
from base64 import urlsafe_b64encode
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from lnbits.db import open_ext_db
|
from lnbits.db import open_ext_db
|
||||||
|
|
||||||
###Products
|
### Products
|
||||||
|
|
||||||
|
|
||||||
@diagonalley_ext.route("/api/v1/diagonalley/products", methods=["GET"])
|
@diagonalley_ext.route("/api/v1/diagonalley/products", methods=["GET"])
|
||||||
@api_check_wallet_key(key_type="invoice")
|
@api_check_wallet_key(key_type="invoice")
|
||||||
def api_diagonalley_products():
|
async def api_diagonalley_products():
|
||||||
wallet_ids = [g.wallet.id]
|
wallet_ids = [g.wallet.id]
|
||||||
|
|
||||||
if "all_wallets" in request.args:
|
if "all_wallets" in request.args:
|
||||||
|
|
@ -52,7 +52,7 @@ def api_diagonalley_products():
|
||||||
"quantity": {"type": "integer", "min": 0, "required": True},
|
"quantity": {"type": "integer", "min": 0, "required": True},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def api_diagonalley_product_create(product_id=None):
|
async def api_diagonalley_product_create(product_id=None):
|
||||||
|
|
||||||
if product_id:
|
if product_id:
|
||||||
product = get_diagonalleys_indexer(product_id)
|
product = get_diagonalleys_indexer(product_id)
|
||||||
|
|
@ -72,7 +72,7 @@ def api_diagonalley_product_create(product_id=None):
|
||||||
|
|
||||||
@diagonalley_ext.route("/api/v1/diagonalley/products/<product_id>", methods=["DELETE"])
|
@diagonalley_ext.route("/api/v1/diagonalley/products/<product_id>", methods=["DELETE"])
|
||||||
@api_check_wallet_key(key_type="invoice")
|
@api_check_wallet_key(key_type="invoice")
|
||||||
def api_diagonalley_products_delete(product_id):
|
async def api_diagonalley_products_delete(product_id):
|
||||||
product = get_diagonalleys_product(product_id)
|
product = get_diagonalleys_product(product_id)
|
||||||
|
|
||||||
if not product:
|
if not product:
|
||||||
|
|
@ -91,7 +91,7 @@ def api_diagonalley_products_delete(product_id):
|
||||||
|
|
||||||
@diagonalley_ext.route("/api/v1/diagonalley/indexers", methods=["GET"])
|
@diagonalley_ext.route("/api/v1/diagonalley/indexers", methods=["GET"])
|
||||||
@api_check_wallet_key(key_type="invoice")
|
@api_check_wallet_key(key_type="invoice")
|
||||||
def api_diagonalley_indexers():
|
async def api_diagonalley_indexers():
|
||||||
wallet_ids = [g.wallet.id]
|
wallet_ids = [g.wallet.id]
|
||||||
|
|
||||||
if "all_wallets" in request.args:
|
if "all_wallets" in request.args:
|
||||||
|
|
@ -114,7 +114,7 @@ def api_diagonalley_indexers():
|
||||||
"zone2cost": {"type": "integer", "min": 0, "required": True},
|
"zone2cost": {"type": "integer", "min": 0, "required": True},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def api_diagonalley_indexer_create(indexer_id=None):
|
async def api_diagonalley_indexer_create(indexer_id=None):
|
||||||
|
|
||||||
if indexer_id:
|
if indexer_id:
|
||||||
indexer = get_diagonalleys_indexer(indexer_id)
|
indexer = get_diagonalleys_indexer(indexer_id)
|
||||||
|
|
@ -134,7 +134,7 @@ def api_diagonalley_indexer_create(indexer_id=None):
|
||||||
|
|
||||||
@diagonalley_ext.route("/api/v1/diagonalley/indexers/<indexer_id>", methods=["DELETE"])
|
@diagonalley_ext.route("/api/v1/diagonalley/indexers/<indexer_id>", methods=["DELETE"])
|
||||||
@api_check_wallet_key(key_type="invoice")
|
@api_check_wallet_key(key_type="invoice")
|
||||||
def api_diagonalley_indexer_delete(indexer_id):
|
async def api_diagonalley_indexer_delete(indexer_id):
|
||||||
indexer = get_diagonalleys_indexer(indexer_id)
|
indexer = get_diagonalleys_indexer(indexer_id)
|
||||||
|
|
||||||
if not indexer:
|
if not indexer:
|
||||||
|
|
@ -153,7 +153,7 @@ def api_diagonalley_indexer_delete(indexer_id):
|
||||||
|
|
||||||
@diagonalley_ext.route("/api/v1/diagonalley/orders", methods=["GET"])
|
@diagonalley_ext.route("/api/v1/diagonalley/orders", methods=["GET"])
|
||||||
@api_check_wallet_key(key_type="invoice")
|
@api_check_wallet_key(key_type="invoice")
|
||||||
def api_diagonalley_orders():
|
async def api_diagonalley_orders():
|
||||||
wallet_ids = [g.wallet.id]
|
wallet_ids = [g.wallet.id]
|
||||||
|
|
||||||
if "all_wallets" in request.args:
|
if "all_wallets" in request.args:
|
||||||
|
|
@ -173,14 +173,14 @@ def api_diagonalley_orders():
|
||||||
"shippingzone": {"type": "integer", "empty": False, "required": True},
|
"shippingzone": {"type": "integer", "empty": False, "required": True},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def api_diagonalley_order_create():
|
async def api_diagonalley_order_create():
|
||||||
order = create_diagonalleys_order(wallet_id=g.wallet.id, **g.data)
|
order = create_diagonalleys_order(wallet_id=g.wallet.id, **g.data)
|
||||||
return jsonify(order._asdict()), HTTPStatus.CREATED
|
return jsonify(order._asdict()), HTTPStatus.CREATED
|
||||||
|
|
||||||
|
|
||||||
@diagonalley_ext.route("/api/v1/diagonalley/orders/<order_id>", methods=["DELETE"])
|
@diagonalley_ext.route("/api/v1/diagonalley/orders/<order_id>", methods=["DELETE"])
|
||||||
@api_check_wallet_key(key_type="invoice")
|
@api_check_wallet_key(key_type="invoice")
|
||||||
def api_diagonalley_order_delete(order_id):
|
async def api_diagonalley_order_delete(order_id):
|
||||||
order = get_diagonalleys_order(order_id)
|
order = get_diagonalleys_order(order_id)
|
||||||
|
|
||||||
if not order:
|
if not order:
|
||||||
|
|
@ -196,7 +196,7 @@ def api_diagonalley_order_delete(order_id):
|
||||||
|
|
||||||
@diagonalley_ext.route("/api/v1/diagonalley/orders/paid/<order_id>", methods=["GET"])
|
@diagonalley_ext.route("/api/v1/diagonalley/orders/paid/<order_id>", methods=["GET"])
|
||||||
@api_check_wallet_key(key_type="invoice")
|
@api_check_wallet_key(key_type="invoice")
|
||||||
def api_diagonalleys_order_paid(order_id):
|
async def api_diagonalleys_order_paid(order_id):
|
||||||
with open_ext_db("diagonalley") as db:
|
with open_ext_db("diagonalley") as db:
|
||||||
db.execute(
|
db.execute(
|
||||||
"UPDATE orders SET paid = ? WHERE id = ?",
|
"UPDATE orders SET paid = ? WHERE id = ?",
|
||||||
|
|
@ -210,7 +210,7 @@ def api_diagonalleys_order_paid(order_id):
|
||||||
|
|
||||||
@diagonalley_ext.route("/api/v1/diagonalley/orders/shipped/<order_id>", methods=["GET"])
|
@diagonalley_ext.route("/api/v1/diagonalley/orders/shipped/<order_id>", methods=["GET"])
|
||||||
@api_check_wallet_key(key_type="invoice")
|
@api_check_wallet_key(key_type="invoice")
|
||||||
def api_diagonalleys_order_shipped(order_id):
|
async def api_diagonalleys_order_shipped(order_id):
|
||||||
with open_ext_db("diagonalley") as db:
|
with open_ext_db("diagonalley") as db:
|
||||||
db.execute(
|
db.execute(
|
||||||
"UPDATE orders SET shipped = ? WHERE id = ?",
|
"UPDATE orders SET shipped = ? WHERE id = ?",
|
||||||
|
|
@ -228,7 +228,7 @@ def api_diagonalleys_order_shipped(order_id):
|
||||||
|
|
||||||
|
|
||||||
@diagonalley_ext.route("/api/v1/diagonalley/stall/products/<indexer_id>", methods=["GET"])
|
@diagonalley_ext.route("/api/v1/diagonalley/stall/products/<indexer_id>", methods=["GET"])
|
||||||
def api_diagonalleys_stall_products(indexer_id):
|
async def api_diagonalleys_stall_products(indexer_id):
|
||||||
with open_ext_db("diagonalley") as db:
|
with open_ext_db("diagonalley") as db:
|
||||||
rows = db.fetchone("SELECT * FROM indexers WHERE id = ?", (indexer_id,))
|
rows = db.fetchone("SELECT * FROM indexers WHERE id = ?", (indexer_id,))
|
||||||
print(rows[1])
|
print(rows[1])
|
||||||
|
|
@ -246,7 +246,7 @@ def api_diagonalleys_stall_products(indexer_id):
|
||||||
|
|
||||||
|
|
||||||
@diagonalley_ext.route("/api/v1/diagonalley/stall/checkshipped/<checking_id>", methods=["GET"])
|
@diagonalley_ext.route("/api/v1/diagonalley/stall/checkshipped/<checking_id>", methods=["GET"])
|
||||||
def api_diagonalleys_stall_checkshipped(checking_id):
|
async def api_diagonalleys_stall_checkshipped(checking_id):
|
||||||
with open_ext_db("diagonalley") as db:
|
with open_ext_db("diagonalley") as db:
|
||||||
rows = db.fetchone("SELECT * FROM orders WHERE invoiceid = ?", (checking_id,))
|
rows = db.fetchone("SELECT * FROM orders WHERE invoiceid = ?", (checking_id,))
|
||||||
|
|
||||||
|
|
@ -266,7 +266,7 @@ def api_diagonalleys_stall_checkshipped(checking_id):
|
||||||
"shippingzone": {"type": "integer", "empty": False, "required": True},
|
"shippingzone": {"type": "integer", "empty": False, "required": True},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def api_diagonalley_stall_order(indexer_id):
|
async def api_diagonalley_stall_order(indexer_id):
|
||||||
product = get_diagonalleys_product(g.data["id"])
|
product = get_diagonalleys_product(g.data["id"])
|
||||||
shipping = get_diagonalleys_indexer(indexer_id)
|
shipping = get_diagonalleys_indexer(indexer_id)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from flask import Blueprint
|
from quart import Blueprint
|
||||||
|
|
||||||
|
|
||||||
events_ext: Blueprint = Blueprint("events", __name__, static_folder="static", template_folder="templates")
|
events_ext: Blueprint = Blueprint("events", __name__, static_folder="static", template_folder="templates")
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,6 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %} {% block scripts %}
|
{% endblock %} {% block scripts %}
|
||||||
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
|
|
||||||
<script>
|
<script>
|
||||||
console.log('{{ form_costpword }}')
|
console.log('{{ form_costpword }}')
|
||||||
Vue.component(VueQrcode.name, VueQrcode)
|
Vue.component(VueQrcode.name, VueQrcode)
|
||||||
|
|
|
||||||
|
|
@ -78,24 +78,7 @@
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %} {% block styles %}
|
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
type="text/css"
|
|
||||||
href="{{ url_for('static', filename='vendor/vue-qrcode-reader@2.2.0/vue-qrcode-reader.min.css') }}"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{% endblock %} {% block scripts %}
|
{% endblock %} {% block scripts %}
|
||||||
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
|
|
||||||
{% assets filters='rjsmin', output='__bundle__/core/chart.js',
|
|
||||||
'vendor/moment@2.27.0/moment.min.js', 'vendor/chart.js@2.9.3/chart.min.js' %}
|
|
||||||
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
|
|
||||||
{% endassets %} {% assets filters='rjsmin', output='__bundle__/core/wallet.js',
|
|
||||||
'vendor/bolt11/utils.js', 'vendor/bolt11/decoder.js',
|
|
||||||
'vendor/vue-qrcode-reader@2.2.0/vue-qrcode-reader.min.js' %}
|
|
||||||
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
|
|
||||||
{% endassets %}
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
Vue.component(VueQrcode.name, VueQrcode)
|
Vue.component(VueQrcode.name, VueQrcode)
|
||||||
Vue.use(VueQrcodeReader)
|
Vue.use(VueQrcodeReader)
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %} {% block scripts %}
|
{% endblock %} {% block scripts %}
|
||||||
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
|
|
||||||
<script>
|
<script>
|
||||||
Vue.component(VueQrcode.name, VueQrcode)
|
Vue.component(VueQrcode.name, VueQrcode)
|
||||||
new Vue({
|
new Vue({
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from flask import g, abort, render_template
|
from quart import g, abort, render_template
|
||||||
from datetime import date, datetime
|
from datetime import date, datetime
|
||||||
|
|
||||||
from lnbits.decorators import check_user_exists, validate_uuids
|
from lnbits.decorators import check_user_exists, validate_uuids
|
||||||
|
|
@ -11,22 +11,24 @@ from .crud import get_ticket, get_event
|
||||||
@events_ext.route("/")
|
@events_ext.route("/")
|
||||||
@validate_uuids(["usr"], required=True)
|
@validate_uuids(["usr"], required=True)
|
||||||
@check_user_exists()
|
@check_user_exists()
|
||||||
def index():
|
async def index():
|
||||||
return render_template("events/index.html", user=g.user)
|
return await render_template("events/index.html", user=g.user)
|
||||||
|
|
||||||
|
|
||||||
@events_ext.route("/<event_id>")
|
@events_ext.route("/<event_id>")
|
||||||
def display(event_id):
|
async def display(event_id):
|
||||||
event = get_event(event_id) or abort(HTTPStatus.NOT_FOUND, "Event does not exist.")
|
event = get_event(event_id) or abort(HTTPStatus.NOT_FOUND, "Event does not exist.")
|
||||||
if event.amount_tickets < 1:
|
if event.amount_tickets < 1:
|
||||||
return render_template("events/error.html", event_name=event.name, event_error="Sorry, tickets are sold out :(")
|
return await render_template(
|
||||||
|
"events/error.html", event_name=event.name, event_error="Sorry, tickets are sold out :("
|
||||||
|
)
|
||||||
datetime_object = datetime.strptime(event.closing_date, "%Y-%m-%d").date()
|
datetime_object = datetime.strptime(event.closing_date, "%Y-%m-%d").date()
|
||||||
if date.today() > datetime_object:
|
if date.today() > datetime_object:
|
||||||
return render_template(
|
return await render_template(
|
||||||
"events/error.html", event_name=event.name, event_error="Sorry, ticket closing date has passed :("
|
"events/error.html", event_name=event.name, event_error="Sorry, ticket closing date has passed :("
|
||||||
)
|
)
|
||||||
|
|
||||||
return render_template(
|
return await render_template(
|
||||||
"events/display.html",
|
"events/display.html",
|
||||||
event_id=event_id,
|
event_id=event_id,
|
||||||
event_name=event.name,
|
event_name=event.name,
|
||||||
|
|
@ -36,14 +38,18 @@ def display(event_id):
|
||||||
|
|
||||||
|
|
||||||
@events_ext.route("/ticket/<ticket_id>")
|
@events_ext.route("/ticket/<ticket_id>")
|
||||||
def ticket(ticket_id):
|
async def ticket(ticket_id):
|
||||||
ticket = get_ticket(ticket_id) or abort(HTTPStatus.NOT_FOUND, "Ticket does not exist.")
|
ticket = get_ticket(ticket_id) or abort(HTTPStatus.NOT_FOUND, "Ticket does not exist.")
|
||||||
event = get_event(ticket.event) or abort(HTTPStatus.NOT_FOUND, "Event does not exist.")
|
event = get_event(ticket.event) or abort(HTTPStatus.NOT_FOUND, "Event does not exist.")
|
||||||
return render_template("events/ticket.html", ticket_id=ticket_id, ticket_name=event.name, ticket_info=event.info)
|
return await render_template(
|
||||||
|
"events/ticket.html", ticket_id=ticket_id, ticket_name=event.name, ticket_info=event.info
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@events_ext.route("/register/<event_id>")
|
@events_ext.route("/register/<event_id>")
|
||||||
def register(event_id):
|
async def register(event_id):
|
||||||
event = get_event(event_id) or abort(HTTPStatus.NOT_FOUND, "Event does not exist.")
|
event = get_event(event_id) or abort(HTTPStatus.NOT_FOUND, "Event does not exist.")
|
||||||
|
|
||||||
return render_template("events/register.html", event_id=event_id, event_name=event.name, wallet_id=event.wallet)
|
return await render_template(
|
||||||
|
"events/register.html", event_id=event_id, event_name=event.name, wallet_id=event.wallet
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from flask import g, jsonify, request
|
from quart import g, jsonify, request
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
|
||||||
from lnbits.core.crud import get_user, get_wallet
|
from lnbits.core.crud import get_user, get_wallet
|
||||||
|
|
@ -27,7 +27,7 @@ from .crud import (
|
||||||
|
|
||||||
@events_ext.route("/api/v1/events", methods=["GET"])
|
@events_ext.route("/api/v1/events", methods=["GET"])
|
||||||
@api_check_wallet_key("invoice")
|
@api_check_wallet_key("invoice")
|
||||||
def api_events():
|
async def api_events():
|
||||||
wallet_ids = [g.wallet.id]
|
wallet_ids = [g.wallet.id]
|
||||||
|
|
||||||
if "all_wallets" in request.args:
|
if "all_wallets" in request.args:
|
||||||
|
|
@ -51,7 +51,7 @@ def api_events():
|
||||||
"price_per_ticket": {"type": "integer", "min": 0, "required": True},
|
"price_per_ticket": {"type": "integer", "min": 0, "required": True},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def api_event_create(event_id=None):
|
async def api_event_create(event_id=None):
|
||||||
if event_id:
|
if event_id:
|
||||||
event = get_event(event_id)
|
event = get_event(event_id)
|
||||||
print(g.data)
|
print(g.data)
|
||||||
|
|
@ -71,7 +71,7 @@ def api_event_create(event_id=None):
|
||||||
|
|
||||||
@events_ext.route("/api/v1/events/<event_id>", methods=["DELETE"])
|
@events_ext.route("/api/v1/events/<event_id>", methods=["DELETE"])
|
||||||
@api_check_wallet_key("invoice")
|
@api_check_wallet_key("invoice")
|
||||||
def api_form_delete(event_id):
|
async def api_form_delete(event_id):
|
||||||
event = get_event(event_id)
|
event = get_event(event_id)
|
||||||
|
|
||||||
if not event:
|
if not event:
|
||||||
|
|
@ -90,7 +90,7 @@ def api_form_delete(event_id):
|
||||||
|
|
||||||
@events_ext.route("/api/v1/tickets", methods=["GET"])
|
@events_ext.route("/api/v1/tickets", methods=["GET"])
|
||||||
@api_check_wallet_key("invoice")
|
@api_check_wallet_key("invoice")
|
||||||
def api_tickets():
|
async def api_tickets():
|
||||||
wallet_ids = [g.wallet.id]
|
wallet_ids = [g.wallet.id]
|
||||||
|
|
||||||
if "all_wallets" in request.args:
|
if "all_wallets" in request.args:
|
||||||
|
|
@ -106,7 +106,7 @@ def api_tickets():
|
||||||
"email": {"type": "string", "empty": False, "required": True},
|
"email": {"type": "string", "empty": False, "required": True},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def api_ticket_make_ticket(event_id, sats):
|
async def api_ticket_make_ticket(event_id, sats):
|
||||||
event = get_event(event_id)
|
event = get_event(event_id)
|
||||||
if not event:
|
if not event:
|
||||||
return jsonify({"message": "Event does not exist."}), HTTPStatus.NOT_FOUND
|
return jsonify({"message": "Event does not exist."}), HTTPStatus.NOT_FOUND
|
||||||
|
|
@ -126,7 +126,7 @@ def api_ticket_make_ticket(event_id, sats):
|
||||||
|
|
||||||
|
|
||||||
@events_ext.route("/api/v1/tickets/<payment_hash>", methods=["GET"])
|
@events_ext.route("/api/v1/tickets/<payment_hash>", methods=["GET"])
|
||||||
def api_ticket_send_ticket(payment_hash):
|
async def api_ticket_send_ticket(payment_hash):
|
||||||
ticket = get_ticket(payment_hash)
|
ticket = get_ticket(payment_hash)
|
||||||
try:
|
try:
|
||||||
is_paid = not check_invoice_status(ticket.wallet, payment_hash).pending
|
is_paid = not check_invoice_status(ticket.wallet, payment_hash).pending
|
||||||
|
|
@ -146,7 +146,7 @@ def api_ticket_send_ticket(payment_hash):
|
||||||
|
|
||||||
@events_ext.route("/api/v1/tickets/<ticket_id>", methods=["DELETE"])
|
@events_ext.route("/api/v1/tickets/<ticket_id>", methods=["DELETE"])
|
||||||
@api_check_wallet_key("invoice")
|
@api_check_wallet_key("invoice")
|
||||||
def api_ticket_delete(ticket_id):
|
async def api_ticket_delete(ticket_id):
|
||||||
ticket = get_ticket(ticket_id)
|
ticket = get_ticket(ticket_id)
|
||||||
|
|
||||||
if not ticket:
|
if not ticket:
|
||||||
|
|
@ -164,7 +164,7 @@ def api_ticket_delete(ticket_id):
|
||||||
|
|
||||||
|
|
||||||
@events_ext.route("/api/v1/eventtickets/<wallet_id>/<event_id>", methods=["GET"])
|
@events_ext.route("/api/v1/eventtickets/<wallet_id>/<event_id>", methods=["GET"])
|
||||||
def api_event_tickets(wallet_id, event_id):
|
async def api_event_tickets(wallet_id, event_id):
|
||||||
|
|
||||||
return (
|
return (
|
||||||
jsonify([ticket._asdict() for ticket in get_event_tickets(wallet_id=wallet_id, event_id=event_id)]),
|
jsonify([ticket._asdict() for ticket in get_event_tickets(wallet_id=wallet_id, event_id=event_id)]),
|
||||||
|
|
@ -173,7 +173,7 @@ def api_event_tickets(wallet_id, event_id):
|
||||||
|
|
||||||
|
|
||||||
@events_ext.route("/api/v1/register/ticket/<ticket_id>", methods=["GET"])
|
@events_ext.route("/api/v1/register/ticket/<ticket_id>", methods=["GET"])
|
||||||
def api_event_register_ticket(ticket_id):
|
async def api_event_register_ticket(ticket_id):
|
||||||
|
|
||||||
ticket = get_ticket(ticket_id)
|
ticket = get_ticket(ticket_id)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from flask import Blueprint
|
from quart import Blueprint
|
||||||
|
|
||||||
|
|
||||||
example_ext: Blueprint = Blueprint("example", __name__, static_folder="static", template_folder="templates")
|
example_ext: Blueprint = Blueprint("example", __name__, static_folder="static", template_folder="templates")
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from flask import g, render_template
|
from quart import g, render_template
|
||||||
|
|
||||||
from lnbits.decorators import check_user_exists, validate_uuids
|
from lnbits.decorators import check_user_exists, validate_uuids
|
||||||
from lnbits.extensions.example import example_ext
|
from lnbits.extensions.example import example_ext
|
||||||
|
|
@ -7,5 +7,5 @@ from lnbits.extensions.example import example_ext
|
||||||
@example_ext.route("/")
|
@example_ext.route("/")
|
||||||
@validate_uuids(["usr"], required=True)
|
@validate_uuids(["usr"], required=True)
|
||||||
@check_user_exists()
|
@check_user_exists()
|
||||||
def index():
|
async def index():
|
||||||
return render_template("example/index.html", user=g.user)
|
return await render_template("example/index.html", user=g.user)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
# import json
|
# import json
|
||||||
# import requests
|
# import requests
|
||||||
|
|
||||||
from flask import jsonify
|
from quart import jsonify
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
|
||||||
from lnbits.extensions.example import example_ext
|
from lnbits.extensions.example import example_ext
|
||||||
|
|
@ -15,7 +15,7 @@ from lnbits.extensions.example import example_ext
|
||||||
|
|
||||||
|
|
||||||
@example_ext.route("/api/v1/tools", methods=["GET"])
|
@example_ext.route("/api/v1/tools", methods=["GET"])
|
||||||
def api_example():
|
async def api_example():
|
||||||
"""Try to add descriptions for others."""
|
"""Try to add descriptions for others."""
|
||||||
tools = [
|
tools = [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from flask import Blueprint
|
from quart import Blueprint
|
||||||
|
|
||||||
|
|
||||||
lndhub_ext: Blueprint = Blueprint("lndhub", __name__, static_folder="static", template_folder="templates")
|
lndhub_ext: Blueprint = Blueprint("lndhub", __name__, static_folder="static", template_folder="templates")
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from base64 import b64decode
|
from base64 import b64decode
|
||||||
from flask import jsonify, g, request
|
from quart import jsonify, g, request
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
from lnbits.core.crud import get_wallet_for_key
|
from lnbits.core.crud import get_wallet_for_key
|
||||||
|
|
@ -8,7 +8,7 @@ from lnbits.core.crud import get_wallet_for_key
|
||||||
def check_wallet(requires_admin=False):
|
def check_wallet(requires_admin=False):
|
||||||
def wrap(view):
|
def wrap(view):
|
||||||
@wraps(view)
|
@wraps(view)
|
||||||
def wrapped_view(**kwargs):
|
async def wrapped_view(**kwargs):
|
||||||
token = request.headers["Authorization"].split("Bearer ")[1]
|
token = request.headers["Authorization"].split("Bearer ")[1]
|
||||||
key_type, key = b64decode(token).decode("utf-8").split(":")
|
key_type, key = b64decode(token).decode("utf-8").split(":")
|
||||||
|
|
||||||
|
|
@ -18,7 +18,7 @@ def check_wallet(requires_admin=False):
|
||||||
g.wallet = get_wallet_for_key(key, key_type)
|
g.wallet = get_wallet_for_key(key, key_type)
|
||||||
if not g.wallet:
|
if not g.wallet:
|
||||||
return jsonify({"error": True, "code": 2, "message": "insufficient permissions"})
|
return jsonify({"error": True, "code": 2, "message": "insufficient permissions"})
|
||||||
return view(**kwargs)
|
return await view(**kwargs)
|
||||||
|
|
||||||
return wrapped_view
|
return wrapped_view
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,6 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
||||||
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
|
|
||||||
<script>
|
<script>
|
||||||
Vue.component(VueQrcode.name, VueQrcode)
|
Vue.component(VueQrcode.name, VueQrcode)
|
||||||
|
|
||||||
|
|
@ -75,7 +74,9 @@
|
||||||
el: '#vue',
|
el: '#vue',
|
||||||
mixins: [windowMixin],
|
mixins: [windowMixin],
|
||||||
data: function () {
|
data: function () {
|
||||||
var wallets = ({{ g.user.wallets | tojson }}).map(LNbits.map.wallet).map(wallet => ({
|
var wallets = JSON.parse('{{ g.user.wallets | tojson }}')
|
||||||
|
.map(LNbits.map.wallet)
|
||||||
|
.map(wallet => ({
|
||||||
label: wallet.name,
|
label: wallet.name,
|
||||||
admin: `lndhub://admin:${wallet.adminkey}@${location.protocol}//${location.host}/lndhub/ext/`,
|
admin: `lndhub://admin:${wallet.adminkey}@${location.protocol}//${location.host}/lndhub/ext/`,
|
||||||
invoice: `lndhub://invoice:${wallet.inkey}@${location.protocol}//${location.host}/lndhub/ext/`
|
invoice: `lndhub://invoice:${wallet.inkey}@${location.protocol}//${location.host}/lndhub/ext/`
|
||||||
|
|
@ -85,7 +86,7 @@
|
||||||
wallets: wallets,
|
wallets: wallets,
|
||||||
selectedWallet: wallets[0]
|
selectedWallet: wallets[0]
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from flask import render_template, g
|
from quart import render_template, g
|
||||||
|
|
||||||
from lnbits.decorators import check_user_exists, validate_uuids
|
from lnbits.decorators import check_user_exists, validate_uuids
|
||||||
from lnbits.extensions.lndhub import lndhub_ext
|
from lnbits.extensions.lndhub import lndhub_ext
|
||||||
|
|
@ -7,5 +7,5 @@ from lnbits.extensions.lndhub import lndhub_ext
|
||||||
@lndhub_ext.route("/")
|
@lndhub_ext.route("/")
|
||||||
@validate_uuids(["usr"], required=True)
|
@validate_uuids(["usr"], required=True)
|
||||||
@check_user_exists()
|
@check_user_exists()
|
||||||
def lndhub_index():
|
async def lndhub_index():
|
||||||
return render_template("lndhub/index.html", user=g.user)
|
return await render_template("lndhub/index.html", user=g.user)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import time
|
import time
|
||||||
from base64 import urlsafe_b64encode
|
from base64 import urlsafe_b64encode
|
||||||
from flask import jsonify, g, request
|
from quart import jsonify, g, request
|
||||||
|
|
||||||
from lnbits.core.services import pay_invoice, create_invoice
|
from lnbits.core.services import pay_invoice, create_invoice
|
||||||
from lnbits.core.crud import delete_expired_invoices
|
from lnbits.core.crud import delete_expired_invoices
|
||||||
|
|
@ -14,7 +14,7 @@ from .utils import to_buffer, decoded_as_lndhub
|
||||||
|
|
||||||
|
|
||||||
@lndhub_ext.route("/ext/getinfo", methods=["GET"])
|
@lndhub_ext.route("/ext/getinfo", methods=["GET"])
|
||||||
def lndhub_getinfo():
|
async def lndhub_getinfo():
|
||||||
return jsonify({"error": True, "code": 1, "message": "bad auth"})
|
return jsonify({"error": True, "code": 1, "message": "bad auth"})
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -26,7 +26,7 @@ def lndhub_getinfo():
|
||||||
"refresh_token": {"type": "string", "required": True, "excludes": ["login", "password"]},
|
"refresh_token": {"type": "string", "required": True, "excludes": ["login", "password"]},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def lndhub_auth():
|
async def lndhub_auth():
|
||||||
token = (
|
token = (
|
||||||
g.data["token"]
|
g.data["token"]
|
||||||
if "token" in g.data and g.data["token"]
|
if "token" in g.data and g.data["token"]
|
||||||
|
|
@ -44,7 +44,7 @@ def lndhub_auth():
|
||||||
"preimage": {"type": "string", "required": False},
|
"preimage": {"type": "string", "required": False},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def lndhub_addinvoice():
|
async def lndhub_addinvoice():
|
||||||
try:
|
try:
|
||||||
_, pr = create_invoice(
|
_, pr = create_invoice(
|
||||||
wallet_id=g.wallet.id,
|
wallet_id=g.wallet.id,
|
||||||
|
|
@ -76,7 +76,7 @@ def lndhub_addinvoice():
|
||||||
@lndhub_ext.route("/ext/payinvoice", methods=["POST"])
|
@lndhub_ext.route("/ext/payinvoice", methods=["POST"])
|
||||||
@check_wallet(requires_admin=True)
|
@check_wallet(requires_admin=True)
|
||||||
@api_validate_post_request(schema={"invoice": {"type": "string", "required": True}})
|
@api_validate_post_request(schema={"invoice": {"type": "string", "required": True}})
|
||||||
def lndhub_payinvoice():
|
async def lndhub_payinvoice():
|
||||||
try:
|
try:
|
||||||
pay_invoice(
|
pay_invoice(
|
||||||
wallet_id=g.wallet.id,
|
wallet_id=g.wallet.id,
|
||||||
|
|
@ -112,13 +112,13 @@ def lndhub_payinvoice():
|
||||||
|
|
||||||
@lndhub_ext.route("/ext/balance", methods=["GET"])
|
@lndhub_ext.route("/ext/balance", methods=["GET"])
|
||||||
@check_wallet()
|
@check_wallet()
|
||||||
def lndhub_balance():
|
async def lndhub_balance():
|
||||||
return jsonify({"BTC": {"AvailableBalance": g.wallet.balance}})
|
return jsonify({"BTC": {"AvailableBalance": g.wallet.balance}})
|
||||||
|
|
||||||
|
|
||||||
@lndhub_ext.route("/ext/gettxs", methods=["GET"])
|
@lndhub_ext.route("/ext/gettxs", methods=["GET"])
|
||||||
@check_wallet()
|
@check_wallet()
|
||||||
def lndhub_gettxs():
|
async def lndhub_gettxs():
|
||||||
for payment in g.wallet.get_payments(
|
for payment in g.wallet.get_payments(
|
||||||
complete=False, pending=True, outgoing=True, incoming=False, exclude_uncheckable=True
|
complete=False, pending=True, outgoing=True, incoming=False, exclude_uncheckable=True
|
||||||
):
|
):
|
||||||
|
|
@ -146,7 +146,7 @@ def lndhub_gettxs():
|
||||||
|
|
||||||
@lndhub_ext.route("/ext/getuserinvoices", methods=["GET"])
|
@lndhub_ext.route("/ext/getuserinvoices", methods=["GET"])
|
||||||
@check_wallet()
|
@check_wallet()
|
||||||
def lndhub_getuserinvoices():
|
async def lndhub_getuserinvoices():
|
||||||
delete_expired_invoices()
|
delete_expired_invoices()
|
||||||
for invoice in g.wallet.get_payments(
|
for invoice in g.wallet.get_payments(
|
||||||
complete=False, pending=True, outgoing=False, incoming=True, exclude_uncheckable=True
|
complete=False, pending=True, outgoing=False, incoming=True, exclude_uncheckable=True
|
||||||
|
|
@ -177,26 +177,26 @@ def lndhub_getuserinvoices():
|
||||||
|
|
||||||
@lndhub_ext.route("/ext/getbtc", methods=["GET"])
|
@lndhub_ext.route("/ext/getbtc", methods=["GET"])
|
||||||
@check_wallet()
|
@check_wallet()
|
||||||
def lndhub_getbtc():
|
async def lndhub_getbtc():
|
||||||
"load an address for incoming onchain btc"
|
"load an address for incoming onchain btc"
|
||||||
return jsonify([])
|
return jsonify([])
|
||||||
|
|
||||||
|
|
||||||
@lndhub_ext.route("/ext/getpending", methods=["GET"])
|
@lndhub_ext.route("/ext/getpending", methods=["GET"])
|
||||||
@check_wallet()
|
@check_wallet()
|
||||||
def lndhub_getpending():
|
async def lndhub_getpending():
|
||||||
"pending onchain transactions"
|
"pending onchain transactions"
|
||||||
return jsonify([])
|
return jsonify([])
|
||||||
|
|
||||||
|
|
||||||
@lndhub_ext.route("/ext/decodeinvoice", methods=["GET"])
|
@lndhub_ext.route("/ext/decodeinvoice", methods=["GET"])
|
||||||
def lndhub_decodeinvoice():
|
async def lndhub_decodeinvoice():
|
||||||
invoice = request.args.get("invoice")
|
invoice = request.args.get("invoice")
|
||||||
inv = bolt11.decode(invoice)
|
inv = bolt11.decode(invoice)
|
||||||
return jsonify(decoded_as_lndhub(inv))
|
return jsonify(decoded_as_lndhub(inv))
|
||||||
|
|
||||||
|
|
||||||
@lndhub_ext.route("/ext/checkrouteinvoice", methods=["GET"])
|
@lndhub_ext.route("/ext/checkrouteinvoice", methods=["GET"])
|
||||||
def lndhub_checkrouteinvoice():
|
async def lndhub_checkrouteinvoice():
|
||||||
"not implemented on canonical lndhub"
|
"not implemented on canonical lndhub"
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from flask import Blueprint
|
from quart import Blueprint
|
||||||
|
|
||||||
|
|
||||||
lnticket_ext: Blueprint = Blueprint("lnticket", __name__, static_folder="static", template_folder="templates")
|
lnticket_ext: Blueprint = Blueprint("lnticket", __name__, static_folder="static", template_folder="templates")
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,6 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %} {% block scripts %}
|
{% endblock %} {% block scripts %}
|
||||||
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
|
|
||||||
<script>
|
<script>
|
||||||
console.log('{{ form_costpword }}')
|
console.log('{{ form_costpword }}')
|
||||||
Vue.component(VueQrcode.name, VueQrcode)
|
Vue.component(VueQrcode.name, VueQrcode)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from flask import g, abort, render_template
|
from quart import g, abort, render_template
|
||||||
|
|
||||||
from lnbits.decorators import check_user_exists, validate_uuids
|
from lnbits.decorators import check_user_exists, validate_uuids
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
|
@ -10,16 +10,16 @@ from .crud import get_form
|
||||||
@lnticket_ext.route("/")
|
@lnticket_ext.route("/")
|
||||||
@validate_uuids(["usr"], required=True)
|
@validate_uuids(["usr"], required=True)
|
||||||
@check_user_exists()
|
@check_user_exists()
|
||||||
def index():
|
async def index():
|
||||||
return render_template("lnticket/index.html", user=g.user)
|
return await render_template("lnticket/index.html", user=g.user)
|
||||||
|
|
||||||
|
|
||||||
@lnticket_ext.route("/<form_id>")
|
@lnticket_ext.route("/<form_id>")
|
||||||
def display(form_id):
|
async def display(form_id):
|
||||||
form = get_form(form_id) or abort(HTTPStatus.NOT_FOUND, "LNTicket does not exist.")
|
form = get_form(form_id) or abort(HTTPStatus.NOT_FOUND, "LNTicket does not exist.")
|
||||||
print(form.id)
|
print(form.id)
|
||||||
|
|
||||||
return render_template(
|
return await render_template(
|
||||||
"lnticket/display.html",
|
"lnticket/display.html",
|
||||||
form_id=form.id,
|
form_id=form.id,
|
||||||
form_name=form.name,
|
form_name=form.name,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import re
|
import re
|
||||||
from flask import g, jsonify, request
|
from quart import g, jsonify, request
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
|
||||||
from lnbits.core.crud import get_user, get_wallet
|
from lnbits.core.crud import get_user, get_wallet
|
||||||
|
|
@ -26,7 +26,7 @@ from .crud import (
|
||||||
|
|
||||||
@lnticket_ext.route("/api/v1/forms", methods=["GET"])
|
@lnticket_ext.route("/api/v1/forms", methods=["GET"])
|
||||||
@api_check_wallet_key("invoice")
|
@api_check_wallet_key("invoice")
|
||||||
def api_forms():
|
async def api_forms():
|
||||||
wallet_ids = [g.wallet.id]
|
wallet_ids = [g.wallet.id]
|
||||||
|
|
||||||
if "all_wallets" in request.args:
|
if "all_wallets" in request.args:
|
||||||
|
|
@ -46,7 +46,7 @@ def api_forms():
|
||||||
"costpword": {"type": "integer", "min": 0, "required": True},
|
"costpword": {"type": "integer", "min": 0, "required": True},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def api_form_create(form_id=None):
|
async def api_form_create(form_id=None):
|
||||||
if form_id:
|
if form_id:
|
||||||
form = get_form(form_id)
|
form = get_form(form_id)
|
||||||
|
|
||||||
|
|
@ -64,7 +64,7 @@ def api_form_create(form_id=None):
|
||||||
|
|
||||||
@lnticket_ext.route("/api/v1/forms/<form_id>", methods=["DELETE"])
|
@lnticket_ext.route("/api/v1/forms/<form_id>", methods=["DELETE"])
|
||||||
@api_check_wallet_key("invoice")
|
@api_check_wallet_key("invoice")
|
||||||
def api_form_delete(form_id):
|
async def api_form_delete(form_id):
|
||||||
form = get_form(form_id)
|
form = get_form(form_id)
|
||||||
|
|
||||||
if not form:
|
if not form:
|
||||||
|
|
@ -83,7 +83,7 @@ def api_form_delete(form_id):
|
||||||
|
|
||||||
@lnticket_ext.route("/api/v1/tickets", methods=["GET"])
|
@lnticket_ext.route("/api/v1/tickets", methods=["GET"])
|
||||||
@api_check_wallet_key("invoice")
|
@api_check_wallet_key("invoice")
|
||||||
def api_tickets():
|
async def api_tickets():
|
||||||
wallet_ids = [g.wallet.id]
|
wallet_ids = [g.wallet.id]
|
||||||
|
|
||||||
if "all_wallets" in request.args:
|
if "all_wallets" in request.args:
|
||||||
|
|
@ -101,7 +101,7 @@ def api_tickets():
|
||||||
"ltext": {"type": "string", "empty": False, "required": True},
|
"ltext": {"type": "string", "empty": False, "required": True},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def api_ticket_make_ticket(form_id):
|
async def api_ticket_make_ticket(form_id):
|
||||||
form = get_form(form_id)
|
form = get_form(form_id)
|
||||||
if not form:
|
if not form:
|
||||||
return jsonify({"message": "LNTicket does not exist."}), HTTPStatus.NOT_FOUND
|
return jsonify({"message": "LNTicket does not exist."}), HTTPStatus.NOT_FOUND
|
||||||
|
|
@ -126,7 +126,7 @@ def api_ticket_make_ticket(form_id):
|
||||||
|
|
||||||
|
|
||||||
@lnticket_ext.route("/api/v1/tickets/<payment_hash>", methods=["GET"])
|
@lnticket_ext.route("/api/v1/tickets/<payment_hash>", methods=["GET"])
|
||||||
def api_ticket_send_ticket(payment_hash):
|
async def api_ticket_send_ticket(payment_hash):
|
||||||
ticket = get_ticket(payment_hash)
|
ticket = get_ticket(payment_hash)
|
||||||
try:
|
try:
|
||||||
is_paid = not check_invoice_status(ticket.wallet, payment_hash).pending
|
is_paid = not check_invoice_status(ticket.wallet, payment_hash).pending
|
||||||
|
|
@ -145,7 +145,7 @@ def api_ticket_send_ticket(payment_hash):
|
||||||
|
|
||||||
@lnticket_ext.route("/api/v1/tickets/<ticket_id>", methods=["DELETE"])
|
@lnticket_ext.route("/api/v1/tickets/<ticket_id>", methods=["DELETE"])
|
||||||
@api_check_wallet_key("invoice")
|
@api_check_wallet_key("invoice")
|
||||||
def api_ticket_delete(ticket_id):
|
async def api_ticket_delete(ticket_id):
|
||||||
ticket = get_ticket(ticket_id)
|
ticket = get_ticket(ticket_id)
|
||||||
|
|
||||||
if not ticket:
|
if not ticket:
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from flask import Blueprint
|
from quart import Blueprint
|
||||||
|
|
||||||
|
|
||||||
lnurlp_ext: Blueprint = Blueprint("lnurlp", __name__, static_folder="static", template_folder="templates")
|
lnurlp_ext: Blueprint = Blueprint("lnurlp", __name__, static_folder="static", template_folder="templates")
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import json
|
import json
|
||||||
from flask import url_for
|
from quart import url_for
|
||||||
from lnurl import Lnurl, encode as lnurl_encode
|
from lnurl import Lnurl, encode as lnurl_encode
|
||||||
from lnurl.types import LnurlPayMetadata
|
from lnurl.types import LnurlPayMetadata
|
||||||
from sqlite3 import Row
|
from sqlite3 import Row
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %} {% block scripts %}
|
{% endblock %} {% block scripts %}
|
||||||
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
|
|
||||||
<script>
|
<script>
|
||||||
Vue.component(VueQrcode.name, VueQrcode)
|
Vue.component(VueQrcode.name, VueQrcode)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -205,7 +205,6 @@
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
||||||
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
|
|
||||||
<script>
|
<script>
|
||||||
Vue.component(VueQrcode.name, VueQrcode)
|
Vue.component(VueQrcode.name, VueQrcode)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %} {% block scripts %}
|
{% endblock %} {% block scripts %}
|
||||||
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
|
|
||||||
<script>
|
<script>
|
||||||
Vue.component(VueQrcode.name, VueQrcode)
|
Vue.component(VueQrcode.name, VueQrcode)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from flask import g, abort, render_template
|
from quart import g, abort, render_template
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
|
||||||
from lnbits.decorators import check_user_exists, validate_uuids
|
from lnbits.decorators import check_user_exists, validate_uuids
|
||||||
|
|
@ -10,19 +10,17 @@ from .crud import get_pay_link
|
||||||
@lnurlp_ext.route("/")
|
@lnurlp_ext.route("/")
|
||||||
@validate_uuids(["usr"], required=True)
|
@validate_uuids(["usr"], required=True)
|
||||||
@check_user_exists()
|
@check_user_exists()
|
||||||
def index():
|
async def index():
|
||||||
return render_template("lnurlp/index.html", user=g.user)
|
return await render_template("lnurlp/index.html", user=g.user)
|
||||||
|
|
||||||
|
|
||||||
@lnurlp_ext.route("/<link_id>")
|
@lnurlp_ext.route("/<link_id>")
|
||||||
def display(link_id):
|
async def display(link_id):
|
||||||
link = get_pay_link(link_id) or abort(HTTPStatus.NOT_FOUND, "Pay link does not exist.")
|
link = get_pay_link(link_id) or abort(HTTPStatus.NOT_FOUND, "Pay link does not exist.")
|
||||||
|
return await render_template("lnurlp/display.html", link=link)
|
||||||
return render_template("lnurlp/display.html", link=link)
|
|
||||||
|
|
||||||
|
|
||||||
@lnurlp_ext.route("/print/<link_id>")
|
@lnurlp_ext.route("/print/<link_id>")
|
||||||
def print_qr(link_id):
|
async def print_qr(link_id):
|
||||||
link = get_pay_link(link_id) or abort(HTTPStatus.NOT_FOUND, "Pay link does not exist.")
|
link = get_pay_link(link_id) or abort(HTTPStatus.NOT_FOUND, "Pay link does not exist.")
|
||||||
|
return await render_template("lnurlp/print_qr.html", link=link)
|
||||||
return render_template("lnurlp/print_qr.html", link=link)
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import hashlib
|
import hashlib
|
||||||
from flask import g, jsonify, request, url_for
|
from quart import g, jsonify, request, url_for
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from lnurl import LnurlPayResponse, LnurlPayActionResponse
|
from lnurl import LnurlPayResponse, LnurlPayActionResponse
|
||||||
from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl
|
from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl
|
||||||
|
|
@ -22,7 +22,7 @@ from .crud import (
|
||||||
|
|
||||||
@lnurlp_ext.route("/api/v1/links", methods=["GET"])
|
@lnurlp_ext.route("/api/v1/links", methods=["GET"])
|
||||||
@api_check_wallet_key("invoice")
|
@api_check_wallet_key("invoice")
|
||||||
def api_links():
|
async def api_links():
|
||||||
wallet_ids = [g.wallet.id]
|
wallet_ids = [g.wallet.id]
|
||||||
|
|
||||||
if "all_wallets" in request.args:
|
if "all_wallets" in request.args:
|
||||||
|
|
@ -42,7 +42,7 @@ def api_links():
|
||||||
|
|
||||||
@lnurlp_ext.route("/api/v1/links/<link_id>", methods=["GET"])
|
@lnurlp_ext.route("/api/v1/links/<link_id>", methods=["GET"])
|
||||||
@api_check_wallet_key("invoice")
|
@api_check_wallet_key("invoice")
|
||||||
def api_link_retrieve(link_id):
|
async def api_link_retrieve(link_id):
|
||||||
link = get_pay_link(link_id)
|
link = get_pay_link(link_id)
|
||||||
|
|
||||||
if not link:
|
if not link:
|
||||||
|
|
@ -63,7 +63,7 @@ def api_link_retrieve(link_id):
|
||||||
"amount": {"type": "integer", "min": 1, "required": True},
|
"amount": {"type": "integer", "min": 1, "required": True},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def api_link_create_or_update(link_id=None):
|
async def api_link_create_or_update(link_id=None):
|
||||||
if link_id:
|
if link_id:
|
||||||
link = get_pay_link(link_id)
|
link = get_pay_link(link_id)
|
||||||
|
|
||||||
|
|
@ -82,7 +82,7 @@ def api_link_create_or_update(link_id=None):
|
||||||
|
|
||||||
@lnurlp_ext.route("/api/v1/links/<link_id>", methods=["DELETE"])
|
@lnurlp_ext.route("/api/v1/links/<link_id>", methods=["DELETE"])
|
||||||
@api_check_wallet_key("invoice")
|
@api_check_wallet_key("invoice")
|
||||||
def api_link_delete(link_id):
|
async def api_link_delete(link_id):
|
||||||
link = get_pay_link(link_id)
|
link = get_pay_link(link_id)
|
||||||
|
|
||||||
if not link:
|
if not link:
|
||||||
|
|
@ -97,7 +97,7 @@ def api_link_delete(link_id):
|
||||||
|
|
||||||
|
|
||||||
@lnurlp_ext.route("/api/v1/lnurl/<link_id>", methods=["GET"])
|
@lnurlp_ext.route("/api/v1/lnurl/<link_id>", methods=["GET"])
|
||||||
def api_lnurl_response(link_id):
|
async def api_lnurl_response(link_id):
|
||||||
link = increment_pay_link(link_id, served_meta=1)
|
link = increment_pay_link(link_id, served_meta=1)
|
||||||
if not link:
|
if not link:
|
||||||
return jsonify({"status": "ERROR", "reason": "LNURL-pay not found."}), HTTPStatus.OK
|
return jsonify({"status": "ERROR", "reason": "LNURL-pay not found."}), HTTPStatus.OK
|
||||||
|
|
@ -116,7 +116,7 @@ def api_lnurl_response(link_id):
|
||||||
|
|
||||||
|
|
||||||
@lnurlp_ext.route("/api/v1/lnurl/cb/<link_id>", methods=["GET"])
|
@lnurlp_ext.route("/api/v1/lnurl/cb/<link_id>", methods=["GET"])
|
||||||
def api_lnurl_callback(link_id):
|
async def api_lnurl_callback(link_id):
|
||||||
link = increment_pay_link(link_id, served_pr=1)
|
link = increment_pay_link(link_id, served_pr=1)
|
||||||
if not link:
|
if not link:
|
||||||
return jsonify({"status": "ERROR", "reason": "LNURL-pay not found."}), HTTPStatus.OK
|
return jsonify({"status": "ERROR", "reason": "LNURL-pay not found."}), HTTPStatus.OK
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from flask import Blueprint
|
from quart import Blueprint
|
||||||
|
|
||||||
|
|
||||||
paywall_ext: Blueprint = Blueprint("paywall", __name__, static_folder="static", template_folder="templates")
|
paywall_ext: Blueprint = Blueprint("paywall", __name__, static_folder="static", template_folder="templates")
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %} {% block scripts %}
|
{% endblock %} {% block scripts %}
|
||||||
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
|
|
||||||
<script>
|
<script>
|
||||||
Vue.component(VueQrcode.name, VueQrcode)
|
Vue.component(VueQrcode.name, VueQrcode)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from flask import g, abort, render_template
|
from quart import g, abort, render_template
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
|
||||||
from lnbits.decorators import check_user_exists, validate_uuids
|
from lnbits.decorators import check_user_exists, validate_uuids
|
||||||
|
|
@ -10,12 +10,11 @@ from .crud import get_paywall
|
||||||
@paywall_ext.route("/")
|
@paywall_ext.route("/")
|
||||||
@validate_uuids(["usr"], required=True)
|
@validate_uuids(["usr"], required=True)
|
||||||
@check_user_exists()
|
@check_user_exists()
|
||||||
def index():
|
async def index():
|
||||||
return render_template("paywall/index.html", user=g.user)
|
return await render_template("paywall/index.html", user=g.user)
|
||||||
|
|
||||||
|
|
||||||
@paywall_ext.route("/<paywall_id>")
|
@paywall_ext.route("/<paywall_id>")
|
||||||
def display(paywall_id):
|
async def display(paywall_id):
|
||||||
paywall = get_paywall(paywall_id) or abort(HTTPStatus.NOT_FOUND, "Paywall does not exist.")
|
paywall = get_paywall(paywall_id) or abort(HTTPStatus.NOT_FOUND, "Paywall does not exist.")
|
||||||
|
return await render_template("paywall/display.html", paywall=paywall)
|
||||||
return render_template("paywall/display.html", paywall=paywall)
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from flask import g, jsonify, request
|
from quart import g, jsonify, request
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
|
||||||
from lnbits.core.crud import get_user, get_wallet
|
from lnbits.core.crud import get_user, get_wallet
|
||||||
|
|
@ -11,7 +11,7 @@ from .crud import create_paywall, get_paywall, get_paywalls, delete_paywall
|
||||||
|
|
||||||
@paywall_ext.route("/api/v1/paywalls", methods=["GET"])
|
@paywall_ext.route("/api/v1/paywalls", methods=["GET"])
|
||||||
@api_check_wallet_key("invoice")
|
@api_check_wallet_key("invoice")
|
||||||
def api_paywalls():
|
async def api_paywalls():
|
||||||
wallet_ids = [g.wallet.id]
|
wallet_ids = [g.wallet.id]
|
||||||
|
|
||||||
if "all_wallets" in request.args:
|
if "all_wallets" in request.args:
|
||||||
|
|
@ -31,7 +31,7 @@ def api_paywalls():
|
||||||
"remembers": {"type": "boolean", "required": True},
|
"remembers": {"type": "boolean", "required": True},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def api_paywall_create():
|
async def api_paywall_create():
|
||||||
paywall = create_paywall(wallet_id=g.wallet.id, **g.data)
|
paywall = create_paywall(wallet_id=g.wallet.id, **g.data)
|
||||||
|
|
||||||
return jsonify(paywall._asdict()), HTTPStatus.CREATED
|
return jsonify(paywall._asdict()), HTTPStatus.CREATED
|
||||||
|
|
@ -39,7 +39,7 @@ def api_paywall_create():
|
||||||
|
|
||||||
@paywall_ext.route("/api/v1/paywalls/<paywall_id>", methods=["DELETE"])
|
@paywall_ext.route("/api/v1/paywalls/<paywall_id>", methods=["DELETE"])
|
||||||
@api_check_wallet_key("invoice")
|
@api_check_wallet_key("invoice")
|
||||||
def api_paywall_delete(paywall_id):
|
async def api_paywall_delete(paywall_id):
|
||||||
paywall = get_paywall(paywall_id)
|
paywall = get_paywall(paywall_id)
|
||||||
|
|
||||||
if not paywall:
|
if not paywall:
|
||||||
|
|
@ -55,7 +55,7 @@ def api_paywall_delete(paywall_id):
|
||||||
|
|
||||||
@paywall_ext.route("/api/v1/paywalls/<paywall_id>/invoice", methods=["POST"])
|
@paywall_ext.route("/api/v1/paywalls/<paywall_id>/invoice", methods=["POST"])
|
||||||
@api_validate_post_request(schema={"amount": {"type": "integer", "min": 1, "required": True}})
|
@api_validate_post_request(schema={"amount": {"type": "integer", "min": 1, "required": True}})
|
||||||
def api_paywall_create_invoice(paywall_id):
|
async def api_paywall_create_invoice(paywall_id):
|
||||||
paywall = get_paywall(paywall_id)
|
paywall = get_paywall(paywall_id)
|
||||||
|
|
||||||
if g.data["amount"] < paywall.amount:
|
if g.data["amount"] < paywall.amount:
|
||||||
|
|
@ -74,7 +74,7 @@ def api_paywall_create_invoice(paywall_id):
|
||||||
|
|
||||||
@paywall_ext.route("/api/v1/paywalls/<paywall_id>/check_invoice", methods=["POST"])
|
@paywall_ext.route("/api/v1/paywalls/<paywall_id>/check_invoice", methods=["POST"])
|
||||||
@api_validate_post_request(schema={"payment_hash": {"type": "string", "empty": False, "required": True}})
|
@api_validate_post_request(schema={"payment_hash": {"type": "string", "empty": False, "required": True}})
|
||||||
def api_paywal_check_invoice(paywall_id):
|
async def api_paywal_check_invoice(paywall_id):
|
||||||
paywall = get_paywall(paywall_id)
|
paywall = get_paywall(paywall_id)
|
||||||
|
|
||||||
if not paywall:
|
if not paywall:
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from flask import Blueprint
|
from quart import Blueprint
|
||||||
|
|
||||||
|
|
||||||
tpos_ext: Blueprint = Blueprint("tpos", __name__, static_folder="static", template_folder="templates")
|
tpos_ext: Blueprint = Blueprint("tpos", __name__, static_folder="static", template_folder="templates")
|
||||||
|
|
|
||||||
|
|
@ -152,7 +152,6 @@
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %} {% block scripts %}
|
{% endblock %} {% block scripts %}
|
||||||
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
|
|
||||||
<script>
|
<script>
|
||||||
Vue.component(VueQrcode.name, VueQrcode)
|
Vue.component(VueQrcode.name, VueQrcode)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from flask import g, abort, render_template
|
from quart import g, abort, render_template
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
|
||||||
from lnbits.decorators import check_user_exists, validate_uuids
|
from lnbits.decorators import check_user_exists, validate_uuids
|
||||||
|
|
@ -10,12 +10,12 @@ from .crud import get_tpos
|
||||||
@tpos_ext.route("/")
|
@tpos_ext.route("/")
|
||||||
@validate_uuids(["usr"], required=True)
|
@validate_uuids(["usr"], required=True)
|
||||||
@check_user_exists()
|
@check_user_exists()
|
||||||
def index():
|
async def index():
|
||||||
return render_template("tpos/index.html", user=g.user)
|
return await render_template("tpos/index.html", user=g.user)
|
||||||
|
|
||||||
|
|
||||||
@tpos_ext.route("/<tpos_id>")
|
@tpos_ext.route("/<tpos_id>")
|
||||||
def tpos(tpos_id):
|
async def tpos(tpos_id):
|
||||||
tpos = get_tpos(tpos_id) or abort(HTTPStatus.NOT_FOUND, "TPoS does not exist.")
|
tpos = get_tpos(tpos_id) or abort(HTTPStatus.NOT_FOUND, "TPoS does not exist.")
|
||||||
|
|
||||||
return render_template("tpos/tpos.html", tpos=tpos)
|
return await render_template("tpos/tpos.html", tpos=tpos)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from flask import g, jsonify, request
|
from quart import g, jsonify, request
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
|
||||||
from lnbits.core.crud import get_user, get_wallet
|
from lnbits.core.crud import get_user, get_wallet
|
||||||
|
|
@ -11,7 +11,7 @@ from .crud import create_tpos, get_tpos, get_tposs, delete_tpos
|
||||||
|
|
||||||
@tpos_ext.route("/api/v1/tposs", methods=["GET"])
|
@tpos_ext.route("/api/v1/tposs", methods=["GET"])
|
||||||
@api_check_wallet_key("invoice")
|
@api_check_wallet_key("invoice")
|
||||||
def api_tposs():
|
async def api_tposs():
|
||||||
wallet_ids = [g.wallet.id]
|
wallet_ids = [g.wallet.id]
|
||||||
|
|
||||||
if "all_wallets" in request.args:
|
if "all_wallets" in request.args:
|
||||||
|
|
@ -28,7 +28,7 @@ def api_tposs():
|
||||||
"currency": {"type": "string", "empty": False, "required": True},
|
"currency": {"type": "string", "empty": False, "required": True},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def api_tpos_create():
|
async def api_tpos_create():
|
||||||
tpos = create_tpos(wallet_id=g.wallet.id, **g.data)
|
tpos = create_tpos(wallet_id=g.wallet.id, **g.data)
|
||||||
|
|
||||||
return jsonify(tpos._asdict()), HTTPStatus.CREATED
|
return jsonify(tpos._asdict()), HTTPStatus.CREATED
|
||||||
|
|
@ -36,7 +36,7 @@ def api_tpos_create():
|
||||||
|
|
||||||
@tpos_ext.route("/api/v1/tposs/<tpos_id>", methods=["DELETE"])
|
@tpos_ext.route("/api/v1/tposs/<tpos_id>", methods=["DELETE"])
|
||||||
@api_check_wallet_key("admin")
|
@api_check_wallet_key("admin")
|
||||||
def api_tpos_delete(tpos_id):
|
async def api_tpos_delete(tpos_id):
|
||||||
tpos = get_tpos(tpos_id)
|
tpos = get_tpos(tpos_id)
|
||||||
|
|
||||||
if not tpos:
|
if not tpos:
|
||||||
|
|
@ -52,7 +52,7 @@ def api_tpos_delete(tpos_id):
|
||||||
|
|
||||||
@tpos_ext.route("/api/v1/tposs/<tpos_id>/invoices/", methods=["POST"])
|
@tpos_ext.route("/api/v1/tposs/<tpos_id>/invoices/", methods=["POST"])
|
||||||
@api_validate_post_request(schema={"amount": {"type": "integer", "min": 1, "required": True}})
|
@api_validate_post_request(schema={"amount": {"type": "integer", "min": 1, "required": True}})
|
||||||
def api_tpos_create_invoice(tpos_id):
|
async def api_tpos_create_invoice(tpos_id):
|
||||||
tpos = get_tpos(tpos_id)
|
tpos = get_tpos(tpos_id)
|
||||||
|
|
||||||
if not tpos:
|
if not tpos:
|
||||||
|
|
@ -69,7 +69,7 @@ def api_tpos_create_invoice(tpos_id):
|
||||||
|
|
||||||
|
|
||||||
@tpos_ext.route("/api/v1/tposs/<tpos_id>/invoices/<payment_hash>", methods=["GET"])
|
@tpos_ext.route("/api/v1/tposs/<tpos_id>/invoices/<payment_hash>", methods=["GET"])
|
||||||
def api_tpos_check_invoice(tpos_id, payment_hash):
|
async def api_tpos_check_invoice(tpos_id, payment_hash):
|
||||||
tpos = get_tpos(tpos_id)
|
tpos = get_tpos(tpos_id)
|
||||||
|
|
||||||
if not tpos:
|
if not tpos:
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from flask import Blueprint
|
from quart import Blueprint
|
||||||
|
|
||||||
|
|
||||||
usermanager_ext: Blueprint = Blueprint("usermanager", __name__, static_folder="static", template_folder="templates")
|
usermanager_ext: Blueprint = Blueprint("usermanager", __name__, static_folder="static", template_folder="templates")
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,10 @@
|
||||||
from flask import g, abort, render_template, jsonify
|
from quart import g, render_template
|
||||||
import json
|
|
||||||
from lnbits.decorators import check_user_exists, validate_uuids
|
from lnbits.decorators import check_user_exists, validate_uuids
|
||||||
from lnbits.extensions.usermanager import usermanager_ext
|
from lnbits.extensions.usermanager import usermanager_ext
|
||||||
from lnbits.db import open_ext_db
|
|
||||||
|
|
||||||
|
|
||||||
@usermanager_ext.route("/")
|
@usermanager_ext.route("/")
|
||||||
@validate_uuids(["usr"], required=True)
|
@validate_uuids(["usr"], required=True)
|
||||||
@check_user_exists()
|
@check_user_exists()
|
||||||
def index():
|
async def index():
|
||||||
|
return await render_template("usermanager/index.html", user=g.user)
|
||||||
return render_template("usermanager/index.html", user=g.user)
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from flask import g, jsonify, request
|
from quart import g, jsonify
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
|
||||||
from lnbits.core.crud import get_user
|
from lnbits.core.crud import get_user
|
||||||
|
|
@ -17,20 +17,15 @@ from .crud import (
|
||||||
get_usermanager_wallets,
|
get_usermanager_wallets,
|
||||||
delete_usermanager_wallet,
|
delete_usermanager_wallet,
|
||||||
)
|
)
|
||||||
from lnbits.core.services import create_invoice
|
from lnbits.core import update_user_extension
|
||||||
from base64 import urlsafe_b64encode
|
|
||||||
from uuid import uuid4
|
|
||||||
from lnbits.db import open_ext_db
|
|
||||||
|
|
||||||
from ...core import update_user_extension
|
|
||||||
|
|
||||||
|
|
||||||
###Users
|
### Users
|
||||||
|
|
||||||
|
|
||||||
@usermanager_ext.route("/api/v1/users", methods=["GET"])
|
@usermanager_ext.route("/api/v1/users", methods=["GET"])
|
||||||
@api_check_wallet_key(key_type="invoice")
|
@api_check_wallet_key(key_type="invoice")
|
||||||
def api_usermanager_users():
|
async def api_usermanager_users():
|
||||||
user_id = g.wallet.user
|
user_id = g.wallet.user
|
||||||
return jsonify([user._asdict() for user in get_usermanager_users(user_id)]), HTTPStatus.OK
|
return jsonify([user._asdict() for user in get_usermanager_users(user_id)]), HTTPStatus.OK
|
||||||
|
|
||||||
|
|
@ -44,14 +39,14 @@ def api_usermanager_users():
|
||||||
"wallet_name": {"type": "string", "empty": False, "required": True},
|
"wallet_name": {"type": "string", "empty": False, "required": True},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def api_usermanager_users_create():
|
async def api_usermanager_users_create():
|
||||||
user = create_usermanager_user(g.data["user_name"], g.data["wallet_name"], g.data["admin_id"])
|
user = create_usermanager_user(g.data["user_name"], g.data["wallet_name"], g.data["admin_id"])
|
||||||
return jsonify(user._asdict()), HTTPStatus.CREATED
|
return jsonify(user._asdict()), HTTPStatus.CREATED
|
||||||
|
|
||||||
|
|
||||||
@usermanager_ext.route("/api/v1/users/<user_id>", methods=["DELETE"])
|
@usermanager_ext.route("/api/v1/users/<user_id>", methods=["DELETE"])
|
||||||
@api_check_wallet_key(key_type="invoice")
|
@api_check_wallet_key(key_type="invoice")
|
||||||
def api_usermanager_users_delete(user_id):
|
async def api_usermanager_users_delete(user_id):
|
||||||
user = get_usermanager_user(user_id)
|
user = get_usermanager_user(user_id)
|
||||||
if not user:
|
if not user:
|
||||||
return jsonify({"message": "User does not exist."}), HTTPStatus.NOT_FOUND
|
return jsonify({"message": "User does not exist."}), HTTPStatus.NOT_FOUND
|
||||||
|
|
@ -71,7 +66,7 @@ def api_usermanager_users_delete(user_id):
|
||||||
"active": {"type": "boolean", "required": True},
|
"active": {"type": "boolean", "required": True},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def api_usermanager_activate_extension():
|
async def api_usermanager_activate_extension():
|
||||||
user = get_user(g.data["userid"])
|
user = get_user(g.data["userid"])
|
||||||
if not user:
|
if not user:
|
||||||
return jsonify({"error": "no such user"}), HTTPStatus.NO_CONTENT
|
return jsonify({"error": "no such user"}), HTTPStatus.NO_CONTENT
|
||||||
|
|
@ -84,7 +79,7 @@ def api_usermanager_activate_extension():
|
||||||
|
|
||||||
@usermanager_ext.route("/api/v1/wallets", methods=["GET"])
|
@usermanager_ext.route("/api/v1/wallets", methods=["GET"])
|
||||||
@api_check_wallet_key(key_type="invoice")
|
@api_check_wallet_key(key_type="invoice")
|
||||||
def api_usermanager_wallets():
|
async def api_usermanager_wallets():
|
||||||
user_id = g.wallet.user
|
user_id = g.wallet.user
|
||||||
return jsonify([wallet._asdict() for wallet in get_usermanager_wallets(user_id)]), HTTPStatus.OK
|
return jsonify([wallet._asdict() for wallet in get_usermanager_wallets(user_id)]), HTTPStatus.OK
|
||||||
|
|
||||||
|
|
@ -98,27 +93,27 @@ def api_usermanager_wallets():
|
||||||
"admin_id": {"type": "string", "empty": False, "required": True},
|
"admin_id": {"type": "string", "empty": False, "required": True},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def api_usermanager_wallets_create():
|
async def api_usermanager_wallets_create():
|
||||||
user = create_usermanager_wallet(g.data["user_id"], g.data["wallet_name"], g.data["admin_id"])
|
user = create_usermanager_wallet(g.data["user_id"], g.data["wallet_name"], g.data["admin_id"])
|
||||||
return jsonify(user._asdict()), HTTPStatus.CREATED
|
return jsonify(user._asdict()), HTTPStatus.CREATED
|
||||||
|
|
||||||
|
|
||||||
@usermanager_ext.route("/api/v1/wallets<wallet_id>", methods=["GET"])
|
@usermanager_ext.route("/api/v1/wallets<wallet_id>", methods=["GET"])
|
||||||
@api_check_wallet_key(key_type="invoice")
|
@api_check_wallet_key(key_type="invoice")
|
||||||
def api_usermanager_wallet_transactions(wallet_id):
|
async def api_usermanager_wallet_transactions(wallet_id):
|
||||||
|
|
||||||
return jsonify(get_usermanager_wallet_transactions(wallet_id)), HTTPStatus.OK
|
return jsonify(get_usermanager_wallet_transactions(wallet_id)), HTTPStatus.OK
|
||||||
|
|
||||||
|
|
||||||
@usermanager_ext.route("/api/v1/wallets/<user_id>", methods=["GET"])
|
@usermanager_ext.route("/api/v1/wallets/<user_id>", methods=["GET"])
|
||||||
@api_check_wallet_key(key_type="invoice")
|
@api_check_wallet_key(key_type="invoice")
|
||||||
def api_usermanager_wallet_balances(user_id):
|
async def api_usermanager_wallet_balances(user_id):
|
||||||
return jsonify(get_usermanager_wallet_balances(user_id)), HTTPStatus.OK
|
return jsonify(get_usermanager_wallet_balances(user_id)), HTTPStatus.OK
|
||||||
|
|
||||||
|
|
||||||
@usermanager_ext.route("/api/v1/wallets/<wallet_id>", methods=["DELETE"])
|
@usermanager_ext.route("/api/v1/wallets/<wallet_id>", methods=["DELETE"])
|
||||||
@api_check_wallet_key(key_type="invoice")
|
@api_check_wallet_key(key_type="invoice")
|
||||||
def api_usermanager_wallets_delete(wallet_id):
|
async def api_usermanager_wallets_delete(wallet_id):
|
||||||
wallet = get_usermanager_wallet(wallet_id)
|
wallet = get_usermanager_wallet(wallet_id)
|
||||||
print(wallet.id)
|
print(wallet.id)
|
||||||
if not wallet:
|
if not wallet:
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from flask import Blueprint
|
from quart import Blueprint
|
||||||
|
|
||||||
|
|
||||||
withdraw_ext: Blueprint = Blueprint("withdraw", __name__, static_folder="static", template_folder="templates")
|
withdraw_ext: Blueprint = Blueprint("withdraw", __name__, static_folder="static", template_folder="templates")
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from flask import url_for
|
from quart import url_for
|
||||||
from lnurl import Lnurl, LnurlWithdrawResponse, encode as lnurl_encode
|
from lnurl import Lnurl, LnurlWithdrawResponse, encode as lnurl_encode
|
||||||
from sqlite3 import Row
|
from sqlite3 import Row
|
||||||
from typing import NamedTuple
|
from typing import NamedTuple
|
||||||
|
|
|
||||||
|
|
@ -43,8 +43,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %} {% block scripts %}
|
{% endblock %} {% block scripts %}
|
||||||
|
|
||||||
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
|
|
||||||
<script>
|
<script>
|
||||||
Vue.component(VueQrcode.name, VueQrcode)
|
Vue.component(VueQrcode.name, VueQrcode)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
|
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
|
||||||
%} {% block scripts %} {{ window_vars(user) }}
|
%} {% block scripts %} {{ window_vars(user) }}
|
||||||
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
|
<script type="text/javascript" src="/withdraw/static/js/index.js"></script>
|
||||||
{% assets filters='rjsmin', output='__bundle__/withdraw/index.js',
|
{% endblock %} {% block page %}
|
||||||
'withdraw/js/index.js' %}
|
|
||||||
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
|
|
||||||
{% endassets %} {% endblock %} {% block page %}
|
|
||||||
<div class="row q-col-gutter-md">
|
<div class="row q-col-gutter-md">
|
||||||
<div class="col-12 col-md-7 q-gutter-y-md">
|
<div class="col-12 col-md-7 q-gutter-y-md">
|
||||||
<q-card>
|
<q-card>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
<!DOCTYPE html>
|
{% extends "print.html" %} {% block page %}
|
||||||
{% block page %}
|
|
||||||
|
|
||||||
<div class="row justify-center">
|
<div class="row justify-center">
|
||||||
<div class="col-12 col-sm-8 col-lg-6 text-center" id="vue">
|
<div class="col-12 col-sm-8 col-lg-6 text-center" id="vue">
|
||||||
|
|
@ -50,15 +49,6 @@
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %} {% block scripts %}
|
{% endblock %} {% block scripts %}
|
||||||
<script src="{{ url_for('static', filename='vendor/vue@2.6.12/vue.js') }}"></script>
|
|
||||||
<script src="{{ url_for('static', filename='vendor/vuex@3.5.1/vuex.js') }}"></script>
|
|
||||||
<script src="{{ url_for('static', filename='vendor/vue-router@3.4.3/vue-router.js') }}"></script>
|
|
||||||
<script src="{{ url_for('static', filename='vendor/quasar@1.13.2/quasar.umd.js') }}"></script>
|
|
||||||
<script
|
|
||||||
type="text/javascript"
|
|
||||||
src="/static/__bundle__/base.js?a52a989e"
|
|
||||||
></script>
|
|
||||||
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
|
|
||||||
<script>
|
<script>
|
||||||
Vue.component(VueQrcode.name, VueQrcode)
|
Vue.component(VueQrcode.name, VueQrcode)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from flask import g, abort, render_template
|
from quart import g, abort, render_template
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
|
||||||
from lnbits.decorators import check_user_exists, validate_uuids
|
from lnbits.decorators import check_user_exists, validate_uuids
|
||||||
|
|
@ -10,21 +10,21 @@ from .crud import get_withdraw_link, chunks
|
||||||
@withdraw_ext.route("/")
|
@withdraw_ext.route("/")
|
||||||
@validate_uuids(["usr"], required=True)
|
@validate_uuids(["usr"], required=True)
|
||||||
@check_user_exists()
|
@check_user_exists()
|
||||||
def index():
|
async def index():
|
||||||
return render_template("withdraw/index.html", user=g.user)
|
return await render_template("withdraw/index.html", user=g.user)
|
||||||
|
|
||||||
|
|
||||||
@withdraw_ext.route("/<link_id>")
|
@withdraw_ext.route("/<link_id>")
|
||||||
def display(link_id):
|
async def display(link_id):
|
||||||
link = get_withdraw_link(link_id, 0) or abort(HTTPStatus.NOT_FOUND, "Withdraw link does not exist.")
|
link = get_withdraw_link(link_id, 0) or abort(HTTPStatus.NOT_FOUND, "Withdraw link does not exist.")
|
||||||
return render_template("withdraw/display.html", link=link, unique=True)
|
return await render_template("withdraw/display.html", link=link, unique=True)
|
||||||
|
|
||||||
|
|
||||||
@withdraw_ext.route("/print/<link_id>")
|
@withdraw_ext.route("/print/<link_id>")
|
||||||
def print_qr(link_id):
|
async def print_qr(link_id):
|
||||||
link = get_withdraw_link(link_id) or abort(HTTPStatus.NOT_FOUND, "Withdraw link does not exist.")
|
link = get_withdraw_link(link_id) or abort(HTTPStatus.NOT_FOUND, "Withdraw link does not exist.")
|
||||||
if link.uses == 0:
|
if link.uses == 0:
|
||||||
return render_template("withdraw/print_qr.html", link=link, unique=False)
|
return await render_template("withdraw/print_qr.html", link=link, unique=False)
|
||||||
links = []
|
links = []
|
||||||
count = 0
|
count = 0
|
||||||
for x in link.usescsv.split(","):
|
for x in link.usescsv.split(","):
|
||||||
|
|
@ -33,4 +33,4 @@ def print_qr(link_id):
|
||||||
count = count + 1
|
count = count + 1
|
||||||
page_link = list(chunks(links, 2))
|
page_link = list(chunks(links, 2))
|
||||||
linked = list(chunks(page_link, 5))
|
linked = list(chunks(page_link, 5))
|
||||||
return render_template("withdraw/print_qr.html", link=linked, unique=True)
|
return await render_template("withdraw/print_qr.html", link=linked, unique=True)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from flask import g, jsonify, request
|
from quart import g, jsonify, request
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl
|
from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl
|
||||||
import shortuuid # type: ignore
|
import shortuuid # type: ignore
|
||||||
|
|
@ -21,7 +21,7 @@ from .crud import (
|
||||||
|
|
||||||
@withdraw_ext.route("/api/v1/links", methods=["GET"])
|
@withdraw_ext.route("/api/v1/links", methods=["GET"])
|
||||||
@api_check_wallet_key("invoice")
|
@api_check_wallet_key("invoice")
|
||||||
def api_links():
|
async def api_links():
|
||||||
wallet_ids = [g.wallet.id]
|
wallet_ids = [g.wallet.id]
|
||||||
|
|
||||||
if "all_wallets" in request.args:
|
if "all_wallets" in request.args:
|
||||||
|
|
@ -40,7 +40,7 @@ def api_links():
|
||||||
|
|
||||||
@withdraw_ext.route("/api/v1/links/<link_id>", methods=["GET"])
|
@withdraw_ext.route("/api/v1/links/<link_id>", methods=["GET"])
|
||||||
@api_check_wallet_key("invoice")
|
@api_check_wallet_key("invoice")
|
||||||
def api_link_retrieve(link_id):
|
async def api_link_retrieve(link_id):
|
||||||
link = get_withdraw_link(link_id, 0)
|
link = get_withdraw_link(link_id, 0)
|
||||||
|
|
||||||
if not link:
|
if not link:
|
||||||
|
|
@ -65,7 +65,7 @@ def api_link_retrieve(link_id):
|
||||||
"is_unique": {"type": "boolean", "required": True},
|
"is_unique": {"type": "boolean", "required": True},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
def api_link_create_or_update(link_id=None):
|
async def api_link_create_or_update(link_id=None):
|
||||||
if g.data["max_withdrawable"] < g.data["min_withdrawable"]:
|
if g.data["max_withdrawable"] < g.data["min_withdrawable"]:
|
||||||
return (
|
return (
|
||||||
jsonify({"message": "`max_withdrawable` needs to be at least `min_withdrawable`."}),
|
jsonify({"message": "`max_withdrawable` needs to be at least `min_withdrawable`."}),
|
||||||
|
|
@ -95,7 +95,7 @@ def api_link_create_or_update(link_id=None):
|
||||||
|
|
||||||
@withdraw_ext.route("/api/v1/links/<link_id>", methods=["DELETE"])
|
@withdraw_ext.route("/api/v1/links/<link_id>", methods=["DELETE"])
|
||||||
@api_check_wallet_key("admin")
|
@api_check_wallet_key("admin")
|
||||||
def api_link_delete(link_id):
|
async def api_link_delete(link_id):
|
||||||
link = get_withdraw_link(link_id)
|
link = get_withdraw_link(link_id)
|
||||||
|
|
||||||
if not link:
|
if not link:
|
||||||
|
|
@ -113,7 +113,7 @@ def api_link_delete(link_id):
|
||||||
|
|
||||||
|
|
||||||
@withdraw_ext.route("/api/v1/lnurl/<unique_hash>", methods=["GET"])
|
@withdraw_ext.route("/api/v1/lnurl/<unique_hash>", methods=["GET"])
|
||||||
def api_lnurl_response(unique_hash):
|
async def api_lnurl_response(unique_hash):
|
||||||
link = get_withdraw_link_by_hash(unique_hash)
|
link = get_withdraw_link_by_hash(unique_hash)
|
||||||
|
|
||||||
if not link:
|
if not link:
|
||||||
|
|
@ -134,7 +134,7 @@ def api_lnurl_response(unique_hash):
|
||||||
|
|
||||||
|
|
||||||
@withdraw_ext.route("/api/v1/lnurl/<unique_hash>/<id_unique_hash>", methods=["GET"])
|
@withdraw_ext.route("/api/v1/lnurl/<unique_hash>/<id_unique_hash>", methods=["GET"])
|
||||||
def api_lnurl_multi_response(unique_hash, id_unique_hash):
|
async def api_lnurl_multi_response(unique_hash, id_unique_hash):
|
||||||
link = get_withdraw_link_by_hash(unique_hash)
|
link = get_withdraw_link_by_hash(unique_hash)
|
||||||
|
|
||||||
if not link:
|
if not link:
|
||||||
|
|
@ -163,7 +163,7 @@ def api_lnurl_multi_response(unique_hash, id_unique_hash):
|
||||||
|
|
||||||
|
|
||||||
@withdraw_ext.route("/api/v1/lnurl/cb/<unique_hash>", methods=["GET"])
|
@withdraw_ext.route("/api/v1/lnurl/cb/<unique_hash>", methods=["GET"])
|
||||||
def api_lnurl_callback(unique_hash):
|
async def api_lnurl_callback(unique_hash):
|
||||||
link = get_withdraw_link_by_hash(unique_hash)
|
link = get_withdraw_link_by_hash(unique_hash)
|
||||||
k1 = request.args.get("k1", type=str)
|
k1 = request.args.get("k1", type=str)
|
||||||
payment_request = request.args.get("pr", type=str)
|
payment_request = request.args.get("pr", type=str)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import glob
|
||||||
import shortuuid # type: ignore
|
import shortuuid # type: ignore
|
||||||
|
|
||||||
from typing import List, NamedTuple, Optional
|
from typing import List, NamedTuple, Optional
|
||||||
|
|
@ -54,3 +55,54 @@ def get_valid_extensions() -> List[Extension]:
|
||||||
|
|
||||||
def urlsafe_short_hash() -> str:
|
def urlsafe_short_hash() -> str:
|
||||||
return shortuuid.uuid()
|
return shortuuid.uuid()
|
||||||
|
|
||||||
|
|
||||||
|
def get_js_vendored(prefer_minified: bool = False) -> List[str]:
|
||||||
|
paths = get_vendored(".js", prefer_minified)
|
||||||
|
|
||||||
|
def sorter(key: str):
|
||||||
|
if "moment@" in key:
|
||||||
|
return 1
|
||||||
|
if "vue@" in key:
|
||||||
|
return 2
|
||||||
|
if "vue-router@" in key:
|
||||||
|
return 3
|
||||||
|
if "polyfills" in key:
|
||||||
|
return 4
|
||||||
|
return 9
|
||||||
|
|
||||||
|
return sorted(paths, key=sorter)
|
||||||
|
|
||||||
|
|
||||||
|
def get_css_vendored(prefer_minified: bool = False) -> List[str]:
|
||||||
|
return get_vendored(".css", prefer_minified)
|
||||||
|
|
||||||
|
|
||||||
|
def get_vendored(ext: str, prefer_minified: bool = False) -> List[str]:
|
||||||
|
paths: List[str] = []
|
||||||
|
for path in glob.glob(os.path.join(LNBITS_PATH, "static/vendor/**"), recursive=True):
|
||||||
|
if path.endswith(".min" + ext):
|
||||||
|
# path is minified
|
||||||
|
unminified = path.replace(".min" + ext, ext)
|
||||||
|
if prefer_minified:
|
||||||
|
paths.append(path)
|
||||||
|
if unminified in paths:
|
||||||
|
paths.remove(unminified)
|
||||||
|
elif unminified not in paths:
|
||||||
|
paths.append(path)
|
||||||
|
|
||||||
|
elif path.endswith(ext):
|
||||||
|
# path is not minified
|
||||||
|
minified = path.replace(ext, ".min" + ext)
|
||||||
|
if not prefer_minified:
|
||||||
|
paths.append(path)
|
||||||
|
if minified in paths:
|
||||||
|
paths.remove(minified)
|
||||||
|
elif minified not in paths:
|
||||||
|
paths.append(path)
|
||||||
|
|
||||||
|
return paths
|
||||||
|
|
||||||
|
|
||||||
|
def url_for_vendored(abspath: str) -> str:
|
||||||
|
return "/" + os.path.relpath(abspath, LNBITS_PATH)
|
||||||
|
|
|
||||||
100
lnbits/proxy_fix.py
Normal file
100
lnbits/proxy_fix.py
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
from typing import Optional, List
|
||||||
|
from urllib.request import parse_http_list as _parse_list_header
|
||||||
|
|
||||||
|
from quart import request
|
||||||
|
|
||||||
|
|
||||||
|
class ProxyFix:
|
||||||
|
def __init__(self, app=None, x_for: int = 1, x_proto: int = 1, x_host: int = 0, x_port: int = 0, x_prefix: int = 0):
|
||||||
|
self.app = app
|
||||||
|
self.x_for = x_for
|
||||||
|
self.x_proto = x_proto
|
||||||
|
self.x_host = x_host
|
||||||
|
self.x_port = x_port
|
||||||
|
self.x_prefix = x_prefix
|
||||||
|
|
||||||
|
if app:
|
||||||
|
self.init_app(app)
|
||||||
|
|
||||||
|
def init_app(self, app):
|
||||||
|
@app.before_request
|
||||||
|
async def before_request():
|
||||||
|
x_for = self._get_real_value(self.x_for, request.headers.get("X-Forwarded-For"))
|
||||||
|
if x_for:
|
||||||
|
request.headers["Remote-Addr"] = x_for
|
||||||
|
|
||||||
|
x_proto = self._get_real_value(self.x_proto, request.headers.get("X-Forwarded-Proto"))
|
||||||
|
if x_proto:
|
||||||
|
request.scheme = x_proto
|
||||||
|
|
||||||
|
x_host = self._get_real_value(self.x_host, request.headers.get("X-Forwarded-Host"))
|
||||||
|
if x_host:
|
||||||
|
request.headers["host"] = x_host.lower()
|
||||||
|
parts = x_host.split(":", 1)
|
||||||
|
# environ["SERVER_NAME"] = parts[0]
|
||||||
|
# if len(parts) == 2:
|
||||||
|
# environ["SERVER_PORT"] = parts[1]
|
||||||
|
|
||||||
|
x_port = self._get_real_value(self.x_port, request.headers.get("X-Forwarded-Port"))
|
||||||
|
if x_port:
|
||||||
|
host = request.host
|
||||||
|
if host:
|
||||||
|
parts = host.split(":", 1)
|
||||||
|
host = parts[0] if len(parts) == 2 else host
|
||||||
|
request.headers["host"] = f"{host}:{x_port}"
|
||||||
|
# environ["SERVER_PORT"] = x_port
|
||||||
|
|
||||||
|
def _get_real_value(self, trusted: int, value: Optional[str]) -> Optional[str]:
|
||||||
|
"""Get the real value from a list header based on the configured
|
||||||
|
number of trusted proxies.
|
||||||
|
:param trusted: Number of values to trust in the header.
|
||||||
|
:param value: Comma separated list header value to parse.
|
||||||
|
:return: The real value, or ``None`` if there are fewer values
|
||||||
|
than the number of trusted proxies.
|
||||||
|
.. versionchanged:: 1.0
|
||||||
|
Renamed from ``_get_trusted_comma``.
|
||||||
|
.. versionadded:: 0.15
|
||||||
|
"""
|
||||||
|
if not (trusted and value):
|
||||||
|
return None
|
||||||
|
|
||||||
|
values = self.parse_list_header(value)
|
||||||
|
if len(values) >= trusted:
|
||||||
|
return values[-trusted]
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def parse_list_header(self, value: str) -> List[str]:
|
||||||
|
result = []
|
||||||
|
for item in _parse_list_header(value):
|
||||||
|
if item[:1] == item[-1:] == '"':
|
||||||
|
item = self.unquote_header_value(item[1:-1])
|
||||||
|
result.append(item)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def unquote_header_value(self, value: str, is_filename: bool = False) -> str:
|
||||||
|
r"""Unquotes a header value. (Reversal of :func:`quote_header_value`).
|
||||||
|
This does not use the real unquoting but what browsers are actually
|
||||||
|
using for quoting.
|
||||||
|
.. versionadded:: 0.5
|
||||||
|
:param value: the header value to unquote.
|
||||||
|
:param is_filename: The value represents a filename or path.
|
||||||
|
"""
|
||||||
|
if value and value[0] == value[-1] == '"':
|
||||||
|
# this is not the real unquoting, but fixing this so that the
|
||||||
|
# RFC is met will result in bugs with internet explorer and
|
||||||
|
# probably some other browsers as well. IE for example is
|
||||||
|
# uploading files with "C:\foo\bar.txt" as filename
|
||||||
|
value = value[1:-1]
|
||||||
|
|
||||||
|
# if this is a filename and the starting characters look like
|
||||||
|
# a UNC path, then just return the value without quotes. Using the
|
||||||
|
# replace sequence below on a UNC path has the effect of turning
|
||||||
|
# the leading double slash into a single slash and then
|
||||||
|
# _fix_ie_filename() doesn't work correctly. See #458.
|
||||||
|
if not is_filename or value[:2] != "\\\\":
|
||||||
|
return value.replace("\\\\", "\\").replace('\\"', '"')
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
# host, request.root_path, subdomain, request.scheme, request.method, request.path, request.query_string.decode(),
|
||||||
|
|
@ -11,8 +11,10 @@ env.read_env()
|
||||||
wallets_module = importlib.import_module("lnbits.wallets")
|
wallets_module = importlib.import_module("lnbits.wallets")
|
||||||
wallet_class = getattr(wallets_module, env.str("LNBITS_BACKEND_WALLET_CLASS", default="VoidWallet"))
|
wallet_class = getattr(wallets_module, env.str("LNBITS_BACKEND_WALLET_CLASS", default="VoidWallet"))
|
||||||
|
|
||||||
ENV = env.str("FLASK_ENV", default="production")
|
ENV = env.str("QUART_ENV", default="production")
|
||||||
DEBUG = ENV == "development"
|
DEBUG = env.bool("QUART_DEBUG", default=False) or ENV == "development"
|
||||||
|
HOST = env.str("HOST", default="127.0.0.1")
|
||||||
|
PORT = env.int("PORT", default=5000)
|
||||||
|
|
||||||
LNBITS_PATH = path.dirname(path.realpath(__file__))
|
LNBITS_PATH = path.dirname(path.realpath(__file__))
|
||||||
LNBITS_DATA_FOLDER = env.str("LNBITS_DATA_FOLDER", default=path.join(LNBITS_PATH, "data"))
|
LNBITS_DATA_FOLDER = env.str("LNBITS_DATA_FOLDER", default=path.join(LNBITS_PATH, "data"))
|
||||||
|
|
|
||||||
1
lnbits/static/css/.gitignore
vendored
Normal file
1
lnbits/static/css/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
base.css
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
[v-cloak] {
|
|
||||||
display: none; }
|
|
||||||
|
|
||||||
.bg-lnbits-dark {
|
|
||||||
background-color: #1f2234; }
|
|
||||||
|
|
||||||
body.body--dark, body.body--dark .q-drawer--dark, body.body--dark .q-menu--dark {
|
|
||||||
background: #1f2234; }
|
|
||||||
|
|
||||||
body.body--dark .q-card--dark {
|
|
||||||
background: #333646; }
|
|
||||||
|
|
||||||
body.body--dark .q-table--dark {
|
|
||||||
background: transparent; }
|
|
||||||
|
|
||||||
body.body--light, body.body--light .q-drawer {
|
|
||||||
background: whitesmoke; }
|
|
||||||
|
|
||||||
body.body--dark .q-field--error .text-negative,
|
|
||||||
body.body--dark .q-field--error .q-field__messages {
|
|
||||||
color: yellow !important; }
|
|
||||||
|
|
||||||
.lnbits-drawer__q-list .q-item {
|
|
||||||
padding-top: 5px !important;
|
|
||||||
padding-bottom: 5px !important;
|
|
||||||
border-top-right-radius: 3px;
|
|
||||||
border-bottom-right-radius: 3px; }
|
|
||||||
.lnbits-drawer__q-list .q-item.q-item--active {
|
|
||||||
color: inherit;
|
|
||||||
font-weight: bold; }
|
|
||||||
|
|
||||||
.lnbits__dialog-card {
|
|
||||||
width: 500px; }
|
|
||||||
|
|
||||||
.q-table--dense th:first-child, .q-table--dense td:first-child,
|
|
||||||
.q-table--dense .q-table__bottom {
|
|
||||||
padding-left: 6px !important; }
|
|
||||||
.q-table--dense th:last-child, .q-table--dense td:last-child,
|
|
||||||
.q-table--dense .q-table__bottom {
|
|
||||||
padding-right: 6px !important; }
|
|
||||||
|
|
||||||
a.inherit {
|
|
||||||
color: inherit;
|
|
||||||
text-decoration: none; }
|
|
||||||
|
|
||||||
video {
|
|
||||||
border-radius: 3px; }
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Material Icons';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
src: url(../fonts/material-icons-v50.woff2) format('woff2'); }
|
|
||||||
|
|
||||||
.material-icons {
|
|
||||||
font-family: 'Material Icons';
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
font-size: 24px;
|
|
||||||
line-height: 1;
|
|
||||||
letter-spacing: normal;
|
|
||||||
text-transform: none;
|
|
||||||
display: inline-block;
|
|
||||||
white-space: nowrap;
|
|
||||||
word-wrap: normal;
|
|
||||||
direction: ltr;
|
|
||||||
-moz-font-feature-settings: 'liga';
|
|
||||||
-moz-osx-font-smoothing: grayscale; }
|
|
||||||
|
|
||||||
.text-wrap {
|
|
||||||
word-wrap: break-word;
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mono {
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
389
lnbits/static/vendor/bolt11/decoder.js
vendored
389
lnbits/static/vendor/bolt11/decoder.js
vendored
|
|
@ -4,233 +4,344 @@
|
||||||
//TODO - A reader MUST use the n field to validate the signature instead of performing signature recovery if a valid n field is provided.
|
//TODO - A reader MUST use the n field to validate the signature instead of performing signature recovery if a valid n field is provided.
|
||||||
|
|
||||||
function decode(paymentRequest) {
|
function decode(paymentRequest) {
|
||||||
let input = paymentRequest.toLowerCase();
|
let input = paymentRequest.toLowerCase()
|
||||||
let splitPosition = input.lastIndexOf('1');
|
let splitPosition = input.lastIndexOf('1')
|
||||||
let humanReadablePart = input.substring(0, splitPosition);
|
let humanReadablePart = input.substring(0, splitPosition)
|
||||||
let data = input.substring(splitPosition + 1, input.length - 6);
|
let data = input.substring(splitPosition + 1, input.length - 6)
|
||||||
let checksum = input.substring(input.length - 6, input.length);
|
let checksum = input.substring(input.length - 6, input.length)
|
||||||
if (!verify_checksum(humanReadablePart, bech32ToFiveBitArray(data + checksum))) {
|
if (
|
||||||
throw 'Malformed request: checksum is incorrect'; // A reader MUST fail if the checksum is incorrect.
|
!verify_checksum(humanReadablePart, bech32ToFiveBitArray(data + checksum))
|
||||||
|
) {
|
||||||
|
throw 'Malformed request: checksum is incorrect' // A reader MUST fail if the checksum is incorrect.
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
'human_readable_part': decodeHumanReadablePart(humanReadablePart),
|
human_readable_part: decodeHumanReadablePart(humanReadablePart),
|
||||||
'data': decodeData(data, humanReadablePart),
|
data: decodeData(data, humanReadablePart),
|
||||||
'checksum': checksum
|
checksum: checksum
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function decodeHumanReadablePart(humanReadablePart) {
|
function decodeHumanReadablePart(humanReadablePart) {
|
||||||
let prefixes = ['lnbc', 'lntb', 'lnbcrt', 'lnsb'];
|
let prefixes = ['lnbc', 'lntb', 'lnbcrt', 'lnsb']
|
||||||
let prefix;
|
let prefix
|
||||||
prefixes.forEach(value => {
|
prefixes.forEach(value => {
|
||||||
if (humanReadablePart.substring(0, value.length) === value) {
|
if (humanReadablePart.substring(0, value.length) === value) {
|
||||||
prefix = value;
|
prefix = value
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
if (prefix == null) throw 'Malformed request: unknown prefix'; // A reader MUST fail if it does not understand the prefix.
|
if (prefix == null) throw 'Malformed request: unknown prefix' // A reader MUST fail if it does not understand the prefix.
|
||||||
let amount = decodeAmount(humanReadablePart.substring(prefix.length, humanReadablePart.length));
|
let amount = decodeAmount(
|
||||||
|
humanReadablePart.substring(prefix.length, humanReadablePart.length)
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
'prefix': prefix,
|
prefix: prefix,
|
||||||
'amount': amount
|
amount: amount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function decodeData(data, humanReadablePart) {
|
function decodeData(data, humanReadablePart) {
|
||||||
let date32 = data.substring(0, 7);
|
let date32 = data.substring(0, 7)
|
||||||
let dateEpoch = bech32ToInt(date32);
|
let dateEpoch = bech32ToInt(date32)
|
||||||
let signature = data.substring(data.length - 104, data.length);
|
let signature = data.substring(data.length - 104, data.length)
|
||||||
let tagData = data.substring(7, data.length - 104);
|
let tagData = data.substring(7, data.length - 104)
|
||||||
let decodedTags = decodeTags(tagData);
|
let decodedTags = decodeTags(tagData)
|
||||||
let value = bech32ToFiveBitArray(date32 + tagData);
|
let value = bech32ToFiveBitArray(date32 + tagData)
|
||||||
value = fiveBitArrayTo8BitArray(value, true);
|
value = fiveBitArrayTo8BitArray(value, true)
|
||||||
value = textToHexString(humanReadablePart).concat(byteArrayToHexString(value));
|
value = textToHexString(humanReadablePart).concat(byteArrayToHexString(value))
|
||||||
return {
|
return {
|
||||||
'time_stamp': dateEpoch,
|
time_stamp: dateEpoch,
|
||||||
'tags': decodedTags,
|
tags: decodedTags,
|
||||||
'signature': decodeSignature(signature),
|
signature: decodeSignature(signature),
|
||||||
'signing_data': value
|
signing_data: value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function decodeSignature(signature) {
|
function decodeSignature(signature) {
|
||||||
let data = fiveBitArrayTo8BitArray(bech32ToFiveBitArray(signature));
|
let data = fiveBitArrayTo8BitArray(bech32ToFiveBitArray(signature))
|
||||||
let recoveryFlag = data[data.length - 1];
|
let recoveryFlag = data[data.length - 1]
|
||||||
let r = byteArrayToHexString(data.slice(0, 32));
|
let r = byteArrayToHexString(data.slice(0, 32))
|
||||||
let s = byteArrayToHexString(data.slice(32, data.length - 1));
|
let s = byteArrayToHexString(data.slice(32, data.length - 1))
|
||||||
return {
|
return {
|
||||||
'r': r,
|
r: r,
|
||||||
's': s,
|
s: s,
|
||||||
'recovery_flag': recoveryFlag
|
recovery_flag: recoveryFlag
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function decodeAmount(str) {
|
function decodeAmount(str) {
|
||||||
let multiplier = str.charAt(str.length - 1);
|
let multiplier = str.charAt(str.length - 1)
|
||||||
let amount = str.substring(0, str.length - 1);
|
let amount = str.substring(0, str.length - 1)
|
||||||
if (amount.substring(0, 1) === '0') {
|
if (amount.substring(0, 1) === '0') {
|
||||||
throw 'Malformed request: amount cannot contain leading zeros';
|
throw 'Malformed request: amount cannot contain leading zeros'
|
||||||
}
|
}
|
||||||
amount = Number(amount);
|
amount = Number(amount)
|
||||||
if (amount < 0 || !Number.isInteger(amount)) {
|
if (amount < 0 || !Number.isInteger(amount)) {
|
||||||
throw 'Malformed request: amount must be a positive decimal integer'; // A reader SHOULD fail if amount contains a non-digit
|
throw 'Malformed request: amount must be a positive decimal integer' // A reader SHOULD fail if amount contains a non-digit
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (multiplier) {
|
switch (multiplier) {
|
||||||
case '':
|
case '':
|
||||||
return 'Any amount'; // A reader SHOULD indicate if amount is unspecified
|
return 'Any amount' // A reader SHOULD indicate if amount is unspecified
|
||||||
case 'p':
|
case 'p':
|
||||||
return amount / 10;
|
return amount / 10
|
||||||
case 'n':
|
case 'n':
|
||||||
return amount * 100;
|
return amount * 100
|
||||||
case 'u':
|
case 'u':
|
||||||
return amount * 100000;
|
return amount * 100000
|
||||||
case 'm':
|
case 'm':
|
||||||
return amount * 100000000;
|
return amount * 100000000
|
||||||
default:
|
default:
|
||||||
// A reader SHOULD fail if amount is followed by anything except a defined multiplier.
|
// A reader SHOULD fail if amount is followed by anything except a defined multiplier.
|
||||||
throw 'Malformed request: undefined amount multiplier';
|
throw 'Malformed request: undefined amount multiplier'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function decodeTags(tagData) {
|
function decodeTags(tagData) {
|
||||||
let tags = extractTags(tagData);
|
let tags = extractTags(tagData)
|
||||||
let decodedTags = [];
|
let decodedTags = []
|
||||||
tags.forEach(value => decodedTags.push(decodeTag(value.type, value.length, value.data)));
|
tags.forEach(value =>
|
||||||
return decodedTags;
|
decodedTags.push(decodeTag(value.type, value.length, value.data))
|
||||||
|
)
|
||||||
|
return decodedTags
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractTags(str) {
|
function extractTags(str) {
|
||||||
let tags = [];
|
let tags = []
|
||||||
while (str.length > 0) {
|
while (str.length > 0) {
|
||||||
let type = str.charAt(0);
|
let type = str.charAt(0)
|
||||||
let dataLength = bech32ToInt(str.substring(1, 3));
|
let dataLength = bech32ToInt(str.substring(1, 3))
|
||||||
let data = str.substring(3, dataLength + 3);
|
let data = str.substring(3, dataLength + 3)
|
||||||
tags.push({
|
tags.push({
|
||||||
'type': type,
|
type: type,
|
||||||
'length': dataLength,
|
length: dataLength,
|
||||||
'data': data
|
data: data
|
||||||
});
|
})
|
||||||
str = str.substring(3 + dataLength, str.length);
|
str = str.substring(3 + dataLength, str.length)
|
||||||
}
|
}
|
||||||
return tags;
|
return tags
|
||||||
}
|
}
|
||||||
|
|
||||||
function decodeTag(type, length, data) {
|
function decodeTag(type, length, data) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'p':
|
case 'p':
|
||||||
if (length !== 52) break; // A reader MUST skip over a 'p' field that does not have data_length 52
|
if (length !== 52) break // A reader MUST skip over a 'p' field that does not have data_length 52
|
||||||
return {
|
return {
|
||||||
'type': type,
|
type: type,
|
||||||
'length': length,
|
length: length,
|
||||||
'description': 'payment_hash',
|
description: 'payment_hash',
|
||||||
'value': byteArrayToHexString(fiveBitArrayTo8BitArray(bech32ToFiveBitArray(data)))
|
value: byteArrayToHexString(
|
||||||
};
|
fiveBitArrayTo8BitArray(bech32ToFiveBitArray(data))
|
||||||
|
)
|
||||||
|
}
|
||||||
case 'd':
|
case 'd':
|
||||||
return {
|
return {
|
||||||
'type': type,
|
type: type,
|
||||||
'length': length,
|
length: length,
|
||||||
'description': 'description',
|
description: 'description',
|
||||||
'value': bech32ToUTF8String(data)
|
value: bech32ToUTF8String(data)
|
||||||
};
|
}
|
||||||
case 'n':
|
case 'n':
|
||||||
if (length !== 53) break; // A reader MUST skip over a 'n' field that does not have data_length 53
|
if (length !== 53) break // A reader MUST skip over a 'n' field that does not have data_length 53
|
||||||
return {
|
return {
|
||||||
'type': type,
|
type: type,
|
||||||
'length': length,
|
length: length,
|
||||||
'description': 'payee_public_key',
|
description: 'payee_public_key',
|
||||||
'value': byteArrayToHexString(fiveBitArrayTo8BitArray(bech32ToFiveBitArray(data)))
|
value: byteArrayToHexString(
|
||||||
};
|
fiveBitArrayTo8BitArray(bech32ToFiveBitArray(data))
|
||||||
|
)
|
||||||
|
}
|
||||||
case 'h':
|
case 'h':
|
||||||
if (length !== 52) break; // A reader MUST skip over a 'h' field that does not have data_length 52
|
if (length !== 52) break // A reader MUST skip over a 'h' field that does not have data_length 52
|
||||||
return {
|
return {
|
||||||
'type': type,
|
type: type,
|
||||||
'length': length,
|
length: length,
|
||||||
'description': 'description_hash',
|
description: 'description_hash',
|
||||||
'value': data
|
value: data
|
||||||
};
|
}
|
||||||
case 'x':
|
case 'x':
|
||||||
return {
|
return {
|
||||||
'type': type,
|
type: type,
|
||||||
'length': length,
|
length: length,
|
||||||
'description': 'expiry',
|
description: 'expiry',
|
||||||
'value': bech32ToInt(data)
|
value: bech32ToInt(data)
|
||||||
};
|
}
|
||||||
case 'c':
|
case 'c':
|
||||||
return {
|
return {
|
||||||
'type': type,
|
type: type,
|
||||||
'length': length,
|
length: length,
|
||||||
'description': 'min_final_cltv_expiry',
|
description: 'min_final_cltv_expiry',
|
||||||
'value': bech32ToInt(data)
|
value: bech32ToInt(data)
|
||||||
};
|
}
|
||||||
case 'f':
|
case 'f':
|
||||||
let version = bech32ToFiveBitArray(data.charAt(0))[0];
|
let version = bech32ToFiveBitArray(data.charAt(0))[0]
|
||||||
if (version < 0 || version > 18) break; // a reader MUST skip over an f field with unknown version.
|
if (version < 0 || version > 18) break // a reader MUST skip over an f field with unknown version.
|
||||||
data = data.substring(1, data.length);
|
data = data.substring(1, data.length)
|
||||||
return {
|
return {
|
||||||
'type': type,
|
type: type,
|
||||||
'length': length,
|
length: length,
|
||||||
'description': 'fallback_address',
|
description: 'fallback_address',
|
||||||
'value': {
|
value: {
|
||||||
'version': version,
|
version: version,
|
||||||
'fallback_address': data
|
fallback_address: data
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
case 'r':
|
case 'r':
|
||||||
data = fiveBitArrayTo8BitArray(bech32ToFiveBitArray(data));
|
data = fiveBitArrayTo8BitArray(bech32ToFiveBitArray(data))
|
||||||
let pubkey = data.slice(0, 33);
|
let pubkey = data.slice(0, 33)
|
||||||
let shortChannelId = data.slice(33, 41);
|
let shortChannelId = data.slice(33, 41)
|
||||||
let feeBaseMsat = data.slice(41, 45);
|
let feeBaseMsat = data.slice(41, 45)
|
||||||
let feeProportionalMillionths = data.slice(45, 49);
|
let feeProportionalMillionths = data.slice(45, 49)
|
||||||
let cltvExpiryDelta = data.slice(49, 51);
|
let cltvExpiryDelta = data.slice(49, 51)
|
||||||
return {
|
return {
|
||||||
'type': type,
|
type: type,
|
||||||
'length': length,
|
length: length,
|
||||||
'description': 'routing_information',
|
description: 'routing_information',
|
||||||
'value': {
|
value: {
|
||||||
'public_key': byteArrayToHexString(pubkey),
|
public_key: byteArrayToHexString(pubkey),
|
||||||
'short_channel_id': byteArrayToHexString(shortChannelId),
|
short_channel_id: byteArrayToHexString(shortChannelId),
|
||||||
'fee_base_msat': byteArrayToInt(feeBaseMsat),
|
fee_base_msat: byteArrayToInt(feeBaseMsat),
|
||||||
'fee_proportional_millionths': byteArrayToInt(feeProportionalMillionths),
|
fee_proportional_millionths: byteArrayToInt(
|
||||||
'cltv_expiry_delta': byteArrayToInt(cltvExpiryDelta)
|
feeProportionalMillionths
|
||||||
|
),
|
||||||
|
cltv_expiry_delta: byteArrayToInt(cltvExpiryDelta)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
default:
|
default:
|
||||||
// reader MUST skip over unknown fields
|
// reader MUST skip over unknown fields
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function polymod(values) {
|
function polymod(values) {
|
||||||
let GEN = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];
|
let GEN = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
|
||||||
let chk = 1;
|
let chk = 1
|
||||||
values.forEach((value) => {
|
values.forEach(value => {
|
||||||
let b = (chk >> 25);
|
let b = chk >> 25
|
||||||
chk = (chk & 0x1ffffff) << 5 ^ value;
|
chk = ((chk & 0x1ffffff) << 5) ^ value
|
||||||
for (let i = 0; i < 5; i++) {
|
for (let i = 0; i < 5; i++) {
|
||||||
if (((b >> i) & 1) === 1) {
|
if (((b >> i) & 1) === 1) {
|
||||||
chk ^= GEN[i];
|
chk ^= GEN[i]
|
||||||
} else {
|
} else {
|
||||||
chk ^= 0;
|
chk ^= 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
return chk;
|
return chk
|
||||||
}
|
}
|
||||||
|
|
||||||
function expand(str) {
|
function expand(str) {
|
||||||
let array = [];
|
let array = []
|
||||||
for (let i = 0; i < str.length; i++) {
|
for (let i = 0; i < str.length; i++) {
|
||||||
array.push(str.charCodeAt(i) >> 5);
|
array.push(str.charCodeAt(i) >> 5)
|
||||||
}
|
}
|
||||||
array.push(0);
|
array.push(0)
|
||||||
for (let i = 0; i < str.length; i++) {
|
for (let i = 0; i < str.length; i++) {
|
||||||
array.push(str.charCodeAt(i) & 31);
|
array.push(str.charCodeAt(i) & 31)
|
||||||
}
|
}
|
||||||
return array;
|
return array
|
||||||
}
|
}
|
||||||
|
|
||||||
function verify_checksum(hrp, data) {
|
function verify_checksum(hrp, data) {
|
||||||
hrp = expand(hrp);
|
hrp = expand(hrp)
|
||||||
let all = hrp.concat(data);
|
let all = hrp.concat(data)
|
||||||
let bool = polymod(all);
|
let bool = polymod(all)
|
||||||
return bool === 1;
|
return bool === 1
|
||||||
|
}
|
||||||
|
|
||||||
|
const bech32CharValues = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'
|
||||||
|
|
||||||
|
function byteArrayToInt(byteArray) {
|
||||||
|
let value = 0
|
||||||
|
for (let i = 0; i < byteArray.length; ++i) {
|
||||||
|
value = (value << 8) + byteArray[i]
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
function bech32ToInt(str) {
|
||||||
|
let sum = 0
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
sum = sum * 32
|
||||||
|
sum = sum + bech32CharValues.indexOf(str.charAt(i))
|
||||||
|
}
|
||||||
|
return sum
|
||||||
|
}
|
||||||
|
|
||||||
|
function bech32ToFiveBitArray(str) {
|
||||||
|
let array = []
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
array.push(bech32CharValues.indexOf(str.charAt(i)))
|
||||||
|
}
|
||||||
|
return array
|
||||||
|
}
|
||||||
|
|
||||||
|
function fiveBitArrayTo8BitArray(int5Array, includeOverflow) {
|
||||||
|
let count = 0
|
||||||
|
let buffer = 0
|
||||||
|
let byteArray = []
|
||||||
|
int5Array.forEach(value => {
|
||||||
|
buffer = (buffer << 5) + value
|
||||||
|
count += 5
|
||||||
|
if (count >= 8) {
|
||||||
|
byteArray.push((buffer >> (count - 8)) & 255)
|
||||||
|
count -= 8
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (includeOverflow && count > 0) {
|
||||||
|
byteArray.push((buffer << (8 - count)) & 255)
|
||||||
|
}
|
||||||
|
return byteArray
|
||||||
|
}
|
||||||
|
|
||||||
|
function bech32ToUTF8String(str) {
|
||||||
|
let int5Array = bech32ToFiveBitArray(str)
|
||||||
|
let byteArray = fiveBitArrayTo8BitArray(int5Array)
|
||||||
|
|
||||||
|
let utf8String = ''
|
||||||
|
for (let i = 0; i < byteArray.length; i++) {
|
||||||
|
utf8String += '%' + ('0' + byteArray[i].toString(16)).slice(-2)
|
||||||
|
}
|
||||||
|
return decodeURIComponent(utf8String)
|
||||||
|
}
|
||||||
|
|
||||||
|
function byteArrayToHexString(byteArray) {
|
||||||
|
return Array.prototype.map
|
||||||
|
.call(byteArray, function (byte) {
|
||||||
|
return ('0' + (byte & 0xff).toString(16)).slice(-2)
|
||||||
|
})
|
||||||
|
.join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
function textToHexString(text) {
|
||||||
|
let hexString = ''
|
||||||
|
for (let i = 0; i < text.length; i++) {
|
||||||
|
hexString += text.charCodeAt(i).toString(16)
|
||||||
|
}
|
||||||
|
return hexString
|
||||||
|
}
|
||||||
|
|
||||||
|
function epochToDate(int) {
|
||||||
|
let date = new Date(int * 1000)
|
||||||
|
return date.toUTCString()
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmptyOrSpaces(str) {
|
||||||
|
return str === null || str.match(/^ *$/) !== null
|
||||||
|
}
|
||||||
|
|
||||||
|
function toFixed(x) {
|
||||||
|
if (Math.abs(x) < 1.0) {
|
||||||
|
var e = parseInt(x.toString().split('e-')[1])
|
||||||
|
if (e) {
|
||||||
|
x *= Math.pow(10, e - 1)
|
||||||
|
x = '0.' + new Array(e).join('0') + x.toString().substring(2)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var e = parseInt(x.toString().split('+')[1])
|
||||||
|
if (e > 20) {
|
||||||
|
e -= 20
|
||||||
|
x /= Math.pow(10, e)
|
||||||
|
x += new Array(e + 1).join('0')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return x
|
||||||
}
|
}
|
||||||
96
lnbits/static/vendor/bolt11/utils.js
vendored
96
lnbits/static/vendor/bolt11/utils.js
vendored
|
|
@ -1,96 +0,0 @@
|
||||||
const bech32CharValues = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
|
|
||||||
|
|
||||||
function byteArrayToInt(byteArray) {
|
|
||||||
let value = 0;
|
|
||||||
for (let i = 0; i < byteArray.length; ++i) {
|
|
||||||
value = (value << 8) + byteArray[i];
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function bech32ToInt(str) {
|
|
||||||
let sum = 0;
|
|
||||||
for (let i = 0; i < str.length; i++) {
|
|
||||||
sum = sum * 32;
|
|
||||||
sum = sum + bech32CharValues.indexOf(str.charAt(i));
|
|
||||||
}
|
|
||||||
return sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
function bech32ToFiveBitArray(str) {
|
|
||||||
let array = [];
|
|
||||||
for (let i = 0; i < str.length; i++) {
|
|
||||||
array.push(bech32CharValues.indexOf(str.charAt(i)));
|
|
||||||
}
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
function fiveBitArrayTo8BitArray(int5Array, includeOverflow) {
|
|
||||||
let count = 0;
|
|
||||||
let buffer = 0;
|
|
||||||
let byteArray = [];
|
|
||||||
int5Array.forEach((value) => {
|
|
||||||
buffer = (buffer << 5) + value;
|
|
||||||
count += 5;
|
|
||||||
if (count >= 8) {
|
|
||||||
byteArray.push(buffer >> (count - 8) & 255);
|
|
||||||
count -= 8;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (includeOverflow && count > 0) {
|
|
||||||
byteArray.push(buffer << (8 - count) & 255);
|
|
||||||
}
|
|
||||||
return byteArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
function bech32ToUTF8String(str) {
|
|
||||||
let int5Array = bech32ToFiveBitArray(str);
|
|
||||||
let byteArray = fiveBitArrayTo8BitArray(int5Array);
|
|
||||||
|
|
||||||
let utf8String = '';
|
|
||||||
for (let i = 0; i < byteArray.length; i++) {
|
|
||||||
utf8String += '%' + ('0' + byteArray[i].toString(16)).slice(-2);
|
|
||||||
}
|
|
||||||
return decodeURIComponent(utf8String);
|
|
||||||
}
|
|
||||||
|
|
||||||
function byteArrayToHexString(byteArray) {
|
|
||||||
return Array.prototype.map.call(byteArray, function (byte) {
|
|
||||||
return ('0' + (byte & 0xFF).toString(16)).slice(-2);
|
|
||||||
}).join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
function textToHexString(text) {
|
|
||||||
let hexString = '';
|
|
||||||
for (let i = 0; i < text.length; i++) {
|
|
||||||
hexString += text.charCodeAt(i).toString(16);
|
|
||||||
}
|
|
||||||
return hexString;
|
|
||||||
}
|
|
||||||
|
|
||||||
function epochToDate(int) {
|
|
||||||
let date = new Date(int * 1000);
|
|
||||||
return date.toUTCString();
|
|
||||||
}
|
|
||||||
|
|
||||||
function isEmptyOrSpaces(str){
|
|
||||||
return str === null || str.match(/^ *$/) !== null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function toFixed(x) {
|
|
||||||
if (Math.abs(x) < 1.0) {
|
|
||||||
var e = parseInt(x.toString().split('e-')[1]);
|
|
||||||
if (e) {
|
|
||||||
x *= Math.pow(10,e-1);
|
|
||||||
x = '0.' + (new Array(e)).join('0') + x.toString().substring(2);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var e = parseInt(x.toString().split('+')[1]);
|
|
||||||
if (e > 20) {
|
|
||||||
e -= 20;
|
|
||||||
x /= Math.pow(10,e);
|
|
||||||
x += (new Array(e+1)).join('0');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
|
|
@ -2,14 +2,12 @@
|
||||||
|
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<link
|
{% for url in g.VENDORED_CSS %}
|
||||||
rel="stylesheet"
|
<link rel="stylesheet" type="text/css" href="{{ url }}" />
|
||||||
type="text/css"
|
{% endfor %}
|
||||||
href="{{ url_for('static', filename='vendor/quasar@1.13.2/quasar.min.css') }}"
|
<!---->
|
||||||
/>
|
<link rel="stylesheet" type="text/css" href="/static/css/base.css" />
|
||||||
{% assets 'base_css' %}
|
{% block styles %}{% endblock %}
|
||||||
<link rel="stylesheet" type="text/css" href="{{ ASSET_URL }}" />
|
|
||||||
{% endassets %} {% block styles %}{% endblock %}
|
|
||||||
<title>
|
<title>
|
||||||
{% block title %} {% if SITE_TITLE != 'LNbits' %}{{ SITE_TITLE }}{% else
|
{% block title %} {% if SITE_TITLE != 'LNbits' %}{{ SITE_TITLE }}{% else
|
||||||
%}LNbits{% endif %} {% endblock %}
|
%}LNbits{% endif %} {% endblock %}
|
||||||
|
|
@ -108,21 +106,14 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</q-layout>
|
</q-layout>
|
||||||
|
|
||||||
{% block vue_templates %}{% endblock %} {% if DEBUG %}
|
{% block vue_templates %}{% endblock %}
|
||||||
<script src="{{ url_for('static', filename='vendor/vue@2.6.12/vue.js') }}"></script>
|
<!---->
|
||||||
<script src="{{ url_for('static', filename='vendor/vuex@3.5.1/vuex.js') }}"></script>
|
{% for url in g.VENDORED_JS %}
|
||||||
<script src="{{ url_for('static', filename='vendor/vue-router@3.4.3/vue-router.js') }}"></script>
|
<script src="{{ url }}"></script>
|
||||||
<script src="{{ url_for('static', filename='vendor/quasar@1.13.2/quasar.umd.js') }}"></script>
|
{% endfor %}
|
||||||
{% else %} {% assets output='__bundle__/vue.js',
|
<!---->
|
||||||
'vendor/quasar@1.13.2/quasar.ie.polyfills.umd.min.js',
|
<script src="/static/js/base.js"></script>
|
||||||
'vendor/vue@2.6.12/vue.min.js', 'vendor/vue-router@3.4.3/vue-router.min.js',
|
<script src="/static/js/components.js"></script>
|
||||||
'vendor/vuex@3.5.1/vuex.min.js', 'vendor/quasar@1.13.2/quasar.umd.min.js' %}
|
{% block scripts %}{% endblock %}
|
||||||
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
|
|
||||||
{% endassets %} {% endif %} {% assets filters='rjsmin',
|
|
||||||
output='__bundle__/base.js', 'vendor/axios@0.20.0/axios.min.js',
|
|
||||||
'vendor/underscore@1.10.2/underscore.min.js', 'js/base.js',
|
|
||||||
'js/components.js' %}
|
|
||||||
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
|
|
||||||
{% endassets %} {% block scripts %}{% endblock %}
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,9 @@
|
||||||
|
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<link
|
{% for url in g.VENDORED_CSS %}
|
||||||
rel="stylesheet"
|
<link rel="stylesheet" type="text/css" href="{{ url }}" />
|
||||||
href="//fonts.googleapis.com/css?family=Material+Icons"
|
{% endfor %}
|
||||||
type="text/css"
|
|
||||||
/>
|
|
||||||
<style>
|
<style>
|
||||||
@page {
|
@page {
|
||||||
size: A4 portrait;
|
size: A4 portrait;
|
||||||
|
|
@ -38,13 +36,10 @@
|
||||||
</q-page-container>
|
</q-page-container>
|
||||||
</q-layout>
|
</q-layout>
|
||||||
|
|
||||||
{% if DEBUG %}
|
{% for url in g.VENDORED_JS %}
|
||||||
<script src="{{ url_for('static', filename='vendor/vue@2.6.12/vue.js') }}"></script>
|
<script src="{{ url }}"></script>
|
||||||
<script src="{{ url_for('static', filename='vendor/quasar@1.13.2/quasar.umd.js') }}"></script>
|
{% endfor %}
|
||||||
{% else %} {% assets output='__bundle__/vue-print.js',
|
<!---->
|
||||||
'vendor/quasar@1.13.2/quasar.ie.polyfills.umd.min.js',
|
{% block scripts %}{% endblock %}
|
||||||
'vendor/vue@2.6.12/vue.min.js', 'vendor/quasar@1.13.2/quasar.umd.min.js' %}
|
|
||||||
<script type="text/javascript" src="{{ ASSET_URL }}"></script>
|
|
||||||
{% endassets %} {% endif %} {% block scripts %}{% endblock %}
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,38 @@
|
||||||
bech32==1.2.0; python_version >= '3.5'
|
aiofiles==0.5.0
|
||||||
|
bech32==1.2.0
|
||||||
bitstring==3.1.7
|
bitstring==3.1.7
|
||||||
|
blinker==1.4
|
||||||
brotli==1.0.9
|
brotli==1.0.9
|
||||||
cerberus==1.3.2
|
cerberus==1.3.2
|
||||||
certifi==2020.6.20
|
certifi==2020.6.20
|
||||||
chardet==3.0.4
|
chardet==3.0.4
|
||||||
click==7.1.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
|
click==7.1.2
|
||||||
ecdsa==0.16.0
|
ecdsa==0.16.0
|
||||||
environs==8.0.0
|
environs==8.0.0
|
||||||
flask-assets==2.0
|
h11==0.10.0
|
||||||
flask-compress==1.5.0
|
h2==3.2.0
|
||||||
flask-cors==3.0.9
|
hpack==3.0.0
|
||||||
flask-talisman==0.7.0
|
hypercorn==0.10.2
|
||||||
flask==1.1.2
|
hyperframe==5.2.0
|
||||||
idna==2.10; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
|
idna==2.10
|
||||||
itsdangerous==1.1.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
|
itsdangerous==1.1.0
|
||||||
jinja2==2.11.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
|
jinja2==2.11.2
|
||||||
lnurl==0.3.5
|
lnurl==0.3.5
|
||||||
markupsafe==1.1.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
|
markupsafe==1.1.1
|
||||||
marshmallow==3.7.1; python_version >= '3.5'
|
marshmallow==3.8.0
|
||||||
pydantic==1.6.1; python_version >= '3.6'
|
priority==1.3.0
|
||||||
|
pydantic==1.6.1
|
||||||
pyscss==1.3.7
|
pyscss==1.3.7
|
||||||
python-dotenv==0.14.0
|
python-dotenv==0.14.0
|
||||||
|
quart==0.13.1
|
||||||
|
quart-compress==0.2.1
|
||||||
|
quart-cors==0.3.0
|
||||||
requests==2.24.0
|
requests==2.24.0
|
||||||
|
secure==0.2.1
|
||||||
shortuuid==1.0.1
|
shortuuid==1.0.1
|
||||||
six==1.15.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
|
six==1.15.0
|
||||||
typing-extensions==3.7.4.3; python_version < '3.8'
|
toml==0.10.1
|
||||||
urllib3==1.25.10; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'
|
typing-extensions==3.7.4.3
|
||||||
webassets==2.0
|
urllib3==1.25.10
|
||||||
werkzeug==1.0.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
|
werkzeug==1.0.1
|
||||||
|
wsproto==0.15.0
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,10 @@ from lnbits.app import create_app
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def client():
|
@pytest.mark.asyncio
|
||||||
|
async def client():
|
||||||
app = create_app()
|
app = create_app()
|
||||||
app.config["TESTING"] = True
|
app.config["TESTING"] = True
|
||||||
|
|
||||||
with app.test_client() as client:
|
async with app.test_client() as client:
|
||||||
yield client
|
yield client
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
def test_homepage(client):
|
import pytest
|
||||||
r = client.get("/")
|
|
||||||
assert b"Add a new wallet" in r.data
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_homepage(client):
|
||||||
|
r = await client.get("/")
|
||||||
|
assert b"Add a new wallet" in await r.get_data()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue